Добавлен тест на вертикальное центрирование подписи «Временный диалог» относительно квадрата чекбокса и исправлена стилизация текста в компонентах диалога.
This commit is contained in:
@@ -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,19 +392,31 @@ private fun TemporalCheckbox(temporal: Boolean) {
|
||||
Text("✓", color = onAccent, fontSize = 13.sp, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
// Однострочный текст в 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)
|
||||
.height(20.dp),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
Text(
|
||||
text = "Временный диалог",
|
||||
color = colors.text,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
// Типографская компенсация: строчные глифы (х-высота) визуально
|
||||
// «проседают» на ~1.5px вниз относительно геометрического центра
|
||||
// строки из-за метрик шрифта (ascent > x-height). Сдвигаем текст
|
||||
// чуть вверх, чтобы его визуальная масса совпала с центром
|
||||
// квадратика чекбокса.
|
||||
.offset(y = (-1.5).dp),
|
||||
style = LocalTextStyle.current.copy(
|
||||
lineHeightStyle = LineHeightStyle(
|
||||
alignment = LineHeightStyle.Alignment.Center,
|
||||
trim = LineHeightStyle.Trim.Both,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user