import 'package:cyberhybridhub/data/remote/spelling_words_remote_store.dart'; import 'package:cyberhybridhub/screens/spelling_list_screen.dart'; import 'package:cyberhybridhub/theme/app_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; class MockSpellingWordsRemoteStore extends SpellingWordsRemoteStore { MockSpellingWordsRemoteStore({ this.words = const [], this.fetchWordsResult, this.deleteWordResult, this.deleteWordError, }); List words; List? fetchWordsResult; List? deleteWordResult; Object? deleteWordError; final List deletedWords = []; int fetchCount = 0; int deleteCount = 0; @override Future> fetchWords() async { fetchCount++; if (fetchWordsResult != null) { return fetchWordsResult!; } return words; } @override Future> deleteWord(String word) async { deleteCount++; deletedWords.add(word); if (deleteWordError != null) { throw deleteWordError!; } if (deleteWordResult != null) { return deleteWordResult!; } final List newWords = List.from(words)..remove(word); return newWords; } } void main() { testWidgets('lists words loaded from remote store', (WidgetTester tester) async { final mockStore = MockSpellingWordsRemoteStore( words: ['apple', 'banana', 'cherry'], ); await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), home: SpellingListScreen(api: mockStore), ), ); // Shows loading first expect(find.byType(CircularProgressIndicator), findsOneWidget); await tester.pumpAndSettle(); // Verify loading is gone and words are displayed expect(find.byType(CircularProgressIndicator), findsNothing); expect(find.text('apple'), findsOneWidget); expect(find.text('banana'), findsOneWidget); expect(find.text('cherry'), findsOneWidget); expect(find.byKey(const Key('spelling-word-delete-apple')), findsOneWidget); expect(find.byKey(const Key('spelling-word-delete-banana')), findsOneWidget); expect(find.byKey(const Key('spelling-word-delete-cherry')), findsOneWidget); }); testWidgets('delete button cancel does not call delete API and leaves list intact', (WidgetTester tester) async { final mockStore = MockSpellingWordsRemoteStore( words: ['apple', 'banana'], ); await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), home: SpellingListScreen(api: mockStore), ), ); await tester.pumpAndSettle(); // Tap delete button for apple await tester.tap(find.byKey(const Key('spelling-word-delete-apple'))); await tester.pumpAndSettle(); // Verify confirmation dialog is visible expect(find.byKey(const Key('spelling-word-delete-confirm-dialog')), findsOneWidget); expect(find.text('Delete spelling word?'), findsOneWidget); expect(find.textContaining('This deletes "apple"'), findsOneWidget); // Tap Cancel await tester.tap(find.text('Cancel')); await tester.pumpAndSettle(); // Dialog should be gone expect(find.byKey(const Key('spelling-word-delete-confirm-dialog')), findsNothing); // Store is not called expect(mockStore.deleteCount, 0); expect(mockStore.deletedWords, isEmpty); // Words still exist expect(find.text('apple'), findsOneWidget); expect(find.text('banana'), findsOneWidget); }); testWidgets('delete button confirm calls delete API, updates UI list, and shows snackbar', (WidgetTester tester) async { final mockStore = MockSpellingWordsRemoteStore( words: ['apple', 'banana'], ); await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), home: SpellingListScreen(api: mockStore), ), ); await tester.pumpAndSettle(); // Tap delete button for apple await tester.tap(find.byKey(const Key('spelling-word-delete-apple'))); await tester.pumpAndSettle(); // Tap Delete inside dialog await tester.tap(find.byKey(const Key('spelling-word-delete-confirm-button'))); await tester.pump(); // Start async action // Verify mockStore is called expect(mockStore.deleteCount, 1); expect(mockStore.deletedWords, ['apple']); await tester.pumpAndSettle(); // Dialog should be gone expect(find.byKey(const Key('spelling-word-delete-confirm-dialog')), findsNothing); // List is updated expect(find.text('apple'), findsNothing); expect(find.text('banana'), findsOneWidget); // Snackbar is shown expect(find.text('Deleted "apple"'), findsOneWidget); }); testWidgets('delete failure shows error snackbar and retains word in list', (WidgetTester tester) async { final mockStore = MockSpellingWordsRemoteStore( words: ['apple', 'banana'], deleteWordError: Exception('Failed to delete'), ); await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), home: SpellingListScreen(api: mockStore), ), ); await tester.pumpAndSettle(); // Tap delete button for apple await tester.tap(find.byKey(const Key('spelling-word-delete-apple'))); await tester.pumpAndSettle(); // Tap Delete inside dialog await tester.tap(find.byKey(const Key('spelling-word-delete-confirm-button'))); await tester.pumpAndSettle(); // Dialog is gone expect(find.byKey(const Key('spelling-word-delete-confirm-dialog')), findsNothing); // Store was called expect(mockStore.deleteCount, 1); // List is NOT updated, apple is still there expect(find.text('apple'), findsOneWidget); expect(find.text('banana'), findsOneWidget); // Error snackbar is shown expect(find.text('Could not delete word.'), findsOneWidget); }); }