mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
L2 (silent data loss): debounce gains flush()/pending(); _reloadConfigOnly flushes a pending write and defers while one is in flight; conflict path forces; failed reload now toasts instead of an empty catch; teardown flushes. G1 (split corruption): same-edge cuts carve the niche properly instead of walking the outline twice; partition invariant (parts sum to the original) rejects anything else; +1 unit test covering 5 niche shapes and both legacy cut shapes. B1 (unauthenticated content): plans and marker files move to HouseplanContentView (/api/houseplan/content/..., requires_auth); only the card bundle stays static; contentUrl() rewrites legacy URLs on read (no storage migration); repairs.py accepts both prefixes; +1 unit test. L3 (dialog zombies): all four save catch-blocks guard against a closed dialog; the card no longer blanks when a save fails after Esc. smokes: smoke_save_race, smoke_dialog_zombie; docs (TESTING/CHANGELOG/ ARCHITECTURE incl. the optimistic-UI note) same-commit
56 lines
2.5 KiB
JavaScript
56 lines
2.5 KiB
JavaScript
import { launch } 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;
|
|
let pageError = null;
|
|
window.addEventListener('error', (e) => { pageError = String(e.message); });
|
|
// сохранение падает, а диалог закрыт до ответа — карточка не должна умереть
|
|
c.hass = { ...c.hass, callWS: async (msg) => {
|
|
if (String(msg.type).endsWith('/set')) { await new Promise((r) => setTimeout(r, 60)); throw new Error('boom'); }
|
|
return { config: c._serverCfg, rev: c._cfgRev };
|
|
} };
|
|
await c.updateComplete;
|
|
// 1) диалог общих настроек
|
|
c._openSettingsDialog(); await c.updateComplete;
|
|
const p = c._saveSettingsDialog();
|
|
c._settingsDialog = null; // Esc во время сохранения
|
|
await c.updateComplete;
|
|
await p.catch(() => {});
|
|
await new Promise((r) => setTimeout(r, 120));
|
|
await c.updateComplete;
|
|
out.settingsStaysClosed = c._settingsDialog === null;
|
|
out.cardAliveAfterSettings = !!sr().querySelector('.stage');
|
|
// 2) диалог правил
|
|
c._openRulesDialog(); await c.updateComplete;
|
|
const p2 = c._saveRules();
|
|
c._rulesDialog = null; await c.updateComplete;
|
|
await p2.catch(() => {});
|
|
await new Promise((r) => setTimeout(r, 120));
|
|
await c.updateComplete;
|
|
out.rulesStaysClosed = c._rulesDialog === null;
|
|
out.cardAliveAfterRules = !!sr().querySelector('.stage');
|
|
// 3) диалог устройства
|
|
c._setMode('devices'); await c.updateComplete;
|
|
c._openMarkerDialog(c._devices[0]); await c.updateComplete;
|
|
const p3 = c._saveMarker();
|
|
c._markerDialog = null; await c.updateComplete;
|
|
await p3.catch(() => {});
|
|
await new Promise((r) => setTimeout(r, 120));
|
|
await c.updateComplete;
|
|
out.markerStaysClosed = c._markerDialog === null;
|
|
out.cardAliveAfterMarker = !!sr().querySelector('.stage');
|
|
out.noPageError = pageError === null;
|
|
// 4) при ОТКРЫТОМ диалоге ошибка снимает busy (поведение сохранено)
|
|
c._openSettingsDialog(); await c.updateComplete;
|
|
const p4 = c._saveSettingsDialog();
|
|
await p4.catch(() => {});
|
|
await new Promise((r) => setTimeout(r, 120));
|
|
out.busyClearedWhenOpen = c._settingsDialog !== null && c._settingsDialog.busy === false;
|
|
c._settingsDialog = null;
|
|
return out;
|
|
});
|
|
console.log(JSON.stringify(res, null, 1));
|
|
await browser.close();
|