import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test from 'node:test'; import { prepareGoldenFixture } from '../demo/golden/harness.mjs'; import { GOLDEN_MATRIX_VERSION, GOLDEN_SCENARIOS, OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT, STAGE6_ACCEPTANCE_SCENARIOS, } from '../demo/golden/matrix.mjs'; import { fixtureWallKey } from '../demo/fixtures/visual-matrix.mjs'; import { readHouseplanProductionSource } from './houseplan-source.mjs'; test('golden matrix has stable unique ids and bounded comparison thresholds', () => { assert.equal(Number.isInteger(GOLDEN_MATRIX_VERSION) && GOLDEN_MATRIX_VERSION > 0, true); assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.id)).size, GOLDEN_SCENARIOS.length); for (const scenario of GOLDEN_SCENARIOS) { assert.match(scenario.id, /^[a-z0-9-]+$/); assert.equal(['visual', 'large'].includes(scenario.fixture), true, scenario.id); assert.equal(['page', 'stage', 'sun-window'].includes(scenario.capture), true, scenario.id); assert.equal(scenario.viewport.width > 0 && scenario.viewport.height > 0, true, scenario.id); assert.equal(scenario.threshold.maxChannelDelta >= 0 && scenario.threshold.maxChannelDelta <= 32, true, scenario.id); assert.equal(scenario.threshold.maxDiffRatio >= 0 && scenario.threshold.maxDiffRatio <= 0.01, true, scenario.id); if (scenario.warmPixelRegion) { const region = scenario.warmPixelRegion; assert.equal(region.x >= 0 && region.y >= 0 && region.w > 0 && region.h > 0, true, scenario.id); assert.equal(region.x + region.w <= 1 && region.y + region.h <= 1, true, scenario.id); assert.equal(Number.isInteger(region.minPixels) && region.minPixels > 0, true, scenario.id); assert.equal(region.minRedBlueDelta > 0, true, scenario.id); } // #649: 2.5D is enabled by settings.volumetric_view, never by the alpha switch. assert.equal('alpha' in scenario, false, scenario.id); if (scenario.projection === 'iso') assert.equal(scenario.mode, 'view', scenario.id); assert.equal('labs' in scenario, false, scenario.id); assert.equal('testOnlyLabsSnapshot' in scenario, false, scenario.id); if (scenario.sunRayPixels) { assert.equal(Number.isInteger(scenario.sunRayPixels.minPixels) && scenario.sunRayPixels.minPixels > 0, true, scenario.id); assert.equal(Number.isInteger(scenario.sunRayPixels.minChannelDelta) && scenario.sunRayPixels.minChannelDelta > 0 && scenario.sunRayPixels.minChannelDelta <= 32, true, scenario.id); } if (typeof scenario.northDeg === 'number') { assert.equal(Number.isInteger(scenario.northDeg) && scenario.northDeg >= 0 && scenario.northDeg < 360, true, scenario.id); } if (scenario.openingPreviewPixels) { assert.ok(scenario.openingPreview, scenario.id); assert.equal(Number.isInteger(scenario.openingPreviewPixels.minPixels) && scenario.openingPreviewPixels.minPixels > 0, true, scenario.id); 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); assert.equal(['door', 'window', 'gate'].includes(scenario.openingGeometry.type), true, scenario.id); assert.equal(Number.isFinite(scenario.openingGeometry.angle), true, scenario.id); } if (scenario.openingSymbolContract) { const contract = scenario.openingSymbolContract; assert.equal(['room', 'partition'].includes(contract.kind), true, scenario.id); assert.equal(['flat', 'iso'].includes(contract.surface), true, scenario.id); assert.equal(contract.wallCm > 0, true, scenario.id); assert.equal(contract.openings.length >= 3, true, scenario.id); assert.equal(new Set(contract.openings.map((opening) => opening.id)).size, contract.openings.length, scenario.id); for (const opening of contract.openings) { assert.match(opening.id, /^[a-z0-9-]+$/, scenario.id); assert.equal(['door', 'window', 'gate'].includes(opening.type), true, scenario.id); const expectedOffset = contract.surface === 'iso' && opening.type !== 'window' ? 'selected-face' : 'center'; assert.equal(opening.offset, expectedOffset, scenario.id); assert.equal(typeof opening.flipV, 'boolean', scenario.id); assert.equal(Number.isFinite(opening.at) && opening.length > 0, true, scenario.id); } assert.equal(contract.surface === 'iso', scenario.projection === 'iso', scenario.id); } if (scenario.tunnelContinuity) { assert.match(scenario.tunnelContinuity.openingId, /^[a-z0-9-]+$/, scenario.id); assert.equal(Number.isInteger(scenario.tunnelContinuity.insetPx) && scenario.tunnelContinuity.insetPx >= 1, true, scenario.id); assert.equal(scenario.tunnelContinuity.maxChannelJump >= 0 && scenario.tunnelContinuity.maxChannelJump <= 32, true, scenario.id); assert.equal(typeof scenario.tunnelContinuity.dpr2, 'boolean', scenario.id); } if (scenario.decorPixelProbes) { assert.match(scenario.decorPixelProbes.color, /^#[0-9a-f]{6}$/i, scenario.id); assert.equal(Number.isInteger(scenario.decorPixelProbes.radius) && scenario.decorPixelProbes.radius >= 0, true, scenario.id); assert.equal(scenario.decorPixelProbes.minMatchingFraction > 0 && scenario.decorPixelProbes.minMatchingFraction <= 1, true, scenario.id); assert.equal(scenario.decorPixelProbes.points.length >= 1, true, scenario.id); for (const point of scenario.decorPixelProbes.points) { assert.match(point.id, /^[a-z0-9-]+$/, scenario.id); assert.equal(point.x >= 0 && point.x <= 1 && point.y >= 0 && point.y <= 1, true, scenario.id); } } if (scenario.decorOverride) { assert.equal(new Set(scenario.decorOverride.map((shape) => shape.id)).size, scenario.decorOverride.length, scenario.id); // #606 deliberately isolates four furniture drawings; other decor // override scenes remain the five-kind integration fixtures. const expectedKinds = scenario.id === 'furniture-corrected-art-light' ? new Set(['furniture']) : new Set(['line', 'rect', 'ellipse', 'text', 'furniture']); assert.deepEqual(new Set(scenario.decorOverride.map((shape) => shape.kind)), expectedKinds, scenario.id); } if (scenario.helpTextRegion) { assert.match(scenario.helpTextRegion.key, /^[a-z0-9_.-]+\.help$/, scenario.id); assert.equal(Number.isInteger(scenario.helpTextRegion.minPixels) && scenario.helpTextRegion.minPixels > 0, true, scenario.id); assert.equal(scenario.openHelp, scenario.helpTextRegion.key, scenario.id); } if (scenario.planSnap) { assert.equal(scenario.planSnap.tool, 'draw', scenario.id); assert.equal(['endpoint', 'line'].includes(scenario.planSnap.expectedKind), true, scenario.id); assert.equal(scenario.mode, 'plan', scenario.id); assert.equal(scenario.capture, 'page', scenario.id); } if (scenario.roomLabelParity) { assert.equal(scenario.space, 'golden-lighting', scenario.id); assert.equal(['view', 'plan'].includes(scenario.mode), true, scenario.id); assert.equal(scenario.capture, 'page', scenario.id); } if (scenario.wallJunctionPreview) { assert.equal(scenario.wallJunctions, true, scenario.id); assert.equal(scenario.mode, 'plan', scenario.id); assert.equal(scenario.capture, 'page', scenario.id); assert.ok(scenario.wallJunctionPreview.path.length >= 1, scenario.id); } if (scenario.tabDrag) { assert.equal(['before', 'after'].includes(scenario.tabDrag), true, scenario.id); assert.equal(scenario.applianceLifecycle, true, scenario.id); assert.equal(scenario.mode, 'plan', scenario.id); assert.equal(scenario.capture, 'page', scenario.id); } } }); test('issue 486 golden matrix has exactly the three focused panel-host surfaces', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.panelHost); assert.deepEqual(scenarios.map((scenario) => scenario.id), [ 'panel-wide-view-light-en', 'panel-wide-plan-editor-dark-en', 'panel-narrow-read-only-empty-light-en', ]); assert.deepEqual(scenarios.map((scenario) => scenario.viewport), [ { width: 1280, height: 800 }, { width: 1280, height: 800 }, { width: 320, height: 720 }, ]); assert.equal(scenarios.every((scenario) => scenario.capture === 'page'), true); assert.deepEqual(scenarios.map((scenario) => scenario.mode), ['view', 'plan', 'view']); const readOnly = scenarios.at(-1); assert.equal(readOnly.emptyPlan, true); assert.equal(readOnly.canWrite, false); assert.equal(readOnly.userIsAdmin, false); assert.equal(readOnly.preloadEditorRuntime, false); const fixture = prepareGoldenFixture(readOnly); assert.deepEqual(fixture.config.spaces, []); assert.deepEqual(fixture.config.markers, []); assert.deepEqual(fixture.layout, {}); }); test('issue 462 has reviewed desktop, narrow-touch and attempted-kiosk notices', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('version-mismatch-')); assert.deepEqual(scenarios.map((scenario) => scenario.id), [ 'version-mismatch-desktop-dark-en', 'version-mismatch-touch-light-ru', 'version-mismatch-kiosk-attempted-dark-de', ]); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.language)), new Set(['en', 'ru', 'de'])); assert.equal(scenarios.every((scenario) => scenario.mode === 'view' && scenario.capture === 'page' && scenario.integrationVersion === '0.0.0-golden-backend'), true); const touch = scenarios.find((scenario) => scenario.touchViewport); assert.deepEqual(touch?.viewport, { width: 390, height: 760 }); const kiosk = scenarios.find((scenario) => scenario.versionRecoveryAttempted); assert.equal(kiosk?.kiosk, true); assert.equal(kiosk?.theme, 'dark'); const harness = readFileSync(new URL('../demo/golden/harness.mjs', import.meta.url), 'utf8'); assert.match(harness, /sessionStorage\.setItem\(attemptKey, attemptedTarget\)/); assert.match(harness, /integration_version: scenario\.integrationVersion/); }); test('golden matrix covers required geometry, rendering and adaptive surfaces', () => { const ids = GOLDEN_SCENARIOS.map((scenario) => scenario.id).join(' '); for (const token of ['geometry', 'diagonal-45-opening', 'openings', 'openings-hidden', 'fill-light', 'fill-temp', 'fill-lqi', 'lighting', 'hover', 'zoom-040', 'zoom-250', 'warm-remount', 'dialog-mobile', 'color-popover', 'tray-wide', 'tray-medium', 'sun-window', 'tray-narrow', 'opaque-glow-two-doorways', 'filled-tunnel', 'opening-placement', 'backup-full', 'backup-space', 'backup-plan-only', 'value-badge-positions', 'value-face-cover-source', 'isometric-geometry', 'isometric-live-layers', 'isometric-no-borders', 'isometric-touch-kiosk', 'isometric-large-warm-remount', 'split-corner-wall', 'plan-snap-endpoint', 'plan-snap-line-gaps', 'wall-junctions', 'isometric-wall-junctions', 'wall-key-roundtrip', 'washer-active-cycle', 'washer-idle-cycle', 'space-tab-drop-before', 'space-tab-drop-after', 'decor-over-opaque-hover', 'decor-over-glow-base']) assert.equal(ids.includes(token), true, token); assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.mode)).has('plan'), true); assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.mode)).has('devices'), true); assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.mode)).has('decor'), true); assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.theme)).has('light'), true); assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.theme)).has('dark'), true); }); test('room-label parity goldens pair View and Plan in light and dark themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.roomLabelParity); assert.deepEqual(scenarios.map(({ mode, theme }) => `${mode}-${theme}`).sort(), [ 'plan-dark', 'plan-light', 'view-dark', 'view-light', ]); for (const scenario of scenarios) { const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(space.settings.show_names, true); assert.equal(space.settings.label_temp, true); assert.equal(space.settings.label_lqi, true); assert.equal(space.settings.label_light, true); assert.deepEqual(Object.keys(fixture.layout).filter((id) => id.startsWith('rl_')).sort(), [ 'rl_light-left', 'rl_light-right', ]); } }); test('space-tab drop goldens hold both insertion sides in light and dark', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.tabDrag); assert.equal(scenarios.length, 2); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.tabDrag)), new Set(['before', 'after'])); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.theme)), new Set(['light', 'dark'])); for (const scenario of scenarios) { assert.equal(prepareGoldenFixture(scenario).config.spaces.length >= 3, true, scenario.id); } }); test('washer lifecycle goldens pair active and idle snapshots of one composite fixture', () => { const active = GOLDEN_SCENARIOS.find((scenario) => scenario.id === 'washer-active-cycle-dark'); const idle = GOLDEN_SCENARIOS.find((scenario) => scenario.id === 'washer-idle-cycle-dark'); assert.ok(active); assert.ok(idle); assert.equal(active.space, 'golden-appliance'); assert.equal(idle.space, active.space); assert.equal(active.applianceLifecycle, true); assert.equal(idle.applianceLifecycle, true); assert.equal(active.stateOverrides['sensor.golden_washer_status'].state, 'start'); assert.equal(idle.stateOverrides['sensor.golden_washer_status'].state, 'done'); for (const scenario of [active, idle]) { const fixture = prepareGoldenFixture(scenario); assert.equal(fixture.states['switch.golden_washer_power'].state, 'on'); assert.equal(fixture.states['switch.golden_washer_child_lock'].state, 'off'); assert.equal(fixture.entities['sensor.golden_washer_status'].translation_key, 'status'); assert.equal(fixture.layout['golden-washer'].s, 'golden-appliance'); } }); test('wall junction goldens cover live L/T previews plus saved flat and isometric bodies', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.wallJunctions); assert.deepEqual(scenarios.map((scenario) => scenario.id), [ 'wall-junctions-plan-preview-light', 'wall-junctions-plan-t-dark', 'wall-junctions-view-dark', 'isometric-wall-junctions-dark', ]); for (const scenario of scenarios) { const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(space.partitions.length, 9); assert.equal('room_drafts' in space, false); assert.ok(space.partitions.some((item) => item.b[1] === 0.94), 'room-wall T fixture'); } }); test('corner Split golden captures before, thin and thick facade states', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.cornerSplitWall); assert.deepEqual( scenarios.map((scenario) => scenario.cornerSplitWall), ['before', 'thin', 'thick', 'zero-taper'], ); for (const scenario of scenarios) { const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.equal(space.settings.show_borders, true); if (scenario.cornerSplitWall === 'zero-taper') { assert.equal(space.rooms.length, 2); assert.equal(space.rooms[0].poly.some((point) => point[1] === 0.405), true); assert.equal( space.walls.some((wall) => ( wall.key === fixtureWallKey([0.60, 0.40], [0.90, 0.405]) )), false, ); continue; } assert.equal(space.rooms.length, scenario.cornerSplitWall === 'before' ? 1 : 2); if (scenario.cornerSplitWall !== 'before') { const divider = space.walls.find((wall) => ( wall.a?.[0] === 0.10 && wall.a?.[1] === 0.10 && wall.b?.[0] === 0.90 && wall.b?.[1] === 0.50 )); assert.equal(divider?.cm, scenario.cornerSplitWall === 'thin' ? 15 : 100); } } }); test('issue 276 golden captures 5 cm offsets and hosted door before/after 10/30/virtual', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => ( scenario.coincidentPartition && !scenario.hiddenWallDiagnostics )); assert.deepEqual( scenarios.map((scenario) => scenario.coincidentPartition), ['before', 'thin', 'thick', 'virtual'], ); for (const scenario of scenarios) { const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.equal(space.cell_cm, 5); assert.ok(space.rooms[0].poly.some((point) => point[0] === 0.5 && point[1] === 0.004166666666666667), 'short 5 cm offset must be visible'); assert.equal(space.openings.length, 1); if (scenario.coincidentPartition === 'before') { assert.equal(space.partitions.length, 1); assert.equal(space.openings[0].host.id, 'redundant'); continue; } assert.equal(space.partitions, undefined); assert.equal(space.openings[0].host, undefined); assert.equal(space.openings[0].x, 0.504166667); if (scenario.coincidentPartition === 'virtual') { assert.equal(space.open_spans.length, 1); } else { assert.equal(space.walls[0].cm, scenario.coincidentPartition === 'thin' ? 10 : 30); } } }); test('issue 296 golden shows hidden independent-wall diagnostics in both themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.hiddenWallDiagnostics); assert.deepEqual(scenarios.map((scenario) => scenario.theme).sort(), ['dark', 'light']); for (const scenario of scenarios) { assert.equal(scenario.mode, 'plan'); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(space.partitions.length, 2); assert.equal('room_drafts' in space, false); assert.ok(space.partitions.some((item) => item.id === 'hidden-saved-chain-edge' && item.a[0] === 0 && item.a[1] === 0 && item.b[0] === 0 && item.b[1] === 1)); } }); test('filled opening golden has a pixel-level seam detector', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'openings-filled-tunnel-dark'); assert.ok(scenario); assert.equal(scenario.fillMode, 'custom'); assert.equal(scenario.customFill.a > 0 && scenario.customFill.a < 1, true); assert.equal(scenario.glowEnabled, false); assert.equal(scenario.hideOpenings, true, 'opening symbols must not mask the sampled tunnel pixels'); assert.equal(scenario.tunnelContinuity.openingId, 'light-door'); assert.equal(scenario.tunnelContinuity.maxChannelJump <= 3, true); assert.equal(scenario.tunnelContinuity.dpr2, true); assert.equal(scenario.wallReplacements[0].segments.length, 2); assert.notEqual( scenario.wallReplacements[0].segments[0].cm, scenario.wallReplacements[0].segments[1].cm, ); }); test('decor layer goldens pair opaque hover and Glow base with semantic pixels', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.decorPixelProbes); assert.deepEqual(scenarios.map((item) => item.id), [ 'decor-over-opaque-hover-light', 'decor-over-glow-base-dark', ]); assert.deepEqual(scenarios.map((item) => item.theme), ['light', 'dark']); for (const scenario of scenarios) { assert.equal(scenario.showBorders, false); assert.equal(scenario.showNames, false); assert.equal(scenario.hideOpenings, true); assert.equal(scenario.sunRays, false); assert.equal(scenario.decorPixelProbes.points.some((point) => point.id === 'opening-tunnel'), true); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.deepEqual(new Set(space.decor.map((shape) => shape.kind)), new Set(['line', 'rect', 'ellipse', 'text', 'furniture'])); assert.equal(space.settings.show_borders, false); assert.equal(space.settings.hide_openings, true); } assert.equal(scenarios[0].fillMode, 'custom'); assert.equal(scenarios[0].customFill.a, 1); assert.equal(scenarios[0].hoverRoom, 'light-left'); assert.equal(scenarios[1].fillMode, 'glow'); assert.equal(scenarios[1].glowEnabled, true); assert.equal(scenarios[1].allLightsOff, true); }); test('doorway spill golden exposes the opaque-fill failure mode from issue 71', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'lighting-opaque-glow-two-doorways-dark'); assert.ok(scenario); assert.equal(scenario.theme, 'dark'); assert.equal(scenario.customFill?.a, 1); assert.equal(scenario.glowEnabled, true); assert.equal(scenario.sunRays, false); assert.equal(scenario.allLightsOff, true); assert.equal(scenario.extraOpenings?.filter((opening) => opening.type === 'door').length, 2); assert.equal(scenario.extraOpenings?.find((opening) => opening.id === 'light-door-second')?.contact, 'cover.golden_door_closed'); assert.equal(scenario.extraOpenings?.find((opening) => opening.id === 'light-door-third')?.contact, 'cover.golden_door_half'); assert.equal(scenario.stateOverrides?.['light.golden_light_one']?.state, 'on'); assert.deepEqual(scenario.layoutOverrides?.['golden-light-one'], { s: 'golden-lighting', x: 0.40, y: 0.48, }); assert.equal(scenario.warmPixelRegion?.minPixels >= 2500, true); assert.equal(scenario.warmPixelRegion?.x >= 0.5, true); }); test('stepped exterior PDF golden pins the complete reconstructable dimension chain', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'pdf-export-stepped-dimensions-light'); assert.ok(scenario); assert.equal(scenario.pdfExport, true); assert.equal(scenario.pdfSteppedExterior, true); assert.deepEqual(scenario.pdfSemantic.requiredDimensionLabels, ['1.20 m', '2.40 m', '3.60 m', '3.75 m', '7.35 m', '9.75 m']); assert.equal(scenario.pdfSemantic.maxDimensionAxisErrorDeg, 0.5); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(space.rooms[0].poly.length, 8); assert.equal(space.wall_segments.length, 8); assert.equal(space.wall_segments.every((wall) => wall.cm === 15), true); }); test('sun-ray golden requires browser-painted light from a state-only sun entity', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'lighting-sun-window-state-only-dark'); assert.ok(scenario); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(GOLDEN_MATRIX_VERSION, 67); assert.equal(space.settings.sun_rays, true); assert.equal(scenario.northDeg, 90, 'the sign-sensitive golden must keep a non-zero north direction'); assert.equal(space.settings.north_deg, 90); assert.equal(fixture.states['sun.sun']?.attributes?.azimuth, 270, 'north=90 plus azimuth=270 points to the top window; subtraction would point down'); assert.equal(space.openings.some((opening) => opening.type === 'window'), true); assert.equal(fixture.states['sun.sun']?.state, 'above_horizon'); assert.equal(fixture.entities['sun.sun'], undefined, 'sun.sun must exercise the real state-only HA path'); assert.equal(scenario.capture, 'sun-window'); assert.equal(scenario.glowEnabled, false); assert.equal(scenario.allLightsOff, true); assert.equal(scenario.sunRayPixels.minPixels >= 500, true); assert.equal(scenario.sunRayPixels.minChannelDelta >= 4, true); const outer = GOLDEN_SCENARIOS.find((item) => item.id === 'lighting-sun-window-outer-thick-dark'); assert.ok(outer); const outerFixture = prepareGoldenFixture(outer); const outerSpace = outerFixture.config.spaces.find((item) => item.id === outer.space); const hostWall = outerSpace.walls.find((wall) => Math.abs(wall.a[1] - 0.10) < 1e-9 && Math.abs(wall.b[1] - 0.10) < 1e-9); assert.equal(outerFixture.config.settings.sun_ray_origin, 'outer'); assert.equal(outerSpace.settings.sun_ray_origin, undefined, 'the selector is global-only and must not be hidden in a space override'); assert.equal(hostWall?.cm, 15, 'outer golden must retain a physical wall tunnel'); assert.equal(outer.capture, 'sun-window'); assert.equal(outer.sunRayPixels.minPixels >= 500, true); const occlusion = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('lighting-sun-window-outer-occluded-dpr')); assert.deepEqual(occlusion.map((item) => item.deviceScaleFactor), [1, 2]); for (const item of occlusion) { const prepared = prepareGoldenFixture(item); assert.equal(prepared.config.settings.sun_ray_origin, 'outer'); assert.equal(prepared.states['sun.sun']?.attributes?.azimuth, 240, 'north=90 plus azimuth=240 keeps the top window lit at an oblique angle'); assert.equal(item.capture, 'sun-window'); assert.equal(item.sunRayPixels.minPixels >= 400, true); } }); test('vacuum smoothing golden covers current gaps and the previous run', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'vacuum-trail-smoothing-dark'); assert.ok(scenario); assert.equal(scenario.mode, 'view'); assert.equal(scenario.vacuumTrail?.maxDeviationCm, 17.5); assert.equal(scenario.vacuumTrail?.current.length, 2); assert.equal(scenario.vacuumTrail?.current.flat().length >= 8, true); assert.equal(scenario.vacuumTrail?.previous.length >= 4, true); const fixture = prepareGoldenFixture(scenario); assert.equal(fixture.config.markers.some((marker) => marker.id === 'golden-vacuum-trail'), true); assert.equal(fixture.states['vacuum.golden_vacuum_trail']?.state, 'cleaning'); }); test('issue #277 golden pairs safe/disabled handles and an opening clamp in both themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.safeResizePreview); assert.deepEqual(scenarios.map((item) => item.theme).sort(), ['dark', 'light']); for (const scenario of scenarios) { assert.equal(scenario.safeResizeFixture, true); assert.equal(scenario.mode, 'plan'); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(space.rooms.length, 3); assert.equal(space.rooms.some((room) => room.id === 'resize-diagonal'), true); assert.equal(space.openings.some((opening) => opening.id === 'resize-side-door'), true); } }); test('issue 252 golden matrix covers owner-aware cleanup in both themes and languages', () => { const optimize = GOLDEN_SCENARIOS.filter( (item) => item.dialog === 'optimize-orphan-references', ); assert.deepEqual(optimize.map((item) => item.theme).sort(), ['dark', 'light']); assert.deepEqual(optimize.map((item) => item.language).sort(), ['en', 'ru']); for (const scenario of optimize) { const live = scenario.markerOverrides.find((marker) => marker.id === 'golden-light-two'); const removed = scenario.markerOverrides.find((marker) => marker.id === 'golden-presence'); assert.equal(live.space, 'removed-floor'); assert.equal(!!live.name, true); assert.equal(removed.removed, true); assert.equal(scenario.layoutOverrides['golden-light-one'].s, 'unresolved-floor'); assert.equal(scenario.layoutOverrides['golden-presence'].s, 'removed-floor'); } // The existing #244 default-floor warning remains paired light/dark as well. const editors = GOLDEN_SCENARIOS.filter( (item) => item.cardEditorInvalidDefaultFloor === 'removed-floor', ); assert.deepEqual(editors.map((item) => item.theme).sort(), ['dark', 'light']); assert.deepEqual(editors.map((item) => item.language).sort(), ['en', 'ru']); }); test('issue #197 golden keeps the complete junction fixture in Plan and View', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('junction-patch-resilience-')); assert.deepEqual(scenarios.map((item) => item.mode).sort(), ['plan', 'view']); for (const scenario of scenarios) { assert.equal(scenario.theme, 'dark'); assert.equal(scenario.junctionPatchResilience, true); assert.deepEqual(scenario.retainedWedgeProbe, [0.8955, 0.556]); assert.deepEqual( scenario.absentWallProbes, [[0.420833333, 0.37625], [0.92, 0.348]], ); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.deepEqual( [space.rooms.length, space.walls.length, space.open_spans.length], [8, 25, 3], ); } }); test('issues #278/#291 golden covers the stored wall-union fixture in both themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.wallUnionIsolation); assert.deepEqual(scenarios.map((item) => item.theme).sort(), ['dark', 'light']); for (const scenario of scenarios) { assert.equal(scenario.mode, 'view'); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.deepEqual([space.rooms.length, space.walls.length], [2, 3]); } }); test('issue #249 golden isolates a bounded physical three-ray bevel', () => { const scenario = GOLDEN_SCENARIOS.find( (item) => item.id === 'multiwall-junction-bevel-view-dark', ); assert.ok(scenario); assert.equal(scenario.mode, 'view'); assert.equal(scenario.theme, 'dark'); assert.equal(scenario.multiWallJunction.rays, 3); assert.equal(scenario.multiWallJunction.node.length, 2); assert.equal(scenario.multiWallJunction.retainedOverlapProbe.length, 2); assert.equal(scenario.multiWallJunction.enclosedHoles, 0, 'the product contract must not preserve the temporary two-hole inventory'); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.deepEqual(space.node, scenario.multiWallJunction.node); const endpointRays = space.walls.filter((wall) => [wall.a, wall.b].some((point) => ( Math.hypot(point[0] - space.node[0], point[1] - space.node[1]) < 1e-8 ))); assert.equal(endpointRays.length >= scenario.multiWallJunction.rays, true); assert.equal(space.settings.show_borders, true); }); test('every multi-wall golden scene declares its enclosed-hole inventory (#272)', () => { const scenes = GOLDEN_SCENARIOS.filter((item) => item.multiWallJunction); assert.ok(scenes.length >= 1, 'multi-wall semantic scenes disappeared'); for (const scene of scenes) { assert.equal(Number.isInteger(scene.multiWallJunction.enclosedHoles), true, `${scene.id}: enclosedHoles is missing`); assert.ok(scene.multiWallJunction.enclosedHoles >= 0); } }); test('issue #275 golden preflight samples protected strips, not just enclosed holes', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.orthogonalStripContainment); assert.deepEqual(scenarios.map((item) => item.id), [ 'orthogonal-strip-cell-5-view-dark', 'orthogonal-strip-cell-1-view-dark', ]); assert.deepEqual( scenarios.map((item) => item.orthogonalStripContainment.caseId), ['cell-5-mixed-depth-t', 'cell-1-thick-crossbar-t'], ); for (const scenario of scenarios) { assert.equal(scenario.mode, 'view'); assert.ok(scenario.orthogonalStripContainment.minSamples >= 300); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.ok(space.nodes.length >= 1); assert.ok(space.walls.length >= 3); assert.equal(space.settings.show_borders, true); } }); test('issue #258 golden renders the affected persisted key at its T-junction', () => { const scenario = GOLDEN_SCENARIOS.find( (item) => item.id === 'wall-key-roundtrip-view-dark', ); assert.ok(scenario); assert.equal(scenario.mode, 'view'); assert.equal(scenario.theme, 'dark'); assert.equal(scenario.wallKeyRoundtrip.variant, 'affected'); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space); assert.equal(space.walls[0].key, '0.887500,0.195833@1.5706'); assert.deepEqual(scenario.wallKeyRoundtrip.node, [0.8875, 0.345833333]); assert.equal(space.settings.show_borders, true); }); test('the open color picker golden covers dark mobile and light desktop themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.dialog === 'decor-color'); assert.deepEqual(scenarios.map(({ id, language, theme, viewport }) => ({ id, language, theme, width: viewport.width, })), [ { id: 'decor-color-popover-mobile-ru', language: 'ru', theme: 'dark', width: 390 }, { id: 'decor-color-popover-desktop-en', language: 'en', theme: 'light', width: 760 }, ]); }); test('all newly unified color-picker dialog families have reviewed-golden candidates', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => [ 'general-color', 'device-ripple-color', 'space-room-color', ].includes(item.dialog)); assert.deepEqual(scenarios.map(({ id, dialog, theme, viewport }) => ({ id, dialog, theme, width: viewport.width, })), [ { id: 'general-color-popover-desktop-en', dialog: 'general-color', theme: 'light', width: 900 }, { id: 'device-ripple-color-popover-mobile-ru', dialog: 'device-ripple-color', theme: 'dark', width: 390 }, { id: 'space-room-color-popover-desktop-ru', dialog: 'space-room-color', theme: 'dark', width: 900 }, ]); }); test('value badge golden covers four positions and bottom badge with separate LQI', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'device-value-badge-positions-dark'); assert.ok(scenario); assert.deepEqual( new Set(scenario.markerOverrides.map((marker) => marker.value_badge.position)), new Set(['right', 'bottom', 'left', 'top']), ); const bottom = scenario.markerOverrides.find((marker) => marker.value_badge.position === 'bottom'); assert.equal(bottom.id, 'golden-climate'); assert.notEqual(bottom.value_badge.source.kind, 'derived_lqi'); const fixture = prepareGoldenFixture(scenario); assert.equal(fixture.states['climate.golden_climate'].attributes.lqi, 190); assert.equal( fixture.config.markers.find((marker) => marker.id === 'golden-climate').value_badge.position, 'bottom', ); }); test('explicit cover value-source golden pins the 42 percent value face', () => { const scenario = GOLDEN_SCENARIOS.find( (item) => item.id === 'device-value-face-cover-source-dark', ); assert.ok(scenario); assert.equal(scenario.deviceOnly, 'golden-left-linkquality'); const fixture = prepareGoldenFixture(scenario); const marker = fixture.config.markers.find((item) => item.id === scenario.deviceOnly); assert.equal(marker.binding, 'entity:cover.golden_door_half'); assert.equal(marker.display, 'value'); assert.deepEqual(marker.value_source, { kind: 'entity_attribute', entity_id: 'cover.golden_door_half', attribute: 'current_position', }); assert.equal(fixture.states['cover.golden_door_half'].attributes.current_position, 42); }); test('device icon state-table goldens cover both themes and design facets', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('device-icon-state-table-')); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.theme)), new Set(['light', 'dark'])); for (const scenario of scenarios) { assert.equal(scenario.focusDevice, 'golden-climate'); assert.equal(scenario.hoverDevice, 'golden-presence'); assert.equal(scenario.hideHoverTooltip, true); assert.deepEqual(scenario.deviceClassOverrides['golden-climate'], ['alarm', 'sel']); assert.deepEqual(scenario.deviceClassOverrides['golden-presence'], ['virtual']); assert.deepEqual(scenario.deviceClassOverrides['golden-light-two'], ['lock-locked']); assert.deepEqual(scenario.deviceClassOverrides['golden-light-three'], ['lock-unlocked']); assert.deepEqual(scenario.deviceClassOverrides['golden-right-linkquality'], ['open']); const fixture = prepareGoldenFixture(scenario); assert.equal(fixture.states['light.golden_light_one'].attributes.lqi, 40); assert.equal(fixture.states['light.golden_light_two'].attributes.lqi, 41); assert.equal(fixture.states['light.golden_light_three'].attributes.lqi, 180); assert.equal(fixture.states['light.golden_light_three'].state, 'unavailable'); const targetAvailabilityController = fixture.config.markers.find( (marker) => marker.id === 'golden-left-linkquality', ); assert.equal(targetAvailabilityController.tap_action, 'toggle'); assert.deepEqual(targetAvailabilityController.controls, ['light.golden_light_three']); assert.equal( fixture.config.markers.find((marker) => marker.id === 'golden-left-temperature').display, 'value', ); assert.equal( fixture.config.markers.find((marker) => marker.id === 'golden-right-temperature').display, 'static_icon', ); // #588. Пятый режим стоит на включённой RGB-лампе, и это существенно: // «Значение + состояние» красит её маркер, новый режим обязан оставить его // нейтральным. На нейтральном источнике (сенсор, крышка) обе стороны дали // бы один и тот же кадр, и пересъёмка доказывала бы только сама себя. assert.equal( fixture.config.markers.find((marker) => marker.id === 'golden-light-one').display, 'value_static_icon', ); assert.equal(fixture.states['light.golden_light_one'].state, 'on'); assert.deepEqual(fixture.states['light.golden_light_one'].attributes.rgb_color, [255, 196, 112]); } }); test('Text shell regression goldens isolate a large long value in both themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('device-text-shell-long-')); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.theme)), new Set(['light', 'dark'])); for (const scenario of scenarios) { assert.equal(scenario.deviceOnly, 'golden-left-linkquality'); const fixture = prepareGoldenFixture(scenario); const marker = fixture.config.markers.find((item) => item.id === 'golden-left-linkquality'); assert.equal(marker.display, 'value'); assert.equal(marker.size, 3); assert.equal( fixture.config.spaces.find((item) => item.id === scenario.space).settings.show_names, false, ); assert.equal(fixture.states['sensor.golden_left_linkquality'].state, '498'); assert.equal( fixture.states['sensor.golden_left_linkquality'].attributes.unit_of_measurement, 'ppm', ); } }); test('opening placement golden requires browser-painted preview pixels', () => { const door = GOLDEN_SCENARIOS.find( (item) => item.id === 'opening-placement-door-thick-wall-dark', ); 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', () => { const scenario = GOLDEN_SCENARIOS.find( (item) => item.id === 'geometry-diagonal-45-opening-dark', ); assert.ok(scenario); assert.deepEqual(scenario.openingGeometry, { id: 'geo-diagonal-window', type: 'window', angle: 45, }); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.deepEqual(space.openings.map((opening) => opening.id), ['geo-diagonal-window']); }); test('opening symbol goldens lock room, diagonal, flip-pair and hidden Iso contracts', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.openingSymbolContract && !item.staticHatchSharpness); assert.deepEqual(scenarios.map((item) => item.id), [ 'opening-symbol-room-wall-light', 'opening-symbol-diagonal-partition-dark', 'opening-symbol-flip-pairs-light', 'isometric-opening-symbol-parity-dark', ]); for (const scenario of scenarios) { const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.ok(space, scenario.id); assert.equal(space.openings.length, scenario.openingSymbolContract.openings.length, scenario.id); assert.equal(space.settings.show_borders, true, scenario.id); assert.equal(space.openings.every((opening) => Number.isFinite(opening.x) && Number.isFinite(opening.y) && Number.isFinite(opening.angle)), true, scenario.id); if (scenario.openingSymbolContract.kind === 'partition') { assert.equal(space.partitions.length, 1, scenario.id); assert.equal(space.openings.every((opening) => opening.host?.id === space.partitions[0].id), true, scenario.id); } else { assert.equal(space.rooms.length, 2, scenario.id); assert.equal(space.walls.some((wall) => wall.cm === scenario.openingSymbolContract.wallCm), true, scenario.id); } } const flipPair = scenarios.find((item) => item.id === 'opening-symbol-flip-pairs-light'); assert.deepEqual(flipPair.openingSymbolContract.openings.map((opening) => [ opening.type, opening.flipV, opening.offset, ]), [ ['door', false, 'center'], ['door', true, 'center'], ['window', false, 'center'], ['window', true, 'center'], ['gate', false, 'center'], ['gate', true, 'center'], ]); assert.equal(OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT.length, 67); assert.equal(new Set(OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT).size, 67); const scenarioIds = new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.id)); // #681: список живёт только в demo/golden/matrix.mjs (там же — как он // измерен); копия в чек-листе docs/testing-notes/ снята вместе с чек-листом. for (const id of OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT) { assert.equal(scenarioIds.has(id), true, id); } }); test('issue 570 Stage 4 reuses the historical iso goldens for visual handoff coverage', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('isometric-stage3-')); assert.deepEqual(scenarios.map((item) => item.id), [ 'isometric-stage3-overlays-light', 'isometric-stage3-overlays-dark', 'isometric-stage3-openings-dark', 'isometric-stage3-forced-colors-dark', 'isometric-stage3-no-filter-dark', ]); assert.equal(scenarios.every((item) => item.projection === 'iso' && item.mode === 'view' && item.capture === 'stage'), true); const overlays = scenarios.filter((item) => item.id.includes('-overlays-')); assert.deepEqual(new Set(overlays.map((item) => item.theme)), new Set(['light', 'dark'])); assert.equal(overlays.every((item) => item.stage3Golden.requireNudged && item.stage3Golden.requireDenseFacets && item.stage3Golden.requireNoTetherCues && item.stage3Golden.requireNoGrounding && item.stage3Golden.requiredKinds.includes('device') && item.stage3Golden.requiredKinds.includes('room-label') && item.stage3Golden.requiredKinds.includes('opening-lock')), true); assert.equal(overlays.find((item) => item.theme === 'dark')?.stage3Golden.requireVacuumFloor, true); assert.equal(overlays.find((item) => item.theme === 'dark')?.stage3Golden.requireLiveLayers, true); assert.ok(overlays.find((item) => item.theme === 'dark')?.decorOverride?.length); const combinedFixture = prepareGoldenFixture(overlays.find((item) => item.theme === 'dark')); const combinedSpace = combinedFixture.config.spaces.find((item) => item.id === 'golden-lighting'); const combinedAreas = new Set(combinedSpace.rooms.map((room) => room.area)); assert.ok(combinedAreas.has(combinedFixture.devices['golden-vacuum-trail'].area_id)); assert.equal(combinedSpace.settings.north_deg, 90); assert.equal(combinedFixture.states['sun.sun'].attributes.azimuth, 270); const openingScenario = scenarios.find((item) => item.id === 'isometric-stage3-openings-dark'); const openingFixture = prepareGoldenFixture(openingScenario); const openingSpace = openingFixture.config.spaces.find( (item) => item.id === openingScenario.space, ); assert.deepEqual(openingScenario.stage3Golden.openingKinds, ['door', 'window', 'gate']); assert.equal(openingScenario.stage3Golden.requirePassage, true); assert.equal(openingSpace.openings.some((item) => item.type === 'passage'), true); const lockedOpening = openingSpace.openings.find((item) => item.id === 'light-door'); assert.equal(openingFixture.states[lockedOpening.lock].state, 'unlocked'); const denseFixture = prepareGoldenFixture(overlays[0]); const denseSpace = denseFixture.config.spaces.find((item) => item.id === overlays[0].space); assert.equal(denseSpace.settings.label_temp, true); assert.equal(denseSpace.settings.label_lqi, true); assert.deepEqual(denseFixture.config.settings.new_device_ids, ['golden-presence']); assert.equal(denseFixture.config.markers.find((item) => item.id === 'golden-presence')?.display, 'icon_ripple'); assert.ok(denseFixture.layout['rl_light-left']); const forced = scenarios.find((item) => item.forcedColors); const noFilter = scenarios.find((item) => item.disableIsoFilters); assert.equal(forced.stage3Golden.requireNoMaterialDefs, true); assert.equal(noFilter.stage3Golden.requireNoMaterialDefs, true); assert.equal(forced.stage3Golden.requireNoTetherCues, true); assert.equal(noFilter.stage3Golden.requireNoTetherCues, true); assert.equal(forced.stage3Golden.requireNoGrounding, true); assert.equal(noFilter.stage3Golden.requireNoGrounding, true); const noBorders = GOLDEN_SCENARIOS.find((item) => item.id === 'isometric-no-borders-dark'); assert.equal(noBorders.stage3Golden.noBorders, true); const harness = readFileSync(new URL('../demo/golden/harness.mjs', import.meta.url), 'utf8'); assert.match(harness, /\[data-hp-iso-overlay-kind\]\[data-hp-iso-floor\]\[data-hp-iso-visual\]/); assert.match(harness, /forcedColors: scenario\.forcedColors \? 'active' : 'none'/); assert.match(harness, /Stage 4 golden raised a floor-bound vacuum/); assert.match(harness, /Stage 4 no-borders golden retained low overlays/); assert.match(harness, /!scenario\.stage3Golden\?\.noBorders[\s\S]*?data-hp-iso-stage/, 'no-borders remains true Iso without requiring the intentionally absent volume stage'); assert.match(harness, /\.\.\.\(scenario\.stage3Golden[\s\S]*?\? \{ themes: \{ darkMode: scenario\.theme === 'dark' \} \}[\s\S]*?: \{\}\)/, 'explicit HA darkMode must be scoped to isometric golden scenarios'); assert.doesNotMatch(harness, /locale: \{ language: scenario\.language \|\| 'en' \},\s*themes:/, 'legacy Flat and Iso golden inputs must not gain an unconditional HA themes field'); assert.match(harness, /Stage 4 golden did not use HA darkMode/); assert.match(harness, /Stage 4 dense golden lacks/); assert.match(harness, /Stage 4 combined golden lacks/); assert.ok(harness.lastIndexOf('Stage 4 combined golden lacks') > harness.lastIndexOf("await until(() => !!card.renderRoot.querySelector('.vactrail > path.case'))"), 'combined live-layer preflight must run after the async vacuum fixture settles'); }); test('#673 Stage 6 designer acceptance scenes are canonical golden entries', () => { const ids = STAGE6_ACCEPTANCE_SCENARIOS.map((scenario) => scenario.id); assert.equal(GOLDEN_MATRIX_VERSION, 67); assert.deepEqual(ids, [ 'isometric-stage6-light-lightfloor', 'isometric-stage6-light-darkfloor', 'isometric-stage6-dark-darkfloor', 'isometric-stage6-dark-lightfloor', 'isometric-stage6-hover-light', ]); for (const scenario of STAGE6_ACCEPTANCE_SCENARIOS) { assert.strictEqual(GOLDEN_SCENARIOS.find((item) => item.id === scenario.id), scenario, `${scenario.id} must be the exact reviewed matrix object, not a drifting copy`); assert.equal(scenario.projection, 'iso'); assert.equal(scenario.mode, 'view'); assert.equal(scenario.capture, 'stage'); assert.deepEqual(scenario.stage3Golden.requiredKinds, ['device', 'room-label', 'opening-lock']); } const themeFloor = STAGE6_ACCEPTANCE_SCENARIOS.slice(0, 4); assert.deepEqual(themeFloor.map((scenario) => [scenario.theme, scenario.customFill.c]), [ ['light', '#eee8de'], ['light', '#737777'], ['dark', '#737777'], ['dark', '#eee8de'], ]); assert.equal(themeFloor.every((scenario) => scenario.sunRays === true && scenario.northDeg === 180 && scenario.stateOverrides['sun.sun'].attributes.azimuth === 150 && scenario.stateOverrides['sun.sun'].attributes.elevation === 52), true); const hover = STAGE6_ACCEPTANCE_SCENARIOS[4]; assert.equal(hover.hoverDevice, 'golden-light-two'); assert.equal(hover.sunRays, false); }); test('golden harness applies doorway, state and layout overrides to a cloned fixture', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'lighting-opaque-glow-two-doorways-dark'); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); assert.equal(space.openings.filter((opening) => opening.type === 'door').length, 3); assert.equal(fixture.states['cover.golden_door_closed'].attributes.current_position, 0); assert.equal(fixture.states['cover.golden_door_half'].attributes.current_position, 50); assert.equal(fixture.states['light.golden_light_one'].state, 'on'); assert.deepEqual(fixture.layout['golden-light-one'], { s: 'golden-lighting', x: 0.40, y: 0.48, }); assert.equal(space.settings.sun_rays, false); assert.equal(space.settings.custom_fill.a, 1); }); test('golden overrides fail closed on misspelled fixture references', () => { const base = { id: 'invalid', fixture: 'visual', space: 'golden-lighting', mode: 'view', theme: 'dark', viewport: { width: 1000, height: 900 }, capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.001 }, }; assert.throws(() => prepareGoldenFixture({ ...base, stateOverrides: { 'light.golden_light_onee': { state: 'on' } }, }), /missing entity/); assert.throws(() => prepareGoldenFixture({ ...base, layoutOverrides: { 'golden-light-onee': { x: 0.4, y: 0.5 } }, }), /missing item/); assert.throws(() => prepareGoldenFixture({ ...base, roomGlow: { 'light-rightt': true }, }), /missing room/); assert.throws(() => prepareGoldenFixture({ ...base, decorOverride: [{ id: 'bad', kind: 'triangle' }], }), /unknown kind/); }); test('a light source paints exactly one region: the floor it can see', () => { const source = readHouseplanProductionSource(); const glow = readFileSync(new URL('../src/glow-scene.ts', import.meta.url), 'utf8'); // One region per source, and it is the visibility polygon clipped to floor. assert.match(glow, /visibilityPolygon\(\s*\[input\.source\.x, input\.source\.y\], input\.radius/); assert.match(glow, /lit: seen\.length >= 3\s*\? intersectionPaths\(\[seen\], input\.scene\.floor, \{/); assert.match(glow, /onBoundsFailure: \(\{ boundIndex, phase \}\) =>/, 'room-local boolean failures must be observable without restoring an unclipped fan'); assert.match(glow, / 0 \? perUnit : 1\)/); // #396: the freeze keys on "the camera is still", not on the two gesture // flags. An animated transition sets neither, so every tween frame rebuilt // the blur region — the cost this gate exists to avoid. Both the predicate // and its use are pinned: a predicate that stops reading the transition is // the regression, and so is a call that stops reading the predicate. assert.match(source, /const cameraStill = !this\._pinchStart && !this\._panStart\s*\n\s*&& !this\._cameraTransition\.active;/); assert.match(source, /resolveGlowFeather\(\s*this\._glowRuntimeState, perUnit, cameraStill,/); assert.match(glow, /filter=\$\{input\.featherEnabled \? 'url\(#hp-glowfeather\)' : nothing\}/, 'the expensive whole-layer filter must be bypassed during a viewport gesture/transition'); // Barriers are keyed by their own content: `_cfgEpoch` lags behind geometry // edited in place, and a stale barrier set lights straight through a wall. assert.doesNotMatch(glow, /_cfgEpoch/); assert.match(glow, /const geometryFingerprint = contentFingerprint\(\[\s*input\.rawSpaceConfig, input\.cellCm, input\.gridPitch/); assert.match(glow, /fingerprint: contentFingerprint\(\[geometryFingerprint, openingStateSignature\]\)/); assert.match(glow, /const cacheKey = `\$\{input\.space\.id\}\|\$\{revision\.fingerprint\}`/); assert.match(glow, /sharedFingerprint === revision\.geometryFingerprint/, 'an in-place edit must never recut stale shared masonry'); }); test('all destructive editor dialogs use the medium shell and shared responsive footer groups', () => { const source = readHouseplanProductionSource(); // #600: тело формы пространства живёт в общем модуле `space-form.ts`, а метод // — тонкая обёртка с оболочкой. Футер ищем в обоих текстах. const sharedSpaceForm = readFileSync(new URL('../src/editors/space-form.ts', import.meta.url), 'utf8'); for (const method of ['_renderOpeningDialog', '_renderPhysicalDialog', '_renderSpaceDialog']) { const start = source.indexOf(`private ${method}`); assert.notEqual(start, -1, method); const end = source.indexOf('\n private ', start + 10); const body = source.slice(start, end < 0 ? undefined : end) + (method === '_renderSpaceDialog' ? sharedSpaceForm : ''); const openTag = body.match(//)?.[0] || ''; assert.match(openTag, /\bwide\b/, `${method} must reserve the wide desktop shell (500 px; 560 px with form-shell, #600 Q7)`); assert.match(body, /dialog-action-footer/, method); assert.match(body, /dialog-action-danger/, method); assert.match(body, /dialog-action-commit/, method); } }); test('editor tray golden contract covers every adaptive width in English and Russian', () => { const tray = GOLDEN_SCENARIOS.filter((scenario) => scenario.editorTray); assert.equal(tray.length, 6); const widths = { wide: (scenario) => scenario.viewport.width >= 1000, medium: (scenario) => scenario.viewport.width >= 600 && scenario.viewport.width < 1000, narrow: (scenario) => scenario.viewport.width < 600, }; for (const [name, predicate] of Object.entries(widths)) { const languages = new Set(tray.filter(predicate).map((scenario) => scenario.language)); assert.deepEqual(languages, new Set(['en', 'ru']), name); } assert.deepEqual(new Set(tray.map((scenario) => scenario.editorTray)), new Set([ 'plan-selection', 'plan-tool', 'group', 'decor-selection', 'furniture-palette', 'decor-tool', ])); }); test('issue 159 goldens cover both palette levels and every plan-art compatibility class', () => { const furniture = GOLDEN_SCENARIOS.filter((scenario) => scenario.furniturePalette); assert.deepEqual(new Set(furniture.map((scenario) => scenario.theme)), new Set(['dark', 'light'])); assert.deepEqual(new Set(furniture.map((scenario) => scenario.furniturePalette)), new Set(['categories', 'variants'])); const plan = GOLDEN_SCENARIOS.find((scenario) => scenario.id === 'furniture-plan-art-dark'); assert.deepEqual(new Set(plan.decorOverride.filter((shape) => shape.kind === 'furniture') .map((shape) => shape.symbol)), new Set(['sofa', 'fridge', 'sofa_corner_right'])); }); test('issue 606 isolates both corrected drawings and the saved exercise alias', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'furniture-corrected-art-light'); assert.equal(scenario?.mode, 'view'); assert.equal(scenario?.fixture, 'visual'); assert.deepEqual(scenario.decorOverride.map((shape) => shape.symbol), ['cactus', 'exercise', 'bookshelf', 'shelf_floor']); assert.ok(scenario.decorOverride.every((shape) => shape.kind === 'furniture')); assert.equal(scenario.decorOverride[0].flip_h, true); assert.equal(scenario.decorOverride[2].flip_v, true); }); test('issue 359 has one deterministic light-theme furniture placement preview golden', () => { const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'furniture-placement-preview-light'); assert.ok(scenario); assert.equal(scenario.mode, 'decor'); assert.equal(scenario.theme, 'light'); assert.equal(scenario.capture, 'stage'); assert.deepEqual(scenario.furniturePlacementPreview, { symbol: 'sofa', widthCm: 180, depthCm: 90, pointer: [0.35, 0.90], }); }); test('issue 383 goldens cover every furniture mirror orientation in light and dark themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((item) => item.id.startsWith('furniture-transform-')); assert.equal(scenarios.length, 2); assert.deepEqual(new Set(scenarios.map((item) => item.theme)), new Set(['light', 'dark'])); for (const scenario of scenarios) { const furniture = scenario.decorOverride.filter((shape) => shape.kind === 'furniture'); const orientations = furniture.map((shape) => `${shape.flip_h ? 'h' : ''}${shape.flip_v ? 'v' : ''}` || 'normal'); assert.deepEqual(new Set(orientations), new Set(['normal', 'h', 'v', 'hv']), scenario.id); assert.ok(furniture.every((shape) => shape.symbol === 'sofa_corner_right')); } const selected = scenarios.find((scenario) => scenario.decorSelection); assert.equal(selected?.theme, 'light'); assert.equal(selected?.mode, 'decor'); assert.equal(selected?.decorSelection, 'furn-flip-hv'); }); test('device dialog goldens expose the complete light-source controls at desktop and mobile widths', () => { const dialogs = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('device-dialog-')); assert.equal(dialogs.length, 3); assert.deepEqual(new Set(dialogs.map((scenario) => scenario.language)), new Set(['en', 'ru', 'de'])); for (const scenario of dialogs) { assert.equal(scenario.deviceLightControls, true, scenario.id); assert.equal(scenario.deviceId, 'golden-light-two', scenario.id); assert.equal(scenario.capture, 'page', scenario.id); assert.equal(scenario.viewport.height >= 1000, true, scenario.id); } const harness = readFileSync(new URL('../demo/golden/harness.mjs', import.meta.url), 'utf8'); assert.match(harness, /if \(scenario\.deviceLightControls\)/, 'the harness must activate the declared light-controls scenario flag'); }); test('issue #199 golden candidates cover the blocked Optimize dialog in both themes', () => { const scenarios = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('optimize-preflight-dialog-')); assert.equal(scenarios.length, 2); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.theme)), new Set(['dark', 'light'])); assert.deepEqual(new Set(scenarios.map((scenario) => scenario.language)), new Set(['en', 'ru'])); for (const scenario of scenarios) { assert.equal(scenario.dialog, 'optimize-preflight'); assert.equal(scenario.capture, 'page'); } }); test('golden harness neutralizes the shared pointer before every scenario', () => { const harness = readFileSync(new URL('../demo/golden/harness.mjs', import.meta.url), 'utf8'); const reset = harness.indexOf('await page.mouse.move(0, 0);'); const fixture = harness.indexOf('const fixture = prepareGoldenFixture(scenario);', reset); assert.equal(reset >= 0, true, 'the shared Playwright pointer must be reset'); assert.equal(fixture > reset, true, 'pointer reset must happen before fixture adoption'); }); test('toggle-entity dialog goldens cover selected and stale states across themes and widths', () => { const dialogs = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('toggle-entity-dialog-')); assert.equal(dialogs.length, 2); assert.deepEqual(new Set(dialogs.map((scenario) => scenario.deviceToggleEntity)), new Set(['selected', 'stale'])); assert.deepEqual(new Set(dialogs.map((scenario) => scenario.language)), new Set(['en', 'ru'])); assert.deepEqual(new Set(dialogs.map((scenario) => scenario.theme)), new Set(['light', 'dark'])); assert.equal(dialogs.some((scenario) => scenario.viewport.width < 600), true); assert.equal(dialogs.some((scenario) => scenario.viewport.width >= 1000), true); }); test('help affordance golden covers an open text-bearing surface in both themes', () => { const help = GOLDEN_SCENARIOS.filter((scenario) => scenario.openHelp); assert.equal(help.length >= 2, true); assert.deepEqual(new Set(help.map((scenario) => scenario.theme)), new Set(['dark', 'light'])); for (const scenario of help) { assert.equal(scenario.helpTextRegion?.key, scenario.openHelp, scenario.id); assert.equal(scenario.helpTextRegion?.minPixels >= 30, true, scenario.id); } }); test('issue 86 help goldens render browser zoom 200% in both themes', () => { const zoom = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('settings-help-zoom-200-')); assert.equal(zoom.length, 2); assert.deepEqual(new Set(zoom.map((scenario) => scenario.theme)), new Set(['light', 'dark'])); assert.deepEqual(new Set(zoom.map((scenario) => scenario.language)), new Set(['en', 'ru'])); for (const scenario of zoom) { assert.equal(scenario.deviceScaleFactor, 2); assert.deepEqual(scenario.viewport, { width: 390, height: 900 }); assert.equal(scenario.dialog, 'general-help'); assert.equal(scenario.openHelp, 'gs.bg_mode.help'); assert.equal(scenario.capture, 'page'); } }); test('issue 43 support dialog has the six reviewed responsive states', () => { const support = GOLDEN_SCENARIOS.filter((scenario) => scenario.dialog === 'support'); assert.equal(support.length, 6); assert.deepEqual(new Set(support.map((scenario) => scenario.supportState)), new Set([ 'empty', 'preview', 'validation', 'success', 'relay-error', ])); assert.deepEqual(new Set(support.map((scenario) => scenario.theme)), new Set(['light', 'dark'])); assert.equal(support.some((scenario) => scenario.viewport.width === 320), true); assert.equal(support.some((scenario) => scenario.viewport.width === 390), true); assert.equal(support.some((scenario) => scenario.viewport.width === 768), true); assert.equal(support.filter((scenario) => scenario.viewport.width >= 900).length, 3); for (const scenario of support) assert.equal(scenario.capture, 'page', scenario.id); });