This commit is contained in:
Home Assistant Version Control
2026-08-06 15:24:19 +00:00
parent 6aaf37b9bc
commit d5da256341
93 changed files with 5199 additions and 8790 deletions
@@ -167,3 +167,74 @@ describe("complete-dialog", () => {
expect(completedEvent).to.be.false;
});
});
describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
async function mountWithParts(over: Partial<MaintenanceCompleteDialog> = {}) {
const { hass } = createMockHass({});
const el = await fixture<MaintenanceCompleteDialog>(html`
<maintenance-complete-dialog
.hass=${hass}
.entryId=${"entry1"}
.taskId=${"task1"}
.taskName=${"Uses Parts"}
.lang=${"en"}
></maintenance-complete-dialog>
`);
Object.assign(el, over);
el.open();
await el.updateComplete;
return el;
}
const chip = (el: MaintenanceCompleteDialog) =>
el.shadowRoot!.querySelector<HTMLButtonElement>(".cost-suggestion");
it("sums selected consumed parts (qty x unit cost) and fills on click", async () => {
const el = await mountWithParts({
parts: [
{ id: "p1", name: "Filter", cost: 12.5 },
{ id: "p2", name: "O-Ring", cost: 2.25 },
{ id: "p3", name: "Unpriced", cost: null },
] as never,
consumesParts: [
{ part_id: "p1", quantity: 1 },
{ part_id: "p2", quantity: 2 },
{ part_id: "p3", quantity: 1 },
] as never,
currencySymbol: "€",
});
const c = chip(el)!;
expect(c, "suggestion chip rendered").to.exist;
expect(c.textContent).to.include("17.00");
expect(c.textContent).to.include("€");
c.click();
await el.updateComplete;
const cost = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>(".field-input")][1];
expect(cost.value).to.equal("17.00");
expect(chip(el), "chip hides once cost is set").to.equal(null);
});
it("buy task: restock qty x unit cost, follows the qty field", async () => {
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
expect(chip(el)!.textContent).to.include("9.00");
});
it("no chip when no involved part carries a price", async () => {
const el = await mountWithParts({
parts: [{ id: "p1", name: "Filter", cost: null }] as never,
consumesParts: [{ part_id: "p1", quantity: 1 }] as never,
});
expect(chip(el)).to.equal(null);
});
it("no chip once the user typed a cost themselves", async () => {
const el = await mountWithParts({
parts: [{ id: "p1", name: "Filter", cost: 5 }] as never,
consumesParts: [{ part_id: "p1", quantity: 1 }] as never,
});
expect(chip(el)).to.exist;
setInput(el, 1, "3.10");
await el.updateComplete;
expect(chip(el)).to.equal(null);
});
});
@@ -20,7 +20,9 @@ function setDeepLink(query: string) {
}
async function settleRaf(el: { updateComplete: Promise<unknown> }) {
// Deep-link dialog opens behind a requestAnimationFrame.
// Deep-link dialog opens behind a requestAnimationFrame — and since the
// dialogs became lazy code-split chunks, behind their dynamic import too.
await customElements.whenDefined("maintenance-complete-dialog");
await new Promise((r) => requestAnimationFrame(() => r(null)));
await new Promise((r) => setTimeout(r, 20));
await (el as HTMLElement & { updateComplete: Promise<unknown> }).updateComplete;
@@ -30,6 +32,15 @@ function completeDialog(el: HTMLElement): MaintenanceCompleteDialog | null {
return sr(el).querySelector<MaintenanceCompleteDialog>("maintenance-complete-dialog");
}
/** The dialogs are lazy code-split chunks — the open lands whenever the
* whole lazy-UI group has loaded, so poll instead of guessing a delay. */
async function waitForOpenCompleteDialog(el: HTMLElement): Promise<void> {
for (let i = 0; i < 100; i++) {
if (completeDialog(el)?.shadowRoot?.querySelector("ha-dialog")) return;
await new Promise((r) => setTimeout(r, 20));
}
}
describe("panel deep links (QR scan routing)", () => {
beforeEach(() => {
resetTaskSeq();
@@ -52,6 +63,7 @@ describe("panel deep links (QR scan routing)", () => {
expect(sr(el).querySelector(".task-header"), "task detail rendered").to.exist;
expect(sr(el).querySelector(".task-name-breadcrumb")!.textContent).to.include("Scan Me");
// …with the complete dialog open and targeted at the scanned task.
await waitForOpenCompleteDialog(el);
const dlg = completeDialog(el)!;
expect(dlg.shadowRoot!.querySelector("ha-dialog"), "complete dialog open").to.exist;
expect(dlg.entryId).to.equal("e1");
@@ -94,6 +106,7 @@ describe("panel deep links (QR scan routing)", () => {
expect(
sent.filter((m) => m.type === "maintenance_supporter/task/quick_complete").length,
).to.equal(1);
await waitForOpenCompleteDialog(el);
const dlg = completeDialog(el)!;
expect(dlg.shadowRoot!.querySelector("ha-dialog"), "fallback dialog open").to.exist;
expect(dlg.taskName).to.equal("No Defaults");