* feat(chat): add animated GIF rendering with tap-to-play animation Closes #721 * fix(#721): add missing GifImageThumbnail component and format ktlint * fix(analytics): resolve ktlint indentation error in itemsIndexed * feat(chat): add interactive play/pause toggle for animated GIFs * fix(chat): control GIF playback via Animatable start and stop
This commit is contained in:
32 files changed
+370
-118
No files matched your search
@@ -92,18 +92,17 @@ class ChatMessageMapperTest {
|
||||
assertNull(createEntityWithToolStatus(null).toUiModel().toolStatus)
|
||||
}
|
||||
|
||||
private fun createEntityWithRole(role: String): ChatMessageEntity {
|
||||
return ChatMessageEntity(
|
||||
private fun createEntityWithRole(role: String): ChatMessageEntity =
|
||||
ChatMessageEntity(
|
||||
id = "id",
|
||||
sessionId = "session",
|
||||
role = role,
|
||||
content = "content",
|
||||
timestamp = 0L,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createEntityWithToolStatus(status: String?): ChatMessageEntity {
|
||||
return ChatMessageEntity(
|
||||
private fun createEntityWithToolStatus(status: String?): ChatMessageEntity =
|
||||
ChatMessageEntity(
|
||||
id = "id",
|
||||
sessionId = "session",
|
||||
role = "TOOL",
|
||||
@@ -111,7 +110,6 @@ class ChatMessageMapperTest {
|
||||
timestamp = 0L,
|
||||
toolStatus = status,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun entityToUiModelCarriesReasoningText() {
|
||||
|
||||
@@ -22,7 +22,8 @@ class CallExtTest {
|
||||
val call = mockk<Call>()
|
||||
val request = Request.Builder().url("http://localhost").build()
|
||||
val response =
|
||||
Response.Builder()
|
||||
Response
|
||||
.Builder()
|
||||
.request(request)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(200)
|
||||
|
||||
@@ -26,9 +26,7 @@ class CookieManagerTest {
|
||||
return jar
|
||||
}
|
||||
|
||||
private fun ep(host: String): ServerEndpoint {
|
||||
return ServerEndpoint.fromLegacy(host, 9119)
|
||||
}
|
||||
private fun ep(host: String): ServerEndpoint = ServerEndpoint.fromLegacy(host, 9119)
|
||||
|
||||
private fun httpsEp(
|
||||
host: String,
|
||||
|
||||
@@ -53,7 +53,8 @@ class HermesWsClientTest {
|
||||
every { AuthManager.serverStore } returns
|
||||
mockk<com.m57.hermescontrol.data.config.ServerStore>().also {
|
||||
every { it.getLatestState() } returns
|
||||
com.m57.hermescontrol.data.config.ServerStoreState()
|
||||
com.m57.hermescontrol.data.config
|
||||
.ServerStoreState()
|
||||
}
|
||||
|
||||
// Issue #470: clients are built through OkHttpProvider, which now
|
||||
@@ -496,7 +497,8 @@ class HermesWsClientTest {
|
||||
every { AuthManager.serverStore } returns
|
||||
mockk<com.m57.hermescontrol.data.config.ServerStore>().also {
|
||||
every { it.getLatestState() } returns
|
||||
com.m57.hermescontrol.data.config.ServerStoreState(wsAuthParam = "ticket")
|
||||
com.m57.hermescontrol.data.config
|
||||
.ServerStoreState(wsAuthParam = "ticket")
|
||||
}
|
||||
// No bare-name session cookie present (the prefixed one is server-side).
|
||||
every { AuthManager.getSessionCookie() } returns null
|
||||
|
||||
@@ -2032,6 +2032,9 @@ class ChatViewModelTest {
|
||||
advanceUntilIdle()
|
||||
|
||||
assertNotNull(vm.uiState.value.openError)
|
||||
assertTrue(vm.uiState.value.openError!!.contains("missing.pdf"))
|
||||
assertTrue(
|
||||
vm.uiState.value.openError!!
|
||||
.contains("missing.pdf"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -222,4 +222,26 @@ class MarkdownTextFeatureTest {
|
||||
val blocks = parseBlocks(md)
|
||||
assertTrue("should remain a paragraph", blocks.single() is MdBlock.Paragraph)
|
||||
}
|
||||
|
||||
// 13. Attachment isGif check (issue #721)
|
||||
@Test
|
||||
fun testAttachment_isGif() {
|
||||
val gifAttachment =
|
||||
com.m57.hermescontrol.data.model.Attachment(
|
||||
uri = "content://media/1.gif",
|
||||
name = "test.gif",
|
||||
mimeType = "image/gif",
|
||||
)
|
||||
assertTrue(gifAttachment.isImage)
|
||||
assertTrue(gifAttachment.isGif)
|
||||
|
||||
val nonGifAttachment =
|
||||
com.m57.hermescontrol.data.model.Attachment(
|
||||
uri = "content://media/1.jpg",
|
||||
name = "test.jpg",
|
||||
mimeType = "image/jpeg",
|
||||
)
|
||||
assertTrue(nonGifAttachment.isImage)
|
||||
assertFalse(nonGifAttachment.isGif)
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,9 @@ class SlashCommandDispatchRpcTest {
|
||||
vm.sendMessage("/help")
|
||||
advanceUntilIdle()
|
||||
|
||||
val last = vm.uiState.value.messages.lastOrNull()
|
||||
val last =
|
||||
vm.uiState.value.messages
|
||||
.lastOrNull()
|
||||
assertEquals("⚠️ /help: session busy — /interrupt first", last?.content)
|
||||
}
|
||||
|
||||
@@ -232,7 +234,9 @@ class SlashCommandDispatchRpcTest {
|
||||
assertEquals(sessionId, params["session_id"])
|
||||
|
||||
// And the slash.exec output must be surfaced to the user.
|
||||
val last = vm.uiState.value.messages.lastOrNull()
|
||||
val last =
|
||||
vm.uiState.value.messages
|
||||
.lastOrNull()
|
||||
assertEquals("STATUS: gateway reachable", last?.content)
|
||||
}
|
||||
|
||||
@@ -254,7 +258,9 @@ class SlashCommandDispatchRpcTest {
|
||||
vm.sendMessage("/redraw")
|
||||
advanceUntilIdle()
|
||||
|
||||
val last = vm.uiState.value.messages.lastOrNull()
|
||||
val last =
|
||||
vm.uiState.value.messages
|
||||
.lastOrNull()
|
||||
assertTrue(
|
||||
"expected a 'not supported on mobile' message, got: ${last?.content}",
|
||||
last?.content?.contains("not supported on mobile") == true,
|
||||
@@ -292,7 +298,9 @@ class SlashCommandDispatchRpcTest {
|
||||
advanceUntilIdle()
|
||||
|
||||
// Both RPCs fail -> the secondary slash.exec error must surface.
|
||||
val last = vm.uiState.value.messages.lastOrNull()
|
||||
val last =
|
||||
vm.uiState.value.messages
|
||||
.lastOrNull()
|
||||
assertEquals("⚠️ /status: slash worker start failed: boom", last?.content)
|
||||
}
|
||||
|
||||
@@ -327,6 +335,11 @@ class SlashCommandDispatchRpcTest {
|
||||
// must NOT append an assistant bubble — so the last message is still
|
||||
// the user's own command, and only one message was added.
|
||||
assertEquals(before + 1, vm.uiState.value.messages.size)
|
||||
assertEquals("/status", vm.uiState.value.messages.lastOrNull()?.content)
|
||||
assertEquals(
|
||||
"/status",
|
||||
vm.uiState.value.messages
|
||||
.lastOrNull()
|
||||
?.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user