mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
test(smoke): cover the binding picker
git grep _bindingCandidates -- test/ demo/ was empty: the function that decides what the user is offered under Add had no test and no smoke. Tombstones were covered from every side, the list they filter was never asked. That is how #262 reached us through a user report instead of a gate — smoke_hidden_flag assigns binding straight into _markerDialog and bypasses the picker entirely. Twelve checks against the real bundle: a deleted device is offered again, a deleted plain entity is offered again behind the checkbox, the checkbox itself is the trap (a device entity is absent with it off, present with it on, and it starts off for a new marker), a placed binding is not duplicated, and re-adding replaces the tombstone and leaves the picker. The twelfth pins the known defect #262 as current behaviour: a device tombstone still hides its child entities. Fixing it turns the check red and forces it to be flipped, so the fix cannot pass the coverage by. smoke-links registers only the pure tombstone helpers. The picker names itself and is found by direct match; the helpers are not named anywhere in the scenario. Verified by probe: touching src/devices.ts alone selects this smoke as a registered link, and without the entry nothing would select it. Issue: #263 User-Visible: no
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Пикер привязок: что пользователь видит в списке «Добавить» (#263).
|
||||
//
|
||||
// Зачем смок, а не юнит-тест. `_bindingCandidates` — приватный метод карточки,
|
||||
// читающий `_planHass`, `_devices`, `_markers` и состояние диалога. Переписать
|
||||
// его логику в тесте значит проверять копию: ровно так дефект #262 и дожил до
|
||||
// отчёта пользователя — надгробия были покрыты со всех сторон, а список никто
|
||||
// не спрашивал. Здесь дёргается настоящий метод собранного бандла.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
const { page, browser } = await launch();
|
||||
const out = {};
|
||||
|
||||
Object.assign(out, await page.evaluate(async () => {
|
||||
const o = {};
|
||||
const c = window.__card;
|
||||
const nativeConfirm = window.confirm;
|
||||
|
||||
// Пикер читает `showEntities` и `bindingFilter` из открытого диалога, поэтому
|
||||
// спрашивать его при закрытом бессмысленно: отдельные сущности устройств не
|
||||
// перечисляются вовсе, и «сущность вернулась» ничего не значило бы.
|
||||
const openAdd = async (showEntities) => {
|
||||
c._openMarkerDialog();
|
||||
await c.updateComplete;
|
||||
c._markerDialog = { ...c._markerDialog, showEntities: !!showEntities, bindingFilter: '' };
|
||||
await c.updateComplete;
|
||||
};
|
||||
const offered = async (showEntities) => {
|
||||
await openAdd(showEntities);
|
||||
return c._bindingCandidates().map((item) => item.value);
|
||||
};
|
||||
const remove = async (device) => {
|
||||
c._openMarkerDialog(device);
|
||||
await c.updateComplete;
|
||||
window.confirm = () => true;
|
||||
await c._deleteMarker();
|
||||
await c.updateComplete;
|
||||
window.confirm = nativeConfirm;
|
||||
};
|
||||
const tombstones = () => (c._serverCfg.markers || []).filter((m) => m.removed === true);
|
||||
|
||||
// --- 1. удалили устройство — оно снова предлагается ----------------------
|
||||
const device = c._devices.find((d) => d.bindingKind === 'device' && d.bindingRef && !d.hidden);
|
||||
o.standHasDeviceMarker = !!device;
|
||||
const deviceBinding = `device:${device.bindingRef}`;
|
||||
const childEntity = Object.entries(c._planHass.entities)
|
||||
.find(([, reg]) => reg.device_id === device.bindingRef)?.[0];
|
||||
o.standHasChildEntity = !!childEntity;
|
||||
|
||||
o.placedDeviceNotOffered = !(await offered(false)).includes(deviceBinding);
|
||||
await remove(device);
|
||||
o.deleteLeavesTombstone = tombstones().some((m) => m.binding === deviceBinding);
|
||||
o.deletedDeviceOfferedAgain = (await offered(false)).includes(deviceBinding);
|
||||
|
||||
// --- 2. чекбокс «показывать сущности» и есть ловушка ---------------------
|
||||
// «Нет в списке» и «спрятано за галкой» для пользователя неразличимы: для
|
||||
// нового маркера галка выключена, и обычная сущность устройства отсутствует.
|
||||
const withoutCheckbox = await offered(false);
|
||||
const withCheckbox = await offered(true);
|
||||
const plainEntity = Object.entries(c._planHass.entities)
|
||||
.find(([eid, reg]) => reg.device_id && reg.device_id !== device.bindingRef
|
||||
&& !withoutCheckbox.includes(`entity:${eid}`))?.[0];
|
||||
o.standHasPlainEntity = !!plainEntity;
|
||||
o.plainEntityHiddenWithoutCheckbox = !withoutCheckbox.includes(`entity:${plainEntity}`);
|
||||
o.plainEntityShownWithCheckbox = withCheckbox.includes(`entity:${plainEntity}`);
|
||||
o.checkboxOffForNewMarker = (await (async () => {
|
||||
c._openMarkerDialog();
|
||||
await c.updateComplete;
|
||||
return c._markerDialog.showEntities;
|
||||
})()) === false;
|
||||
|
||||
// --- 3. известный дефект #262 -------------------------------------------
|
||||
// Надгробие ключуется по точной строке привязки, поэтому `device:<id>`
|
||||
// разблокирует только само устройство: `isRemovedPlanEntity` продолжает
|
||||
// выкидывать из списка все его дочерние сущности. Здесь закреплено ТЕКУЩЕЕ
|
||||
// поведение — починят #262, и смок покраснеет, потребовав перевернуть
|
||||
// проверку. Молча пройти фикс мимо покрытия не сможет.
|
||||
o.knownDefect262ChildEntityBlocked =
|
||||
!(await offered(true)).includes(`entity:${childEntity}`);
|
||||
|
||||
// --- 4. повторное добавление возвращает устройство в список размещённых --
|
||||
await openAdd(false);
|
||||
c._markerDialog = { ...c._markerDialog, bindingMode: 'ha', binding: deviceBinding,
|
||||
name: device.name };
|
||||
await c._saveMarker();
|
||||
await c.updateComplete;
|
||||
o.readdReplacesTombstone = !tombstones().some((m) => m.binding === deviceBinding)
|
||||
&& (c._serverCfg.markers || []).some((m) => m.binding === deviceBinding);
|
||||
o.readdRemovesFromPicker = !(await offered(false)).includes(deviceBinding);
|
||||
|
||||
c._markerDialog = null;
|
||||
c._setMode('view');
|
||||
await c.updateComplete;
|
||||
return o;
|
||||
}));
|
||||
|
||||
checkAll(out);
|
||||
await finish(browser, out);
|
||||
@@ -27,6 +27,19 @@
|
||||
|
||||
/** @type {SmokeLink[]} */
|
||||
export const SMOKE_LINKS = [
|
||||
{
|
||||
// #263: пикер называет себя (`_bindingCandidates`) и потому находится
|
||||
// прямым совпадением. Регистрируются только чистые помощники надгробий:
|
||||
// смок не произносит ни одного их имени — он видит лишь список, который
|
||||
// они отфильтровали. Проверено зондом: правка одного `src/devices.ts` без
|
||||
// этой записи не выбирает ни одного смока.
|
||||
symbols: ['removedPlanBindings', 'isRemovedPlanEntity', 'deletePlanMarkerRecords'],
|
||||
smokes: ['smoke_binding_picker.mjs'],
|
||||
because: 'смок проверяет, что удалённая привязка снова предлагается в списке «Добавить», '
|
||||
+ 'что отдельные сущности устройств живут за галкой и что надгробие устройства '
|
||||
+ 'по-прежнему прячет его дочерние сущности (#262) — всё это следствия фильтров '
|
||||
+ 'надгробий, чьи имена в браузерный сценарий не попадают',
|
||||
},
|
||||
{
|
||||
// #258: the browser sees only the resulting path/caches; it cannot call
|
||||
// the pure identity helpers by name through the production bundle.
|
||||
|
||||
Reference in New Issue
Block a user