Files
pleno-vue/tests/e2e/admin-pos-orders.spec.ts
T
Jeppe Bundgaard 6c4b9cb599 Add and improve e2e tests for Edge Gateway flows and configure pre-commit hooks:
- Introduced live smoke tests for Edge Gateways, verifying gateway routes and destructive-action prevention.
- Added e2e scenarios for deep-link navigation, unavailable gateway recovery, token rotation, and background page refresh.
- Refactored test helpers for streamlined functional validation in gateway scenarios.
- Updated `EdgeGatewayTerminal.vue` with test IDs for enhanced testability.
- Added and configured Husky pre-commit hooks for automated test file formatting and validation.
2026-04-16 10:52:56 +02:00

1865 lines
79 KiB
TypeScript

import { test, expect, Page } from "@playwright/test";
import { createPosFixture, mockApi, seedAuthenticatedState } from "./support/network.js";
const POS_PERMISSIONS = [
"admin",
"department_access_12",
"delete_order",
"edit_order",
"edit_order_items",
"get_user",
"list_customer_notes",
"list_customer_attributes",
"get_custom_prices_other",
];
async function primeOperatorSession(page: Page, token = "pos-orders-token", _permissions = POS_PERMISSIONS) {
await seedAuthenticatedState(page, token);
const sessionRequest = page.waitForResponse((response) => {
return response.request().method() === "GET" && response.url().includes("/auth/session");
});
await page.goto("/login");
await sessionRequest;
}
async function createDisposableOrder(page: Page) {
const uniqueSuffix = Date.now().toString().slice(-6);
const reg1 = `PW${uniqueSuffix}`;
const reference = `E2E-${uniqueSuffix}`;
await page.goto("/admin/12/modules/pos?step=1");
await expect(page.getByTestId("pos-step-1")).toBeVisible();
await page.locator("#reg_1").fill(reg1);
await page.locator("#reference").fill(reference);
await page.locator("#pos_select_customer_input").fill("12345679");
await expect(page.locator(".customer-drop-down-select").first()).toBeVisible();
await page.locator(".customer-drop-down-select").first().click();
await expect(page.locator(".field.has-addons input[disabled]").last()).toHaveValue(
/\(TEST\) Pleno Vognmandsforretning/
);
await page.getByTestId("pos-step-1").getByTestId("pos-next-step").click();
await expect(page).toHaveURL(/\/admin\/12\/modules\/pos\?id=\d+&customer_id=12345679&step=2/);
const url = new URL(page.url());
const orderId = Number(url.searchParams.get("id"));
expect(orderId).toBeGreaterThan(0);
return {
orderId,
reg1,
reference,
};
}
async function selectStepOneCustomer(
page: Page,
customerNumber: number | string,
expectedName: string | RegExp = /\(TEST\) Pleno Vognmandsforretning/
) {
await page.locator("#pos_select_customer_input").fill(String(customerNumber));
await expect(page.locator(".customer-drop-down-select").first()).toBeVisible();
await page.locator(".customer-drop-down-select").first().click();
await expect(page.locator(".field.has-addons input[disabled]").last()).toHaveValue(expectedName);
}
async function clickVisibleTestId(page: Page, testId: string) {
const locator = page.getByTestId(testId);
const count = await locator.count();
for (let index = 0; index < count; index += 1) {
const candidate = locator.nth(index);
if (await candidate.isVisible()) {
await candidate.click();
return;
}
}
throw new Error(`No visible element found for test id "${testId}"`);
}
async function waitForSwalToClose(page: Page) {
await page.waitForFunction(() => {
const popup = document.querySelector(".swal2-popup");
if (!popup) {
return true;
}
return (
popup.classList.contains("swal2-hide") ||
popup.getAttribute("aria-hidden") === "true" ||
window.getComputedStyle(popup).display === "none"
);
});
}
async function openOrderSettings(page: Page, orderId: number) {
await page.goto(`/admin/12/modules/pos/orders/${orderId}`);
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await clickVisibleTestId(page, "pos-order-tab-settings");
await expect(page.getByTestId("pos-order-panel-settings")).toBeVisible();
}
async function reloadOrderSettings(page: Page, orderId: number) {
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await clickVisibleTestId(page, "pos-order-tab-settings");
await expect(page.getByTestId("pos-order-panel-settings")).toBeVisible();
}
function getOrderSettingsField(page: Page, field: string) {
return page.locator(`[data-testid="pos-order-settings-field-${field}"]:visible`).first();
}
function getOrderSettingsEditButton(page: Page, field: string) {
return page.locator(`[data-testid="pos-order-settings-edit-${field}"]:visible`).first();
}
function getOrderDetailField(page: Page, field: string) {
return page.locator(`[data-testid="pos-order-detail-${field}"]:visible`).first();
}
function getOrderDetailInput(page: Page, field: string) {
return getOrderDetailField(page, field).locator("input, textarea").first();
}
async function openOrderDetailsTab(page: Page) {
await clickVisibleTestId(page, "pos-order-tab-details");
await expect(page.getByTestId("pos-order-panel-details")).toBeVisible();
}
async function submitOrderSettingModal(page: Page, field: string, value: string, type: "input" | "select" = "input") {
await getOrderSettingsEditButton(page, field).click();
const popup = page.locator(".swal2-popup");
await expect(popup).toBeVisible();
const control = popup.locator(`#${field}`).first();
await expect(control).toBeVisible();
if (type === "select") {
await control.selectOption(value);
} else {
await control.fill(value);
}
await popup.locator(".swal2-confirm").click();
await expect(popup).toBeHidden({ timeout: 10000 });
}
async function createInvoiceCollectionFromPicker(page: Page, closedAt: string) {
const popup = page.locator(".swal2-popup");
await expect(popup).toBeVisible();
await expect(popup.locator(".tabs")).toBeVisible({ timeout: 10000 });
const createTab = popup.locator(".tabs li a").last();
await expect(createTab).toBeVisible({ timeout: 10000 });
await createTab.click();
const dateInput = popup.locator('input[type="date"]').first();
await expect(dateInput).toBeVisible({ timeout: 10000 });
await dateInput.fill(closedAt);
const createRequest = page.waitForRequest((request) => {
return request.method() === "POST" && request.url().includes("/collected-invoices");
});
const createResponse = page.waitForResponse((response) => {
return response.request().method() === "POST" && response.url().includes("/collected-invoices");
});
const createButton = popup.locator("button.button.is-primary").first();
await expect(createButton).toBeEnabled({ timeout: 10000 });
await createButton.click();
await createRequest;
await createResponse;
}
async function waitForOrderInvoiceCollectionSuccess(page: Page) {
const successPopup = page.locator(".swal2-popup");
await expect(successPopup).toContainText(/Faktura samling ændret/i, { timeout: 10000 });
await expect(successPopup).toBeHidden({ timeout: 4000 });
}
async function changeOrderCustomer(page: Page, customerNumber: string, closedAt: string) {
await getOrderSettingsEditButton(page, "customer_id").click();
const customerPopup = page.locator(".swal2-popup");
await expect(customerPopup).toBeVisible();
await customerPopup.locator("#pos_select_customer_input").fill(customerNumber);
const customerResult = customerPopup.locator(".customer-drop-down-select").first();
await expect(customerResult).toBeVisible();
await customerResult.click();
await expect(customerPopup.locator("#pos_select_customer_input")).toHaveCount(0, { timeout: 10000 });
await createInvoiceCollectionFromPicker(page, closedAt);
await waitForOrderInvoiceCollectionSuccess(page);
}
async function changeOrderInvoiceCollection(page: Page, closedAt: string) {
await getOrderSettingsEditButton(page, "invoice_collection_id").click();
await createInvoiceCollectionFromPicker(page, closedAt);
await waitForOrderInvoiceCollectionSuccess(page);
}
async function openOrderDetail(page: Page, orderId = 54518) {
await page.goto(`/admin/12/modules/pos/orders/${orderId}`);
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
}
async function openOrderAttachments(page: Page, orderId = 54518) {
await openOrderDetail(page, orderId);
await clickVisibleTestId(page, "pos-order-tab-attachments");
await expect(page.getByTestId("pos-order-panel-attachments")).toBeVisible();
}
async function openAddAttachmentCard(page: Page) {
const addAttachmentCard = getVisibleTestId(page, "pos-order-attachments-add-card");
const attachWashCertificateButton = page.getByTestId("pos-order-attachments-attach-wash-certificate");
await expect(addAttachmentCard).toBeVisible();
if ((await attachWashCertificateButton.count()) === 0) {
await addAttachmentCard.locator(".card-header").click();
}
await expect(attachWashCertificateButton).toBeVisible();
}
async function openAddItemsPanel(page: Page) {
const addItemsPanel = page.locator('[data-testid="pos-order-add-items-panel"]:visible').first();
const addItemsBackButton = page.locator('[data-testid="pos-order-add-items-back"]:visible').first();
await page.getByTestId("pos-order-add-item").click();
await expect(addItemsPanel).toBeVisible();
await expect(addItemsBackButton).toBeVisible();
}
function getVisibleTestId(page: Page, testId: string) {
return page.locator(`[data-testid="${testId}"]:visible`).first();
}
async function expectOrderTotal(page: Page, total: number) {
await expect(page.getByTestId("pos-order-total").first()).toHaveText(`${total} DKK`);
}
async function openOrderItemEditModal(page: Page, orderItemId: number) {
await page.getByTestId(`pos-order-item-edit-${orderItemId}`).click();
await expect(page.getByTestId("pos-order-item-edit-modal")).toBeVisible();
}
async function waitForOrderItemMutation(page: Page, method: "POST" | "PUT" | "DELETE", match: RegExp | string) {
return page.waitForRequest((request) => {
if (request.method() !== method || !request.url().includes("/order/items")) {
return false;
}
return typeof match === "string" ? request.url().includes(match) : match.test(request.url());
});
}
async function waitForOrderMutation(
page: Page,
method: "PUT" | "POST" | "DELETE",
endpoint: "/order" | "/orders",
predicate: (body: Record<string, unknown>) => boolean
) {
return page.waitForRequest((request) => {
if (request.method() !== method || !request.url().includes(endpoint)) {
return false;
}
const body = (request.postDataJSON?.() || {}) as Record<string, unknown>;
return predicate(body);
});
}
function createRequiredWarningsPosFixture() {
const customerNumber = 12345679;
const baseFixture = createPosFixture();
return createPosFixture({
customerAttributesByNumber: {
[customerNumber]: [
{
id: 11,
customer_number: customerNumber,
attribute: "requiresReferenceNumber",
},
{
id: 12,
customer_number: customerNumber,
attribute: "usePONumbers",
},
],
},
ordersById: {
54518: {
...baseFixture.ordersById[54518],
reference: "",
po: "",
},
},
});
}
function createCustomerConflictPosFixture() {
const baseFixture = createPosFixture();
const defaultCustomer = baseFixture.customersByNumber[12345679];
const vehicleCustomerNumber = 22334455;
const vehicleCustomerName = "(TEST) Conflict Fleet";
return createPosFixture({
customersByNumber: {
[vehicleCustomerNumber]: {
...defaultCustomer,
id: 3,
customerNumber: vehicleCustomerNumber,
economic_customer: vehicleCustomerNumber,
name: vehicleCustomerName,
email: "conflict@example.com",
mobilePhone: "22334455",
},
},
vehicles: [
...baseFixture.vehicles.filter((vehicle) => vehicle.reg !== "CONFLICT1"),
{
id: 7701,
reg: "CONFLICT1",
customer_id: vehicleCustomerNumber,
customer_name: vehicleCustomerName,
type: 53,
status: "verified",
barred: false,
wash_subscription: false,
addons: {
enabled: 0,
available: 0,
list: [],
},
reference: "CONFLICT-VEHICLE-REF",
last_order_id: null,
},
],
});
}
test.describe("Admin POS Orders - desktop settings", () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only order settings coverage");
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: createPosFixture(),
});
await primeOperatorSession(page);
});
test("loads order detail metadata without order_id prop/setup errors on first render", async ({ page }) => {
const consoleMessages: string[] = [];
const pageErrors: string[] = [];
page.on("console", (message) => {
if (message.type() === "warning" || message.type() === "error") {
consoleMessages.push(message.text());
}
});
page.on("pageerror", (error) => {
pageErrors.push(String(error));
});
await openOrderDetail(page);
await expect(page.getByTestId("pos-order-registration-1")).toBeVisible();
await expect(page.getByTestId("pos-order-metadata-customer-wishes")).toBeVisible();
await expect(page.getByTestId("pos-order-metadata-note")).toBeVisible();
const combinedMessages = [...consoleMessages, ...pageErrors].join("\n");
expect(combinedMessages).not.toContain("Missing required prop: order_id");
expect(combinedMessages).not.toContain('Invalid prop: type check failed for prop "order_id"');
expect(combinedMessages).not.toContain("Unhandled error during execution of setup function");
});
test("renders order detail metadata and item actions without the Excel export affordance", async ({ page }) => {
await openOrderDetail(page);
await expect(page.getByTestId("pos-order-header-export-actions")).toHaveCount(0);
await expect(page.locator('[data-auto-excel-export-button="1"]')).toHaveCount(0);
const inlineDepartment = page.getByTestId("pos-order-inline-department");
const inlineCreated = page.getByTestId("pos-order-inline-created");
await expect(inlineDepartment).toBeVisible();
await expect(inlineCreated).toBeVisible();
await expect(inlineDepartment).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(inlineCreated).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(inlineDepartment).toHaveCSS("padding-left", "0px");
await expect(inlineCreated).toHaveCSS("padding-left", "0px");
const transactionLabel = page.getByTestId("pos-order-transaction-label");
await expect(transactionLabel).toBeVisible();
const priceHeader = page.getByTestId("pos-order-price-header");
const actionsHeader = page.getByTestId("pos-order-actions-header");
await expect(priceHeader).toContainText("Pris (DKK)");
await expect(actionsHeader).toContainText("Handlinger");
await expect(actionsHeader).not.toContainText("table.actions");
const [transactionFontSize, inlineDepartmentFontSize, inlineCreatedFontSize] = await Promise.all([
transactionLabel.evaluate((element) => window.getComputedStyle(element).fontSize),
inlineDepartment.evaluate((element) => window.getComputedStyle(element).fontSize),
inlineCreated.evaluate((element) => window.getComputedStyle(element).fontSize),
]);
expect(inlineDepartmentFontSize).toBe(transactionFontSize);
expect(inlineCreatedFontSize).toBe(transactionFontSize);
const [priceHeaderStyles, actionsHeaderStyles] = await Promise.all([
priceHeader.evaluate((element) => {
const target = element.querySelector(".th-wrap") || element;
const styles = window.getComputedStyle(target);
return {
justifyContent: styles.justifyContent,
textAlign: styles.textAlign,
};
}),
actionsHeader.evaluate((element) => {
const target = element.querySelector(".th-wrap") || element;
const styles = window.getComputedStyle(target);
return {
justifyContent: styles.justifyContent,
textAlign: styles.textAlign,
};
}),
]);
expect(priceHeaderStyles.justifyContent).toBe("flex-end");
expect(priceHeaderStyles.textAlign).toBe("right");
expect(actionsHeaderStyles.justifyContent).toBe("flex-end");
expect(actionsHeaderStyles.textAlign).toBe("right");
await expect(page.getByTestId("pos-order-item-price-9101")).toHaveCSS("text-align", "right");
const licensePlatesSection = page.getByTestId("pos-order-metadata-license-plates");
const customerWishesSection = page.getByTestId("pos-order-metadata-customer-wishes");
const noteSection = page.getByTestId("pos-order-metadata-note");
const metadataSections = [licensePlatesSection, customerWishesSection, noteSection];
for (const section of metadataSections) {
await expect(section).toBeVisible();
const box = await section.boundingBox();
expect(box).not.toBeNull();
expect(box?.width ?? 0).toBeGreaterThan(120);
expect(box?.height ?? 0).toBeGreaterThan(70);
}
await expect(licensePlatesSection.locator(".skeleton-lines")).toHaveCount(0);
const reg1 = page.getByTestId("pos-order-registration-1");
const reg2Add = page.getByTestId("pos-order-registration-add-2");
const reg3Add = page.getByTestId("pos-order-registration-add-3");
const customerWishesReference = page.getByTestId("pos-order-customer-wishes-reference");
const customerWishesPo = page.getByTestId("pos-order-customer-wishes-po");
await expect(reg1).toBeVisible();
await expect(reg2Add).toBeVisible();
await expect(reg3Add).toBeVisible();
await expect(customerWishesReference).toBeVisible();
await expect(customerWishesPo).toBeVisible();
await expect(customerWishesReference).toContainText("EC21233 - Test Ref. / Intern nummer");
await expect(customerWishesPo).toContainText("+ Tilføj");
const noteEmptyState = page.getByTestId("pos-order-note-empty-state");
await expect(noteEmptyState).toBeVisible();
await expect(noteEmptyState.locator(".pos-order-field__empty-pill")).toHaveText(/^\+\s+\S+/);
await expect(noteEmptyState).not.toContainText(/Ingen data/i);
await expect(noteEmptyState.locator(".pos-order-note-editor__empty-copy")).toHaveCount(0);
const reg1Box = await reg1.boundingBox();
const reg2AddBox = await reg2Add.boundingBox();
const licensePlatesBox = await licensePlatesSection.boundingBox();
const customerWishesSectionBox = await customerWishesSection.boundingBox();
const noteSectionBox = await noteSection.boundingBox();
const customerWishesReferenceBox = await customerWishesReference.boundingBox();
const customerWishesPoBox = await customerWishesPo.boundingBox();
expect(reg1Box).not.toBeNull();
expect(reg2AddBox).not.toBeNull();
expect(licensePlatesBox).not.toBeNull();
expect(customerWishesSectionBox).not.toBeNull();
expect(noteSectionBox).not.toBeNull();
expect(customerWishesReferenceBox).not.toBeNull();
expect(customerWishesPoBox).not.toBeNull();
expect(Math.abs((reg1Box?.height ?? 0) - (reg2AddBox?.height ?? 0))).toBeLessThanOrEqual(2);
expect(Math.abs((licensePlatesBox?.height ?? 0) - (customerWishesSectionBox?.height ?? 0))).toBeLessThanOrEqual(2);
expect(customerWishesSectionBox?.x ?? 0).toBeGreaterThan(licensePlatesBox?.x ?? 0);
expect(noteSectionBox?.y ?? 0).toBeGreaterThan(customerWishesSectionBox?.y ?? 0);
expect(Math.abs((licensePlatesBox?.x ?? 0) - (noteSectionBox?.x ?? 0))).toBeLessThanOrEqual(2);
expect(noteSectionBox?.width ?? 0).toBeGreaterThan(customerWishesSectionBox?.width ?? 0);
const noteControlBox = await noteSection.locator(".pos-order-field__control").boundingBox();
expect(noteControlBox).not.toBeNull();
expect(
Math.abs((customerWishesReferenceBox?.height ?? 0) - (customerWishesPoBox?.height ?? 0))
).toBeLessThanOrEqual(2);
expect(noteControlBox?.height ?? 0).toBeLessThan(100);
await expect(noteEmptyState).not.toContainText("pos.add_note_placeholder");
await expect(page.getByTestId("pos-order-item-edit-9101")).toBeVisible();
await expect(page.getByTestId("pos-order-item-delete-9101")).toBeVisible();
await expect(page.getByTestId("pos-order-add-item")).toBeVisible();
await expectOrderTotal(page, 1372);
await page.getByTestId("pos-order-add-item").click();
await expect(page.getByTestId("pos-order-add-items-panel").first()).toBeVisible();
await expect(page.getByTestId("pos-product-card-53").first()).toBeVisible();
});
test("keeps a readable split layout in add-items mode and restores the normal shell after backing out", async ({
page,
}) => {
await openOrderDetail(page);
const main = getVisibleTestId(page, "pos-order-main");
const beforeMainBox = await main.boundingBox();
const beforeRailBox = await getVisibleTestId(page, "pos-order-rail").boundingBox();
expect(beforeMainBox).not.toBeNull();
expect(beforeRailBox).not.toBeNull();
await openAddItemsPanel(page);
await expect(page.locator('[data-testid="pos-order-rail"]:visible')).toHaveCount(0);
const workspace = getVisibleTestId(page, "pos-order-workspace");
const productPanel = page.locator('[data-testid="pos-order-add-items-panel"]:visible').first();
const cartPanel = page.locator('[data-testid="pos-order-panel-cart"]:visible').first();
const addItemsBackButton = page.locator('[data-testid="pos-order-add-items-back"]:visible').first();
const afterMainBox = await main.boundingBox();
const workspaceBox = await workspace.boundingBox();
const productPanelBox = await productPanel.boundingBox();
const cartPanelBox = await cartPanel.boundingBox();
const addItemsBackButtonBox = await addItemsBackButton.boundingBox();
expect(afterMainBox).not.toBeNull();
expect(workspaceBox).not.toBeNull();
expect(productPanelBox).not.toBeNull();
expect(cartPanelBox).not.toBeNull();
expect(addItemsBackButtonBox).not.toBeNull();
expect((afterMainBox?.width ?? 0) - (beforeMainBox?.width ?? 0)).toBeGreaterThan(200);
expect(workspaceBox?.width ?? 0).toBeGreaterThan(800);
const productPanelWidth = productPanelBox?.width ?? 0;
const cartPanelWidth = cartPanelBox?.width ?? 0;
const workspaceWidth = workspaceBox?.width ?? 0;
expect(productPanelWidth).toBeGreaterThan(470);
expect(cartPanelWidth).toBeGreaterThan(300);
expect(cartPanelWidth).toBeLessThan(420);
expect(productPanelWidth).toBeGreaterThan(cartPanelWidth);
expect(productPanelWidth / workspaceWidth).toBeGreaterThan(0.55);
expect(cartPanelWidth / workspaceWidth).toBeLessThan(0.42);
expect(productPanelBox?.x ?? 0).toBeLessThan(cartPanelBox?.x ?? 0);
expect(addItemsBackButtonBox?.y ?? 0).toBeGreaterThan((cartPanelBox?.y ?? 0) + (cartPanelBox?.height ?? 0) - 2);
expect(Math.abs((addItemsBackButtonBox?.x ?? 0) - (cartPanelBox?.x ?? 0))).toBeLessThanOrEqual(8);
await expect(getVisibleTestId(page, "pos-order-total")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-item-edit-9101")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-registration-1")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-metadata-customer-wishes")).toBeVisible();
const categoryTabs = productPanel.locator(".tabs li");
await expect(categoryTabs).toHaveCount(3);
await categoryTabs.nth(2).click();
await expect(productPanel.getByTestId("pos-product-card-64")).toBeVisible();
await expect(productPanel.getByTestId("pos-product-card-53")).toHaveCount(0);
await productPanel.getByTestId("pos-product-card-64").click();
const addRequest = waitForOrderItemMutation(page, "POST", "/order/items");
await productPanel.getByTestId("pos-add-to-cart-64").click();
await addRequest;
await expect(getVisibleTestId(page, "pos-order-item-edit-9200")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-item-name-9200")).toContainText("Vaskecertifikat");
await expectOrderTotal(page, 1397);
await page.locator('[data-testid="pos-order-add-items-back"]:visible').first().click();
await expect(page.locator('[data-testid="pos-order-add-items-panel"]:visible')).toHaveCount(0);
await expect(getVisibleTestId(page, "pos-order-rail")).toBeVisible();
const restoredMainBox = await main.boundingBox();
const restoredRailBox = await getVisibleTestId(page, "pos-order-rail").boundingBox();
expect(restoredMainBox).not.toBeNull();
expect(restoredRailBox).not.toBeNull();
expect(Math.abs((restoredMainBox?.width ?? 0) - (beforeMainBox?.width ?? 0))).toBeLessThanOrEqual(20);
expect(Math.abs((beforeRailBox?.width ?? 0) - (restoredRailBox?.width ?? 0))).toBeLessThanOrEqual(20);
await page.reload({ waitUntil: "domcontentloaded" });
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-item-edit-9200")).toBeVisible();
await expectOrderTotal(page, 1397);
});
test("loads step 2 customer context after reload and keeps actions directly below the basket card", async ({
page,
}) => {
await page.goto("/admin/12/modules/pos");
await expect(page.getByTestId("pos-step-1")).toBeVisible();
await page.locator("#reg_1").fill("EC21235");
await expect(page.getByTestId("pos-step-1").locator(".pos-card-tabs > .tabs li.is-active")).toContainText("Kunde");
await page.getByTestId("pos-step-1").getByTestId("pos-next-step").click();
await expect(page).toHaveURL(/\/admin\/12\/modules\/pos\?id=\d+&customer_id=12345679&step=2/);
const stepTwo = page.getByTestId("pos-step-2");
const orderCard = stepTwo.getByTestId("pos-order-card");
const actions = stepTwo.getByTestId("pos-step-2-actions");
const customerName = () => page.locator('[data-testid="pos-order-customer-name"]:visible').first();
const licensePlatesCard = stepTwo.getByTestId("pos-order-metadata-license-plates");
const customerWishesCard = stepTwo.getByTestId("pos-order-metadata-customer-wishes");
const noteCard = stepTwo.getByTestId("pos-order-metadata-note");
await expect(stepTwo).toBeVisible();
await expect(customerName()).toHaveText(/\(TEST\) Pleno Vognmandsforretning/, { timeout: 10000 });
await expect(actions.getByTestId("pos-next-step")).toBeVisible();
await expect(licensePlatesCard).toBeVisible();
await expect(customerWishesCard).toBeVisible();
await expect(noteCard).toBeVisible();
const orderCardBox = await orderCard.boundingBox();
const actionPanelBox = await actions.boundingBox();
const licensePlatesCardBox = await licensePlatesCard.boundingBox();
const customerWishesCardBox = await customerWishesCard.boundingBox();
expect(orderCardBox).not.toBeNull();
expect(actionPanelBox).not.toBeNull();
expect(licensePlatesCardBox).not.toBeNull();
expect(customerWishesCardBox).not.toBeNull();
const orderCardBottom = (orderCardBox?.y ?? 0) + (orderCardBox?.height ?? 0);
expect(actionPanelBox?.y ?? 0).toBeGreaterThan(orderCardBottom - 2);
expect((actionPanelBox?.y ?? 0) - orderCardBottom).toBeLessThanOrEqual(40);
expect(Math.abs((actionPanelBox?.x ?? 0) - (orderCardBox?.x ?? 0))).toBeLessThanOrEqual(2);
expect(Math.abs((actionPanelBox?.width ?? 0) - (orderCardBox?.width ?? 0))).toBeLessThanOrEqual(2);
expect(customerWishesCardBox?.y ?? 0).toBeGreaterThan(
(licensePlatesCardBox?.y ?? 0) + (licensePlatesCardBox?.height ?? 0) - 2
);
expect(Math.abs((customerWishesCardBox?.x ?? 0) - (licensePlatesCardBox?.x ?? 0))).toBeLessThanOrEqual(2);
await page.reload({ waitUntil: "domcontentloaded" });
await expect(stepTwo).toBeVisible();
await expect(page).toHaveURL(/\/admin\/12\/modules\/pos\?id=\d+&customer_id=12345679&step=2/);
await expect(getVisibleTestId(page, "pos-order-registration-1")).toContainText("EC21235");
await expect(getVisibleTestId(page, "pos-order-metadata-customer-wishes")).toContainText(
"EC21233 - Test Ref. / Intern nummer"
);
await expect(stepTwo.getByTestId("pos-next-step")).toBeVisible();
});
test("autosaves customer wishes and note metadata after a typing pause without blur", async ({ page }) => {
await openOrderDetail(page);
const referenceValue = "AUTOSAVE-REF-2026";
const poValue = "AUTOSAVE-PO-2026";
const noteValue = "Autosaved desktop note";
const referenceRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.reference === referenceValue
);
await page.getByTestId("pos-order-customer-wishes-reference").click();
const referenceInput = page.getByTestId("pos-order-customer-wishes-reference-input");
await referenceInput.fill(referenceValue);
const capturedReferenceRequest = await referenceRequest;
expect(capturedReferenceRequest.postDataJSON()).toMatchObject({
id: 54518,
reference: referenceValue,
});
await expect(referenceInput).toBeFocused();
const poRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.po === poValue
);
await page.getByTestId("pos-order-customer-wishes-po").click();
const poInput = page.getByTestId("pos-order-customer-wishes-po-input");
await poInput.fill(poValue);
const capturedPoRequest = await poRequest;
expect(capturedPoRequest.postDataJSON()).toMatchObject({
id: 54518,
po: poValue,
});
await expect(poInput).toBeFocused();
const noteRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.notes === noteValue
);
await page.getByTestId("pos-order-metadata-note").locator("button").click();
const noteInput = page.getByTestId("pos-order-note-textarea");
await noteInput.fill(noteValue);
const capturedNoteRequest = await noteRequest;
expect(capturedNoteRequest.postDataJSON()).toMatchObject({
id: 54518,
notes: noteValue,
});
await expect(noteInput).toBeFocused();
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-metadata-customer-wishes")).toContainText(referenceValue);
await expect(page.getByTestId("pos-order-metadata-customer-wishes")).toContainText(poValue);
await expect(page.getByTestId("pos-order-metadata-note")).toContainText(noteValue);
});
test("shows empty-state pills for cleared customer wishes fields", async ({ page }) => {
await openOrderDetail(page);
const clearReferenceRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.reference === ""
);
await page.getByTestId("pos-order-customer-wishes-reference").click();
const referenceInput = page.getByTestId("pos-order-customer-wishes-reference-input");
await referenceInput.fill("");
const capturedReferenceRequest = await clearReferenceRequest;
expect(capturedReferenceRequest.postDataJSON()).toMatchObject({
id: 54518,
reference: "",
});
await page.getByTestId("pos-order-customer-wishes-po").click();
const poInput = page.getByTestId("pos-order-customer-wishes-po-input");
const setPoRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.po === "temp-po"
);
await poInput.fill("temp-po");
const capturedSetPoRequest = await setPoRequest;
expect(capturedSetPoRequest.postDataJSON()).toMatchObject({
id: 54518,
po: "temp-po",
});
const clearPoRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.po === ""
);
await poInput.fill("");
const capturedPoRequest = await clearPoRequest;
expect(capturedPoRequest.postDataJSON()).toMatchObject({
id: 54518,
po: "",
});
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-customer-wishes-reference")).toContainText("+ Tilføj");
await expect(page.getByTestId("pos-order-customer-wishes-po")).toContainText("+ Tilføj");
});
test("autosaves inline registration metadata and reloads normalized values", async ({ page }) => {
await openOrderDetail(page);
const rawReg1 = "xy-12 34";
const rawReg2 = "tr/56 78";
const rawReg3 = "no_90 12";
const reg1Request = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.reg_1 === "XY-12 34"
);
await page.getByTestId("pos-order-registration-1").click();
const reg1Input = page.getByTestId("pos-order-registration-input-1");
await reg1Input.fill(rawReg1);
const capturedReg1Request = await reg1Request;
expect(capturedReg1Request.postDataJSON()).toMatchObject({
id: 54518,
reg_1: "XY-12 34",
});
await expect(reg1Input).toHaveValue("XY1234", { timeout: 10000 });
const reg2Request = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.reg_2 === "TR/56 78"
);
await page.getByTestId("pos-order-registration-add-2").click();
const reg2Input = page.getByTestId("pos-order-registration-input-2");
await reg2Input.fill(rawReg2);
const capturedReg2Request = await reg2Request;
expect(capturedReg2Request.postDataJSON()).toMatchObject({
id: 54518,
reg_2: "TR/56 78",
});
await expect(reg2Input).toHaveValue("TR5678", { timeout: 10000 });
const reg3Request = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.reg_3 === "NO_90 12"
);
await page.getByTestId("pos-order-registration-add-3").click();
const reg3Input = page.getByTestId("pos-order-registration-input-3");
await reg3Input.fill(rawReg3);
const capturedReg3Request = await reg3Request;
expect(capturedReg3Request.postDataJSON()).toMatchObject({
id: 54518,
reg_3: "NO_90 12",
});
await expect(reg3Input).toHaveValue("NO9012", { timeout: 10000 });
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-registration-1")).toContainText("XY1234");
await expect(page.getByTestId("pos-order-registration-2")).toContainText("TR5678");
await expect(page.getByTestId("pos-order-registration-3")).toContainText("NO9012");
});
test("edits a primary order item in the Buefy modal and persists after reload", async ({ page }) => {
await openOrderDetail(page);
await expectOrderTotal(page, 1372);
await openOrderItemEditModal(page, 9101);
await page.getByTestId("pos-order-item-edit-price").fill("700");
await page.getByTestId("pos-order-item-edit-quantity").fill("2");
await page.getByTestId("pos-order-item-edit-notes").fill("Primary item note");
await page.getByTestId("pos-order-item-edit-reference").fill("PRIMARY-REF");
const editRequest = waitForOrderItemMutation(page, "PUT", "/order/items");
await page.getByTestId("pos-order-item-edit-save").click();
await editRequest;
await expect(page.getByTestId("pos-order-item-edit-modal")).toBeHidden();
await expect(page.getByTestId("pos-order-item-note-trigger-9101")).toBeVisible();
await expect(page.getByTestId("pos-order-item-reference-trigger-9101")).toBeVisible();
await expect(page.getByTestId("pos-order-item-quantity-9101")).toContainText("2");
await expect(page.getByTestId("pos-order-item-price-9101")).toContainText("1400");
await expectOrderTotal(page, 2123);
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-item-note-trigger-9101")).toBeVisible();
await expect(page.getByTestId("pos-order-item-reference-trigger-9101")).toBeVisible();
await expect(page.getByTestId("pos-order-item-price-9101")).toContainText("1400");
await expectOrderTotal(page, 2123);
});
test("edits a related addon item and preserves grouped rendering after reload", async ({ page }) => {
await openOrderDetail(page);
await openOrderItemEditModal(page, 9102);
await page.getByTestId("pos-order-item-edit-price").fill("450");
await page.getByTestId("pos-order-item-edit-notes").fill("Addon note");
const editRequest = waitForOrderItemMutation(page, "PUT", "/order/items");
await page.getByTestId("pos-order-item-edit-save").click();
await editRequest;
await expect(page.getByTestId("pos-order-item-edit-modal")).toBeHidden();
await expect(page.getByTestId("pos-order-item-note-trigger-9102")).toBeVisible();
await expect(page.getByTestId("pos-order-item-price-9102")).toContainText("450");
await expect(page.getByTestId("pos-order-item-name-9102")).toContainText("Indvendig vask Forvogn");
await expectOrderTotal(page, 1423);
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-item-note-trigger-9102")).toBeVisible();
await expect(page.getByTestId("pos-order-item-price-9102")).toContainText("450");
await expectOrderTotal(page, 1423);
});
test("cancels modal edits without mutating the order item", async ({ page }) => {
const putRequests: string[] = [];
page.on("request", (request) => {
if (request.method() === "PUT" && request.url().includes("/order/items")) {
putRequests.push(request.url());
}
});
await openOrderDetail(page);
await openOrderItemEditModal(page, 9104);
await page.getByTestId("pos-order-item-edit-price").fill("999");
await page.getByTestId("pos-order-item-edit-notes").fill("Do not save");
await page.getByTestId("pos-order-item-edit-cancel").click();
await expect(page.getByTestId("pos-order-item-edit-modal")).toBeHidden();
await page.waitForTimeout(200);
expect(putRequests).toHaveLength(0);
await expect(page.getByTestId("pos-order-item-price-9104")).toContainText("299");
await expect(page.getByTestId("pos-order-item-note-trigger-9104")).toHaveCount(0);
await expectOrderTotal(page, 1372);
});
test("deletes related and primary items and hides orphaned related rows after reload", async ({ page }) => {
await openOrderDetail(page);
await expectOrderTotal(page, 1372);
const deleteRelatedRequest = waitForOrderItemMutation(page, "DELETE", "id=9104");
await page.getByTestId("pos-order-item-delete-9104").click();
await deleteRelatedRequest;
await expect(page.getByTestId("pos-order-item-delete-9104")).toHaveCount(0);
await expectOrderTotal(page, 1073);
const deletePrimaryRequest = waitForOrderItemMutation(page, "DELETE", "id=9101");
await page.getByTestId("pos-order-item-delete-9101").click();
await deletePrimaryRequest;
await expect(page.getByTestId("pos-order-empty-state")).toBeVisible();
await expect(page.getByTestId("pos-order-item-delete-9102")).toHaveCount(0);
await expect(page.getByTestId("pos-order-item-delete-9103")).toHaveCount(0);
await expectOrderTotal(page, 0);
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-empty-state")).toBeVisible();
await expectOrderTotal(page, 0);
});
test("adds a new product from the side panel and persists the created order item", async ({ page }) => {
await openOrderDetail(page);
await expectOrderTotal(page, 1372);
await page.getByTestId("pos-order-add-item").click();
await expect(page.getByTestId("pos-order-add-items-panel").first()).toBeVisible();
await page.getByTestId("pos-product-card-53").first().click();
await expect(page.getByTestId("pos-add-to-cart-53")).toBeVisible();
const addRequest = waitForOrderItemMutation(page, "POST", "/order/items");
await page.getByTestId("pos-add-to-cart-53").click();
await addRequest;
await expect(page.getByTestId("pos-order-item-edit-9200").first()).toBeVisible();
await expect(page.getByTestId("pos-order-item-name-9200").first()).toContainText("Forvogn");
await expectOrderTotal(page, 2021);
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(page.getByTestId("pos-order-item-edit-9200").first()).toBeVisible();
await expectOrderTotal(page, 2021);
});
test("attaches a wash certificate from the attachments tab and keeps duplicate attempts idempotent", async ({
page,
}) => {
await openOrderAttachments(page);
await openAddAttachmentCard(page);
const attachmentCards = page.locator(".card-header-title").filter({ hasText: /Attachment #|Vedhæftning #/i });
const attachWashCertificateButton = page.getByTestId("pos-order-attachments-attach-wash-certificate");
const uploadAction = page.getByTestId("pos-order-attachments-upload-action");
await expect(attachmentCards).toHaveCount(1);
await expect(attachWashCertificateButton).toBeVisible();
await expect(uploadAction).toHaveText("Upload");
const firstRequestPromise = page.waitForRequest((request) => {
if (request.method() !== "POST" || !request.url().includes("/order/wash-certificate")) {
return false;
}
const body = request.postDataJSON?.();
return Number(body?.id) === 54518;
});
await attachWashCertificateButton.click();
await expect(page.locator(".swal2-popup")).toBeVisible();
await page.locator(".swal2-input").fill("12345");
await page.locator(".swal2-confirm").click();
const firstRequest = await firstRequestPromise;
expect(firstRequest.postDataJSON()).toMatchObject({
id: 54518,
safety_seal: "12345",
});
await expect(page.getByText(/(Attachment|Vedhæftning) #400/i)).toBeVisible();
await expect(attachmentCards).toHaveCount(2);
await expect(page.locator(".swal2-popup")).toContainText(/generated and attached|genereret og vedhæftet/i);
await waitForSwalToClose(page);
await getVisibleTestId(page, "pos-order-attachment-card-400").locator(".card-header").click();
await expect(page.getByTestId("pos-order-attachment-download-400")).toHaveText("Download");
const secondRequestPromise = page.waitForRequest((request) => {
if (request.method() !== "POST" || !request.url().includes("/order/wash-certificate")) {
return false;
}
const body = request.postDataJSON?.();
return Number(body?.id) === 54518;
});
await attachWashCertificateButton.click();
await expect(page.locator(".swal2-popup")).toBeVisible();
await page.locator(".swal2-input").fill("99999");
await page.locator(".swal2-confirm").click();
const secondRequest = await secondRequestPromise;
expect(secondRequest.postDataJSON()).toMatchObject({
id: 54518,
safety_seal: "99999",
});
await expect(page.locator(".swal2-popup")).toContainText(/already attached|allerede vedhæftet/i);
await expect(attachmentCards).toHaveCount(2);
await waitForSwalToClose(page);
});
test("renders the settings surface and keeps destructive actions available", async ({ page }) => {
const { orderId } = await createDisposableOrder(page);
await openOrderSettings(page, orderId);
await expect(getOrderSettingsField(page, "customer_id")).toBeVisible();
await expect(getOrderSettingsField(page, "department_id")).toBeVisible();
await expect(getOrderSettingsField(page, "created_at")).toBeVisible();
await expect(getOrderSettingsField(page, "include_in_invoice")).toBeVisible();
await expect(page.getByTestId("pos-order-settings-include-helper")).not.toHaveText(/^\s*$/);
await expect(getOrderSettingsEditButton(page, "reference")).toBeVisible();
await expect(page.getByTestId("pos-order-settings-delete")).toBeVisible();
});
test("persists scalar metadata edits across settings and details tabs", async ({ page }) => {
const { orderId } = await createDisposableOrder(page);
const submittedValues = {
reference: "UPDATED-REF-2026",
reg_1: " zz-99 881 ",
reg_2: " tr/99-882 ",
reg_3: " tl_99 883 ",
notes: "Updated order note",
po: "PO-9001",
lane: "7",
wash_id: "WASH-77",
booking_id: "8801",
};
const expectedValues = {
...submittedValues,
reg_1: "ZZ99881",
reg_2: "TR99882",
reg_3: "TL99883",
};
await openOrderSettings(page, orderId);
await submitOrderSettingModal(page, "reference", submittedValues.reference);
await submitOrderSettingModal(page, "reg_1", submittedValues.reg_1);
await submitOrderSettingModal(page, "reg_2", submittedValues.reg_2);
await submitOrderSettingModal(page, "reg_3", submittedValues.reg_3);
await submitOrderSettingModal(page, "notes", submittedValues.notes);
await submitOrderSettingModal(page, "po", submittedValues.po);
await submitOrderSettingModal(page, "lane", submittedValues.lane);
await submitOrderSettingModal(page, "wash_id", submittedValues.wash_id);
await submitOrderSettingModal(page, "booking_id", submittedValues.booking_id);
await reloadOrderSettings(page, orderId);
await expect(getOrderSettingsField(page, "reference")).toContainText(expectedValues.reference);
await expect(getOrderSettingsField(page, "reg_1")).toContainText(expectedValues.reg_1);
await expect(getOrderSettingsField(page, "reg_2")).toContainText(expectedValues.reg_2);
await expect(getOrderSettingsField(page, "reg_3")).toContainText(expectedValues.reg_3);
await expect(getOrderSettingsField(page, "notes")).toContainText(expectedValues.notes);
await expect(getOrderSettingsField(page, "po")).toContainText(expectedValues.po);
await expect(getOrderSettingsField(page, "lane")).toContainText(expectedValues.lane);
await expect(getOrderSettingsField(page, "wash_id")).toContainText(expectedValues.wash_id);
await expect(getOrderSettingsField(page, "booking_id")).toContainText(expectedValues.booking_id);
await openOrderDetailsTab(page);
await expect(getOrderDetailInput(page, "reference")).toHaveValue(expectedValues.reference);
await expect(getOrderDetailInput(page, "reg_1")).toHaveValue(expectedValues.reg_1);
await expect(getOrderDetailInput(page, "reg_2")).toHaveValue(expectedValues.reg_2);
await expect(getOrderDetailInput(page, "reg_3")).toHaveValue(expectedValues.reg_3);
await expect(getOrderDetailInput(page, "notes")).toHaveValue(expectedValues.notes);
await expect(getOrderDetailInput(page, "po")).toHaveValue(expectedValues.po);
await expect(getOrderDetailInput(page, "lane")).toHaveValue(expectedValues.lane);
await expect(getOrderDetailInput(page, "wash_id")).toHaveValue(expectedValues.wash_id);
await expect(getOrderDetailInput(page, "booking_id")).toHaveValue(expectedValues.booking_id);
});
test("persists department, created_at, and invoice inclusion changes through per-field modals", async ({ page }) => {
const { orderId } = await createDisposableOrder(page);
const updatedCreatedAt = "2026-04-09T13:37";
const helper = page.getByTestId("pos-order-settings-include-helper");
await openOrderSettings(page, orderId);
const initialHelper = ((await helper.textContent()) || "").trim();
expect(initialHelper).not.toBe("");
await submitOrderSettingModal(page, "department_id", "1", "select");
await submitOrderSettingModal(page, "created_at", updatedCreatedAt);
await submitOrderSettingModal(page, "include_in_invoice", "exclude", "select");
await reloadOrderSettings(page, orderId);
await expect(getOrderSettingsField(page, "department_id")).toContainText("Taastrup");
await expect(getOrderSettingsField(page, "created_at")).toContainText("2026-04-09 13:37:00");
await expect(getOrderSettingsField(page, "include_in_invoice")).toContainText(/Ekskluder|Exclude/);
const excludedHelper = ((await helper.textContent()) || "").trim();
expect(excludedHelper).not.toBe("");
expect(excludedHelper).not.toBe(initialHelper);
await submitOrderSettingModal(page, "include_in_invoice", "include", "select");
await reloadOrderSettings(page, orderId);
await expect(getOrderSettingsField(page, "include_in_invoice")).toContainText(/Inkluder|Include/);
const includedHelper = ((await helper.textContent()) || "").trim();
expect(includedHelper).not.toBe("");
expect(includedHelper).not.toBe(excludedHelper);
await submitOrderSettingModal(page, "include_in_invoice", "use_department", "select");
await reloadOrderSettings(page, orderId);
await expect(getOrderSettingsField(page, "include_in_invoice")).toContainText(/Brug afdeling|Use department/);
const inheritedExcludedHelper = ((await helper.textContent()) || "").trim();
expect(inheritedExcludedHelper).not.toBe("");
expect(inheritedExcludedHelper).not.toBe(includedHelper);
await openOrderDetailsTab(page);
await expect(getOrderDetailInput(page, "department_id")).toHaveValue("Taastrup");
await expect(getOrderDetailInput(page, "created_at")).toHaveValue("2026-04-09 13:37:00");
await expect(getOrderDetailInput(page, "include_in_invoice")).toHaveValue(/Ekskluder|Exclude/);
});
test("updates customer and invoice collection through the dedicated modal flows without a manual reload", async ({
page,
}) => {
const { orderId } = await createDisposableOrder(page);
await openOrderSettings(page, orderId);
const initialUrl = page.url();
await changeOrderCustomer(page, "999", "2026-04-30");
await expect(page).toHaveURL(initialUrl);
await expect(getOrderSettingsField(page, "customer_id")).toContainText("999");
await expect(getOrderSettingsField(page, "invoice_collection_id")).toContainText("300");
await openOrderDetailsTab(page);
await expect(getOrderDetailInput(page, "customer_id")).toHaveValue("999");
await expect(getOrderDetailInput(page, "invoice_collection_id")).toHaveValue("300");
await clickVisibleTestId(page, "pos-order-tab-settings");
await expect(page.getByTestId("pos-order-panel-settings")).toBeVisible();
await changeOrderInvoiceCollection(page, "2026-05-01");
await expect(page).toHaveURL(initialUrl);
await expect(getOrderSettingsField(page, "invoice_collection_id")).toContainText("301");
await openOrderDetailsTab(page);
await expect(getOrderDetailInput(page, "invoice_collection_id")).toHaveValue("301");
});
test("shows a paperclip attachment control, all attachment actions, and a hover preview when the order has attachments", async ({
page,
}) => {
await page.goto("/admin/12/modules/pos/orders");
const attachmentDropdown = getVisibleTestId(page, "pos-order-list-attachments-54518");
const attachmentAction = attachmentDropdown.locator(".dropdown-trigger button");
const settingsAction = getVisibleTestId(page, "pos-order-list-settings-54518").locator(".dropdown-trigger button");
await expect(attachmentAction).toBeVisible();
await expect(settingsAction).toBeVisible();
const attachmentBox = await attachmentAction.boundingBox();
const settingsBox = await settingsAction.boundingBox();
expect(attachmentBox).not.toBeNull();
expect(settingsBox).not.toBeNull();
expect(attachmentBox?.x ?? 0).toBeLessThan(settingsBox?.x ?? 0);
await expect(attachmentAction).toHaveClass(/is-dark/);
await expect(attachmentAction.locator(".fa-paperclip")).toBeVisible();
await attachmentAction.click();
const attachmentItems = attachmentDropdown.locator(".dropdown-content button.dropdown-item-action");
await expect(attachmentItems.nth(0)).toContainText(/vaskecertifikat|wash certificate/i);
await expect(attachmentItems.nth(1)).toContainText(/upload/i);
await expect(attachmentItems.nth(2)).toContainText(/safety-seal\.pdf/i);
const hoveredAttachmentItem = page.getByTestId("pos-order-list-attachment-item-54518-301");
await hoveredAttachmentItem.hover();
const dropdownContent = attachmentDropdown.locator(".dropdown-content");
const previewPanel = page.getByTestId("pos-order-list-attachment-preview-54518");
await expect(previewPanel).toBeVisible();
await expect(previewPanel.locator("iframe")).toBeVisible();
const dropdownContentBox = await dropdownContent.boundingBox();
const previewPanelBox = await previewPanel.boundingBox();
expect(dropdownContentBox).not.toBeNull();
expect(previewPanelBox).not.toBeNull();
expect(previewPanelBox?.x ?? 0).toBeLessThan(dropdownContentBox?.x ?? 0);
});
test("shows an icon-only plus attachment control and outlined dropdown when the order has no attachments", async ({
page,
}) => {
const { orderId } = await createDisposableOrder(page);
await page.goto("/admin/12/modules/pos/orders");
const paperclipAction = getVisibleTestId(page, "pos-order-list-attachments-54518").locator(
".dropdown-trigger button"
);
const attachmentDropdown = getVisibleTestId(page, `pos-order-list-attachments-${orderId}`);
const attachmentAction = attachmentDropdown.locator(".dropdown-trigger button");
const settingsAction = getVisibleTestId(page, `pos-order-list-settings-${orderId}`).locator(
".dropdown-trigger button"
);
await expect(attachmentAction).toBeVisible();
await expect(settingsAction).toBeVisible();
await expect(attachmentAction).toHaveClass(/action-settings-wheel-trigger--text/);
await expect(attachmentAction).not.toHaveClass(/is-text/);
await expect(attachmentAction).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
await expect(attachmentAction).toHaveCSS("text-decoration-line", "none");
await expect(attachmentAction).toHaveCSS("justify-content", "center");
await expect(attachmentAction.locator(".fa-plus")).toBeVisible();
await expect(attachmentAction.locator(".ml-2")).toHaveCount(0);
const paperclipBox = await paperclipAction.boundingBox();
const attachmentBox = await attachmentAction.boundingBox();
const settingsBox = await settingsAction.boundingBox();
const plusIconBox = await attachmentAction.locator(".fa-plus").boundingBox();
expect(paperclipBox).not.toBeNull();
expect(attachmentBox).not.toBeNull();
expect(settingsBox).not.toBeNull();
expect(plusIconBox).not.toBeNull();
expect(Math.abs((attachmentBox?.width ?? 0) - (paperclipBox?.width ?? 0))).toBeLessThanOrEqual(1);
expect(Math.abs((attachmentBox?.height ?? 0) - (paperclipBox?.height ?? 0))).toBeLessThanOrEqual(1);
expect(
Math.abs(
(plusIconBox?.x ?? 0) +
(plusIconBox?.width ?? 0) / 2 -
((attachmentBox?.x ?? 0) + (attachmentBox?.width ?? 0) / 2)
)
).toBeLessThanOrEqual(1);
expect(
Math.abs(
(plusIconBox?.y ?? 0) +
(plusIconBox?.height ?? 0) / 2 -
((attachmentBox?.y ?? 0) + (attachmentBox?.height ?? 0) / 2)
)
).toBeLessThanOrEqual(1);
expect(attachmentBox?.x ?? 0).toBeLessThan(settingsBox?.x ?? 0);
await attachmentAction.click();
const dropdownContent = attachmentDropdown.locator(".dropdown-content");
const attachmentItems = dropdownContent.locator("button.dropdown-item-action");
await expect(dropdownContent).toBeVisible();
await expect(dropdownContent).toHaveCSS("border-top-width", "1px");
await expect(dropdownContent).toHaveCSS("border-right-width", "1px");
await expect(dropdownContent).toHaveCSS("border-bottom-width", "1px");
await expect(dropdownContent).toHaveCSS("border-left-width", "1px");
await expect(dropdownContent).toHaveCSS("border-top-style", "solid");
await expect(attachmentItems.nth(0)).toContainText(/vaskecertifikat|wash certificate/i);
await expect(attachmentItems.nth(1)).toContainText(/upload/i);
});
});
test.describe("Admin POS wash certificate completion", () => {
test("shows and autosaves the safety seal field when the order contains a wash certificate item", async ({
page,
}, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only wash certificate metadata coverage");
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: createPosFixture(),
});
await primeOperatorSession(page, "pos-safety-seal-token");
await openOrderDetail(page);
const safetySealField = page.getByTestId("pos-order-customer-wishes-safety-seal");
const referenceControl = page.getByTestId("pos-order-customer-wishes-reference-control");
const poControl = page.getByTestId("pos-order-customer-wishes-po-control");
const safetySealControl = page.getByTestId("pos-order-customer-wishes-safety-seal-control");
await expect(safetySealField).toBeVisible();
await expect(referenceControl).toBeVisible();
await expect(poControl).toBeVisible();
await expect(safetySealControl).toBeVisible();
const [referenceBox, poBox, safetySealBox] = await Promise.all([
referenceControl.boundingBox(),
poControl.boundingBox(),
safetySealControl.boundingBox(),
]);
expect(referenceBox).not.toBeNull();
expect(poBox).not.toBeNull();
expect(safetySealBox).not.toBeNull();
expect(safetySealBox!.width).toBeGreaterThan(referenceBox!.width * 1.8);
expect(safetySealBox!.width).toBeGreaterThan(poBox!.width * 1.8);
const updateRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.safety_seal === "SEAL-5566"
);
await safetySealField.click();
const safetySealInput = page.getByTestId("pos-order-customer-wishes-safety-seal-input");
await expect(safetySealInput).toBeVisible();
await safetySealInput.fill("SEAL-5566");
const capturedRequest = await updateRequest;
expect(capturedRequest.postDataJSON()).toMatchObject({
id: 54518,
safety_seal: "SEAL-5566",
});
await expect(safetySealInput).toBeFocused();
await expect(safetySealInput).toHaveValue("SEAL-5566");
});
test("defers material desktop completion to step 4 and auto-attaches the wash certificate on final completion", async ({
page,
}, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only completion sequencing coverage");
const baseFixture = createPosFixture();
const materialOrderId = 56001;
const fixture = createPosFixture({
ordersById: {
...baseFixture.ordersById,
[materialOrderId]: {
...baseFixture.ordersById[54518],
id: materialOrderId,
reference: "AUTO-SEAL-REF",
completed_at: null,
},
},
orderItemsByOrderId: {
...baseFixture.orderItemsByOrderId,
[materialOrderId]: [
{
...baseFixture.orderItemsByOrderId[54518][0],
id: 9801,
order_id: materialOrderId,
related_item_id: null,
},
{
...baseFixture.orderItemsByOrderId[54518][2],
id: 9802,
order_id: materialOrderId,
related_item_id: 9801,
},
],
},
attachmentsByOrderId: {
...baseFixture.attachmentsByOrderId,
[materialOrderId]: [],
},
});
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: fixture,
});
await primeOperatorSession(page, "pos-material-sequencing-token");
await page.goto(`/admin/12/modules/pos?id=${materialOrderId}&customer_id=12345679&step=3`);
await expect(page.getByTestId("pos-step-3")).toBeVisible();
await clickVisibleTestId(page, "pos-next-step");
await expect(page.getByTestId("pos-step-4")).toBeVisible();
expect(fixture.ordersById[materialOrderId]?.completed_at).toBeNull();
await expect(page.getByTestId("pos-step-4").getByTestId("pos-next-step")).toBeVisible({ timeout: 10_000 });
const completionRequest = page.waitForRequest((request) => {
return request.method() === "POST" && request.url().includes("/orders/mark_as_completed");
});
await clickVisibleTestId(page, "pos-next-step");
await completionRequest;
await expect.poll(() => Boolean(fixture.ordersById[materialOrderId]?.completed_at), { timeout: 10_000 }).toBe(true);
await expect
.poll(
() =>
Boolean(
fixture.attachmentsByOrderId[materialOrderId]?.some(
(attachment) => String(attachment?.content?.other || "").toUpperCase() === "WASH_CERTIFICATE"
)
),
{ timeout: 10_000 }
)
.toBe(true);
});
});
test.describe("Admin POS Orders - desktop required warning states", () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only required warning coverage");
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: createRequiredWarningsPosFixture(),
});
await primeOperatorSession(page, "pos-orders-required-warnings-token");
});
test("shows required reference and po warnings in order detail and clears them without changing field size", async ({
page,
}) => {
await openOrderDetail(page);
const referenceControl = page.getByTestId("pos-order-customer-wishes-reference-control");
const poControl = page.getByTestId("pos-order-customer-wishes-po-control");
await expect(referenceControl).toHaveAttribute("data-warning-state", "danger");
await expect(poControl).toHaveAttribute("data-warning-state", "warning");
await expect(page.getByTestId("pos-order-customer-wishes-reference-warning-icon")).toBeVisible();
await expect(page.getByTestId("pos-order-customer-wishes-po-warning-icon")).toBeVisible();
const warningReferenceBox = await referenceControl.boundingBox();
const warningPoBox = await poControl.boundingBox();
expect(warningReferenceBox).not.toBeNull();
expect(warningPoBox).not.toBeNull();
expect(Math.abs((warningReferenceBox?.height ?? 0) - (warningPoBox?.height ?? 0))).toBeLessThanOrEqual(2);
const referenceValue = "REF1";
const poValue = "PO1";
const referenceRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.reference === referenceValue
);
await page.getByTestId("pos-order-customer-wishes-reference").click();
const referenceInput = page.getByTestId("pos-order-customer-wishes-reference-input");
await referenceInput.fill(referenceValue);
await expect(referenceControl).not.toHaveAttribute("data-warning-state", "danger");
await expect(page.getByTestId("pos-order-customer-wishes-reference-warning-icon")).toHaveCount(0);
await referenceInput.press("Enter");
await expect(referenceInput).toHaveCount(0);
await referenceRequest;
await expect(referenceControl).not.toHaveClass(/is-loading/);
const poRequest = waitForOrderMutation(
page,
"PUT",
"/orders",
(body) => Number(body.id) === 54518 && body.po === poValue
);
await page.getByTestId("pos-order-customer-wishes-po").click();
const poInput = page.getByTestId("pos-order-customer-wishes-po-input");
await poInput.fill(poValue);
await expect(poControl).not.toHaveAttribute("data-warning-state", "warning");
await expect(page.getByTestId("pos-order-customer-wishes-po-warning-icon")).toHaveCount(0);
await poInput.press("Enter");
await expect(poInput).toHaveCount(0);
await poRequest;
await expect(poControl).not.toHaveClass(/is-loading/);
await expect(page.getByTestId("pos-order-customer-wishes-reference")).toContainText(referenceValue);
await expect(page.getByTestId("pos-order-customer-wishes-po")).toContainText(poValue);
await expect(referenceControl).not.toHaveClass(/is-loading/);
await expect(poControl).not.toHaveClass(/is-loading/);
const clearedReferenceBox = await referenceControl.boundingBox();
const clearedPoBox = await poControl.boundingBox();
expect(clearedReferenceBox).not.toBeNull();
expect(clearedPoBox).not.toBeNull();
expect(Math.abs((clearedReferenceBox?.height ?? 0) - (clearedPoBox?.height ?? 0))).toBeLessThanOrEqual(2);
});
test("shows the same required warnings in the shared desktop step 2 workspace", async ({ page }) => {
await page.goto("/admin/12/modules/pos?step=1");
await expect(page.getByTestId("pos-step-1")).toBeVisible();
await page.locator("#reg_1").fill("WARN123");
await page.locator("#pos_select_customer_input").fill("12345679");
await expect(page.locator(".customer-drop-down-select").first()).toBeVisible();
await page.locator(".customer-drop-down-select").first().click();
await page.getByTestId("pos-step-1").getByTestId("pos-next-step").click();
const stepTwo = page.getByTestId("pos-step-2");
await expect(stepTwo).toBeVisible();
await expect(stepTwo.getByTestId("pos-order-customer-wishes-reference-control")).toHaveAttribute(
"data-warning-state",
"danger"
);
await expect(stepTwo.getByTestId("pos-order-customer-wishes-po-control")).toHaveAttribute(
"data-warning-state",
"warning"
);
await expect(stepTwo.getByTestId("pos-order-customer-wishes-reference-warning-icon")).toBeVisible();
await expect(stepTwo.getByTestId("pos-order-customer-wishes-po-warning-icon")).toBeVisible();
});
});
test.describe("Admin POS Orders - desktop step 1 customer and vehicle ownership", () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only step 1 customer ownership coverage");
});
test("preserves a manually selected required-reference customer and manual reference while editing reg_1", async ({
page,
}) => {
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: createRequiredWarningsPosFixture(),
});
await primeOperatorSession(page, "pos-orders-manual-customer-reg-edit-token");
await page.goto("/admin/12/modules/pos?step=1");
await expect(page.getByTestId("pos-step-1")).toBeVisible();
await page.locator("#reg_1").fill("MAN");
await selectStepOneCustomer(page, 12345679);
const referenceInput = page.locator("#reference");
await referenceInput.fill("MANUAL-REF-001");
await page.locator("#reg_1").fill("MANUAL1");
await expect(
page
.locator(".pos-selected-customer__title")
.filter({ hasText: /\(TEST\) Pleno/ })
.first()
).toBeVisible();
await expect(referenceInput).toHaveValue("MANUAL-REF-001");
await page.locator("#reg_1").fill("");
await expect(
page
.locator(".pos-selected-customer__title")
.filter({ hasText: /\(TEST\) Pleno/ })
.first()
).toBeVisible();
await expect(referenceInput).toHaveValue("MANUAL-REF-001");
await page.locator("#reg_1").fill("MANUAL2");
await expect(
page
.locator(".pos-selected-customer__title")
.filter({ hasText: /\(TEST\) Pleno/ })
.first()
).toBeVisible();
await expect(referenceInput).toHaveValue("MANUAL-REF-001");
});
test("asks whether to keep the selected customer when an exact plate belongs to another customer", async ({
page,
}) => {
const fixture = createCustomerConflictPosFixture();
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: fixture,
});
await primeOperatorSession(page, "pos-orders-customer-conflict-keep-token");
await page.goto("/admin/12/modules/pos?step=1");
await expect(page.getByTestId("pos-step-1")).toBeVisible();
await page.locator("#reg_1").fill("CON");
await selectStepOneCustomer(page, 12345679);
await page.locator("#reference").fill("KEEP-REF");
await page.locator("#reg_1").fill("CONFLICT1");
const activeConflictModal = page.locator('[data-testid="pos-desktop-customer-conflict-modal"].is-active');
await expect(activeConflictModal).toBeVisible();
await activeConflictModal.getByTestId("pos-desktop-customer-conflict-keep").click();
await expect(activeConflictModal).toHaveCount(0);
await expect(
page
.locator(".pos-selected-customer__title")
.filter({ hasText: /\(TEST\) Pleno/ })
.first()
).toBeVisible();
await expect(page.locator("#reference")).toHaveValue("KEEP-REF");
await expect(page.locator("#reg_1")).toHaveValue("CONFLICT1");
await page.getByTestId("pos-step-1").getByTestId("pos-next-step").click();
await expect(page).toHaveURL(/\/admin\/12\/modules\/pos\?id=\d+&customer_id=12345679&step=2/);
});
test("can switch to the matched vehicle customer from the conflict modal", async ({ page }) => {
const fixture = createCustomerConflictPosFixture();
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: fixture,
});
await primeOperatorSession(page, "pos-orders-customer-conflict-use-vehicle-token");
await page.goto("/admin/12/modules/pos?step=1");
await expect(page.getByTestId("pos-step-1")).toBeVisible();
await page.locator("#reg_1").fill("CON");
await selectStepOneCustomer(page, 12345679);
await page.locator("#reference").fill("MANUAL-REF");
await page.locator("#reg_1").fill("CONFLICT1");
const activeConflictModal = page.locator('[data-testid="pos-desktop-customer-conflict-modal"].is-active');
await expect(activeConflictModal).toBeVisible();
await activeConflictModal.getByTestId("pos-desktop-customer-conflict-use-vehicle").click();
await expect(activeConflictModal).toHaveCount(0);
await expect(
page
.locator(".pos-selected-customer__title")
.filter({ hasText: /\(TEST\) Conflict Fleet/ })
.first()
).toBeVisible();
await expect(page.locator("#reference")).toHaveValue("CONFLICT-VEHICLE-REF");
await expect(page.locator("#reg_1")).toHaveValue("CONFLICT1");
await page.getByTestId("pos-step-1").getByTestId("pos-next-step").click();
await expect(page).toHaveURL(/\/admin\/12\/modules\/pos\?id=\d+&customer_id=22334455&step=2/);
});
});
test.describe("Admin POS Orders - desktop attachment discovery", () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only attachment discovery coverage");
const posFixture = createPosFixture();
posFixture.ordersById[54518] = {
...posFixture.ordersById[54518],
attachments: [],
has_attachments: false,
attachments_count: 0,
attachment_count: 0,
attachmentsCount: 0,
};
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: posFixture,
});
await primeOperatorSession(page, "pos-orders-attachment-discovery-token");
});
test("discovers fetched attachments and switches the list trigger from plus to paperclip", async ({ page }) => {
await page.goto("/admin/12/modules/pos/orders");
const attachmentDropdown = getVisibleTestId(page, "pos-order-list-attachments-54518");
const attachmentAction = attachmentDropdown.locator(".dropdown-trigger button");
await expect.poll(async () => attachmentAction.locator(".fa-paperclip").count(), { timeout: 10000 }).toBe(1);
await expect(attachmentAction).toHaveClass(/is-dark/);
await expect(attachmentAction.locator(".fa-plus")).toHaveCount(0);
await attachmentAction.click();
const attachmentItems = attachmentDropdown.locator(".dropdown-content button.dropdown-item-action");
await expect(attachmentItems.nth(0)).toContainText(/vaskecertifikat|wash certificate/i);
await expect(attachmentItems.nth(1)).toContainText(/upload/i);
await expect(attachmentItems.nth(2)).toContainText(/safety-seal\.pdf/i);
});
});
test.describe("Admin POS Orders - desktop locked states", () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("desktop"), "Desktop-only order lock coverage");
});
test("hides item mutation controls when edit permission is missing", async ({ page }) => {
const limitedPermissions = POS_PERMISSIONS.filter((permission) => permission !== "edit_order_items");
await mockApi(page, {
authenticated: true,
permissions: limitedPermissions,
edgeGateways: false,
pos: createPosFixture(),
});
await primeOperatorSession(page, "pos-orders-no-edit-token", limitedPermissions);
await openOrderDetail(page);
await expect(page.getByTestId("pos-order-item-edit-9101")).toHaveCount(0);
await expect(page.getByTestId("pos-order-item-delete-9101")).toHaveCount(0);
await expect(page.getByTestId("pos-order-add-item")).toHaveCount(0);
await expectOrderTotal(page, 1372);
});
test("hides item mutation controls when the order is already invoiced", async ({ page }) => {
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: createPosFixture({
economicModuleOrdersByOrderId: {
54518: {
invoice_id: 99101,
invoice_draft_id: null,
},
},
}),
});
await primeOperatorSession(page, "pos-orders-invoiced-token");
await openOrderDetail(page);
await expect(page.getByTestId("pos-order-item-edit-9101")).toHaveCount(0);
await expect(page.getByTestId("pos-order-item-delete-9101")).toHaveCount(0);
await expect(page.getByTestId("pos-order-add-item")).toHaveCount(0);
await expectOrderTotal(page, 1372);
});
});
test.describe("Admin POS Orders - mobile smoke", () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.toLowerCase().includes("mobile"), "Mobile-only order settings smoke");
await mockApi(page, {
authenticated: true,
permissions: POS_PERMISSIONS,
edgeGateways: false,
pos: createPosFixture(),
});
await primeOperatorSession(page, "pos-orders-mobile-token");
});
test("opens the settings tab and renders the new controls", async ({ page }) => {
await page.goto("/admin/12/modules/pos/orders/54518");
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await clickVisibleTestId(page, "pos-order-tab-settings");
await expect(page.getByTestId("pos-order-panel-settings")).toBeVisible();
await expect(getOrderSettingsField(page, "department_id")).toBeVisible();
await expect(getOrderSettingsField(page, "created_at")).toBeVisible();
await expect(getOrderSettingsField(page, "include_in_invoice")).toBeVisible();
});
test("shows the wash certificate action in the attachments tab", async ({ page }) => {
await openOrderAttachments(page);
await openAddAttachmentCard(page);
});
test("keeps horizontal gutters around the mobile order tabs", async ({ page }) => {
await openOrderDetail(page);
const noteEmptyState = page.getByTestId("pos-order-note-empty-state");
await expect(noteEmptyState).toBeVisible();
await expect(noteEmptyState.locator(".pos-order-field__empty-pill")).toHaveText(/^\+\s+\S+/);
const tabsContainer = getVisibleTestId(page, "pos-order-mobile-tabs");
const cartTab = getVisibleTestId(page, "pos-order-tab-cart");
const containerBox = await tabsContainer.boundingBox();
const cartTabBox = await cartTab.boundingBox();
expect(containerBox).not.toBeNull();
expect(cartTabBox).not.toBeNull();
expect(cartTabBox!.x - containerBox!.x).toBeGreaterThanOrEqual(10);
expect(containerBox!.x + containerBox!.width - (cartTabBox!.x + cartTabBox!.width)).toBeGreaterThanOrEqual(10);
});
test("supports mobile item edit, add-panel access, and delete control visibility", async ({ page }) => {
await openOrderDetail(page);
await expectOrderTotal(page, 1372);
await openOrderItemEditModal(page, 9101);
await page.getByTestId("pos-order-item-edit-quantity").fill("2");
const editRequest = waitForOrderItemMutation(page, "PUT", "/order/items");
await page.getByTestId("pos-order-item-edit-save").click();
await editRequest;
await expect(page.getByTestId("pos-order-item-edit-modal")).toBeHidden();
await expectOrderTotal(page, 2021);
await page.getByTestId("pos-order-add-item").click();
await expect(page.getByTestId("pos-order-add-items-panel").first()).toBeVisible();
await page.getByTestId("pos-order-item-delete-9101").first().scrollIntoViewIfNeeded();
await expect(page.getByTestId("pos-order-item-delete-9101").first()).toBeVisible();
});
test("keeps the mobile search and reload controls inline and shows a clear card boundary", async ({ page }) => {
await page.goto("/admin/12/modules/pos/orders");
const searchInput = page.getByTestId("pagination-search-input");
const reloadActions = page.getByTestId("pagination-reload-actions");
const orderCard = page.getByTestId("pos-order-list-card-54518");
await expect(searchInput).toBeVisible();
await expect(reloadActions).toBeVisible();
await expect(orderCard).toBeVisible();
const searchBox = await searchInput.boundingBox();
const reloadBox = await reloadActions.boundingBox();
expect(searchBox).not.toBeNull();
expect(reloadBox).not.toBeNull();
const searchCenterY = (searchBox?.y ?? 0) + (searchBox?.height ?? 0) / 2;
const reloadCenterY = (reloadBox?.y ?? 0) + (reloadBox?.height ?? 0) / 2;
expect(Math.abs(searchCenterY - reloadCenterY)).toBeLessThanOrEqual(4);
expect(searchBox?.x ?? 0).toBeLessThan(reloadBox?.x ?? 0);
expect((searchBox?.x ?? 0) + (searchBox?.width ?? 0) - (reloadBox?.x ?? 0)).toBeLessThanOrEqual(4);
expect(searchBox?.width ?? 0).toBeGreaterThan(reloadBox?.width ?? 0);
await expect(orderCard).toHaveCSS("border-top-width", "1px");
await expect(orderCard).toHaveCSS("border-right-width", "1px");
await expect(orderCard).toHaveCSS("border-bottom-width", "1px");
await expect(orderCard).toHaveCSS("border-left-width", "1px");
});
test("stacks the add-items workspace on mobile and still lets the user add products", async ({ page }) => {
await openOrderDetail(page);
const main = getVisibleTestId(page, "pos-order-main");
const beforeMainBox = await main.boundingBox();
const beforeRailBox = await getVisibleTestId(page, "pos-order-rail").boundingBox();
expect(beforeMainBox).not.toBeNull();
expect(beforeRailBox).not.toBeNull();
await openAddItemsPanel(page);
await expect(page.locator('[data-testid="pos-order-rail"]:visible')).toHaveCount(0);
const productPanel = page.locator('[data-testid="pos-order-add-items-panel"]:visible').first();
const cartPanel = page.locator('[data-testid="pos-order-panel-cart"]:visible').first();
const workspace = getVisibleTestId(page, "pos-order-workspace");
const productPanelBox = await productPanel.boundingBox();
const cartPanelBox = await cartPanel.boundingBox();
const workspaceBox = await workspace.boundingBox();
expect(productPanelBox).not.toBeNull();
expect(cartPanelBox).not.toBeNull();
expect(workspaceBox).not.toBeNull();
expect(workspaceBox?.width ?? 0).toBeGreaterThan(300);
expect(Math.abs((productPanelBox?.x ?? 0) - (cartPanelBox?.x ?? 0))).toBeLessThanOrEqual(12);
expect(Math.abs((productPanelBox?.width ?? 0) - (cartPanelBox?.width ?? 0))).toBeLessThanOrEqual(24);
expect(cartPanelBox?.y ?? 0).toBeGreaterThan((productPanelBox?.y ?? 0) + 100);
await productPanel.locator("select").first().selectOption("8");
await expect(productPanel.getByTestId("pos-product-card-64")).toBeVisible();
await expect(productPanel.getByTestId("pos-product-card-53")).toHaveCount(0);
await productPanel.getByTestId("pos-product-card-64").click();
const addRequest = waitForOrderItemMutation(page, "POST", "/order/items");
await productPanel.getByTestId("pos-add-to-cart-64").click();
await addRequest;
await expect(getVisibleTestId(page, "pos-order-item-edit-9200")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-item-name-9200")).toContainText("Vaskecertifikat");
await expectOrderTotal(page, 1397);
await page.locator('[data-testid="pos-order-add-items-back"]:visible').first().click();
await expect(page.locator('[data-testid="pos-order-add-items-panel"]:visible')).toHaveCount(0);
await expect(getVisibleTestId(page, "pos-order-rail")).toBeVisible();
const restoredMainBox = await main.boundingBox();
expect(restoredMainBox).not.toBeNull();
expect(Math.abs((restoredMainBox?.width ?? 0) - (beforeMainBox?.width ?? 0))).toBeLessThanOrEqual(20);
await page.reload();
await expect(page.getByTestId("pos-order-detail")).toBeVisible();
await expect(getVisibleTestId(page, "pos-order-item-edit-9200")).toBeVisible();
await expectOrderTotal(page, 1397);
});
});