- Introduced `.prettierrc.json` to enforce consistent code formatting across the project. - Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
345 lines
9.5 KiB
JavaScript
345 lines
9.5 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { nextTick, ref } from "vue";
|
|
import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";
|
|
import { mountWithApp } from "./helpers/mountWithApp.js";
|
|
|
|
const paginationState = vi.hoisted(() => ({
|
|
list: null,
|
|
metaCurrentPage: null,
|
|
metaItemsPerPage: null,
|
|
metaTotalItems: null,
|
|
metaSearch: null,
|
|
loadList: vi.fn(),
|
|
setEndpoint: vi.fn(),
|
|
setMetaItemsPerPage: vi.fn(),
|
|
setPage: vi.fn(),
|
|
search: vi.fn(),
|
|
setFilter: vi.fn(),
|
|
setOrder: vi.fn(),
|
|
}));
|
|
|
|
const posState = vi.hoisted(() => ({
|
|
departmentId: null,
|
|
getDepartment: vi.fn(() => 12),
|
|
selectScan: vi.fn(),
|
|
}));
|
|
|
|
const requestMock = vi.hoisted(() => vi.fn());
|
|
const lookupMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@/components/pagination/paginatedList.vue", async () => {
|
|
paginationState.list = ref([]);
|
|
paginationState.metaCurrentPage = ref(1);
|
|
paginationState.metaItemsPerPage = ref(10);
|
|
paginationState.metaTotalItems = ref(0);
|
|
paginationState.metaSearch = ref("");
|
|
|
|
return {
|
|
list: paginationState.list,
|
|
metaCurrentPage: paginationState.metaCurrentPage,
|
|
metaItemsPerPage: paginationState.metaItemsPerPage,
|
|
metaTotalItems: paginationState.metaTotalItems,
|
|
metaSearch: paginationState.metaSearch,
|
|
loadList: paginationState.loadList,
|
|
setEndpoint: paginationState.setEndpoint,
|
|
setMetaItemsPerPage: paginationState.setMetaItemsPerPage,
|
|
setPage: paginationState.setPage,
|
|
search: paginationState.search,
|
|
setFilter: paginationState.setFilter,
|
|
setOrder: paginationState.setOrder,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/shop/POSDepartmentProcess.vue", async () => {
|
|
posState.departmentId = ref(12);
|
|
|
|
return {
|
|
department_id: posState.departmentId,
|
|
getDepartment: posState.getDepartment,
|
|
selectScan: posState.selectScan,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
request: requestMock,
|
|
functions: {
|
|
date: {
|
|
toLocalTimeHours: vi.fn((value) => String(value).slice(11, 16)),
|
|
},
|
|
},
|
|
objects: {
|
|
order_bookings: {
|
|
meta: {
|
|
endpoint: "/order-bookings",
|
|
},
|
|
},
|
|
orders: {
|
|
columns: {
|
|
reg_1: {
|
|
label: "License plate",
|
|
},
|
|
},
|
|
},
|
|
global: {
|
|
language: {
|
|
nothing_left_to_show: "Nothing left to show",
|
|
},
|
|
},
|
|
},
|
|
superUser: {
|
|
modules: {
|
|
motorapi: {
|
|
functions: {
|
|
lookup: lookupMock,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/ThemeConfig.vue", () => ({
|
|
Colors: {
|
|
inputs: {
|
|
default: {
|
|
backgroundColor: "#ffffff",
|
|
textColor: "#22364d",
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@vueuse/core", async () => {
|
|
const { ref } = await import("vue");
|
|
return {
|
|
useElementVisibility: vi.fn(() => ref(false)),
|
|
};
|
|
});
|
|
|
|
import PosLastScannedLicensePlatesV2 from "@/components/displays/department/pos/PosLastScannedLicensePlatesV2.vue";
|
|
|
|
let consoleErrorSpy;
|
|
|
|
const messages = {
|
|
en: {
|
|
admin: {
|
|
pos: {
|
|
customer: "Customer",
|
|
customer_number: "Customer number",
|
|
booking: "Booking",
|
|
license_plate: "License plate",
|
|
loading: "Loading...",
|
|
make: "Make",
|
|
model: "Model",
|
|
no_plates_found: "No license plates found",
|
|
recent_scans: "Recent scans",
|
|
recent_scans_helper: "Select a license plate to view vehicle details.",
|
|
recent_scan_details_empty: "No vehicle details are available for this scan.",
|
|
recent_scan_details_error: "Vehicle details could not be loaded. Please try another scan.",
|
|
recent_scan_details_loading: "Loading vehicle details...",
|
|
scanner: "Scanner",
|
|
status_barred: "Barred",
|
|
status_booking: "Booking",
|
|
status_unknown: "New",
|
|
try_again: "Try again",
|
|
type: "Type",
|
|
use: "Use",
|
|
variant: "Variant",
|
|
},
|
|
},
|
|
global: {
|
|
search_license_plate: "Search for license plate",
|
|
},
|
|
},
|
|
};
|
|
|
|
const scans = [
|
|
{
|
|
id: 801,
|
|
plate: "AB12345",
|
|
plate_scanner_id: 1,
|
|
created_at: "2026-04-08 08:44:07",
|
|
customer_number: 12345679,
|
|
customer_name: "Pleno Logistics",
|
|
seen_before: true,
|
|
barred: false,
|
|
},
|
|
{
|
|
id: 802,
|
|
plate: "CD67890",
|
|
plate_scanner_id: 2,
|
|
created_at: "2026-04-08 08:31:05",
|
|
customer_number: null,
|
|
customer_name: "",
|
|
seen_before: false,
|
|
barred: false,
|
|
},
|
|
];
|
|
|
|
const flushUi = async () => {
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
await nextTick();
|
|
};
|
|
|
|
function buildLookupResponse(overrides = {}) {
|
|
return {
|
|
data: {
|
|
data: {
|
|
make: "RENAULT",
|
|
model: "Captur",
|
|
variant: "dCi 90",
|
|
type: "Personbil",
|
|
use: "Privat personkørsel",
|
|
...overrides,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("PosLastScannedLicensePlatesV2", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
paginationState.list.value = [];
|
|
paginationState.metaCurrentPage.value = 1;
|
|
paginationState.metaItemsPerPage.value = 10;
|
|
paginationState.metaTotalItems.value = scans.length;
|
|
paginationState.metaSearch.value = "";
|
|
|
|
paginationState.loadList.mockClear();
|
|
paginationState.setEndpoint.mockClear();
|
|
paginationState.setMetaItemsPerPage.mockClear();
|
|
paginationState.setPage.mockClear();
|
|
paginationState.search.mockClear();
|
|
paginationState.setFilter.mockClear();
|
|
paginationState.setOrder.mockClear();
|
|
|
|
posState.selectScan.mockClear();
|
|
posState.getDepartment.mockReturnValue(12);
|
|
posState.departmentId.value = 12;
|
|
|
|
requestMock.mockReset();
|
|
requestMock.mockImplementation((url) => {
|
|
if (String(url).startsWith("/department/numberplatescanners")) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ id: 1, name: "North scanner" },
|
|
{ id: 2, name: "South scanner" },
|
|
],
|
|
},
|
|
});
|
|
}
|
|
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [{ id: 501, reg_1: "AB12345", reg_2: "" }],
|
|
},
|
|
});
|
|
});
|
|
|
|
lookupMock.mockReset();
|
|
lookupMock.mockImplementation((plate) => {
|
|
if (plate === "AB12345") {
|
|
return Promise.resolve(buildLookupResponse());
|
|
}
|
|
|
|
return Promise.resolve(
|
|
buildLookupResponse({
|
|
make: "SCANIA",
|
|
model: "R500",
|
|
variant: "Highline",
|
|
type: "Lastbil",
|
|
use: "Godstransport",
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.runOnlyPendingTimers();
|
|
consoleErrorSpy.mockRestore();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("clicking a row selects the scan and reveals inline details", async () => {
|
|
const wrapper = mountWithApp(PosLastScannedLicensePlatesV2, { messages });
|
|
|
|
await vi.advanceTimersByTimeAsync(1000);
|
|
paginationState.list.value = scans;
|
|
await flushUi();
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-801"]').trigger("click");
|
|
await flushUi();
|
|
|
|
expect(posState.selectScan).toHaveBeenCalledWith(expect.objectContaining({ id: 801, plate: "AB12345" }));
|
|
expect(lookupMock).toHaveBeenCalledWith("AB12345");
|
|
expect(wrapper.get('[data-testid="pos-recent-scan-details-801"]').text()).toContain("RENAULT");
|
|
expect(wrapper.get('[data-testid="pos-recent-scan-details-801"]').text()).toContain("Customer number");
|
|
});
|
|
|
|
it("keeps only one inline detail panel expanded at a time", async () => {
|
|
const wrapper = mountWithApp(PosLastScannedLicensePlatesV2, { messages });
|
|
|
|
await vi.advanceTimersByTimeAsync(1000);
|
|
paginationState.list.value = scans;
|
|
await flushUi();
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-801"]').trigger("click");
|
|
await flushUi();
|
|
expect(wrapper.find('[data-testid="pos-recent-scan-details-801"]').exists()).toBe(true);
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-802"]').trigger("click");
|
|
await flushUi();
|
|
|
|
expect(wrapper.find('[data-testid="pos-recent-scan-details-801"]').exists()).toBe(false);
|
|
expect(wrapper.find('[data-testid="pos-recent-scan-details-802"]').exists()).toBe(true);
|
|
});
|
|
|
|
it("caches MotorAPI lookups by plate when the user revisits a scan", async () => {
|
|
const wrapper = mountWithApp(PosLastScannedLicensePlatesV2, { messages });
|
|
|
|
await vi.advanceTimersByTimeAsync(1000);
|
|
paginationState.list.value = scans;
|
|
await flushUi();
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-801"]').trigger("click");
|
|
await flushUi();
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-802"]').trigger("click");
|
|
await flushUi();
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-801"]').trigger("click");
|
|
await flushUi();
|
|
|
|
expect(lookupMock.mock.calls.filter(([plate]) => plate === "AB12345")).toHaveLength(1);
|
|
expect(lookupMock.mock.calls.filter(([plate]) => plate === "CD67890")).toHaveLength(1);
|
|
});
|
|
|
|
it("shows an inline error message when MotorAPI lookup fails", async () => {
|
|
lookupMock.mockImplementation((plate) => {
|
|
if (plate === "CD67890") {
|
|
return Promise.reject(new Error("Lookup failed"));
|
|
}
|
|
|
|
return Promise.resolve(buildLookupResponse());
|
|
});
|
|
|
|
const wrapper = mountWithApp(PosLastScannedLicensePlatesV2, { messages });
|
|
|
|
await vi.advanceTimersByTimeAsync(1000);
|
|
paginationState.list.value = scans;
|
|
await flushUi();
|
|
|
|
await wrapper.get('[data-testid="pos-recent-scan-row-802"]').trigger("click");
|
|
await flushUi();
|
|
|
|
expect(wrapper.get('[data-testid="pos-recent-scan-details-802"]').text()).toContain(
|
|
"Vehicle details could not be loaded"
|
|
);
|
|
});
|
|
});
|