Files
pleno-vue/tests/e2e/admin-overview-night-washes.spec.ts
T
Jeppe Bundgaard b39b458f4f Add .prettierrc.json and refactor test files for improved formatting consistency:
- Introduced `.prettierrc.json` to enforce consistent code formatting across the project.
- Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
2026-04-13 09:13:27 +02:00

387 lines
12 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockApi, seedAuthenticatedState } from "./support/network.js";
const adminPermissions = [
"admin",
"list_department_daily_reports",
"list_bookings",
"department_access_1",
"department_access_2",
"department_access_3",
];
const json = (body: unknown, status = 200) => ({
status,
contentType: "application/json",
body: JSON.stringify(body),
});
const departments = [
{ id: 1, name: "North" },
{ id: 2, name: "South" },
{ id: 3, name: "Missing Hours" },
];
const overviewByRange: Record<string, Record<string, unknown>> = {
"2026-03-23|2026-03-24": {
department_ids: [1, 2, 3],
date: "2026-03-23",
date_to: "2026-03-24",
metrics: {
bookings: { state: "ready", value: 9, out_of: 12, message: null },
complaints: {
state: "unavailable",
value: null,
out_of: null,
message: "Kundeklager er ikke tilgængelig endnu.",
},
night_washes: {
state: "ready",
value: 5,
out_of: null,
message: null,
by_source: {
orders: 1,
xlvask: 2,
selfserve: 2,
},
has_missing_opening_hours: true,
missing_department_ids: [3],
},
revenue: { state: "ready", value: 5400, out_of: null, message: null },
washes: { state: "ready", value: 18, out_of: null, message: null },
products_sold: { state: "ready", value: 26, out_of: null, message: null },
transactions: { state: "ready", value: 15, out_of: null, message: null },
water_usage: { state: "ready", value: 44, out_of: null, message: null },
overtime: { state: "ready", value: 2.5, out_of: null, message: null },
},
products: [],
},
"2026-03-23|2026-03-25": {
department_ids: [1, 2, 3],
date: "2026-03-23",
date_to: "2026-03-25",
metrics: {
bookings: { state: "ready", value: 12, out_of: 16, message: null },
complaints: {
state: "unavailable",
value: null,
out_of: null,
message: "Kundeklager er ikke tilgængelig endnu.",
},
night_washes: {
state: "ready",
value: 7,
out_of: null,
message: null,
by_source: {
orders: 2,
xlvask: 2,
selfserve: 3,
},
has_missing_opening_hours: true,
missing_department_ids: [3],
},
revenue: { state: "ready", value: 6900, out_of: null, message: null },
washes: { state: "ready", value: 23, out_of: null, message: null },
products_sold: { state: "ready", value: 31, out_of: null, message: null },
transactions: { state: "ready", value: 19, out_of: null, message: null },
water_usage: { state: "ready", value: 58, out_of: null, message: null },
overtime: { state: "ready", value: 3.25, out_of: null, message: null },
},
products: [],
},
};
const transactionCountsByRange: Record<string, Record<number, Record<string, unknown>>> = {
"2026-03-23|2026-03-24": {
1: {
earnings: 2200,
products: 9,
quantity: 6,
washes: 8,
outside_hours: {
total: 2,
by_source: { orders: 1, xlvask: 1, selfserve: 0 },
has_missing_opening_hours: false,
missing_department_ids: [],
},
},
2: {
earnings: 3200,
products: 12,
quantity: 9,
washes: 10,
outside_hours: {
total: 3,
by_source: { orders: 0, xlvask: 1, selfserve: 2 },
has_missing_opening_hours: false,
missing_department_ids: [],
},
},
3: {
earnings: 500,
products: 2,
quantity: 1,
washes: 2,
outside_hours: {
total: 0,
by_source: { orders: 0, xlvask: 0, selfserve: 0 },
has_missing_opening_hours: true,
missing_department_ids: [3],
},
},
},
"2026-03-23|2026-03-25": {
1: {
earnings: 2700,
products: 11,
quantity: 8,
washes: 10,
outside_hours: {
total: 3,
by_source: { orders: 1, xlvask: 1, selfserve: 1 },
has_missing_opening_hours: false,
missing_department_ids: [],
},
},
2: {
earnings: 3700,
products: 14,
quantity: 10,
washes: 11,
outside_hours: {
total: 4,
by_source: { orders: 1, xlvask: 1, selfserve: 2 },
has_missing_opening_hours: false,
missing_department_ids: [],
},
},
3: {
earnings: 500,
products: 2,
quantity: 1,
washes: 2,
outside_hours: {
total: 0,
by_source: { orders: 0, xlvask: 0, selfserve: 0 },
has_missing_opening_hours: true,
missing_department_ids: [3],
},
},
},
};
const trendByRange: Record<string, Record<string, unknown>> = {
"2026-03-23|2026-03-24": {
points: [
{
date: "2026-03-23",
total: 2,
by_source: {
orders: 1,
xlvask: 0,
selfserve: 1,
},
},
{
date: "2026-03-24",
total: 3,
by_source: {
orders: 0,
xlvask: 2,
selfserve: 1,
},
},
],
has_missing_opening_hours: true,
missing_department_ids: [3],
},
"2026-03-23|2026-03-25": {
points: [
{
date: "2026-03-23",
total: 2,
by_source: {
orders: 1,
xlvask: 0,
selfserve: 1,
},
},
{
date: "2026-03-24",
total: 3,
by_source: {
orders: 0,
xlvask: 2,
selfserve: 1,
},
},
{
date: "2026-03-25",
total: 2,
by_source: {
orders: 1,
xlvask: 0,
selfserve: 1,
},
},
],
has_missing_opening_hours: true,
missing_department_ids: [3],
},
};
async function mockOverviewDependencies(page) {
await seedAuthenticatedState(page);
await mockApi(page, {
authenticated: true,
permissions: adminPermissions,
});
await page.route(/\/departments(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const departmentId = Number(url.searchParams.get("id"));
if (departmentId > 0) {
await route.fulfill(
json({
data: departments.find((department) => department.id === departmentId) || null,
})
);
return;
}
await route.fulfill(json({ data: departments }));
});
await page.route(/\/departments\/weather(\?.*)?$/i, async (route) => {
await route.fulfill(json({ data: [] }));
});
await page.route(/\/departments\/daily-reports\/get(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const date = url.searchParams.get("date") || "2026-03-23";
await route.fulfill(
json({
data: {
id: 10,
department_id: 1,
water_usage: 32,
notes: "",
filled_by: 1,
created_at: `${date} 00:00:00`,
},
})
);
});
await page.route(/\/departments\/daily-reports\/overview(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const key = `${url.searchParams.get("date")}|${url.searchParams.get("date_to") || url.searchParams.get("date")}`;
await route.fulfill(
json({
data: overviewByRange[key] || overviewByRange["2026-03-23|2026-03-24"],
})
);
});
await page.route(/\/departments\/daily-reports\/transaction-count(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const key = `${url.searchParams.get("date")}|${url.searchParams.get("date_to") || url.searchParams.get("date")}`;
const departmentId = Number(url.searchParams.get("department_id"));
const payload =
transactionCountsByRange[key]?.[departmentId] || transactionCountsByRange["2026-03-23|2026-03-24"][departmentId];
await route.fulfill(
json({
data: payload,
})
);
});
await page.route(/\/departments\/daily-reports\/outside-hours-trend(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const key = `${url.searchParams.get("date")}|${url.searchParams.get("date_to")}`;
await route.fulfill(
json({
data: trendByRange[key] || trendByRange["2026-03-23|2026-03-24"],
})
);
});
await page.route(
/\/departments\/daily-reports(?!\/overview|\/transaction-count|\/outside-hours-trend|\/get)(\?.*)?$/i,
async (route) => {
await route.fulfill(json({ data: [] }));
}
);
}
async function expandOverviewReportsOnMobile(page, testInfo) {
if (!testInfo.project.use.isMobile) {
return;
}
if (await page.getByTestId("daily-report-page").count()) {
return;
}
await page.getByTestId("admin-mobile-reports-toggle").click();
await expect(page.getByTestId("daily-report-page")).toBeVisible();
}
test.describe("Admin overview night washes", () => {
test("renders deduped Døgnvask totals across cards, list, and chart with stable range updates", async ({
page,
}, testInfo) => {
await mockOverviewDependencies(page);
await page.goto("/admin?departmentIds=1,2,3&dateFrom=2026-03-23&dateTo=2026-03-24");
await expandOverviewReportsOnMobile(page, testInfo);
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.getByTestId("daily-report-tile-night-washes")).toContainText("5");
await expect(page.getByTestId("daily-report-night-washes-breakdown")).toContainText("Ordre: 1");
await expect(page.getByTestId("daily-report-night-washes-breakdown")).toContainText("XL Vask: 2");
await expect(page.getByTestId("daily-report-night-washes-breakdown")).toContainText("Self-serve: 2");
await expect(page.getByTestId("daily-report-night-washes-warning")).toContainText("Missing Hours");
const tabs = page.locator(".tabs li");
await tabs.nth(1).click();
await expect(page.getByTestId("overview-summary-night-washes-total")).toContainText("5");
await expect(page.getByTestId("overview-summary-night-washes-breakdown")).toContainText("Self-serve: 2");
await expect(page.getByTestId("overview-summary-night-washes-warning")).toContainText("Missing Hours");
await expect(page.getByTestId("overview-department-1-night-washes-total")).toContainText("2");
await expect(page.getByTestId("overview-department-2-night-washes-total")).toContainText("3");
await expect(page.getByTestId("overview-department-3-night-washes-total")).toContainText("0");
await tabs.nth(2).click();
await expect(page.getByTestId("outside-hours-chart")).toBeVisible();
await expect(page.getByTestId("outside-hours-chart-warning")).toContainText("Missing Hours");
const endDateInput = page.locator("[data-testid='date-period-end']:visible").first();
await endDateInput.fill("2026-03-25");
await endDateInput.blur();
await tabs.nth(0).click();
await expect(page.getByTestId("daily-report-tile-night-washes")).toContainText("7");
await expect(page.getByTestId("daily-report-night-washes-breakdown")).toContainText("Self-serve: 3");
await tabs.nth(1).click();
await expect(page.getByTestId("overview-summary-night-washes-total")).toContainText("7");
await expect(page.getByTestId("overview-summary-night-washes-breakdown")).toContainText("Ordre: 2");
await expect(page.getByTestId("overview-summary-night-washes-breakdown")).toContainText("XL Vask: 2");
await expect(page.getByTestId("overview-summary-night-washes-breakdown")).toContainText("Self-serve: 3");
await tabs.nth(2).click();
await expect(page.getByTestId("outside-hours-chart")).toBeVisible();
if (testInfo.project.use.isMobile) {
await tabs.nth(0).click();
await expect(page.getByTestId("daily-report-tile-night-washes")).toBeVisible();
await tabs.nth(1).click();
await expect(page.getByTestId("overview-summary-night-washes-total")).toBeVisible();
}
});
});