393 files

This commit is contained in:
Home Assistant Version Control
2026-09-27 17:16:54 +00:00
parent b05b7a897e
commit 39d97ac2db
404 changed files with 54006 additions and 11034 deletions
@@ -234,7 +234,7 @@ describe("complete-dialog", () => {
expect(msg.photo_doc_ids).to.deep.equal(["doc-photo-1", "doc-photo-2", "doc-photo-3"]);
expect("photo_doc_id" in msg).to.be.false;
// Completing keeps the uploads — no delete goes out.
expect(sent.some((m) => m.type === "maintenance_supporter/documents/delete")).to.be.false;
expect(sent.some((m) => ["maintenance_supporter/documents/delete", "maintenance_supporter/documents/discard_upload"].includes(m.type))).to.be.false;
} finally {
restore();
}
@@ -271,14 +271,14 @@ describe("complete-dialog", () => {
removeButtons[0].click();
await el.updateComplete;
expect(el.shadowRoot!.querySelectorAll(".photo-preview img").length).to.equal(1);
let deletes = sent.filter((m) => m.type === "maintenance_supporter/documents/delete");
let deletes = sent.filter((m) => m.type === "maintenance_supporter/documents/discard_upload");
expect(deletes.map((m) => m.doc_id)).to.deep.equal(["doc-photo-1"]);
// Cancel: the remaining upload is an orphan nobody references.
const cancel = el.shadowRoot!.querySelector<HTMLElement>(".dialog-actions ha-button")!;
cancel.click();
await el.updateComplete;
deletes = sent.filter((m) => m.type === "maintenance_supporter/documents/delete");
deletes = sent.filter((m) => m.type === "maintenance_supporter/documents/discard_upload");
expect(deletes.map((m) => m.doc_id)).to.deep.equal(["doc-photo-1", "doc-photo-2"]);
expect(sent.some((m) => m.type === "maintenance_supporter/task/complete")).to.be.false;
} finally {
@@ -144,14 +144,19 @@ describe("panel deep links (QR scan routing)", () => {
expect(complete!.via_tag_scan).to.equal(true);
});
it("a plain complete (no scan) opens the dialog WITHOUT the scan flag", async () => {
it("the printed Complete QR (action=complete) counts as the scan", async () => {
// action=complete URLs come ONLY from the QR generator (qr dialog, work
// sheet, batch print) — the docs promise that scanning the printed QR
// satisfies "Require tag scan". The 2026-08-29 version of this test
// pinned the opposite, so a gated task refused its own sticker (bug
// audit 2026-09-26, FE-5).
setDeepLink("entry_id=e1&task_id=t1&action=complete");
const { el } = await mountPanel([obj("e1", [task({ name: "Gated", require_tag_scan: true })])]);
await settleRaf(el);
await waitForOpenCompleteDialog(el);
const dlg = completeDialog(el)!;
expect(dlg.viaTagScan).to.equal(false);
expect(dlg.shadowRoot!.querySelector(".scan-required-note"), "note shown").to.exist;
expect(dlg.viaTagScan).to.equal(true);
expect(dlg.shadowRoot!.querySelector(".scan-required-note"), "no 'scan required' note — this was the scan").to.equal(null);
});
it("any other quick_complete refusal relays the server's message", async () => {
@@ -17,8 +17,10 @@ import { sharedStyles } from "../styles";
import { panelStyles } from "../panel-styles";
const STATUSES = ["ok", "due_soon", "overdue", "triggered", "done", "archived", "paused"];
// The task-detail view uses a SEPARATE `.status-chip` set in panel-styles.ts.
const CHIP_STATUSES = ["ok", "warning", "overdue", "done"];
// The task-detail view uses a SEPARATE `.status-chip` set in panel-styles.ts
// — keyed like the pills since the audit 2026-09-26 (due_soon, not the old
// "warning"; triggered / paused / archived had no rule at all).
const CHIP_STATUSES = ["ok", "due_soon", "overdue", "triggered", "paused", "archived", "done"];
@customElement("badge-contrast-probe")
class BadgeContrastProbe extends LitElement {
@@ -139,7 +139,8 @@ describe("task-detail renderer", () => {
expect(host.querySelector(".task-name-breadcrumb")!.textContent).to.include("Filter Wechsel");
expect(host.querySelector(".object-name-breadcrumb")!.textContent).to.include("Pool Pump");
const chip = host.querySelector(".status-chip")!;
expect(chip.classList.contains("warning")).to.be.true;
// The status key itself (renderers/status.ts), no longer the "warning" alias.
expect(chip.classList.contains("due_soon")).to.be.true;
});
it("Complete routes to openComplete with the task; Skip to promptSkip", () => {
@@ -163,15 +164,18 @@ describe("task-detail renderer", () => {
expect(buttons[1].getAttribute("variant")).to.equal("warning");
});
it("operator mode: only Complete/Skip buttons; menu carries just QR + worksheet", () => {
it("operator mode: only Complete/Skip buttons; menu carries QR, worksheet and the household actions", () => {
const host = mount(task(), ctx({ isOperator: true, moreMenuOpen: true }));
// The ⋮ stays (QR + worksheet moved into it), but write actions don't.
// Reset / Postpone / Snooze are READ tier on the server (any household
// member) — hiding them was the audit 2026-09-26 finding FE-A3.
expect(host.querySelector(".more-menu-wrapper"), "menu present").to.exist;
const buttons = [...host.querySelectorAll(".task-header-actions ha-button")];
expect(buttons.length).to.equal(2); // Complete + Skip only
const items = [...host.querySelectorAll(".popup-menu-item")].map((i) => i.textContent?.trim() || "");
expect(items.length).to.equal(2);
expect(items.join(" ")).to.not.match(/edit|archive|delete|duplicate|reset/i);
expect(items.length).to.equal(5);
expect(items.join(" ")).to.match(/reset/i);
expect(items.join(" ")).to.not.match(/edit|archive|delete|duplicate|move/i);
});
it("open more-menu lists edit/qr/worksheet/duplicate/reset/postpone/snooze/archive/delete and fires callbacks", () => {
@@ -180,12 +180,13 @@ describe("maintenance-task-detail-view", () => {
expect(el.querySelector(".detail-section")).to.equal(null);
});
it("operators get the more-menu with only the read-safe items (QR + worksheet)", async () => {
it("operators get the more-menu with only the read-tier items (QR, worksheet, reset, postpone, snooze)", async () => {
const el = await mount(task(), ctx({ isOperator: true, moreMenuOpen: true }));
expect(el.querySelector(".more-menu-wrapper"), "menu present for operators").to.exist;
const labels = [...el.querySelectorAll(".popup-menu-item")].map((i) => i.textContent?.trim());
expect(labels.length).to.equal(2);
// No write actions leak into the operator menu.
expect(labels.join(" ")).to.not.match(/edit|archive|delete|duplicate|reset/i);
expect(labels.length).to.equal(5);
// No write actions leak into the operator menu (audit 2026-09-26 FE-A3:
// the household actions are read tier and belong in it).
expect(labels.join(" ")).to.not.match(/edit|archive|delete|duplicate|move/i);
});
});