Добавлен тест на вертикальное центрирование подписи «Временный диалог» относительно квадрата чекбокса и исправлена стилизация текста в компонентах диалога.
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.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.requiredHeight
|
import androidx.compose.foundation.layout.requiredHeight
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -32,12 +31,14 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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.text.input.ImeAction
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -372,6 +373,7 @@ private fun TemporalCheckbox(temporal: Boolean) {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.testTag("temporal-checkbox")
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(colors.panelElevated)
|
.background(colors.panelElevated)
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
@@ -390,19 +392,31 @@ private fun TemporalCheckbox(temporal: Boolean) {
|
|||||||
Text("✓", color = onAccent, fontSize = 13.sp, fontWeight = FontWeight.Bold)
|
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
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.height(20.dp),
|
// Типографская компенсация: строчные глифы (х-высота) визуально
|
||||||
contentAlignment = Alignment.CenterStart,
|
// «проседают» на ~1.5px вниз относительно геометрического центра
|
||||||
) {
|
// строки из-за метрик шрифта (ascent > x-height). Сдвигаем текст
|
||||||
Text(
|
// чуть вверх, чтобы его визуальная масса совпала с центром
|
||||||
text = "Временный диалог",
|
// квадратика чекбокса.
|
||||||
color = colors.text,
|
.offset(y = (-1.5).dp),
|
||||||
fontSize = 13.sp,
|
style = LocalTextStyle.current.copy(
|
||||||
fontWeight = FontWeight.Medium,
|
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.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.assertTextEquals
|
import androidx.compose.ui.test.assertTextEquals
|
||||||
import androidx.compose.ui.test.captureToImage
|
import androidx.compose.ui.test.captureToImage
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.onRoot
|
import androidx.compose.ui.test.onRoot
|
||||||
import androidx.compose.ui.test.performClick
|
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(
|
private fun sampleConversation(
|
||||||
title: String = "Диалог",
|
title: String = "Диалог",
|
||||||
temporal: Boolean = false,
|
temporal: Boolean = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user