- Introduced `PosDesktopOrderBookingSelectorModal.vue` to streamline booking selection for desktop POS. - Added `orderBookingDisplay.js` utility for handling booking display and reference value normalization. - Created unit test `select-vehicle-form-pos.spec.js` to validate booking selection flow and registration matching. - Implemented `run-playwright-ci-parallel.mjs` script to optimize Playwright CI with parallel testing. - Updated styles and responsiveness for enhanced booking modal UX.
309 lines
9.0 KiB
JavaScript
309 lines
9.0 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { nextTick, ref } from "vue";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { mountWithApp } from "./helpers/mountWithApp.js";
|
|
|
|
const posState = vi.hoisted(() => ({
|
|
customerId: null,
|
|
notes: null,
|
|
customerName: null,
|
|
reference: null,
|
|
reg1: null,
|
|
reg2: null,
|
|
reg3: null,
|
|
customerAttributes: null,
|
|
orderNotes: null,
|
|
orderPo: null,
|
|
orderId: null,
|
|
selectedOrderBookingId: null,
|
|
selectedOrderBookingPlate: null,
|
|
searchAndSelectCustomer: vi.fn(),
|
|
selectCustomer: vi.fn(),
|
|
setSelectedOrderBookingSelection: vi.fn(),
|
|
clearSelectedOrderBookingSelection: vi.fn(),
|
|
skipSelectedOrderBookingSelection: vi.fn(),
|
|
isSelectedOrderBookingSkippedForPlate: vi.fn(() => false),
|
|
}));
|
|
|
|
vi.mock("@/components/shop/POSDepartmentProcess.vue", async () => {
|
|
posState.customerId = ref(null);
|
|
posState.notes = ref([]);
|
|
posState.customerName = ref("");
|
|
posState.reference = ref("");
|
|
posState.reg1 = ref("AB12345");
|
|
posState.reg2 = ref("");
|
|
posState.reg3 = ref("");
|
|
posState.customerAttributes = ref([]);
|
|
posState.orderNotes = ref("");
|
|
posState.orderPo = ref("");
|
|
posState.orderId = ref(null);
|
|
posState.selectedOrderBookingId = ref(null);
|
|
posState.selectedOrderBookingPlate = ref("");
|
|
|
|
posState.selectCustomer.mockImplementation((customer) => {
|
|
posState.customerId.value = customer?.customerNumber ?? null;
|
|
posState.customerName.value = customer?.name ?? "";
|
|
});
|
|
|
|
return {
|
|
customer_id: posState.customerId,
|
|
notes: posState.notes,
|
|
isCustomerBarred: vi.fn(() => false),
|
|
customer_name: posState.customerName,
|
|
reference: posState.reference,
|
|
reg_1: posState.reg1,
|
|
reg_2: posState.reg2,
|
|
reg_3: posState.reg3,
|
|
order_notes: posState.orderNotes,
|
|
order_po: posState.orderPo,
|
|
order_id: posState.orderId,
|
|
searchAndSelectCustomer: posState.searchAndSelectCustomer,
|
|
selectCustomer: posState.selectCustomer,
|
|
isCustomerSelected: () => Boolean(posState.customerId.value),
|
|
customer_attributes: posState.customerAttributes,
|
|
selectedOrderBookingId: posState.selectedOrderBookingId,
|
|
selectedOrderBookingPlate: posState.selectedOrderBookingPlate,
|
|
setSelectedOrderBookingSelection: posState.setSelectedOrderBookingSelection,
|
|
clearSelectedOrderBookingSelection: posState.clearSelectedOrderBookingSelection,
|
|
skipSelectedOrderBookingSelection: posState.skipSelectedOrderBookingSelection,
|
|
isSelectedOrderBookingSkippedForPlate: posState.isSelectedOrderBookingSkippedForPlate,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
adminUser: false,
|
|
objects: {
|
|
orders: {
|
|
columns: {
|
|
reg_1: { label: "Nummerplade" },
|
|
reg_2: { label: "Reg. 2" },
|
|
reg_3: { label: "Reg. 3" },
|
|
reference: { label: "Reference" },
|
|
},
|
|
},
|
|
global: {
|
|
language: {
|
|
customer: "Kunde",
|
|
select: "Vælg",
|
|
other: "Andet",
|
|
no_data: "Ingen data",
|
|
last_wash: "Sidste vask",
|
|
copy: "Kopier",
|
|
have: "Har",
|
|
have_not: "Har ikke",
|
|
quantity: "Antal",
|
|
show: "Vis",
|
|
hide: "Skjul",
|
|
},
|
|
},
|
|
vehicles: {
|
|
meta: {
|
|
labels: {
|
|
single: "Køretøj",
|
|
},
|
|
},
|
|
columns: {
|
|
wash_subscription: {
|
|
label: "Vaskeabonnement",
|
|
},
|
|
},
|
|
},
|
|
products: {
|
|
meta: {
|
|
labels: {
|
|
single: "Produkt",
|
|
},
|
|
},
|
|
functions: {
|
|
getProductName: vi.fn(() => "Forvogn"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
import SelectVehicleFormPOS from "@/components/forms/department/pos/SelectVehicleFormPOS.vue";
|
|
|
|
const linkedVehicle = {
|
|
id: 7002,
|
|
reg: "EC21235",
|
|
customer_id: 12345679,
|
|
type: 53,
|
|
wash_subscription: false,
|
|
addons: { list: [] },
|
|
last_order_id: null,
|
|
};
|
|
|
|
const unlinkedVehicle = {
|
|
id: 7001,
|
|
reg: "AB12345",
|
|
customer_id: null,
|
|
type: 53,
|
|
wash_subscription: false,
|
|
addons: { list: [] },
|
|
last_order_id: null,
|
|
};
|
|
|
|
const LicensePlateReg1InputStub = {
|
|
name: "LicensePlateReg1Input",
|
|
emits: ["update:vehicleObject", "update:focus", "update:bookingObject", "update:bookingMatches"],
|
|
methods: {
|
|
emitUnlinkedVehicle() {
|
|
this.$emit("update:vehicleObject", { ...unlinkedVehicle });
|
|
},
|
|
emitLinkedVehicle() {
|
|
this.$emit("update:vehicleObject", { ...linkedVehicle });
|
|
},
|
|
},
|
|
template: `
|
|
<div>
|
|
<button
|
|
type="button"
|
|
data-testid="emit-unlinked-vehicle"
|
|
@click="emitUnlinkedVehicle"
|
|
>
|
|
Emit unlinked vehicle
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-testid="emit-linked-vehicle"
|
|
@click="emitLinkedVehicle"
|
|
>
|
|
Emit linked vehicle
|
|
</button>
|
|
</div>
|
|
`,
|
|
};
|
|
|
|
const ExpandableContentBoxStub = {
|
|
name: "ExpandableContentBox",
|
|
props: {
|
|
expanded: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
emits: ["update:expanded"],
|
|
template: `
|
|
<div data-testid="expandable-content-box-stub">
|
|
<slot v-if="expanded" name="expandedContent" />
|
|
<button
|
|
type="button"
|
|
data-testid="expandable-toggle"
|
|
@click="$emit('update:expanded', !expanded)"
|
|
>
|
|
Toggle
|
|
</button>
|
|
</div>
|
|
`,
|
|
};
|
|
|
|
const CustomerSearchFieldPosStub = {
|
|
name: "CustomerSearchFieldPos",
|
|
props: {
|
|
showCardPaymentButton: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
template: `
|
|
<div data-testid="customer-search-field-pos-stub">
|
|
<button
|
|
v-if="showCardPaymentButton"
|
|
type="button"
|
|
data-testid="customer-search-card-payment-stub"
|
|
>
|
|
Vælg direkte betaling med betalingskort
|
|
</button>
|
|
</div>
|
|
`,
|
|
};
|
|
|
|
const SimpleStub = {
|
|
template: "<div />",
|
|
};
|
|
|
|
function mountForm() {
|
|
return mountWithApp(SelectVehicleFormPOS, {
|
|
global: {
|
|
stubs: {
|
|
CustomerSearchFieldPos: CustomerSearchFieldPosStub,
|
|
customerSearchFieldPos: CustomerSearchFieldPosStub,
|
|
ExpandableContentBox: ExpandableContentBoxStub,
|
|
LicensePlateReg1Input: LicensePlateReg1InputStub,
|
|
LicensePlateInput: SimpleStub,
|
|
PosNotes: SimpleStub,
|
|
OrderContentTable: SimpleStub,
|
|
OrderItemsTable: SimpleStub,
|
|
VehicleCustomerSuggestionsPos: SimpleStub,
|
|
DefaultObjectSelector: SimpleStub,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
describe("SelectVehicleFormPOS", () => {
|
|
beforeEach(() => {
|
|
posState.customerId.value = null;
|
|
posState.notes.value = [];
|
|
posState.customerName.value = "";
|
|
posState.reference.value = "";
|
|
posState.reg1.value = "AB12345";
|
|
posState.reg2.value = "";
|
|
posState.reg3.value = "";
|
|
posState.customerAttributes.value = [];
|
|
posState.orderNotes.value = "";
|
|
posState.orderPo.value = "";
|
|
posState.orderId.value = null;
|
|
posState.selectedOrderBookingId.value = null;
|
|
posState.selectedOrderBookingPlate.value = "";
|
|
posState.searchAndSelectCustomer.mockClear();
|
|
posState.selectCustomer.mockClear();
|
|
posState.setSelectedOrderBookingSelection.mockClear();
|
|
posState.clearSelectedOrderBookingSelection.mockClear();
|
|
posState.skipSelectedOrderBookingSelection.mockClear();
|
|
posState.isSelectedOrderBookingSkippedForPlate.mockReset();
|
|
posState.isSelectedOrderBookingSkippedForPlate.mockReturnValue(false);
|
|
});
|
|
|
|
it("shows a single inline customer selector before a vehicle is linked", () => {
|
|
const wrapper = mountForm();
|
|
|
|
expect(wrapper.findAll('[data-testid="customer-search-field-pos-stub"]')).toHaveLength(1);
|
|
expect(wrapper.findAll('[data-testid="customer-search-card-payment-stub"]')).toHaveLength(1);
|
|
expect(wrapper.find('[data-testid="expandable-content-box-stub"]').exists()).toBe(false);
|
|
});
|
|
|
|
it("keeps a single inline customer selector when an unlinked vehicle card is expanded", async () => {
|
|
const wrapper = mountForm();
|
|
|
|
await wrapper.get('[data-testid="emit-unlinked-vehicle"]').trigger("click");
|
|
await nextTick();
|
|
|
|
expect(wrapper.findAll('[data-testid="customer-search-field-pos-stub"]')).toHaveLength(1);
|
|
expect(wrapper.find('[data-testid="expandable-content-box-stub"]').exists()).toBe(true);
|
|
|
|
await wrapper.get('[data-testid="expandable-toggle"]').trigger("click");
|
|
await nextTick();
|
|
|
|
expect(wrapper.findAll('[data-testid="customer-search-field-pos-stub"]')).toHaveLength(1);
|
|
expect(wrapper.findAll('[data-testid="customer-search-card-payment-stub"]')).toHaveLength(1);
|
|
});
|
|
|
|
it("moves customer selection into the expanded other section for linked vehicles", async () => {
|
|
const wrapper = mountForm();
|
|
|
|
await wrapper.get('[data-testid="emit-linked-vehicle"]').trigger("click");
|
|
await nextTick();
|
|
|
|
expect(wrapper.findAll('[data-testid="customer-search-field-pos-stub"]')).toHaveLength(0);
|
|
|
|
await wrapper.get('[data-testid="expandable-toggle"]').trigger("click");
|
|
await nextTick();
|
|
|
|
expect(wrapper.findAll('[data-testid="customer-search-field-pos-stub"]')).toHaveLength(1);
|
|
expect(wrapper.findAll('[data-testid="customer-search-card-payment-stub"]')).toHaveLength(1);
|
|
});
|
|
});
|