324 lines
10 KiB
TypeScript
324 lines
10 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { apiPathPattern, mockApi, seedAuthenticatedState } from "./support/network.js";
|
|
import { isDesktopProject } from "./support/projects";
|
|
|
|
const json = (body: unknown, status = 200) => ({
|
|
status,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const users = [
|
|
{
|
|
id: 11,
|
|
customer_number: 12345,
|
|
display_name: "Anna Andersen",
|
|
group_id: 1,
|
|
},
|
|
{
|
|
id: 12,
|
|
customer_number: 67890,
|
|
display_name: "Bo Nielsen",
|
|
group_id: 2,
|
|
},
|
|
];
|
|
|
|
const userEnvelope = (rows: Array<Record<string, unknown>>) => ({
|
|
data: rows,
|
|
meta: {
|
|
pagination: {
|
|
page: 1,
|
|
per_page: 100,
|
|
total: rows.length,
|
|
},
|
|
},
|
|
});
|
|
|
|
const overviewUser = {
|
|
id: 11,
|
|
customer_number: 12345,
|
|
display_name: "Anna Andersen",
|
|
email: "anna@example.test",
|
|
phone: {
|
|
number: "12345678",
|
|
country_code: 45,
|
|
},
|
|
group_id: 1,
|
|
created_at: "2026-01-01 08:30:00",
|
|
updated_at: "2026-02-01 09:45:00",
|
|
economic_customer: {
|
|
customerNumber: 12345,
|
|
name: "Anna Transport",
|
|
address: "Main Road 1",
|
|
zip: "2100",
|
|
city: "Copenhagen",
|
|
mobilePhone: "12345678",
|
|
email: "billing@example.test",
|
|
corporateIdentificationNumber: "12345678",
|
|
currency: "DKK",
|
|
country: "Denmark",
|
|
barred: false,
|
|
},
|
|
permissions: ["superuser", "user", "get_user", "set_custom_price"],
|
|
attributes: [{ id: 1, attribute: "invoiceAllOrdersIndividually" }],
|
|
discounts: [{ id: 999999, percentage: 10 }],
|
|
orders_not_invoiced: [{ id: 501 }],
|
|
keys: {
|
|
open_invoice_draft: "DRAFT-1",
|
|
OtherSpecialArrangement: "Night wash agreement",
|
|
OtherVaskeabonnement: "Monthly wash subscription note",
|
|
},
|
|
wash_subscription_transactions: [
|
|
{
|
|
id: 6250,
|
|
customer_id: 12345,
|
|
cashier_id: 1857,
|
|
reference: "Vaskeabonnementer",
|
|
notes: "",
|
|
department_id: 10,
|
|
reg_1: "",
|
|
reg_2: "",
|
|
reg_3: "",
|
|
completed_at: null,
|
|
created_at: "2026-03-01 00:00:01",
|
|
deleted_at: null,
|
|
total_net_amount: 694,
|
|
invoice_collection_id: 1634,
|
|
booking_id: 0,
|
|
closed_at: "2026-03-24 12:45:10",
|
|
},
|
|
],
|
|
};
|
|
|
|
const overviewVehicles = [
|
|
{
|
|
id: 301,
|
|
customer_id: 12345,
|
|
reg: "AA11223",
|
|
type: 1,
|
|
reference: "Truck 1",
|
|
wash_subscription: true,
|
|
xlvask: true,
|
|
addons: {
|
|
list: [{ product: { id: 91, name: "Interior cleaning" } }],
|
|
},
|
|
},
|
|
{
|
|
id: 302,
|
|
customer_id: 12345,
|
|
reg: "BB44556",
|
|
type: 1,
|
|
reference: "Trailer",
|
|
wash_subscription: false,
|
|
xlvask: false,
|
|
addons: {
|
|
list: [],
|
|
},
|
|
},
|
|
];
|
|
|
|
const setupOverviewApi = async (page, options: { failDetailUntilEnabled?: boolean } = {}) => {
|
|
let detailRequests = 0;
|
|
let detailSuccessEnabled = !options.failDetailUntilEnabled;
|
|
|
|
await page.route(apiPathPattern("/superuser/user"), async (route) => {
|
|
const request = route.request();
|
|
|
|
if (request.method() !== "GET") {
|
|
await route.fulfill(json({ data: { message: "OK" } }));
|
|
return;
|
|
}
|
|
|
|
detailRequests += 1;
|
|
if (!detailSuccessEnabled) {
|
|
await route.fulfill(json({ message: "User detail unavailable" }, 500));
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(json({ data: overviewUser }));
|
|
});
|
|
|
|
await page.route(apiPathPattern("/superuser/user/keys"), async (route) => {
|
|
if (route.request().method() === "POST") {
|
|
await route.fulfill(json({ data: { message: "OK" } }));
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
OtherSpecialArrangement: overviewUser.keys.OtherSpecialArrangement,
|
|
OtherVaskeabonnement: overviewUser.keys.OtherVaskeabonnement,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(apiPathPattern("/customer/pricing/fixed"), async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
price: 1234,
|
|
description: "Fixed agreement",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(apiPathPattern("/customer/department/default"), async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
department: 1,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(apiPathPattern("/vehicles"), async (route) => {
|
|
const request = route.request();
|
|
|
|
if (request.method() !== "GET") {
|
|
await route.fulfill(json({ data: { message: "OK" } }));
|
|
return;
|
|
}
|
|
|
|
const url = new URL(request.url());
|
|
const filters = url.searchParams.get("filters") || "";
|
|
const rows = filters.includes("wash_subscription:1")
|
|
? overviewVehicles.filter((vehicle) => vehicle.wash_subscription)
|
|
: overviewVehicles;
|
|
|
|
await route.fulfill(json({ data: rows }));
|
|
});
|
|
|
|
return {
|
|
enableDetailSuccess: () => {
|
|
detailSuccessEnabled = true;
|
|
},
|
|
detailRequests: () => detailRequests,
|
|
};
|
|
};
|
|
|
|
test.describe("Superuser employees list", () => {
|
|
test("uses shared search, pagination reload, and action wheel controls", async ({ page }, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
const searchesSeen: Array<string | null> = [];
|
|
|
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
await seedAuthenticatedState(page, "superuser-users-token");
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: ["superuser", "user"],
|
|
sessionData: {
|
|
group_id: 1,
|
|
},
|
|
});
|
|
|
|
await page.route(
|
|
/https?:\/\/(?:api\.truckwash\.io(?::\d+)?\/users|localhost(?::\d+)?\/api\/users|127\.0\.0\.1(?::\d+)?\/api\/users)(?:\?.*)?$/i,
|
|
async (route) => {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
|
|
if (request.method() !== "GET") {
|
|
await route.fulfill(json({ data: { message: "OK" } }));
|
|
return;
|
|
}
|
|
|
|
const search = url.searchParams.get("search");
|
|
searchesSeen.push(search);
|
|
const normalizedSearch = String(search || "").toLowerCase();
|
|
const rows = normalizedSearch.includes("anna") ? [users[0]] : users;
|
|
|
|
await route.fulfill(json(userEnvelope(rows)));
|
|
}
|
|
);
|
|
|
|
await page.goto("/superuser/users", { waitUntil: "domcontentloaded" });
|
|
|
|
await expect(page.getByTestId("pagination-reload-actions")).toBeVisible();
|
|
await expect(page.getByTestId("pagination-search-input")).toHaveAttribute(
|
|
"placeholder",
|
|
/Søg efter bruger|Search for user/
|
|
);
|
|
await expect(page.getByTestId("superuser-users-table")).toBeVisible();
|
|
await expect(page.getByTestId("superuser-users-row-11")).toContainText("Anna Andersen");
|
|
await expect(page.getByTestId("superuser-users-row-12")).toContainText("Bo Nielsen");
|
|
|
|
await page.getByTestId("pagination-search-input").fill("Anna");
|
|
|
|
await expect(page.getByTestId("superuser-users-row-11")).toContainText("Anna Andersen");
|
|
await expect(page.getByTestId("superuser-users-row-12")).toHaveCount(0);
|
|
|
|
await page.locator('[data-testid="superuser-user-actions-11"] .action-settings-wheel-trigger').click();
|
|
await expect(page.getByTestId("superuser-user-edit-11")).toBeVisible();
|
|
await expect(page.getByTestId("action-settings-wheel-section-customer")).toBeVisible();
|
|
expect(searchesSeen).toContain("Anna");
|
|
});
|
|
});
|
|
|
|
test.describe("Superuser user overview", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await seedAuthenticatedState(page, "superuser-user-overview-token");
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: ["superuser", "user", "get_user", "set_custom_price"],
|
|
pos: {
|
|
vehicles: overviewVehicles,
|
|
},
|
|
sessionData: {
|
|
group_id: 1,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("loads the management hub without raw debug output", async ({ page }) => {
|
|
await setupOverviewApi(page);
|
|
|
|
await page.goto("/superuser/users/11", { waitUntil: "domcontentloaded" });
|
|
|
|
await expect(page.getByTestId("superuser-user-overview-page")).toBeVisible();
|
|
await expect(page.getByTestId("superuser-user-overview-header")).toContainText("Anna Transport");
|
|
await expect(page.getByTestId("superuser-user-tab-overview")).toHaveAttribute("href", "/superuser/users/11");
|
|
await expect(page.getByTestId("superuser-user-tab-pricing")).toHaveAttribute("href", "/superuser/users/11/pricing");
|
|
await expect(page.getByTestId("superuser-user-overview-metric-customer")).toContainText("12345");
|
|
await expect(page.getByTestId("superuser-user-overview-metric-vehicles")).toContainText("2");
|
|
await expect(page.getByTestId("superuser-user-overview-metric-orders")).toContainText("1");
|
|
await expect(page.getByTestId("superuser-user-overview-metric-discounts")).toContainText("1");
|
|
await expect(page.getByTestId("superuser-user-overview-account")).toContainText("anna@example.test");
|
|
await expect(page.getByTestId("superuser-user-overview-economic")).toContainText("billing@example.test");
|
|
await expect(page.getByTestId("superuser-user-overview-access")).toContainText("set_custom_price");
|
|
await expect(page.getByTestId("superuser-user-overview-rules")).toBeVisible();
|
|
await expect(page.getByTestId("superuser-user-special-arrangement-input")).toHaveValue("Night wash agreement");
|
|
await expect(page.getByTestId("superuser-user-wash-subscription-note-input")).toHaveValue(
|
|
"Monthly wash subscription note"
|
|
);
|
|
await expect(page.getByTestId("superuser-user-overview-vehicles")).toContainText("AA11223");
|
|
await expect(page.getByTestId("superuser-user-overview-subscriptions")).toContainText("694");
|
|
await expect(page.getByTestId("superuser-user-overview-link-vehicles")).toHaveAttribute(
|
|
"href",
|
|
"/superuser/users/11/vehicles"
|
|
);
|
|
|
|
const body = page.locator("body");
|
|
await expect(body).not.toContainText("SessionUser:");
|
|
await expect(body).not.toContainText("economicData:");
|
|
});
|
|
|
|
test("shows a retryable error when the detail request fails", async ({ page }) => {
|
|
const overviewApi = await setupOverviewApi(page, { failDetailUntilEnabled: true });
|
|
|
|
await page.goto("/superuser/users/11", { waitUntil: "domcontentloaded" });
|
|
|
|
await expect(page.getByTestId("superuser-user-overview-error")).toContainText("User detail unavailable");
|
|
expect(overviewApi.detailRequests()).toBeGreaterThan(0);
|
|
overviewApi.enableDetailSuccess();
|
|
await page.getByTestId("superuser-user-overview-retry").click();
|
|
await expect(page.getByTestId("superuser-user-overview-header")).toContainText("Anna Transport");
|
|
await expect(page.getByTestId("superuser-user-overview-error")).toHaveCount(0);
|
|
});
|
|
});
|