mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 15:09:30 +00:00
@@ -2,7 +2,8 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import {
|
||||
radarConfigFromDraft, radarDraft, radarSourceCandidates, recognizeRadar,
|
||||
freshRadarInstallationId, radarAfterBindingChange, radarConfigFromDraft, radarDraft,
|
||||
radarSourceCandidates, recognizeRadar,
|
||||
} from '../test-build/radar-editor.js';
|
||||
|
||||
const space = { id: 'floor', title: 'Floor', cellCm: 5, rooms: [
|
||||
@@ -50,6 +51,22 @@ test('coordinate-looking entity names alone do not claim an LD2450 adapter', ()
|
||||
'sensor.machine_target_1_x', 'sensor.machine_target_1_y',
|
||||
] });
|
||||
assert.equal(recognizeRadar(ordinary, {}).eligible, false);
|
||||
const fuzzy = device({ model: 'Presence Radar FP2', allEntities: [
|
||||
'sensor.machine_target_1_x', 'sensor.machine_target_1_y',
|
||||
] });
|
||||
assert.equal(recognizeRadar(fuzzy, {}).eligible, false);
|
||||
});
|
||||
|
||||
test('rebinding preserves only an already-saved radar and a new installation gets a new id', () => {
|
||||
const pending = radarDraft(device(), space, [50, 60], {}, true);
|
||||
assert.deepEqual(radarAfterBindingChange(pending, true, false), {
|
||||
radar: null, radarEligible: false, radarTouched: true, radarRemove: true,
|
||||
});
|
||||
const saved = { ...pending, original: { version: 1 } };
|
||||
assert.deepEqual(radarAfterBindingChange(saved, false, false), {
|
||||
radar: saved, radarEligible: true, radarTouched: false, radarRemove: false,
|
||||
});
|
||||
assert.notEqual(freshRadarInstallationId(), freshRadarInstallationId());
|
||||
});
|
||||
|
||||
test('manual exact sources may come from separate HA devices', () => {
|
||||
|
||||
@@ -37,6 +37,27 @@ test('two reference solve keeps physical scale rigid', () => {
|
||||
], 5), /invalid_selection/);
|
||||
});
|
||||
|
||||
test('two-point calibration keeps every distance and fit guard distinct', () => {
|
||||
const mount = [.5, .5];
|
||||
const plan = (x, y) => [.5 + x / 1200, .5 - y / 1200];
|
||||
assert.throws(() => solveRadarTwoPoint(mount, [[49, 0], [0, 100]], [
|
||||
plan(49, 0), plan(0, 100),
|
||||
], 5), /invalid_selection/, 'references under 50 cm are rejected');
|
||||
assert.throws(() => solveRadarTwoPoint(mount, [[100, 0], [0, 100]], [
|
||||
plan(100, 0), plan(-50, 86.603),
|
||||
], 5), /invalid_selection/, 'RMS over 20 cm is rejected');
|
||||
assert.throws(() => solveRadarTwoPoint(mount, [[100, 0], [0, 100]], [
|
||||
plan(100, 0), plan(-64.279, 76.604),
|
||||
], 5), /invalid_selection/, 'an individual error over 30 cm is rejected');
|
||||
});
|
||||
|
||||
test('two-point calibration exposes an ambiguous mirror instead of choosing silently', () => {
|
||||
const plan = (x, y) => [.5 + x / 1200, .5 - y / 1200];
|
||||
assert.throws(() => solveRadarTwoPoint([.5, .5], [[-20, 80], [20, 80]], [
|
||||
plan(0, 80), plan(0, 80),
|
||||
], 5), /ambiguous_sources/);
|
||||
});
|
||||
|
||||
test('capture median rejects one sample more than 15cm away', () => {
|
||||
assert.deepEqual(radarMedianSample([[100, 200], [102, 198], [101, 201]]), [101, 200]);
|
||||
assert.throws(() => radarMedianSample([[100, 200], [101, 199], [140, 240]]), /bad_fit/);
|
||||
|
||||
@@ -26,10 +26,14 @@ test('radar frames reject malformed geometry and cap nested payloads', () => {
|
||||
],
|
||||
ranges: [{ id: 'r', x: 10, y: 20, radius: 3, reported_at: 100, expires_at: 103,
|
||||
segments: [[[1, 2], [3, 4]], [[Number.NaN, 2], [3, 4]]] }],
|
||||
zones: [{ id: 'desk', state: true, polygon: [[.1, .1], [.3, .1], [.3, .3]] },
|
||||
{ id: 'broken', state: true, polygon: [[.1, Number.NaN], [.2, .2], [.3, .3]] }],
|
||||
}));
|
||||
assert.ok(normalized);
|
||||
assert.equal(normalized.targets.length, 1);
|
||||
assert.deepEqual(normalized.ranges[0].segments, [[[1, 2], [3, 4]]]);
|
||||
assert.deepEqual(normalized.zones[0].polygon, [[.1, .1], [.3, .1], [.3, .3]]);
|
||||
assert.equal(normalized.zones[1].polygon, undefined);
|
||||
assert.equal(normalizeRadarFrame({ marker_id: 'x' }), null);
|
||||
});
|
||||
|
||||
@@ -84,6 +88,9 @@ test('radar health respects disabled config before runtime state', () => {
|
||||
const radar = { version: 1, enabled: false, show_live: true };
|
||||
assert.equal(radarHealthI18nKey(radar, 'ok'), 'radar.health_disabled');
|
||||
assert.equal(radarHealthI18nKey({ ...radar, enabled: true }, 'ok'), 'radar.health_ok');
|
||||
assert.equal(radarHealthI18nKey({ ...radar, enabled: true }, 'partial'), 'radar.health_partial');
|
||||
assert.equal(radarHealthI18nKey({ ...radar, enabled: true }, 'restricted'), 'radar.health_restricted');
|
||||
assert.equal(radarHealthI18nKey({ ...radar, enabled: true }, 'incomplete'), 'radar.health_incomplete');
|
||||
assert.equal(radarHealthI18nKey({ ...radar, enabled: true }, 'future_status'),
|
||||
'radar.health_unknown');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { renderRadarLive } from '../test-build/radar-render.js';
|
||||
|
||||
const frame = (state) => ({
|
||||
marker_id: 'radar', targets: [], ranges: [],
|
||||
zones: [{ id: 'desk', state, polygon: [[.1, .1], [.3, .1], [.3, .3]] }],
|
||||
});
|
||||
|
||||
test('known occupied radar zones render server-owned geometry only', () => {
|
||||
const occupied = renderRadarLive([frame(true)], { x: 0, y: 0, w: 1000, h: 1000 }, (p) => p);
|
||||
const zone = occupied.values[0].values[4][0];
|
||||
assert.match(zone.strings.join(''), /class="radar-zone"/);
|
||||
assert.equal(zone.values[0], 'desk');
|
||||
assert.equal(zone.values[1], '100,100 300,100 300,300');
|
||||
|
||||
const empty = renderRadarLive([frame(false)], { x: 0, y: 0, w: 1000, h: 1000 }, (p) => p);
|
||||
assert.equal(empty, Symbol.for('lit-nothing'));
|
||||
});
|
||||
@@ -23,6 +23,7 @@ test('on-plan installation changes only the editor draft until ordinary Save', (
|
||||
const controller = new RadarSetupController({
|
||||
hass: () => ({}), requestUpdate() {}, t: (key) => key,
|
||||
configFromDraft: radarConfigFromDraft, apply: (next) => { applied = next; },
|
||||
confirmDiscard: async () => true,
|
||||
});
|
||||
assert.equal(controller.begin('radar', draft(), {
|
||||
id: 'living', name: 'Living', poly: [[0, 0], [1, 0], [1, 1], [0, 1]],
|
||||
@@ -40,17 +41,37 @@ test('on-plan installation changes only the editor draft until ordinary Save', (
|
||||
assert.equal(radarConfigFromDraft(applied, 5).mount.x, .25);
|
||||
});
|
||||
|
||||
test('cancelling calibration releases its draft subscription exactly once', () => {
|
||||
test('cancelling calibration releases its draft subscription exactly once', async () => {
|
||||
const controller = new RadarSetupController({
|
||||
hass: () => ({}), requestUpdate() {}, t: (key) => key,
|
||||
configFromDraft: radarConfigFromDraft, apply() {},
|
||||
configFromDraft: radarConfigFromDraft, apply() {}, confirmDiscard: async () => true,
|
||||
});
|
||||
assert.equal(controller.begin('radar', draft(), {
|
||||
id: 'living', name: 'Living', poly: [[0, 0], [1, 0], [1, 1]],
|
||||
}, 5, 7), true);
|
||||
let calls = 0;
|
||||
controller.unsubscribe = () => { calls += 1; };
|
||||
assert.equal(controller.cancel(), true);
|
||||
assert.equal(controller.cancel(), false);
|
||||
assert.equal(await controller.cancel(), true);
|
||||
assert.equal(await controller.cancel(), true);
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
|
||||
test('dirty calibration stays open when discard confirmation is rejected', async () => {
|
||||
let confirmations = 0;
|
||||
const controller = new RadarSetupController({
|
||||
hass: () => ({}), requestUpdate() {}, t: (key) => key,
|
||||
configFromDraft: radarConfigFromDraft, apply() {},
|
||||
confirmDiscard: async () => { confirmations += 1; return false; },
|
||||
});
|
||||
assert.equal(controller.begin('radar', draft(), {
|
||||
id: 'living', name: 'Living', poly: [[0, 0], [1, 0], [1, 1]],
|
||||
}, 5, 7), true);
|
||||
controller.choosePoint(pointer(250, 400));
|
||||
let cleanups = 0;
|
||||
controller.unsubscribe = () => { cleanups += 1; };
|
||||
|
||||
assert.equal(await controller.cancel(), false);
|
||||
assert.equal(confirmations, 1);
|
||||
assert.equal(cleanups, 0);
|
||||
assert.ok(controller.active);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user