mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
175 lines
7.3 KiB
JavaScript
175 lines
7.3 KiB
JavaScript
// #607: explicit authentic Home Assistant dialog diagnostic. This is not a
|
|
// regular smoke: it loads the pinned official HA frontend fixture (~124 MB),
|
|
// then exercises all four settings-dialog owners through HA's real close
|
|
// button and WebAwesome modal surface.
|
|
import assert from 'node:assert/strict';
|
|
import { assertFreshDemoBundle } from './bundle-freshness.mjs';
|
|
import { launchHaDialogFixture } from './helpers/ha-dialog-fixture.mjs';
|
|
|
|
const cases = [
|
|
{ kind: 'marker', input: '#marker-name', state: '_markerDialog' },
|
|
{ kind: 'room', input: '#room-name', state: '_roomDialog' },
|
|
{ kind: 'space', input: '#space-title', state: '_spaceDialog' },
|
|
{ kind: 'settings', input: '#gs-north', state: '_settingsDialog' },
|
|
];
|
|
|
|
const fixture = await launchHaDialogFixture({
|
|
authentic: true,
|
|
viewport: { width: 1280, height: 900 },
|
|
});
|
|
|
|
const { page } = fixture;
|
|
const results = {};
|
|
|
|
const waitOwnerOpen = async (testCase) => page.waitForFunction(({ kind, state }) => {
|
|
const card = window.__card;
|
|
const dialog = card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"]`);
|
|
const ha = dialog?.shadowRoot.querySelector('ha-dialog');
|
|
const surface = ha?.shadowRoot.querySelector('wa-dialog')?.shadowRoot.querySelector('dialog');
|
|
return Boolean(card[state]) && ha?.open === true && surface?.matches(':modal') === true;
|
|
}, testCase);
|
|
|
|
const waitOwnerClosed = async ({ kind, state }) => page.waitForFunction(({ kind, state }) => {
|
|
const card = window.__card;
|
|
return !card[state] && !card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"]`)
|
|
&& !card.shadowRoot.querySelector('hp-confirm');
|
|
}, { kind, state });
|
|
|
|
const open = async (testCase) => {
|
|
await page.evaluate(async ({ kind }) => {
|
|
const card = window.__card;
|
|
if (card._ensureEditorRuntime && !await card._ensureEditorRuntime()) {
|
|
throw new Error('Editor runtime failed to load');
|
|
}
|
|
if (kind === 'marker') {
|
|
const device = card._devices.find((item) => item.id === 'd_light1') || card._devices[0];
|
|
card._openMarkerDialog(device);
|
|
} else if (kind === 'room') {
|
|
card._openRoomEdit(card._curSpaceCfg.rooms[0]);
|
|
} else if (kind === 'space') {
|
|
card._openSpaceDialog('edit', card._space);
|
|
} else {
|
|
card._openSettingsDialog();
|
|
}
|
|
card.requestUpdate();
|
|
await card.updateComplete;
|
|
}, testCase);
|
|
await waitOwnerOpen(testCase);
|
|
};
|
|
|
|
const edit = async (testCase, suffix) => page.evaluate(async ({ kind, input, suffix }) => {
|
|
const card = window.__card;
|
|
const field = card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"] ${input}`);
|
|
if (!field) throw new Error(`${kind}: missing field ${input}`);
|
|
if (kind === 'settings') field.value = suffix === ' first' ? '47' : '48';
|
|
else field.value = `${field.value}${suffix}`;
|
|
field.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
await card.updateComplete;
|
|
return field.value;
|
|
}, { ...testCase, suffix });
|
|
|
|
const clickAuthenticClose = async (testCase) => page.evaluate(({ kind }) => {
|
|
const card = window.__card;
|
|
const dialog = card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"]`);
|
|
const ha = dialog?.shadowRoot.querySelector('ha-dialog');
|
|
const button = ha?.shadowRoot.querySelector('ha-icon-button[data-dialog="close"]');
|
|
if (!button) throw new Error(`${kind}: authentic HA close button not found`);
|
|
dialog.__hp607CloseCount = dialog.__hp607CloseCount || 0;
|
|
if (!dialog.__hp607Counting) {
|
|
dialog.__hp607Counting = true;
|
|
dialog.addEventListener('hp-close', () => { dialog.__hp607CloseCount += 1; });
|
|
}
|
|
button.click();
|
|
}, testCase);
|
|
|
|
const waitConfirm = async (testCase) => {
|
|
try {
|
|
await page.waitForFunction(({ kind }) => {
|
|
const card = window.__card;
|
|
const dialog = card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"]`);
|
|
const ha = dialog?.shadowRoot.querySelector('ha-dialog');
|
|
return ha?.open === false && Boolean(card.shadowRoot.querySelector('hp-confirm'));
|
|
}, testCase);
|
|
} catch (error) {
|
|
const state = await page.evaluate(({ kind, state }) => {
|
|
const card = window.__card;
|
|
const dialog = card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"]`);
|
|
const ha = dialog?.shadowRoot.querySelector('ha-dialog');
|
|
return {
|
|
owner: Boolean(card[state]), dialog: Boolean(dialog), haOpen: ha?.open,
|
|
closeCount: dialog?.__hp607CloseCount,
|
|
confirm: Boolean(card.shadowRoot.querySelector('hp-confirm')),
|
|
buttons: [...(ha?.shadowRoot.querySelectorAll('ha-icon-button') || [])]
|
|
.map((button) => ({ dataDialog: button.getAttribute('data-dialog'), label: button.getAttribute('aria-label') })),
|
|
};
|
|
}, testCase);
|
|
throw new Error(`${testCase.kind}: confirmation did not open: ${JSON.stringify(state)}`, { cause: error });
|
|
}
|
|
};
|
|
|
|
const decide = async (accept) => page.evaluate((accept) => {
|
|
const card = window.__card;
|
|
const selector = accept ? '[data-hp="dialog-confirm"]' : '[data-hp="dialog-cancel"]';
|
|
const button = card.shadowRoot.querySelector(`hp-confirm ${selector}`);
|
|
if (!button) throw new Error(`Confirmation button not found: ${selector}`);
|
|
button.click();
|
|
}, accept);
|
|
|
|
try {
|
|
await page.goto(`${fixture.url}/product.html`);
|
|
await page.waitForFunction(() => window.__card?._model?.length > 0
|
|
&& window.__card?._devices?.length > 0 && window.__card?._booting === false);
|
|
await assertFreshDemoBundle(page);
|
|
|
|
for (const testCase of cases) {
|
|
console.log(`Checking ${testCase.kind}`);
|
|
await open(testCase);
|
|
const firstDraft = await edit(testCase, ' first');
|
|
await clickAuthenticClose(testCase);
|
|
await waitConfirm(testCase);
|
|
await decide(false);
|
|
await waitOwnerOpen(testCase);
|
|
const secondDraft = await edit(testCase, ' second');
|
|
|
|
const continued = await page.evaluate(({ kind, state, input, firstDraft, secondDraft }) => {
|
|
const card = window.__card;
|
|
const dialog = card.shadowRoot.querySelector(`hp-dialog[data-kind="${kind}"]`);
|
|
const field = dialog?.querySelector(input);
|
|
return {
|
|
sameDraft: Boolean(card[state]) && firstDraft !== secondDraft && field?.value === secondDraft,
|
|
oneClose: dialog?.__hp607CloseCount === 1,
|
|
};
|
|
}, { ...testCase, firstDraft, secondDraft });
|
|
|
|
await clickAuthenticClose(testCase);
|
|
await waitConfirm(testCase);
|
|
const twoCloses = await page.evaluate(({ kind }) => window.__card.shadowRoot
|
|
.querySelector(`hp-dialog[data-kind="${kind}"]`)?.__hp607CloseCount === 2, testCase);
|
|
await decide(true);
|
|
await waitOwnerClosed(testCase);
|
|
|
|
await open(testCase);
|
|
await clickAuthenticClose(testCase);
|
|
await waitOwnerClosed(testCase);
|
|
|
|
results[`${testCase.kind}: continue restores modal`] = continued.sameDraft;
|
|
results[`${testCase.kind}: one close before retry`] = continued.oneClose;
|
|
results[`${testCase.kind}: retry emits exactly one more close`] = twoCloses;
|
|
results[`${testCase.kind}: discard and clean close finish`] = true;
|
|
}
|
|
|
|
await fixture.assertClean();
|
|
for (const [name, value] of Object.entries(results)) assert.equal(value, true, name);
|
|
console.log(JSON.stringify({
|
|
issue: 607,
|
|
authenticHaFrontend: fixture.provenance,
|
|
results,
|
|
pageErrors: fixture.errors,
|
|
externalRequests: fixture.externalRequests,
|
|
websocketAttempts: fixture.websocketAttempts,
|
|
}, null, 2));
|
|
console.log('OK');
|
|
} finally {
|
|
await fixture.close();
|
|
}
|