mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
125 lines
5.6 KiB
JavaScript
125 lines
5.6 KiB
JavaScript
// #291: the production bundle's ordinary config and point-wise layout writers
|
|
// must adopt and send the same exact lattice candidate without editor-specific
|
|
// snap calls.
|
|
import { execFileSync } from 'node:child_process';
|
|
import { resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
|
|
// `model-invariants.mjs` intentionally imports compiled production geometry.
|
|
// A standalone smoke must therefore create the same test-build that `npm test`
|
|
// creates; relying on a developer's leftover directory made clean Linux CI
|
|
// fail before the browser scenario even started.
|
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
execFileSync(process.execPath, [
|
|
resolve(root, 'node_modules/typescript/bin/tsc'), '-p', 'tsconfig.test.json',
|
|
], { cwd: root, stdio: 'inherit' });
|
|
execFileSync(process.execPath, [resolve(root, 'scripts/fix-test-build.mjs')], {
|
|
cwd: root, stdio: 'inherit',
|
|
});
|
|
const { latticeProfile } = await import('../scripts/model-invariants.mjs');
|
|
|
|
const { page, browser } = await launch({ width: 920, height: 840 });
|
|
const out = await page.evaluate(async () => {
|
|
const card = window.__card;
|
|
const result = {};
|
|
const node = 83 / 240;
|
|
const noise = Number(node.toFixed(9));
|
|
const far = 0.0605;
|
|
const unknown = 0.1234567896;
|
|
const config = {
|
|
model_version: 7,
|
|
spaces: [{
|
|
id: 'floor', title: 'Floor', cell_cm: 5, view_box: [0, 0, 1, 1],
|
|
plan_x: unknown,
|
|
rooms: [{ id: 'room', poly: [[noise, noise], [0.5, noise], [0.5, 0.5], [noise, 0.5]] }],
|
|
walls: [{ key: '0.422917,0.345833@0.0000', cm: 20, a: [noise, noise], b: [0.5, noise] }],
|
|
openings: [{ id: 'door', type: 'door', x: noise, y: noise,
|
|
angle: 1.2345678906, length: 0.2, host: { kind: 'partition', id: 'p', t: 0.5 } }],
|
|
decor: [
|
|
{ id: 'line', kind: 'line', x1: noise, y1: noise, x2: 0.5, y2: noise },
|
|
{ id: 'box', kind: 'rect', x: far, y: noise, w: noise, h: noise, angle: 0 },
|
|
{ id: 'label', kind: 'text', x: noise, y: noise, scale: 1, angle: 0 },
|
|
],
|
|
room_drafts: [{ id: 'draft', points: [[noise, noise], [0.5, noise]] }],
|
|
partitions: [{ id: 'p', a: [noise, noise], b: [noise, 0.5], cm: 10 }],
|
|
wall_columns: [{ id: 'column', shape: 'circle', center: [noise, noise], cm: 20 }],
|
|
open_spans: [{ a: [0.5, noise], b: [0.5, 0.5] }],
|
|
future: { numeric: unknown },
|
|
}],
|
|
markers: [{ id: 'marker', angle: 1.2345678906, future: unknown }],
|
|
settings: {},
|
|
};
|
|
const writes = [];
|
|
let rev = 10;
|
|
const baseCall = card.hass.callWS.bind(card.hass);
|
|
card.hass = { ...card.hass, callWS: async (message) => {
|
|
if (message.type === 'houseplan/config/set') {
|
|
writes.push(structuredClone(message));
|
|
return { ok: true, rev: ++rev };
|
|
}
|
|
if (message.type === 'houseplan/layout/update') {
|
|
writes.push(structuredClone(message));
|
|
return { ok: true, rev: ++rev };
|
|
}
|
|
return baseCall(message);
|
|
} };
|
|
|
|
card._serverCfg = structuredClone(config);
|
|
card._cfgRev = 10;
|
|
card._pendingPhysicalWrites.clear();
|
|
await card._writeConfig();
|
|
const configWrite = writes.find((write) => write.type === 'houseplan/config/set');
|
|
const sent = configWrite?.config;
|
|
const serialized = JSON.stringify(sent);
|
|
result.configWriterAdoptsExactPayload = !!sent
|
|
&& JSON.stringify(card._serverCfg) === serialized;
|
|
const floor = sent?.spaces[0];
|
|
result.roomAndWallCoordinatesCanonical = floor?.rooms?.[0]?.poly?.[0]?.[0] === node
|
|
&& floor?.walls?.[0]?.a?.[0] === node;
|
|
result.openingAndDecorCoordinatesCanonical = floor?.openings?.[0]?.x === node
|
|
&& floor?.decor?.[0]?.x1 === node && floor?.decor?.[1]?.w === node
|
|
&& floor?.decor?.[2]?.x === node;
|
|
result.draftPartitionColumnSpanCoordinatesCanonical =
|
|
floor?.room_drafts?.[0]?.points?.[0]?.[0] === node
|
|
&& floor?.partitions?.[0]?.a?.[0] === node
|
|
&& floor?.wall_columns?.[0]?.center?.[0] === node
|
|
&& floor?.open_spans?.[0]?.a?.[1] === node;
|
|
result.authoredOffGridAndUnknownSurvive = floor?.decor?.[1]?.x === far
|
|
&& floor?.future?.numeric === unknown
|
|
&& sent?.markers?.[0]?.future === unknown;
|
|
result.scalarFieldsKeepNineDecimalContract = floor?.plan_x === 0.12345679
|
|
&& floor?.openings?.[0]?.angle === 1.234567891
|
|
&& sent?.markers?.[0]?.angle === 1.234567891;
|
|
|
|
card._serverStorage = true;
|
|
card._layout = {
|
|
marker: { s: 'floor', x: noise, y: noise, future: unknown },
|
|
'rl:room': { s: 'floor', x: noise, y: noise },
|
|
};
|
|
card._dirtyPos = new Set(Object.keys(card._layout));
|
|
card._persistLayout();
|
|
card._persistLayout.flush();
|
|
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
const positionWrites = writes.filter((write) => write.type === 'houseplan/layout/update');
|
|
result.layoutWritersAllCanonical = positionWrites.length === 2
|
|
&& positionWrites.every((write) => write.pos.x === node && write.pos.y === node)
|
|
&& Object.values(card._layout).every((position) => position.x === node && position.y === node);
|
|
result.layoutMetadataSurvives = card._layout.marker.future === unknown;
|
|
return {
|
|
result,
|
|
// Profile the exact candidates adopted by the production writers, not the
|
|
// noisy objects injected above. This is the common-boundary half of AC4;
|
|
// the controller half is exercised by the production smokes named in the
|
|
// specification and docs/TESTING.md.
|
|
committedPairs: [
|
|
{ config: structuredClone(sent), layout: {} },
|
|
{ config: structuredClone(sent), layout: structuredClone(card._layout) },
|
|
],
|
|
};
|
|
});
|
|
|
|
out.result.latticeProfileNoiseZeroAfterEveryCommittedPair = out.committedPairs
|
|
.every((model) => latticeProfile(model).noise === 0);
|
|
await finish(browser, checkAll(out.result));
|