feat: Implement husband features and fix iOS Safari web startup

Implement initial features for husband's companion app, including mock data
service and husband notes screen. Refactor scripture and cycle services
for improved stability and testability. Address iOS Safari web app
startup issue by removing deprecated initialization.

- Implemented MockDataService and HusbandNotesScreen.
- Converted _DashboardTab and DevotionalScreen to StatefulWidgets for robust
  scripture provider initialization.
- Refactored CycleService to use immutable CycleInfo class, reducing UI rebuilds.
- Removed deprecated window.flutterConfiguration from index.html, resolving
  Flutter web app startup failure on iOS Safari.
- Updated and fixed related tests.
This commit is contained in:
2025-12-26 22:40:52 -06:00
parent 464692ce56
commit b4b2bfe749
47 changed files with 240110 additions and 2578 deletions

155
test/scripture_test.dart Normal file
View File

@@ -0,0 +1,155 @@
import 'dart:convert';
import 'dart:io';
import 'package:christian_period_tracker/models/scripture.dart';
import 'package:christian_period_tracker/models/user_profile.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:path_provider_linux/path_provider_linux.dart';
import 'package:mockito/mockito.dart';
import 'mocks.mocks.dart'; // Generated mock file
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('Scripture', () {
test('fromJson creates a valid Scripture object', () {
final json = {
"verses": {
"esv": "Test verse",
},
"reference": "Test 1:1",
};
final scripture = Scripture.fromJson(json);
expect(scripture.reference, "Test 1:1");
expect(scripture.getVerse(BibleTranslation.esv), "Test verse");
});
});
group('ScriptureDatabase', () {
late ScriptureDatabase database;
late String testPath;
setUpAll(() async {
// Initialize path_provider_platform_interface for testing
// This is a common pattern for mocking platform-specific plugins in tests.
PathProviderPlatform.instance = _MockPathProviderPlatform();
testPath = Directory.current.path + '/test_hive_temp';
// Ensure the directory exists
final Directory tempDir = Directory(testPath);
if (!await tempDir.exists()) {
await tempDir.create(recursive: true);
}
// Create and configure the mock BibleXmlParser
final mockBibleXmlParser = MockBibleXmlParser();
when(mockBibleXmlParser.getVerseFromAsset(any, any)).thenAnswer((invocation) async {
final String reference = invocation.positionalArguments[1];
// Return a mock verse based on the reference for testing purposes
return 'Mock Verse for $reference';
});
// Mock the rootBundle for JSON assets (XML assets are now handled by mockBibleXmlParser)
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMessageHandler(
'flutter/assets',
(ByteData? message) async {
final String key = utf8.decode(message!.buffer.asUint8List());
if (key == 'assets/scriptures.json' || key == 'assets/scriptures_optimized.json') {
final json = {
"menstrual": [
{
"verses": {"esv": "Menstrual verse"},
"reference": "Menstrual 1:1",
"applicablePhases": ["menstrual"]
}
],
"follicular": [
{
"verses": {"esv": "Follicular verse"},
"reference": "Follicular 1:1",
"applicablePhases": ["follicular"]
}
],
"ovulation": [
{
"verses": {"esv": "Ovulation verse"},
"reference": "Ovulation 1:1",
"applicablePhases": ["ovulation"]
}
],
"luteal": [
{
"verses": {"esv": "Luteal verse"},
"reference": "Luteal 1:1",
"applicablePhases": ["luteal"]
}
],
"husband": [
{
"verses": {"esv": "Husband verse"},
"reference": "Husband 1:1",
"applicablePhases": ["husband"]
}
],
"womanhood": [
{
"verses": {"esv": "Womanhood verse"},
"reference": "Womanhood 1:1",
"applicableContexts": ["womanhood"]
}
],
"contextual": {
"pain": [
{
"verses": {"esv": "Pain verse"},
"reference": "Pain 1:1",
"applicableContexts": ["pain"]
}
]
}
};
return utf8.encode(jsonEncode(json)).buffer.asByteData();
}
return null; // Return null for other asset requests not explicitly mocked
},
);
Hive.init(testPath);
Hive.registerAdapter(ScriptureAdapter()); // Register your adapter
Hive.registerAdapter(BibleTranslationAdapter()); // Register BibleTranslationAdapter
database = ScriptureDatabase(bibleXmlParser: mockBibleXmlParser); // Instantiate with mock
await database.loadScriptures();
});
tearDownAll(() async {
await Hive.close();
await Directory(testPath).delete(recursive: true);
});
test('getScriptureForPhase returns the correct scripture', () {
final scripture = database.getScriptureForPhase('menstrual');
expect(scripture.reference, "Menstrual 1:1");
});
test('getHusbandScripture returns a husband scripture', () {
final scripture = database.getHusbandScripture();
expect(scripture.applicablePhases, contains('husband'));
});
});
}
class _MockPathProviderPlatform extends PathProviderPlatform {
@override
Future<String?> getApplicationSupportPath() async {
return Directory.current.path + '/test_hive_temp';
}
@override
Future<String?> getApplicationDocumentsPath() async {
return Directory.current.path + '/test_hive_temp';
}
}