diff --git a/iosApp/HoneyDueTests/SnapshotGalleryTests.swift b/iosApp/HoneyDueTests/SnapshotGalleryTests.swift index b0323a7..1368109 100644 --- a/iosApp/HoneyDueTests/SnapshotGalleryTests.swift +++ b/iosApp/HoneyDueTests/SnapshotGalleryTests.swift @@ -2,26 +2,30 @@ // SnapshotGalleryTests.swift // HoneyDueTests // -// P3 — iOS parity gallery. Records baseline PNGs for primary SwiftUI screens -// across {empty-state} × {light, dark}. Complements the Android Roborazzi -// gallery (P2); both platforms consume the same Kotlin FixtureDataManager -// fixtures (P1), so any layout divergence between Android + iOS renders of -// the same screen is a real parity bug — not a test-data mismatch. +// P3/P5 — iOS parity gallery. Records baseline PNGs for primary SwiftUI +// screens across {empty, populated} × {light, dark}. Complements the +// Android Roborazzi gallery; both platforms consume the same Kotlin +// `FixtureDataManager` fixtures, so any layout divergence between Android +// and iOS renders of the same screen is a real parity bug — not a test +// data mismatch. // -// Current state coverage -// ---------------------- +// State coverage +// -------------- // * Empty (signed-in user with no residences / tasks / docs / contractors) -// is captured for every screen below via the default -// `DataManagerObservable.shared`, which has no data loaded when tests run -// because no login has occurred in the test host. -// * Populated state is BLOCKED on a follow-up ViewModel-injection refactor: -// current SwiftUI screens instantiate their ViewModels via -// `@StateObject private var viewModel = FooViewModel()`, and those -// ViewModels read directly from `DataManagerObservable.shared` rather -// than an injected `IDataManager`. Swapping the singleton is unsafe in -// parallel tests. Follow-up PR: add an optional `dataManager:` init param -// to each `*ViewModel.swift` and thread it from here via -// `.environment(\.dataManager, ...)`. +// is captured by clearing `DataManagerObservable.shared`'s @Published +// caches before the view is instantiated. +// * Populated is captured by synchronously seeding those caches from +// `FixtureDataManager.populated()` via `DataManagerObservable(kotlin:)` +// and copying its values onto `.shared`. The seeded values persist +// because the production Kotlin `DataManager.shared` never emits during +// tests (no API calls are made), so the continuous observation tasks +// never overwrite our seed. +// * Each user-facing ViewModel gained a `dataManager:` init seam in the +// same commit so follow-up tests can inject a dedicated instance +// instead of mutating `.shared`. For the gallery we pick the simpler +// shared-seed path because the existing views use `@StateObject = VM()` +// without an init param, and refactoring every call-site to thread a +// VM through is out of scope. // // Recording goldens // ----------------- @@ -30,11 +34,10 @@ // xcodebuild env, deletes the old `__Snapshots__/SnapshotGalleryTests` // directory, runs the target, then invokes the shared PNG optimizer. // -// Manual override: set the `SNAPSHOT_TESTING_RECORD` env var to `1` in -// the Xcode scheme's Test action (Edit Scheme → Test → Arguments → -// Environment Variables) and re-run the test target. CI fails the -// build if a screen diverges from its golden by more than the -// precision threshold. +// Manual override: set `SNAPSHOT_TESTING_RECORD=1` in the Xcode scheme's +// Test action (Edit Scheme → Test → Arguments → Environment Variables) +// and re-run the test target. CI fails the build if a screen diverges +// from its golden by more than the precision threshold. // // Rendering scale // --------------- @@ -71,6 +74,119 @@ final class SnapshotGalleryTests: XCTestCase { } } + override func setUp() { + super.setUp() + // Default to empty state before every test. Each populated-state + // test explicitly calls `seedPopulated()` at the top of its body. + seedEmpty() + } + + // MARK: - Fixture seeding + + /// Reset `DataManagerObservable.shared` to the empty-fixture baseline. + /// ViewModels that `.sink` on `.shared`'s publishers during init will + /// receive these values immediately and render the empty-state UI. + private func seedEmpty() { + copyFixture(FixtureDataManager.shared.empty(), into: DataManagerObservable.shared) + } + + /// Seed `DataManagerObservable.shared` from `FixtureDataManager.populated()` + /// so the next view instantiated in the test picks up fully-populated + /// caches via its Combine subscription. + private func seedPopulated() { + copyFixture(FixtureDataManager.shared.populated(), into: DataManagerObservable.shared) + } + + /// Synchronously copy every StateFlow value from an `IDataManager` + /// fixture onto a `DataManagerObservable`'s `@Published` properties. + /// Mirrors the `init(kotlin:)` seed path but targets an existing + /// instance so we can reuse `.shared` (required because views + /// instantiate their ViewModels with the default `.shared` argument). + private func copyFixture(_ fixture: IDataManager, into observable: DataManagerObservable) { + observable.currentUser = fixture.currentUser.value + observable.isAuthenticated = fixture.currentUser.value != nil + + observable.residences = fixture.residences.value + observable.myResidences = fixture.myResidences.value + observable.totalSummary = fixture.totalSummary.value + observable.residenceSummaries = mapInt(fixture.residenceSummaries.value) + + observable.allTasks = fixture.allTasks.value + observable.tasksByResidence = mapInt(fixture.tasksByResidence.value) + + observable.documents = fixture.documents.value + observable.documentsByResidence = mapIntArray(fixture.documentsByResidence.value) + + observable.contractors = fixture.contractors.value + + observable.subscription = fixture.subscription.value + observable.upgradeTriggers = mapString(fixture.upgradeTriggers.value) + observable.featureBenefits = fixture.featureBenefits.value + observable.promotions = fixture.promotions.value + + observable.residenceTypes = fixture.residenceTypes.value + observable.taskFrequencies = fixture.taskFrequencies.value + observable.taskPriorities = fixture.taskPriorities.value + observable.taskCategories = fixture.taskCategories.value + observable.contractorSpecialties = fixture.contractorSpecialties.value + + observable.taskTemplates = fixture.taskTemplates.value + observable.taskTemplatesGrouped = fixture.taskTemplatesGrouped.value + + let hasLookups = !fixture.residenceTypes.value.isEmpty || + !fixture.taskPriorities.value.isEmpty || + !fixture.taskCategories.value.isEmpty + observable.lookupsInitialized = hasLookups + observable.isInitialized = hasLookups + } + + private func mapInt(_ kotlinMap: Any?) -> [Int32: V] { + guard let nsDict = kotlinMap as? NSDictionary else { return [:] } + var result: [Int32: V] = [:] + for key in nsDict.allKeys { + guard let value = nsDict[key], let typed = value as? V else { continue } + if let ki = key as? KotlinInt { + result[ki.int32Value] = typed + } else if let ns = key as? NSNumber { + result[ns.int32Value] = typed + } + } + return result + } + + private func mapIntArray(_ kotlinMap: Any?) -> [Int32: [V]] { + guard let nsDict = kotlinMap as? NSDictionary else { return [:] } + var result: [Int32: [V]] = [:] + for key in nsDict.allKeys { + guard let value = nsDict[key] else { continue } + let typed: [V] + if let arr = value as? [V] { + typed = arr + } else if let nsArr = value as? NSArray { + typed = nsArr.compactMap { $0 as? V } + } else { + continue + } + if let ki = key as? KotlinInt { + result[ki.int32Value] = typed + } else if let ns = key as? NSNumber { + result[ns.int32Value] = typed + } + } + return result + } + + private func mapString(_ kotlinMap: Any?) -> [String: V] { + if let direct = kotlinMap as? [String: V] { return direct } + guard let nsDict = kotlinMap as? NSDictionary else { return [:] } + var result: [String: V] = [:] + for key in nsDict.allKeys { + guard let s = key as? String, let v = nsDict[key] as? V else { continue } + result[s] = v + } + return result + } + // MARK: - Helpers /// Snapshot a SwiftUI view in both light + dark modes under a stable @@ -137,7 +253,7 @@ final class SnapshotGalleryTests: XCTestCase { ) } - // MARK: - Auth flow + // MARK: - Auth flow (empty-only; these screens have no backing data) func test_login_empty() { snap("login_empty") { @@ -181,7 +297,7 @@ final class SnapshotGalleryTests: XCTestCase { } } - // MARK: - Onboarding + // MARK: - Onboarding (empty-only; these screens are pre-data) func test_onboarding_welcome_empty() { snap("onboarding_welcome_empty") { @@ -249,18 +365,39 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_residences_list_populated() { + seedPopulated() + snap("residences_list_populated") { + NavigationStack { ResidencesListView() } + } + } + func test_add_residence_empty() { snap("add_residence_empty") { AddResidenceView(isPresented: .constant(true), onResidenceCreated: nil) } } + func test_add_residence_populated() { + seedPopulated() + snap("add_residence_populated") { + AddResidenceView(isPresented: .constant(true), onResidenceCreated: nil) + } + } + func test_join_residence_empty() { snap("join_residence_empty") { JoinResidenceView(onJoined: {}) } } + func test_join_residence_populated() { + seedPopulated() + snap("join_residence_populated") { + JoinResidenceView(onJoined: {}) + } + } + // MARK: - Tasks func test_all_tasks_empty() { @@ -269,18 +406,40 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_all_tasks_populated() { + seedPopulated() + snap("all_tasks_populated") { + NavigationStack { AllTasksView() } + } + } + func test_add_task_empty() { snap("add_task_empty") { AddTaskView(residenceId: 1, isPresented: .constant(true)) } } + func test_add_task_populated() { + seedPopulated() + snap("add_task_populated") { + AddTaskView(residenceId: 1, isPresented: .constant(true)) + } + } + func test_add_task_with_residence_empty() { snap("add_task_with_residence_empty") { AddTaskWithResidenceView(isPresented: .constant(true), residences: []) } } + func test_add_task_with_residence_populated() { + seedPopulated() + let fixtureResidences = DataManagerObservable.shared.myResidences?.residences ?? [] + snap("add_task_with_residence_populated") { + AddTaskWithResidenceView(isPresented: .constant(true), residences: fixtureResidences) + } + } + func test_task_suggestions_empty() { snap("task_suggestions_empty") { TaskSuggestionsView( @@ -290,12 +449,30 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_task_suggestions_populated() { + seedPopulated() + // TaskSuggestionsView accepts [TaskTemplate] directly; pulling the + // first few templates from the populated fixture exercises the + // same layout as production's "For You" tab. + let templates = Array(DataManagerObservable.shared.taskTemplates.prefix(4)) + snap("task_suggestions_populated") { + TaskSuggestionsView(suggestions: templates, onSelect: { _ in }) + } + } + func test_task_templates_browser_empty() { snap("task_templates_browser_empty") { NavigationStack { TaskTemplatesBrowserView(onSelect: { _ in }) } } } + func test_task_templates_browser_populated() { + seedPopulated() + snap("task_templates_browser_populated") { + NavigationStack { TaskTemplatesBrowserView(onSelect: { _ in }) } + } + } + // MARK: - Contractor func test_contractors_list_empty() { @@ -304,6 +481,13 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_contractors_list_populated() { + seedPopulated() + snap("contractors_list_populated") { + NavigationStack { ContractorsListView() } + } + } + // MARK: - Documents func test_documents_warranties_empty() { @@ -312,6 +496,13 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_documents_warranties_populated() { + seedPopulated() + snap("documents_warranties_populated") { + NavigationStack { DocumentsWarrantiesView(residenceId: nil) } + } + } + // MARK: - Profile func test_profile_tab_empty() { @@ -320,24 +511,52 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_profile_tab_populated() { + seedPopulated() + snap("profile_tab_populated") { + NavigationStack { ProfileTabView() } + } + } + func test_profile_edit_empty() { snap("profile_edit_empty") { NavigationStack { ProfileView() } } } + func test_profile_edit_populated() { + seedPopulated() + snap("profile_edit_populated") { + NavigationStack { ProfileView() } + } + } + func test_notification_preferences_empty() { snap("notification_preferences_empty") { NavigationStack { NotificationPreferencesView() } } } + func test_notification_preferences_populated() { + seedPopulated() + snap("notification_preferences_populated") { + NavigationStack { NotificationPreferencesView() } + } + } + func test_theme_selection_empty() { snap("theme_selection_empty") { NavigationStack { ThemeSelectionView() } } } + func test_theme_selection_populated() { + seedPopulated() + snap("theme_selection_populated") { + NavigationStack { ThemeSelectionView() } + } + } + // MARK: - Subscription func test_feature_comparison_empty() { @@ -346,6 +565,13 @@ final class SnapshotGalleryTests: XCTestCase { } } + func test_feature_comparison_populated() { + seedPopulated() + snap("feature_comparison_populated") { + FeatureComparisonView(isPresented: .constant(true)) + } + } + // NOTE: UpgradeFeatureView is intentionally excluded from the parity // gallery. It reads `SubscriptionCacheWrapper.shared` and // `StoreKitManager.shared` on appear, both of which populate diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_populated.add_residence_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_populated.add_residence_populated_dark.png new file mode 100644 index 0000000..3eeb3d5 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_populated.add_residence_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_populated.add_residence_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_populated.add_residence_populated_light.png new file mode 100644 index 0000000..a429c65 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_populated.add_residence_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_dark.png index ff8896d..6c9c859 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_light.png index b29f6aa..b380a23 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_populated.add_task_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_populated.add_task_populated_dark.png new file mode 100644 index 0000000..6c9c859 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_populated.add_task_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_populated.add_task_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_populated.add_task_populated_light.png new file mode 100644 index 0000000..b380a23 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_populated.add_task_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_dark.png index 36edde7..0fb8cf0 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_light.png index 9fab222..94b2f46 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_populated.add_task_with_residence_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_populated.add_task_with_residence_populated_dark.png new file mode 100644 index 0000000..d5d71b6 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_populated.add_task_with_residence_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_populated.add_task_with_residence_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_populated.add_task_with_residence_populated_light.png new file mode 100644 index 0000000..40bf62b Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_populated.add_task_with_residence_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_dark.png index 050ff62..1946770 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_light.png index 6ae5c73..3751391 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_populated.all_tasks_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_populated.all_tasks_populated_dark.png new file mode 100644 index 0000000..5866bc4 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_populated.all_tasks_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_populated.all_tasks_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_populated.all_tasks_populated_light.png new file mode 100644 index 0000000..5866bc4 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_populated.all_tasks_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_dark.png index 53b6b4f..15736e4 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_light.png index 57b0458..1bb9db2 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_populated.contractors_list_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_populated.contractors_list_populated_dark.png new file mode 100644 index 0000000..45fdc97 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_populated.contractors_list_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_populated.contractors_list_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_populated.contractors_list_populated_light.png new file mode 100644 index 0000000..8fe9880 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_populated.contractors_list_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_dark.png index 30ede82..789f653 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_light.png index 474f92d..2947e61 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_populated.documents_warranties_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_populated.documents_warranties_populated_dark.png new file mode 100644 index 0000000..af110a6 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_populated.documents_warranties_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_populated.documents_warranties_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_populated.documents_warranties_populated_light.png new file mode 100644 index 0000000..737362d Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_populated.documents_warranties_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_dark.png index 3c32e1f..4fd16b8 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_light.png index d516483..e307d97 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_populated.feature_comparison_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_populated.feature_comparison_populated_dark.png new file mode 100644 index 0000000..117ed7f Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_populated.feature_comparison_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_populated.feature_comparison_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_populated.feature_comparison_populated_light.png new file mode 100644 index 0000000..e868f0e Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_populated.feature_comparison_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_dark.png index 2a48bc0..6ec262d 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_light.png index e1a5202..5515e4f 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_dark.png index 3837384..51ff4ba 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_light.png index 2e5e3c3..9ccfa28 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_populated.join_residence_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_populated.join_residence_populated_dark.png new file mode 100644 index 0000000..51ff4ba Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_populated.join_residence_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_populated.join_residence_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_populated.join_residence_populated_light.png new file mode 100644 index 0000000..9ccfa28 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_populated.join_residence_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_dark.png index 0a3ce4a..d217a3c 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_light.png index 037e376..68b21ec 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_dark.png index 434201a..febcd7e 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_light.png index c1a54b5..81b53ce 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_populated.notification_preferences_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_populated.notification_preferences_populated_dark.png new file mode 100644 index 0000000..febcd7e Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_populated.notification_preferences_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_populated.notification_preferences_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_populated.notification_preferences_populated_light.png new file mode 100644 index 0000000..81b53ce Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_populated.notification_preferences_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_dark.png index 739613b..fc93813 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_light.png index 408e2b0..a9fae63 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_dark.png index 3e1f6c2..a170f2a 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_light.png index 4ff9914..c86bd44 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_dark.png index 410fadc..acd70ff 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_light.png index 296520e..9b00088 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_dark.png index 11a0b74..35363f8 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_light.png index 737e92e..7c83677 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_dark.png index 2d7743d..178f0aa 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_light.png index 67efbe8..f065d45 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_dark.png index 006a781..678d9fb 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_light.png index 983d6ad..44116be 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_dark.png index a53a87c..eb97404 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_light.png index 2f250d6..9f72a2a 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_dark.png index 9fe2ba1..51b23f4 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_light.png index 19e855b..4c6f26d 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_dark.png index d9961be..f4611c9 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_light.png index d0a2127..4e04acc 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_populated.profile_edit_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_populated.profile_edit_populated_dark.png new file mode 100644 index 0000000..2a80a50 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_populated.profile_edit_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_populated.profile_edit_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_populated.profile_edit_populated_light.png new file mode 100644 index 0000000..6996a81 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_populated.profile_edit_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_dark.png index 700b036..09bc689 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_light.png index 16ec7dc..69c53d4 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_populated.profile_tab_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_populated.profile_tab_populated_dark.png new file mode 100644 index 0000000..700b036 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_populated.profile_tab_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_populated.profile_tab_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_populated.profile_tab_populated_light.png new file mode 100644 index 0000000..16ec7dc Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_populated.profile_tab_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_dark.png index 4933151..a52aac6 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_light.png index f8ac607..478f463 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_dark.png index db980d9..e3f38ae 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_light.png index 5d70dc0..3bd7b2f 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_dark.png index 050ff62..1946770 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_light.png index 6ae5c73..3751391 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_populated.residences_list_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_populated.residences_list_populated_dark.png new file mode 100644 index 0000000..5042b82 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_populated.residences_list_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_populated.residences_list_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_populated.residences_list_populated_light.png new file mode 100644 index 0000000..8563abc Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_populated.residences_list_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_populated.task_suggestions_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_populated.task_suggestions_populated_dark.png new file mode 100644 index 0000000..ffc1e85 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_populated.task_suggestions_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_populated.task_suggestions_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_populated.task_suggestions_populated_light.png new file mode 100644 index 0000000..baa3528 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_populated.task_suggestions_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_dark.png index 9689a0b..6619965 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_light.png index e2f4b22..ede73ed 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_populated.task_templates_browser_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_populated.task_templates_browser_populated_dark.png new file mode 100644 index 0000000..6619965 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_populated.task_templates_browser_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_populated.task_templates_browser_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_populated.task_templates_browser_populated_light.png new file mode 100644 index 0000000..ede73ed Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_populated.task_templates_browser_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_dark.png index a88b8e9..960f16e 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_light.png index 94281f8..c4bbd06 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_populated.theme_selection_populated_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_populated.theme_selection_populated_dark.png new file mode 100644 index 0000000..960f16e Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_populated.theme_selection_populated_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_populated.theme_selection_populated_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_populated.theme_selection_populated_light.png new file mode 100644 index 0000000..c4bbd06 Binary files /dev/null and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_populated.theme_selection_populated_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_dark.png index d6e3c88..07329eb 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_light.png index 18a1e1e..e96f7f7 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_light.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_dark.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_dark.png index e2a68ee..511323b 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_dark.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_dark.png differ diff --git a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_light.png b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_light.png index c0c7195..e180134 100644 Binary files a/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_light.png and b/iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_light.png differ diff --git a/iosApp/iosApp/Contractor/ContractorViewModel.swift b/iosApp/iosApp/Contractor/ContractorViewModel.swift index 7ded601..3e829e1 100644 --- a/iosApp/iosApp/Contractor/ContractorViewModel.swift +++ b/iosApp/iosApp/Contractor/ContractorViewModel.swift @@ -19,6 +19,7 @@ class ContractorViewModel: ObservableObject { // MARK: - Private Properties private var cancellables = Set() + private let dataManager: DataManagerObservable /// Timestamp of the last mutation that already set selectedContractor from its response. /// Used to suppress redundant detail reloads within 1 second of a mutation. /// Unlike a boolean flag, this naturally expires and can never get stuck. @@ -26,9 +27,17 @@ class ContractorViewModel: ObservableObject { // MARK: - Initialization - init() { - // Observe contractors from DataManagerObservable - DataManagerObservable.shared.$contractors + /// - Parameter dataManager: Observable cache the VM subscribes to. + /// Defaults to the shared singleton. + init(dataManager: DataManagerObservable = .shared) { + self.dataManager = dataManager + + // Seed from current cache so snapshot tests/previews render + // populated state without waiting for Combine's async dispatch. + self.contractors = dataManager.contractors + + // Observe contractors from injected DataManagerObservable + dataManager.$contractors .receive(on: DispatchQueue.main) .sink { [weak self] contractors in self?.contractors = contractors diff --git a/iosApp/iosApp/Data/DataManagerObservable.swift b/iosApp/iosApp/Data/DataManagerObservable.swift index d7089ac..d2995d8 100644 --- a/iosApp/iosApp/Data/DataManagerObservable.swift +++ b/iosApp/iosApp/Data/DataManagerObservable.swift @@ -89,6 +89,130 @@ class DataManagerObservable: ObservableObject { startObserving() } + /// Test-only initializer that skips observing the Kotlin + /// `DataManager.shared` singleton, so callers can assign the + /// `@Published` properties directly from fixture data. Used by the + /// parity-gallery SnapshotGalleryTests to produce populated-state + /// snapshots. Production code must never call this — views always + /// resolve `DataManagerObservable.shared`. + /// + /// - Parameter observeSharedDataManager: When `false`, no Kotlin + /// StateFlow observation tasks are spun up. Callers are expected + /// to seed `@Published` properties manually. + init(observeSharedDataManager: Bool) { + if observeSharedDataManager { + startObserving() + } + } + + /// Test-only initializer that seeds every `@Published` property from + /// the current value of the matching Kotlin StateFlow on the given + /// `IDataManager`. This is a synchronous snapshot — later changes to + /// the Kotlin fixture are NOT observed. Used by the parity-gallery + /// SnapshotGalleryTests to produce populated-state renders from + /// `FixtureDataManager.populated()`. + /// + /// Production code must never call this — views always resolve + /// `DataManagerObservable.shared`. + convenience init(kotlin fixture: IDataManager) { + self.init(observeSharedDataManager: false) + + // Auth + self.currentUser = fixture.currentUser.value + self.isAuthenticated = fixture.currentUser.value != nil + + // Residences + self.residences = fixture.residences.value + self.myResidences = fixture.myResidences.value + self.totalSummary = fixture.totalSummary.value + self.residenceSummaries = Self.convertIntMapSync(fixture.residenceSummaries.value) + + // Tasks + self.allTasks = fixture.allTasks.value + self.tasksByResidence = Self.convertIntMapSync(fixture.tasksByResidence.value) + + // Documents + self.documents = fixture.documents.value + self.documentsByResidence = Self.convertIntArrayMapSync(fixture.documentsByResidence.value) + + // Contractors + self.contractors = fixture.contractors.value + + // Subscription + self.subscription = fixture.subscription.value + self.upgradeTriggers = Self.convertStringMapSync(fixture.upgradeTriggers.value) + self.featureBenefits = fixture.featureBenefits.value + self.promotions = fixture.promotions.value + + // Lookups + self.residenceTypes = fixture.residenceTypes.value + self.taskFrequencies = fixture.taskFrequencies.value + self.taskPriorities = fixture.taskPriorities.value + self.taskCategories = fixture.taskCategories.value + self.contractorSpecialties = fixture.contractorSpecialties.value + + // Task Templates + self.taskTemplates = fixture.taskTemplates.value + self.taskTemplatesGrouped = fixture.taskTemplatesGrouped.value + + // Lookups are fully populated by both FixtureDataManager.empty() + // and FixtureDataManager.populated(), so surfaces that gate UI on + // `lookupsInitialized` render their non-empty form. + self.lookupsInitialized = !fixture.residenceTypes.value.isEmpty || + !fixture.taskPriorities.value.isEmpty || + !fixture.taskCategories.value.isEmpty + self.isInitialized = self.lookupsInitialized + } + + // MARK: - Synchronous Map Converters (fixture seeding) + + private static func convertIntMapSync(_ kotlinMap: Any?) -> [Int32: V] { + guard let nsDict = kotlinMap as? NSDictionary else { return [:] } + var result: [Int32: V] = [:] + for key in nsDict.allKeys { + guard let value = nsDict[key], let typedValue = value as? V else { continue } + if let kotlinKey = key as? KotlinInt { + result[kotlinKey.int32Value] = typedValue + } else if let nsNumberKey = key as? NSNumber { + result[nsNumberKey.int32Value] = typedValue + } + } + return result + } + + private static func convertIntArrayMapSync(_ kotlinMap: Any?) -> [Int32: [V]] { + guard let nsDict = kotlinMap as? NSDictionary else { return [:] } + var result: [Int32: [V]] = [:] + for key in nsDict.allKeys { + guard let value = nsDict[key] else { continue } + let typed: [V] + if let arr = value as? [V] { + typed = arr + } else if let nsArr = value as? NSArray { + typed = nsArr.compactMap { $0 as? V } + } else { + continue + } + if let kotlinKey = key as? KotlinInt { + result[kotlinKey.int32Value] = typed + } else if let nsNumberKey = key as? NSNumber { + result[nsNumberKey.int32Value] = typed + } + } + return result + } + + private static func convertStringMapSync(_ kotlinMap: Any?) -> [String: V] { + if let direct = kotlinMap as? [String: V] { return direct } + guard let nsDict = kotlinMap as? NSDictionary else { return [:] } + var result: [String: V] = [:] + for key in nsDict.allKeys { + guard let sKey = key as? String, let value = nsDict[key] as? V else { continue } + result[sKey] = value + } + return result + } + // MARK: - Observation Setup /// Start observing all DataManager StateFlows diff --git a/iosApp/iosApp/Documents/DocumentViewModel.swift b/iosApp/iosApp/Documents/DocumentViewModel.swift index e0a81e5..cf045e6 100644 --- a/iosApp/iosApp/Documents/DocumentViewModel.swift +++ b/iosApp/iosApp/Documents/DocumentViewModel.swift @@ -14,10 +14,19 @@ class DocumentViewModel: ObservableObject { // MARK: - Private Properties private var cancellables = Set() + private let dataManager: DataManagerObservable - init() { - // Observe documents from DataManagerObservable - DataManagerObservable.shared.$documents + /// - Parameter dataManager: Observable cache the VM subscribes to. + /// Defaults to the shared singleton. + init(dataManager: DataManagerObservable = .shared) { + self.dataManager = dataManager + + // Seed from current cache so snapshot tests/previews render + // populated state without waiting for Combine's async dispatch. + self.documents = dataManager.documents + + // Observe documents from injected DataManagerObservable + dataManager.$documents .receive(on: DispatchQueue.main) .sink { [weak self] documents in self?.documents = documents diff --git a/iosApp/iosApp/Login/LoginViewModel.swift b/iosApp/iosApp/Login/LoginViewModel.swift index e95d8ec..f22795d 100644 --- a/iosApp/iosApp/Login/LoginViewModel.swift +++ b/iosApp/iosApp/Login/LoginViewModel.swift @@ -23,18 +23,28 @@ class LoginViewModel: ObservableObject { // MARK: - Private Properties private var cancellables = Set() + private let dataManager: DataManagerObservable // MARK: - Initialization - init() { - // Observe DataManagerObservable for authentication state - DataManagerObservable.shared.$currentUser + /// - Parameter dataManager: Observable cache the VM subscribes to. + /// Defaults to the shared singleton. + init(dataManager: DataManagerObservable = .shared) { + self.dataManager = dataManager + + // Seed from current cache so snapshot tests/previews render + // populated state without waiting for Combine's async dispatch. + self.currentUser = dataManager.currentUser + self.isAuthenticated = dataManager.isAuthenticated + + // Observe injected DataManagerObservable for authentication state + dataManager.$currentUser .receive(on: DispatchQueue.main) .sink { [weak self] user in self?.currentUser = user } .store(in: &cancellables) - DataManagerObservable.shared.$isAuthenticated + dataManager.$isAuthenticated .receive(on: DispatchQueue.main) .sink { [weak self] isAuth in self?.isAuthenticated = isAuth diff --git a/iosApp/iosApp/Profile/ProfileViewModel.swift b/iosApp/iosApp/Profile/ProfileViewModel.swift index 22a3997..eef6382 100644 --- a/iosApp/iosApp/Profile/ProfileViewModel.swift +++ b/iosApp/iosApp/Profile/ProfileViewModel.swift @@ -18,14 +18,30 @@ class ProfileViewModel: ObservableObject { // MARK: - Private Properties private let tokenStorage: TokenStorageProtocol + private let dataManager: DataManagerObservable private var cancellables = Set() // MARK: - Initialization - init(tokenStorage: TokenStorageProtocol? = nil) { + /// - Parameter dataManager: Observable cache the VM subscribes to. + /// Defaults to the shared singleton. + init( + tokenStorage: TokenStorageProtocol? = nil, + dataManager: DataManagerObservable = .shared + ) { self.tokenStorage = tokenStorage ?? Dependencies.current.makeTokenStorage() + self.dataManager = dataManager - // Observe current user from DataManagerObservable - DataManagerObservable.shared.$currentUser + // Seed from current cache so snapshot tests/previews render + // populated state without waiting for Combine's async dispatch. + if let user = dataManager.currentUser { + self.firstName = user.firstName ?? "" + self.lastName = user.lastName ?? "" + self.email = user.email + self.isLoadingUser = false + } + + // Observe current user from injected DataManagerObservable + dataManager.$currentUser .receive(on: DispatchQueue.main) .sink { [weak self] user in guard let self else { return } @@ -51,7 +67,7 @@ class ProfileViewModel: ObservableObject { } // Check if we already have user data - if DataManagerObservable.shared.currentUser != nil { + if dataManager.currentUser != nil { isLoadingUser = false return } diff --git a/iosApp/iosApp/Residence/ResidenceViewModel.swift b/iosApp/iosApp/Residence/ResidenceViewModel.swift index 8924027..2511330 100644 --- a/iosApp/iosApp/Residence/ResidenceViewModel.swift +++ b/iosApp/iosApp/Residence/ResidenceViewModel.swift @@ -24,11 +24,26 @@ class ResidenceViewModel: ObservableObject { // MARK: - Private Properties private var cancellables = Set() + private let dataManager: DataManagerObservable // MARK: - Initialization - init() { - // Observe DataManagerObservable for residence data - DataManagerObservable.shared.$myResidences + /// - Parameter dataManager: Observable cache the VM subscribes to. + /// Defaults to the shared singleton. Tests and the parity-gallery + /// pass a fixture-backed instance instead. + init(dataManager: DataManagerObservable = .shared) { + self.dataManager = dataManager + + // Seed the VM's @Published mirrors synchronously from the current + // cache values so snapshot tests and previews render populated + // state without waiting for Combine's async dispatch. Production + // runs hit this path too but the values are identical to what + // the `.sink` closure would assign moments later on the main queue. + self.myResidences = dataManager.myResidences + self.residences = dataManager.residences + self.totalSummary = dataManager.totalSummary + + // Observe injected DataManagerObservable for residence data + dataManager.$myResidences .receive(on: DispatchQueue.main) .sink { [weak self] myResidences in self?.myResidences = myResidences @@ -44,7 +59,7 @@ class ResidenceViewModel: ObservableObject { } .store(in: &cancellables) - DataManagerObservable.shared.$residences + dataManager.$residences .receive(on: DispatchQueue.main) .sink { [weak self] residences in self?.residences = residences @@ -56,7 +71,7 @@ class ResidenceViewModel: ObservableObject { } .store(in: &cancellables) - DataManagerObservable.shared.$totalSummary + dataManager.$totalSummary .receive(on: DispatchQueue.main) .sink { [weak self] summary in self?.totalSummary = summary @@ -97,7 +112,7 @@ class ResidenceViewModel: ObservableObject { /// Load my residences - checks cache first, then fetches if needed func loadMyResidences(forceRefresh: Bool = false) { // Ensure lookups are initialized (may not be during onboarding) - if !DataManagerObservable.shared.lookupsInitialized { + if !dataManager.lookupsInitialized { Task { _ = try? await APILayer.shared.initializeLookups() } @@ -118,7 +133,7 @@ class ResidenceViewModel: ObservableObject { errorMessage = nil // Check if we have cached data and don't need to refresh - if !forceRefresh && DataManagerObservable.shared.myResidences != nil { + if !forceRefresh && dataManager.myResidences != nil { // Data already available via observation, no API call needed return } diff --git a/iosApp/iosApp/Task/TaskViewModel.swift b/iosApp/iosApp/Task/TaskViewModel.swift index 312ddb7..d163104 100644 --- a/iosApp/iosApp/Task/TaskViewModel.swift +++ b/iosApp/iosApp/Task/TaskViewModel.swift @@ -39,11 +39,21 @@ class TaskViewModel: ObservableObject { // MARK: - Private Properties private var cancellables = Set() + private let dataManager: DataManagerObservable // MARK: - Initialization - init() { - // Observe DataManagerObservable for all tasks data - DataManagerObservable.shared.$allTasks + /// - Parameter dataManager: Observable cache the VM subscribes to. + /// Defaults to the shared singleton. Tests inject a fixture-backed + /// instance so populated-state snapshots render real data. + init(dataManager: DataManagerObservable = .shared) { + self.dataManager = dataManager + + // Seed from current cache so snapshot tests/previews render + // populated state without waiting for Combine's async dispatch. + self.tasksResponse = dataManager.allTasks + + // Observe injected DataManagerObservable for all tasks data + dataManager.$allTasks .receive(on: DispatchQueue.main) .sink { [weak self] allTasks in // Skip DataManager updates during completion animation to prevent @@ -60,7 +70,7 @@ class TaskViewModel: ObservableObject { .store(in: &cancellables) // Observe tasks by residence - DataManagerObservable.shared.$tasksByResidence + dataManager.$tasksByResidence .receive(on: DispatchQueue.main) .sink { [weak self] tasksByResidence in guard self?.isAnimatingCompletion != true else { return }