Files
honeyDueKMP/composeApp/src/commonMain/kotlin/com/tt/honeyDue/testing/FixtureDataManager.kt
Trey T 2230cde071 P0: IDataManager coverage gaps — contractorDetail/documentDetail/taskCompletions/contractorsByResidence
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>
2026-04-19 18:31:06 -05:00

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,
)
}