The flow editor is a Ch∆In Studio sub-component (chain product), not a F∆I-vendor package. Rename the package, entry library file, and all package: imports. flutter analyze: clean. Signed-off-by: flemming-it <sf@flemming.it>
112 lines
3.6 KiB
Dart
112 lines
3.6 KiB
Dart
// Widget tests for FlowNode — the canvas card.
|
|
//
|
|
// Cover: dynamic width grows with long labels, two-column
|
|
// body renders both input + output labels, port tooltips are
|
|
// attached when supplied, height accounts for max(inputs,
|
|
// outputs).
|
|
|
|
import 'package:chain_studio_flow_editor/src/model/flow_graph.dart';
|
|
import 'package:chain_studio_flow_editor/src/widgets/flow_node.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
Widget pump(Widget child) =>
|
|
MaterialApp(home: Scaffold(body: Center(child: child)));
|
|
|
|
group('NodeGeometry', () {
|
|
test('widthFor grows beyond minimum when labels are long', () {
|
|
// Bare minimum case.
|
|
expect(
|
|
NodeGeometry.widthFor(maxInputChars: 4, maxOutputChars: 4),
|
|
NodeGeometry.width,
|
|
);
|
|
// Long labels push past the minimum.
|
|
final wide = NodeGeometry.widthFor(
|
|
maxInputChars: 13, // "system_prompt"
|
|
maxOutputChars: 14, // "model_endpoint"
|
|
);
|
|
expect(wide, greaterThan(NodeGeometry.width));
|
|
});
|
|
|
|
test('heightFor uses the taller of inputs / outputs', () {
|
|
// 3 inputs + 5 outputs → 5-row body.
|
|
final h35 = NodeGeometry.heightFor(3, 5);
|
|
// 5 inputs + 3 outputs → also 5-row body, same height.
|
|
final h53 = NodeGeometry.heightFor(5, 3);
|
|
expect(h35, h53);
|
|
});
|
|
|
|
test('inputPortY/outputPortY align row-for-row', () {
|
|
// Both sides walk the same row geometry from the body's
|
|
// top; that's how the labels stay visually aligned with
|
|
// their dots.
|
|
expect(NodeGeometry.inputPortY(0), NodeGeometry.outputPortY(0));
|
|
expect(NodeGeometry.inputPortY(2), NodeGeometry.outputPortY(2));
|
|
});
|
|
});
|
|
|
|
group('FlowNode rendering', () {
|
|
testWidgets('renders both input and output labels', (tester) async {
|
|
await tester.pumpWidget(
|
|
pump(
|
|
const FlowNode(
|
|
id: 'sum',
|
|
title: 'sum',
|
|
subtitle: 'llm.chat@^0',
|
|
inputPortLabels: ['prompt', 'endpoint'],
|
|
outputPortLabels: ['response', 'model_endpoint'],
|
|
),
|
|
),
|
|
);
|
|
expect(find.text('prompt'), findsOneWidget);
|
|
expect(find.text('endpoint'), findsOneWidget);
|
|
expect(find.text('response'), findsOneWidget);
|
|
expect(find.text('model_endpoint'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('honors explicit width parameter', (tester) async {
|
|
await tester.pumpWidget(
|
|
pump(
|
|
const FlowNode(
|
|
id: 'x',
|
|
title: 'x',
|
|
inputPortLabels: ['a_very_long_input_label'],
|
|
outputPortLabels: ['another_long_output_label'],
|
|
width: 320,
|
|
),
|
|
),
|
|
);
|
|
final size = tester.getSize(find.byType(FlowNode));
|
|
expect(size.width, 320);
|
|
});
|
|
|
|
testWidgets('attaches Tooltip when portTooltips is non-empty', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
pump(
|
|
const FlowNode(
|
|
id: 'sum',
|
|
title: 'sum',
|
|
inputPortLabels: ['prompt'],
|
|
portTooltips: {'prompt': 'The user-facing prompt text.'},
|
|
),
|
|
),
|
|
);
|
|
expect(find.byType(Tooltip), findsAtLeastNWidgets(1));
|
|
});
|
|
});
|
|
|
|
group('kindForStep', () {
|
|
test('classifies system.approval steps as approval kind', () {
|
|
final approval = FlowStep(id: 'r', use: 'system.approval@^0');
|
|
expect(kindForStep(approval), NodeVisualKind.approval);
|
|
});
|
|
|
|
test('classifies generic capabilities as module kind', () {
|
|
final mod = FlowStep(id: 's', use: 'llm.chat@^0');
|
|
expect(kindForStep(mod), NodeVisualKind.module);
|
|
});
|
|
});
|
|
}
|