Добавлен тест на вертикальное центрирование подписи «Временный диалог» относительно квадрата чекбокса и исправлена стилизация текста в компонентах диалога.

This commit is contained in:
2026-09-29 11:02:28 +03:00
parent 7a9f60274b
commit 2ad2d65f06
2 changed files with 101 additions and 15 deletions
@@ -10,8 +10,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -21,8 +20,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -32,12 +31,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -372,6 +373,7 @@ private fun TemporalCheckbox(temporal: Boolean) {
Row(
modifier = Modifier
.fillMaxWidth()
.testTag("temporal-checkbox")
.clip(RoundedCornerShape(10.dp))
.background(colors.panelElevated)
.padding(10.dp),
@@ -390,20 +392,32 @@ private fun TemporalCheckbox(temporal: Boolean) {
Text("✓", color = onAccent, fontSize = 13.sp, fontWeight = FontWeight.Bold)
}
}
Box(
modifier = Modifier
.weight(1f)
.height(20.dp),
contentAlignment = Alignment.CenterStart,
) {
// Однострочный текст в Compose может «проседать» вниз, если lineHeight
// не задан явно: LineHeightStyle.Alignment.Center применяется только
// при заданном lineHeight. Рецепт ровного центра: lineHeight == fontSize
// + LineHeightStyle(Center, Trim.Both).
Text(
text = "Временный диалог",
color = colors.text,
fontSize = 13.sp,
lineHeight = 13.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier
.weight(1f)
// Типографская компенсация: строчные глифы (х-высота) визуально
// «проседают» на ~1.5px вниз относительно геометрического центра
// строки из-за метрик шрифта (ascent > x-height). Сдвигаем текст
// чуть вверх, чтобы его визуальная масса совпала с центром
// квадратика чекбокса.
.offset(y = (-1.5).dp),
style = LocalTextStyle.current.copy(
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.Both,
),
),
)
}
}
}
@Composable
@@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
@@ -367,8 +368,79 @@ class DialogInfoDialogTest {
)
}
// ── helpers ────────────────────────────────────────────────────────────
/**
* Регрессия на вертикальное центрирование подписи «Временный диалог»
* относительно квадратика чекбокса. Рендерит строку в битмап и сравнивает
* вертикальные центры синего квадратика (accent) и белых глифов текста.
*/
@OptIn(ExperimentalTestApi::class)
@Test
fun `temporal checkbox label is vertically centered with the box`() = runComposeUiTest {
val conversation = sampleConversation(title = "X", temporal = true)
setContent {
withAvatarStorage {
ProvideAgentikTheme("dark".toAgentikTheme()) {
Box(Modifier.fillMaxSize().background(darkAgentikColors.background)) {
DialogInfoDialog(
conversation = conversation,
agentName = null,
agentAddress = null,
agentColor = null,
online = false,
onClose = {},
onRename = { _, _ -> },
onDelete = {},
)
}
}
}
}
val pixels = onNodeWithTag("temporal-checkbox").captureToImage().toPixelMap()
var sqMinY = Int.MAX_VALUE; var sqMaxY = -1
var sqMaxX = -1
var txMinY = Int.MAX_VALUE; var txMaxY = -1
var sawText = false
for (y in 0 until pixels.height) {
for (x in 0 until pixels.width) {
val c = pixels[x, y]
val isAccent = kotlin.math.abs(c.red - 0.416f) < 0.12f &&
kotlin.math.abs(c.green - 0.698f) < 0.12f &&
kotlin.math.abs(c.blue - 0.949f) < 0.12f
if (isAccent) {
if (y < sqMinY) sqMinY = y
if (y > sqMaxY) sqMaxY = y
if (x > sqMaxX) sqMaxX = x
}
}
}
// Текст ищем только правее квадратика, чтобы ✓ внутри квадрата не мешал.
for (y in 0 until pixels.height) {
for (x in (sqMaxX + 3) until pixels.width) {
val c = pixels[x, y]
val isText = c.red > 0.55f && c.green > 0.55f && c.blue > 0.55f &&
(maxOf(c.red, c.green, c.blue) - minOf(c.red, c.green, c.blue)) < 0.2f
if (isText) {
sawText = true
if (y < txMinY) txMinY = y
if (y > txMaxY) txMaxY = y
}
}
}
assertTrue(sawText, "глифы подписи не найдены в битмапе")
val sqCenter = (sqMinY + sqMaxY) / 2f
val txCenter = (txMinY + txMaxY) / 2f
val delta = txCenter - sqCenter
assertTrue(
kotlin.math.abs(delta) <= 1.0f,
"центр подписи (y=$txCenter) должен совпадать с центром квадратика " +
"(y=$sqCenter); смещение=${delta}px (sqY=$sqMinY..$sqMaxY, txY=$txMinY..$txMaxY)",
)
}
// ── helpers ────────────────────────────────────────────────────────────
private fun sampleConversation(
title: String = "Диалог",
temporal: Boolean = false,