mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -46,6 +46,8 @@ test('golden matrix has stable unique ids and bounded comparison thresholds', ()
|
||||
assert.equal(Number.isInteger(scenario.openingPreviewPixels.minChannelDelta)
|
||||
&& scenario.openingPreviewPixels.minChannelDelta > 0
|
||||
&& scenario.openingPreviewPixels.minChannelDelta <= 32, true, scenario.id);
|
||||
assert.equal(Number.isInteger(scenario.openingPreviewPixels.minInsideWallPixels)
|
||||
&& scenario.openingPreviewPixels.minInsideWallPixels > 0, true, scenario.id);
|
||||
}
|
||||
if (scenario.openingGeometry) {
|
||||
assert.match(scenario.openingGeometry.id, /^[a-z0-9-]+$/, scenario.id);
|
||||
@@ -209,7 +211,7 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity
|
||||
assert.ok(scenario);
|
||||
const fixture = prepareGoldenFixture(scenario);
|
||||
const space = fixture.config.spaces.find((item) => item.id === scenario.space);
|
||||
assert.equal(GOLDEN_MATRIX_VERSION, 27);
|
||||
assert.equal(GOLDEN_MATRIX_VERSION, 28);
|
||||
assert.equal(space.settings.sun_rays, true);
|
||||
assert.equal(scenario.northDeg, 90,
|
||||
'the sign-sensitive golden must keep a non-zero north direction');
|
||||
@@ -256,15 +258,28 @@ test('value badge golden covers four positions and bottom badge with separate LQ
|
||||
});
|
||||
|
||||
test('opening placement golden requires browser-painted preview pixels', () => {
|
||||
const scenario = GOLDEN_SCENARIOS.find(
|
||||
const door = GOLDEN_SCENARIOS.find(
|
||||
(item) => item.id === 'opening-placement-door-thick-wall-dark',
|
||||
);
|
||||
assert.ok(scenario);
|
||||
assert.equal(scenario.mode, 'plan');
|
||||
assert.equal(scenario.openingPreview?.type, 'door');
|
||||
assert.deepEqual(scenario.openingPreview?.pointer, [0.48, 0.65]);
|
||||
assert.equal(scenario.openingPreviewPixels.minPixels >= 150, true);
|
||||
assert.equal(scenario.openingPreviewPixels.minChannelDelta >= 4, true);
|
||||
assert.ok(door);
|
||||
assert.equal(door.mode, 'plan');
|
||||
assert.equal(door.openingPreview?.type, 'door');
|
||||
assert.deepEqual(door.openingPreview?.pointer, [0.48, 0.65]);
|
||||
assert.equal(door.openingPreviewPixels.minPixels >= 150, true);
|
||||
assert.equal(door.openingPreviewPixels.minChannelDelta >= 4, true);
|
||||
|
||||
const passages = GOLDEN_SCENARIOS.filter(
|
||||
(item) => item.id.startsWith('opening-placement-passage-thick-wall-'),
|
||||
);
|
||||
assert.deepEqual(passages.map((scenario) => scenario.theme), ['dark', 'light']);
|
||||
for (const scenario of passages) {
|
||||
assert.equal(scenario.mode, 'plan');
|
||||
assert.equal(scenario.openingPreview?.type, 'passage');
|
||||
assert.deepEqual(scenario.openingPreview?.pointer, [0.48, 0.65]);
|
||||
assert.equal(scenario.openingPreviewPixels.minPixels >= 150, true);
|
||||
assert.equal(scenario.openingPreviewPixels.minInsideWallPixels >= 8, true);
|
||||
assert.equal(scenario.openingPreviewPixels.minChannelDelta >= 4, true);
|
||||
}
|
||||
});
|
||||
|
||||
test('diagonal opening golden asserts a real painted 45 degree symbol', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const card = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8');
|
||||
const styles = readFileSync(new URL('../src/styles.ts', import.meta.url), 'utf8');
|
||||
const staticRender = readFileSync(new URL('../src/space-render.ts', import.meta.url), 'utf8');
|
||||
const backend = readFileSync(new URL('../custom_components/houseplan/websocket_api.py', import.meta.url), 'utf8');
|
||||
const importer = readFileSync(new URL('../custom_components/houseplan/import_export.py', import.meta.url), 'utf8');
|
||||
@@ -51,6 +52,21 @@ test('static passage cuts and tunnels are passage-only additions', () => {
|
||||
assert.match(staticRender, /passageGlowTunnels/);
|
||||
});
|
||||
|
||||
test('placement preview adds passage-only cut geometry without changing saved symbols', () => {
|
||||
const start = card.indexOf('private _renderOpeningPlacementPreview()');
|
||||
const preview = card.slice(start, card.indexOf('private _renderOpenings(', start));
|
||||
assert.match(preview, /candidate\.type === 'passage'/);
|
||||
assert.match(preview, /passagePlacementPreviewGeometry\(candidate, this\._gridPitch\)/);
|
||||
assert.equal((preview.match(/class="passage-preview-cut"/g) || []).length, 1);
|
||||
assert.equal((preview.match(/class="passage-preview-boundary"/g) || []).length, 1,
|
||||
'one mapped template emits exactly two resolved boundary records');
|
||||
assert.match(preview, /: renderOpeningVisibleGeometry\(visibleSpec\)/);
|
||||
assert.match(preview, /aria-hidden="true" pointer-events="none"/);
|
||||
assert.match(styles, /\.opening-preview\[data-kind="passage"\][\s\S]*opacity: 1/);
|
||||
assert.match(styles, /\.passage-preview-cut[\s\S]*fill-opacity: 0\.35/);
|
||||
assert.match(styles, /\.passage-preview-boundary[\s\S]*stroke: var\(--hp-open/);
|
||||
});
|
||||
|
||||
test('all write/import paths invoke the semantic passage validator', () => {
|
||||
assert.equal((backend.match(/validate_opening_passages\(/g) || []).length, 2);
|
||||
assert.match(importer, /validate_opening_passages\(merged_config, current_config\)/);
|
||||
|
||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import {
|
||||
openingPlacementPreset,
|
||||
openingPlacementTargets,
|
||||
passagePlacementPreviewGeometry,
|
||||
resolveOpeningPlacement,
|
||||
sameOpeningPlacementInput,
|
||||
} from '../test-build/opening-placement.js';
|
||||
@@ -42,6 +43,45 @@ test('Opening presets use the agreed type-specific defaults', () => {
|
||||
assert.equal(openingPlacementPreset('gate', 3).lengthCm, 300);
|
||||
});
|
||||
|
||||
test('passage preview geometry follows the resolved length and standard wall depth', () => {
|
||||
const geometry = passagePlacementPreviewGeometry({
|
||||
renderedLength: 90,
|
||||
target: { physicalHalfWidth: 7.5 },
|
||||
}, 10);
|
||||
assert.deepEqual(geometry, {
|
||||
rect: { x: -45, y: -7.5, width: 90, height: 15 },
|
||||
boundaries: [
|
||||
{ x1: -45, y1: -9.3, x2: -45, y2: 9.3 },
|
||||
{ x1: 45, y1: -9.3, x2: 45, y2: 9.3 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('passage preview wall depth changes only its rect height and boundary span', () => {
|
||||
const geometry = passagePlacementPreviewGeometry({
|
||||
renderedLength: 90,
|
||||
target: { physicalHalfWidth: 12.5 },
|
||||
}, 10);
|
||||
assert.deepEqual(geometry.rect, { x: -45, y: -12.5, width: 90, height: 25 });
|
||||
assert.deepEqual(geometry.boundaries, [
|
||||
{ x1: -45, y1: -14.3, x2: -45, y2: 14.3 },
|
||||
{ x1: 45, y1: -14.3, x2: 45, y2: 14.3 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('passage preview preserves boundary marks when physical wall depth is zero', () => {
|
||||
const geometry = passagePlacementPreviewGeometry({
|
||||
renderedLength: 60,
|
||||
target: { physicalHalfWidth: 0 },
|
||||
}, 5);
|
||||
assert.deepEqual(geometry.rect, { x: -30, y: 0, width: 60, height: 0 });
|
||||
const boundaryHalf = 5 * 0.18;
|
||||
assert.deepEqual(geometry.boundaries, [
|
||||
{ x1: -30, y1: -boundaryHalf, x2: -30, y2: boundaryHalf },
|
||||
{ x1: 30, y1: -boundaryHalf, x2: 30, y2: boundaryHalf },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Shared room-owned copies collapse to one physical target', () => {
|
||||
const targets = openingPlacementTargets([
|
||||
interval({ roomId: 'r1', half: 7 }),
|
||||
|
||||
Reference in New Issue
Block a user