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:
1 parent
aa28d96b5d
commit
908255f5ad
10 files changed
+316
-20
No files matched your search
@@ -312,6 +312,41 @@ class ModelSerializationTest {
|
||||
assertEquals(false, s8.pinned)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSessionInfoDeserialization_hiddenToleratesIntAndBooleanAndString() {
|
||||
val s1 = json.decodeFromString<SessionInfo>("""{"id": "s1", "hidden": 0}""")
|
||||
assertEquals(false, s1.hidden)
|
||||
|
||||
val s2 = json.decodeFromString<SessionInfo>("""{"id": "s2", "hidden": 1}""")
|
||||
assertEquals(true, s2.hidden)
|
||||
|
||||
val s3 = json.decodeFromString<SessionInfo>("""{"id": "s3", "hidden": false}""")
|
||||
assertEquals(false, s3.hidden)
|
||||
|
||||
val s4 = json.decodeFromString<SessionInfo>("""{"id": "s4", "hidden": true}""")
|
||||
assertEquals(true, s4.hidden)
|
||||
|
||||
val s5 = json.decodeFromString<SessionInfo>("""{"id": "s5", "hidden": null}""")
|
||||
assertNull(s5.hidden)
|
||||
|
||||
val s6 = json.decodeFromString<SessionInfo>("""{"id": "s6"}""")
|
||||
assertNull(s6.hidden)
|
||||
|
||||
val s7 = json.decodeFromString<SessionInfo>("""{"id": "s7", "hidden": "1"}""")
|
||||
assertEquals(true, s7.hidden)
|
||||
|
||||
val s8 = json.decodeFromString<SessionInfo>("""{"id": "s8", "hidden": "false"}""")
|
||||
assertEquals(false, s8.hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSessionRenameRequestSerialization_includesHidden() {
|
||||
val req = SessionRenameRequest(title = "new name", hidden = true)
|
||||
val encoded = json.encodeToString(SessionRenameRequest.serializer(), req)
|
||||
assertTrue(encoded.contains("\"hidden\":true"))
|
||||
assertTrue(encoded.contains("\"title\":\"new name\""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSessionMessagesResponseDeserialization_missingMessages_causesNullOnNonNullableField() {
|
||||
val jsonStr = "{}"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user