chain-studio-flow-editor/test/flow_list_project_test.dart
flemming-it c4a39a3779 feat: project separation in the flow list (0.25.0)
Filter the file list by the host's active project — flows without
a project: key count as 'general', display-only, the file is never
rewritten. New flows are stamped with the active project's key
(general and all-projects stay unstamped). Adds a toolbarTrailing
slot so the host can mount its workspace switcher in the editor's
single toolbar, and a flowsDir injection point so widget tests run
against a temp dir instead of the operator's ~/.chain flows.
Covered by pure filter-semantics tests plus hermetic widget tests
for filtering, the project-empty state, and new-flow stamping.

Signed-off-by: flemming-it <sf@flemming.it>
2026-07-22 13:56:29 +02:00

146 lines
4.1 KiB
Dart

// Widget-level proof of the flow list's project separation:
// switching the active workspace filters the list, and a new flow
// is stamped with the active project's key. Hermetic — the editor
// gets a temp flows dir injected and never touches ~/.chain.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:chain_studio_flow_editor/chain_studio_flow_editor.dart';
Future<void> _pumpEditor(
WidgetTester tester, {
required String flowsDir,
required String activeProject,
}) async {
// Build + let the real file IO of the listing complete inside
// runAsync (the fake-async test zone never drives real IO).
await tester.runAsync(() async {
await tester.pumpWidget(
MaterialApp(
home: FlowEditorPage(
flowsDir: flowsDir,
activeProject: activeProject,
),
),
);
await Future<void>.delayed(const Duration(milliseconds: 100));
});
await tester.pump();
await tester.pump();
}
void main() {
late Directory tmp;
setUp(() {
tmp = Directory.systemTemp.createTempSync('chain_editor_flows_test');
File('${tmp.path}/alpha-report.yaml').writeAsStringSync(
'name: alpha-report\nproject: client-a\nsteps: []\n',
);
File('${tmp.path}/plain.yaml').writeAsStringSync(
'name: plain\nsteps: []\n',
);
});
tearDown(() {
tmp.deleteSync(recursive: true);
});
testWidgets('active project filters the flow list', (tester) async {
await _pumpEditor(
tester,
flowsDir: tmp.path,
activeProject: 'client-a',
);
expect(find.text('alpha-report'), findsOneWidget);
expect(find.text('plain'), findsNothing);
});
testWidgets('general shows keyless flows only', (tester) async {
await _pumpEditor(
tester,
flowsDir: tmp.path,
activeProject: 'general',
);
expect(find.text('plain'), findsOneWidget);
expect(find.text('alpha-report'), findsNothing);
});
testWidgets('all projects shows everything', (tester) async {
await _pumpEditor(tester, flowsDir: tmp.path, activeProject: '');
expect(find.text('plain'), findsOneWidget);
expect(find.text('alpha-report'), findsOneWidget);
});
testWidgets('empty project state names the project and the way out', (
tester,
) async {
await _pumpEditor(
tester,
flowsDir: tmp.path,
activeProject: 'client-b',
);
expect(find.textContaining('client-b'), findsOneWidget);
expect(find.text('alpha-report'), findsNothing);
expect(find.text('plain'), findsNothing);
});
testWidgets('new flow is stamped with the active project key', (
tester,
) async {
await _pumpEditor(
tester,
flowsDir: tmp.path,
activeProject: 'client-a',
);
await tester.tap(find.text('New flow'));
await tester.pumpAndSettle();
await tester.enterText(
find.descendant(
of: find.byType(AlertDialog),
matching: find.byType(TextField),
),
'fresh-flow',
);
await tester.tap(find.text('Create'));
await tester.runAsync(() => Future<void>.delayed(
const Duration(milliseconds: 50),
));
await tester.pumpAndSettle();
final created = File('${tmp.path}/fresh-flow.yaml');
expect(created.existsSync(), isTrue);
expect(created.readAsStringSync(), contains('project: client-a'));
});
testWidgets('new flow under general stays unstamped (no key = general)', (
tester,
) async {
await _pumpEditor(
tester,
flowsDir: tmp.path,
activeProject: 'general',
);
await tester.tap(find.text('New flow'));
await tester.pumpAndSettle();
await tester.enterText(
find.descendant(
of: find.byType(AlertDialog),
matching: find.byType(TextField),
),
'general-flow',
);
await tester.tap(find.text('Create'));
await tester.runAsync(() => Future<void>.delayed(
const Duration(milliseconds: 50),
));
await tester.pumpAndSettle();
final created = File('${tmp.path}/general-flow.yaml');
expect(created.existsSync(), isTrue);
expect(created.readAsStringSync(), isNot(contains('project:')));
});
}