Assert the panel motion contract and hard rules, not just build them
The confirm dialog (fade + scale .96->1 over --nq-dur-modal/--nq-ease-out), the seal (fade + scale 1.15->1 over --nq-dur-base), the quest-complete 12 s countdown return and the 40 s idle return already matched PANEL-SPEC, but nothing pinned them: a token or keyframe edit could silently drift the panel off the approved mockup timing. tests/test_panel_cards.py now asserts the entrance timing and easing, the byte-identical served token copy, and the section 1/6 hard rules (22px body floor, token-only inks, no hover states, no animation loops, inert dock strip, whole-plate tap target, control-free away plate), and the quest-log harness drives the 1c countdown return end-to-end against the bundle HACS ships.
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
*
|
||||
* Usage: node render-quest-log.mjs < spec.json
|
||||
*
|
||||
* The spec JSON carries {bundle, url, config, hass, idle_ms?}; the card is
|
||||
* mounted at the given URL (it resolves its child from the pathname's last
|
||||
* segment), and after the optional idle wait stdout receives one JSON object
|
||||
* {location, headline, html} — the window pathname and the rendered title.
|
||||
* The Python panel tests drive this harness so the assertions run against
|
||||
* the exact bundle HACS ships.
|
||||
* The spec JSON carries {bundle, url, config, hass, idle_ms?, probe_ms?};
|
||||
* the card is mounted at the given URL (it resolves its child from the
|
||||
* pathname's last segment), after the optional probe wait one snapshot of
|
||||
* the render is taken mid-wait, then after the optional idle wait stdout
|
||||
* receives one JSON object {location, headline, countdown, html, probe} —
|
||||
* the final window pathname, the rendered title, the complete-screen
|
||||
* countdown text, the rendered shadow DOM, and the probe snapshot when
|
||||
* probe_ms was given. The Python panel tests drive this harness so the
|
||||
* assertions run against the exact bundle HACS ships.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -46,14 +49,25 @@ element.hass = spec.hass;
|
||||
document.body.appendChild(element);
|
||||
await element.updateComplete;
|
||||
|
||||
const snapshot = () => ({
|
||||
location: window.location.pathname,
|
||||
headline: element.shadowRoot.querySelector(".title")?.textContent?.trim() ?? "",
|
||||
countdown:
|
||||
element.shadowRoot.querySelector(".complete-countdown")?.textContent?.trim() ??
|
||||
"",
|
||||
html: element.shadowRoot.innerHTML,
|
||||
});
|
||||
|
||||
let probe = null;
|
||||
if (spec.probe_ms !== undefined) {
|
||||
await new Promise((resolve) => setTimeout(resolve, spec.probe_ms));
|
||||
probe = snapshot();
|
||||
}
|
||||
|
||||
if (spec.idle_ms !== undefined) {
|
||||
await new Promise((resolve) => setTimeout(resolve, spec.idle_ms));
|
||||
}
|
||||
|
||||
const payload = JSON.stringify({
|
||||
location: window.location.pathname,
|
||||
headline: element.shadowRoot.querySelector(".title")?.textContent?.trim() ?? "",
|
||||
html: element.shadowRoot.innerHTML,
|
||||
});
|
||||
const payload = JSON.stringify({ ...snapshot(), probe });
|
||||
await new Promise((resolve) => process.stdout.write(payload, () => resolve()));
|
||||
process.exit(0);
|
||||
Reference in New Issue
Block a user