Adds 4 new StateFlow members to IDataManager + DataManager + InMemoryDataManager + FixtureDataManager: - contractorDetail: Map<Int, Contractor> — cached detail fetches - documentDetail: Map<Int, Document> - taskCompletions: Map<Int, List<TaskCompletionResponse>> - contractorsByResidence: Map<Int, List<ContractorSummary>> APILayer now writes to these on successful detail/per-residence fetches: - getTaskCompletions -> setTaskCompletions - getDocument -> setDocumentDetail - getContractor -> setContractorDetail - getContractorsByResidence -> setContractorsForResidence Fixture populated() seeds contractorDetail + contractorsByResidence. Populated taskCompletions is empty (Fixtures doesn't define any completions yet). Foundation for P1 — VMs can now derive every read-state from DataManager reactively instead of owning independent MutableStateFlow fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
4.4 KiB
Kotlin
105 lines
4.4 KiB
Kotlin
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,
|
|
documentDetail = Fixtures.documents.associateBy { it.id ?: 0 }.filterKeys { it != 0 },
|
|
contractors = Fixtures.contractorSummaries,
|
|
contractorsByResidence = Fixtures.residences.associate { r ->
|
|
r.id to Fixtures.contractorSummaries.filter { it.residenceId == r.id }
|
|
},
|
|
contractorDetail = Fixtures.contractors.associateBy { it.id },
|
|
taskCompletions = emptyMap(), // Fixtures doesn't define task completions; leave empty
|
|
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,
|
|
)
|
|
}
|