13 files changed
+332
-14
No files matched your search
@@ -210,7 +210,7 @@ class ChatSendStoreTest {
|
||||
val local = ChatMessage("send-1", MessageRole.USER, "repeat", serverRowId = 10)
|
||||
val server = ChatMessage("rest-session-20", MessageRole.USER, "repeat", serverRowId = 20)
|
||||
val reply = ChatMessage("rest-session-21", MessageRole.ASSISTANT, "done", serverRowId = 21)
|
||||
val receipt = pending("send-1", "repeat").copy(userRowId = 10)
|
||||
val receipt = pending("send-1", "repeat", PendingSendState.UNKNOWN).copy(userRowId = 10)
|
||||
val merged = mergeTranscriptWithLive(listOf(server, reply), listOf(local), preserveLiveIds = true)
|
||||
|
||||
assertEquals(
|
||||
@@ -228,4 +228,21 @@ class ChatSendStoreTest {
|
||||
|
||||
assertEquals(listOf(queued, confirmed), messagesWithoutUnconfirmedReceipts(listOf(queued, confirmed), pending))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun normalSendingAndAcceptedPromptsAreNotRecoveryItems() {
|
||||
val sends = PendingSendState.entries.map { pending(it.name, "prompt", it) }
|
||||
|
||||
assertEquals(
|
||||
setOf(
|
||||
PendingSendState.QUEUED,
|
||||
PendingSendState.PARKED,
|
||||
PendingSendState.UNKNOWN,
|
||||
PendingSendState.REJECTED,
|
||||
),
|
||||
sends.filter { it.needsRecovery }.map { it.state }.toSet(),
|
||||
)
|
||||
val live = sends.filterNot { it.needsRecovery }.map { ChatMessage(it.id, MessageRole.USER, it.text) }
|
||||
assertEquals(live, messagesWithoutUnconfirmedReceipts(live, sends))
|
||||
}
|
||||
}
|
||||
@@ -10540,6 +10540,62 @@ class ChatViewModelTest {
|
||||
assertTrue(vm.uiState.value.canInterrupt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun normalAcceptanceKeepsPromptVisibleBeforeAndDuringItsTurn() =
|
||||
runTest {
|
||||
val store = ChatSendStore()
|
||||
val (vm, session) = createViewModelWithSession(sendStore = store)
|
||||
every { HermesWsClient.sendMessage(session, "live-prompt", any(), any()) } answers {
|
||||
arg<((String) -> Unit)?>(2)?.invoke("live-submit")
|
||||
"live-submit"
|
||||
}
|
||||
assertTrue(vm.sendMessage("live-prompt"))
|
||||
advanceUntilIdle()
|
||||
mockEventsFlow.emit(WsEvent.RpcResult("live-submit", mapOf("status" to "streaming", "user_row_id" to 10)))
|
||||
advanceUntilIdle()
|
||||
vm.syncCurrentSession()
|
||||
advanceUntilIdle()
|
||||
assertEquals(PendingSendState.ACCEPTED, store.all().single().state)
|
||||
assertEquals(
|
||||
"live-prompt",
|
||||
vm.transcriptState.value.messages
|
||||
.single { it.role == MessageRole.USER }
|
||||
.content,
|
||||
)
|
||||
mockEventsFlow.emit(WsEvent.MessageStart(session))
|
||||
mockEventsFlow.emit(WsEvent.MessageToken("Working", session))
|
||||
advanceUntilIdle()
|
||||
assertEquals(PendingSendState.ACCEPTED, store.all().single().state)
|
||||
assertTrue(
|
||||
vm.transcriptState.value.messages
|
||||
.any { it.content == "live-prompt" },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gatewayQueuedAcceptanceStaysVisibleThroughPreviousTurnCompletion() =
|
||||
runTest {
|
||||
val store = ChatSendStore()
|
||||
val (vm, session) = createViewModelWithSession(sendStore = store)
|
||||
mockEventsFlow.emit(WsEvent.MessageStart(session))
|
||||
advanceUntilIdle()
|
||||
assertTrue(vm.sendMessage("guide-next", BusySendMode.GUIDE))
|
||||
advanceUntilIdle()
|
||||
val requestId = sentRequestMethods.last { it.first == WsMethods.SESSION_STEER }.second
|
||||
mockEventsFlow.emit(WsEvent.RpcResult(requestId, mapOf("status" to "queued", "user_row_id" to 10)))
|
||||
advanceUntilIdle()
|
||||
mockEventsFlow.emit(WsEvent.MessageComplete("Previous reply", session))
|
||||
advanceUntilIdle()
|
||||
vm.syncCurrentSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(PendingSendState.ACCEPTED, store.all().single().state)
|
||||
assertTrue(
|
||||
vm.transcriptState.value.messages
|
||||
.any { it.content == "guide-next" },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changedReceiptIdentityDoesNotBlockNewPromptsOrFalselyConfirmDelivery() =
|
||||
runTest {
|
||||
@@ -10580,7 +10636,7 @@ class ChatViewModelTest {
|
||||
.last()
|
||||
.content,
|
||||
)
|
||||
assertEquals(PendingSendState.ACCEPTED, store.all().single().state)
|
||||
assertEquals(PendingSendState.UNKNOWN, store.all().single().state)
|
||||
assertTrue(vm.uiState.value.isSessionReady)
|
||||
assertTrue(vm.sendMessage("probe-second"))
|
||||
advanceUntilIdle()
|
||||
@@ -10592,7 +10648,7 @@ class ChatViewModelTest {
|
||||
assertNull(vm.uiState.value.errorMessage)
|
||||
verify(exactly = 1) { HermesWsClient.sendMessage(session, "probe-second", any(), any()) }
|
||||
verify(exactly = 1) { HermesWsClient.sendMessage(session, "probe-first", any(), any()) }
|
||||
assertEquals(PendingSendState.ACCEPTED, store.all().single { it.text == "probe-first" }.state)
|
||||
assertEquals(PendingSendState.UNKNOWN, store.all().single { it.text == "probe-first" }.state)
|
||||
assertEquals(
|
||||
1,
|
||||
vm.transcriptState.value.messages
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.m57.hermescontrol.ui.chat.fullbleed
|
||||
|
||||
import com.m57.hermescontrol.data.model.Attachment
|
||||
import com.m57.hermescontrol.data.model.BusySendMode
|
||||
import com.m57.hermescontrol.ui.chat.ChatMessage
|
||||
import com.m57.hermescontrol.ui.chat.ChatTimelineState
|
||||
import com.m57.hermescontrol.ui.chat.ChatUiState
|
||||
import com.m57.hermescontrol.ui.chat.ClarifyUi
|
||||
import com.m57.hermescontrol.ui.chat.MessageRole
|
||||
import com.m57.hermescontrol.ui.chat.PendingSend
|
||||
import com.m57.hermescontrol.ui.chat.PendingSendState
|
||||
import com.m57.hermescontrol.ui.chat.StreamingState
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -18,6 +22,40 @@ class TranscriptUiStateTest {
|
||||
private val historical = ChatMessage(id = "historical", role = MessageRole.USER, content = "older")
|
||||
private val clarify = ClarifyUi(text = "choose")
|
||||
|
||||
@Test
|
||||
fun acceptedLivePromptKeepsItsTextAndImageUntilHistoryConfirmsIt() {
|
||||
val image = Attachment("file:///private/photo", "photo.png", "image/png", 3)
|
||||
val prompt = live.copy(attachments = listOf(image), serverRowId = 10)
|
||||
val result =
|
||||
TranscriptUiState.resolve(
|
||||
chat =
|
||||
ChatUiState(
|
||||
messages = listOf(prompt),
|
||||
isAgentTyping = true,
|
||||
pendingSends =
|
||||
listOf(
|
||||
PendingSend(
|
||||
prompt.id,
|
||||
"scope",
|
||||
"session",
|
||||
prompt.content,
|
||||
attachments = listOf(image),
|
||||
mode = BusySendMode.CORRECT,
|
||||
state = PendingSendState.ACCEPTED,
|
||||
userRowId = 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
timeline = ChatTimelineState(),
|
||||
streaming = StreamingState(),
|
||||
savingAttachmentPath = null,
|
||||
speakingMessageId = null,
|
||||
)
|
||||
|
||||
assertEquals(listOf(prompt), result.messages)
|
||||
assertEquals(listOf(image), result.messages.single().attachments)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `live state preserves prompts streaming and paging actions`() {
|
||||
val stream = StreamingState()
|
||||
|
||||
Reference in new issue
Block a user