Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4392b7915f |
+11
-13
@@ -3,8 +3,6 @@ name: Automated Tests
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
@@ -13,13 +11,13 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: frontend-tests-${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
|
||||
group: frontend-tests-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
format-tests:
|
||||
# CI runs on the repository's self-hosted runner pool.
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend]
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -29,6 +27,7 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Check AI workflow sync
|
||||
run: node scripts/sync-ai-workflow.mjs --check
|
||||
@@ -41,7 +40,7 @@ jobs:
|
||||
|
||||
build-and-unit:
|
||||
needs: format-tests
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend]
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -51,6 +50,7 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
if: github.event_name != 'schedule'
|
||||
needs: build-and-unit
|
||||
name: E2E-pr-${{ matrix.suite }}-${{ matrix.project }}
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend]
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -117,6 +117,7 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
@@ -129,10 +130,8 @@ jobs:
|
||||
env:
|
||||
MATRIX_SUITE: ${{ matrix.suite }}
|
||||
MATRIX_PROJECT: ${{ matrix.project }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
workflow_offset=$(( (RUN_ID % 90) * 600 ))
|
||||
case "$MATRIX_SUITE" in
|
||||
core) suite_offset=0 ;;
|
||||
changed) suite_offset=10 ;;
|
||||
@@ -143,7 +142,7 @@ jobs:
|
||||
chromium-mobile) project_offset=2 ;;
|
||||
*) echo "Unsupported Playwright PR project: $MATRIX_PROJECT" >&2; exit 1 ;;
|
||||
esac
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((10000 + workflow_offset + suite_offset + project_offset))" >> "$GITHUB_ENV"
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((5200 + suite_offset + project_offset))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Playwright smoke tests
|
||||
if: matrix.suite == 'core'
|
||||
@@ -183,7 +182,7 @@ jobs:
|
||||
(github.event_name == 'schedule' || needs.e2e-pr.result == 'success')
|
||||
needs: [build-and-unit, e2e-pr]
|
||||
name: E2E-full-${{ matrix.role }}-${{ matrix.browser_label }}-${{ matrix.device }}
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend]
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -213,6 +212,7 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
@@ -226,10 +226,8 @@ jobs:
|
||||
MATRIX_ROLE: ${{ matrix.role }}
|
||||
MATRIX_BROWSER: ${{ matrix.browser }}
|
||||
MATRIX_DEVICE: ${{ matrix.device }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
workflow_offset=$(( (RUN_ID % 90) * 600 ))
|
||||
case "$MATRIX_ROLE" in
|
||||
customer) role_offset=0 ;;
|
||||
subuser) role_offset=100 ;;
|
||||
@@ -249,7 +247,7 @@ jobs:
|
||||
desktop) device_offset=3 ;;
|
||||
*) echo "Unsupported Playwright device: $MATRIX_DEVICE" >&2; exit 1 ;;
|
||||
esac
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((10000 + workflow_offset + role_offset + browser_offset + device_offset))" >> "$GITHUB_ENV"
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((5300 + role_offset + browser_offset + device_offset))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run full Playwright slice
|
||||
run: |
|
||||
|
||||
@@ -53,6 +53,9 @@ import PosDepartmentStepMobile2AdditionalItems from "@/components/displays/depar
|
||||
import { pendingBookings } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import { useOrderMetadataAutosave } from "@/composables/useOrderMetadataAutosave.js";
|
||||
import { useHoldToTrigger } from "@/composables/useHoldToTrigger";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(() => {
|
||||
// Set the header to be transparent
|
||||
@@ -477,6 +480,8 @@ const updateLastOrderItemPrices = (items: PosOrderItem[]) => {
|
||||
const layout = {
|
||||
classes: <string[]>[],
|
||||
};
|
||||
const EXTRAORDINARY_CHEMISTRY_PRODUCT_ID = 27;
|
||||
const EXTRAORDINARY_CHEMISTRY_PRODUCT_NAME = "Ekstraordinær pr. 10 min inkl. kemi";
|
||||
|
||||
const onCopyLastOrder = (vehicleIndex: number) => {
|
||||
lastOrders.select(vehicleIndex);
|
||||
@@ -777,15 +782,18 @@ const buildDesiredOrderItemShapes = () => {
|
||||
|
||||
const addonShapes = (transactionItems.primaryItem.value.addons || [])
|
||||
.filter((addon: any) => Number(addon?.quantity ?? 0) > 0)
|
||||
.map((addon: any) => ({
|
||||
kind: "addon",
|
||||
relatedKey: "primary",
|
||||
product_id: Number(addon?.product?.id ?? addon?.id ?? 0),
|
||||
quantity: Number(addon?.quantity ?? 0),
|
||||
related_item_id: "__PRIMARY__",
|
||||
price: Number(addon?.product?.price ?? addon?.price ?? 0),
|
||||
notes: String(addon?.product?.notes ?? ""),
|
||||
}));
|
||||
.map((addon: any) => {
|
||||
const addonProduct = addon?.product ?? addon;
|
||||
return {
|
||||
kind: "addon",
|
||||
relatedKey: "primary",
|
||||
product_id: Number(addonProduct?.id ?? addon?.id ?? 0),
|
||||
quantity: Number(addon?.quantity ?? 0),
|
||||
related_item_id: "__PRIMARY__",
|
||||
price: Number(addonProduct?.price ?? addon?.price ?? 0),
|
||||
notes: String(addonProduct?.notes ?? addon?.notes ?? ""),
|
||||
};
|
||||
});
|
||||
|
||||
const additionalShapes = (transactionItems.additionalItems.value || [])
|
||||
.filter((item: any) => Number(item?.quantity ?? 0) > 0)
|
||||
@@ -952,16 +960,17 @@ const syncCurrentTransactionToOrder = async () => {
|
||||
|
||||
const addonPromises = (transactionItems.primaryItem.value.addons || [])
|
||||
.filter((addon: any) => Number(addon?.quantity ?? 0) > 0)
|
||||
.map((addon: any) =>
|
||||
createOrderItem(
|
||||
.map((addon: any) => {
|
||||
const addonProduct = addon?.product ?? addon;
|
||||
return createOrderItem(
|
||||
normalizedOrderId,
|
||||
addon.product.id,
|
||||
addonProduct.id,
|
||||
Number(addon.quantity),
|
||||
createdPrimaryItemId,
|
||||
addon.product?.notes || "",
|
||||
addon.product.price
|
||||
)
|
||||
);
|
||||
addonProduct?.notes || addon?.notes || "",
|
||||
addonProduct.price ?? addon.price
|
||||
);
|
||||
});
|
||||
|
||||
const additionalPromises = (transactionItems.additionalItems.value || [])
|
||||
.filter((item: any) => Number(item?.quantity ?? 0) > 0)
|
||||
@@ -973,11 +982,146 @@ const syncCurrentTransactionToOrder = async () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const normalizeText = (value: unknown) => String(value ?? "").trim();
|
||||
|
||||
const isEnabledFlag = (value: unknown) => value === true || value === 1 || value === "1" || value === "true";
|
||||
|
||||
const getProductId = (product: any) =>
|
||||
Number(product?.product?.id ?? product?.product_id ?? product?.id ?? 0);
|
||||
|
||||
const getProductName = (product: any) => normalizeText(product?.product?.name ?? product?.name);
|
||||
|
||||
const productRequiresOrderItemNote = (product: any) => {
|
||||
if (!product) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
isEnabledFlag(product?.requires_note ?? product?.product?.requires_note) ||
|
||||
getProductId(product) === EXTRAORDINARY_CHEMISTRY_PRODUCT_ID ||
|
||||
getProductName(product) === EXTRAORDINARY_CHEMISTRY_PRODUCT_NAME
|
||||
);
|
||||
};
|
||||
|
||||
const productHasOrderItemNote = (product: any) =>
|
||||
normalizeText(product?.notes ?? product?.product?.notes).length > 0;
|
||||
|
||||
const getSelectedProductsMissingRequiredNotes = () => {
|
||||
const missingProducts: any[] = [];
|
||||
const primaryProduct = transactionItems.primaryItem.value;
|
||||
|
||||
if (productRequiresOrderItemNote(primaryProduct) && !productHasOrderItemNote(primaryProduct)) {
|
||||
missingProducts.push(primaryProduct);
|
||||
}
|
||||
|
||||
(primaryProduct?.addons || [])
|
||||
.filter((addon: any) => Number(addon?.quantity ?? 0) > 0)
|
||||
.forEach((addon: any) => {
|
||||
const addonProduct = addon?.product ?? addon;
|
||||
if (
|
||||
(productRequiresOrderItemNote(addonProduct) || productRequiresOrderItemNote(addon)) &&
|
||||
!productHasOrderItemNote(addonProduct) &&
|
||||
!productHasOrderItemNote(addon)
|
||||
) {
|
||||
missingProducts.push(addonProduct);
|
||||
}
|
||||
});
|
||||
|
||||
(transactionItems.additionalItems.value || [])
|
||||
.filter((item: any) => Number(item?.quantity ?? 0) > 0)
|
||||
.forEach((item: any) => {
|
||||
if (productRequiresOrderItemNote(item) && !productHasOrderItemNote(item)) {
|
||||
missingProducts.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return missingProducts;
|
||||
};
|
||||
|
||||
const promptForRequiredProductNote = (product: any) =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
const hadOriginalNote = Object.prototype.hasOwnProperty.call(product, "notes");
|
||||
const originalNote = product?.notes;
|
||||
|
||||
const resolveAndClose = (didConfirm: boolean) => {
|
||||
if (!didConfirm) {
|
||||
if (hadOriginalNote) {
|
||||
product.notes = originalNote;
|
||||
} else {
|
||||
delete product.notes;
|
||||
}
|
||||
}
|
||||
if (popups.get()?.id === "add_product_note") {
|
||||
popups.clear();
|
||||
}
|
||||
resolve(didConfirm);
|
||||
};
|
||||
|
||||
popups.select("add_product_note", {
|
||||
title: `${t("common.note")}: ${getProductName(product) || `#${getProductId(product)}`}`,
|
||||
message: t("objects.products.columns.requires_note"),
|
||||
component: "add_product_note",
|
||||
hideHeader: true,
|
||||
style: { maxHeight: "40vh" },
|
||||
props: {
|
||||
product,
|
||||
validationMessage: "",
|
||||
},
|
||||
actionButtons: [
|
||||
{
|
||||
label: t("common.confirm"),
|
||||
description: t("common.confirm"),
|
||||
color: "primary",
|
||||
testId: "pos-mobile-product-note-confirm",
|
||||
onClick: () => {
|
||||
const activePopup = popups.get();
|
||||
const normalizedNote = normalizeText(activePopup?.props?.product?.notes);
|
||||
if (!normalizedNote) {
|
||||
if (activePopup?.props) {
|
||||
activePopup.props.validationMessage = t("objects.products.columns.requires_note");
|
||||
}
|
||||
return;
|
||||
}
|
||||
product.notes = normalizedNote;
|
||||
resolveAndClose(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("common.cancel"),
|
||||
description: t("common.cancel"),
|
||||
color: "light",
|
||||
testId: "pos-mobile-product-note-cancel",
|
||||
onClick: () => resolveAndClose(false),
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
const ensureRequiredOrderItemNotes = async () => {
|
||||
const missingProducts = getSelectedProductsMissingRequiredNotes();
|
||||
for (const product of missingProducts) {
|
||||
if (productHasOrderItemNote(product)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const didConfirm = await promptForRequiredProductNote(product);
|
||||
if (!didConfirm) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const onBeforeComplete = async () => {
|
||||
if (!transactionItems.primaryItem.value) {
|
||||
throw new Error("No primary item selected");
|
||||
}
|
||||
|
||||
if (!(await ensureRequiredOrderItemNotes())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await syncCurrentTransactionToOrder();
|
||||
return true;
|
||||
};
|
||||
|
||||
+4
-1
@@ -405,7 +405,10 @@ const onClick = async () => {
|
||||
|
||||
isProcessingClick.value = true;
|
||||
try {
|
||||
await props.onBeforeStep();
|
||||
const beforeStepResult = await props.onBeforeStep();
|
||||
if (beforeStepResult === false) {
|
||||
return;
|
||||
}
|
||||
// Proceed to the next step
|
||||
switch (step.value) {
|
||||
case 1:
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ watch(note, (newNote) => {
|
||||
class="input is-searched"
|
||||
v-model="note"
|
||||
type="text"
|
||||
data-testid="pos-mobile-product-note-input"
|
||||
placeholder="Indtast note"
|
||||
/>
|
||||
</div>
|
||||
|
||||
-2
@@ -40,7 +40,6 @@ const currentRoute = useRoute();
|
||||
const currentRouter = useRouter();
|
||||
const toast = useAppToast();
|
||||
const { fitView, getViewport, zoomIn, zoomOut, zoomTo } = useVueFlow({ id: "self-serve-studio-flow" });
|
||||
const PATH_OUTCOMES_CASE_LIMIT = 2048;
|
||||
|
||||
const parseIntOrZero = (value) => {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
@@ -900,7 +899,6 @@ const pathOutcomesRequestPayload = computed(() => {
|
||||
config_source: simulatorForm.value.config_source || "draft",
|
||||
hardware_mode: hardwareMode,
|
||||
include_hardware: hardwareMode !== "none",
|
||||
path_sample_limit: PATH_OUTCOMES_CASE_LIMIT,
|
||||
};
|
||||
});
|
||||
const syncScopeFromRoute = () => {
|
||||
|
||||
@@ -3560,6 +3560,99 @@ test.describe("POS mobile order flow", () => {
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("prompts for a required product note before completing mobile order items", async ({ page }) => {
|
||||
const orderId = 9415;
|
||||
const baseFixture = createMobilePosFixture();
|
||||
const product27 = {
|
||||
id: 27,
|
||||
name: "Ekstraordinær pr. 10 min inkl. kemi",
|
||||
description: "Extraordinary service requiring an item note",
|
||||
price: 125,
|
||||
subscription_allowed: true,
|
||||
category: 8,
|
||||
piktogram: "27",
|
||||
apply_category_discount: false,
|
||||
requires_note: false,
|
||||
is_wash: false,
|
||||
display_in_booking_form: true,
|
||||
order_priority: 5,
|
||||
addons: [],
|
||||
};
|
||||
const primaryProduct = {
|
||||
...fixtureProduct(53),
|
||||
addons: [
|
||||
...fixtureProduct(53).addons,
|
||||
{
|
||||
id: product27.id,
|
||||
name: product27.name,
|
||||
price: product27.price,
|
||||
product: { ...product27 },
|
||||
quantity: 1,
|
||||
min: 0,
|
||||
max: -1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const fixture = createMobilePosFixture({
|
||||
products: baseFixture.products
|
||||
.map((product) => {
|
||||
if (Number(product.id) !== 53) {
|
||||
return product;
|
||||
}
|
||||
return primaryProduct;
|
||||
})
|
||||
.concat(product27),
|
||||
ordersById: {
|
||||
[orderId]: buildRegularOrder(orderId),
|
||||
},
|
||||
orderItemsByOrderId: {
|
||||
[orderId]: [],
|
||||
},
|
||||
});
|
||||
|
||||
await setupMobilePosPage(page, fixture, {
|
||||
token: "mobile-product-27-note-token",
|
||||
seedState: {
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
reg: "AB12345",
|
||||
reference: "PRODUCT-27-NOTE",
|
||||
primaryItem: primaryProduct,
|
||||
vehicleType: 53,
|
||||
lastOrderId: null,
|
||||
},
|
||||
route: {
|
||||
step: 2,
|
||||
orderId,
|
||||
customerId: REGULAR_CUSTOMER_ID,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(page.getByTestId("pos-mobile-step-2")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId("pos-mobile-addon-27-value")).toHaveText("1", { timeout: 10_000 });
|
||||
|
||||
await page.getByTestId("pos-mobile-next-step").click();
|
||||
await expect(page.locator('[data-testid="pos-mobile-popup"][data-popup-id="add_product_note"]')).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await page.getByTestId("pos-mobile-product-note-input").fill("Cancelled note");
|
||||
await page.getByTestId("pos-mobile-product-note-cancel").click();
|
||||
await expect.poll(() => fixture.requestCounters.orderItemsPost, { timeout: 1_000 }).toBe(0);
|
||||
await expect.poll(() => fixture.requestCounters.markAsCompleted, { timeout: 1_000 }).toBe(0);
|
||||
|
||||
await page.getByTestId("pos-mobile-next-step").click();
|
||||
await expect(page.locator('[data-testid="pos-mobile-popup"][data-popup-id="add_product_note"]')).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await page.getByTestId("pos-mobile-product-note-input").fill("Extra chemical treatment on left side");
|
||||
await page.getByTestId("pos-mobile-product-note-confirm").click();
|
||||
|
||||
await expect.poll(() => fixture.requestCounters.orderItemsPost, { timeout: 10_000 }).toBe(2);
|
||||
await expect.poll(() => fixture.requestCounters.markAsCompleted, { timeout: 10_000 }).toBe(1);
|
||||
const product27Create = fixture.requestLog.orderItemCreates.find((entry) => Number(entry.product_id) === 27);
|
||||
expect(product27Create?.notes).toBe("Extra chemical treatment on left side");
|
||||
await waitForStepReset(page);
|
||||
});
|
||||
|
||||
test("booking hydration applies booking items, reference, notes, and po", async ({ page }) => {
|
||||
const orderId = 9405;
|
||||
const fixture = createMobilePosFixture({
|
||||
|
||||
@@ -2963,7 +2963,6 @@ test.describe("All-in-one self-serve studio", () => {
|
||||
await expect
|
||||
.poll(() => Object.prototype.hasOwnProperty.call(captured.pathOutcomes[0] || {}, "max_states"))
|
||||
.toBe(false);
|
||||
await expect.poll(() => captured.pathOutcomes[0]?.path_sample_limit).toBe(2048);
|
||||
await expect(page.getByTestId("studio-path-outcomes-error")).toHaveCount(0);
|
||||
await expect(page.getByTestId("studio-path-outcomes-summary")).toContainText("2");
|
||||
await expect(page.getByTestId("studio-path-outcome-detail")).toContainText("MACHINE");
|
||||
|
||||
@@ -9,6 +9,8 @@ export const DEFAULT_BOOKING_ID = 8101;
|
||||
export const REGULAR_CUSTOMER_ID = 12345;
|
||||
export const CARD_CUSTOMER_ID = 999;
|
||||
export const WASH_CERTIFICATE_PRODUCT_ID = 41;
|
||||
const EXTRAORDINARY_CHEMISTRY_PRODUCT_ID = 27;
|
||||
const EXTRAORDINARY_CHEMISTRY_PRODUCT_NAME = "Ekstraordinær pr. 10 min inkl. kemi";
|
||||
export const MOBILE_PERMISSIONS = ["admin", "department_access_1"];
|
||||
export const MOBILE_NEXT_STEP_COOLDOWN_MS = 2100;
|
||||
|
||||
@@ -325,6 +327,23 @@ function isWashCertificateProduct(product) {
|
||||
return /vaskecertifikat|wash certificate|safety seal/i.test(String(product?.name ?? product?.product?.name ?? ""));
|
||||
}
|
||||
|
||||
function isEnabledFlag(value) {
|
||||
return value === true || value === 1 || value === "1" || value === "true";
|
||||
}
|
||||
|
||||
function productRequiresOrderItemNote(product) {
|
||||
if (!product) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const productId = Number(product?.product?.id ?? product?.product_id ?? product?.id ?? 0);
|
||||
return (
|
||||
isEnabledFlag(product?.requires_note ?? product?.product?.requires_note) ||
|
||||
productId === EXTRAORDINARY_CHEMISTRY_PRODUCT_ID ||
|
||||
String(product?.product?.name ?? product?.name ?? "").trim() === EXTRAORDINARY_CHEMISTRY_PRODUCT_NAME
|
||||
);
|
||||
}
|
||||
|
||||
function orderContainsWashCertificate(fixture, orderId) {
|
||||
return (fixture.orderItemsByOrderId[orderId] || []).some((item) => isWashCertificateProduct(item?.product || item));
|
||||
}
|
||||
@@ -1960,6 +1979,20 @@ export async function mockMobilePosApi(page, fixture) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (productRequiresOrderItemNote(product) && String(body.notes ?? "").trim() === "") {
|
||||
await route.fulfill(
|
||||
json(
|
||||
{
|
||||
success: false,
|
||||
data: {
|
||||
message: "Notes is required for this product",
|
||||
},
|
||||
},
|
||||
400
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
const orderItemId = fixture.nextOrderItemId++;
|
||||
const item = buildOrderItem(
|
||||
product,
|
||||
|
||||
@@ -150,8 +150,6 @@ describe("self-serve studio task editing", () => {
|
||||
"const machineStartPathResultList = computed(() => pathResultList.value.filter(pathCaseShowsMachineStartTask))"
|
||||
);
|
||||
expect(source).toContain("const pathCaseList = computed(() => machineStartPathResultList.value)");
|
||||
expect(source).toContain("const PATH_OUTCOMES_CASE_LIMIT = 2048");
|
||||
expect(source).toContain("path_sample_limit: PATH_OUTCOMES_CASE_LIMIT");
|
||||
expect(source).toContain('data-testid="studio-path-hidden-non-machine-start-cases"');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user