Add unit tests for Orders navigation count refresh and fallback customer caching, update POS mobile accessibility labels:
- Introduced `orders-draft-count-refresh.spec.js` for testing `Orders` navigation hooks on customer changes and order deletions. - Enhanced `use-draft-transaction-customer.spec.js` with tests for clearing cached fallback customers. - Updated POS mobile components with accessibility labels and localization for image viewer and customer suggestion buttons. - Added support for `vehicleCustomerSuggestionsGet` handling in e2e mocks and tests.
This commit is contained in:
@@ -116,6 +116,12 @@ function buildTodayTimestamp(time = "08:00:00.000Z") {
|
||||
return `${todayIsoDate}T${time}`;
|
||||
}
|
||||
|
||||
async function forceLocale(page, locale = "en") {
|
||||
await page.addInitScript((value) => {
|
||||
window.localStorage.setItem("locale", value);
|
||||
}, locale);
|
||||
}
|
||||
|
||||
function createMultiBookingFixture({ reg, vehicle = {}, bookings = [] }) {
|
||||
const baseFixture = createMobilePosFixture();
|
||||
return createMobilePosFixture({
|
||||
@@ -436,7 +442,7 @@ test("mobile customer popup exposes the draft quick action and selects the confi
|
||||
seedState: {
|
||||
customerId: null,
|
||||
includePrimaryItem: false,
|
||||
reg: "AB12345",
|
||||
reg: "FREE123",
|
||||
reference: "MOBILE-DRAFT",
|
||||
},
|
||||
route: {
|
||||
@@ -469,6 +475,94 @@ test("mobile customer popup exposes the draft quick action and selects the confi
|
||||
await expect(page.getByTestId("pos-mobile-customer-name")).toContainText(MOBILE_DRAFT_TRANSACTION_CUSTOMER_NAME);
|
||||
});
|
||||
|
||||
test("mobile customer popup applies a previous-customer suggestion", async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name !== "chromium-mobile", "Mobile POS order suite is scoped to chromium-mobile.");
|
||||
|
||||
const suggestedCustomerId = 55667788;
|
||||
const suggestedCustomerName = "Suggestion Logistics";
|
||||
const secondarySuggestedCustomerId = 66778899;
|
||||
const fixture = createMobilePosFixture({
|
||||
customersByNumber: {
|
||||
[suggestedCustomerId]: {
|
||||
id: suggestedCustomerId,
|
||||
customerNumber: suggestedCustomerId,
|
||||
name: suggestedCustomerName,
|
||||
address: "Suggestion Street 8",
|
||||
zip: "2630",
|
||||
city: "Taastrup",
|
||||
mobilePhone: "55667788",
|
||||
email: "suggestion@example.com",
|
||||
corporateIdentificationNumber: "55667788",
|
||||
barred: false,
|
||||
economic_customer: suggestedCustomerId,
|
||||
},
|
||||
[secondarySuggestedCustomerId]: {
|
||||
id: secondarySuggestedCustomerId,
|
||||
customerNumber: secondarySuggestedCustomerId,
|
||||
name: "Fallback Suggestion",
|
||||
address: "Fallback Street 9",
|
||||
zip: "2630",
|
||||
city: "Taastrup",
|
||||
mobilePhone: "66778899",
|
||||
email: "fallback@example.com",
|
||||
corporateIdentificationNumber: "66778899",
|
||||
barred: false,
|
||||
economic_customer: secondarySuggestedCustomerId,
|
||||
},
|
||||
},
|
||||
vehicleCustomerSuggestionsByReg: {
|
||||
FREE123: [
|
||||
{
|
||||
id: 901,
|
||||
customer_number: suggestedCustomerId,
|
||||
customer_name: suggestedCustomerName,
|
||||
barred: false,
|
||||
},
|
||||
{
|
||||
id: 902,
|
||||
customer_number: secondarySuggestedCustomerId,
|
||||
customer_name: "Fallback Suggestion",
|
||||
barred: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "pos-mobile-previous-customer-suggestion-token",
|
||||
seedState: {
|
||||
customerId: null,
|
||||
includePrimaryItem: false,
|
||||
reg: "FREE123",
|
||||
reference: "MOBILE-SUGGESTION",
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await openCustomerPopupFromStep1(page);
|
||||
await expect(page.getByTestId("pos-customer-suggestions")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId(`pos-customer-suggestion-select-${suggestedCustomerId}`)).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await page.getByTestId(`pos-customer-suggestion-select-${suggestedCustomerId}`).click();
|
||||
await expect(page.getByTestId("pos-mobile-customer-popup")).toBeHidden({ timeout: 10_000 });
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const snapshot = await getStoredPosSnapshot(page);
|
||||
return snapshot?.metadata?.customerId ?? null;
|
||||
})
|
||||
.toBe(suggestedCustomerId);
|
||||
|
||||
await waitForMobileNextStepCooldown(page);
|
||||
await page.getByTestId("pos-mobile-next-step").click();
|
||||
await waitForMobileStepTwoReady(page);
|
||||
await expect(page.getByTestId("pos-mobile-customer-name")).toContainText(suggestedCustomerName);
|
||||
});
|
||||
|
||||
test("mobile customer popup defaults to customer invoice mode and keeps customer search visible", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
@@ -998,6 +1092,74 @@ test.describe("POS mobile order flow", () => {
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("stored step 1 vehicles without status normalize to unknown without Vue prop warnings", async ({ page }) => {
|
||||
const fixture = createMobilePosFixture();
|
||||
const consoleProblems = [];
|
||||
const pageErrors = [];
|
||||
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error" || message.type() === "warning" || message.text().includes('prop "status"')) {
|
||||
consoleProblems.push(`${message.type()}: ${message.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.stack || error.message);
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "stored-step1-missing-status-token",
|
||||
seedState: false,
|
||||
route: {
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
const storedValue = window.localStorage.getItem("pos");
|
||||
const snapshot = storedValue ? JSON.parse(storedValue) : {};
|
||||
snapshot.vehicles = snapshot.vehicles || {};
|
||||
snapshot.vehicles.vehicle_1 = {
|
||||
reg: "EC2123",
|
||||
customer_id: null,
|
||||
type: null,
|
||||
booking_id: null,
|
||||
booking_matches: [],
|
||||
wash_subscription: false,
|
||||
barred: false,
|
||||
reference: "",
|
||||
last_order_id: null,
|
||||
};
|
||||
snapshot.vehicles.vehicle_2 = null;
|
||||
snapshot.vehicles.vehicle_3 = null;
|
||||
snapshot.vehicles.activeVehicleIndex = 1;
|
||||
window.localStorage.setItem("pos", JSON.stringify(snapshot));
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
await expect(page.getByTestId("pos-mobile-step-1-shell")).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const snapshot = await getStoredPosSnapshot(page);
|
||||
return {
|
||||
reg: snapshot?.vehicles?.vehicle_1?.reg ?? null,
|
||||
status: snapshot?.vehicles?.vehicle_1?.status ?? null,
|
||||
};
|
||||
},
|
||||
{ timeout: 10_000 }
|
||||
)
|
||||
.toEqual({
|
||||
reg: "EC2123",
|
||||
status: "unknown",
|
||||
});
|
||||
|
||||
const combinedProblems = [...consoleProblems, ...pageErrors].join("\n");
|
||||
expect(combinedProblems).not.toContain('Invalid prop: type check failed for prop "status"');
|
||||
expect(combinedProblems).not.toContain('Expected String with value "undefined"');
|
||||
});
|
||||
|
||||
test("matched vehicle manual input seeds the step 2 reference and primary product defaults", async ({ page }) => {
|
||||
const fixture = createMobilePosFixture();
|
||||
await createOrderFromStep1(page, fixture, {
|
||||
@@ -2292,6 +2454,8 @@ test.describe("POS mobile order flow", () => {
|
||||
},
|
||||
});
|
||||
|
||||
await forceLocale(page, "en");
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "mobile-action-metadata-token",
|
||||
seedState: {
|
||||
@@ -2317,7 +2481,7 @@ test.describe("POS mobile order flow", () => {
|
||||
await expect(clearAllButton).toHaveAttribute("data-action-key", "pos-mobile-clear-all");
|
||||
await expect(completeButton).toHaveAttribute("data-copy-key", "complete");
|
||||
await expect(clearAllButton).toHaveAttribute("data-copy-key", "clear_all");
|
||||
await expect(completeButton).toContainText(/Afslut|Fuldf/);
|
||||
await expect(completeButton).toContainText(/Complete|Afslut|Fuldf/);
|
||||
await expectCleanActionText(completeButton);
|
||||
await expectCleanActionText(clearAllButton);
|
||||
|
||||
@@ -2329,21 +2493,63 @@ test.describe("POS mobile order flow", () => {
|
||||
const attachmentsToggle = page.getByTestId("pos-mobile-attachments-toggle");
|
||||
await expect(attachmentsToggle).toBeVisible({ timeout: 10_000 });
|
||||
await expect(attachmentsToggle).toHaveAttribute("data-action-key", "pos-mobile-attachments-toggle");
|
||||
await expect(attachmentsToggle).toContainText("Attachments");
|
||||
await expectCleanActionText(attachmentsToggle);
|
||||
|
||||
await attachmentsToggle.click();
|
||||
|
||||
for (const actionKey of [
|
||||
"pos-mobile-attachment-view-take-picture",
|
||||
"pos-mobile-attachment-view-close",
|
||||
"pos-mobile-attachments-upload-file",
|
||||
"pos-mobile-attachments-wash-certificate",
|
||||
]) {
|
||||
const expectedLabelsByActionKey = {
|
||||
"pos-mobile-attachment-view-take-picture": "Take picture",
|
||||
"pos-mobile-attachment-view-close": "Close",
|
||||
"pos-mobile-attachments-upload-file": "Upload",
|
||||
"pos-mobile-attachments-wash-certificate": "Wash certificate",
|
||||
};
|
||||
|
||||
for (const [actionKey, label] of Object.entries(expectedLabelsByActionKey)) {
|
||||
const action = getByActionKey(page, actionKey);
|
||||
await expect(action).toBeVisible({ timeout: 10_000 });
|
||||
await expect(action).toHaveAttribute("data-action-key", actionKey);
|
||||
await expect(action).toContainText(label);
|
||||
await expectCleanActionText(action);
|
||||
}
|
||||
|
||||
await expect(page.locator("body")).not.toContainText("admin.pos.wash_certificate");
|
||||
});
|
||||
|
||||
test("mobile image viewer uses localized controls", async ({ page }) => {
|
||||
const imageAttachment = {
|
||||
filename: "damage.svg",
|
||||
base64String:
|
||||
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiI+PHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSJyZWQiLz48L3N2Zz4=",
|
||||
};
|
||||
|
||||
await forceLocale(page, "en");
|
||||
|
||||
await setupMobilePosPage(page, createMobilePosFixture(), {
|
||||
token: "mobile-image-viewer-locale-token",
|
||||
seedState: {
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
reg: "AB12345",
|
||||
includePrimaryItem: false,
|
||||
attachmentsBase64: [imageAttachment],
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-step-1")).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByTestId("pos-mobile-attachments-toggle").click();
|
||||
|
||||
const thumbnail = page.getByAltText("damage.svg");
|
||||
await expect(thumbnail).toBeVisible({ timeout: 10_000 });
|
||||
await thumbnail.click();
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-zoom-in")).toContainText("Zoom", { timeout: 10_000 });
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-zoom-out")).toContainText("Zoom out");
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-reset")).toContainText("Reset");
|
||||
await expect(page.getByTestId("pos-mobile-image-viewer-close")).toContainText("Close");
|
||||
});
|
||||
|
||||
test("step 2 sync is idempotent when the order already matches the local transaction", async ({ page }) => {
|
||||
@@ -2649,6 +2855,63 @@ test.describe("POS mobile order flow", () => {
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("booking completion popup reuses the step 2 safety seal without asking for it again", async ({ page }) => {
|
||||
const orderId = 9412;
|
||||
const fixture = createMobilePosFixture({
|
||||
ordersById: {
|
||||
[orderId]: buildRegularOrder(orderId, {
|
||||
reg_1: "SEAL321",
|
||||
reference: "",
|
||||
}),
|
||||
},
|
||||
orderItemsByOrderId: {
|
||||
[orderId]: [],
|
||||
},
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "mobile-booking-prefilled-seal-token",
|
||||
seedState: {
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
reg: "SEAL321",
|
||||
reference: "",
|
||||
includePrimaryItem: false,
|
||||
vehicleType: null,
|
||||
bookingId: 8103,
|
||||
vehicleStatus: "booked",
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 2,
|
||||
orderId,
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-step-2")).toBeVisible({ timeout: 10_000 });
|
||||
await waitForBookingHydration(page, {
|
||||
primaryId: 53,
|
||||
addonProductIds: [41],
|
||||
});
|
||||
|
||||
const safetySealInput = page.getByTestId("pos-mobile-safety-seal-step-2-input");
|
||||
await expect(safetySealInput).toBeVisible({ timeout: 10_000 });
|
||||
await safetySealInput.fill("5150");
|
||||
|
||||
await page.getByTestId("pos-mobile-next-step").click();
|
||||
|
||||
const popupSafetySealInput = page.getByTestId("pos-mobile-booking-safety-seal-input");
|
||||
await expect(page.getByTestId("pos-mobile-popup")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(popupSafetySealInput).toHaveValue("5150");
|
||||
|
||||
await page.getByTestId("pos-mobile-booking-complete-with-certificate").click();
|
||||
|
||||
await expect.poll(() => fixture.requestCounters.bookingComplete, { timeout: 10_000 }).toBe(1);
|
||||
await expect.poll(() => fixture.bookingsById[8103]?.status ?? "").toBe("completed");
|
||||
await expect.poll(() => String(fixture.bookingsById[8103]?.safety_seal ?? "")).toBe("5150");
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("booking completion failure keeps the mobile booking popup open for retry", async ({ page }) => {
|
||||
const orderId = 9410;
|
||||
const fixture = createMobilePosFixture({
|
||||
|
||||
Reference in New Issue
Block a user