test: characterize getTasksForResidence filter contract
Locks down the contract that becomes the primary path for residence detail in Phase 3: - filters _allTasks by residenceId - returns empty shell for residence with no tasks (vs null for cache miss) - returns null when _allTasks itself is null (caller must hit API)
This commit is contained in:
@@ -88,6 +88,53 @@ class DataManagerTaskCacheTest {
|
|||||||
assertEquals("v2", inProgress.tasks.first { it.id == 5 }.title)
|
assertEquals("v2", inProgress.tasks.first { it.id == 5 }.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Characterization: getTasksForResidence filters _allTasks by
|
||||||
|
/// residence id. This is the helper that becomes the primary path
|
||||||
|
/// for residence-detail in Phase 3 (collapse the dual cache).
|
||||||
|
@Test
|
||||||
|
fun getTasksForResidence_filtersAllTasksByResidenceId() {
|
||||||
|
// Seed _allTasks with tasks across two residences via the upsert path.
|
||||||
|
DataManager.updateTask(sampleTask(id = 1, residenceId = 100, column = "upcoming_tasks"))
|
||||||
|
DataManager.updateTask(sampleTask(id = 2, residenceId = 100, column = "overdue_tasks"))
|
||||||
|
DataManager.updateTask(sampleTask(id = 3, residenceId = 200, column = "upcoming_tasks"))
|
||||||
|
|
||||||
|
val r100 = DataManager.getTasksForResidence(100)
|
||||||
|
assertNotNull(r100)
|
||||||
|
val r100Ids = r100.columns.flatMap { it.tasks }.map { it.id }.toSet()
|
||||||
|
assertEquals(setOf(1, 2), r100Ids)
|
||||||
|
|
||||||
|
val r200 = DataManager.getTasksForResidence(200)
|
||||||
|
assertNotNull(r200)
|
||||||
|
val r200Ids = r200.columns.flatMap { it.tasks }.map { it.id }.toSet()
|
||||||
|
assertEquals(setOf(3), r200Ids)
|
||||||
|
|
||||||
|
// Counts on each column must match the filtered task lists.
|
||||||
|
for (column in r100.columns) {
|
||||||
|
assertEquals(column.tasks.size, column.count, "column ${column.name} count mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Characterization: residenceId with no tasks returns a non-null
|
||||||
|
/// shell so the residence-detail screen can distinguish "loading"
|
||||||
|
/// (null) from "loaded, no tasks" (non-null with empty columns).
|
||||||
|
@Test
|
||||||
|
fun getTasksForResidence_returnsEmptyShellForResidenceWithNoTasks() {
|
||||||
|
DataManager.updateTask(sampleTask(id = 1, residenceId = 100, column = "upcoming_tasks"))
|
||||||
|
|
||||||
|
val r999 = DataManager.getTasksForResidence(999)
|
||||||
|
assertNotNull(r999, "residence with no tasks must return an empty shell, not null")
|
||||||
|
assertEquals(0, r999.columns.sumOf { it.tasks.size })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Characterization: when _allTasks is null entirely (cache never
|
||||||
|
/// populated), getTasksForResidence returns null — caller must call
|
||||||
|
/// the API path. Phase 3's getTasksByResidence relies on this.
|
||||||
|
@Test
|
||||||
|
fun getTasksForResidence_returnsNullWhenAllTasksIsNull() {
|
||||||
|
DataManager.clear()
|
||||||
|
assertEquals(null, DataManager.getTasksForResidence(100))
|
||||||
|
}
|
||||||
|
|
||||||
/// Lockdown: updateTask must NOT touch `_tasksByResidence`. That cache
|
/// Lockdown: updateTask must NOT touch `_tasksByResidence`. That cache
|
||||||
/// is being deleted in Phase 3; until then, updateTask must leave it
|
/// is being deleted in Phase 3; until then, updateTask must leave it
|
||||||
/// alone. If a future commit re-introduces the conditional write
|
/// alone. If a future commit re-introduces the conditional write
|
||||||
|
|||||||
Reference in New Issue
Block a user