test(dialog): cover HA theme and enlarged text (#609)

Extend the authentic pinned-HA diagnostic with explicit light and dark palettes, a 32 px root-font case, one-scroller geometry assertions, and reviewed evidence images. Correct stale AC references in the mutation descriptions.

Issue: #609
User-Visible: no
This commit is contained in:
Claude
2026-09-23 03:55:27 +03:00
parent 52a8f58909
commit e8e5c863e4
7 changed files with 65 additions and 6 deletions
+5 -2
View File
@@ -31,9 +31,12 @@ It opens the real Room settings form and checks the settled WebAwesome surface:
560 px desktop width, the 940 px/viewport height cap, zero duplicate HA body
padding, one HA-owned scroller, a distinct form canvas, footer containment and
edge-to-edge layouts at 480×800 and 390×844. It also keeps HA's own fullscreen
behaviour for a 1280×480 viewport. `--capture` writes the two reviewed diagnostic
behaviour for a 1280×480 viewport. `--capture` writes the three reviewed diagnostic
images under `docs/design/600-settings-dialogs/pairs/`; without it the command
is read-only. The fast Validate witness is
is read-only. The authentic diagnostic explicitly applies the light palette,
then repeats desktop geometry in the dark palette with a 32 px root font and
proves that canvas contrast, the single HA scroller and the footer survive.
The fast Validate witness is
`demo/smoke_ha_form_shell_parity.mjs`; neither diagnostic is a golden producer.
From a checkout with the usual `npm ci` dependencies and Playwright Chromium:
+54
View File
@@ -55,9 +55,15 @@ const inspect = async () => page.evaluate(() => {
const cardStyle = getComputedStyle(formCard);
const panelBox = panel.getBoundingClientRect();
const overflowNodes = [];
const scrollOwners = [];
const visit = (root) => {
for (const node of root.querySelectorAll('*')) {
const box = node.getBoundingClientRect();
const style = getComputedStyle(node);
if (node.scrollHeight > node.clientHeight + 1 && /auto|scroll/.test(style.overflowY)) {
scrollOwners.push({ tag: node.localName, id: node.id, className: String(node.className || ''),
scrollHeight: node.scrollHeight, clientHeight: node.clientHeight });
}
// wa-dialog itself stays in document flow at the demo host's 8 px page
// inset; its rendered native dialog is fixed and is the geometry users see.
if (node.localName !== 'wa-dialog' && box.width > 0
@@ -85,12 +91,32 @@ const inspect = async () => page.evaluate(() => {
formBodyOverflowY: formBodyStyle.overflowY,
formBodyBackground: formBodyStyle.backgroundColor,
cardBackground: cardStyle.backgroundColor,
rootFontSize: getComputedStyle(document.documentElement).fontSize,
colorScheme: getComputedStyle(document.documentElement).colorScheme,
scrollOwners,
horizontalOverflow: overflowNodes.length > 0,
documentWidth: [document.documentElement.scrollWidth, document.body.scrollWidth],
overflowNodes: overflowNodes.slice(0, 20),
};
});
const setPresentation = async ({ theme, textSize }) => page.evaluate(async ({ theme, textSize }) => {
const dark = theme === 'dark';
const colors = dark ? ['#3ea6ff', '#e6e7eb', '#9aa4ad', '#202126', '#3a3d45', '#11151b', '#2b2d33']
: ['#0b73b8', '#202124', '#5f6368', '#ffffff', '#d7d9de', '#eef1f4', '#e7eaee'];
['primary-color', 'primary-text-color', 'secondary-text-color', 'card-background-color',
'divider-color', 'primary-background-color', 'secondary-background-color']
.forEach((key, index) => document.documentElement.style.setProperty(`--${key}`, colors[index]));
document.documentElement.style.setProperty('--ha-card-background', colors[3]);
document.documentElement.style.colorScheme = theme;
document.documentElement.style.fontSize = `${textSize}px`;
document.body.style.background = colors[5];
const card = window.__card;
card.hass = { ...card.hass, themes: { ...card.hass?.themes, darkMode: dark } };
card.requestUpdate();
await card.updateComplete;
}, { theme, textSize });
const clipPanel = async (name, state) => {
if (!capture) return;
const clip = {
@@ -122,6 +148,7 @@ try {
return ha?.open === true
&& ha.shadowRoot.querySelector('wa-dialog')?.shadowRoot.querySelector('dialog')?.matches(':modal');
});
await setPresentation({ theme: 'light', textSize: 16 });
await settle();
const desktop = await inspect();
@@ -134,10 +161,36 @@ try {
assert.equal(desktop.haBodyOverflowY, 'auto');
assert.equal(desktop.formBodyOverflowY, 'visible');
assert.notEqual(desktop.formBodyBackground, desktop.cardBackground);
assert.equal(desktop.cardBackground, 'rgb(255, 255, 255)');
assert.ok(desktop.colorScheme.includes('light'), `desktop scheme ${desktop.colorScheme}`);
assert.equal(desktop.rootFontSize, '16px');
assert.equal(desktop.scrollOwners.length, 1, JSON.stringify(desktop.scrollOwners));
assert.ok(desktop.haBodyScroll[0] > desktop.haBodyScroll[1], `desktop scroll ${desktop.haBodyScroll}`);
assert.ok(desktop.footer.bottom <= desktop.panel.bottom + 1);
await clipPanel('room-ha-light.png', desktop);
await setPresentation({ theme: 'dark', textSize: 32 });
await settle();
const darkText200 = await inspect();
assert.ok(Math.abs(darkText200.panel.width - 560) <= 1,
`dark 200% width ${darkText200.panel.width}`);
assert.ok(darkText200.panel.height <= 941,
`dark 200% height ${darkText200.panel.height}`);
assert.equal(darkText200.rootFontSize, '32px');
assert.ok(darkText200.colorScheme.includes('dark'), `dark scheme ${darkText200.colorScheme}`);
assert.equal(darkText200.cardBackground, 'rgb(32, 33, 38)');
assert.notEqual(darkText200.formBodyBackground, darkText200.cardBackground);
assert.equal(darkText200.haBodyOverflowY, 'auto');
assert.equal(darkText200.formBodyOverflowY, 'visible');
assert.equal(darkText200.scrollOwners.length, 1, JSON.stringify(darkText200.scrollOwners));
assert.ok(darkText200.haBodyScroll[0] > darkText200.haBodyScroll[1],
`dark 200% scroll ${darkText200.haBodyScroll}`);
assert.equal(darkText200.horizontalOverflow, false, JSON.stringify(darkText200.overflowNodes));
assert.ok(darkText200.footer.bottom <= darkText200.panel.bottom + 1);
await clipPanel('room-ha-dark-text-200.png', darkText200);
await setPresentation({ theme: 'light', textSize: 16 });
await page.setViewportSize({ width: 480, height: 800 });
await settle();
const edge = await inspect();
@@ -170,6 +223,7 @@ try {
authenticHaFrontend: fixture.provenance,
capture,
desktop,
darkText200,
edge480: edge,
mobile390: mobile,
shortViewport: short,
@@ -51,12 +51,14 @@ node demo/verify_ha_form_shell_609.mjs --capture
| Режим | Поверхность | Проверенный результат |
|---|---:|---|
| desktop 1600×1000 | 560×940 | канва вокруг карточек, один скроллер `.body` HA, footer внутри |
| dark + text 200%, 1600×1000 | 560×940 | root font 32 px, один HA-скроллер, footer внутри, канва не сливается с карточками |
| граница 480×800 | 480×800 | fullscreen, радиус 0, горизонтального переполнения нет |
| mobile 390×844 | 390×844 | fullscreen, радиус 0, footer внутри |
| низкое окно 1280×480 | 1280×480 | штатный fullscreen HA сохранён |
Снимки приёмки: `pairs/room-ha-light.png` и
`pairs/room-ha-mobile-light.png`. Это диагностические доказательства, а не
Снимки приёмки: `pairs/room-ha-light.png`,
`pairs/room-ha-dark-text-200.png` и `pairs/room-ha-mobile-light.png`. Это
диагностические доказательства, а не
golden-базы; 13 нативных golden-сцен остаются неизменными. Быстрый обязательный
свидетель тех же публичных CSS-контрактов —
`demo/smoke_ha_form_shell_parity.mjs`, а перечень пяти потребителей оболочки
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 33 KiB

+2 -2
View File
@@ -75,7 +75,7 @@ const MUTANT_DEFINITIONS = [
{
id: 'ha-form-shell-width-falls-back-to-generic',
guard: 'node demo/smoke_ha_form_shell_parity.mjs',
because: '#609 AC1/AC8: the authentic HA branch must consume the reviewed 560 px form '
because: '#609 AC1: the authentic HA branch must consume the reviewed 560 px form '
+ 'width; falling back to HA generic medium width recreates the released/native mismatch',
patches: [{
file: 'src/hp-dialog.ts',
@@ -97,7 +97,7 @@ const MUTANT_DEFINITIONS = [
{
id: 'ha-form-shell-mobile-keeps-desktop-inset',
guard: 'node demo/smoke_ha_form_shell_parity.mjs',
because: '#609 AC4/AC8: at 480 px and below the form shell is edge-to-edge; retaining the '
because: '#609 AC4: at 480 px and below the form shell is edge-to-edge; retaining the '
+ 'desktop 48 px height inset clips the reviewed mobile surface and footer',
patches: [{
file: 'src/hp-dialog.ts',