Resolve recommended-profile Critical and High findings, update vulnerable dependencies, restore invoice queue E2E authentication setup, and clear the remaining frontend Qodana findings.
828 lines
30 KiB
TypeScript
828 lines
30 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { mockApi, seedAuthenticatedState } from "./support/network.js";
|
|
import { isCompactProject, isDesktopProject } from "./support/projects";
|
|
|
|
const adminPermissions = [
|
|
"admin",
|
|
"list_department_daily_reports",
|
|
"department_access_1",
|
|
"department_access_2",
|
|
"department_access_3",
|
|
"department_access_4",
|
|
"department_access_5",
|
|
];
|
|
|
|
const defaultDepartments = [
|
|
{ id: 1, name: "Visible North", visible: true, order_priority: 20 },
|
|
{ id: 2, name: "Hidden South", visible: false },
|
|
{ id: 3, name: "Legacy East", order_priority: 10 },
|
|
{ id: 4, name: "Numeric Hidden", visible: 0 },
|
|
{ id: 5, name: "Archived West", visible: true, archived: true },
|
|
{ id: 6, name: "Unassigned West", visible: true, order_priority: 5 },
|
|
];
|
|
|
|
const json = (body: unknown, status = 200) => ({
|
|
status,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const _formatLocalDateTime = (value: Date) => {
|
|
const pad = (segment: number) => String(segment).padStart(2, "0");
|
|
|
|
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(value.getHours())}:${pad(
|
|
value.getMinutes()
|
|
)}:${pad(value.getSeconds())}`;
|
|
};
|
|
|
|
const buildOverviewPayload = () => ({
|
|
data: {
|
|
department_ids: [1],
|
|
date: "2026-04-08",
|
|
date_to: "2026-04-08",
|
|
metrics: {
|
|
bookings: { state: "ready", value: 4, out_of: 6, message: null },
|
|
complaints: {
|
|
state: "unavailable",
|
|
value: null,
|
|
out_of: null,
|
|
message: "Complaints data is not available yet.",
|
|
},
|
|
night_washes: { state: "ready", value: 1, out_of: null, message: null },
|
|
revenue: { state: "ready", value: 2400, out_of: null, message: null },
|
|
washes: { state: "ready", value: 8, out_of: null, message: null },
|
|
products_sold: { state: "ready", value: 19, out_of: null, message: null },
|
|
transactions: { state: "ready", value: 11, out_of: null, message: null },
|
|
water_usage: { state: "ready", value: 32, out_of: null, message: null },
|
|
overtime: { state: "ready", value: 1.5, out_of: null, message: null },
|
|
},
|
|
products: [],
|
|
},
|
|
});
|
|
|
|
async function mockAdminDepartmentDependencies(
|
|
page,
|
|
options: {
|
|
departmentDelayMs?: number;
|
|
departments?: Array<Record<string, unknown>>;
|
|
permissions?: string[];
|
|
sessionData?: Record<string, unknown>;
|
|
} = {}
|
|
) {
|
|
await seedAuthenticatedState(page);
|
|
await mockApi(page, {
|
|
authenticated: true,
|
|
permissions: options.permissions || adminPermissions,
|
|
sessionData: options.sessionData,
|
|
});
|
|
|
|
await page.route(/\/departments(\?.*)?$/i, async (route) => {
|
|
if (options.departmentDelayMs) {
|
|
await new Promise((resolve) => setTimeout(resolve, options.departmentDelayMs));
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: options.departments || defaultDepartments,
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/departments\/weather(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(json({ data: [] }));
|
|
});
|
|
|
|
await page.route(/\/departments\/daily-reports\/get(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
id: 10,
|
|
department_id: 1,
|
|
water_usage: 32,
|
|
notes: "",
|
|
filled_by: 1,
|
|
created_at: "2026-04-08 00:00:00",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/departments\/daily-reports(?:\/overview)?(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
if (url.pathname.endsWith("/departments/daily-reports/overview")) {
|
|
await route.fulfill(json(buildOverviewPayload()));
|
|
return;
|
|
}
|
|
|
|
await route.fulfill(json({ data: [] }));
|
|
});
|
|
}
|
|
|
|
async function expandDepartmentFiltersOnMobile(page, testInfo) {
|
|
if (!isCompactProject(testInfo)) {
|
|
return;
|
|
}
|
|
|
|
if (await page.getByTestId("daily-report-department-controls").count()) {
|
|
return;
|
|
}
|
|
|
|
await page.getByTestId("daily-report-mobile-departments-toggle").click();
|
|
await expect(page.getByTestId("daily-report-department-controls")).toBeVisible();
|
|
}
|
|
|
|
async function expectDailyReportPageVisible(page) {
|
|
await expect(page.getByTestId("daily-report-page")).toBeVisible({ timeout: 20_000 });
|
|
}
|
|
|
|
test.describe("Admin department visibility", () => {
|
|
test("hides placeholder departments from desktop admin navigation pickers", async ({ page }, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "department_access_5"],
|
|
departments: [
|
|
{ id: 1, name: "Visible North", visible: true, order_priority: 20 },
|
|
{ id: 2, name: "Ingen data", visible: true, order_priority: 1 },
|
|
{ id: 3, name: " ", visible: true, order_priority: 2 },
|
|
{ id: 4, name: "Visible East", visible: true, order_priority: 10 },
|
|
],
|
|
});
|
|
|
|
await page.goto("/admin");
|
|
|
|
await expectDailyReportPageVisible(page);
|
|
|
|
const desktopNavigation = page.getByTestId("desktop-buefy-navigation");
|
|
const desktopDepartmentSelect = page.getByTestId("desktop-header-department-select");
|
|
|
|
await expect(desktopNavigation).toContainText("Visible North");
|
|
await expect(desktopNavigation).toContainText("Visible East");
|
|
await expect(desktopNavigation).not.toContainText(/Ingen data/i);
|
|
const desktopNavigationText = await desktopNavigation.innerText();
|
|
expect(desktopNavigationText.indexOf("Visible East")).toBeLessThan(desktopNavigationText.indexOf("Visible North"));
|
|
|
|
await expect(desktopDepartmentSelect.locator("option")).toHaveCount(3);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Visible North" })).toHaveCount(1);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Visible East" })).toHaveCount(1);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Ingen data" })).toHaveCount(0);
|
|
const desktopDepartmentOptionTexts = await desktopDepartmentSelect
|
|
.locator("option")
|
|
.evaluateAll((options) => options.map((option) => option.textContent?.trim() || ""));
|
|
expect(desktopDepartmentOptionTexts.slice(1)).toEqual(["Visible East", "Visible North"]);
|
|
});
|
|
|
|
test("hides invisible departments from both the header selector and the daily report department controls", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await mockAdminDepartmentDependencies(page);
|
|
|
|
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-04-08&dateTo=2026-04-08");
|
|
|
|
await expectDailyReportPageVisible(page);
|
|
await expandDepartmentFiltersOnMobile(page, testInfo);
|
|
|
|
const departmentControls = page.getByTestId("daily-report-department-controls");
|
|
await expect(departmentControls.getByRole("button", { name: "Visible North" })).toBeVisible();
|
|
await expect(departmentControls.getByRole("button", { name: "Legacy East" })).toBeVisible();
|
|
await expect(departmentControls.getByRole("button", { name: "Hidden South" })).toHaveCount(0);
|
|
await expect(departmentControls.getByRole("button", { name: "Numeric Hidden" })).toHaveCount(0);
|
|
await expect(departmentControls.getByRole("button", { name: "Archived West" })).toHaveCount(0);
|
|
await expect(departmentControls.getByRole("button", { name: "Unassigned West" })).toHaveCount(0);
|
|
const departmentControlTexts = await departmentControls
|
|
.getByRole("button")
|
|
.evaluateAll((buttons) => buttons.map((button) => button.textContent?.trim() || ""));
|
|
expect(departmentControlTexts.filter((text) => ["Visible North", "Legacy East"].includes(text))).toEqual([
|
|
"Legacy East",
|
|
"Visible North",
|
|
]);
|
|
|
|
if (isDesktopProject(test.info())) {
|
|
const desktopDepartmentSelect = page.getByTestId("desktop-header-department-select");
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Visible North" })).toHaveCount(1);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Legacy East" })).toHaveCount(1);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Hidden South" })).toHaveCount(0);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Numeric Hidden" })).toHaveCount(0);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Archived West" })).toHaveCount(0);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Unassigned West" })).toHaveCount(0);
|
|
}
|
|
});
|
|
|
|
test("does not expose unassigned departments to superuser admin department pickers", async ({ page }, testInfo) => {
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [
|
|
"superuser",
|
|
"admin",
|
|
"list_department_daily_reports",
|
|
"department_access_1",
|
|
"department_access_3",
|
|
],
|
|
departments: defaultDepartments,
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-04-08&dateTo=2026-04-08");
|
|
|
|
await expectDailyReportPageVisible(page);
|
|
await expandDepartmentFiltersOnMobile(page, testInfo);
|
|
|
|
const departmentControls = page.getByTestId("daily-report-department-controls");
|
|
await expect(departmentControls.getByRole("button", { name: "Legacy East" })).toBeVisible();
|
|
await expect(departmentControls.getByRole("button", { name: "Visible North" })).toBeVisible();
|
|
await expect(departmentControls.getByRole("button", { name: "Unassigned West" })).toHaveCount(0);
|
|
|
|
if (isDesktopProject(test.info())) {
|
|
const desktopNavigation = page.getByTestId("desktop-buefy-navigation");
|
|
const desktopDepartmentSelect = page.getByTestId("desktop-header-department-select");
|
|
|
|
await expect(desktopNavigation).not.toContainText("Unassigned West");
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Legacy East" })).toHaveCount(1);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Visible North" })).toHaveCount(1);
|
|
await expect(desktopDepartmentSelect.locator("option", { hasText: "Unassigned West" })).toHaveCount(0);
|
|
}
|
|
});
|
|
|
|
test("does not expose unassigned departments in the overview list tab", async ({ page }, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [
|
|
"superuser",
|
|
"admin",
|
|
"list_department_daily_reports",
|
|
"department_access_1",
|
|
"department_access_3",
|
|
],
|
|
departments: defaultDepartments,
|
|
});
|
|
|
|
await page.goto("/admin");
|
|
await expectDailyReportPageVisible(page);
|
|
await page.getByText("Liste", { exact: true }).click();
|
|
|
|
const overviewList = page.getByTestId("admin-overview-list");
|
|
await expect(overviewList.getByTestId("admin-overview-list-department-3")).toBeVisible();
|
|
await expect(overviewList.getByTestId("admin-overview-list-department-1")).toBeVisible();
|
|
await expect(overviewList.getByTestId("admin-overview-list-department-6")).toHaveCount(0);
|
|
await expect(overviewList.getByTestId("admin-overview-list-department-2")).toHaveCount(0);
|
|
await expect(overviewList.getByTestId("admin-overview-list-department-5")).toHaveCount(0);
|
|
});
|
|
|
|
test("renders department controls after a delayed departments response", async ({ page }, testInfo) => {
|
|
await mockAdminDepartmentDependencies(page, { departmentDelayMs: 400 });
|
|
|
|
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-04-08&dateTo=2026-04-08");
|
|
await expectDailyReportPageVisible(page);
|
|
await expandDepartmentFiltersOnMobile(page, testInfo);
|
|
|
|
const departmentControls = page.getByTestId("daily-report-department-controls");
|
|
await expect(departmentControls.getByRole("button", { name: "Visible North" })).toBeVisible();
|
|
await expect(departmentControls.getByRole("button", { name: "Legacy East" })).toBeVisible();
|
|
});
|
|
|
|
test("shows the draft transactions navigation item in the desktop buefy menu when a draft customer is configured", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "add_order", "list_orders"],
|
|
departments: [
|
|
{ id: 1, name: "Visible North", visible: true },
|
|
{ id: 2, name: "Visible South", visible: true },
|
|
],
|
|
sessionData: {
|
|
runtime_config: {
|
|
economic: {
|
|
transaction_draft_customer_number: 6001,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const desktopNavigation = page.getByTestId("desktop-buefy-navigation");
|
|
await expect(desktopNavigation).toContainText("Kladder");
|
|
|
|
await desktopNavigation.getByText("Kladder", { exact: true }).click();
|
|
await expect(page).toHaveURL(/\/admin\/1\/modules\/pos\/drafts$/);
|
|
await expect(page.getByTestId("department-pos-drafts-page")).toBeVisible();
|
|
});
|
|
|
|
test("shows a department-scoped draft count badge in the desktop buefy menu", async ({ page }, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "add_order", "list_orders"],
|
|
sessionData: {
|
|
runtime_config: {
|
|
economic: {
|
|
transaction_draft_customer_number: 6001,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
await page.route(/\/orders(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const filters = url.searchParams.get("filters") || "";
|
|
const isDraftRequest = filters.includes("customer_id:6001");
|
|
const isDepartmentOne = filters.includes("department_id:1");
|
|
const isDepartmentTwo = filters.includes("department_id:2");
|
|
const total = isDraftRequest ? (isDepartmentOne ? 3 : isDepartmentTwo ? 0 : 0) : 0;
|
|
const perPage = Number(url.searchParams.get("limit") || "100");
|
|
const drafts =
|
|
isDraftRequest && isDepartmentOne
|
|
? [
|
|
{ id: 501, department_id: 1, customer_id: 6001, cashier_id: 5 },
|
|
{ id: 502, department_id: 1, customer_id: 6001, cashier_id: 5 },
|
|
{ id: 503, department_id: 1, customer_id: 6001, cashier_id: 5 },
|
|
]
|
|
: [];
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: drafts.slice(0, Math.min(perPage, drafts.length)),
|
|
meta: {
|
|
pagination: {
|
|
page: 1,
|
|
per_page: perPage,
|
|
total,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const draftsLabel = page.getByTestId("desktop-buefy-nav-drafts-label");
|
|
const draftsBadge = page.getByTestId("desktop-buefy-nav-drafts-badge");
|
|
|
|
await expect(draftsLabel).toContainText("Kladder");
|
|
await expect(draftsBadge).toHaveText("3");
|
|
await draftsBadge.hover();
|
|
await expect(page.getByTestId("desktop-buefy-nav-drafts-badge-tooltip")).toContainText("Kladder");
|
|
|
|
await page.goto("/admin/2/modules/daily-report");
|
|
|
|
await expect(draftsLabel).toContainText("Kladder");
|
|
await expect(draftsBadge).toHaveCount(0);
|
|
|
|
await draftsLabel.click();
|
|
await expect(page).toHaveURL(/\/admin\/2\/modules\/pos\/drafts$/);
|
|
await expect(page.getByTestId("department-pos-drafts-page")).toBeVisible();
|
|
});
|
|
|
|
test("shows a light grey loading indicator while desktop buefy draft counts are fetching", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
const requestedFilters: string[] = [];
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "add_order", "list_orders"],
|
|
sessionData: {
|
|
runtime_config: {
|
|
economic: {
|
|
transaction_draft_customer_number: 6001,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
await page.route(/\/orders(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const filters = url.searchParams.get("filters") || "";
|
|
requestedFilters.push(filters);
|
|
const isDraftRequest = filters.includes("customer_id:6001") && filters.includes("department_id:1");
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: isDraftRequest ? [{ id: 501, department_id: 1, customer_id: 6001, cashier_id: 5 }] : [],
|
|
meta: {
|
|
pagination: {
|
|
page: 1,
|
|
per_page: Number(url.searchParams.get("limit") || "100"),
|
|
total: isDraftRequest ? 1 : 0,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const draftsLabel = page.getByTestId("desktop-buefy-nav-drafts-label");
|
|
const loadingBadge = page.getByTestId("desktop-buefy-nav-drafts-badge-loading");
|
|
const draftsBadge = page.getByTestId("desktop-buefy-nav-drafts-badge");
|
|
|
|
await expect(draftsLabel).toContainText("Kladder");
|
|
await expect(loadingBadge).toBeVisible();
|
|
await expect(loadingBadge).toHaveCSS("background-color", "rgb(229, 231, 235)");
|
|
await expect(draftsBadge).toHaveCount(0);
|
|
|
|
const loadingBadgeStyles = await loadingBadge.evaluate((element) => {
|
|
const styles = getComputedStyle(element);
|
|
return {
|
|
backgroundImage: styles.backgroundImage,
|
|
};
|
|
});
|
|
expect(loadingBadgeStyles.backgroundImage).not.toBe("none");
|
|
|
|
await expect(loadingBadge).toHaveCount(0);
|
|
await expect(draftsBadge).toHaveText("1");
|
|
expect(
|
|
requestedFilters.some((filters) => filters.includes("department_id:1") && filters.includes("customer_id:6001"))
|
|
).toBe(true);
|
|
});
|
|
|
|
test("fetches desktop buefy bookings counts from the department-wide navigation request", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
const requestedDepartments: string[] = [];
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const department = url.searchParams.get("department") || "";
|
|
requestedDepartments.push(department);
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
past: 0,
|
|
current: department === "1" ? 1 : 0,
|
|
future: 0,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const bookingsLabel = page.getByTestId("desktop-buefy-nav-bookings-label");
|
|
const currentBookingsBadge = page.getByTestId("desktop-buefy-nav-bookings-badge");
|
|
|
|
await expect(bookingsLabel).toContainText("Bookinger");
|
|
await expect(currentBookingsBadge).toHaveText("1");
|
|
await currentBookingsBadge.hover();
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-badge-tooltip")).toContainText("I dag");
|
|
expect(requestedDepartments).toContain("1");
|
|
});
|
|
|
|
test("resolves the desktop buefy bookings loader even when the count request is slower than the poll interval", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const department = url.searchParams.get("department") || "";
|
|
const isDepartmentOneNavigationCountRequest = department === "1";
|
|
|
|
if (isDepartmentOneNavigationCountRequest) {
|
|
await new Promise((resolve) => setTimeout(resolve, 5500));
|
|
}
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
past: 0,
|
|
current: isDepartmentOneNavigationCountRequest ? 2 : 0,
|
|
future: 0,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const loadingBadge = page.getByTestId("desktop-buefy-nav-bookings-badge-loading");
|
|
const currentBookingsBadge = page.getByTestId("desktop-buefy-nav-bookings-badge");
|
|
|
|
await expect(loadingBadge).toBeVisible();
|
|
await expect(currentBookingsBadge).toHaveCount(0);
|
|
await expect(currentBookingsBadge).toHaveText("2", { timeout: 8000 });
|
|
await expect(loadingBadge).toHaveCount(0);
|
|
});
|
|
|
|
test("shows the Bookinger-style loader on the self-serve module while its state is fetching", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/admin\/bookings\/department\/count(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
data: {
|
|
message: 0,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/departments\/self-serve\/enabled(\?.*)?$/i, async (route) => {
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
data: {
|
|
enabled: true,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1");
|
|
|
|
const loadingBadge = page.getByTestId("department-module-self-serve-state-loading");
|
|
await expect(loadingBadge).toBeVisible();
|
|
await expect(loadingBadge).toHaveCSS("background-color", "rgb(229, 231, 235)");
|
|
|
|
const loadingBadgeStyles = await loadingBadge.evaluate((element) => {
|
|
const styles = getComputedStyle(element);
|
|
return {
|
|
backgroundImage: styles.backgroundImage,
|
|
};
|
|
});
|
|
expect(loadingBadgeStyles.backgroundImage).not.toBe("none");
|
|
|
|
await expect(loadingBadge).toHaveCount(0);
|
|
});
|
|
|
|
test("right-aligns the self-serve module switch in the card header", async ({ page }, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/admin\/bookings\/department\/count(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
data: {
|
|
message: 0,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/departments\/self-serve\/enabled(\?.*)?$/i, async (route) => {
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
data: {
|
|
enabled: true,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1");
|
|
|
|
const selfServeCard = page
|
|
.locator(".department-module-card")
|
|
.filter({ hasText: /Selvvask|Self-serve/i })
|
|
.first();
|
|
await expect(selfServeCard).toBeVisible();
|
|
await expect(selfServeCard.locator(".department-module-card__switch")).toBeVisible();
|
|
|
|
const layout = await selfServeCard.evaluate((card) => {
|
|
const header = card.querySelector(".card-header");
|
|
const actions = card.querySelector(".department-module-card__actions");
|
|
const switchButton = card.querySelector(".department-module-card__switch");
|
|
|
|
if (
|
|
!(header instanceof HTMLElement) ||
|
|
!(actions instanceof HTMLElement) ||
|
|
!(switchButton instanceof HTMLElement)
|
|
) {
|
|
throw new Error("Expected self-serve module header elements to be present");
|
|
}
|
|
|
|
const headerBox = header.getBoundingClientRect();
|
|
const actionsBox = actions.getBoundingClientRect();
|
|
const switchBox = switchButton.getBoundingClientRect();
|
|
|
|
return {
|
|
actionsRightGap: headerBox.right - actionsBox.right,
|
|
switchRightGap: headerBox.right - switchBox.right,
|
|
};
|
|
});
|
|
|
|
expect(Math.abs(layout.actionsRightGap)).toBeLessThanOrEqual(1);
|
|
expect(layout.switchRightGap).toBeGreaterThanOrEqual(8);
|
|
expect(layout.switchRightGap).toBeLessThanOrEqual(20);
|
|
});
|
|
|
|
test("shows only the current order-booking badge inline and exposes past and future in a tooltip", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
const requestedDepartments: string[] = [];
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const department = url.searchParams.get("department") || "";
|
|
requestedDepartments.push(department);
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data:
|
|
department === "1"
|
|
? {
|
|
past: 2,
|
|
current: 3,
|
|
future: 1,
|
|
}
|
|
: {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const bookingsLabel = page.getByTestId("desktop-buefy-nav-bookings-label");
|
|
const overdueBookingsBadge = page.getByTestId("desktop-buefy-nav-bookings-badge-overdue");
|
|
const currentBookingsBadge = page.getByTestId("desktop-buefy-nav-bookings-badge");
|
|
const futureBookingsBadge = page.getByTestId("desktop-buefy-nav-bookings-badge-future");
|
|
const bookingsTooltip = page.getByTestId("desktop-buefy-nav-bookings-tooltip");
|
|
|
|
await expect(bookingsLabel).toContainText("Bookinger");
|
|
await expect(currentBookingsBadge).toHaveText("3");
|
|
await expect(overdueBookingsBadge).toHaveCount(0);
|
|
await expect(futureBookingsBadge).toHaveCount(0);
|
|
expect(requestedDepartments).toContain("1");
|
|
|
|
await currentBookingsBadge.hover();
|
|
await expect(bookingsTooltip).toBeVisible();
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-tooltip-overdue")).toContainText("2");
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-tooltip-future")).toContainText("1");
|
|
|
|
await page.goto("/admin/2/modules/daily-report");
|
|
|
|
await expect(bookingsLabel).toContainText("Bookinger");
|
|
await expect(overdueBookingsBadge).toHaveCount(0);
|
|
await expect(currentBookingsBadge).toHaveCount(0);
|
|
await expect(futureBookingsBadge).toHaveCount(0);
|
|
|
|
await bookingsLabel.click();
|
|
await expect(page).toHaveURL(/\/admin\/2\/modules\/bookings$/);
|
|
});
|
|
|
|
test("keeps the bookings navigation row full width when only the fallback tooltip is available", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const department = url.searchParams.get("department") || "";
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data:
|
|
department === "1"
|
|
? {
|
|
past: 2,
|
|
current: 0,
|
|
future: 1,
|
|
}
|
|
: {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
const bookingsLabel = page.getByTestId("desktop-buefy-nav-bookings-label");
|
|
await expect(bookingsLabel).toContainText("Bookinger");
|
|
|
|
const bookingsLabelBox = await bookingsLabel.boundingBox();
|
|
expect(bookingsLabelBox?.width ?? 0).toBeGreaterThan(250);
|
|
|
|
await bookingsLabel.hover();
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-tooltip-overdue")).toContainText("2");
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-tooltip-future")).toContainText("1");
|
|
});
|
|
|
|
test("uses the cached bookings counts endpoint for desktop buefy menu badges", async ({ page }, testInfo) => {
|
|
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
|
|
|
const countRequests: string[] = [];
|
|
const listRequests: string[] = [];
|
|
|
|
await mockAdminDepartmentDependencies(page, {
|
|
permissions: [...adminPermissions, "list_bookings"],
|
|
});
|
|
|
|
await page.route(/\/order-bookings\/counts(\?.*)?$/i, async (route) => {
|
|
const url = new URL(route.request().url());
|
|
countRequests.push(url.searchParams.get("department") || "");
|
|
|
|
await route.fulfill(
|
|
json({
|
|
data: {
|
|
past: 1,
|
|
current: 2,
|
|
future: 3,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.route(/\/order-bookings(\?.*)?$/i, async (route) => {
|
|
listRequests.push(route.request().url());
|
|
await route.fulfill(
|
|
json({
|
|
success: true,
|
|
data: [],
|
|
meta: {
|
|
pagination: {
|
|
total: 0,
|
|
},
|
|
},
|
|
})
|
|
);
|
|
});
|
|
|
|
await page.goto("/admin/1/modules/daily-report");
|
|
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-badge-overdue")).toHaveCount(0);
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-badge")).toHaveText("2");
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-badge-future")).toHaveCount(0);
|
|
|
|
await page.getByTestId("desktop-buefy-nav-bookings-badge").hover();
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-tooltip-overdue")).toContainText("1");
|
|
await expect(page.getByTestId("desktop-buefy-nav-bookings-tooltip-future")).toContainText("3");
|
|
|
|
expect(countRequests).toContain("1");
|
|
expect(listRequests).toEqual([]);
|
|
});
|
|
});
|