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>
146 lines
4.1 KiB
Dart
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:')));
|
|
});
|
|
}
|