git.iko.soy/opencode-android
on d518ef7 help sign in
opencode-android/app/src/test/java/soy/iko/opencode/ui/chat/ConversationExportTest.kt
120 lines 4.3 kB history raw
1package soy.iko.opencode.ui.chat
3import org.junit.Assert.assertEquals
4import org.junit.Assert.assertTrue
5import org.junit.Test
6import soy.iko.opencode.data.model.AssistantMessage
7import soy.iko.opencode.data.model.MessageWithParts
8import soy.iko.opencode.data.model.ReasoningPart
9import soy.iko.opencode.data.model.TextPart
10import soy.iko.opencode.data.model.ToolCompleted
11import soy.iko.opencode.data.model.ToolPart
12import soy.iko.opencode.data.model.UserMessage
14class ConversationExportTest {
16 private val sid = "s1"
18 private fun user(id: String, text: String) =
19 MessageWithParts(UserMessage(id, sid), listOf(TextPart(id = "p$id", text = text)))
21 private fun assistant(id: String, text: String) =
22 MessageWithParts(AssistantMessage(id, sid), listOf(TextPart(id = "p$id", text = text)))
24 @Test
25 fun rendersRoleHeadersAndBodies() {
26 val md = buildConversationMarkdown(
27 listOf(user("u1", "Hello"), assistant("a1", "Hi there!")),
28 title = "My chat",
29 )
30 assertTrue(md.startsWith("# My chat"))
31 assertTrue(md.contains("## You\n\nHello"))
32 assertTrue(md.contains("## opencode\n\nHi there!"))
33 }
35 @Test
36 fun omitsTitleWhenBlank() {
37 val md = buildConversationMarkdown(listOf(user("u1", "hi")), title = null)
38 // The metadata header is now prepended (message count + timestamp range), so the
39 // first message heading follows it.
40 assertTrue(md.contains("## You"))
41 }
43 @Test
44 fun skipsMessagesWithoutText() {
45 val onlyReasoning = MessageWithParts(
46 AssistantMessage("a1", sid),
47 listOf(ReasoningPart(id = "r1", text = "hmm")),
48 )
49 val md = buildConversationMarkdown(listOf(onlyReasoning), title = null)
50 // Reasoning is exported in italics, so it still appears.
51 assertTrue(md.contains("## opencode"))
52 assertTrue(md.contains("_hmm_"))
53 }
55 @Test
56 fun emptyConversationProducesJustTheTitle() {
57 // No message bodies to export, but the title header is still emitted.
58 assertEquals("# x", buildConversationMarkdown(emptyList(), title = "x"))
59 }
61 @Test
62 fun includesToolCallSummary() {
63 val assistant = MessageWithParts(
64 AssistantMessage("a1", sid),
65 listOf(
66 TextPart(id = "t1", text = "Done"),
67 ToolPart(id = "tool1", tool = "read", state = ToolCompleted(output = "file contents")),
68 ),
69 )
70 val md = buildConversationMarkdown(listOf(user("u1", "read the file"), assistant), title = null)
71 assertTrue(md.contains("## opencode"))
72 assertTrue("tool name should appear", md.contains("**read**"))
73 assertTrue("tool output should appear", md.contains("file contents"))
74 }
76 @Test
77 fun includesToolCallWithTitle() {
78 val assistant = MessageWithParts(
79 AssistantMessage("a1", sid),
80 listOf(
81 ToolPart(
82 id = "tool1",
83 tool = "edit",
84 state = ToolCompleted(title = "Editing src/main.kt", output = "ok"),
85 ),
86 ),
87 )
88 val md = buildConversationMarkdown(listOf(assistant), title = null)
89 assertTrue(md.contains("**edit**"))
90 assertTrue(md.contains("Editing src/main.kt"))
91 }
93 @Test
94 fun buildMessageMarkdownRendersAssistantReply() {
95 val md = buildMessageMarkdown(assistant("a1", "Hi there!"))
96 assertNotNull(md)
97 assertTrue(md!!.startsWith("## opencode"))
98 assertTrue(md.contains("Hi there!"))
99 }
101 @Test
102 fun buildMessageMarkdownRendersUserMessage() {
103 val md = buildMessageMarkdown(user("u1", "Hello"))
104 assertNotNull(md)
105 assertTrue(md!!.startsWith("## You"))
106 assertTrue(md.contains("Hello"))
107 }
109 @Test
110 fun buildMessageMarkdownReturnsNullForEmptyMessage() {
111 val empty = MessageWithParts(
112 AssistantMessage("a1", sid),
113 listOf(ReasoningPart(id = "r1", text = "")),
114 )
115 assertNull(buildMessageMarkdown(empty))
116 }
118 private fun assertNotNull(value: String?) = org.junit.Assert.assertNotNull(value)
119 private fun assertNull(value: String?) = org.junit.Assert.assertNull(value)