feat(chat): support /undo command with transcript reload and composer prefill (#1051)
This commit is contained in:
8 files changed
+159
No files matched your search
@@ -896,6 +896,45 @@ class ChatViewModelTest {
|
||||
assertEquals("openai/gpt-4o", viewModel.uiState.value.currentSessionModel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUndoCommand_dispatchesAndPrefillsComposer() =
|
||||
runTest {
|
||||
val (viewModel, sessionId) = createViewModelWithSession()
|
||||
|
||||
every {
|
||||
HermesWsClient.request(
|
||||
WsMethods.COMMAND_DISPATCH,
|
||||
mapOf("name" to "undo", "arg" to "2", "session_id" to sessionId),
|
||||
any(),
|
||||
)
|
||||
} returns
|
||||
CompletableDeferred(
|
||||
mapOf(
|
||||
"type" to "prefill",
|
||||
"message" to "undone user message",
|
||||
"notice" to "↶ Undid 2 turns",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.sendMessage("/undo 2")
|
||||
advanceUntilIdle()
|
||||
|
||||
// Prefill text is staged in UI state
|
||||
assertEquals("undone user message", viewModel.uiState.value.pendingPrefillText)
|
||||
// Notice is displayed as system message with rewind target feedback
|
||||
assertTrue(
|
||||
viewModel.uiState.value.messages
|
||||
.any { it.content.contains("↶ Undid 2 turns") && it.content.contains("undone user message") },
|
||||
)
|
||||
// Verify slash usage was recorded
|
||||
assertEquals(1, viewModel.uiState.value.slashUsageCounts["/undo"])
|
||||
|
||||
// Consume prefill text
|
||||
viewModel.consumePendingPrefill()
|
||||
advanceUntilIdle()
|
||||
assertNull(viewModel.uiState.value.pendingPrefillText)
|
||||
}
|
||||
|
||||
// ── Connection / init tests ──────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
@@ -82,6 +82,14 @@ class SlashCommandDispatcherTest {
|
||||
assertEquals(SlashResult.SideQuestion(""), dispatcher.dispatch("/btw "))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `undo routes to Undo`() {
|
||||
assertEquals(SlashResult.Undo(""), dispatcher.dispatch("/undo"))
|
||||
assertEquals(SlashResult.Undo("2"), dispatcher.dispatch("/undo 2"))
|
||||
assertEquals(SlashResult.Undo("3"), dispatcher.dispatch("/UNDO 3"))
|
||||
assertEquals(SlashResult.Undo(""), dispatcher.dispatch("/Undo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NEW uppercase still routes to NewSession`() {
|
||||
// Dispatcher lower-cases before matching, so case must not matter.
|
||||
|
||||
@@ -27,6 +27,10 @@ class FakeChatMessageDao : ChatMessageDao {
|
||||
messageList.forEach { messages[it.id] = it }
|
||||
}
|
||||
|
||||
override suspend fun deleteMessagesForSession(sessionId: String) {
|
||||
messages.values.removeAll { it.sessionId == sessionId }
|
||||
}
|
||||
|
||||
/** Direct access for test setup — bypasses the suspend modifier. */
|
||||
fun addMessageDirect(message: ChatMessageEntity) {
|
||||
messages[message.id] = message
|
||||
|
||||
Reference in new issue
Block a user