Files
pleno-vue/tests/unit/department-weather.spec.js
T
Jeppe Bundgaard 828e2fd312 Add test cases for internationalization, orders table rendering, and error handling:
- **Internationalization:** Added end-to-end test `i18n.smoke.spec.ts` to validate Danish locale translations. Ensured normalization and removed unexpected strings in the `da` locale file.
- **Orders Table:** Created unit test `orders-table.spec.js` to verify sparse data handling, invoice collection preloading, and rendering correctness.
- **Error Handling:** Enhanced connectivity issue UI (`ConnectivityIssue.vue`) with `data-testid` attributes for improved testability.
- **E2E Playwright Tests:** Updated and simplified e2e test utilities. Replaced `seedAuthenticatedState` with `primeMockSession` for session preparation. Added new tests for `/user/orders` and `/user/invoices` pages focusing on edge cases and maintaining structure consistency.
2026-04-13 12:44:06 +02:00

351 lines
11 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());
};
const getCellByTestId = (wrapper, testId) => {
return wrapper.get(`[data-testid="${testId}"]`);
};
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("shows loading state while weather request is in flight", async () => {
const pending = createDeferred();
getObjectsMock.mockImplementationOnce(() => pending.promise);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-24",
date_to: "2026-03-24",
},
});
await nextTick();
expect(wrapper.find(".daily-metrics-loading").exists()).toBe(true);
expect(wrapper.find("table.table").exists()).toBe(false);
pending.resolve([makeEntry({ time: "06:00", washes: 4, hours: 2 })]);
await flushAll();
expect(wrapper.find(".daily-metrics-loading").exists()).toBe(false);
expect(wrapper.find("table.table").exists()).toBe(true);
});
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('[data-testid="department-weather-summary-toggle"]').exists()).toBe(true);
await wrapper.setProps({
date_to: "2026-03-23",
});
await flushAll();
expect(wrapper.find('[data-testid="department-weather-summary-toggle"]').exists()).toBe(false);
});
it("renders hours as rounded-up whole numbers and exposes stable weather cell hooks", async () => {
getObjectsMock.mockResolvedValueOnce([
makeEntry({ date: "2026-03-23", time: "06:00", washes: 0, hours: 0.5, status: "unhealthy" }),
makeEntry({ date: "2026-03-23", time: "19:00", washes: 20, hours: 19.0, status: "degraded" }),
makeEntry({ date: "2026-03-23", time: "20:00", washes: 10, hours: 20.19, status: "unhealthy" }),
makeEntry({ date: "2026-03-24", time: "08:00", washes: 2, hours: 0, status: "unknown" }),
]);
const wrapper = mount(DepartmentWeather, {
props: {
department_ids: [1],
date_from: "2026-03-23",
date_to: "2026-03-24",
},
});
await flushAll();
expect(getCellByTestId(wrapper, "department-weather-hours-2026-03-23-06:00").text()).toBe("1");
expect(getCellByTestId(wrapper, "department-weather-hours-2026-03-23-19:00").text()).toBe("19");
expect(getCellByTestId(wrapper, "department-weather-hours-2026-03-23-20:00").text()).toBe("21");
expect(getCellByTestId(wrapper, "department-weather-productivity-2026-03-23-06:00").classes()).toContain("red");
expect(getCellByTestId(wrapper, "department-weather-productivity-2026-03-24-08:00").classes()).toContain("gray");
await wrapper.get('[data-testid="department-weather-summary-toggle"]').trigger("click");
await flushAll();
expect(getCellByTestId(wrapper, "department-weather-hours-2026-03-23-00:00").text()).toBe("40");
expect(getCellByTestId(wrapper, "department-weather-hours-2026-03-24-00:00").text()).toBe("0");
expect(getCellByTestId(wrapper, "department-weather-productivity-2026-03-23-00:00").classes()).toContain("red");
expect(getCellByTestId(wrapper, "department-weather-productivity-2026-03-24-00:00").classes()).toContain("gray");
});
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('[data-testid="department-weather-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(["green"]),
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('[data-testid="department-weather-summary-toggle"]').trigger("click");
await flushAll();
expect(getRenderedHours(wrapper)).toEqual(["23/03", "24/03"]);
});
});