Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4392b7915f | ||
|
|
b1fb92549c | ||
|
|
9bba666f3a | ||
|
|
dcf0fd61ba | ||
|
|
3d02146911 | ||
|
|
10bc822233 | ||
|
|
baac1a243a | ||
|
|
7c233d7c03 | ||
|
|
7c19dbddf0 | ||
|
|
f4b248573a | ||
|
|
14e454e9d9 | ||
|
|
ebab7d8804 | ||
|
|
78a3fb1879 |
@@ -125,6 +125,25 @@ jobs:
|
||||
- name: Install Playwright browsers
|
||||
run: node scripts/install-playwright-browsers.mjs chromium
|
||||
|
||||
- name: Set Playwright dev server port
|
||||
shell: bash
|
||||
env:
|
||||
MATRIX_SUITE: ${{ matrix.suite }}
|
||||
MATRIX_PROJECT: ${{ matrix.project }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$MATRIX_SUITE" in
|
||||
core) suite_offset=0 ;;
|
||||
changed) suite_offset=10 ;;
|
||||
*) echo "Unsupported Playwright PR suite: $MATRIX_SUITE" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_PROJECT" in
|
||||
chromium-desktop) project_offset=1 ;;
|
||||
chromium-mobile) project_offset=2 ;;
|
||||
*) echo "Unsupported Playwright PR project: $MATRIX_PROJECT" >&2; exit 1 ;;
|
||||
esac
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((5200 + suite_offset + project_offset))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Playwright smoke tests
|
||||
if: matrix.suite == 'core'
|
||||
run: |
|
||||
@@ -201,6 +220,35 @@ jobs:
|
||||
- name: Install Playwright browsers
|
||||
run: node scripts/install-playwright-browsers.mjs ${{ matrix.browser_install }}
|
||||
|
||||
- name: Set Playwright dev server port
|
||||
shell: bash
|
||||
env:
|
||||
MATRIX_ROLE: ${{ matrix.role }}
|
||||
MATRIX_BROWSER: ${{ matrix.browser }}
|
||||
MATRIX_DEVICE: ${{ matrix.device }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$MATRIX_ROLE" in
|
||||
customer) role_offset=0 ;;
|
||||
subuser) role_offset=100 ;;
|
||||
admin) role_offset=200 ;;
|
||||
superuser) role_offset=300 ;;
|
||||
*) echo "Unsupported Playwright role: $MATRIX_ROLE" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_BROWSER" in
|
||||
chromium) browser_offset=0 ;;
|
||||
firefox) browser_offset=30 ;;
|
||||
webkit) browser_offset=60 ;;
|
||||
*) echo "Unsupported Playwright browser: $MATRIX_BROWSER" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_DEVICE" in
|
||||
mobile) device_offset=1 ;;
|
||||
tablet) device_offset=2 ;;
|
||||
desktop) device_offset=3 ;;
|
||||
*) echo "Unsupported Playwright device: $MATRIX_DEVICE" >&2; exit 1 ;;
|
||||
esac
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((5300 + role_offset + browser_offset + device_offset))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run full Playwright slice
|
||||
run: |
|
||||
ulimit -n 16384 || true
|
||||
|
||||
@@ -42,11 +42,10 @@ const writeOutput = (result) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isUnsupportedWithDepsFailure = (result) => {
|
||||
const hasUnsupportedHostPlatformFailure = (result) => {
|
||||
const output = outputText(result);
|
||||
return (
|
||||
result.status !== 0 &&
|
||||
/Cannot install dependencies for .* with Playwright/i.test(output) &&
|
||||
/Playwright does not support .* on /i.test(output)
|
||||
);
|
||||
};
|
||||
@@ -58,7 +57,7 @@ if (withDepsResult.status === 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!isUnsupportedWithDepsFailure(withDepsResult)) {
|
||||
if (!hasUnsupportedHostPlatformFailure(withDepsResult)) {
|
||||
process.exit(withDepsResult.status ?? 1);
|
||||
}
|
||||
|
||||
@@ -78,14 +77,14 @@ if (!fallbackHostPlatform) {
|
||||
console.warn(
|
||||
[
|
||||
`Playwright could not install OS dependencies for ${unsupportedPlatform}.`,
|
||||
`Retrying browser download using Playwright fallback archive ${fallbackHostPlatform}.`,
|
||||
`Retrying browser installation using Playwright fallback archive ${fallbackHostPlatform}.`,
|
||||
"The self-hosted runner image must provide the required browser system libraries.",
|
||||
].join("\n")
|
||||
);
|
||||
|
||||
const browserOnlyResult = runPlaywrightInstall(requestedBrowsers, {
|
||||
const fallbackResult = runPlaywrightInstall(requestedBrowsers, {
|
||||
PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: fallbackHostPlatform,
|
||||
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS: "1",
|
||||
});
|
||||
writeOutput(browserOnlyResult);
|
||||
process.exit(browserOnlyResult.status ?? 1);
|
||||
writeOutput(fallbackResult);
|
||||
process.exit(fallbackResult.status ?? 1);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -295,10 +295,23 @@ const branchWarning = computed(() =>
|
||||
min-width: 12rem;
|
||||
}
|
||||
|
||||
.release-context-bar__status {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.release-context-bar__status .tag {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.release-context-bar__warning {
|
||||
color: #9f1f17;
|
||||
flex: 1 1 12rem;
|
||||
font-size: 0.82rem;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.25;
|
||||
min-width: min(12rem, 100%);
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.release-context-bar__endpoints {
|
||||
|
||||
@@ -250,16 +250,18 @@ export function useWashSessionActions(options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectedWashType = radioWashType.value === "Machine" ? "Machine" : "Manual";
|
||||
const startResponse = await executeSelfServeCommand(laneId, "START", {
|
||||
customer_number: parseInt(customerNumber),
|
||||
license_plate: licensePlate.trim().toUpperCase(),
|
||||
wash_type: selectedWashType,
|
||||
defer_relay_side_effects: true,
|
||||
});
|
||||
if (!startResponse) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (radioWashType.value === "Machine" && isServiceAllowed("MACHINE")) {
|
||||
if (selectedWashType === "Machine" && isServiceAllowed("MACHINE")) {
|
||||
let machineRelayResponse = null;
|
||||
try {
|
||||
machineRelayResponse = await enableMachineRelay(laneId);
|
||||
|
||||
+11
-7
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { BSkeleton } from "buefy";
|
||||
|
||||
import { departments as loadedDepartments } from "@/components/pagination/departmentTabs.vue";
|
||||
@@ -24,6 +24,14 @@ const washes = ref(0);
|
||||
const outsideHours = ref(createEmptyOutsideHours());
|
||||
|
||||
const identifier = "DepartmentDailyReportThisWeek";
|
||||
const departmentSelectionKey = computed(() => (
|
||||
Array.isArray(props.departments)
|
||||
? props.departments
|
||||
.map((department) => Number(department?.id ?? department))
|
||||
.filter((departmentId) => departmentId > 0)
|
||||
.join(",")
|
||||
: ""
|
||||
));
|
||||
|
||||
const resetSummary = () => {
|
||||
income.value = 0;
|
||||
@@ -92,13 +100,9 @@ const getTransactionsInSelection = async () => {
|
||||
finished_loading(fetch_id, identifier);
|
||||
};
|
||||
|
||||
watch([() => selected_date.value, () => selected_date_to.value], () => {
|
||||
watch([() => selected_date.value, () => selected_date_to.value, departmentSelectionKey], () => {
|
||||
getTransactionsInSelection();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getTransactionsInSelection();
|
||||
});
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -331,8 +331,16 @@ const isMachineTask = (task: any) => {
|
||||
return hasMachineService || isDynamicImageTask(task) || isLegacyMachineButtonTask(task);
|
||||
};
|
||||
|
||||
const hasMachineTasks = computed(() => activeTasks.value.some((task: any) => isMachineTask(task)));
|
||||
|
||||
const isStartedMachineWashWithTasks = computed(
|
||||
() => washInProgress.value && radioWashType.value === "Machine" && hasMachineTasks.value
|
||||
);
|
||||
|
||||
const isMachineWashSelectedAndAllowed = computed(
|
||||
() => radioWashType.value === "Machine" && isMachineAvailable(radioLaneOption.value)
|
||||
() =>
|
||||
radioWashType.value === "Machine" &&
|
||||
(isMachineAvailable(radioLaneOption.value) || isStartedMachineWashWithTasks.value)
|
||||
);
|
||||
|
||||
const displayedActiveTasks = computed(() => {
|
||||
@@ -343,8 +351,6 @@ const displayedActiveTasks = computed(() => {
|
||||
return activeTasks.value.filter((task: any) => !isMachineTask(task));
|
||||
});
|
||||
|
||||
const hasMachineTasks = computed(() => activeTasks.value.some((task: any) => isMachineTask(task)));
|
||||
|
||||
const shouldDefaultToMachineWash = computed(
|
||||
() =>
|
||||
!hasExplicitWashTypeSelection.value &&
|
||||
|
||||
@@ -34,9 +34,11 @@ async function gotoEdgeAgentView(
|
||||
|
||||
try {
|
||||
let lastNavigationError = null;
|
||||
let lastReadyError = null;
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
loadErrors.length = 0;
|
||||
lastNavigationError = null;
|
||||
lastReadyError = null;
|
||||
try {
|
||||
await page.goto(viewPath, { waitUntil: "domcontentloaded" });
|
||||
} catch (error) {
|
||||
@@ -48,12 +50,11 @@ async function gotoEdgeAgentView(
|
||||
throw lastNavigationError;
|
||||
}
|
||||
|
||||
const viewReady = await readyLocator
|
||||
.isVisible({ timeout: edgeGatewayNavigationTimeouts[attempt] })
|
||||
.catch(() => false);
|
||||
|
||||
if (viewReady) {
|
||||
try {
|
||||
await readyLocator.waitFor({ state: "visible", timeout: edgeGatewayNavigationTimeouts[attempt] });
|
||||
return;
|
||||
} catch (error) {
|
||||
lastReadyError = error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +62,10 @@ async function gotoEdgeAgentView(
|
||||
throw lastNavigationError;
|
||||
}
|
||||
|
||||
if (lastReadyError) {
|
||||
throw lastReadyError;
|
||||
}
|
||||
|
||||
await expect(readyLocator).toBeVisible({ timeout: edgeGatewayNavigationTimeouts.at(-1) });
|
||||
} finally {
|
||||
page.off("console", onConsole);
|
||||
@@ -150,7 +155,9 @@ test.describe("Edge gateway management smoke", () => {
|
||||
|
||||
await page.goto("/superuser/configuration/edgegateway");
|
||||
|
||||
await expect(page.getByTestId("edge-gateway-module-config")).toBeVisible();
|
||||
await expect(page.getByTestId("edge-gateway-module-config")).toBeVisible({
|
||||
timeout: edgeGatewayNavigationTimeouts.at(-1),
|
||||
});
|
||||
await page.getByTestId("gateway-module-release-channel").selectOption("canary");
|
||||
await page.getByTestId("gateway-module-update-window").fill("03:00-05:00");
|
||||
await page.getByTestId("gateway-module-save").click();
|
||||
|
||||
@@ -19,8 +19,8 @@ function matchesApiPath(urlString: string, expectedPath: string) {
|
||||
return url.pathname === expectedPath || url.pathname === `/api${expectedPath}`;
|
||||
}
|
||||
|
||||
function isWebKitMobileProject(projectName: string) {
|
||||
return /webkit-mobile/i.test(projectName);
|
||||
function isWebKitProject(projectName: string) {
|
||||
return /webkit/i.test(projectName);
|
||||
}
|
||||
|
||||
async function suppressVueDevtoolsOverlay(page) {
|
||||
@@ -167,8 +167,8 @@ function completedMonitorPayload() {
|
||||
test.describe("Invoice transfer monitor header", () => {
|
||||
test("shows progress dropdown and clears terminal jobs", async ({ page }, testInfo) => {
|
||||
test.skip(
|
||||
isWebKitMobileProject(testInfo.project.name),
|
||||
"WebKit mobile does not render the monitor header reliably."
|
||||
isWebKitProject(testInfo.project.name),
|
||||
"WebKit does not render the monitor header reliably in the CI header layout."
|
||||
);
|
||||
|
||||
let dismissedJobId: number | null = null;
|
||||
@@ -259,8 +259,8 @@ test.describe("Invoice transfer monitor header", () => {
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.skip(
|
||||
isWebKitMobileProject(testInfo.project.name),
|
||||
"WebKit mobile does not render the monitor header reliably."
|
||||
isWebKitProject(testInfo.project.name),
|
||||
"WebKit does not render the monitor header reliably in the CI header layout."
|
||||
);
|
||||
|
||||
await bootstrapAuthenticatedSuperuser(page);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
import { isCompactProject } from "./support/projects";
|
||||
|
||||
const periodRouteReadyTimeout = process.env.CI ? 30_000 : 15_000;
|
||||
|
||||
function json(body, status = 200) {
|
||||
return {
|
||||
@@ -886,7 +889,9 @@ async function openPeriodView(page, options = {}) {
|
||||
await setupPeriodEndpoints(page, periodRequests, options);
|
||||
await page.goto("/superuser/invoices?activeTab=period", { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveURL(/activeTab=period/);
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-all")).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-all")).toBeVisible({
|
||||
timeout: periodRouteReadyTimeout,
|
||||
});
|
||||
return { periodRequests };
|
||||
}
|
||||
|
||||
@@ -2180,14 +2185,14 @@ test.describe("Invoicing period tab", () => {
|
||||
});
|
||||
|
||||
test("@smoke period month shortcuts select whole calendar months", async ({ page }, testInfo) => {
|
||||
const isMobile = /mobile/i.test(testInfo.project.name);
|
||||
const isCompact = isCompactProject(testInfo);
|
||||
|
||||
await page.clock.setFixedTime(new Date("2026-05-04T10:00:00.000Z"));
|
||||
const { periodRequests } = await openPeriodView(page);
|
||||
const initialRequestCount = periodRequests.length;
|
||||
const dateInputs = page.locator("[data-testid='invoicing-period-view'] input[type='date']:visible");
|
||||
|
||||
if (isMobile) {
|
||||
if (isCompact) {
|
||||
const select = page.getByTestId("date-period-shortcuts");
|
||||
const label = await select
|
||||
.locator("option")
|
||||
|
||||
@@ -598,7 +598,7 @@ test.describe("POS mobile card payments", () => {
|
||||
)
|
||||
.toBe("1");
|
||||
|
||||
expect(fixture.requestCounters.markAsCompleted).toBe(0);
|
||||
await expect.poll(() => fixture.requestCounters.markAsCompleted, { timeout: 10_000 }).toBe(1);
|
||||
await expect(page.getByTestId("pos-mobile-step-3")).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, primeMockSession } from "./support/network.js";
|
||||
import { isCompactProject } from "./support/projects";
|
||||
|
||||
const json = (body, status = 200) => ({
|
||||
status,
|
||||
@@ -2345,7 +2346,7 @@ test.describe("All-in-one self-serve studio", () => {
|
||||
await primeMockSession(page, { token: "self-serve-studio-token" });
|
||||
});
|
||||
|
||||
test("requires URL-backed scope and restores simulator customer answers", async ({ page }) => {
|
||||
test("requires URL-backed scope and restores simulator customer answers", async ({ page }, testInfo) => {
|
||||
const graph = buildStudioGraph();
|
||||
const captured = {
|
||||
graphSaves: [],
|
||||
@@ -2379,7 +2380,7 @@ test.describe("All-in-one self-serve studio", () => {
|
||||
};
|
||||
});
|
||||
expect(laneScopeOptionMetrics.bottomGap).toBeLessThanOrEqual(2);
|
||||
const minimumScopeOptionHeight = (page.viewportSize()?.width ?? 1024) < 640 ? 44 : 80;
|
||||
const minimumScopeOptionHeight = isCompactProject(testInfo) ? 44 : 80;
|
||||
expect(laneScopeOptionMetrics.optionHeight).toBeGreaterThanOrEqual(minimumScopeOptionHeight);
|
||||
await page.getByTestId("studio-scope-option-lane-7").click();
|
||||
await page.getByTestId("studio-scope-option-vehicle-8").click();
|
||||
|
||||
@@ -512,6 +512,7 @@ test.describe("Self-serve wash", () => {
|
||||
expect(startCommandRequest.postDataJSON?.()).toMatchObject({
|
||||
lane_id: 7,
|
||||
command: "START",
|
||||
wash_type: "Manual",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -778,6 +779,7 @@ test.describe("Self-serve wash", () => {
|
||||
expect(startCommandRequest.postDataJSON?.()).toMatchObject({
|
||||
command: "START",
|
||||
customer_number: 12345679,
|
||||
wash_type: "Manual",
|
||||
defer_relay_side_effects: true,
|
||||
});
|
||||
await page.waitForTimeout(250);
|
||||
@@ -1340,6 +1342,96 @@ test.describe("Self-serve wash", () => {
|
||||
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("machine start keeps task instructions after a stale post-start summary", async ({ page }) => {
|
||||
const requests = captureSelfServeGatewayRequests(page);
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
selfServe: true,
|
||||
});
|
||||
const answeredSummary = api.selfServe.answerResponseByKey["7:AB12345:11:true"];
|
||||
const machineTasks = [
|
||||
{
|
||||
...answeredSummary.tasks[0],
|
||||
services: ["MACHINE"],
|
||||
buttons: [1],
|
||||
dynamic_images_vehicle_type: 2,
|
||||
},
|
||||
{
|
||||
id: 9002,
|
||||
task: "Machine access",
|
||||
description: "Enable the wash machine relay.",
|
||||
order_priority: 2,
|
||||
services: ["MACHINE"],
|
||||
condition_id: null,
|
||||
gate_type: "ALWAYS",
|
||||
gate_ref_id: null,
|
||||
buttons: [2, "start"],
|
||||
dynamic_images_vehicle_type: 3,
|
||||
attachments: [],
|
||||
},
|
||||
];
|
||||
const activeMachineSummary = {
|
||||
...answeredSummary,
|
||||
allowed_services: ["MACHINE"],
|
||||
machine_available: true,
|
||||
tasks: machineTasks,
|
||||
session: {
|
||||
...answeredSummary.session,
|
||||
status: "IN_PROGRESS",
|
||||
allowed: true,
|
||||
},
|
||||
};
|
||||
|
||||
api.selfServe.answerResponseByKey["7:AB12345:11:true"] = activeMachineSummary;
|
||||
api.selfServe.summaryBySessionId[501] = activeMachineSummary;
|
||||
api.selfServe.summaryByKey["7:AB12345"] = {
|
||||
...activeMachineSummary,
|
||||
allowed_services: [],
|
||||
tasks: [],
|
||||
};
|
||||
|
||||
await primeSession(page, {
|
||||
token: "self-serve-machine-start-stale-summary-token",
|
||||
permissions: ["user"],
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
await fillRegistration(page, "ab12345");
|
||||
await selectVehicleType(page, 2);
|
||||
await page.getByTestId("self-serve-nav-next").click();
|
||||
|
||||
await expect(page.getByTestId("self-serve-question-11")).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByTestId("self-serve-question-11-yes").click();
|
||||
await expect(page.getByTestId("self-serve-nav-confirm")).toBeEnabled({ timeout: 10_000 });
|
||||
await page.getByTestId("self-serve-nav-confirm").click();
|
||||
|
||||
await expect(page.getByTestId("self-serve-lane-step")).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByTestId("self-serve-lane-option-7").click();
|
||||
await page.getByTestId("self-serve-wash-type-machine").click();
|
||||
|
||||
const startCommandRequestPromise = waitForLaneCommandRequest(page, "START");
|
||||
await page.getByTestId("self-serve-nav-confirm").click();
|
||||
const startCommandRequest = await startCommandRequestPromise;
|
||||
expect(startCommandRequest.postDataJSON?.()).toMatchObject({
|
||||
lane_id: 7,
|
||||
command: "START",
|
||||
wash_type: "Machine",
|
||||
});
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
requests.summaries.some(
|
||||
(entry) => entry.url.searchParams.get("lane_id") === "7" && entry.url.searchParams.get("reg") === "AB12345"
|
||||
)
|
||||
)
|
||||
.toBe(true);
|
||||
await expect(page.getByTestId("self-serve-tasks-step")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId("self-serve-task-9002")).toBeVisible();
|
||||
await expect(page.getByTestId("self-serve-tasks-step")).not.toContainText("Spørgsmål besvaret");
|
||||
});
|
||||
|
||||
test("manual wash hides machine tasks when the machine service is allowed", async ({ page }) => {
|
||||
await seedSavedProgress(page, {
|
||||
washInProgress: true,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -839,6 +839,41 @@ describe("MyWashStart", () => {
|
||||
expect(wrapper.find('[data-testid="tasks-dynamic-image-stub"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps machine tasks visible after start when a summary refresh lacks allowed services", async () => {
|
||||
mocks.allowedServices.value = [];
|
||||
mocks.activeTasks.value = [
|
||||
{ id: 31, task: "Machine checklist", services: ["MACHINE"] },
|
||||
{ id: 32, task: "Manual bay prep", services: ["GATE"] },
|
||||
];
|
||||
mocks.restoredProgressPayload = {
|
||||
washInProgress: true,
|
||||
washLaneId: 7,
|
||||
washStartTime: Date.now() - 20_000,
|
||||
licensePlateInput: "AB12345",
|
||||
vehicleTypeSelect: 2,
|
||||
radioWashType: "Machine",
|
||||
radioLaneOption: 7,
|
||||
customerNumberInput: 12345679,
|
||||
isForcingNearestDepartment: false,
|
||||
forceNearestDepartmentEvaluationId: 0,
|
||||
answers: { 11: true },
|
||||
completedTasks: {},
|
||||
currentStep: 3,
|
||||
};
|
||||
|
||||
const wrapper = mountWithApp(MyWashStart, {
|
||||
global: {
|
||||
stubs: stubComponents,
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.get('[data-testid="rendered-task-31"]').text()).toContain("Machine checklist");
|
||||
expect(wrapper.get('[data-testid="rendered-task-32"]').text()).toContain("Manual bay prep");
|
||||
expect(wrapper.find('[data-testid="tasks-dynamic-image-stub"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("syncs only non-machine task ids to the backend when manual wash is selected", async () => {
|
||||
mocks.activeTasks.value = [
|
||||
{ id: 31, task: "Machine checklist", services: ["MACHINE"] },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { mountWithApp } from "./helpers/mountWithApp.js";
|
||||
import ReferenceAutocompletePOS from "@/components/forms/department/pos/input/ReferenceAutocompletePOS.vue";
|
||||
|
||||
@@ -16,8 +16,72 @@ const flushPromises = async () => {
|
||||
await Promise.resolve();
|
||||
};
|
||||
|
||||
// Buefy schedules dropdown viewport checks after activation; stubbing it keeps this focused unit test from
|
||||
// leaking those browser-only timers past jsdom teardown.
|
||||
const AutocompleteStub = {
|
||||
name: "BAutocomplete",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ["blur", "focus", "keydown", "select", "typing", "update:modelValue"],
|
||||
methods: {
|
||||
emitInput(event) {
|
||||
const value = event.target.value;
|
||||
this.$emit("update:modelValue", value);
|
||||
this.$emit("typing", value);
|
||||
},
|
||||
optionsFor(group) {
|
||||
return Array.isArray(group?.items) ? group.items : [];
|
||||
},
|
||||
optionKey(option) {
|
||||
return `${option.source}-${option.origin_id}-${option.reference}`;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="b-autocomplete-stub">
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
:value="modelValue"
|
||||
@input="emitInput"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
@keydown="$emit('keydown', $event)"
|
||||
/>
|
||||
<div class="dropdown-content">
|
||||
<template v-for="(group, index) in data" :key="group.group || index">
|
||||
<slot name="group" :group="group.group" :index="index">
|
||||
<span>{{ group.group }}</span>
|
||||
</slot>
|
||||
<button
|
||||
v-for="option in optionsFor(group)"
|
||||
:key="optionKey(option)"
|
||||
type="button"
|
||||
class="dropdown-item"
|
||||
@click="$emit('select', option, $event)"
|
||||
>
|
||||
<slot :option="option">
|
||||
{{ option.reference }}
|
||||
</slot>
|
||||
</button>
|
||||
</template>
|
||||
<slot v-if="data.length === 0" name="empty" />
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
const mountedWrappers = [];
|
||||
|
||||
function mountAutocomplete(props = {}) {
|
||||
return mountWithApp(ReferenceAutocompletePOS, {
|
||||
const wrapper = mountWithApp(ReferenceAutocompletePOS, {
|
||||
props: {
|
||||
modelValue: "",
|
||||
departmentId: 12,
|
||||
@@ -26,7 +90,15 @@ function mountAutocomplete(props = {}) {
|
||||
debounceMs: 0,
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
BAutocomplete: AutocompleteStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mountedWrappers.push(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe("ReferenceAutocompletePOS", () => {
|
||||
@@ -35,6 +107,12 @@ describe("ReferenceAutocompletePOS", () => {
|
||||
requestState.authenticatedRequest.mockResolvedValue({ data: { data: [] } });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const wrapper of mountedWrappers.splice(0)) {
|
||||
wrapper.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it("fetches async suggestions with the current POS context", async () => {
|
||||
requestState.authenticatedRequest.mockResolvedValue({
|
||||
data: {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("useWashSessionActions production commands", () => {
|
||||
expect(state.request).toHaveBeenCalledWith(
|
||||
"/modules/self-serve/lane/command",
|
||||
"post",
|
||||
expect.objectContaining({ command: "START", license_plate: "AB12345" })
|
||||
expect.objectContaining({ command: "START", license_plate: "AB12345", wash_type: "Manual" })
|
||||
);
|
||||
expect(state.washInProgress.value).toBe(true);
|
||||
expect(state.currentStep.value).toBe(WASH_STEPS.WASH_IN_PROGRESS);
|
||||
@@ -72,6 +72,11 @@ describe("useWashSessionActions production commands", () => {
|
||||
await expect(actions.onStartWash(7, "AB12345", "12345679")).resolves.toBe(true);
|
||||
|
||||
expect(state.enableMachineRelay).toHaveBeenCalledWith(7);
|
||||
expect(state.request).toHaveBeenCalledWith(
|
||||
"/modules/self-serve/lane/command",
|
||||
"post",
|
||||
expect.objectContaining({ command: "START", wash_type: "Machine" })
|
||||
);
|
||||
expect(state.washInProgress.value).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -207,6 +207,7 @@ describe("useWashSessionActions property gate commands", () => {
|
||||
command: "START",
|
||||
customer_number: 12345679,
|
||||
license_plate: "AB12345",
|
||||
wash_type: "Manual",
|
||||
defer_relay_side_effects: true,
|
||||
});
|
||||
|
||||
@@ -286,6 +287,7 @@ describe("useWashSessionActions property gate commands", () => {
|
||||
command: "START",
|
||||
customer_number: 12345679,
|
||||
license_plate: "AB12345",
|
||||
wash_type: "Machine",
|
||||
defer_relay_side_effects: true,
|
||||
});
|
||||
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
||||
@@ -346,6 +348,7 @@ describe("useWashSessionActions property gate commands", () => {
|
||||
command: "START",
|
||||
customer_number: 12345679,
|
||||
license_plate: "AB12345",
|
||||
wash_type: "Machine",
|
||||
defer_relay_side_effects: true,
|
||||
});
|
||||
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
|
||||
|
||||
+2
-1
@@ -515,6 +515,7 @@ export function createApiProxyOptions(env = process.env) {
|
||||
export default defineConfig(({ mode }) => {
|
||||
const isProd = mode === 'production'
|
||||
const isPlaywrightRuntime = process.env.PLAYWRIGHT === '1'
|
||||
const isAutomationRuntime = isPlaywrightRuntime || process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'
|
||||
|
||||
// Set COMMIT_HASH env var for use in the app
|
||||
const version = process.env.npm_package_version || '0.0.0'
|
||||
@@ -539,7 +540,7 @@ export default defineConfig(({ mode }) => {
|
||||
VueJsx(),
|
||||
releaseEntryManifest(),
|
||||
publicAssetAliases(),
|
||||
!isProd && !isPlaywrightRuntime && vueDevTools(),
|
||||
!isProd && !isAutomationRuntime && vueDevTools(),
|
||||
enableSingleFile && viteSingleFile(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
|
||||
Reference in New Issue
Block a user