mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
test(floor): judge both frames of a held resize drag (#744)
smoke_floor_geometry_cache failed in Validate run 36875756451 on one check, ac2cPreviewWallStandsWhereAFreshCardDrawsIt, with every area check green. The check read the moving wall only from the live layer (data-kind "preview"). A host render during a held drag ends that layer: updated() commits it, and nothing repaints it until the next accepted move. The settled scene then draws the preview record itself, and its walls equal those of a fresh card on that record. In the smoke, the "Room updated" toast from the AC1 rename expires 3.5 s after the save, about when the drag starts: locally the rename-to-drag gap is 3.47-3.6 s. On a faster runner the expiry landed after the last move, so the check found no live path. A light toggled in Home Assistant mid-drag gives the same red. The drag now starts once the toast is gone, so the live frame normally judges the live layer. If a host render still lands, the check judges the settled union the same way; a stale union has none of the moved faces. A new check takes the settled frame on purpose: a state change during the held drag, then the union path and the areas must equal the fresh card's. The live strips never pass through the floor-geometry caches, so the old check stayed green with a floor key that ignores the preview. The new check fails on that key, and a failure prints a diagnostic line naming the judged layer and whether the settled union is the stored one. Issue: #744 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -14,12 +14,17 @@
|
||||
// floors (the stairs editor used to clear the cache of every floor).
|
||||
// AC2c a live Resize preview on `f1`: the frame follows the preview record,
|
||||
// as the independent card on that record says; cancelling restores the
|
||||
// walls and areas of the stored record.
|
||||
// walls and areas of the stored record. Both frames of a held drag are
|
||||
// judged: the live layer that paints the moving wall, and the settled
|
||||
// scene that replaces it whenever the host renders mid-drag (an entity
|
||||
// state change from Home Assistant, a toast expiring) and draws the
|
||||
// preview record through the floor-geometry caches.
|
||||
//
|
||||
// The independent card is the oracle: a new `houseplan-card` given the same
|
||||
// config through its own `config/get`, with nothing in its caches to reuse.
|
||||
// Card internals are read-only here; writes go through the harness facade,
|
||||
// the room dialog, the stairs tool and real pointer input.
|
||||
// the room dialog, the stairs tool, real pointer input and the demo's Home
|
||||
// Assistant stub (a service call delivers the new entity state).
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
import { fixtureWallKey } from './fixtures/wall-key.mjs';
|
||||
|
||||
@@ -251,6 +256,17 @@ const res = await page.evaluate(async (fixture) => {
|
||||
// ======== AC2c setup: the Resize tool and the r1/r2 shared wall handle =====
|
||||
// Outer walls of this fixture are partial (their records span two rooms) and
|
||||
// stay disabled; the shared wall moves the faces of both rooms.
|
||||
// The drag starts on a quiet card. A host render ends the live layer of a
|
||||
// held drag (`updated()` commits it) and nothing repaints it until the next
|
||||
// accepted move. The rename's toast expires 3.5 s after the save; on a fast
|
||||
// runner that landed after the last move (Validate run 36875756451), and the
|
||||
// live frame below found no live layer. The settled frame is taken on
|
||||
// purpose, by a state change, after the live one.
|
||||
const toastDeadline = performance.now() + 5000;
|
||||
while (root().querySelector('[data-hp="toast"]') && performance.now() < toastDeadline)
|
||||
await new Promise((done) => setTimeout(done, 30));
|
||||
out.ac2cStartsWithoutAToast = !root().querySelector('[data-hp="toast"]');
|
||||
await settled();
|
||||
await hp.setTool('resize');
|
||||
diag.ac2cStored = shown();
|
||||
const handle = [...root().querySelectorAll('.rszhandle:not(.rszcorner)')].find((node) =>
|
||||
@@ -283,6 +299,20 @@ if (gesture) {
|
||||
.map((label) => [label.roomId, label.text]);
|
||||
return { ac2cPreviewIsLive: !!preview && preview.space === 'f1' && card._resize.dragging };
|
||||
}));
|
||||
// The settled frame of the held drag: a light switched in the house renders
|
||||
// the host; the settled scene now draws the walls and areas of the preview.
|
||||
Object.assign(checks, await page.evaluate(async () => {
|
||||
const card = window.__card;
|
||||
const { diag, shown, settled } = window.__hp744;
|
||||
const record = JSON.stringify(card._resize.preview?.sp ?? null);
|
||||
await card.hass.callService('light', 'toggle', { entity_id: 'light.ceiling' });
|
||||
await settled();
|
||||
diag.ac2cSettled = shown();
|
||||
return {
|
||||
ac2cStateChangeKeepsThePreview: card._resize.dragging
|
||||
&& JSON.stringify(card._resize.preview?.sp ?? null) === record,
|
||||
};
|
||||
}));
|
||||
await page.keyboard.press('Escape');
|
||||
await page.mouse.up();
|
||||
Object.assign(checks, await page.evaluate(async () => {
|
||||
@@ -303,14 +333,30 @@ if (gesture) {
|
||||
out.ac2cPreviewMovesBothRooms = diag.ac2cPreview.areas.r1 !== diag.ac2cStored.areas.r1
|
||||
&& diag.ac2cPreview.areas.r2 !== diag.ac2cStored.areas.r2;
|
||||
// The live layer draws the moving wall; its faces are where the fresh
|
||||
// card on the preview record draws them, and not where they were.
|
||||
// card on the preview record draws them, and not where they were. Should
|
||||
// a host render still land after the last move, the settled scene draws
|
||||
// the drag instead, and its walls are judged the same way: a stale
|
||||
// (stored) union has none of the moved faces.
|
||||
const numbers = (text) => new Set((text.match(/-?\d+(?:\.\d+)?/g) || []).map(Number));
|
||||
const parts = (walls, kind) => walls.split(' | ').filter((part) => part.startsWith(`${kind}:`));
|
||||
const stored = numbers(diag.ac2cStored.walls);
|
||||
const moved = [...numbers(fresh.walls)].filter((value) => !stored.has(value));
|
||||
const previewPath = numbers(diag.ac2cPreview.walls.split(' | ')
|
||||
.filter((part) => part.startsWith('preview:')).join(' '));
|
||||
out.ac2cPreviewWallStandsWhereAFreshCardDrawsIt = moved.length > 0
|
||||
&& moved.every((value) => previewPath.has(value));
|
||||
const live = parts(diag.ac2cPreview.walls, 'preview');
|
||||
const drawn = numbers((live.length ? live : parts(diag.ac2cPreview.walls, 'union')).join(' '));
|
||||
const missing = moved.filter((value) => !drawn.has(value));
|
||||
out.ac2cPreviewWallStandsWhereAFreshCardDrawsIt = moved.length > 0 && !missing.length;
|
||||
// The settled scene mid-drag is the fresh card's frame on the preview
|
||||
// record: the same union path, the same areas. A key that ignores the
|
||||
// preview hands it the stored union and contours.
|
||||
const union = (frame) => parts(frame?.walls || '', 'union').join(' | ');
|
||||
const settledUnion = union(diag.ac2cSettled);
|
||||
out.ac2cSettledFrameEqualsAFreshCard = !!settledUnion && settledUnion === union(fresh)
|
||||
&& JSON.stringify(diag.ac2cSettled.areas) === JSON.stringify(fresh.areas);
|
||||
diag.ac2cJudged = {
|
||||
layer: live.length ? 'preview' : 'union', moved: moved.length, missing,
|
||||
settledUnion: !settledUnion ? 'none' : settledUnion === union(fresh) ? 'fresh'
|
||||
: settledUnion === union(diag.ac2cStored) ? 'stored' : 'other',
|
||||
};
|
||||
// Room areas through the cached contour, and the live labels beside the
|
||||
// moving wall, are the fresh card's numbers for the preview record.
|
||||
out.ac2cPreviewAreasEqualAFreshCard = JSON.stringify(diag.ac2cPreview.areas) === JSON.stringify(fresh.areas);
|
||||
@@ -320,6 +366,11 @@ if (gesture) {
|
||||
diag.ac2cCancelled = cancelled;
|
||||
return out;
|
||||
}));
|
||||
if (!checks.ac2cPreviewWallStandsWhereAFreshCardDrawsIt || !checks.ac2cSettledFrameEqualsAFreshCard) {
|
||||
// Validate prints only `diagnostic` lines and the tail of a failed smoke.
|
||||
const judged = await page.evaluate(() => window.__hp744.diag.ac2cJudged);
|
||||
console.log(`diagnostic ac2c: ${JSON.stringify(judged)}`);
|
||||
}
|
||||
}
|
||||
|
||||
checkAll(checks, {
|
||||
|
||||
Reference in New Issue
Block a user