- 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.
345 lines
14 KiB
TypeScript
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);
|
|
});
|
|
});
|