@@ -4,6 +4,7 @@ package soy.iko.opencode.ui.chat
5import kotlinx.coroutines.Dispatchers
5import kotlinx.coroutines.Dispatchers
6import kotlinx.coroutines.ExperimentalCoroutinesApi
6import kotlinx.coroutines.ExperimentalCoroutinesApi
7+import kotlinx.coroutines.launch
7import kotlinx.coroutines.test.StandardTestDispatcher
8import kotlinx.coroutines.test.StandardTestDispatcher
8import kotlinx.coroutines.test.TestScope
9import kotlinx.coroutines.test.TestScope
9import kotlinx.coroutines.test.resetMain
10import kotlinx.coroutines.test.resetMain
@@ -18,6 +19,7 @@ import org.junit.Assert.assertTrue
18import org.junit.Before
19import org.junit.Before
19import org.junit.Test
20import org.junit.Test
20import soy.iko.opencode.data.model.Command
21import soy.iko.opencode.data.model.Command
22+import soy.iko.opencode.data.model.MessageWithParts
21import soy.iko.opencode.data.model.ModelInfo
23import soy.iko.opencode.data.model.ModelInfo
22import soy.iko.opencode.data.model.Permission
24import soy.iko.opencode.data.model.Permission
23import soy.iko.opencode.data.model.PermissionResponse
25import soy.iko.opencode.data.model.PermissionResponse
@@ -30,6 +32,7 @@ import soy.iko.opencode.data.model.SessionDeleted
30import soy.iko.opencode.data.model.SessionError
32import soy.iko.opencode.data.model.SessionError
31import soy.iko.opencode.data.model.SessionIdle
33import soy.iko.opencode.data.model.SessionIdle
32import soy.iko.opencode.data.model.SessionUpdated
34import soy.iko.opencode.data.model.SessionUpdated
35+import soy.iko.opencode.data.model.UserMessage
33import soy.iko.opencode.data.network.EventStreamClient
36import soy.iko.opencode.data.network.EventStreamClient
34import soy.iko.opencode.ui.testing.FakeAppContainer
37import soy.iko.opencode.ui.testing.FakeAppContainer
35import soy.iko.opencode.ui.testing.FakeEventStreamClient
38import soy.iko.opencode.ui.testing.FakeEventStreamClient
@@ -478,4 +481,55 @@ class ChatViewModelTest {
478 vm.clearError()
481 vm.clearError()
479 assertNull(vm.error.value)
482 assertNull(vm.error.value)
486+ fun messagesFlowRetry_clearsStaleErrorOnSuccess() = testScope.runTest {
487+ val api = FakeOpencodeApiClient()
488+ val events = FakeEventStreamClient()
489+ // Use a controllable flow so we can simulate a re-emission after an error.
490+ val messagesFlow = kotlinx.coroutines.flow.MutableStateFlow(
491+ listOf(MessageWithParts(info = UserMessage(id = "m1", sessionID = "session1")))
493+ val repo = FakeSessionRepository(api, events)
494+ repo.observeMessagesOverride = messagesFlow
495+ val container = makeContainer(api = api, events = events, repo = repo)
496+ val vm = makeVm(container)
497+ // Subscribe to messages so the flow's onEach is active and can clear errors.
498+ val collector = launch { vm.messages.collect { /* keep subscribed */ } }
499+ testScope.testScheduler.advanceUntilIdle()
500+ // Set an error via SSE
501+ events.fakeEvents.tryEmit(SessionError(SessionError.Props(sessionID = "session1")))
502+ testScope.testScheduler.advanceUntilIdle()
503+ assertNotNull(vm.error.value)
504+ // Trigger a new messages emission — onEach should clear the stale error
505+ messagesFlow.value = messagesFlow.value + MessageWithParts(
506+ info = UserMessage(id = "m2", sessionID = "session1"),
508+ testScope.testScheduler.advanceUntilIdle()
509+ assertNull(vm.error.value)
513+ // --- Connection switch clears session title ---
516+ fun connectionChange_clearsSessionTitle() = testScope.runTest {
517+ val events = FakeEventStreamClient()
518+ val container = makeContainer(events = events)
519+ val vm = makeVm(container)
520+ // Set the title via SSE event
521+ events.fakeEvents.tryEmit(
522+ SessionUpdated(SessionUpdated.Props(info = Session(id = "session1", title = "Old Title"))),
524+ testScope.testScheduler.advanceUntilIdle()
525+ assertEquals("Old Title", vm.sessionTitle.value)
526+ // Switch to a new connection
527+ val api2 = FakeOpencodeApiClient()
528+ val events2 = FakeEventStreamClient()
529+ val repo2 = FakeSessionRepository(api2, events2)
530+ val conn2 = FakeOpencodeConnection(api2, events2, repo2, ServerProfile(id = "s2", label = "Server 2", baseUrl = "http://other"))
531+ container.setActiveConnection(conn2)
532+ testScope.testScheduler.advanceUntilIdle()
533+ assertNull(vm.sessionTitle.value)