mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
Owner's spec (2026-07-29), agreed points: one 'Run' action covering the three runnable domains of HA (a script is the idiomatic 'action' — with automations alone people would build trigger-less dummies); the confirm checkbox guards BOTH toggle and run; covers and valves join the card-wide toggle so curtains work natively. - marker.tap_action gains 'run'; marker.tap_target (schema-bounded to automation./script./scene. ids); marker.tap_confirm. - the dialog: a searchable picker over the three domains (friendly name + kind), save refuses a run action without a target, a vanished target gets a warning hint; the checkbox shows for any actionable tap (explicit or effective-default toggle). - the tap: automation.trigger / script.turn_on / scene.turn_on, started/ error toasts; with confirm on — our own dialog (not window.confirm, it must work on a wall tablet), Esc/backdrop/Cancel = no call. The guard covers the controls-toggle path too. - 'run' is explicit-only by construction: it needs a per-marker target, so it can never arrive as a card-wide default. - covers: the old test pinned 'garage stays shut' — that intent survives as COVER_GUARDED_CLASSES (garage/door/gate stay out of the CARD-WIDE toggle; an explicit per-device toggle remains the owner's conscious choice). Locks/alarms stay forbidden everywhere, run included is not affected — we do not inspect automation contents, same trust as HA's own Run button. Tests: unit resolveTapAction/runServiceFor + cover guard, backend schema parity picks 'run' automatically + tap_target bounds, smoke_tap_run with 11 assertions (picker, search, save guard, confirm cancel/ok, per-domain services, missing target). smoke_tap_ctx: 4 options now. Inventory: 148 / 52 / 43 / 72.
62 lines
3.2 KiB
JavaScript
62 lines
3.2 KiB
JavaScript
import { launch, checkAll, finish } from './serve.mjs';
|
||
const { page, browser } = await launch();
|
||
const res = await page.evaluate(async () => {
|
||
const out = {};
|
||
const c = window.__card;
|
||
const sr = () => c.shadowRoot || c.renderRoot;
|
||
// 1) в селекте действий 3 опции, дефолт «Карточка устройства»
|
||
c._setMode('devices'); await c.updateComplete;
|
||
// v1.39.0: у ЛАМП дефолт 'toggle', поэтому для проверки дефолта 'info'
|
||
// берём заведомо не-световое устройство
|
||
const dev = c._devices.find((d) => !d.virtual && d.primary && !d.primary.startsWith('light.'));
|
||
c._openMarkerDialog(dev); await c.updateComplete;
|
||
const sel = [...sr().querySelectorAll('.dialog select')].find((s) =>
|
||
[...s.options].some((o) => o.textContent === c._t('tap.toggle')));
|
||
out.threeOptions = sel && sel.options.length === 4; // + «Запустить…» (2026-07-29)
|
||
out.noAutoOption = sel && ![...sel.options].some((o) => o.value === '');
|
||
out.defaultInfo = sel && sel.value === 'info';
|
||
c._markerDialog = null; await c.updateComplete;
|
||
// 2) правый клик в Просмотре открывает more-info
|
||
c._setMode('view'); await c.updateComplete;
|
||
let moreInfo = null;
|
||
c._openMoreInfo = (eid) => { moreInfo = eid; };
|
||
const ev = new MouseEvent('contextmenu', { bubbles: true, cancelable: true });
|
||
c._ctxDevice(ev, dev);
|
||
out.ctxMoreInfo = moreInfo === dev.primary;
|
||
out.ctxPrevented = ev.defaultPrevented;
|
||
// 3) в редакторах правый клик не перехватывается
|
||
c._setMode('devices'); await c.updateComplete;
|
||
moreInfo = null;
|
||
const ev2 = new MouseEvent('contextmenu', { bubbles: true, cancelable: true });
|
||
c._ctxDevice(ev2, dev);
|
||
out.editorNative = moreInfo === null && !ev2.defaultPrevented;
|
||
// 4) виртуальное без primary → инфо-карточка
|
||
c._setMode('view'); await c.updateComplete;
|
||
const virt = c._devices.find((d) => d.virtual && !d.primary) || null;
|
||
if (virt) {
|
||
c._ctxDevice(new MouseEvent('contextmenu', { cancelable: true }), virt);
|
||
out.virtInfo = c._infoCard === virt;
|
||
c._infoCard = null;
|
||
} else out.virtInfo = 'no-virt';
|
||
// 5) card-wide tap_action игнорируется: без явного действия клик = инфо
|
||
c._config = { ...c._config, tap_action: 'toggle' };
|
||
const calls = [];
|
||
c.hass = { ...c.hass, callService: (d2, s2, data) => { calls.push([d2, s2, data]); return Promise.resolve(); } };
|
||
await c.updateComplete;
|
||
// card-wide tap_action игнорируется: НЕ-световое устройство остаётся на инфо
|
||
const plain = c._devices.find((d) => !d.virtual && d.primary
|
||
&& !d.primary.startsWith('light.') && !d.tapAction && !d.marker?.controls?.length);
|
||
if (plain) {
|
||
c._infoCard = null;
|
||
c._clickDevice(new MouseEvent('click'), plain);
|
||
out.cardTapIgnored = calls.length === 0 && !!c._infoCard;
|
||
c._infoCard = null;
|
||
} else out.cardTapIgnored = 'no-plain-device';
|
||
return out;
|
||
});
|
||
// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1)
|
||
checkAll(res, {
|
||
"virtInfo": "no-virt",
|
||
});
|
||
await finish(browser, res);
|