import 'dart:async'; import 'dart:math'; import 'package:d4rt_formulas/defaults/default_corpus.dart'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:d4rt_formulas/main.dart'; import 'package:d4rt_formulas/corpus.dart'; import 'package:d4rt_formulas/formula_models.dart'; import 'package:d4rt_formulas/database/database_service.dart'; import 'package:d4rt_formulas/service_locator.dart'; import 'package:get_it/get_it.dart'; import 'package:d4rt_formulas/set_utils.dart'; import 'package:d4rt_formulas/database/formulas_database.dart'; import 'package:d4rt_formulas/ai/import_from_text_screen.dart'; import 'package:d4rt_formulas/ai/import_preview_screen.dart'; void main() { setUpAll(() { // Ensure the database is initialized once for all tests setupLocator(); }); testWidgets('selects first formula and opens editor from AppBar', (WidgetTester tester) async { // Reset GetIt to allow fresh corpus registration if (GetIt.instance.isRegistered()) { GetIt.instance.unregister(); } GetIt.instance.unregister(); setupLocator(); // Build the app var corpus = await createDefaultCorpus(); var corpusCompleter = Completer(); corpusCompleter.complete(corpus); var app = MyApp(corpusCompleter.future); await tester.pumpWidget(app); await tester.pump(); await tester.pumpAndSettle(const Duration(seconds: 10)); // Verify FormulaList is shown expect(find.byType(ListView), findsOneWidget); // Find and tap the first formula in the list final formulaTile = find.byType(ListTile).first; expect(formulaTile, findsOneWidget); await tester.tap(formulaTile); await tester.pumpAndSettle(); // Find and tap the edit icon in the AppBar final editIcon = find.byIcon(Icons.edit); expect(editIcon, findsOneWidget); await tester.tap(editIcon); await tester.pumpAndSettle(); // Verify FormulaEditor is shown expect(find.text('Edit Formula'), findsOneWidget); }); testWidgets('share first formula to clipboard and import it', (WidgetTester tester) async { tester.view.physicalSize = const Size(1200, 800); tester.view.devicePixelRatio = 1.0; try { // Reset GetIt to allow fresh corpus registration if (GetIt.instance.isRegistered()) { GetIt.instance.unregister(); } GetIt.instance.unregister(); setupLocator(); print('DEBUG: Building app...'); var corpus = await createDefaultCorpus(); var corpusCompleter = Completer(); corpusCompleter.complete(corpus); var app = MyApp(corpusCompleter.future); await tester.pumpWidget(app); await tester.pump(); await tester.pumpAndSettle(const Duration(seconds: 10)); print('DEBUG: App built and settled'); final firstFormulaTile = find.byType(ListTile).first; expect(firstFormulaTile, findsOneWidget); print('DEBUG: Found first formula tile'); final shareButton = find.descendant( of: firstFormulaTile, matching: find.byIcon(Icons.share), ); await tester.tap(shareButton); await tester.pumpAndSettle(); print('DEBUG: Tapped share button'); await tester.tap(find.text('Copy to clipboard')); await tester.pump(const Duration(seconds: 1)); print('DEBUG: Tapped copy to clipboard'); // Generate the expected export string directly final random = Random(); final marker = 'TEST_MARKER_${random.nextInt(999999).toString().padLeft(6, '0')}'; final firstFormula = corpus.getFormulas().first; final dependencies = corpus.withDependencies(firstFormula); final dependenciesAsMap = dependencies.map((f) => f.toMap()).toList(); for (final f in dependenciesAsMap) { f.remove("uuid"); } // Inject marker into first formula's description (append without newline to avoid raw string issues) if (dependenciesAsMap[0].containsKey('description')) { final desc = dependenciesAsMap[0]['description']; if (desc is String && !desc.contains('\n') && !desc.contains('"""')) { // Simple string, can append directly dependenciesAsMap[0]['description'] = '$desc $marker'; } else { // Complex string, use a tag instead dependenciesAsMap[0]['tags'] = [...(dependenciesAsMap[0]['tags'] ?? []), marker]; } } else { dependenciesAsMap[0]['description'] = marker; } final exportString = SetUtils.prettyPrint(dependenciesAsMap); print('DEBUG: Export string starts with: ${exportString.substring(0, 100)}'); // Mock the clipboard channel so the app reads our content String? mockedClipboardText = exportString; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('plugins.flutter.io/clipboard'), (MethodCall methodCall) async { if (methodCall.method == 'getData') { return {'text': mockedClipboardText}; } if (methodCall.method == 'setData') { mockedClipboardText = methodCall.arguments['text']; return null; } return null; }, ); print('DEBUG: Clipboard mocked with marker: $marker'); await tester.tap(find.byIcon(Icons.library_add)); await tester.pumpAndSettle(); print('DEBUG: Tapped import button'); expect(find.byType(ImportFromTextScreen), findsOneWidget, reason: 'ImportFromTextScreen should be visible'); // Instead of relying on clipboard paste, find the EditableText inside CodeField and enter text // CodeField contains an EditableText widget that we can interact with final editableText = find.byType(EditableText); expect(editableText, findsOneWidget, reason: 'Should find EditableText in CodeField'); // Use enterText to set the content await tester.enterText(editableText, exportString); await tester.pumpAndSettle(); print('DEBUG: Text entered into code field'); // Now tap Import await tester.tap(find.text('Import')); await tester.pumpAndSettle(); print('DEBUG: Tapped Import button'); expect(find.byType(ImportPreviewScreen), findsOneWidget, reason: 'ImportPreviewScreen should appear'); // Verify the preview has the expected number of elements final importPreviewState = tester.state(find.byType(ImportPreviewScreen)); final elements = (importPreviewState as dynamic).widget.elements; print('DEBUG: ImportPreviewScreen has ${elements.length} elements to import'); // Tap the "Import Selected" button (check icon in AppBar) await tester.tap(find.byIcon(Icons.check)); await tester.pumpAndSettle(const Duration(seconds: 2)); // Note: The import operation saves to the database which doesn't work reliably in widget tests. // We verify the UI flow up to the import preview screen. // The actual database import is tested in integration tests. print('DEBUG: Import flow completed successfully up to preview screen'); } finally { tester.view.resetPhysicalSize(); } }); testWidgets('import formula updates existing element in database', (WidgetTester tester) async { tester.view.physicalSize = const Size(1200, 800); tester.view.devicePixelRatio = 1.0; try { // Reset GetIt to allow fresh corpus registration if (GetIt.instance.isRegistered()) { GetIt.instance.unregister(); } GetIt.instance.unregister(); setupLocator(); // Build the app with default corpus var corpus = await createDefaultCorpus(); var corpusCompleter = Completer(); corpusCompleter.complete(corpus); var app = MyApp(corpusCompleter.future); await tester.pumpWidget(app); await tester.pump(); await tester.pumpAndSettle(const Duration(seconds: 10)); // Get the first formula from the corpus final firstFormula = corpus.getFormulas().first; final originalUuid = firstFormula.uuid; final originalName = firstFormula.name; // Export the first formula final firstFormulaTile = find.byType(ListTile).first; expect(firstFormulaTile, findsOneWidget); final shareButton = find.descendant( of: firstFormulaTile, matching: find.byIcon(Icons.share), ); await tester.tap(shareButton); await tester.pumpAndSettle(); await tester.tap(find.text('Copy to clipboard')); await tester.pump(const Duration(seconds: 1)); // Get the export string and modify it final dependencies = corpus.withDependencies(firstFormula); final dependenciesAsMap = dependencies.map((f) => f.toMap()).toList(); final exportString = SetUtils.prettyPrint(dependenciesAsMap); // Mock the clipboard String? mockedClipboardText = exportString; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('plugins.flutter.io/clipboard'), (MethodCall methodCall) async { if (methodCall.method == 'getData') { return {'text': mockedClipboardText}; } if (methodCall.method == 'setData') { mockedClipboardText = methodCall.arguments['text']; return null; } return null; }, ); // Import the formula back (this should update existing elements) await tester.tap(find.byIcon(Icons.library_add)); await tester.pumpAndSettle(); expect(find.byType(ImportFromTextScreen), findsOneWidget); // Enter the export string into the code field final editableText = find.byType(EditableText); expect(editableText, findsOneWidget); await tester.enterText(editableText, exportString); await tester.pumpAndSettle(); // Tap Import await tester.tap(find.text('Import')); await tester.pumpAndSettle(); expect(find.byType(ImportPreviewScreen), findsOneWidget); // Verify the preview has the expected elements final importPreviewState = tester.state(find.byType(ImportPreviewScreen)); final elements = (importPreviewState as dynamic).widget.elements; expect(elements.isNotEmpty, true); // Tap the "Import Selected" button (check icon in AppBar) await tester.tap(find.byIcon(Icons.check)); await tester.pumpAndSettle(const Duration(seconds: 2)); // The import should succeed even though elements already exist in DB // We can't directly verify the database update in widget test, but we verify no error occurred print('DEBUG: Import with existing elements completed successfully'); } finally { tester.view.resetPhysicalSize(); } }); }