fix(#586): route hardcoded colors through theme tokens (Material You / presets) (#592)

Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-14 00:57:12 +03:00
1 parent 1870e4bb01
commit 29b630a2ec
9 files changed
+207 -48

No files matched your search

@@ -68,6 +68,8 @@ val StatusYellowContainer = Color(0xFF3D2F0F)
val StatusBlue = Color(0xFF4DA8FF)
val StatusBlueContainer = Color(0xFF0F2A3D)
val StatusGrey = Color(0xFF9E9E9E)
val StatusGreyDark = Color(0xFF1A1A24)
val StatusGreyLight = Color(0xFFF5F5F5)
// ── Chat-specific colours (always brand-defined for consistency) ───────
@@ -2,6 +2,7 @@ package com.m57.hermescontrol.theme
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
/**
* Semantic status colour set — always brand-defined (never dynamic).
@@ -16,25 +17,58 @@ import androidx.compose.ui.graphics.Color
data class HermesStatusColors(
val success: Color,
val successContainer: Color,
val onSuccess: Color,
val warning: Color,
val warningContainer: Color,
val onWarning: Color,
val error: Color,
val errorContainer: Color,
val onError: Color,
val info: Color,
val infoContainer: Color,
val onInfo: Color,
val neutral: Color = StatusGrey,
)
/**
* Theme-aware colors for markdown/search highlighting. Built per-composition from
* [LocalHermesStatusColors] so `==mark==` and search terms follow the active preset
* instead of a fixed yellow.
*/
data class SearchHighlightColors(
val markupBackground: Color,
val searchBackground: Color,
val searchForeground: Color,
val currentSearchBackground: Color,
val currentSearchForeground: Color,
)
/** Build the highlight palette for the current theme preset. */
internal fun onColorFor(color: Color): Color = if (color.luminance() > 0.5f) StatusGreyDark else StatusGreyLight
internal fun searchHighlightColors(statusColors: HermesStatusColors): SearchHighlightColors =
SearchHighlightColors(
markupBackground = statusColors.warning.copy(alpha = 0.3f),
searchBackground = statusColors.warningContainer,
searchForeground = onColorFor(statusColors.warningContainer),
currentSearchBackground = statusColors.warning,
currentSearchForeground = statusColors.onWarning,
)
val LocalHermesStatusColors =
staticCompositionLocalOf {
HermesStatusColors(
success = StatusGreen,
successContainer = StatusGreenContainer,
onSuccess = StatusGreyLight,
warning = StatusYellow,
warningContainer = StatusYellowContainer,
onWarning = StatusGreyLight,
error = StatusRed,
errorContainer = StatusRedContainer,
onError = StatusGreyLight,
info = StatusBlue,
infoContainer = StatusBlueContainer,
onInfo = StatusGreyLight,
)
}
@@ -339,12 +339,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFFB8BB26),
successContainer = Color(0xFF32302F),
onSuccess = Color(0xFF282828),
warning = Color(0xFFFABD2F),
warningContainer = Color(0xFF3C3836),
onWarning = Color(0xFF282828),
error = Color(0xFFFB4934),
errorContainer = Color(0xFF282828),
onError = Color(0xFF282828),
info = Color(0xFF83A598),
infoContainer = Color(0xFF3C3836),
onInfo = Color(0xFF282828),
)
}
@@ -352,12 +356,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFF98971A),
successContainer = Color(0xFFEBDBB2),
onSuccess = Color(0xFFEBDBB2),
warning = Color(0xFFD79921),
warningContainer = Color(0xFFEBDBB2),
onWarning = Color(0xFFEBDBB2),
error = Color(0xFFCC241D),
errorContainer = Color(0xFFEBDBB2),
onError = Color(0xFFEBDBB2),
info = Color(0xFF458588),
infoContainer = Color(0xFFEBDBB2),
onInfo = Color(0xFFEBDBB2),
)
}
@@ -365,12 +373,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFFA6E3A1),
successContainer = Color(0xFF313244),
onSuccess = Color(0xFF11111B),
warning = Color(0xFFF9E2AF),
warningContainer = Color(0xFF313244),
onWarning = Color(0xFF11111B),
error = Color(0xFFF38BA8),
errorContainer = Color(0xFF313244),
onError = Color(0xFF11111B),
info = Color(0xFF89B4FA),
infoContainer = Color(0xFF313244),
onInfo = Color(0xFF11111B),
)
}
@@ -378,12 +390,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFF40A02B),
successContainer = Color(0xFFE6E9EF),
onSuccess = Color(0xFFE6E9EF),
warning = Color(0xFFDF8E1D),
warningContainer = Color(0xFFE6E9EF),
onWarning = Color(0xFFE6E9EF),
error = Color(0xFFD20F39),
errorContainer = Color(0xFFE6E9EF),
onError = Color(0xFFE6E9EF),
info = Color(0xFF1E66F5),
infoContainer = Color(0xFFE6E9EF),
onInfo = Color(0xFFE6E9EF),
)
}
@@ -391,12 +407,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFF6FEA8B),
successContainer = Color(0xFF0A3A14),
onSuccess = Color(0xFF0F0B1A),
warning = Color(0xFFFFB347),
warningContainer = Color(0xFF3A2000),
onWarning = Color(0xFF0F0B1A),
error = Color(0xFFFF3D68),
errorContainer = Color(0xFF3A0A14),
onError = Color(0xFF0F0B1A),
info = Color(0xFF60F0FF),
infoContainer = Color(0xFF003B40),
onInfo = Color(0xFF0F0B1A),
)
}
@@ -404,12 +424,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFF1B873A),
successContainer = Color(0xFFD7F5E0),
onSuccess = Color(0xFFFDF8FF),
warning = Color(0xFFB87800),
warningContainer = Color(0xFFFFEAB3),
onWarning = Color(0xFFFDF8FF),
error = Color(0xFFB3261E),
errorContainer = Color(0xFFF9DEDC),
onError = Color(0xFFFDF8FF),
info = Color(0xFF2E6FBD),
infoContainer = Color(0xFFD4E7FF),
onInfo = Color(0xFFFDF8FF),
)
}
@@ -417,12 +441,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = StatusGreen,
successContainer = StatusGreenContainer,
onSuccess = StatusGreyLight,
warning = StatusYellow,
warningContainer = StatusYellowContainer,
onWarning = StatusGreyLight,
error = StatusRed,
errorContainer = StatusRedContainer,
onError = StatusGreyLight,
info = StatusBlue,
infoContainer = StatusBlueContainer,
onInfo = StatusGreyLight,
)
}
@@ -430,12 +458,16 @@ fun HermesControlTheme(
HermesStatusColors(
success = Color(0xFF1B873A),
successContainer = Color(0xFFD7F5E0),
onSuccess = Color(0xFFFDF8FF),
warning = HermesAmberDark,
warningContainer = Color(0xFFFFEAB3),
onWarning = Color(0xFFFDF8FF),
error = Color(0xFFB3261E),
errorContainer = Color(0xFFF9DEDC),
onError = Color(0xFFFDF8FF),
info = Color(0xFF2E6FBD),
infoContainer = Color(0xFFD4E7FF),
onInfo = Color(0xFFFDF8FF),
)
}
}
@@ -84,13 +84,15 @@ import com.m57.hermescontrol.data.model.Attachment
import com.m57.hermescontrol.data.remote.OkHttpProvider
import com.m57.hermescontrol.theme.AssistantBubble
import com.m57.hermescontrol.theme.AssistantBubbleLight
import com.m57.hermescontrol.theme.DarkOnSurface
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.LightOnSurface
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.theme.StatusGreen
import com.m57.hermescontrol.theme.StatusRed
import com.m57.hermescontrol.theme.SystemMessageColor
import com.m57.hermescontrol.theme.ToolChipColor
import com.m57.hermescontrol.theme.ToolChipColorLight
import com.m57.hermescontrol.theme.UserBubble
import com.m57.hermescontrol.theme.onColorFor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonArray
@@ -176,10 +178,17 @@ private fun UserBubble(
}
}
val statusColors = LocalHermesStatusColors.current
val highlightedText =
remember(message.content, searchQuery, isCurrentMatch) {
remember(message.content, searchQuery, isCurrentMatch, statusColors) {
if (searchQuery.isNotBlank()) {
buildHighlightedString(message.content, searchQuery, isCurrentMatch)
buildHighlightedString(
message.content,
searchQuery,
isCurrentMatch,
statusColors,
)
} else {
AnnotatedString(message.content)
}
@@ -207,13 +216,13 @@ private fun UserBubble(
if (MaterialTheme.colorScheme.onPrimary.luminance() < 0.5f) {
MaterialTheme.colorScheme.onPrimary
} else {
Color(0xFF1A1A24)
LightOnSurface
}
} else {
if (MaterialTheme.colorScheme.onPrimary.luminance() > 0.5f) {
MaterialTheme.colorScheme.onPrimary
} else {
Color.White
DarkOnSurface
}
}
Box {
@@ -324,13 +333,13 @@ private fun AssistantBubble(
if (MaterialTheme.colorScheme.onSurface.luminance() < 0.5f) {
MaterialTheme.colorScheme.onSurface
} else {
Color(0xFF1A1A24)
LightOnSurface
}
} else {
if (MaterialTheme.colorScheme.onSurface.luminance() > 0.5f) {
MaterialTheme.colorScheme.onSurface
} else {
Color(0xFFE8E6EE)
DarkOnSurface
}
}
val clipboard = LocalClipboard.current
@@ -1719,6 +1728,7 @@ fun parseToolOutput(
private fun ExpandedToolContent(
parsed: ParsedToolData,
contentColor: Color,
statusColors: HermesStatusColors,
) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
// Summary line at top of expanded view
@@ -1753,7 +1763,7 @@ private fun ExpandedToolContent(
text = stringResource(R.string.chat_tool_stderr, it),
style =
MaterialTheme.typography.bodySmall.copy(
color = StatusRed.copy(alpha = 0.9f),
color = statusColors.error.copy(alpha = 0.9f),
fontFamily = FontFamily.Monospace,
fontSize = 11.sp,
),
@@ -1764,7 +1774,7 @@ private fun ExpandedToolContent(
text = stringResource(R.string.chat_tool_execution_error, it),
style =
MaterialTheme.typography.bodySmall.copy(
color = StatusRed,
color = statusColors.error,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
fontSize = 11.sp,
@@ -1777,7 +1787,7 @@ private fun ExpandedToolContent(
text = stringResource(R.string.chat_tool_exit_code, code),
style =
MaterialTheme.typography.labelSmall.copy(
color = StatusRed,
color = statusColors.error,
fontWeight = FontWeight.Medium,
),
)
@@ -1854,6 +1864,7 @@ private fun ToolBubble(
var showRawJson by remember { mutableStateOf(false) }
val chipColor = if (isDarkTheme) ToolChipColor else ToolChipColorLight
val contentColor = if (isDarkTheme) Color.White else Color.Black
val statusColors = LocalHermesStatusColors.current
val parsed =
remember(message.content, message.toolName, message.toolStatus) {
@@ -1892,7 +1903,7 @@ private fun ToolBubble(
.padding(horizontal = 10.dp, vertical = 6.dp),
) {
// ── Header row: icon + tool name ──
HeaderRow(message, config, contentColor)
HeaderRow(message, config, contentColor, statusColors)
// ── Tool progress preview (tool.progress) ──
if (message.toolStatus == ToolStatus.RUNNING && !message.progressPreview.isNullOrEmpty()) {
@@ -1978,7 +1989,7 @@ private fun ToolBubble(
// Clean structured expanded view
Box {
SelectionContainer {
ExpandedToolContent(parsed, contentColor)
ExpandedToolContent(parsed, contentColor, statusColors)
}
CopyButton(
visible = showCopyButton,
@@ -2048,6 +2059,7 @@ private fun HeaderRow(
message: ChatMessage,
config: ToolDisplayConfig,
contentColor: Color,
statusColors: HermesStatusColors,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -2069,8 +2081,8 @@ private fun HeaderRow(
}
val tint =
when (message.toolStatus) {
ToolStatus.COMPLETED -> StatusGreen
ToolStatus.FAILED -> StatusRed
ToolStatus.COMPLETED -> statusColors.success
ToolStatus.FAILED -> statusColors.error
else -> contentColor.copy(alpha = 0.6f)
}
Icon(
@@ -2200,8 +2212,14 @@ private fun buildHighlightedString(
text: String,
query: String,
isCurrentMatch: Boolean = false,
statusColors: HermesStatusColors,
): AnnotatedString {
val highlightColor = if (isCurrentMatch) Color(0xFFF57C00) else Color(0xFFFFF176).copy(alpha = 0.9f)
val (highlightColor, highlightText) =
if (isCurrentMatch) {
statusColors.warning to statusColors.onWarning
} else {
statusColors.warningContainer to onColorFor(statusColors.warningContainer)
}
return buildAnnotatedString {
var i = 0
while (i < text.length) {
@@ -2216,7 +2234,7 @@ private fun buildHighlightedString(
append(text.substring(i, matchEnd))
}
// Append the match highlighted
withStyle(SpanStyle(background = highlightColor, color = Color(0xFF1A1A24))) {
withStyle(SpanStyle(background = highlightColor, color = highlightText)) {
append(text.substring(matchEnd, matchEnd + query.length))
}
i = matchEnd + query.length
@@ -131,7 +131,6 @@ import com.m57.hermescontrol.data.ws.ConnectionStatus
import com.m57.hermescontrol.data.ws.HermesWsClient
import com.m57.hermescontrol.notification.NotificationHelper
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.theme.StatusRed
import com.m57.hermescontrol.ui.common.CredentialWarningBanner
import com.m57.hermescontrol.ui.common.EmptyState
import com.m57.hermescontrol.ui.common.HermesScaffold
@@ -358,7 +357,7 @@ fun ChatScreen(
Modifier
.size(10.dp)
.clip(CircleShape)
.background(StatusRed),
.background(LocalHermesStatusColors.current.error),
)
}
}
@@ -57,11 +57,13 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.m57.hermescontrol.R
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.theme.SearchHighlightColors
import com.m57.hermescontrol.theme.searchHighlightColors
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private val URL_PATTERN = Regex("""https?://[^\s)>\[\]"‘’]+""")
private val HIGHLIGHT_BG = Color(0xFFFFFF00).copy(alpha = 0.3f)
private val URL_PATTERN = Regex("""https?://[^\s)>\[\]"'‘’]+""")
private val TABLE_COL_WIDTH = 160.dp
private val FN_DEF_RE = Regex("""^\[\^([^\]]+)\]:\s*(.*)$""")
@@ -84,6 +86,8 @@ fun MarkdownText(
isCurrentMatch: Boolean = false,
modifier: Modifier = Modifier,
) {
val statusColors = LocalHermesStatusColors.current
val highlights = searchHighlightColors(statusColors)
if (isStreaming) {
Text(
text = text,
@@ -121,7 +125,7 @@ fun MarkdownText(
Text(
text =
remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor, highlights)
},
color = textColor,
style =
@@ -145,7 +149,14 @@ fun MarkdownText(
Text(
text =
remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(
block.text,
textColor,
searchQuery,
isCurrentMatch,
linkColor,
highlights,
)
},
color = textColor,
style = MaterialTheme.typography.bodyMedium,
@@ -180,7 +191,14 @@ fun MarkdownText(
Text(
text =
remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(
block.text,
textColor,
searchQuery,
isCurrentMatch,
linkColor,
highlights,
)
},
color = textColor,
style = MaterialTheme.typography.bodyMedium,
@@ -203,7 +221,14 @@ fun MarkdownText(
Text(
text =
remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(
block.text,
textColor,
searchQuery,
isCurrentMatch,
linkColor,
highlights,
)
},
color = textColor,
style = MaterialTheme.typography.bodyMedium,
@@ -229,7 +254,14 @@ fun MarkdownText(
Text(
text =
remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(
block.text,
textColor,
searchQuery,
isCurrentMatch,
linkColor,
highlights,
)
},
color = textColor,
style = MaterialTheme.typography.bodyMedium.copy(fontStyle = FontStyle.Italic),
@@ -283,7 +315,14 @@ fun MarkdownText(
Text(
text =
remember(note.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(note.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(
note.text,
textColor,
searchQuery,
isCurrentMatch,
linkColor,
highlights,
)
},
color = textColor,
style = MaterialTheme.typography.bodySmall,
@@ -298,7 +337,7 @@ fun MarkdownText(
Text(
text =
remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) {
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor)
parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor, highlights)
},
color = textColor,
style = MaterialTheme.typography.bodyMedium,
@@ -663,9 +702,14 @@ internal fun parseInline(
searchQuery: String,
isCurrentMatch: Boolean,
linkColor: Color,
highlights: SearchHighlightColors,
): AnnotatedString {
val searchHighlightColor =
if (isCurrentMatch) Color(0xFFF57C00) else Color(0xFFFFF176).copy(alpha = 0.9f)
if (isCurrentMatch) {
highlights.currentSearchBackground to highlights.currentSearchForeground
} else {
highlights.searchBackground to highlights.searchForeground
}
return buildAnnotatedString {
var i = 0
@@ -732,7 +776,7 @@ internal fun parseInline(
src.startsWith("==", i) -> {
val end = src.indexOf("==", i + 2)
if (end != -1) {
withStyle(SpanStyle(background = HIGHLIGHT_BG)) {
withStyle(SpanStyle(background = highlights.markupBackground)) {
append(src.substring(i + 2, end))
}
i = end + 2
@@ -881,8 +925,8 @@ internal fun parseInline(
src.regionMatches(i, searchQuery, 0, searchQuery.length, ignoreCase = true) -> {
withStyle(
SpanStyle(
background = searchHighlightColor,
color = Color(0xFF1A1A24),
background = searchHighlightColor.first,
color = searchHighlightColor.second,
),
) {
append(src.substring(i, i + searchQuery.length))
@@ -29,13 +29,13 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.m57.hermescontrol.R
import com.m57.hermescontrol.theme.LocalHermesStatusColors
import com.m57.hermescontrol.ui.common.ErrorState
import com.m57.hermescontrol.ui.common.HermesScaffold
import com.m57.hermescontrol.ui.common.LoadingState
@@ -50,6 +50,7 @@ fun GatewayScreen(
viewModel: GatewayViewModel = viewModel { GatewayViewModel() },
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val statusColors = LocalHermesStatusColors.current
LaunchedEffect(Unit) {
viewModel.loadStatus()
@@ -188,8 +189,8 @@ fun GatewayScreen(
enabled = !isRunning && !state.isActionRunning,
colors =
ButtonDefaults.buttonColors(
containerColor = Color(0xFF4CAF50), // Green for start
contentColor = Color.White,
containerColor = statusColors.success,
contentColor = statusColors.onSuccess,
),
) {
Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null)
@@ -203,8 +204,8 @@ fun GatewayScreen(
enabled = isRunning && !state.isActionRunning,
colors =
ButtonDefaults.buttonColors(
containerColor = Color(0xFFF44336), // Red for stop
contentColor = Color.White,
containerColor = statusColors.error,
contentColor = statusColors.onError,
),
) {
Text(stringResource(R.string.gateway_action_stop))
@@ -270,9 +271,9 @@ fun GatewayScreen(
val stateText = pStatus?.state?.uppercase() ?: "UNKNOWN"
val badgeColor =
when (stateText) {
"RUNNING" -> Color(0xFF4CAF50)
"STOPPED" -> Color(0xFFF44336)
"ERROR" -> Color(0xFFFF9800)
"RUNNING" -> statusColors.success
"STOPPED" -> statusColors.error
"ERROR" -> statusColors.warning
else -> MaterialTheme.colorScheme.onSurfaceVariant
}
Text(
@@ -575,7 +575,7 @@ fun SessionsScreen(
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
strokeWidth = 2.dp,
color = Color.White,
color = statusColors.onError,
)
} else {
Icon(
@@ -1,11 +1,39 @@
package com.m57.hermescontrol.ui.chat
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.StatusBlue
import com.m57.hermescontrol.theme.StatusBlueContainer
import com.m57.hermescontrol.theme.StatusGreen
import com.m57.hermescontrol.theme.StatusGreenContainer
import com.m57.hermescontrol.theme.StatusRed
import com.m57.hermescontrol.theme.StatusRedContainer
import com.m57.hermescontrol.theme.StatusYellow
import com.m57.hermescontrol.theme.StatusYellowContainer
import com.m57.hermescontrol.theme.searchHighlightColors
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
private val DEFAULT_HIGHLIGHTS =
searchHighlightColors(
HermesStatusColors(
success = StatusGreen,
successContainer = StatusGreenContainer,
onSuccess = Color.White,
warning = StatusYellow,
warningContainer = StatusYellowContainer,
onWarning = Color.White,
error = StatusRed,
errorContainer = StatusRedContainer,
onError = Color.White,
info = StatusBlue,
infoContainer = StatusBlueContainer,
onInfo = Color.White,
),
)
/**
* Verifies the hand-rolled Markdown parser covers the feature set requested for issue #572
* (math excluded by decision). Each test asserts the block/inline structure actually parses —
@@ -47,7 +75,7 @@ class MarkdownTextFeatureTest {
// 3. STRIKETHROUGH
@Test
fun testStrikethrough_parses() {
val an = parseInline("~~gone~~", Color.Black, "", false, Color.Blue)
val an = parseInline("~~gone~~", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertEquals("gone", an.toString())
assertTrue(an.spanStyles.any { it.item.textDecoration != null })
}
@@ -55,7 +83,7 @@ class MarkdownTextFeatureTest {
// 4. BOLD + ITALIC in same word (***bolditalic***)
@Test
fun testBoldItalic_combined() {
val an = parseInline("***both***", Color.Black, "", false, Color.Blue)
val an = parseInline("***both***", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertEquals("both", an.toString())
assertTrue(an.spanStyles.isNotEmpty())
}
@@ -128,7 +156,7 @@ class MarkdownTextFeatureTest {
// 9. HIGHLIGHT
@Test
fun testHighlight_parses() {
val an = parseInline("==mark==", Color.Black, "", false, Color.Blue)
val an = parseInline("==mark==", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertEquals("mark", an.toString())
assertTrue(an.spanStyles.any { it.item.background != null })
}
@@ -136,16 +164,17 @@ class MarkdownTextFeatureTest {
// 10. SUPERSCRIPT / SUBSCRIPT
@Test
fun testSuperscriptAndSubscript_parse() {
val sup = parseInline("x^2^", Color.Black, "", false, Color.Blue)
val sup = parseInline("x^2^", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertTrue(sup.spanStyles.any { it.item.baselineShift != null })
val sub = parseInline("H~2~O", Color.Black, "", false, Color.Blue)
val sub = parseInline("H~2~O", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertTrue(sub.spanStyles.any { it.item.baselineShift != null })
}
// 11. KEYBOARD KEYS <kbd>
@Test
fun testKbd_parses() {
val an = parseInline("Press <kbd>Ctrl</kbd>+<kbd>C</kbd>", Color.Black, "", false, Color.Blue)
val an =
parseInline("Press <kbd>Ctrl</kbd>+<kbd>C</kbd>", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertTrue(an.toString().contains("Ctrl"))
assertTrue(an.toString().contains("C"))
assertTrue(an.spanStyles.any { it.item.fontFamily != null })
@@ -161,7 +190,7 @@ class MarkdownTextFeatureTest {
// --- regression: streaming gate handled in composable, parser must not split inline code ---
@Test
fun testInlineCode_preserved() {
val an = parseInline("use `val x = 1` here", Color.Black, "", false, Color.Blue)
val an = parseInline("use `val x = 1` here", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS)
assertEquals("use val x = 1 here", an.toString())
assertTrue(an.spanStyles.any { it.item.fontFamily != null })
}