`src/backdrop-pick.ts` и `src/decor-image-editor.ts` не входили в
tsconfig.test.json — юнитов у них не было вовсе. Смок при этом подменял
`hass.callWS` целиком и до `uploadFromInput`, `upload`, `delete` не
доходил; в шапке файла это было честно написано. AC1 #51 обещал
доказательство «component unit + smoke» — не было ни того, ни другого.
Цена известна: #427 — файл тяжелее 2 МиБ нельзя было добавить ничем,
гасли обе кнопки диалога, включая «уменьшить копию», — прожил четыре
круга ревью именно потому, что этот путь не проверяло ничто.
Оба файла теперь в tsconfig.test.json (компиляция +6 с, без правок
продуктового кода) и покрыты 13 юнитами:
- классификация: проходной файл, тяжелее лимита источника (guard с
probe.kind='safe' — предупреждение о размере, не об отказе), warn,
hard, unknown, неподдерживаемый формат;
- состав кнопок диалога на трёх случаях. Декор свыше лимита: «Отмена» и
«уменьшить копию», без «оставить оригинал» — это и есть #427. Подложка
на том же файле сохраняет обе кнопки, hard остаётся с одной «Отмена»,
unknown предупреждает, а не проходит тихо;
- uploadFromInput: сброс input.value, молчание на занятом, отказ формата,
исключение классификации, передача флага замены в оба слота;
- upload: multipart-поле и URL, порядок каталога, палитра против замены
ссылки в диалоге, восемь ответов бэкенда с разными текстами, отпускание
busy на отказе, запрет второго запроса поверх незакончившегося;
- delete: ссылки на ассет, отказ в подтверждении, чистка каталога, карты
и палитры, различение in_use, сохранность каталога на отказе сервера;
- loadCatalog: отбор валидных строк и тост на отказе.
Смок `smoke_decor_images.mjs` получил последний блок, который проходит
настоящие uploadFromInput → upload → delete в собранном бандле, подменяя
только транспорт: fetchWithAuth и одну ветку callWS.
Свидетели (§2.7, #435). Девять мутаций прогнаны на собранном дереве,
каждая краснит свой тест: условие #427 → тест состава кнобок; снятие
лимита источника; input.value; флаг замены; too_large; busy в upload;
подтверждение удаления; used_by; проглоченный отказ каталога. Мутация
busy сначала вешала прогон и уносила пять тестов cancelledByParent —
тест переписан так, чтобы падать на сравнении, а не по таймауту.
В реестр добавлены три мутанта на продуктовый код: условие #427, потеря
флага замены, схлопывание кодов ошибок.
Гейты: npm test 1806 tests, 1805 pass, 0 fail (было 1791);
mutation-gate --check зелёный. Смок локально не прогнать — Chromium в
песочнице нет, проверит CI.
Issue: #433
User-Visible: no
A fully fictional house (plans, devices, states) used for README screenshots,
the demo GIF and headless smoke tests — so no real home data ever appears in
public materials.
srv/demo.html — self-contained host page: <ha-icon>/<ha-card> stubs and a
fake hass (registries, states, callWS, callService, floors).
srv/assets/ — generated plan SVGs and icons.js (node demo/gen_icons.mjs,
needs the repo's devDependencies). The card bundle is copied from dist/:
npm run bundle:sync (копия стенда не коммитится, #255).
serve.mjs — playwright launcher (route interception, no web server).
smoke_*.mjs — feature smoke tests; run with a Chromium installed via
PLAYWRIGHT_BROWSERS_PATH=<dir> npx playwright install chromium-headless-shell.
Note for sandboxed sessions: /tmp does not survive; this directory is the
persistent home of the harness (docs/DEVELOPMENT.md has the LD_LIBRARY_PATH recipe).