Files
pleno-vue/tests/unit/department-weather.spec.js
T
Jeppe Bundgaard 8b8780bbaf Refactor DepartmentWeather for key-based rendering and summary toggle improvements:
- Added `getEntryKey` function to generate unique keys for dynamic entries.
- Enhanced `isMultiDaySelection` watcher with immediate execution option.
- Updated DOM structure for action buttons with inline export button support.
- Adjusted unit tests to cover functional changes and new test case for summary mode toggling.
2026-03-24 13:59:38 +01:00

289 lines
8.1 KiB
JavaScript

// @vitest-environment jsdom
import { mount } from "@vue/test-utils";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { nextTick } from "vue";
const { getObjectsMock } = vi.hoisted(() => ({
getObjectsMock: vi.fn(),
}));
vi.mock("@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue", () => ({
ObjectsGlobal: {
get: {
objects: getObjectsMock,
},
},
}));
import DepartmentWeather from "@/views/dashboards/departmentDashboard/modules/daily-report/DepartmentWeather.vue";
const flushAll = async () => {
await nextTick();
await Promise.resolve();
await nextTick();
await Promise.resolve();
};
const createDeferred = () => {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
const makeEntry = (overrides = {}) => ({
date: "2026-03-24",
time: "00:00",
current: false,
weather: "clear",
washes: 1,
hours: 2,
status: "healthy",
...overrides,
});
const getFirstWashesCellText = (wrapper) => {
const rows = wrapper.findAll("tbody tr");
const washesRow = rows[1];
if (!washesRow) {
return null;
}
const cells = washesRow.findAll("td");
return cells[1]?.text() ?? null;
};
const getRenderedHours = (wrapper) => {
const headers = wrapper.findAll("thead tr th").slice(1);
return headers.map((th) => th.text());
};
const getRowValues = (wrapper, rowIndex) => {
const rows = wrapper.findAll("tbody tr");
const row = rows[rowIndex];
if (!row) {
return [];
}
return row.findAll("td").slice(1).map((td) => td.text());
};
const getProductivityCellClasses = (wrapper) => {
const rows = wrapper.findAll("tbody tr");
const productivityRow = rows[3];
if (!productivityRow) {
return [];
}
return productivityRow.findAll("td").slice(1).map((td) => td.classes());
};
describe("DepartmentWeather", () => {
beforeEach(() => {
getObjectsMock.mockReset();
});
it("calls weather endpoint with ids/date_from/date_to", async () => {
getObjectsMock.mockResolvedValueOnce([makeEntry()]);
mount(DepartmentWeather, {
props: {
department_ids: [3, 1, 3, 2],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
expect(getObjectsMock).toHaveBeenCalledTimes(1);
expect(getObjectsMock).toHaveBeenCalledWith("/departments/weather", {
ids: "1,2,3",
date_from: "2026-03-23",
date_to: "2026-03-24",
});
});
it("refetches when ids or dates change", async () => {
getObjectsMock
.mockResolvedValueOnce([makeEntry({ washes: 3 })])
.mockResolvedValueOnce([makeEntry({ washes: 7 })]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [2],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
await wrapper.setProps({
department_ids: [2, 5],
date_from: "2026-03-24",
date_to: "2026-03-25",
});
await flushAll();
expect(getObjectsMock).toHaveBeenCalledTimes(2);
expect(getObjectsMock).toHaveBeenNthCalledWith(2, "/departments/weather", {
ids: "2,5",
date_from: "2026-03-24",
date_to: "2026-03-25",
});
});
it("clears and skips fetch when no departments are selected", async () => {
getObjectsMock.mockResolvedValueOnce([makeEntry()]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [9],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
expect(wrapper.find("section.daily-metrics").exists()).toBe(true);
await wrapper.setProps({
department_ids: [],
});
await flushAll();
expect(getObjectsMock).toHaveBeenCalledTimes(1);
expect(wrapper.find("section.daily-metrics").exists()).toBe(false);
});
it("ignores stale responses when a newer request finishes first", async () => {
const first = createDeferred();
const second = createDeferred();
getObjectsMock
.mockImplementationOnce(() => first.promise)
.mockImplementationOnce(() => second.promise);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await nextTick();
await wrapper.setProps({
date_to: "2026-03-25",
});
await nextTick();
second.resolve([makeEntry({ washes: 9002, time: "01:00" })]);
await flushAll();
expect(getFirstWashesCellText(wrapper)).toBe("9002");
first.resolve([makeEntry({ washes: 9001, time: "00:00" })]);
await flushAll();
expect(getFirstWashesCellText(wrapper)).toBe("9002");
});
it("hides hour columns when both washes and hours are zero", async () => {
getObjectsMock.mockResolvedValueOnce([
makeEntry({ time: "00:00", washes: 0, hours: 0 }),
makeEntry({ time: "01:00", washes: "0", hours: "0" }),
makeEntry({ time: "06:00", washes: 1, hours: 0 }),
makeEntry({ time: "07:00", washes: 0, hours: 2 }),
]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-24",
date_to: "2026-03-24",
},
});
await flushAll();
expect(getRenderedHours(wrapper)).toEqual(["06", "07"]);
});
it("shows summary toggle only for multi-day selections", async () => {
getObjectsMock.mockResolvedValueOnce([makeEntry({ time: "06:00", washes: 1, hours: 1 })]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
expect(wrapper.find("button.summary-toggle").exists()).toBe(true);
await wrapper.setProps({
date_to: "2026-03-23",
});
await flushAll();
expect(wrapper.find("button.summary-toggle").exists()).toBe(false);
});
it("summarizes entries by day when toggle is enabled", async () => {
getObjectsMock.mockResolvedValueOnce([
makeEntry({ date: "2026-03-23", time: "06:00", weather: "clear", washes: 1, hours: 1, status: "healthy" }),
makeEntry({ date: "2026-03-23", time: "07:00", weather: "rain", washes: 2, hours: 0, status: "degraded" }),
makeEntry({ date: "2026-03-23", time: "08:00", weather: "rain", washes: 0, hours: 1, status: "degraded" }),
makeEntry({ date: "2026-03-24", time: "09:00", weather: "showers", washes: 0, hours: 2, status: "healthy" }),
makeEntry({ date: "2026-03-24", time: "10:00", weather: "showers", washes: 0, hours: 1, status: "degraded" }),
makeEntry({ date: "2026-03-24", time: "11:00", weather: "rain", washes: 1, hours: 0, status: "unhealthy" }),
]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
expect(getRenderedHours(wrapper)).toEqual(["06", "07", "08", "09", "10", "11"]);
await wrapper.get("button.summary-toggle").trigger("click");
await flushAll();
expect(getRenderedHours(wrapper)).toEqual(["23/03", "24/03"]);
expect(getRowValues(wrapper, 1)).toEqual(["3", "1"]);
expect(getRowValues(wrapper, 2)).toEqual(["2", "3"]);
expect(getProductivityCellClasses(wrapper)).toEqual([
expect.arrayContaining(["yellow"]),
expect.arrayContaining(["red"]),
]);
});
it("starts in hourly mode and lets users toggle to daily summary in multi-day mode", async () => {
getObjectsMock.mockResolvedValueOnce([
makeEntry({ date: "2026-03-23", time: "06:00", washes: 1, hours: 1 }),
makeEntry({ date: "2026-03-23", time: "07:00", washes: 2, hours: 2 }),
makeEntry({ date: "2026-03-24", time: "08:00", washes: 3, hours: 3 }),
]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
expect(getRenderedHours(wrapper)).toEqual(["06", "07", "08"]);
await wrapper.get("button.summary-toggle").trigger("click");
await flushAll();
expect(getRenderedHours(wrapper)).toEqual(["23/03", "24/03"]);
});
});