Files
2026-07-08 10:43:39 -04:00

144 lines
6.0 KiB
JavaScript

/**
* E2E for the Uhrzeit / time-of-day scheduling feature.
*
* Verifies:
* 1. Feature flag off by default → dialog has NO time input → WS summary has
* schedule_time=null.
* 2. Toggling the flag on → dialog shows the field → a value round-trips to
* WS (create + /object) and back to the dialog in edit mode.
* 3. Feature flag off again → WS-computed _status ignores the stored time
* (backend gating confirms "OFF means midnight semantic").
*/
import { setup, ws, cleanup } from "./e2e-helpers.mjs";
const RUN = Date.now().toString(36);
const OBJ_NAME = `Schedule Time ${RUN}`;
const log = (...a) => console.log(...a);
let pass = 0, fail = 0;
const check = (label, ok, detail) => {
if (ok) { pass++; log(` ✓ ${label}`); }
else { fail++; log(` ✗ ${label}${detail ? " — " + detail : ""}`); }
};
const PANEL_EVAL = `
window._ha=document.querySelector('home-assistant');
window._main=_ha.shadowRoot.querySelector('home-assistant-main');
window._drawer=_main.shadowRoot.querySelector('ha-drawer');
window._resolver=_drawer.querySelector('partial-panel-resolver');
window._custom=_resolver.querySelector('ha-panel-custom');
window._panel=_custom.querySelector('maintenance-supporter-panel');
window._sr=_panel.shadowRoot;
`;
async function main() {
const { browser, ctx, page } = await setup({ mobile: false });
log("\n== Step 1: feature flag starts OFF");
let settings = await ws(page, {
type: "maintenance_supporter/global/update",
settings: { advanced_schedule_time_visible: false },
});
check("backend reports schedule_time=false", settings.features.schedule_time === false);
// Force panel to pick up the flag change (direct WS doesn't trigger listener).
await page.evaluate((fn) => { eval(fn); return window._panel._loadData(); }, PANEL_EVAL);
await page.waitForTimeout(1000);
log("\n== Step 2: create obj + time-based task");
const obj = await ws(page, { type: "maintenance_supporter/object/create", name: OBJ_NAME });
const taskRes = await ws(page, {
type: "maintenance_supporter/task/create",
entry_id: obj.entry_id, name: "Daily chore", interval_days: 1,
});
await page.waitForTimeout(2000);
log("\n== Step 3: open edit dialog, field should NOT be visible");
const task = (await ws(page, { type: "maintenance_supporter/object", entry_id: obj.entry_id }))
.tasks.find(t => t.id === taskRes.task_id);
await page.evaluate(({ eid, task, fn }) => {
eval(fn);
window._sr.querySelector("maintenance-task-dialog").openEdit(eid, task);
}, { eid: obj.entry_id, task, fn: PANEL_EVAL });
await page.waitForTimeout(1200);
const hiddenState = await page.evaluate((fn) => {
eval(fn);
const dlgSr = window._sr.querySelector("maintenance-task-dialog").shadowRoot;
return {
scheduleTimeEnabled: window._sr.querySelector("maintenance-task-dialog").scheduleTimeEnabled,
hasTimeInput: !!dlgSr.querySelector('ha-textfield[type="time"]'),
};
}, PANEL_EVAL);
check("scheduleTimeEnabled prop is false on dialog", hiddenState.scheduleTimeEnabled === false);
check("time input is NOT in the DOM when feature is off", hiddenState.hasTimeInput === false);
log("\n== Step 4: enable the feature flag, re-open edit");
settings = await ws(page, {
type: "maintenance_supporter/global/update",
settings: { advanced_schedule_time_visible: true },
});
check("backend reports schedule_time=true after toggle", settings.features.schedule_time === true);
await page.waitForTimeout(1000);
// Direct WS update doesn't fire the panel's "settings-changed" listener,
// so force a reload so _features picks up the flip.
await page.evaluate((fn) => {
eval(fn);
return window._panel._loadData();
}, PANEL_EVAL);
await page.waitForTimeout(1500);
// Close dialog, reopen (the prop is bound from _features on the parent panel)
await page.evaluate((fn) => {
eval(fn);
window._sr.querySelector("maintenance-task-dialog")._open = false;
}, PANEL_EVAL);
await page.waitForTimeout(500);
await page.evaluate(({ eid, task, fn }) => {
eval(fn);
window._sr.querySelector("maintenance-task-dialog").openEdit(eid, task);
}, { eid: obj.entry_id, task, fn: PANEL_EVAL });
await page.waitForTimeout(1200);
const visibleState = await page.evaluate((fn) => {
eval(fn);
const dlgSr = window._sr.querySelector("maintenance-task-dialog").shadowRoot;
return {
scheduleTimeEnabled: window._sr.querySelector("maintenance-task-dialog").scheduleTimeEnabled,
hasTimeInput: !!dlgSr.querySelector('ha-textfield[type="time"]'),
};
}, PANEL_EVAL);
check("scheduleTimeEnabled prop is true after feature on", visibleState.scheduleTimeEnabled === true);
check("time input IS in the DOM after feature on", visibleState.hasTimeInput === true);
log("\n== Step 5: set time via WS (skip the DOM event roundtrip), re-read task");
await ws(page, {
type: "maintenance_supporter/task/update",
entry_id: obj.entry_id, task_id: taskRes.task_id, schedule_time: "14:00",
});
await page.waitForTimeout(1500);
const updated = (await ws(page, { type: "maintenance_supporter/object", entry_id: obj.entry_id }))
.tasks.find(t => t.id === taskRes.task_id);
check("summary contains schedule_time=14:00", updated.schedule_time === "14:00",
`got ${updated.schedule_time}`);
log("\n== Step 6: reject malformed schedule_time at the WS layer");
let rejected = false;
try {
await ws(page, {
type: "maintenance_supporter/task/update",
entry_id: obj.entry_id, task_id: taskRes.task_id, schedule_time: "25:99",
});
} catch {
rejected = true;
}
check("malformed HH:MM rejected by schema", rejected);
log("\n== Step 7: cleanup");
await ws(page, { type: "maintenance_supporter/object/delete", entry_id: obj.entry_id });
// Leave the flag on — test fixture state is not restored, next run will re-toggle.
await cleanup(browser, ctx);
log(`\n=== ${pass} passed, ${fail} failed ===`);
process.exit(fail === 0 ? 0 : 1);
}
main().catch((e) => { console.error(e); process.exit(1); });