import Testing @testable import OpenClawChatUI @Suite("ChatTranscriptExporter") struct ChatTranscriptExporterTests { @Test func `formats visible messages and attachments`() { let messages = [ self.message(role: "system", text: "Hidden setup", timestamp: 0), self.message(role: "user", text: "Hello, 世界 👋", timestamp: 0), self.message( role: "assistant", text: """ Do not export this reasoning. | Item | Value | | --- | --- | | code | `ok` | ```swift print("hi") ``` """, timestamp: 1000), OpenClawChatMessage( role: "user", content: [ OpenClawChatMessageContent( type: "attachment", text: nil, mimeType: "text/plain", fileName: "résumé.txt", content: nil), ], timestamp: 2000), self.message(role: "assistant", text: " ", timestamp: 3000), ] let markdown = ChatTranscriptExporter.markdown( sessionTitle: "Project 🐾", sessionKey: "agent:main", messages: messages) #expect(markdown == """ # Project 🐾 ### User — 1970-01-01T00:00:00Z Hello, 世界 👋 ### Assistant — 1970-01-01T00:00:01Z | Item | Value | | --- | --- | | code | `ok` | ```swift print("hi") ``` ### User — 1970-01-01T00:00:02Z _[attachment: résumé.txt]_ """) } @Test(arguments: [ "file", "attachment", "image", "audio", "video", "FILE", "ATTACHMENT", "IMAGE", "AuDiO", "ViDeO", ]) func `classifies gateway media and historical file attachments consistently`(type: String) { let content = OpenClawChatMessageContent( type: type, text: nil, mimeType: nil, fileName: nil, content: nil) #expect(content.isInlineAttachment) } @Test(arguments: ["text", "canvas", "toolCall", "tool_result"]) func `does not classify unrelated content as an attachment`(type: String) { let content = OpenClawChatMessageContent( type: type, text: nil, mimeType: "image/png", fileName: "not-an-attachment.png", content: nil) #expect(!content.isInlineAttachment) } @Test func `does not classify content without a type as an attachment`() { let content = OpenClawChatMessageContent( type: nil, text: nil, mimeType: "image/png", fileName: "not-an-attachment.png", content: nil) #expect(!content.isInlineAttachment) } @Test(arguments: ["user", "assistant"], ["image", "audio", "video", "IMAGE", "AuDiO", "ViDeO"]) func `exports canonical media-only messages`(role: String, type: String) { let isAudio = type.lowercased() == "audio" let isVideo = type.lowercased() == "video" let filename = isAudio ? "voice-note.m4a" : isVideo ? "clip.mp4" : "photo.png" let message = OpenClawChatMessage( role: role, content: [ OpenClawChatMessageContent( type: type, text: nil, mimeType: isAudio ? "audio/mp4" : isVideo ? "video/mp4" : "image/png", fileName: filename, content: nil), ], timestamp: 0) let markdown = ChatTranscriptExporter.markdown( sessionTitle: "Media chat", sessionKey: "agent:main", messages: [message]) #expect(markdown == """ # Media chat ### \(role == "user" ? "User" : "Assistant") — 1970-01-01T00:00:00Z _[attachment: \(filename)]_ """) } @Test(arguments: ["image", "audio", "IMAGE", "AuDiO"]) func `preserves canonical media alongside message text`(type: String) { let isAudio = type.lowercased() == "audio" let filename = isAudio ? "voice-note.m4a" : "photo.png" let message = OpenClawChatMessage( role: "user", content: [ OpenClawChatMessageContent( type: "text", text: "Here is the attachment.", mimeType: nil, fileName: nil, content: nil), OpenClawChatMessageContent( type: type, text: nil, mimeType: isAudio ? "audio/mp4" : "image/png", fileName: filename, content: nil), ], timestamp: 0) let markdown = ChatTranscriptExporter.markdown( sessionTitle: "Media chat", sessionKey: "agent:main", messages: [message]) #expect(markdown == """ # Media chat ### User — 1970-01-01T00:00:00Z Here is the attachment. _[attachment: \(filename)]_ """) } @Test(arguments: ["canvas", "toolCall", "tool_result"]) func `does not export non-attachment content as media`(type: String) { let message = OpenClawChatMessage( role: "assistant", content: [ OpenClawChatMessageContent( type: type, text: nil, mimeType: "image/png", fileName: "not-an-attachment.png", content: nil), ], timestamp: 0) #expect(ChatTranscriptExporter.markdown( sessionTitle: "Media chat", sessionKey: "agent:main", messages: [message]) == "# Media chat\n") } @Test func `sanitizes filename`() { #expect( ChatTranscriptExporter.filename( sessionTitle: " Project / Q3: \"Résumé\"? ", sessionKey: "fallback") == "Project-Q3-Résumé.md") #expect( ChatTranscriptExporter.filename( sessionTitle: "///", sessionKey: "fallback") == "fallback.md") let emojiFilename = ChatTranscriptExporter.filename( sessionTitle: String(repeating: "🐾", count: 200), sessionKey: "fallback") #expect(emojiFilename.utf8.count <= 243) #expect(emojiFilename.hasSuffix(".md")) } @Test func `empty transcript contains header only`() { #expect( ChatTranscriptExporter.markdown( sessionTitle: nil, sessionKey: "agent:main", messages: []) == "# agent:main\n") } private func message(role: String, text: String, timestamp: Double) -> OpenClawChatMessage { OpenClawChatMessage( role: role, content: [ OpenClawChatMessageContent( type: "text", text: text, mimeType: nil, fileName: nil, content: nil), ], timestamp: timestamp) } }