mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
Audit HP-1540-01 (High): an auto-discovered vacuum has no config marker until the device dialog is saved once, yet the live-position section was already interactive. setVac, _vacSaveMatrix and auto-calibration all did cfg.markers.find(...) and silently bailed out — while the auto-calibration toast still claimed success. Every vacuum edit now materializes a minimal marker (same id/binding the dialog Save would produce), _vacSaveMatrix reports whether the write landed, and success toasts are gated on it. HP-1540-04: the auto-calibration room matcher accepted only polygon rooms and told users their room names did not match. It goes through the shared roomPoly() now, so legacy x/y/w/h rectangles count like everywhere else. HP-1540-06: the no-rooms/no-match/rough-fit toasts pointed at the removed point calibration; they now point at the fit panel that shipped instead, and docs/VACUUM.md Setup UX describes the actual UI. Also extracted vacMapIdFromAttrs as the explicit frontend half of the map-id contract (backend half lands with HP-1540-02). Regressions: demo/smoke_vacuum_firstuse.mjs starts from cfg.markers=[] (the fixture gap the audit called out) with rectangle plan rooms and a zero map_index, and fails 11 checks on the v1.54.0 bundle; i18n unit test asserts no point/точк wording in either language.
109 lines
5.5 KiB
JavaScript
109 lines
5.5 KiB
JavaScript
// v1.54.0 audit regressions (HP-1540-01/-04/-06): the FIRST-USE path.
|
|
// An auto-discovered vacuum has NO marker in the config — cfg.markers stays
|
|
// empty here, unlike smoke_vacuum.mjs which always pushes one by hand (the
|
|
// audit called that gap out explicitly). Every vacuum action must materialise
|
|
// the marker and actually persist; plan rooms are legacy RECTANGLES.
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
const { page, browser } = await launch();
|
|
|
|
const out = await page.evaluate(async () => {
|
|
const c = window.__card;
|
|
const o = {};
|
|
const sr = () => c.shadowRoot || c.renderRoot;
|
|
const cfg = c._serverCfg;
|
|
|
|
// legacy rectangle rooms (x/y/w/h, HP-1540-04) in the garden space where
|
|
// the auto-discovered vacuum.mower already lives
|
|
const g = cfg.spaces.find((s) => s.id === 'garden');
|
|
g.rooms = [
|
|
{ id: 'ga', name: 'Кухня', area: 'garden', x: 0.05, y: 0.10, w: 0.25, h: 0.30 },
|
|
{ id: 'gb', name: 'Зал', x: 0.40, y: 0.10, w: 0.25, h: 0.30 },
|
|
{ id: 'gc', name: 'Спальня', x: 0.05, y: 0.50, w: 0.25, h: 0.30 },
|
|
];
|
|
// robot rooms: same centres in "robot mm" (scale 4), map_index is a
|
|
// NUMERIC ZERO — the cross-runtime zero-map contract (HP-1540-02)
|
|
const robotRooms = {
|
|
1: { name: 'Кухня', x0: 300, y0: 600, x1: 1100, y1: 1400 },
|
|
2: { name: 'Зал', x0: 1700, y0: 600, x1: 2500, y1: 1400 },
|
|
3: { name: 'Спальня', x0: 300, y0: 2200, x1: 1100, y1: 3000 },
|
|
};
|
|
const camAttrs = { vacuum_position: { x: 700, y: 1000, a: 0 }, map_index: 0, rooms: robotRooms };
|
|
c.hass = { ...c.hass,
|
|
entities: { ...c.hass.entities,
|
|
'camera.mower_map': { entity_id: 'camera.mower_map', device_id: 'd_mower', platform: 'demo' } },
|
|
states: { ...c.hass.states,
|
|
'camera.mower_map': { state: 'idle', attributes: camAttrs } } };
|
|
c._regSignature = '';
|
|
c.requestUpdate(); await c.updateComplete;
|
|
|
|
// spy on config writes: a success toast is only honest after one of these
|
|
const writes = [];
|
|
const realWS = c.hass.callWS;
|
|
c.hass.callWS = async (m) => { if (m.type === 'houseplan/config/set') writes.push(m); return realWS(m); };
|
|
|
|
const dev = c._devices.find((x) => x.id === 'd_mower');
|
|
o.devFound = !!dev;
|
|
o.freshNoMarker = (cfg.markers || []).length === 0 && !dev.marker;
|
|
|
|
// ---- auto-calibration from scratch (HP-1540-01 + HP-1540-04) ----
|
|
c._vacAutoCalibrate(dev);
|
|
await c.updateComplete;
|
|
const m1 = (cfg.markers || []).find((x) => x.id === 'd_mower');
|
|
o.markerMaterialised = !!m1 && m1.binding === 'device:d_mower';
|
|
const cal0 = m1?.vacuum?.calibration?.['0'];
|
|
o.rectRoomsCalibrated = Array.isArray(cal0) && cal0.length === 6 && cal0.every(Number.isFinite);
|
|
o.sourceStored = m1?.vacuum?.source === 'camera.mower_map';
|
|
// the matrix really maps robot mm → canvas: centre of «Кухня» → (175, 250)
|
|
const ap = (mm, x, y) => [mm[0] * x + mm[1] * y + mm[2], mm[3] * x + mm[4] * y + mm[5]];
|
|
const hit = cal0 ? ap(cal0, 700, 1000) : [0, 0];
|
|
o.matrixMapsRooms = Math.hypot(hit[0] - 175, hit[1] - 250) < 2;
|
|
o.successToastShown = (c._toast || '').startsWith('Done: bound via 3 rooms');
|
|
await new Promise((r) => setTimeout(r, 700)); // debounced _saveConfig
|
|
o.configWritePersisted = writes.length >= 1
|
|
&& !!writes[writes.length - 1].config.markers.find((x) => x.id === 'd_mower')?.vacuum?.calibration?.['0'];
|
|
|
|
// ---- manual fit from scratch (HP-1540-01) ----
|
|
cfg.markers = [];
|
|
c._regSignature = ''; c.requestUpdate(); await c.updateComplete;
|
|
const dev2 = c._devices.find((x) => x.id === 'd_mower');
|
|
o.fitFreshNoMarker = !dev2.marker;
|
|
c._vacStartFit(dev2); await c.updateComplete;
|
|
c._vacFitSave(); await c.updateComplete;
|
|
const m2 = (cfg.markers || []).find((x) => x.id === 'd_mower');
|
|
const calFit = m2?.vacuum?.calibration?.['0'];
|
|
o.fitMaterialises = !!m2 && Array.isArray(calFit) && calFit.length === 6;
|
|
o.fitToastAfterSave = c._toast === c._t('vac.cal_done');
|
|
|
|
// ---- live checkbox + trail select from scratch (HP-1540-01) ----
|
|
cfg.markers = [];
|
|
c._regSignature = ''; c.requestUpdate(); await c.updateComplete;
|
|
c._setMode('devices'); await c.updateComplete;
|
|
c._openMarkerDialog(c._devices.find((x) => x.id === 'd_mower')); await c.updateComplete;
|
|
const liveBox = sr().querySelector('.vacbox input[type=checkbox]');
|
|
o.vacSectionShown = !!liveBox;
|
|
if (liveBox) { liveBox.click(); await c.updateComplete; }
|
|
const m3 = (cfg.markers || []).find((x) => x.id === 'd_mower');
|
|
o.liveToggleMaterialises = !!m3 && m3.vacuum?.live === false;
|
|
const sel = sr().querySelector('.vacbox select');
|
|
if (sel) { sel.value = 'always'; sel.dispatchEvent(new Event('change')); await c.updateComplete; }
|
|
o.trailModePersists = m3?.vacuum?.trail_mode === 'always';
|
|
c._markerDialog = null; c._setMode('view'); await c.updateComplete;
|
|
|
|
// ---- error copy never points at the removed point calibration (HP-1540-06) ----
|
|
const toasts = ['vac.autocal_no_rooms', 'vac.autocal_no_match', 'vac.autocal_res_warn']
|
|
.map((k) => c._t(k)).join(' | ');
|
|
o.noPointCalibrationCopy = !/point|точк/i.test(toasts);
|
|
// and the real no-rooms path shows the reworded toast
|
|
c.hass = { ...c.hass, states: { ...c.hass.states,
|
|
'camera.mower_map': { state: 'idle', attributes: { vacuum_position: { x: 1, y: 2, a: 0 }, map_index: 0 } } } };
|
|
await c.updateComplete;
|
|
c._vacAutoCalibrate(c._devices.find((x) => x.id === 'd_mower'));
|
|
o.noRoomsToastReworded = c._toast === c._t('vac.autocal_no_rooms') && !/point|точк/i.test(c._toast);
|
|
|
|
c.hass.callWS = realWS;
|
|
return o;
|
|
});
|
|
|
|
checkAll(out);
|
|
await finish(browser, out);
|