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>((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>, 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)[orderBy] || ""); const rightValue = String((right as Record)[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; 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); }); });