Files
pleno-vue/tests/e2e/superuser-customer-complaints.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

345 lines
14 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockApi, seedAuthenticatedState } from "./support/network.js";
function json(body: unknown, status = 200) {
return {
status,
contentType: "application/json",
body: JSON.stringify(body),
};
}
async function primeSuperuserSession(page) {
const token = "superuser-customer-complaints-token";
await seedAuthenticatedState(page, token);
await page.goto("/login");
await page.evaluate(async (sessionToken) => {
const sessionModule = await import("/src/components/session/token/SessionUser.vue");
window.localStorage.setItem("token", sessionToken);
sessionModule.SessionUser.token.value = sessionToken;
sessionModule.SessionUser.authenticated.value = true;
sessionModule.SessionUser.permissions.value = ["superuser", "user"];
sessionModule.SessionUser.initiated.value = true;
}, token);
}
function parseFilters(filters: string | null) {
return (filters || "")
.split(",")
.map((entry) => entry.trim())
.filter(Boolean)
.reduce<Record<string, string>>((accumulator, entry) => {
const [key, ...valueParts] = entry.split(":");
accumulator[key] = valueParts.join(":");
return accumulator;
}, {});
}
async function installComplaintMocks(page) {
const requestLog = {
getUrls: [] as string[],
customerLookupQueries: [] as string[],
updateBodies: [] as Array<Record<string, unknown>>,
deleteIds: [] as number[],
};
const complaints = [
{
id: 101,
department_id: 1,
department_name: "Copenhagen",
customer_number: 12345,
customer_name: "Acme Transport",
wash_date: "2026-04-06",
category: "damage_mirrors",
description: "Broken mirror after wash",
created_by: 7,
created_by_name: "Jeppe",
created_at: "2026-04-07 09:00:00",
},
{
id: 102,
department_id: 2,
department_name: "Odense",
customer_number: null,
customer_name: null,
wash_date: null,
category: null,
description: "Late service response",
created_by: 8,
created_by_name: "Anna",
created_at: "2026-04-08 10:30:00",
},
];
await page.route(/\/departments\/daily-reports\/complaints\/customers(?:\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const search = (url.searchParams.get("search") || "").trim().toLowerCase();
requestLog.customerLookupQueries.push(search);
let matches = [
{ customer_number: 12345, customer_name: "Acme Transport" },
{ customer_number: 45678, customer_name: "Updated Customer" },
];
if (search !== "") {
matches = matches.filter((customer) =>
`${customer.customer_name} ${customer.customer_number}`.toLowerCase().includes(search)
);
}
await route.fulfill(json({ success: true, data: matches }));
});
await page.route(/\/departments\/daily-reports\/complaints(?:\?.*)?$/i, async (route) => {
const request = route.request();
const url = new URL(request.url());
const method = request.method().toUpperCase();
if (method === "GET") {
requestLog.getUrls.push(request.url());
const id = Number.parseInt(url.searchParams.get("id") || "", 10);
if (Number.isFinite(id) && id > 0) {
const complaint = complaints.find((item) => item.id === id) || null;
await route.fulfill(json({ success: true, data: complaint }));
return;
}
const filters = parseFilters(url.searchParams.get("filters"));
const search = (url.searchParams.get("search") || "").trim().toLowerCase();
const pageNumber = Math.max(1, Number.parseInt(url.searchParams.get("page") || "1", 10) || 1);
const limit = Math.max(1, Number.parseInt(url.searchParams.get("limit") || "100", 10) || 100);
const order = url.searchParams.get("order") || "created_at:desc";
const [orderBy = "created_at", orderDirection = "desc"] = order.split(":");
let rows = [...complaints];
if (filters.department_id) {
rows = rows.filter((complaint) => String(complaint.department_id) === filters.department_id);
}
if (search !== "") {
rows = rows.filter((complaint) =>
[
complaint.description,
complaint.customer_number,
complaint.customer_name,
complaint.wash_date,
complaint.category,
complaint.department_name,
complaint.created_by_name,
complaint.id,
]
.filter((value) => value !== null && value !== undefined)
.some((value) => String(value).toLowerCase().includes(search))
);
}
rows.sort((left, right) => {
const leftValue = String((left as Record<string, unknown>)[orderBy] || "");
const rightValue = String((right as Record<string, unknown>)[orderBy] || "");
const comparison = leftValue.localeCompare(rightValue);
return orderDirection.toLowerCase() === "asc" ? comparison : comparison * -1;
});
const startIndex = (pageNumber - 1) * limit;
const paginatedRows = rows.slice(startIndex, startIndex + limit);
await route.fulfill(
json({
success: true,
data: paginatedRows,
meta: {
pagination: {
page: pageNumber,
per_page: limit,
total: rows.length,
},
},
})
);
return;
}
if (method === "PUT") {
const body = request.postDataJSON() as Record<string, unknown>;
requestLog.updateBodies.push(body);
const complaint = complaints.find((item) => item.id === Number(body.id));
if (!complaint) {
await route.fulfill(json({ message: "Complaint not found" }, 404));
return;
}
complaint.department_id = Number(body.department_id);
complaint.department_name = complaint.department_id === 2 ? "Odense" : "Copenhagen";
complaint.customer_number = body.customer_number === null ? null : Number(body.customer_number);
complaint.customer_name = complaint.customer_number ? "Updated Customer" : null;
complaint.wash_date = body.wash_date === null ? null : String(body.wash_date || "");
complaint.category = body.category === null ? null : String(body.category || "");
complaint.description = String(body.description || "");
await route.fulfill(json({ success: true, data: complaint }));
return;
}
if (method === "DELETE") {
const id = Number.parseInt(url.searchParams.get("id") || "", 10);
requestLog.deleteIds.push(id);
const index = complaints.findIndex((item) => item.id === id);
if (index >= 0) {
complaints.splice(index, 1);
}
await route.fulfill(
json({
success: true,
data: {
message: "Complaint deleted successfully",
},
})
);
return;
}
await route.fulfill(json({ success: false, data: [] }, 405));
});
return {
complaints,
requestLog,
};
}
test.describe("Superuser customer complaints", () => {
test.beforeEach(async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: ["superuser", "user"],
});
await primeSuperuserSession(page);
});
test("renders the complaints page, filters by department, and searches complaint rows", async ({ page }) => {
const { requestLog } = await installComplaintMocks(page);
await page.goto("/superuser/complaints");
await expect(page).toHaveURL(/\/superuser\/complaints$/);
await expect(page.getByTestId("superuser-complaints-page")).toBeVisible();
await expect(page.getByTestId("superuser-complaint-row-101")).toContainText("Broken mirror after wash");
await expect(page.getByTestId("superuser-complaint-row-102")).toContainText("Late service response");
await expect(page.getByTestId("superuser-complaint-category-101")).toContainText("Skade spejle");
await expect(page.getByTestId("superuser-complaint-wash-date-102")).toContainText("-");
await expect(page.getByTestId("superuser-complaint-category-102")).toContainText("-");
await page.getByTestId("superuser-complaints-department-filter").selectOption("2");
await expect(page.getByTestId("superuser-complaint-row-102")).toBeVisible();
await expect(page.locator('[data-testid="superuser-complaint-row-101"]')).toHaveCount(0);
await page.getByTestId("superuser-complaints-department-filter").selectOption("*");
await expect(page.getByTestId("superuser-complaint-row-101")).toBeVisible();
await page.getByTestId("superuser-complaints-search").fill("mirror");
await expect(page.getByTestId("superuser-complaint-row-101")).toBeVisible();
await expect(page.locator('[data-testid="superuser-complaint-row-102"]')).toHaveCount(0);
await expect
.poll(() =>
requestLog.getUrls.some(
(url) => url.includes("filters=department_id%3A2") || url.includes("filters=department_id:2")
)
)
.toBe(true);
await expect.poll(() => requestLog.getUrls.some((url) => url.toLowerCase().includes("search=mirror"))).toBe(true);
});
test("edits complaints with department, searchable customer selection, and description updates", async ({ page }) => {
const { requestLog } = await installComplaintMocks(page);
await page.goto("/superuser/complaints");
await expect(page.getByTestId("superuser-complaints-page")).toBeVisible();
await page.getByTestId("superuser-complaint-edit-101").click();
await expect(page.getByTestId("superuser-complaint-edit-department")).toHaveValue("1");
await expect(page.getByTestId("superuser-complaint-edit-customer-search")).toHaveValue("Acme Transport - 12345");
await expect(page.getByTestId("superuser-complaint-edit-wash-date")).toHaveValue("2026-04-06");
await expect(page.getByTestId("superuser-complaint-edit-category")).toHaveValue("damage_mirrors");
await expect(page.getByTestId("superuser-complaint-edit-description")).toHaveValue("Broken mirror after wash");
await page.getByTestId("superuser-complaint-edit-department").selectOption("2");
await page.getByTestId("superuser-complaint-edit-customer-search").fill("updated");
await expect(page.getByTestId("superuser-complaint-edit-customer-option-45678")).toBeVisible();
await expect(page.getByTestId("superuser-complaint-edit-description")).toBeVisible();
await page.getByTestId("superuser-complaint-edit-customer-option-45678").click();
await page.getByTestId("superuser-complaint-edit-wash-date").fill("2026-04-08");
await page.getByTestId("superuser-complaint-edit-category").selectOption("wash_quality");
await page.getByTestId("superuser-complaint-edit-description").fill("Updated complaint description");
await page.locator(".swal2-confirm").click();
await expect.poll(() => requestLog.updateBodies.length).toBe(1);
expect(requestLog.customerLookupQueries).toContain("updated");
expect(requestLog.updateBodies[0]).toEqual({
id: 101,
department_id: 2,
customer_number: 45678,
wash_date: "2026-04-08",
category: "wash_quality",
description: "Updated complaint description",
});
await expect(page.getByTestId("superuser-complaint-row-101")).toContainText("Updated complaint description");
await expect(page.getByTestId("superuser-complaint-row-101")).toContainText("Odense");
await expect(page.getByTestId("superuser-complaint-row-101")).toContainText("45678 - Updated Customer");
await expect(page.getByTestId("superuser-complaint-category-101")).toContainText("Vaskekvalitet");
await expect(page.getByTestId("superuser-complaint-wash-date-101")).toContainText(/8\.4\.2026|08\.04\.2026/);
});
test("requires wash date/category for legacy rows, supports clearing the optional customer number, and deletes complaints", async ({
page,
}) => {
const { requestLog } = await installComplaintMocks(page);
await page.goto("/superuser/complaints");
await expect(page.getByTestId("superuser-complaints-page")).toBeVisible();
await page.getByTestId("superuser-complaint-edit-102").click();
await expect(page.getByTestId("superuser-complaint-edit-wash-date")).toHaveValue("");
await expect(page.getByTestId("superuser-complaint-edit-category")).toHaveValue("");
await page.locator(".swal2-confirm").click();
await expect(page.locator(".swal2-validation-message")).toContainText("Dato for vask");
await page.getByTestId("superuser-complaint-edit-wash-date").fill("2026-04-09");
await page.locator(".swal2-confirm").click();
await expect(page.locator(".swal2-validation-message")).toContainText("Kategori");
await page.locator(".swal2-cancel").click();
await page.getByTestId("superuser-complaint-edit-101").click();
await page.getByTestId("superuser-complaint-edit-customer-clear").click();
await expect(page.getByTestId("superuser-complaint-edit-customer-search")).toHaveValue("");
await page.getByTestId("superuser-complaint-edit-wash-date").fill("2026-04-09");
await page.getByTestId("superuser-complaint-edit-category").selectOption("service");
await page.getByTestId("superuser-complaint-edit-description").fill("Customer number removed");
await page.locator(".swal2-confirm").click();
await expect.poll(() => requestLog.updateBodies.length).toBe(1);
expect(requestLog.updateBodies[0]).toEqual({
id: 101,
department_id: 1,
customer_number: null,
wash_date: "2026-04-09",
category: "service",
description: "Customer number removed",
});
await expect(page.getByTestId("superuser-complaint-row-101")).not.toContainText("12345");
await expect(page.getByTestId("superuser-complaint-category-101")).toContainText("Service");
await expect(page.getByTestId("superuser-complaint-wash-date-101")).toContainText(/9\.4\.2026|09\.04\.2026/);
await page.getByTestId("superuser-complaint-delete-102").click();
await page.locator(".swal2-confirm").click();
await expect.poll(() => requestLog.deleteIds).toEqual([102]);
await expect(page.locator('[data-testid="superuser-complaint-row-102"]')).toHaveCount(0);
});
});