test(editor): controller tests for graph<->text round-trip

Four behaviour tests guarding the controller's contract with
the rest of the editor:

 - openFlow seeds the graph + clears the dirty flag against
   the freshly-captured baseline.
 - graph edits re-emit YAML into the shared CodeController
   so the text tab stays in lockstep.
 - graph edits mark the buffer dirty against the baseline;
   markSaved snaps it back to clean.
 - selectStep is idempotent: re-selecting the same step
   doesn't notify listeners (cheap rebuild guard for the
   properties panel).

11/11 tests pass.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-01 00:53:35 +02:00
parent 870cbc29f7
commit e7cd9ca13c

View file

@ -0,0 +1,93 @@
// Behaviour tests for FlowEditorController the bridge
// between the YAML buffer + the graph + the layout sidecar.
// The model tests cover parsing correctness; these cover
// that the controller wires text -> graph reparsing,
// graph -> YAML emission, selection bookkeeping, and
// dirty/saved transitions correctly.
import 'package:fai_studio_flow_editor/src/editor_controller.dart';
import 'package:fai_studio_flow_editor/src/model/flow_graph.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('FlowEditorController', () {
test('openFlow seeds the graph + clears the dirty flag', () {
final c = FlowEditorController();
addTearDown(c.dispose);
c.openFlow('hello', '''
name: hello
inputs:
name: text
steps:
- id: greet
use: debug.echo@^0
with:
message: \$inputs.name
outputs:
greeting: \$greet.echoed
''');
expect(c.activeName, 'hello');
expect(c.graph.steps, hasLength(1));
expect(c.graph.steps.single.id, 'greet');
expect(c.isDirty, isFalse);
});
test('graph edit re-emits YAML into the text buffer', () {
final c = FlowEditorController();
addTearDown(c.dispose);
c.openFlow('demo', '''
name: demo
steps:
- id: a
use: debug.echo@^0
outputs: {}
''');
final initialText = c.codeController.fullText;
c.applyGraphEdit(c.graph.withStepAdded(
const FlowStep(id: 'b', use: 'debug.echo@^0'),
));
expect(c.codeController.fullText, isNot(initialText));
expect(c.codeController.fullText, contains('id: b'));
expect(c.graph.steps, hasLength(2));
});
test('graph edit marks the buffer dirty against the baseline', () {
final c = FlowEditorController();
addTearDown(c.dispose);
c.openFlow('demo', '''
steps:
- id: a
use: debug.echo@^0
outputs: {}
''');
expect(c.isDirty, isFalse);
c.applyGraphEdit(c.graph.withStepAdded(
const FlowStep(id: 'b', use: 'debug.echo@^0'),
));
expect(c.isDirty, isTrue);
c.markSaved();
expect(c.isDirty, isFalse);
});
test('selectStep notifies listeners only on change', () {
final c = FlowEditorController();
addTearDown(c.dispose);
c.openFlow('demo', '''
steps:
- id: only
use: debug.echo@^0
outputs: {}
''');
var calls = 0;
c.addListener(() => calls++);
c.selectStep('only');
final after = calls;
// Selecting the same thing again must NOT bump the
// listener count the controller short-circuits.
c.selectStep('only');
expect(calls, after);
c.selectStep(null);
expect(calls, after + 1);
});
});
}