ui-ux-pro-max-skill/cli/assets/scripts/tests/test_text_layout_resilience.py
Viet Tran a38d04c3d5
feat(search): overhaul relevance and curated design data
Overhaul BM25 relevance, reasoning and data-quality contracts; refresh UI styles and framework guidance; add resilient text, chip, badge and micro-interaction guidance; strengthen release, provenance and catalog refresh gates; update bilingual documentation.
2026-08-14 00:08:23 +07:00

121 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""Canonical regression contracts for resilient UI text layouts."""
import csv
import sys
import unittest
from pathlib import Path
SCRIPTS_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = SCRIPTS_DIR.parent / "data"
sys.path.insert(0, str(SCRIPTS_DIR))
from core import search, search_stack # noqa: E402
def read_rows(relative_path):
with (DATA_DIR / relative_path).open(encoding="utf-8", newline="") as handle:
return list(csv.DictReader(handle))
UX_PHRASES = {
"Heading Line Balance": ("progressive visual heuristic", "natural-wrap fallback"),
"Long Token Wrapping": ("overflow-wrap anywhere", "text children shrink"),
"Text Reflow and Spacing": ("narrow widths zoom", "content-driven height"),
"Essential Text Truncation": ("complete access", "visible full-detail path"),
"Compact Label Semantics": ("badges communicate state", "meaning and ownership"),
"Chip Collection Reflow": ("filter chips", "operable +n disclosure"),
"Compact Label Overflow": ("stay whole on one line", "keyboard pointer and touch"),
"Compact Control Semantics": ("native role", "pressed or selected state"),
"Contextual Live Badge Updates": ("meaningful contextual status", "atomic status"),
"Cancellable State Transitions": ("interrupt an in-flight transition", "final semantic state"),
}
TAILWIND_PHRASES = {
"Balanced heading wrapping": ("text-balance", "natural wrapping fallback"),
"Long token resilience": ("wrap-anywhere", "min-w-0"),
"Compact label layout": ("flex flex-wrap gap-2", "whitespace-nowrap", "shrink-0"),
}
class TestTextLayoutRetrieval(unittest.TestCase):
def test_locked_queries_return_the_canonical_identity_first(self):
cases = (
("orphan heading line balance", "Heading Line Balance"),
("long url token breaks layout", "Long Token Wrapping"),
("badge chip label wraps to second line", "Compact Label Overflow"),
("filter chip collection reflow hidden values", "Chip Collection Reflow"),
("live badge count screen reader", "Contextual Live Badge Updates"),
("rapid chip animation interrupted", "Cancellable State Transitions"),
)
for query, expected in cases:
with self.subTest(query=query):
result = search(query, domain="ux", max_results=3, diagnostics=True)
actual = [row.get("Issue") for row in result["results"]]
self.assertTrue(actual, result.get("diagnostics"))
self.assertEqual(expected, actual[0], f"ranking={actual!r}")
def test_tailwind_query_returns_compact_label_layout_first(self):
result = search_stack(
"chip badge overflow nowrap", "html-tailwind",
max_results=3, diagnostics=True,
)
actual = [row.get("Guideline") for row in result["results"]]
self.assertTrue(actual, result.get("diagnostics"))
self.assertEqual("Compact label layout", actual[0], f"ranking={actual!r}")
class TestTextLayoutDataContracts(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ux = read_rows("ux-guidelines.csv")
cls.tailwind = read_rows("stacks/html-tailwind.csv")
def test_new_ux_rows_are_unique_sequential_and_keep_critical_guidance(self):
matches = [row for row in self.ux if row["Issue"] in UX_PHRASES]
self.assertEqual(len(UX_PHRASES), len(matches))
self.assertEqual(len(matches), len({row["Issue"] for row in matches}))
self.assertEqual(list(range(110, 120)), [int(row["No"]) for row in matches])
for row in matches:
with self.subTest(issue=row["Issue"]):
text = " ".join(row.values()).casefold()
for phrase in UX_PHRASES[row["Issue"]]:
self.assertIn(phrase.casefold(), text)
self.assertIn(row["Severity"], {"Medium", "High", "Critical"})
def test_new_tailwind_rows_are_unique_current_and_keep_required_utilities(self):
matches = [row for row in self.tailwind if row["Guideline"] in TAILWIND_PHRASES]
self.assertEqual(len(TAILWIND_PHRASES), len(matches))
self.assertEqual(len(matches), len({row["Guideline"] for row in matches}))
self.assertEqual([57, 58, 59], [int(row["No"]) for row in matches])
for row in matches:
with self.subTest(guideline=row["Guideline"]):
text = " ".join(row.values()).casefold()
for phrase in TAILWIND_PHRASES[row["Guideline"]]:
self.assertIn(phrase.casefold(), text)
self.assertEqual("active", row["Status"])
self.assertEqual("html-tailwind 4.3", row["Applies To"])
self.assertEqual("2026-08-13", row["Verified At"])
def test_refined_rows_are_context_sensitive_not_universal_recipes(self):
expected = {
"8": ("depends on distance complexity platform", "shared motion tokens"),
"14": ("match how an element changes speed", "linear for constant-rate progress"),
"19": ("badges validation text", "stable content-driven container"),
"78": ("avoid flashing for near-instant work", "platform and component guidance"),
}
forbidden = ("use 150-300ms", "operations > 300ms", "linear motion feels robotic")
by_number = {row["No"]: row for row in self.ux}
for number, phrases in expected.items():
with self.subTest(row=number):
row = by_number[number]
guidance = " ".join((row["Description"], row["Do"])).casefold()
for phrase in phrases:
self.assertIn(phrase, guidance)
for claim in forbidden:
self.assertNotIn(claim, guidance)
if __name__ == "__main__":
unittest.main()