Introduce completeBookingCreationFlow utility and refactor booking flow tests:
- Added `completeBookingCreationFlow` utility to streamline booking creation logic in `bookingFlow.ts`. - Refactored `userBookings.spec.ts` to replace inline booking creation steps with the new utility for improved readability and consistency. - Applied `data-testid` attributes across booking-related components for enhanced testability and test coverage. - Enhanced Playwright navigation error handling with `isBenignNavigationError` and `settleAuthenticatedNavigation` utilities to improve resilience and test robustness.
This commit is contained in:
+132
-54
@@ -70,6 +70,17 @@ function normalizeCreatedAtValue(value) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeRegistrationValue(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return String(value)
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z0-9]/g, "");
|
||||
}
|
||||
|
||||
function normalizeIncludeInInvoiceValue(value) {
|
||||
if (value === null || value === undefined || value === "" || value === "use_department" || value === "null") {
|
||||
return null;
|
||||
@@ -807,13 +818,39 @@ export function createPosFixture(overrides = {}) {
|
||||
[defaultCustomer.customerNumber]: defaultCustomer,
|
||||
[cardCustomer.customerNumber]: cardCustomer,
|
||||
},
|
||||
collectedInvoices: [101, 102, 103, 104, 105, 106].map((invoiceId, index) => ({
|
||||
id: invoiceId,
|
||||
customer_number: 1001 + index,
|
||||
customer_name: `Customer ${invoiceId}`,
|
||||
total_net_amount: 100 + index * 10,
|
||||
})),
|
||||
nextCollectedInvoiceId: 200,
|
||||
collectedInvoices: [
|
||||
{
|
||||
id: 101,
|
||||
customer_number: defaultCustomer.customerNumber,
|
||||
customer_name: defaultCustomer.name,
|
||||
total_net_amount: 100,
|
||||
created_at: "2026-04-10",
|
||||
closed_at: "2026-04-10",
|
||||
},
|
||||
{
|
||||
id: 200,
|
||||
customer_number: cardCustomer.customerNumber,
|
||||
customer_name: cardCustomer.name,
|
||||
total_net_amount: 250,
|
||||
created_at: "2026-04-30",
|
||||
closed_at: "2026-04-30",
|
||||
},
|
||||
{
|
||||
id: 201,
|
||||
customer_number: cardCustomer.customerNumber,
|
||||
customer_name: cardCustomer.name,
|
||||
total_net_amount: 325,
|
||||
created_at: "2026-05-01",
|
||||
closed_at: "2026-05-01",
|
||||
},
|
||||
...[102, 103, 104, 105, 106].map((invoiceId, index) => ({
|
||||
id: invoiceId,
|
||||
customer_number: 1002 + index,
|
||||
customer_name: `Customer ${invoiceId}`,
|
||||
total_net_amount: 110 + index * 10,
|
||||
})),
|
||||
],
|
||||
nextCollectedInvoiceId: 300,
|
||||
customerAttributesByNumber: {
|
||||
[defaultCustomer.customerNumber]: [
|
||||
{ id: 1, customer_number: defaultCustomer.customerNumber, attribute: "invoiceAllOrdersIndividually" },
|
||||
@@ -1298,9 +1335,9 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
|
||||
department_id: Number(body.department_id || body.department || 12),
|
||||
reference: body.reference || "",
|
||||
notes: body.notes || "",
|
||||
reg_1: body.reg_1 || "",
|
||||
reg_2: body.reg_2 || "",
|
||||
reg_3: body.reg_3 || "",
|
||||
reg_1: normalizeRegistrationValue(body.reg_1),
|
||||
reg_2: normalizeRegistrationValue(body.reg_2),
|
||||
reg_3: normalizeRegistrationValue(body.reg_3),
|
||||
invoice_collection_id: null,
|
||||
booking_id: null,
|
||||
completed_at: null,
|
||||
@@ -1328,6 +1365,15 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
|
||||
posFixture.ordersById[orderId] = {
|
||||
...posFixture.ordersById[orderId],
|
||||
...body,
|
||||
...(Object.prototype.hasOwnProperty.call(body, "reg_1")
|
||||
? { reg_1: normalizeRegistrationValue(body.reg_1) }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "reg_2")
|
||||
? { reg_2: normalizeRegistrationValue(body.reg_2) }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "reg_3")
|
||||
? { reg_3: normalizeRegistrationValue(body.reg_3) }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "created_at")
|
||||
? { created_at: normalizeCreatedAtValue(body.created_at) }
|
||||
: {}),
|
||||
@@ -1350,6 +1396,8 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
|
||||
posFixture.ordersById[orderId][body.field] =
|
||||
body.field === "include_in_invoice"
|
||||
? normalizeIncludeInInvoiceValue(body.value)
|
||||
: body.field === "reg_1" || body.field === "reg_2" || body.field === "reg_3"
|
||||
? normalizeRegistrationValue(body.value)
|
||||
: body.field === "created_at"
|
||||
? normalizeCreatedAtValue(body.value)
|
||||
: body.value;
|
||||
@@ -1357,6 +1405,15 @@ async function handlePosRoute({ route, request, parsedUrl, pathname, method, pos
|
||||
posFixture.ordersById[orderId] = {
|
||||
...posFixture.ordersById[orderId],
|
||||
...body,
|
||||
...(Object.prototype.hasOwnProperty.call(body, "reg_1")
|
||||
? { reg_1: normalizeRegistrationValue(body.reg_1) }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "reg_2")
|
||||
? { reg_2: normalizeRegistrationValue(body.reg_2) }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "reg_3")
|
||||
? { reg_3: normalizeRegistrationValue(body.reg_3) }
|
||||
: {}),
|
||||
...(Object.prototype.hasOwnProperty.call(body, "created_at")
|
||||
? { created_at: normalizeCreatedAtValue(body.created_at) }
|
||||
: {}),
|
||||
@@ -1936,7 +1993,10 @@ async function handleEdgeGatewayRoute({
|
||||
}
|
||||
|
||||
export async function mockApi(page, options = {}) {
|
||||
const posFixture = options.pos ? createPosFixture(options.pos === true ? {} : options.pos) : null;
|
||||
const shouldMockPosFixture = Boolean(options.pos || options.invoiceDistribution);
|
||||
const posFixture = shouldMockPosFixture
|
||||
? createPosFixture(options.pos && options.pos !== true ? options.pos : {})
|
||||
: null;
|
||||
const selfServe = options.selfServe
|
||||
? createSelfServeFixture(options.selfServe === true ? {} : options.selfServe)
|
||||
: null;
|
||||
@@ -2402,6 +2462,61 @@ export async function mockApi(page, options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/collected-invoices") && method === "GET") {
|
||||
const invoiceFixture = posFixture || { collectedInvoices: [] };
|
||||
const filters = parseFilterExpressions(parsedUrl.searchParams.get("filters") || "");
|
||||
const customerNumberFilter = Number(filters.customer_number || 0);
|
||||
const collectedInvoices = (invoiceFixture.collectedInvoices || []).filter((invoice) => {
|
||||
if (!customerNumberFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Number(invoice.customer_number) === customerNumberFilter;
|
||||
});
|
||||
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: collectedInvoices,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/collected-invoices") && method === "POST") {
|
||||
const invoiceFixture = posFixture || {
|
||||
collectedInvoices: [],
|
||||
customersByNumber: {},
|
||||
nextCollectedInvoiceId: 200,
|
||||
};
|
||||
const body = request.postDataJSON?.() || {};
|
||||
const invoiceId = Number(invoiceFixture.nextCollectedInvoiceId || 200);
|
||||
const customerNumber = Number(body.customer_number || 0);
|
||||
const customer = invoiceFixture.customersByNumber?.[customerNumber] || null;
|
||||
|
||||
invoiceFixture.nextCollectedInvoiceId = invoiceId + 1;
|
||||
invoiceFixture.collectedInvoices = [
|
||||
{
|
||||
id: invoiceId,
|
||||
customer_number: customerNumber,
|
||||
customer_name: customer?.name || `Customer ${invoiceId}`,
|
||||
total_net_amount: 0,
|
||||
created_at: body.closed_at || toSqlDateTime().slice(0, 10),
|
||||
closed_at: body.closed_at || null,
|
||||
},
|
||||
...(invoiceFixture.collectedInvoices || []),
|
||||
];
|
||||
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
id: invoiceId,
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.invoiceDistribution) {
|
||||
const monthFromDate = parsedUrl.searchParams.get("dateFrom");
|
||||
const monthNumber = monthFromDate ? Number(monthFromDate.split("-")[1]) : 1;
|
||||
@@ -2766,45 +2881,6 @@ export async function mockApi(page, options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/collected-invoices") && method === "GET") {
|
||||
await route.fulfill(
|
||||
json({
|
||||
data: posFixture.collectedInvoices || [],
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/collected-invoices") && method === "POST") {
|
||||
const body = request.postDataJSON?.() || {};
|
||||
const invoiceId = Number(posFixture.nextCollectedInvoiceId || 200);
|
||||
const customerNumber = Number(body.customer_number || 0);
|
||||
const customer = posFixture.customersByNumber?.[customerNumber] || null;
|
||||
|
||||
posFixture.nextCollectedInvoiceId = invoiceId + 1;
|
||||
posFixture.collectedInvoices = [
|
||||
{
|
||||
id: invoiceId,
|
||||
customer_number: customerNumber,
|
||||
customer_name: customer?.name || `Customer ${invoiceId}`,
|
||||
total_net_amount: 0,
|
||||
created_at: body.closed_at || toSqlDateTime().slice(0, 10),
|
||||
closed_at: body.closed_at || null,
|
||||
},
|
||||
...(posFixture.collectedInvoices || []),
|
||||
];
|
||||
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
id: invoiceId,
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/collected-invoices/economic/compare") && method === "GET") {
|
||||
const invoiceId = Number(parsedUrl.searchParams.get("collected_invoice_id") || 0);
|
||||
const mismatch = invoiceId === 101 || invoiceId === 103;
|
||||
@@ -2912,11 +2988,13 @@ export async function seedAuthenticatedState(page, token = "e2e-token") {
|
||||
}, token);
|
||||
}
|
||||
|
||||
export async function primeMockSession(page, { token = "e2e-token", bootPath = "/login" } = {}) {
|
||||
export async function primeMockSession(page, { token = "e2e-token", bootPath = "/redirect" } = {}) {
|
||||
await seedAuthenticatedState(page, token);
|
||||
const sessionRequest = page.waitForResponse((response) => {
|
||||
return response.request().method() === "GET" && response.url().includes("/auth/session");
|
||||
});
|
||||
const sessionRequest = page
|
||||
.waitForResponse((response) => {
|
||||
return response.request().method() === "GET" && response.url().includes("/auth/session");
|
||||
}, { timeout: 10_000 })
|
||||
.catch(() => null);
|
||||
await page.goto(bootPath);
|
||||
await sessionRequest;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user