Files
Tracker/test/scripture_provider_test.dart

276 lines
9.6 KiB
Dart

import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:christian_period_tracker/models/scripture.dart';
import 'package:christian_period_tracker/models/cycle_entry.dart';
import 'package:christian_period_tracker/models/user_profile.dart';
import 'package:christian_period_tracker/providers/scripture_provider.dart';
import 'package:christian_period_tracker/providers/user_provider.dart';
import 'package:hive_flutter/hive_flutter.dart';
// Fake ScriptureDatabase implementation for testing
class FakeScriptureDatabase implements ScriptureDatabase {
final int Function(String phase) getScriptureCountForPhaseFn;
final Scripture? Function(String phase, int index)
getScriptureForPhaseByIndexFn;
final Scripture? Function(String phase)? getRandomScriptureForPhaseFn;
FakeScriptureDatabase({
required this.getScriptureCountForPhaseFn,
required this.getScriptureForPhaseByIndexFn,
this.getRandomScriptureForPhaseFn,
});
@override
int getScriptureCountForPhase(String phase) =>
getScriptureCountForPhaseFn(phase);
@override
Scripture? getScriptureForPhaseByIndex(String phase, int index) =>
getScriptureForPhaseByIndexFn(phase, index);
@override
Scripture? getRandomScriptureForPhase(String phase) =>
getRandomScriptureForPhaseFn != null
? getRandomScriptureForPhaseFn!(phase)
: null;
// Unimplemented methods (not used by ScriptureNotifier)
@override
List<Scripture> getAllScriptures() => throw UnimplementedError();
@override
Scripture getHusbandScripture() => throw UnimplementedError();
@override
Future<void> loadScriptures() =>
Future.value(); // Can be mocked to do nothing
@override
Scripture? getRecommendedScripture(CycleEntry entry) =>
throw UnimplementedError();
@override
Scripture getScriptureForPhase(String phase) => throw UnimplementedError();
}
void main() {
group('ScriptureNotifier', () {
late ProviderContainer container;
late String testPath;
setUpAll(() async {
testPath = '${Directory.current.path}/test_hive_temp_scripture_provider';
final Directory tempDir = Directory(testPath);
if (!await tempDir.exists()) {
await tempDir.create(recursive: true);
}
Hive.init(testPath);
Hive.registerAdapter(UserProfileAdapter());
Hive.registerAdapter(RelationshipStatusAdapter());
Hive.registerAdapter(FertilityGoalAdapter());
Hive.registerAdapter(BibleTranslationAdapter());
Hive.registerAdapter(UserRoleAdapter());
await Hive.openBox<UserProfile>('user_profile');
});
tearDownAll(() async {
await Hive.close();
await Directory(testPath).delete(recursive: true);
});
final testScripture1 = Scripture(
verses: {BibleTranslation.esv: "Verse 1"},
reference: "Ref 1",
applicablePhases: ['menstrual'],
);
final testScripture2 = Scripture(
verses: {BibleTranslation.esv: "Verse 2"},
reference: "Ref 2",
applicablePhases: ['menstrual'],
);
final testScripture3 = Scripture(
verses: {BibleTranslation.esv: "Verse 3"},
reference: "Ref 3",
applicablePhases: ['menstrual'],
);
tearDown(() {
container.dispose();
});
test('initializes with correct scripture for phase', () async {
final fakeDb = FakeScriptureDatabase(
getScriptureCountForPhaseFn: (phase) => 3,
getScriptureForPhaseByIndexFn: (phase, index) => testScripture1,
);
container = ProviderContainer(
overrides: [
scriptureDatabaseProvider.overrideWithValue(fakeDb),
userProfileProvider.overrideWith(
(ref) => UserProfileNotifier(),
),
],
);
final notifier = container.read(scriptureProvider.notifier);
notifier.initializeScripture(CyclePhase.menstrual);
final state = container.read(scriptureProvider);
expect(state.currentScripture, testScripture1);
expect(state.currentPhase, CyclePhase.menstrual);
expect(state.maxIndex, 3);
// currentIndex will depend on dayOfYear % 3, which is hard to predict
// So we'll just check it's within bounds.
expect(state.currentIndex, isNonNegative);
expect(state.currentIndex, lessThan(3));
});
test('getNextScripture cycles correctly', () async {
final scriptures = [testScripture1, testScripture2, testScripture3];
final fakeDb = FakeScriptureDatabase(
getScriptureCountForPhaseFn: (phase) => scriptures.length,
getScriptureForPhaseByIndexFn: (phase, index) => scriptures[index],
);
container = ProviderContainer(
overrides: [
scriptureDatabaseProvider.overrideWithValue(fakeDb),
userProfileProvider.overrideWith(
(ref) => UserProfileNotifier(),
),
],
);
final notifier = container.read(scriptureProvider.notifier);
// Force initial state to 0 for predictable cycling
notifier.initializeScripture(CyclePhase.menstrual);
// Override currentIndex to 0 for predictable test
container.read(scriptureProvider.notifier).state =
container.read(scriptureProvider).copyWith(currentIndex: 0);
// First next
notifier.getNextScripture();
expect(
container.read(scriptureProvider).currentScripture, testScripture2);
expect(container.read(scriptureProvider).currentIndex, 1);
// Second next
notifier.getNextScripture();
expect(
container.read(scriptureProvider).currentScripture, testScripture3);
expect(container.read(scriptureProvider).currentIndex, 2);
// Wrap around
notifier.getNextScripture();
expect(
container.read(scriptureProvider).currentScripture, testScripture1);
expect(container.read(scriptureProvider).currentIndex, 0);
});
test('getPreviousScripture cycles correctly', () async {
final scriptures = [testScripture1, testScripture2, testScripture3];
final fakeDb = FakeScriptureDatabase(
getScriptureCountForPhaseFn: (phase) => scriptures.length,
getScriptureForPhaseByIndexFn: (phase, index) => scriptures[index],
);
container = ProviderContainer(
overrides: [
scriptureDatabaseProvider.overrideWithValue(fakeDb),
userProfileProvider.overrideWith(
(ref) => UserProfileNotifier(),
),
],
);
final notifier = container.read(scriptureProvider.notifier);
// Force initial state to 0 for predictable cycling
notifier.initializeScripture(CyclePhase.menstrual);
// Override currentIndex to 0 for predictable test
container.read(scriptureProvider.notifier).state =
container.read(scriptureProvider).copyWith(currentIndex: 0);
// First previous (wraps around)
notifier.getPreviousScripture();
expect(
container.read(scriptureProvider).currentScripture, testScripture3);
expect(container.read(scriptureProvider).currentIndex, 2);
// Second previous
notifier.getPreviousScripture();
expect(
container.read(scriptureProvider).currentScripture, testScripture2);
expect(container.read(scriptureProvider).currentIndex, 1);
// Third previous
notifier.getPreviousScripture();
expect(
container.read(scriptureProvider).currentScripture, testScripture1);
expect(container.read(scriptureProvider).currentIndex, 0);
});
test('getRandomScripture updates to a valid scripture', () async {
final scriptures = [testScripture1, testScripture2, testScripture3];
final fakeDb = FakeScriptureDatabase(
getScriptureCountForPhaseFn: (phase) => scriptures.length,
getScriptureForPhaseByIndexFn: (phase, index) => scriptures[
index % scriptures.length], // Ensure it always returns a valid one
);
container = ProviderContainer(
overrides: [
scriptureDatabaseProvider.overrideWithValue(fakeDb),
userProfileProvider.overrideWith(
(ref) => UserProfileNotifier(),
),
],
);
final notifier = container.read(scriptureProvider.notifier);
notifier.initializeScripture(CyclePhase.menstrual);
// Perform a random selection
notifier.getRandomScripture();
final state = container.read(scriptureProvider);
expect(state.currentScripture, isNotNull);
expect(state.currentScripture,
isIn(scriptures)); // Ensure it's one of the valid scriptures
expect(state.currentIndex, isNonNegative);
expect(state.currentIndex, lessThan(scriptures.length));
});
test('does not change state if maxIndex is 0', () async {
final fakeDb = FakeScriptureDatabase(
getScriptureCountForPhaseFn: (phase) => 0,
getScriptureForPhaseByIndexFn: (phase, index) => null,
);
container = ProviderContainer(
overrides: [
scriptureDatabaseProvider.overrideWithValue(fakeDb),
userProfileProvider.overrideWith(
(ref) => UserProfileNotifier(),
),
],
);
final notifier = container.read(scriptureProvider.notifier);
notifier.initializeScripture(CyclePhase.menstrual);
final initialState = container.read(scriptureProvider);
expect(initialState.currentScripture, isNull);
expect(initialState.maxIndex, 0);
notifier.getNextScripture();
notifier.getPreviousScripture();
notifier.getRandomScripture();
// State should remain unchanged
expect(container.read(scriptureProvider), initialState);
});
});
}