Files
agentik-desktop/src/jvmTest/kotlin/pw/binom/agentik/desktop/ui/ThemeRenderTest.kt
T

148 lines
6.4 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package pw.binom.agentik.desktop.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.runComposeUiTest
import androidx.compose.ui.unit.dp
import pw.binom.agentik.desktop.ui.theme.AgentikColors
import pw.binom.agentik.desktop.ui.theme.AgentikTheme
import pw.binom.agentik.desktop.ui.theme.darkAgentikColors
import pw.binom.agentik.desktop.ui.theme.lightAgentikColors
import pw.binom.agentik.desktop.ui.theme.ProvideAgentikTheme
import pw.binom.agentik.desktop.ui.theme.toAgentikTheme
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Палитра и реальный рендер Compose без открытия окна.
*
* Зачем: светлая тема до сих пор проверялась только глазами по скриншоту, а
* markdown-код-блок изначально хардкодил тёмный фон (`#1E1E1E`), из-за чего в
* светлой теме выглядел чужеродно. Здесь тема прогоняется через настоящий
* оффскрин-рендер, а цвета сверяются с проектными токенами.
*/
class ThemeRenderTest {
// ── токены (THEMES.md / sketches/style.css) ────────────────────────────
@Test
fun darkPaletteMatchesDesignTokens() {
val c = darkAgentikColors
assertEquals(Color(0xFF121218), c.background)
assertEquals(Color(0xFF17212B), c.panel)
assertEquals(Color(0xFF1C2733), c.panelElevated)
assertEquals(Color(0xFF6AB2F2), c.accent)
assertEquals(Color(0xFFEBEBEB), c.text)
assertEquals(Color(0xFF768C9E), c.textDim)
assertEquals(Color(0xFFEC3942), c.error)
assertEquals(Color(0xFF2B5278), c.userBubble)
}
@Test
fun lightPaletteKeepsSemanticSlots() {
val c = lightAgentikColors
assertEquals(Color(0xFFF5F6F8), c.background)
assertEquals(Color(0xFFFFFFFF), c.panel)
assertEquals(Color(0xFF2A6FB5), c.accent)
assertEquals(Color(0xFF1A1D23), c.text)
// светлый фон обязан быть светлым: контраст к тексту
assertTrue(luminance(c.background) > luminance(c.text), "фон светлее текста")
assertTrue(luminance(c.panel) > luminance(c.panelElevated), "panel светлее panelElevated")
assertTrue(luminance(c.botBubble) > luminance(c.text), "бот-бабл светлее текста")
}
@Test
fun opaqueSlotsStayOpaqueAndScrimTransparent() {
for (c in listOf(darkAgentikColors, lightAgentikColors)) {
assertEquals(1f, c.background.alpha)
assertEquals(1f, c.panel.alpha)
assertEquals(1f, c.text.alpha)
assertEquals(1f, c.accent.alpha)
// scrim/selection — прозрачные по замыслу
assertTrue(c.scrim.alpha < 1f)
assertTrue(c.selection.alpha < 1f)
}
}
// ── рендер ─────────────────────────────────────────────────────────────
@OptIn(ExperimentalTestApi::class)
@Test
fun codeBlockPaintsThemeCodeBackgroundInLightTheme() = runComposeUiTest {
setContent {
ProvideAgentikTheme("light".toAgentikTheme()) {
Box(Modifier.fillMaxSize().background(AgentikTheme.colors.botBubble).padding(8.dp)) {
Column {
renderMarkdownSegments(content = "```kotlin\nval x = 1\n```")
}
}
}
}
val pixels = onRoot().captureToImage().toPixelMap()
val histogram = HashMap<Color, Int>()
for (y in 0 until pixels.height) {
for (x in 0 until pixels.width) {
val color = pixels[x, y]
histogram[color] = (histogram[color] ?: 0) + 1
}
}
val top = histogram.entries.sortedByDescending { it.value }.take(4)
.joinToString { "#${it.key.toHexArgb()}×${it.value}" }
assertTrue(
histogram.containsKey(lightAgentikColors.codeBackground),
"код-блок должен краситься в codeBackground светлой темы, а покрасился в: $top",
)
assertFalse(
histogram.containsKey(Color(0xFF1E1E1E)),
"тёмный хардкод код-фона протёк в светлую тему: $top",
)
}
@OptIn(ExperimentalTestApi::class)
@Test
fun darkThemeStillRendersCodeBlockDark() = runComposeUiTest {
setContent {
ProvideAgentikTheme("dark".toAgentikTheme()) {
Box(Modifier.fillMaxSize().background(AgentikTheme.colors.botBubble).padding(8.dp)) {
Column {
renderMarkdownSegments(content = "```kotlin\nval x = 1\n```")
}
}
}
}
val pixels = onRoot().captureToImage().toPixelMap()
var found = false
for (y in 0 until pixels.height) {
for (x in 0 until pixels.width) {
if (pixels[x, y] == darkAgentikColors.codeBackground) {
found = true
break
}
}
}
assertTrue(found, "в тёмной теме код-блок должен краситься в dark codeBackground")
}
private fun luminance(color: Color): Float =
0.2126f * color.red + 0.7152f * color.green + 0.0722f * color.blue
private fun Color.toHexArgb(): String {
val a = (alpha * 255).toInt()
val r = (red * 255).toInt()
val g = (green * 255).toInt()
val b = (blue * 255).toInt()
return "%02X%02X%02X%02X".format(a, r, g, b)
}
}