257 files
This commit is contained in:
@@ -35,7 +35,6 @@ export interface ServiceCall {
|
||||
/** Same shape as `frontend-src/types.ts::AdvancedFeatures`. */
|
||||
export interface MockFeatures {
|
||||
adaptive: boolean;
|
||||
predictions: boolean;
|
||||
seasonal: boolean;
|
||||
environmental: boolean;
|
||||
budget: boolean;
|
||||
@@ -46,7 +45,7 @@ export interface MockFeatures {
|
||||
}
|
||||
|
||||
export const DEFAULT_FEATURES: MockFeatures = {
|
||||
adaptive: false, predictions: false, seasonal: false,
|
||||
adaptive: false, seasonal: false,
|
||||
environmental: false, budget: false, groups: false,
|
||||
checklists: false, schedule_time: false, completion_actions: false,
|
||||
};
|
||||
@@ -136,6 +135,8 @@ export interface CreateMockHassOptions {
|
||||
settingsResponse?: typeof DEFAULT_SETTINGS_RESPONSE;
|
||||
/** Per-WS-type handlers — return a value or Promise. Wins over built-in defaults. */
|
||||
handlers?: Record<string, WsHandler>;
|
||||
/** Answers every WS type no handler and no built-in default covers. */
|
||||
fallback?: WsHandler;
|
||||
/** Optional `hass.services` registry (for ha-service-picker / schema-driven forms). */
|
||||
services?: Record<string, Record<string, unknown>>;
|
||||
/** Optional `hass.states` (entity_id → state) — e.g. for notify-entity pickers. */
|
||||
@@ -169,6 +170,7 @@ export function createMockHass(opts: CreateMockHassOptions = {}): CreateMockHass
|
||||
if (msg.type === "maintenance_supporter/users/list") return { users: [] };
|
||||
if (msg.type === "maintenance_supporter/objects") return { objects: [] };
|
||||
if (msg.type === "maintenance_supporter/tags/list") return { tags: [] };
|
||||
if (opts.fallback) return await opts.fallback(msg);
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -54,7 +54,7 @@ describe("adopt-problem-sensors dialog: suggested part", () => {
|
||||
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click(); // Adopt selected
|
||||
await el.updateComplete;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as unknown as {
|
||||
selections: Array<{ entity_id: string; part_id?: string }>;
|
||||
};
|
||||
const byId = Object.fromEntries(adopt.selections.map((s) => [s.entity_id, s]));
|
||||
@@ -72,7 +72,7 @@ describe("adopt-problem-sensors dialog: flicker filter (#136)", () => {
|
||||
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click();
|
||||
await el.updateComplete;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as unknown as {
|
||||
selections: Array<{ for_minutes?: number }>;
|
||||
};
|
||||
expect(adopt.selections.every((s) => s.for_minutes === undefined)).to.equal(true);
|
||||
@@ -87,7 +87,7 @@ describe("adopt-problem-sensors dialog: flicker filter (#136)", () => {
|
||||
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click();
|
||||
await el.updateComplete;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as unknown as {
|
||||
selections: Array<{ for_minutes?: number }>;
|
||||
};
|
||||
expect(adopt.selections.length).to.be.greaterThan(0);
|
||||
|
||||
+62
-31
@@ -325,7 +325,7 @@ describe("complete-dialog", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
|
||||
describe("complete-dialog parts cost (#104)", () => {
|
||||
async function mountWithParts(over: Partial<MaintenanceCompleteDialog> = {}) {
|
||||
const { hass } = createMockHass({});
|
||||
const el = await fixture<MaintenanceCompleteDialog>(html`
|
||||
@@ -345,54 +345,85 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
|
||||
|
||||
const chip = (el: MaintenanceCompleteDialog) =>
|
||||
el.shadowRoot!.querySelector<HTMLButtonElement>(".cost-suggestion");
|
||||
const note = (el: MaintenanceCompleteDialog) =>
|
||||
el.shadowRoot!.querySelector<HTMLElement>(".cost-note");
|
||||
|
||||
it("sums selected consumed parts (qty x unit cost) and fills on click", async () => {
|
||||
const USING = {
|
||||
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: "€",
|
||||
};
|
||||
|
||||
it("parts used: their value is a line, never a suggestion into the cost field", async () => {
|
||||
// 2.53 offered the parts' value as a one-click cost. A household that
|
||||
// already booked the purchase counted the same filter twice.
|
||||
const el = await mountWithParts(USING);
|
||||
expect(chip(el), "no cost suggestion on a job that uses parts").to.equal(null);
|
||||
expect(note(el)!.textContent).to.include("17 €");
|
||||
expect(note(el)!.textContent).to.include("when they were bought");
|
||||
});
|
||||
|
||||
it("parts used, counted when used: the line says they are booked", async () => {
|
||||
const el = await mountWithParts({ ...USING, partsCostMode: "use" });
|
||||
expect(chip(el)).to.equal(null);
|
||||
expect(note(el)!.textContent).to.include("17 €");
|
||||
expect(note(el)!.textContent).to.include("booked automatically");
|
||||
});
|
||||
|
||||
it("parts used: a package price is spread over its contents (#98)", 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,
|
||||
parts: [{ id: "p1", name: "Spray", cost: 12, package_size: 400, unit: "ml" }] as never,
|
||||
consumesParts: [{ part_id: "p1", quantity: 100 }] as never,
|
||||
currencySymbol: "€",
|
||||
});
|
||||
const c = chip(el)!;
|
||||
expect(c, "suggestion chip rendered").to.exist;
|
||||
expect(c.textContent).to.include("17 €");
|
||||
expect(c.textContent).to.include("€");
|
||||
c.click();
|
||||
// 100 ml of a 400 ml can at 12 € = 3 €, not 100 × 12 €.
|
||||
expect(note(el)!.textContent).to.include("3 €");
|
||||
});
|
||||
|
||||
it("buy task: restock qty x price per package, follows the qty field, fills on click", async () => {
|
||||
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
|
||||
expect(chip(el)!.textContent).to.include("9");
|
||||
setInput(el, 0, "4");
|
||||
await el.updateComplete;
|
||||
const cost = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>(".field-input")][1];
|
||||
// Machine value for the <input type="number"> — never profile-formatted
|
||||
// ("17,00" would be rejected by the input under a decimal_comma profile).
|
||||
expect(cost.value).to.equal("17");
|
||||
expect(chip(el)!.textContent).to.include("18");
|
||||
chip(el)!.click();
|
||||
await el.updateComplete;
|
||||
// Buy task: restock qty, notes, cost.
|
||||
const cost = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>(".field-input")][2];
|
||||
// Machine value for the <input type="number"> — never profile-formatted.
|
||||
expect(cost.value).to.equal("18");
|
||||
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");
|
||||
it("buy task, counted when used: the price becomes the part's price", async () => {
|
||||
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5, partsCostMode: "use" });
|
||||
expect(chip(el)).to.exist;
|
||||
expect(note(el)!.textContent).to.include("price");
|
||||
const off = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
|
||||
expect(note(off), "no hint when purchases are the spending").to.equal(null);
|
||||
});
|
||||
|
||||
it("no chip when no involved part carries a price", async () => {
|
||||
it("nothing 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);
|
||||
expect(note(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,
|
||||
});
|
||||
it("buy task: no chip once the user typed a cost themselves", async () => {
|
||||
const el = await mountWithParts({ restockDefault: 1, restockUnitCost: 5 });
|
||||
expect(chip(el)).to.exist;
|
||||
setInput(el, 1, "3.10");
|
||||
setInput(el, 2, "3.10");
|
||||
await el.updateComplete;
|
||||
expect(chip(el)).to.equal(null);
|
||||
});
|
||||
|
||||
+6
-6
@@ -78,7 +78,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
|
||||
installation_date: "2025-01-01", documentation_url: "https://x.test/",
|
||||
notes: "test notes",
|
||||
});
|
||||
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
const banned = findBannedTags(el.shadowRoot!);
|
||||
expect(banned, `object-dialog has banned lazy-load elements: ${banned.join(", ")}`)
|
||||
.to.have.lengthOf(0);
|
||||
@@ -106,10 +106,10 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
|
||||
schedule_time: "09:00",
|
||||
on_complete_action: { service: "button.press", target: { entity_id: "button.x" } },
|
||||
});
|
||||
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
// Expand <details> sections so action UI is in the DOM
|
||||
el.shadowRoot!.querySelectorAll<HTMLDetailsElement>("details").forEach((d) => { d.open = true; });
|
||||
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
const banned = findBannedTags(el.shadowRoot!);
|
||||
expect(banned, `task-dialog has banned lazy-load elements: ${banned.join(", ")}`)
|
||||
.to.have.lengthOf(0);
|
||||
@@ -123,7 +123,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
|
||||
html`<maintenance-group-dialog .hass=${hass}></maintenance-group-dialog>`,
|
||||
);
|
||||
el.openCreate();
|
||||
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
const banned = findBannedTags(el.shadowRoot!);
|
||||
expect(banned, `group-dialog has banned lazy-load elements: ${banned.join(", ")}`)
|
||||
.to.have.lengthOf(0);
|
||||
@@ -138,7 +138,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
|
||||
);
|
||||
el.entryId = "entry_x"; el.taskId = "t1"; el.taskName = "Test";
|
||||
el.open();
|
||||
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
const banned = findBannedTags(el.shadowRoot!);
|
||||
expect(banned, `complete-dialog has banned lazy-load elements: ${banned.join(", ")}`)
|
||||
.to.have.lengthOf(0);
|
||||
@@ -156,7 +156,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
|
||||
type: "completed", timestamp: "2025-01-01T00:00:00",
|
||||
notes: null, cost: null, duration: null, completed_by: null,
|
||||
});
|
||||
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
|
||||
const banned = findBannedTags(el.shadowRoot!);
|
||||
expect(banned, `history-edit-dialog has banned lazy-load elements: ${banned.join(", ")}`)
|
||||
.to.have.lengthOf(0);
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ describe("group-dialog task list (#40 alphabetical sort)", () => {
|
||||
await el.updateComplete;
|
||||
|
||||
// Force a re-render with the same objects (ordering invariant).
|
||||
el.objects = [...mockObjects()];
|
||||
el.objects = [...mockObjects()] as never;
|
||||
await el.updateComplete;
|
||||
|
||||
const cbAgain = el.shadowRoot!.querySelectorAll(".object-block")[0]
|
||||
|
||||
+4
-2
@@ -48,8 +48,10 @@ async function fireAndWait(detail: unknown): Promise<HTMLElement | null> {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("ll-custom", { detail, bubbles: true, composed: true }),
|
||||
);
|
||||
// handler imports dialog-mount.ts dynamically, then mounts the dialog
|
||||
for (let i = 0; i < 40; i++) {
|
||||
// handler imports dialog-mount.ts dynamically, then mounts the dialog — the
|
||||
// first import compiles the whole dialog tree, which took over the old 1 s
|
||||
// budget on a cold transform cache (failed three runs in a row, 2026-09-28)
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const dlg = document.body.querySelector<HTMLElement>(OBJECT_DIALOG_TAG);
|
||||
if (dlg) return dlg;
|
||||
await tick(25);
|
||||
|
||||
@@ -181,6 +181,44 @@ describe("panel shell", () => {
|
||||
expect(sr(el).querySelector(".today-person")).to.equal(null);
|
||||
});
|
||||
|
||||
it("the person filter lists the responsible people and shows a saved view's person", async () => {
|
||||
const { el } = await mountPanel(
|
||||
[
|
||||
obj("e1", [
|
||||
task({ name: "Hers", responsible_user_id: "u-anna" }),
|
||||
task({ name: "His", responsible_user_id: "u-ben" }),
|
||||
task({ name: "Nobody's" }),
|
||||
]),
|
||||
],
|
||||
{
|
||||
"maintenance_supporter/users/list": () => ({ users: [{ id: "u-anna", name: "Anna" }, { id: "u-ben", name: "Ben" }, { id: "u-idle", name: "Idle" }] }),
|
||||
"maintenance_supporter/views/list": () => ({ views: [{ id: "v-anna", name: "Anna's tasks", filters: { user_id: "u-anna" } }] }),
|
||||
},
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 30));
|
||||
await el.updateComplete;
|
||||
const userSelect = () =>
|
||||
[...sr(el).querySelectorAll<HTMLSelectElement>("select")].find((s) => s.querySelector('option[value="current_user"]'))!;
|
||||
// Everyone responsible for a task, by name — not people without tasks.
|
||||
expect([...userSelect().options].map((o) => o.textContent!.trim()).slice(2)).to.deep.equal(["Anna", "Ben"]);
|
||||
|
||||
// A saved view that narrows to one person shows that person — the select
|
||||
// used to stay blank because only "All" and "My tasks" existed.
|
||||
(el as unknown as { _applyView(id: string): void })._applyView("v-anna");
|
||||
await el.updateComplete;
|
||||
expect(userSelect().selectedOptions[0].textContent!.trim()).to.equal("Anna");
|
||||
const rows = [...sr(el).querySelectorAll(".task-name")].map((n) => n.textContent!.trim());
|
||||
expect(rows.some((r) => r.includes("Hers"))).to.equal(true);
|
||||
expect(rows.some((r) => r.includes("His") || r.includes("Nobody"))).to.equal(false);
|
||||
|
||||
// Picking a person by hand filters the same way.
|
||||
const sel = userSelect();
|
||||
sel.value = "u-ben";
|
||||
sel.dispatchEvent(new Event("change"));
|
||||
await el.updateComplete;
|
||||
expect((el as unknown as { _filterUser: string | null })._filterUser).to.equal("u-ben");
|
||||
});
|
||||
|
||||
it("Today person chip stays readable on a phone next to a long object name (#169)", async () => {
|
||||
localStorage.setItem("msp-overview-tab", "today");
|
||||
await setViewport({ width: 360, height: 800 });
|
||||
|
||||
+2
-2
@@ -83,7 +83,7 @@ describe("suggested-setups dialog: counting start value (#102)", () => {
|
||||
await el.updateComplete;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as {
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as unknown as {
|
||||
selections: Array<{ device_id: string; entry_id?: string }>;
|
||||
};
|
||||
const byId = Object.fromEntries(adopt.selections.map((s) => [s.device_id, s]));
|
||||
@@ -102,7 +102,7 @@ describe("suggested-setups dialog: counting start value (#102)", () => {
|
||||
await el.updateComplete;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as {
|
||||
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as unknown as {
|
||||
selections: Array<{ device_id: string; baselines?: Record<string, number> }>;
|
||||
};
|
||||
const byId = Object.fromEntries(adopt.selections.map((s) => [s.device_id, s]));
|
||||
|
||||
+27
-9
@@ -48,7 +48,7 @@ function task(overrides: Record<string, unknown> = {}): MaintenanceTask {
|
||||
|
||||
function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
const { hass } = createMockHass({
|
||||
handler: () => ({ documents: [] }),
|
||||
fallback: () => ({ documents: [] }),
|
||||
});
|
||||
return {
|
||||
lang: "en",
|
||||
@@ -59,13 +59,15 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
objectDocUrl: null,
|
||||
objectManualDocs: [],
|
||||
openManualDoc: () => {},
|
||||
setPhaseCursor: () => {},
|
||||
moveTask: () => {},
|
||||
setChecklistItem: () => {},
|
||||
isOperator: false,
|
||||
actionLoading: false,
|
||||
moreMenuOpen: false,
|
||||
activeTab: "overview",
|
||||
features: {
|
||||
adaptive: false, predictions: false, seasonal: false,
|
||||
adaptive: false, seasonal: false,
|
||||
environmental: false, budget: false, groups: false,
|
||||
checklists: false, schedule_time: false, completion_actions: false,
|
||||
},
|
||||
@@ -105,6 +107,7 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
openComplete: () => undefined,
|
||||
promptSkip: () => undefined,
|
||||
toggleArchive: () => undefined,
|
||||
togglePause: () => undefined,
|
||||
openQr: () => undefined,
|
||||
duplicateTask: () => undefined,
|
||||
promptReset: () => undefined,
|
||||
@@ -178,7 +181,7 @@ describe("task-detail renderer", () => {
|
||||
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", () => {
|
||||
it("open more-menu lists edit/qr/worksheet/duplicate/reset/postpone/snooze/pause/archive/delete and fires callbacks", () => {
|
||||
const calls: string[] = [];
|
||||
const host = mount(task(), ctx({
|
||||
moreMenuOpen: true,
|
||||
@@ -187,21 +190,36 @@ describe("task-detail renderer", () => {
|
||||
promptPostpone: () => calls.push("postpone"),
|
||||
snoozeTask: () => calls.push("snooze"),
|
||||
printWorksheet: () => calls.push("worksheet"),
|
||||
togglePause: (paused) => calls.push(paused ? "resume" : "pause"),
|
||||
toggleArchive: () => calls.push("archive"),
|
||||
}));
|
||||
const items = [...host.querySelectorAll(".popup-menu-item")];
|
||||
// edit, qr, worksheet, duplicate, move (#23), reset, postpone, snooze, archive, delete
|
||||
expect(items.length).to.equal(10);
|
||||
// edit, qr, worksheet, duplicate, move (#23), reset, postpone, snooze, pause (#193), archive, delete
|
||||
expect(items.length).to.equal(11);
|
||||
(items[6] as HTMLElement).click(); // postpone
|
||||
(items[7] as HTMLElement).click(); // snooze
|
||||
(items[2] as HTMLElement).click(); // work sheet (v2.21)
|
||||
(items[8] as HTMLElement).click(); // archive (demoted from the header)
|
||||
(items[9] as HTMLElement).click(); // delete (danger)
|
||||
(items[8] as HTMLElement).click(); // pause this one task (#193)
|
||||
(items[9] as HTMLElement).click(); // archive (demoted from the header)
|
||||
(items[10] as HTMLElement).click(); // delete (danger)
|
||||
expect(calls).to.deep.equal([
|
||||
"close", "postpone", "close", "snooze", "close", "worksheet", "close", "archive", "close", "delete",
|
||||
"close", "postpone", "close", "snooze", "close", "worksheet", "close", "pause", "close", "archive", "close", "delete",
|
||||
]);
|
||||
});
|
||||
|
||||
it("a paused task offers Resume and names its resume date (#193)", () => {
|
||||
const calls: string[] = [];
|
||||
const host = mount(task({ paused: true, paused_until: "2027-03-01", status: "paused" } as never), ctx({
|
||||
moreMenuOpen: true,
|
||||
togglePause: (paused) => calls.push(paused ? "resume" : "pause"),
|
||||
}));
|
||||
const resume = [...host.querySelectorAll<HTMLElement>(".popup-menu-item")].find((i) => i.textContent?.trim() === "Resume");
|
||||
expect(resume, "Resume offered").to.exist;
|
||||
resume!.click();
|
||||
expect(calls).to.deep.equal(["resume"]);
|
||||
expect(host.querySelector(".paused-until")?.textContent).to.include("until");
|
||||
});
|
||||
|
||||
it("shows a bell-off badge for a muted task (#173), and none otherwise", () => {
|
||||
const muted = mount(task({ notify_enabled: false }), ctx());
|
||||
expect(muted.querySelector(".muted-badge")).to.exist;
|
||||
@@ -277,7 +295,7 @@ describe("task-detail renderer", () => {
|
||||
task({ checklist: ["Drain", "Clean", "Refill"], checklist_progress: { Clean: true } }),
|
||||
ctx({
|
||||
features: {
|
||||
adaptive: false, predictions: false, seasonal: false,
|
||||
adaptive: false, seasonal: false,
|
||||
environmental: false, budget: false, groups: false,
|
||||
checklists: true, schedule_time: false, completion_actions: false,
|
||||
},
|
||||
|
||||
+5
-2
@@ -45,7 +45,7 @@ function task(overrides: Record<string, unknown> = {}): MaintenanceTask {
|
||||
|
||||
function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
const { hass } = createMockHass({
|
||||
handler: () => ({ documents: [] }),
|
||||
fallback: () => ({ documents: [] }),
|
||||
});
|
||||
return {
|
||||
lang: "en",
|
||||
@@ -56,13 +56,15 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
objectDocUrl: null,
|
||||
objectManualDocs: [],
|
||||
openManualDoc: () => {},
|
||||
setPhaseCursor: () => {},
|
||||
moveTask: () => {},
|
||||
setChecklistItem: () => {},
|
||||
isOperator: false,
|
||||
actionLoading: false,
|
||||
moreMenuOpen: false,
|
||||
activeTab: "overview",
|
||||
features: {
|
||||
adaptive: false, predictions: false, seasonal: false,
|
||||
adaptive: false, seasonal: false,
|
||||
environmental: false, budget: false, groups: false,
|
||||
checklists: false, schedule_time: false, completion_actions: false,
|
||||
},
|
||||
@@ -102,6 +104,7 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
openComplete: () => undefined,
|
||||
promptSkip: () => undefined,
|
||||
toggleArchive: () => undefined,
|
||||
togglePause: () => undefined,
|
||||
openQr: () => undefined,
|
||||
duplicateTask: () => undefined,
|
||||
promptReset: () => undefined,
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ describe("task-dialog calendar kinds (Phase 4)", () => {
|
||||
(el as any)._calOffset = "-2";
|
||||
await (el as any)._save();
|
||||
const msg = sent.find((m: any) => m.type === "maintenance_supporter/task/create");
|
||||
expect(msg.schedule).to.deep.equal({
|
||||
expect(msg!.schedule).to.deep.equal({
|
||||
kind: "day_of_month", day: -1, business: true, offset: -2,
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -55,7 +55,7 @@ describe("task-dialog runtime on-states (#103)", () => {
|
||||
await el.openEdit("entry_x", MOWER_TASK as any);
|
||||
await el.updateComplete;
|
||||
await (el as any)._save();
|
||||
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as {
|
||||
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as unknown as {
|
||||
trigger_config: { trigger_on_states?: string[] };
|
||||
};
|
||||
expect(update.trigger_config.trigger_on_states).to.deep.equal(["mowing"]);
|
||||
@@ -68,7 +68,7 @@ describe("task-dialog runtime on-states (#103)", () => {
|
||||
(el as any)._triggerOnStates = "";
|
||||
await el.updateComplete;
|
||||
await (el as any)._save();
|
||||
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as {
|
||||
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as unknown as {
|
||||
trigger_config: { trigger_on_states?: string[] };
|
||||
};
|
||||
expect(update.trigger_config.trigger_on_states).to.equal(undefined);
|
||||
|
||||
Reference in New Issue
Block a user