feat: support session-level hidden flag in SessionListResponse and SessionsScreen (#1033)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-09-01 16:56:28 +03:00
1 parent aa28d96b5d
commit 908255f5ad
10 files changed
+316 -20

No files matched your search

@@ -31,13 +31,19 @@ class RefreshOnChangeTest {
private class TestViewModel : ViewModel() {
val state = MutableStateFlow("initial")
var job: kotlinx.coroutines.Job? = null
init {
refreshOnChange(
eventType = ChangeEvents.CRON,
apiCall = { NetworkResult.Success("refreshed") },
onSuccess = { state.value = it },
)
job =
refreshOnChange(
eventType = ChangeEvents.CRON,
apiCall = { NetworkResult.Success("refreshed") },
onSuccess = { state.value = it },
)
}
fun cleanup() {
job?.cancel()
}
}
@@ -55,23 +61,31 @@ class RefreshOnChangeTest {
fun testRefreshOnChange_updatesOnMatchingEvent() =
runTest(testDispatcher) {
val viewModel = TestViewModel()
// Let the init coroutine run so the collector actually subscribes
// before the event fires (replay=0 — events before subscription
// are dropped, matching production where VMs subscribe at
// construction and events arrive afterwards).
advanceUntilIdle()
ChangeEventHub.emit(WsEvent.ChangeEvent(ChangeEvents.CRON))
advanceUntilIdle()
assertEquals("refreshed", viewModel.state.value)
try {
// Let the init coroutine run so the collector actually subscribes
// before the event fires (replay=0 — events before subscription
// are dropped, matching production where VMs subscribe at
// construction and events arrive afterwards).
advanceUntilIdle()
ChangeEventHub.emit(WsEvent.ChangeEvent(ChangeEvents.CRON))
advanceUntilIdle()
assertEquals("refreshed", viewModel.state.value)
} finally {
viewModel.cleanup()
}
}
@Test
fun testRefreshOnChange_ignoresOtherEventTypes() =
runTest(testDispatcher) {
val viewModel = TestViewModel()
advanceUntilIdle()
ChangeEventHub.emit(WsEvent.ChangeEvent(ChangeEvents.SESSIONS))
advanceUntilIdle()
assertEquals("initial", viewModel.state.value)
try {
advanceUntilIdle()
ChangeEventHub.emit(WsEvent.ChangeEvent(ChangeEvents.SESSIONS))
advanceUntilIdle()
assertEquals("initial", viewModel.state.value)
} finally {
viewModel.cleanup()
}
}
}
@@ -281,4 +281,84 @@ class SessionsViewModelTest {
.contains("Pin failed"),
)
}
@Test
fun `displaySessions filters out hidden sessions by default and shows them on toggleShowHidden`() {
val vm = createViewModel()
coEvery { mockApi.getSessions(any(), any(), any()) } returns
Response.success(
SessionListResponse(
sessions =
listOf(
SessionInfo("s-visible-1", hidden = false),
SessionInfo("s-hidden", hidden = true),
SessionInfo("s-visible-2", hidden = null),
),
total = 3,
),
)
vm.loadSessions()
testDispatcher.scheduler.advanceUntilIdle()
assertTrue(vm.uiState.value.hasHiddenSessions)
assertFalse(vm.uiState.value.showHidden)
assertEquals(
listOf("s-visible-1", "s-visible-2"),
vm.uiState.value.displaySessions
.map { it.id },
)
vm.toggleShowHidden()
assertTrue(vm.uiState.value.showHidden)
assertEquals(
listOf("s-visible-1", "s-hidden", "s-visible-2"),
vm.uiState.value.displaySessions
.map { it.id },
)
vm.toggleShowHidden()
assertFalse(vm.uiState.value.showHidden)
assertEquals(
listOf("s-visible-1", "s-visible-2"),
vm.uiState.value.displaySessions
.map { it.id },
)
}
@Test
fun `toggleHide updates hidden status and surfaces toast`() {
val vm = createViewModel()
coEvery { mockApi.getSessions(any(), any(), any()) } returns
Response.success(
SessionListResponse(
sessions = listOf(SessionInfo("s-1", hidden = false)),
total = 1,
),
)
vm.loadSessions()
testDispatcher.scheduler.advanceUntilIdle()
coEvery { mockApi.setSessionHidden(any(), any()) } returns Response.success(Unit)
vm.toggleHide("s-1")
testDispatcher.scheduler.advanceUntilIdle()
assertEquals(
true,
vm.uiState.value.sessions
.first()
.hidden,
)
assertEquals("Session hidden", vm.uiState.value.toastMessage)
vm.toggleHide("s-1")
testDispatcher.scheduler.advanceUntilIdle()
assertEquals(
false,
vm.uiState.value.sessions
.first()
.hidden,
)
assertEquals("Session unhidden", vm.uiState.value.toastMessage)
}
}