// 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 _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.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.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.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:'))); }); }