mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
test: cover the space copy workflow
Issue: #456 User-Visible: no Release: v1.72.0-beta.4 Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33948778925
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"matrixVersion": 54,
|
||||
"acceptedAt": "2026-09-04T13:10:56.492Z",
|
||||
"sourceFingerprint": "7acb1b9d3bb384bc1a0c70fb01347386c157ae88b12b00ccd876b7658fc84a1c",
|
||||
"acceptedAt": "2026-09-05T06:21:56.683Z",
|
||||
"sourceFingerprint": "814851e1e157fd126309de061c720fb1203ddd6d63824cd37289542baefca406",
|
||||
"chromium": "151.0.7922.34",
|
||||
"platform": "linux",
|
||||
"witnesses": {
|
||||
"count": 127,
|
||||
"count": 134,
|
||||
"floor": 10
|
||||
},
|
||||
"scenarios": {
|
||||
@@ -152,7 +153,7 @@
|
||||
"support-relay-error-light-en": "e9a9ad906e106eedfa6e84c592325f1604eb64c89c51135c076ac87a6a0766b2",
|
||||
"support-tablet-preview-dark-ru": "1264e005cd764408edb9b285a5dfbe92ceb90cb925f624b8f65cd47edb96c0c9",
|
||||
"device-ripple-color-popover-mobile-ru": "d9ebf83ad0d2995b46f6174daf6e21ca5404952afa308ddd3a66ecefcee565d0",
|
||||
"space-room-color-popover-desktop-ru": "9bbbcf9c5ea6ff5490b500be8cb3086af9d368d14ca50c304f42a697e296ece1",
|
||||
"space-room-color-popover-desktop-ru": "04bf325ddb449001f6c0b459761dcb84eda6432dca2fbd10ce5a129484d0756c",
|
||||
"backup-full-preview-desktop-en": "0fbd23a92121668001faf5c9ed555a31107704f3074d1791709c8436517d3d1f",
|
||||
"backup-plan-only-export-desktop-en": "ce66fb57edbc87d56355b0d73a2d4203a67c275d5927d400ef1758ac0b607b34",
|
||||
"backup-space-preview-mobile-ru": "82cd93cb495ad77b92d635b4a3715601b1ae0e5e2974edcbbd461fe872c75bad",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,141 @@
|
||||
// #456: Copy is a real two-write workflow when the plan needs Optimize and a
|
||||
// one-write workflow when it does not. Exercise the production bundle through
|
||||
// the visible dialogs while delegating every request to the demo backend.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
|
||||
const { page, browser } = await launch({ width: 820, height: 780 });
|
||||
const out = await page.evaluate(async () => {
|
||||
const result = {};
|
||||
const card = window.__card;
|
||||
const root = () => card.shadowRoot || card.renderRoot;
|
||||
const clone = (value) => structuredClone(value);
|
||||
const settle = async () => {
|
||||
await card.updateComplete;
|
||||
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||
};
|
||||
const waitFor = async (predicate, label) => {
|
||||
for (let attempt = 0; attempt < 120; attempt++) {
|
||||
await settle();
|
||||
if (predicate()) return;
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
throw new Error(`space-copy smoke timed out: ${label}`);
|
||||
};
|
||||
const currentDialog = () => root().querySelector('hp-dialog');
|
||||
const buttonWithIcon = (dialog, icon) => [...(dialog?.querySelectorAll('button') || [])]
|
||||
.find((button) => button.querySelector(`ha-icon[icon="${icon}"]`));
|
||||
const openCopy = async () => {
|
||||
card._openSpaceDialog('edit', 'f1');
|
||||
await settle();
|
||||
const settingsDialog = currentDialog();
|
||||
const button = buttonWithIcon(settingsDialog, 'mdi:content-copy');
|
||||
const outsideDanger = !!button && !button.closest('.dialog-action-danger');
|
||||
button?.click();
|
||||
await waitFor(() => !!card._spaceDialog?.copy, 'copy name dialog');
|
||||
const copyDialog = currentDialog();
|
||||
return { outsideDanger, copyDialog };
|
||||
};
|
||||
const nameCopy = async (dialog, title) => {
|
||||
const input = dialog?.querySelector('#space-copy-name');
|
||||
if (input) {
|
||||
input.value = title;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||
}
|
||||
await settle();
|
||||
return input;
|
||||
};
|
||||
const submitCopy = (dialog) => buttonWithIcon(dialog, 'mdi:content-copy')?.click();
|
||||
const confirmUi = () => {
|
||||
const component = root().querySelector('hp-confirm');
|
||||
const dialog = component?.querySelector('hp-dialog');
|
||||
return {
|
||||
component,
|
||||
dialog,
|
||||
buttons: [...(dialog?.querySelectorAll('.danger-confirm-footer button') || [])],
|
||||
};
|
||||
};
|
||||
|
||||
const baseCall = card.hass.callWS.bind(card.hass);
|
||||
const calls = [];
|
||||
card.hass = {
|
||||
...card.hass,
|
||||
callWS: async (message) => {
|
||||
calls.push(clone(message));
|
||||
return baseCall(message);
|
||||
},
|
||||
};
|
||||
await settle();
|
||||
|
||||
// The demo's original f1 needs Optimize. Cancel must leave the name dialog
|
||||
// intact and must not make either durable write.
|
||||
const cancelStart = calls.length;
|
||||
const cancel = await openCopy();
|
||||
const proposedName = cancel.copyDialog?.querySelector('#space-copy-name')?.value || '';
|
||||
await nameCopy(cancel.copyDialog, 'Cancelled copy');
|
||||
submitCopy(cancel.copyDialog);
|
||||
await waitFor(() => confirmUi().buttons.length === 2, 'Optimize warning for Cancel');
|
||||
const cancelWarning = confirmUi();
|
||||
const warningText = cancelWarning.dialog?.textContent || '';
|
||||
cancelWarning.buttons[0].click();
|
||||
await waitFor(() => !confirmUi().component && card._spaceDialog?.copy?.busy === false,
|
||||
'Cancel returns to name');
|
||||
const cancelWrites = calls.slice(cancelStart)
|
||||
.filter((call) => call.type === 'houseplan/plan/optimize'
|
||||
|| call.type === 'houseplan/config/set');
|
||||
result.copyButtonIsNeutralAndDefaultNameIsNumbered = cancel.outsideDanger
|
||||
&& proposedName.endsWith(' (2)');
|
||||
result.optimizeWarningNamesTheWholePlan = warningText.includes('entire plan')
|
||||
&& warningText.includes('including other spaces');
|
||||
result.optimizeCancelMakesZeroWritesAndKeepsName = cancelWrites.length === 0
|
||||
&& card._spaceDialog?.copy?.title === 'Cancelled copy';
|
||||
card._spaceDialog = null;
|
||||
await settle();
|
||||
|
||||
// Accept uses the real demo backend in the promised order and enters the
|
||||
// roomless copy after both writes succeed.
|
||||
const acceptStart = calls.length;
|
||||
const accepted = await openCopy();
|
||||
await nameCopy(accepted.copyDialog, 'Optimized copy');
|
||||
submitCopy(accepted.copyDialog);
|
||||
await waitFor(() => confirmUi().buttons.length === 2, 'Optimize warning for Accept');
|
||||
confirmUi().buttons[1].click();
|
||||
await waitFor(() => card._spaceDialog === null && card._space !== 'f1',
|
||||
'accepted copy transition');
|
||||
const acceptedCalls = calls.slice(acceptStart)
|
||||
.filter((call) => call.type === 'houseplan/plan/optimize'
|
||||
|| call.type === 'houseplan/config/set');
|
||||
const optimizedCopy = card._serverCfg.spaces.find((space) => space.title === 'Optimized copy');
|
||||
result.optimizeAcceptWritesInOrder = acceptedCalls.length === 2
|
||||
&& acceptedCalls[0].type === 'houseplan/plan/optimize'
|
||||
&& acceptedCalls[1].type === 'houseplan/config/set';
|
||||
result.acceptCreatesAndEntersRoomlessCopy = !!optimizedCopy
|
||||
&& optimizedCopy.id === card._space
|
||||
&& optimizedCopy.rooms?.length === 0
|
||||
&& optimizedCopy.wall_segments?.length === 0
|
||||
&& optimizedCopy.partitions?.length > 0
|
||||
&& card._mode === 'plan' && card._tool === 'draw';
|
||||
|
||||
// The accepted config is now optimized. Submitting a second name is itself
|
||||
// the only confirmation and produces exactly one config write.
|
||||
card._commitSpace('f1', true);
|
||||
await settle();
|
||||
const cleanStart = calls.length;
|
||||
const clean = await openCopy();
|
||||
await nameCopy(clean.copyDialog, 'Clean copy');
|
||||
submitCopy(clean.copyDialog);
|
||||
await waitFor(() => card._spaceDialog === null && card._space !== 'f1',
|
||||
'clean copy transition');
|
||||
const cleanCalls = calls.slice(cleanStart)
|
||||
.filter((call) => call.type === 'houseplan/plan/optimize'
|
||||
|| call.type === 'houseplan/config/set');
|
||||
const cleanCopy = card._serverCfg.spaces.find((space) => space.title === 'Clean copy');
|
||||
result.cleanCopyNeedsNoExtraConfirmation = !confirmUi().component
|
||||
&& cleanCalls.length === 1 && cleanCalls[0].type === 'houseplan/config/set';
|
||||
result.cleanCopyIsInsertedAfterSourceAndSelected = !!cleanCopy
|
||||
&& card._serverCfg.spaces[1]?.id === cleanCopy.id
|
||||
&& card._space === cleanCopy.id;
|
||||
return result;
|
||||
});
|
||||
|
||||
checkAll(out);
|
||||
await finish(browser, out);
|
||||
Reference in New Issue
Block a user