package com.sptmobile.render
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class MarkdownTest {
// Distinct sentinel colors so span assertions are unambiguous.
private val styles = MarkdownStyles(
codeBg = Color(0xFF111111),
codeFg = Color(0xFF222222),
link = Color(0xFF333333),
quote = Color(0xFF444444),
xmlTag = Color(0xFF555555),
xmlAttr = Color(0xFF666666),
xmlValue = Color(0xFF777777),
xmlComment = Color(0xFF888888),
)
// [unit->REQ-RICH-RENDER]
// Inline markers collapse to plain text; the emphasis rides as spans.
@Test
fun inlineEmphasisRendersToPlainTextWithSpans() {
val a = renderMarkdown("a **b** _c_ `d`", styles)
assertEquals("a b c d", a.text)
assertTrue(
"bold span over 'b'",
a.spanStyles.any { it.item.fontWeight == FontWeight.Bold && a.text.substring(it.start, it.end) == "b" },
)
assertTrue(
"italic span over 'c'",
a.spanStyles.any { it.item.fontStyle == FontStyle.Italic && a.text.substring(it.start, it.end) == "c" },
)
assertTrue(
"code span carries the code fg",
a.spanStyles.any { it.item.color == styles.codeFg && a.text.substring(it.start, it.end) == "d" },
)
}
// [unit->REQ-RICH-RENDER]
// Unmatched markers are literal — arbitrary agent text never breaks.
@Test
fun unmatchedMarkersRenderLiterally() {
assertEquals("a * b _ c", renderMarkdown("a * b _ c", styles).text)
}
// [unit->REQ-RICH-RENDER]
// A link's [text] shows; the (url) is stripped and the text is colored.
@Test
fun linkTextShowsUrlStripped() {
val a = renderMarkdown("see [docs](https://x.y)", styles)
assertEquals("see docs", a.text)
assertTrue(a.spanStyles.any { it.item.color == styles.link && a.text.substring(it.start, it.end) == "docs" })
}
// [unit->REQ-RICH-RENDER]
// Headings and list markers become readable prefixes/emphasis, not literals.
@Test
fun headingAndListPrefixes() {
assertEquals("Title", renderMarkdown("## Title", styles).text)
assertEquals(" • one\n • two", renderMarkdown("- one\n- two", styles).text)
assertEquals(" 1. first", renderMarkdown("1. first", styles).text)
}
// [unit->REQ-RICH-RENDER]
// A fenced ```xml block is emitted verbatim and colored by highlightXml.
@Test
fun fencedXmlBlockIsHighlighted() {
val src = "```xml\nx\n```"
val a = renderMarkdown(src, styles)
assertEquals("x", a.text)
assertTrue("tag punctuation/name colored", a.spanStyles.any { it.item.color == styles.xmlTag })
assertTrue("attr name colored", a.spanStyles.any { it.item.color == styles.xmlAttr })
assertTrue("attr value colored", a.spanStyles.any { it.item.color == styles.xmlValue })
}
// [unit->REQ-RICH-RENDER]
// highlightXml tokenizes tag / attr / value / comment; malformed is safe.
@Test
fun highlightXmlTokenizesAndToleratesMalformed() {
val a = highlightXml("t", styles)
assertEquals("t", a.text)
assertTrue(a.spanStyles.any { it.item.color == styles.xmlComment && a.text.substring(it.start, it.end) == "" })
// An unclosed tag must not throw and must preserve the text.
assertEquals("