bible-tracker/test/screens/home_screen_test.dart
2026-04-20 15:13:58 -04:00

199 lines
6.3 KiB
Dart

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<Widget> _pumpApp({
Map<String, Object>? initialPrefs,
LastLocation? pending,
}) async {
SharedPreferences.setMockInitialValues(initialPrefs ?? <String, Object>{});
final prefs = await SharedPreferences.getInstance();
return ProviderScope(
overrides: <Override>[
sharedPreferencesProvider.overrideWithValue(prefs),
pendingRestoreProvider.overrideWithValue(pending),
],
child: MaterialApp(
theme: AppTheme.light(),
home: const HomeScreen(),
navigatorObservers: <NavigatorObserver>[
LastLocationObserver(
(LastLocation? loc) => LastLocationService(prefs).save(loc),
),
],
),
);
}
String _seedTrackerJson(Tracker tracker) =>
jsonEncode(<Map<String, dynamic>>[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: <String, Object>{
'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: <String, Object>{
'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(<String, Object>{});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: <Override>[
sharedPreferencesProvider.overrideWithValue(prefs),
],
child: MaterialApp(
theme: AppTheme.light(),
home: const HomeScreen(),
navigatorObservers: <NavigatorObserver>[
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<NavigatorState>(
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);
});
}