import 'dart:convert'; import 'package:bible_tracker/data/bible.dart'; import 'package:bible_tracker/models/tracker.dart'; import 'package:bible_tracker/screens/book_screen.dart'; import 'package:bible_tracker/screens/home_screen.dart'; import 'package:bible_tracker/screens/tracker_screen.dart'; import 'package:bible_tracker/services/last_location.dart'; import 'package:bible_tracker/services/tracker_service.dart'; import 'package:bible_tracker/services/tracker_storage.dart'; import 'package:bible_tracker/widgets/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; Future _pumpApp({ Map? initialPrefs, LastLocation? pending, }) async { SharedPreferences.setMockInitialValues(initialPrefs ?? {}); final prefs = await SharedPreferences.getInstance(); return ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), pendingRestoreProvider.overrideWithValue(pending), ], child: MaterialApp( theme: AppTheme.light(), home: const HomeScreen(), navigatorObservers: [ LastLocationObserver( (LastLocation? loc) => LastLocationService(prefs).save(loc), ), ], ), ); } String _seedTrackerJson(Tracker tracker) => jsonEncode(>[tracker.toJson()]); void main() { testWidgets('shows empty state when no trackers exist', (tester) async { await tester.pumpWidget(await _pumpApp()); await tester.pumpAndSettle(); expect(find.text('Start your first tracker'), findsOneWidget); expect(find.text('New Tracker'), findsOneWidget); }); testWidgets('creating a tracker shows it on the list', (tester) async { await tester.pumpWidget(await _pumpApp()); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(HomeScreen)); final ProviderContainer container = ProviderScope.containerOf(context); await container .read(trackerServiceProvider.notifier) .create(name: 'My Plan', color: const Color(0xFF3F51B5)); await tester.pumpAndSettle(); expect(find.text('My Plan'), findsOneWidget); expect(find.text('0%'), findsOneWidget); }); testWidgets( 'restores tracker + book on launch when pending location matches', (tester) async { final tracker = Tracker( id: 'seed-1', name: 'Persisted', colorValue: 0xFF3F51B5, startDate: DateTime(2024), ); const int bookIndex = 42; // John await tester.pumpWidget( await _pumpApp( initialPrefs: { 'bible_tracker.trackers.v1': _seedTrackerJson(tracker), }, pending: LastLocation(trackerId: tracker.id, bookIndex: bookIndex), ), ); await tester.pumpAndSettle(); // TrackerScreen sits underneath BookScreen and is offstage. expect( find.byType(TrackerScreen, skipOffstage: false), findsOneWidget, ); expect(find.byType(BookScreen), findsOneWidget); expect(find.text(Bible.books[bookIndex].name), findsWidgets); }, ); testWidgets('restores tracker only when no book index is saved', ( tester, ) async { final tracker = Tracker( id: 'seed-2', name: 'Resumed', colorValue: 0xFF3F51B5, startDate: DateTime(2024), ); await tester.pumpWidget( await _pumpApp( initialPrefs: { 'bible_tracker.trackers.v1': _seedTrackerJson(tracker), }, pending: LastLocation(trackerId: tracker.id), ), ); await tester.pumpAndSettle(); expect(find.byType(TrackerScreen), findsOneWidget); expect(find.byType(BookScreen), findsNothing); expect(find.text('Resumed'), findsWidgets); }); testWidgets('skips restore when the saved tracker no longer exists', ( tester, ) async { await tester.pumpWidget( await _pumpApp(pending: const LastLocation(trackerId: 'missing')), ); await tester.pumpAndSettle(); expect(find.byType(TrackerScreen), findsNothing); expect(find.byType(BookScreen), findsNothing); expect(find.text('Start your first tracker'), findsOneWidget); }); testWidgets('navigating into a book persists the location via the observer', ( tester, ) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); await tester.pumpWidget( ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), ], child: MaterialApp( theme: AppTheme.light(), home: const HomeScreen(), navigatorObservers: [ LastLocationObserver( (LastLocation? loc) => LastLocationService(prefs).save(loc), ), ], ), ), ); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(HomeScreen)); final ProviderContainer container = ProviderScope.containerOf(context); final tracker = await container .read(trackerServiceProvider.notifier) .create(name: 'Plan', color: const Color(0xFF3F51B5)); await tester.pumpAndSettle(); await tester.tap(find.text('Plan')); await tester.pumpAndSettle(); expect( LastLocationService(prefs).load(), LastLocation(trackerId: tracker.id), ); await tester.tap(find.text('Genesis')); await tester.pumpAndSettle(); expect( LastLocationService(prefs).load(), LastLocation(trackerId: tracker.id, bookIndex: 0), ); // Pop back to the tracker screen — the persisted location should // shrink back to "tracker only". final NavigatorState navigator = tester.state( find.byType(Navigator).first, ); navigator.pop(); await tester.pumpAndSettle(); expect( LastLocationService(prefs).load(), LastLocation(trackerId: tracker.id), ); // Pop all the way to home — location should clear. navigator.pop(); await tester.pumpAndSettle(); expect(LastLocationService(prefs).load(), isNull); }); }