329 files
This commit is contained in:
@@ -66,7 +66,7 @@ export async function mountPanel(
|
||||
objects: unknown[],
|
||||
extraHandlers: Record<string, WsHandler> = {},
|
||||
) {
|
||||
const { hass, sent } = createMockHass({
|
||||
const { hass, sent, subscriptions } = createMockHass({
|
||||
handlers: {
|
||||
"maintenance_supporter/objects": () => ({ objects }),
|
||||
"maintenance_supporter/statistics": () => ({
|
||||
@@ -97,7 +97,7 @@ export async function mountPanel(
|
||||
await el.updateComplete;
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
await el.updateComplete;
|
||||
return { el, sent };
|
||||
return { el, sent, subscriptions };
|
||||
}
|
||||
|
||||
export function sr(el: HTMLElement): ShadowRoot {
|
||||
|
||||
@@ -97,7 +97,10 @@ export type WsHandler = (msg: SentMessage) => Promise<unknown> | unknown;
|
||||
export interface CreateMockHassResult {
|
||||
hass: {
|
||||
language: string;
|
||||
connection: { sendMessagePromise: (msg: SentMessage) => Promise<unknown> };
|
||||
connection: {
|
||||
sendMessagePromise: (msg: SentMessage) => Promise<unknown>;
|
||||
subscribeMessage: (cb: (event: unknown) => void, msg: SentMessage) => Promise<() => void>;
|
||||
};
|
||||
callService: (
|
||||
domain: string, service: string,
|
||||
data?: Record<string, unknown>, target?: Record<string, unknown>,
|
||||
@@ -107,6 +110,8 @@ export interface CreateMockHassResult {
|
||||
};
|
||||
sent: SentMessage[];
|
||||
serviceCalls: ServiceCall[];
|
||||
/** Captured subscribeMessage registrations — push events via `.push(ev)`. */
|
||||
subscriptions: Array<{ msg: SentMessage; push: (event: unknown) => void }>;
|
||||
}
|
||||
|
||||
export interface CreateMockHassOptions {
|
||||
@@ -157,15 +162,31 @@ export function createMockHass(opts: CreateMockHassOptions = {}): CreateMockHass
|
||||
serviceCalls.push({ domain, service, data, target });
|
||||
};
|
||||
|
||||
// Captured subscriptions: tests push events into components via
|
||||
// `subscriptions.find(...).push(event)`.
|
||||
const subscriptions: Array<{ msg: SentMessage; push: (event: unknown) => void }> = [];
|
||||
const subscribeMessage = async (
|
||||
cb: (event: unknown) => void,
|
||||
msg: SentMessage,
|
||||
): Promise<() => void> => {
|
||||
const entry = { msg, push: (event: unknown) => cb(event) };
|
||||
subscriptions.push(entry);
|
||||
return () => {
|
||||
const i = subscriptions.indexOf(entry);
|
||||
if (i >= 0) subscriptions.splice(i, 1);
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
hass: {
|
||||
language: opts.language ?? "en",
|
||||
connection: { sendMessagePromise },
|
||||
connection: { sendMessagePromise, subscribeMessage },
|
||||
callService,
|
||||
services: opts.services,
|
||||
states: opts.states,
|
||||
},
|
||||
sent,
|
||||
serviceCalls,
|
||||
subscriptions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,31 @@ async function mount(canWrite: boolean): Promise<MaintenancePartsSection> {
|
||||
}
|
||||
|
||||
describe("parts-section", () => {
|
||||
it("shows the inventory value only when a part has price AND tracked stock (#104)", async () => {
|
||||
// PARTS as-is: p1 has stock but no cost, p2 no stock → chip hidden.
|
||||
const bare = await mount(false);
|
||||
expect(bare.shadowRoot!.querySelector(".inventory-value")).to.equal(null);
|
||||
|
||||
const priced: MaintenancePart[] = [
|
||||
{ id: "p1", name: "Filter", cost: 12.5, stock: 3, is_low: false },
|
||||
{ id: "p2", name: "Brush", cost: 4, stock: null, is_low: false }, // untracked → no contribution
|
||||
{ id: "p3", name: "Seal", cost: null, stock: 5, is_low: false }, // unpriced → no contribution
|
||||
];
|
||||
const el = await fixture<MaintenancePartsSection>(html`
|
||||
<maintenance-parts-section
|
||||
.hass=${{ language: "en", connection: { sendMessagePromise: async () => ({}) } } as never}
|
||||
.entryId=${"e1"}
|
||||
.parts=${priced}
|
||||
.currencySymbol=${"€"}
|
||||
></maintenance-parts-section>
|
||||
`);
|
||||
await el.updateComplete;
|
||||
const chip = el.shadowRoot!.querySelector(".inventory-value")!;
|
||||
expect(chip, "value chip rendered").to.exist;
|
||||
expect(chip.textContent).to.include("37.50");
|
||||
expect(chip.textContent).to.include("€");
|
||||
});
|
||||
|
||||
it("renders a row per part with stock badge, identifiers and location", async () => {
|
||||
const el = await mount(false);
|
||||
const rows = el.shadowRoot!.querySelectorAll(".part-row");
|
||||
|
||||
+54
@@ -57,6 +57,9 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
taskId: "t1",
|
||||
objectName: "Pool Pump",
|
||||
objectDocUrl: null,
|
||||
objectManualDocs: [],
|
||||
openManualDoc: () => {},
|
||||
setChecklistItem: () => {},
|
||||
isOperator: false,
|
||||
actionLoading: false,
|
||||
moreMenuOpen: false,
|
||||
@@ -212,6 +215,57 @@ describe("task-detail renderer", () => {
|
||||
expect(host2.querySelector(".kpi-bar")).to.be.null;
|
||||
});
|
||||
|
||||
it("object-manual row falls back to an attached manual when the URL is empty", () => {
|
||||
let opened: unknown = null;
|
||||
const host = mount(task(), ctx({
|
||||
objectDocUrl: null,
|
||||
objectManualDocs: [{ id: "d1", title: "Pump Handbook", kind: "file" }],
|
||||
openManualDoc: (d) => { opened = d; },
|
||||
}));
|
||||
const link = [...host.querySelectorAll(".task-meta-link a")]
|
||||
.find((a) => a.textContent?.includes("Pool Pump")) as HTMLElement | undefined;
|
||||
expect(link, "fallback manual link rendered").to.exist;
|
||||
expect(link!.getAttribute("title")).to.equal("Pump Handbook");
|
||||
link!.click();
|
||||
expect((opened as { id?: string })?.id).to.equal("d1");
|
||||
});
|
||||
|
||||
it("the URL field still wins over attached manuals", () => {
|
||||
const host = mount(task(), ctx({
|
||||
objectDocUrl: "https://vendor.example/manual",
|
||||
objectManualDocs: [{ id: "d1", title: "Pump Handbook", kind: "file" }],
|
||||
}));
|
||||
const link = [...host.querySelectorAll(".task-meta-link a")]
|
||||
.find((a) => a.textContent?.includes("Pool Pump")) as HTMLElement | undefined;
|
||||
expect(link, "manual row rendered").to.exist;
|
||||
expect(link!.getAttribute("href")).to.equal("https://vendor.example/manual");
|
||||
});
|
||||
|
||||
it("checklist ticks render from progress and fire setChecklistItem (#73)", () => {
|
||||
const calls: Array<[string, boolean]> = [];
|
||||
const host = mount(
|
||||
task({ checklist: ["Drain", "Clean", "Refill"], checklist_progress: { Clean: true } }),
|
||||
ctx({
|
||||
features: {
|
||||
adaptive: false, predictions: false, seasonal: false,
|
||||
environmental: false, budget: false, groups: false,
|
||||
checklists: true, schedule_time: false, completion_actions: false,
|
||||
},
|
||||
setChecklistItem: (item, done) => calls.push([item, done]),
|
||||
}),
|
||||
);
|
||||
const header = host.querySelector(".checklist-preview-header")!;
|
||||
expect(header.textContent).to.include("1/3");
|
||||
const boxes = [...host.querySelectorAll<HTMLInputElement>(".checklist-preview-list input")];
|
||||
expect(boxes.length).to.equal(3);
|
||||
expect(boxes[1].checked).to.be.true;
|
||||
expect(boxes[0].checked).to.be.false;
|
||||
expect(host.querySelectorAll(".checklist-preview-list li.checked").length).to.equal(1);
|
||||
|
||||
boxes[0].click();
|
||||
expect(calls).to.deep.equal([["Drain", true]]);
|
||||
});
|
||||
|
||||
it("KPI bar shows warning days and currency symbol", () => {
|
||||
const host = mount(task(), ctx({ currencySymbol: "$" }));
|
||||
const kpi = host.querySelector(".kpi-bar")!;
|
||||
|
||||
+3
@@ -54,6 +54,9 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
|
||||
taskId: "t1",
|
||||
objectName: "Pool Pump",
|
||||
objectDocUrl: null,
|
||||
objectManualDocs: [],
|
||||
openManualDoc: () => {},
|
||||
setChecklistItem: () => {},
|
||||
isOperator: false,
|
||||
actionLoading: false,
|
||||
moreMenuOpen: false,
|
||||
|
||||
Reference in New Issue
Block a user