1package soy.iko.opencode.ui.chat
3import org.junit.Assert.assertEquals
4import org.junit.Assert.assertTrue
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)))
25 fun rendersRoleHeadersAndBodies() {
26 val md = buildConversationMarkdown(
27 listOf(user("u1", "Hello"), assistant("a1", "Hi there!")),
30 assertTrue(md.startsWith("# My chat"))
31 assertTrue(md.contains("## You\n\nHello"))
32 assertTrue(md.contains("## opencode\n\nHi there!"))
36 fun omitsTitleWhenBlank() {
37 val md = buildConversationMarkdown(listOf(user("u1", "hi")), title = null)
38 assertTrue(md.startsWith("## You"))
42 fun skipsMessagesWithoutText() {
43 val onlyReasoning = MessageWithParts(
44 AssistantMessage("a1", sid),
45 listOf(ReasoningPart(id = "r1", text = "hmm")),
47 val md = buildConversationMarkdown(listOf(onlyReasoning), title = null)
48 // Reasoning is exported in italics, so it still appears.
49 assertTrue(md.contains("## opencode"))
50 assertTrue(md.contains("_hmm_"))
54 fun emptyConversationProducesJustTheTitle() {
55 // No message bodies to export, but the title header is still emitted.
56 assertEquals("# x", buildConversationMarkdown(emptyList(), title = "x"))
60 fun includesToolCallSummary() {
61 val assistant = MessageWithParts(
62 AssistantMessage("a1", sid),
64 TextPart(id = "t1", text = "Done"),
65 ToolPart(id = "tool1", tool = "read", state = ToolCompleted(output = "file contents")),
68 val md = buildConversationMarkdown(listOf(user("u1", "read the file"), assistant), title = null)
69 assertTrue(md.contains("## opencode"))
70 assertTrue("tool name should appear", md.contains("**read**"))
71 assertTrue("tool output should appear", md.contains("file contents"))
75 fun includesToolCallWithTitle() {
76 val assistant = MessageWithParts(
77 AssistantMessage("a1", sid),
82 state = ToolCompleted(title = "Editing src/main.kt", output = "ok"),
86 val md = buildConversationMarkdown(listOf(assistant), title = null)
87 assertTrue(md.contains("**edit**"))
88 assertTrue(md.contains("Editing src/main.kt"))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list