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>
This commit is contained in:
parent
ab97e5e834
commit
c4a39a3779
6 changed files with 302 additions and 9 deletions
146
test/flow_list_project_test.dart
Normal file
146
test/flow_list_project_test.dart
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// 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:')));
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue