mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Гард «uncaught exception внутри карточки» жил в demo/serve.mjs с 2026-07-27 и не срабатывал ни разу в самом частом случае. Счётчик читался синхронно, а Playwright доставляет pageerror асинхронно по CDP: если исключение возникло после последнего обращения смока к странице, счётчик к моменту проверки нулевой, а browser.close() уносит недоставленное событие. В логе это видно дословно — EXC печатается после результата и до OK. finish() теперь делает round-trip по открытым страницам перед чтением счётчика. Страницы регистрируются там, где создаются: ссылок на них у finish(browser, out) нет, а менять сигнатуру нельзя — так её зовут 205 смоков. Medium-1 жёлтого ревью ТЗ закрыт расширением, а не оговоркой. Страницы, созданные смоком после launch(), регистрация в launchInternal не покрывает: smoke_zoom_flash печатал своё EXC2 мимо счётчика, три страницы smoke_svg_sandbox не имели слушателя вовсе. Документировать слепую зону в задаче, которая существует ради устранения слепой зоны, значит закрыть issue, оставив дефект. Наружу отдана одна функция watchPage(page): подписка и регистрация неразделимы, иначе появится страница, чьи исключения считаются, а доставки не ждёт никто. Разрыв оказался шире, чем в ревью: проверка по всему набору нашла ещё два файла со своей подпиской — smoke_cold_view_toggle и smoke_cold_view_vacuum. Они не слепая зона, их страница приходит из launchColdView и уже зарегистрирована, а свой счётчик они превращают в отдельное утверждение. Поэтому инвариант сформулирован как «ни одна страница не создаётся мимо гарда» и закреплён по всему набору, а не по двум названным файлам. reportPageErrors() из #407 стал асинхронным: второй читатель счётчика обязан ждать доставку так же, как finish(). Пять смоков получили await. Фикстура smoke_danger_confirmation приведена к объявленному типу: без binding и bindingMode _bindingHasHaPage падал на undefined.split(':') — два исключения, которых гард не видел. Дефекта поведения нет, все 15 мест в src/, создающих диалог, binding пишут; врала фикстура. Два отступления от ТЗ, каждое по измеренной причине. Пробы лежат в demo/guard/, а не demo/fixtures/: последний входит в корпус sourceFingerprint, и каждый файл там объявил бы устаревшими бандл, скриншот-индекс и golden-индекс — пробы же не касаются ни одного пикселя. Поведение доказывается в job со браузером, а не в npm test: job «Фронтенд» браузеры не ставит, и тест молча скипался бы — тот самый тихий успех, против которого вся задача. Issue: #404 User-Visible: no
32 lines
1.9 KiB
JavaScript
32 lines
1.9 KiB
JavaScript
// Asserts device icon badges sit exactly on their anchor point (no content-box/border drift).
|
|
import { launch, reportPageErrors } from './serve.mjs';
|
|
const { page, browser } = await launch({ width: 900, height: 1000 }, 2);
|
|
const bad = await page.evaluate(() => {
|
|
const c = window.__card;
|
|
c._space = 'f1'; c._resetZoom(); c.requestUpdate();
|
|
const layer = c.renderRoot.querySelector('.devlayer');
|
|
const lr = layer.getBoundingClientRect();
|
|
const bad = [];
|
|
for (const d of layer.querySelectorAll('.dev')) {
|
|
const ax = lr.left + (parseFloat(d.style.left) / 100) * lr.width;
|
|
const ay = lr.top + (parseFloat(d.style.top) / 100) * lr.height;
|
|
const r = d.getBoundingClientRect();
|
|
const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
|
|
const offX = cx - ax, offY = cy - ay;
|
|
// glyph centred within the badge
|
|
const icon = d.querySelector('ha-icon');
|
|
const svg = icon?.shadowRoot?.querySelector('svg') || icon?.shadowRoot?.querySelector('ha-svg-icon')?.shadowRoot?.querySelector('svg');
|
|
let gX = 0, gY = 0;
|
|
if (svg) { const sr = svg.getBoundingClientRect(); gX = sr.left + sr.width / 2 - cx; gY = sr.top + sr.height / 2 - cy; }
|
|
if (Math.abs(offX) > 0.6 || Math.abs(offY) > 0.6 || Math.abs(gX) > 0.6 || Math.abs(gY) > 0.6)
|
|
bad.push({ icon: icon?.getAttribute('icon'), offX: +offX.toFixed(2), offY: +offY.toFixed(2), glyphX: +gX.toFixed(2), glyphY: +gY.toFixed(2) });
|
|
}
|
|
return bad;
|
|
});
|
|
await browser.close();
|
|
// #407: своя развязка про исключения в карточке не спрашивает. Вердикт обязан
|
|
// именно остановить: иначе строка успеха печатается после «FAILED».
|
|
if (await reportPageErrors()) process.exit(1);
|
|
if (bad.length) { console.error('FAIL icon-center: badges off anchor', JSON.stringify(bad.slice(0, 5))); process.exit(1); }
|
|
console.log('OK icon-center: all device badges centred on their anchor');
|