mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 22:29:05 +00:00
test: restore the missing LED runtime owner witness (#791)
Exercise a non-null marker absent from the render-device roster alongside a valid lit neighbour. The previous null-marker smoke returned before the mutated guard, so it never tested this defensive consumer boundary. Move this witness to the direct Node runtime suite and update its inventory. Issue: #791 User-Visible: no
This commit is contained in:
@@ -7,18 +7,25 @@ must be added deliberately under one reason below, and its mutant `because` must
|
||||
the concrete browser-only invariant.
|
||||
|
||||
Converted witnesses are not listed here: their registry guard names an explicit `node --test`
|
||||
suite plus `test/mutation-browser-offload.test.mjs`, and every converted mutant is run once
|
||||
to prove that the Node witness actually kills it.
|
||||
suite. The original #659 conversions also use `test/mutation-browser-offload.test.mjs`;
|
||||
new behavioral witnesses execute the consumer directly where possible. Catching the mutant
|
||||
is verified by the nightly run under PROCESS §2.7, not by a development-time mutation run.
|
||||
|
||||
`led-unbound-in-view` moved to `test/led-strip-runtime.test.mjs` in #791: its guard
|
||||
requires a non-null marker absent from runtime devices, whereas the browser's
|
||||
`marker: null` fixture exits before the mutated guard. The Node witness checks the
|
||||
exact surviving strip/owner and its live light; the browser smoke still covers
|
||||
unbound-strip visibility and cold loading.
|
||||
|
||||
| Category | Count | Why a browser is still required |
|
||||
| --- | ---: | --- |
|
||||
| Performance threshold | 4 | The witness measures real browser wall-time or frame work; a pure assertion cannot prove the budget. |
|
||||
| Browser harness integrity | 4 | The mutation breaks page-error, round-trip or page-registration observation in the browser harness itself. |
|
||||
| Paint, cascade and layer composition | 38 | The invariant depends on computed CSS, SVG paint, clipping, stacking or pixels produced by Chromium. |
|
||||
| Paint, cascade and layer composition | 37 | The invariant depends on computed CSS, SVG paint, clipping, stacking or pixels produced by Chromium. |
|
||||
| Pointer geometry and trusted interaction | 49 | The invariant depends on hit testing, pointer capture, touch/keyboard dispatch or live DOM geometry. |
|
||||
| Responsive DOM layout | 38 | The invariant depends on measured element boxes, responsive breakpoints, native/HA dialog shells or focusable target size. |
|
||||
| Custom-element and HA browser lifecycle | 104 | The invariant crosses Lit/custom-element lifecycle, browser storage/events, lazy loading or a complete HA-card state transition. |
|
||||
| **Total** | **237 / 200** | Above the guideline `mutation-gate --check` warns rather than fails (#699); each guard above it is held by its own reason in this inventory and its `because`. |
|
||||
| **Total** | **236 / 200** | Above the guideline `mutation-gate --check` warns rather than fails (#699); each guard above it is held by its own reason in this inventory and its `because`. |
|
||||
|
||||
## Measured effect
|
||||
|
||||
@@ -102,7 +109,6 @@ The invariant depends on computed CSS, SVG paint, clipping, stacking or pixels p
|
||||
- `led-icon-not-suppressed`
|
||||
- `led-source-stays-round-at-anchor`
|
||||
- `led-static-live-ignored`
|
||||
- `led-unbound-in-view`
|
||||
- `stage3-w4-device-target-loses-44px-floor`
|
||||
- `stage3-w5-runtime-nudge-writes-storage`
|
||||
- `stage3-w6-no-borders-keeps-raised-plates`
|
||||
|
||||
@@ -14506,8 +14506,10 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'led-unbound-in-view',
|
||||
guard: 'node demo/smoke_led_strip_glow.mjs',
|
||||
because: "#780 ТЗ §5: an unbound strip has no View representation, light or target",
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test test/led-strip-runtime.test.mjs',
|
||||
because: '#780 ТЗ §5 / #791: ledFrame skips a non-null marker absent from runtime devices '
|
||||
+ 'without losing a valid neighbouring strip; the executable frame boundary needs no browser',
|
||||
patches: [{
|
||||
file: 'src/led-strip-runtime.ts',
|
||||
find: " if (!device || device.space !== input.space.id) continue;",
|
||||
|
||||
@@ -444,6 +444,35 @@ test('#784/AC9: the frame gives every strip the 30 cm default, not the shared ra
|
||||
assert.ok(Math.abs(frame.views[0].radius - (30 / 5) * (1000 / 240)) < 1e-9, `radius ${frame.views[0].radius}`);
|
||||
});
|
||||
|
||||
test('#791: the frame skips missing runtime owners without dropping a valid neighbouring strip', () => {
|
||||
const lamp = device({ entities: ['light.led'], marker: { id: 'm1', binding: 'device:m1' } });
|
||||
const points = [[0.1, 0.1], [0.4, 0.1]];
|
||||
const strips = [
|
||||
// A non-null reference reaches the runtime-owner guard; marker:null alone
|
||||
// is rejected earlier and never exercised the escaped nightly mutant.
|
||||
{ id: 'missing-owner', points, marker: 'absent-from-runtime' },
|
||||
{ id: 'unbound', points, marker: null },
|
||||
{ id: 'other-space', points, marker: 'm2' },
|
||||
{ id: 'inactive', points, marker: 'm1', active: false },
|
||||
{ id: 'valid', points, marker: 'm1' },
|
||||
];
|
||||
const before = structuredClone(strips);
|
||||
const frame = ledFrame({
|
||||
space: { id: 's', vb: [0, 0, 1000, 1000], rooms: [{ id: 'room' }], led_strips: strips },
|
||||
devices: [lamp, device({ id: 'm2', space: 'other', primary: 'light.other', entities: ['light.other'],
|
||||
marker: { id: 'm2', binding: 'device:m2' } })],
|
||||
hass: { states: { 'light.led': { state: 'on', attributes: { rgb_color: [128, 213, 255] } } } },
|
||||
defaultColor: '#ffd27b', paletteAlpha: 0.7, cellCm: 5, gridPitch: 1000 / 240, iconPct: 3.4,
|
||||
scene: null, polygons: [], glowFor: () => true, inRoom: () => true, showHidden: false,
|
||||
});
|
||||
assert.deepEqual(frame.views.map(view => [view.strip.id, view.device.id]), [['valid', 'm1']],
|
||||
'only the same-space bound strip may reach stripe, light and hit rendering');
|
||||
assert.equal(frame.views[0].state, 'on');
|
||||
assert.equal(frame.views[0].glow, true);
|
||||
assert.equal(frame.views[0].appearance?.c, '#80d5ff', 'the valid neighbour retains its live light');
|
||||
assert.deepEqual(strips, before, 'runtime filtering must not rewrite stored strips');
|
||||
});
|
||||
|
||||
test('AC2/r1 M2: an explicit valid room_id wins over the anchor room; a stale one falls back', () => {
|
||||
const rooms = [{ id: 'A' }, { id: 'B' }];
|
||||
const inA = (point, room) => room.id === 'A' && point[0] < 500;
|
||||
|
||||
Reference in New Issue
Block a user