P1: Shared FixtureDataManager (empty + populated) for cross-platform snapshots

InMemoryDataManager + Fixtures with deterministic data (fixed clock 2026-04-15,
2 residences, 8 tasks, 3 contractors, 5 documents). FixtureDataManager.empty()
and .populated() factories. Exposed to Swift via SKIE.

Expanded IDataManager surface (5 -> 22 members) so fixtures cover every
StateFlow and lookup helper screens read: myResidences, allTasks,
tasksByResidence, documents, documentsByResidence, contractors, residenceTypes,
taskFrequencies, taskPriorities, taskCategories, contractorSpecialties,
taskTemplates, taskTemplatesGrouped, residenceSummaries, upgradeTriggers,
promotions, plus get{ResidenceType,TaskFrequency,TaskPriority,TaskCategory,
ContractorSpecialty}(id) lookup helpers. DataManager implementation is a pure
override-keyword addition — no behavior change.

Enables P2 (Android gallery) + P3 (iOS gallery) to render real screens against
identical inputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 19:22:41 -05:00
parent c57743dca0
commit 47eaf5a0c0
6 changed files with 1263 additions and 31 deletions

View File

@@ -0,0 +1,98 @@
package com.tt.honeyDue.testing
import com.tt.honeyDue.data.IDataManager
/**
* Factories that produce deterministic [IDataManager] instances for the
* parity-gallery (Android Roborazzi + iOS swift-snapshot-testing) and any
* other snapshot/preview harness. Both platforms consume the same fixture
* graph (via SKIE bridging on iOS), so any layout divergence between iOS
* and Android renders of the same screen is a real parity bug — not a test
* data mismatch.
*
* Use:
* ```kotlin
* // Android Compose preview or Roborazzi test
* CompositionLocalProvider(LocalDataManager provides FixtureDataManager.empty()) {
* MyScreen()
* }
* ```
*
* ```swift
* // iOS SwiftUI preview or snapshot test
* MyView().environment(\.dataManager,
* DataManagerObservable(kotlin: FixtureDataManager.shared.populated()))
* ```
*/
object FixtureDataManager {
/**
* Data-free fixture — represents a freshly-signed-in user with no
* residences, no tasks, no contractors, no documents. Lookups
* (priorities, categories, frequencies) are still populated because
* empty-state form pickers render them even before the user has any
* entities of their own.
*/
fun empty(): IDataManager = InMemoryDataManager(
currentUser = null,
residences = emptyList(),
myResidencesResponse = null,
totalSummary = null,
residenceSummaries = emptyMap(),
allTasks = null,
tasksByResidence = emptyMap(),
documents = emptyList(),
documentsByResidence = emptyMap(),
contractors = emptyList(),
subscription = Fixtures.freeSubscription,
upgradeTriggers = emptyMap(),
featureBenefits = Fixtures.featureBenefits,
promotions = emptyList(),
residenceTypes = Fixtures.residenceTypes,
taskFrequencies = Fixtures.taskFrequencies,
taskPriorities = Fixtures.taskPriorities,
taskCategories = Fixtures.taskCategories,
contractorSpecialties = Fixtures.contractorSpecialties,
taskTemplates = Fixtures.taskTemplates,
taskTemplatesGrouped = Fixtures.taskTemplatesGrouped,
)
/**
* Fully-populated fixture with realistic content for every screen:
* 2 residences · 8 tasks (mix of overdue/due-soon/upcoming/completed)
* · 3 contractors · 5 documents (2 warranties — one expired —
* + 3 manuals). The user is premium-tier so gated surfaces render
* their "pro" appearance.
*/
fun populated(): IDataManager = InMemoryDataManager(
currentUser = Fixtures.user,
residences = Fixtures.residences,
myResidencesResponse = Fixtures.myResidencesResponse,
totalSummary = Fixtures.totalSummary,
residenceSummaries = Fixtures.residenceSummaries,
allTasks = Fixtures.taskColumnsResponse,
tasksByResidence = Fixtures.residences.associate { residence ->
residence.id to Fixtures.taskColumnsResponse.copy(
columns = Fixtures.taskColumnsResponse.columns.map { column ->
val filtered = column.tasks.filter { it.residenceId == residence.id }
column.copy(tasks = filtered, count = filtered.size)
},
residenceId = residence.id.toString(),
)
},
documents = Fixtures.documents,
documentsByResidence = Fixtures.documentsByResidence,
contractors = Fixtures.contractorSummaries,
subscription = Fixtures.premiumSubscription,
upgradeTriggers = emptyMap(),
featureBenefits = Fixtures.featureBenefits,
promotions = emptyList(),
residenceTypes = Fixtures.residenceTypes,
taskFrequencies = Fixtures.taskFrequencies,
taskPriorities = Fixtures.taskPriorities,
taskCategories = Fixtures.taskCategories,
contractorSpecialties = Fixtures.contractorSpecialties,
taskTemplates = Fixtures.taskTemplates,
taskTemplatesGrouped = Fixtures.taskTemplatesGrouped,
)
}