mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
91 lines
3.4 KiB
JavaScript
91 lines
3.4 KiB
JavaScript
// #113: deleting the final space and receiving an empty plan over WS must be
|
|
// a supported lifecycle state, not a synthetic SpaceModel or an exception.
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
|
|
const { page, browser } = await launch({ width: 820, height: 760 });
|
|
|
|
const result = await page.evaluate(async () => {
|
|
const out = {};
|
|
const card = window.__card;
|
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
const root = () => card.shadowRoot || card.renderRoot;
|
|
const settle = async () => {
|
|
await card.updateComplete;
|
|
await sleep(40);
|
|
await card.updateComplete;
|
|
};
|
|
const original = structuredClone(card._serverCfg);
|
|
const first = structuredClone(original.spaces[0]);
|
|
|
|
// Exercise the real delete command with exactly one remaining space.
|
|
card._serverCfg = { ...structuredClone(original), spaces: [first] };
|
|
card._space = first.id;
|
|
card._cfgEpoch++;
|
|
card.requestUpdate();
|
|
await settle();
|
|
card._openSpaceDialog('edit', first.id);
|
|
card._mode = 'plan';
|
|
card._path = [[100, 100], [200, 100]];
|
|
card._resumeDraftBySpace = { [first.id]: 'stale-draft' };
|
|
card._pointers.set(113, { x: 10, y: 10 });
|
|
card._drag = { id: 'stale-device', sx: 0, sy: 0, ox: 0, oy: 0, moved: true };
|
|
card._saveConfigDebounced();
|
|
const confirmBefore = window.confirm;
|
|
window.confirm = () => true;
|
|
try {
|
|
await card._deleteSpace();
|
|
} finally {
|
|
window.confirm = confirmBefore;
|
|
}
|
|
await settle();
|
|
|
|
out.deleteLastRendersEmpty = !!root().querySelector('.empty') && !root().querySelector('.stage');
|
|
out.deleteLastClearsSelection = card._space === '' && card._spaceModel() === undefined;
|
|
out.deleteLastAbortsEditorState = card._mode === 'view'
|
|
&& card._path.length === 0 && card._pointers.size === 0 && card._drag === null
|
|
&& Object.keys(card._resumeDraftBySpace).length === 0;
|
|
out.deleteLastClosesEditDialog = card._spaceDialog === null;
|
|
out.deleteLastCancelsPendingWrite = card._saveConfigDebounced.pending() === false;
|
|
|
|
// The empty state still owns global recovery flows.
|
|
const add = root().querySelector('.empty button.btn.on');
|
|
add?.click();
|
|
await settle();
|
|
out.createFlowSurvivesEmpty = card._spaceDialog?.mode === 'create';
|
|
|
|
// Recreate a plan, then reproduce the same transition as a config-updated WS
|
|
// event. This also arms the once-per-empty cleanup for a second cycle.
|
|
card._spaceDialog = null;
|
|
card._serverCfg = structuredClone(original);
|
|
card._space = original.spaces[0].id;
|
|
card._cfgEpoch++;
|
|
card.requestUpdate();
|
|
await settle();
|
|
out.recreateRestoresPlan = !!root().querySelector('.stage') && card._spaceModel()?.id === card._space;
|
|
|
|
card._mode = 'devices';
|
|
card._pointers.set(114, { x: 20, y: 20 });
|
|
card._serverCfg = { ...structuredClone(original), spaces: [] };
|
|
card._cfgEpoch++;
|
|
card.requestUpdate();
|
|
await settle();
|
|
out.wsEmptyAbortsLiveGesture = card._mode === 'view'
|
|
&& card._pointers.size === 0 && card._spaceModel() === undefined;
|
|
|
|
// Empty plans must remain inert under unrelated HA/render ticks.
|
|
card._serverCanWrite = false;
|
|
card.hass = {
|
|
...card.hass,
|
|
themes: { ...(card.hass.themes || {}), darkMode: !card.hass.themes?.darkMode },
|
|
};
|
|
window.dispatchEvent(new Event('resize'));
|
|
await settle();
|
|
out.emptySurvivesThemeResizeReadonly = !!root().querySelector('.empty')
|
|
&& !root().querySelector('.stage') && card._mode === 'view';
|
|
|
|
return out;
|
|
});
|
|
|
|
checkAll(result);
|
|
await finish(browser, result);
|