mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 06:08:59 +00:00
test: keep grid scale static fixture coherent
Issue: #411 User-Visible: no
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Build the exact snapshot consumed by the static-card half of the grid-scale
|
||||
* smoke. The main and static cards normally load through different stores; a
|
||||
* comparative fixture must therefore hand the static card one atomic
|
||||
* config+layout pair instead of mixing the demo backend config with the main
|
||||
* card's temporary layout.
|
||||
*/
|
||||
export function coherentGridScaleStaticPatch({ config, layout, revision }) {
|
||||
if (!config || typeof config !== 'object' || !Array.isArray(config.spaces)) {
|
||||
throw new Error('grid-scale static fixture requires a complete config');
|
||||
}
|
||||
if (!layout || typeof layout !== 'object' || Array.isArray(layout)) {
|
||||
throw new Error('grid-scale static fixture requires a complete layout');
|
||||
}
|
||||
const token = Number.isFinite(revision) ? revision : 0;
|
||||
return {
|
||||
config: structuredClone(config),
|
||||
configFingerprint: `grid-scale-fixture:${token}:config`,
|
||||
layout: structuredClone(layout),
|
||||
layoutFingerprint: `grid-scale-fixture:${token}:layout`,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Issue #239: a finer coordinate grid changes precision, never the visible plan.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
import { coherentGridScaleStaticPatch } from './grid-scale-static-fixture.mjs';
|
||||
|
||||
const { page, browser } = await launch({ width: 1000, height: 860 }, 1, [], {
|
||||
reducedMotion: 'reduce',
|
||||
@@ -168,8 +169,16 @@ await page.evaluate(async () => {
|
||||
while (!compact.renderRoot?.querySelector('.hp-static-stage') && Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
}
|
||||
while ((compact._loading || !compact._snap) && Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
}
|
||||
await compact.updateComplete;
|
||||
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||
return {
|
||||
config: card._serverCfg,
|
||||
layout: sharedLayout,
|
||||
revision: card._cfgEpoch,
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -240,7 +249,21 @@ const capture = async (cellCm, mode, {
|
||||
const pixels = await stableScreenshot(stage);
|
||||
let staticPixels = null;
|
||||
if (staticCard) {
|
||||
await page.evaluate(() => window.__makeStaticGridCard());
|
||||
const source = await page.evaluate(() => window.__makeStaticGridCard());
|
||||
const patch = coherentGridScaleStaticPatch(source);
|
||||
await page.evaluate(async (nextPatch) => {
|
||||
const compact = document.querySelector('#grid-static-host houseplan-space-card');
|
||||
if (!compact) throw new Error('grid-scale static fixture card disappeared');
|
||||
if (!compact._snap) throw new Error('grid-scale static fixture did not load its base snapshot');
|
||||
// Preserve runtime-only Set/Map values from the in-page snapshot. Only
|
||||
// config+layout cross the Playwright boundary; both come from one scale.
|
||||
compact._snap = { ...compact._snap, ...nextPatch };
|
||||
compact._loadedOnce = true;
|
||||
compact._refreshDevices();
|
||||
compact.requestUpdate();
|
||||
await compact.updateComplete;
|
||||
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||
}, patch);
|
||||
staticPixels = await stableScreenshot(staticStage());
|
||||
}
|
||||
return { metrics, pixels, staticPixels };
|
||||
@@ -262,7 +285,14 @@ const pixelDiff = async (left, right) => page.evaluate(async ([a, b]) => {
|
||||
const decode = async (base64) => createImageBitmap(await (await fetch(`data:image/png;base64,${base64}`)).blob());
|
||||
const [first, second] = await Promise.all([decode(a), decode(b)]);
|
||||
if (first.width !== second.width || first.height !== second.height) {
|
||||
return { sameSize: false, changed: Infinity, maxDelta: Infinity, meanDelta: Infinity };
|
||||
return {
|
||||
sameSize: false,
|
||||
firstSize: [first.width, first.height],
|
||||
secondSize: [second.width, second.height],
|
||||
changed: Infinity,
|
||||
maxDelta: Infinity,
|
||||
meanDelta: Infinity,
|
||||
};
|
||||
}
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = first.width; canvas.height = first.height;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { coherentGridScaleStaticPatch } from '../demo/grid-scale-static-fixture.mjs';
|
||||
|
||||
test('grid-scale static fixture keeps config and layout in one isolated snapshot', () => {
|
||||
const config = { spaces: [{ id: 'fixture', cell_cm: 1, view_box: [0, 0, 5, 5] }] };
|
||||
const layout = { device: { s: 'fixture', x: 1.2, y: 2.4 } };
|
||||
const patch = coherentGridScaleStaticPatch({ config, layout, revision: 42 });
|
||||
|
||||
assert.deepEqual(patch.config, config);
|
||||
assert.deepEqual(patch.layout, layout);
|
||||
assert.notEqual(patch.config, config);
|
||||
assert.notEqual(patch.layout, layout);
|
||||
assert.equal(patch.configFingerprint, 'grid-scale-fixture:42:config');
|
||||
assert.equal(patch.layoutFingerprint, 'grid-scale-fixture:42:layout');
|
||||
|
||||
config.spaces[0].cell_cm = 5;
|
||||
layout.device.x = 99;
|
||||
assert.equal(patch.config.spaces[0].cell_cm, 1);
|
||||
assert.equal(patch.layout.device.x, 1.2);
|
||||
});
|
||||
|
||||
test('grid-scale smoke uses the coherent static snapshot without weakening raster limits', () => {
|
||||
const source = readFileSync(new URL('../demo/smoke_grid_scale_invariance.mjs', import.meta.url), 'utf8');
|
||||
assert.match(source, /coherentGridScaleStaticPatch\(source\)/);
|
||||
assert.match(source, /compact\._snap = \{ \.\.\.compact\._snap, \.\.\.nextPatch \}/);
|
||||
assert.match(source, /firstSize: \[first\.width, first\.height\]/);
|
||||
assert.match(source, /secondSize: \[second\.width, second\.height\]/);
|
||||
assert.match(source, /diff\.changed <= 150 && diff\.maxDelta <= 40 && diff\.meanDelta <= 0\.05/);
|
||||
});
|
||||
|
||||
test('grid-scale static fixture rejects partial inputs', () => {
|
||||
assert.throws(() => coherentGridScaleStaticPatch({}), /complete config/);
|
||||
assert.throws(() => coherentGridScaleStaticPatch({ config: {}, layout: {} }), /complete config/);
|
||||
assert.throws(() => coherentGridScaleStaticPatch({
|
||||
config: { spaces: [] }, layout: [],
|
||||
}), /complete layout/);
|
||||
});
|
||||
Reference in New Issue
Block a user