144 lines
6.0 KiB
JavaScript
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); });
|