Files
pleno-vue/tests/unit/use-wash-departments.spec.js
T

417 lines
13 KiB
JavaScript

// @vitest-environment jsdom
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
getDepartmentsGuest: vi.fn(),
locationRef: {
value: {
coords: {
latitude: 55.5,
longitude: 12.4,
},
},
},
getDistance: vi.fn(),
}));
vi.mock("@/components/pagination/departmentTabs.vue", () => ({
getDepartmentsGuest: mocks.getDepartmentsGuest,
}));
vi.mock("@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue", () => ({
locations: {
location: mocks.locationRef,
normalizeCoordinatePair: (coordinates, { allowZeroPair = true } = {}) => {
const latitude = Number(coordinates?.latitude);
const longitude = Number(coordinates?.longitude);
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
return null;
}
if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) {
return null;
}
if (!allowZeroPair && latitude === 0 && longitude === 0) {
return null;
}
return { latitude, longitude };
},
getDistance: mocks.getDistance,
},
}));
const { useWashDepartments } = await import("@/composables/useWashDepartments.js");
const mountedWrappers = [];
const mountDepartments = (options = {}) => {
let departments = null;
const wrapper = mount({
setup() {
departments = useWashDepartments(options);
return {};
},
template: "<div />",
});
mountedWrappers.push(wrapper);
return departments;
};
describe("useWashDepartments", () => {
beforeEach(() => {
mountedWrappers.splice(0).forEach((wrapper) => wrapper.unmount());
mocks.getDepartmentsGuest.mockReset();
mocks.getDistance.mockReset();
vi.restoreAllMocks();
mocks.locationRef.value = {
coords: {
latitude: 55.5,
longitude: 12.4,
},
};
mocks.getDistance.mockImplementation((_from, to) => {
if (to.latitude === 55.6) {
return 8;
}
if (to.latitude === 55.7) {
return 2;
}
return 20;
});
});
it("fetches guest departments and resolves the nearest one", async () => {
mocks.getDepartmentsGuest.mockResolvedValue([
{
id: 1,
name: "North",
address: "A",
latitude: 55.6,
longitude: 12.5,
lanes: [{ id: 10, products: [1, 2] }],
self_serve_enabled: true,
},
{
id: 2,
name: "South",
address: "B",
latitude: 55.7,
longitude: 12.6,
lanes: [{ id: 11, products: [2, 3] }],
self_serve_enabled: true,
},
]);
const departments = mountDepartments({ includeLanes: true });
await departments.fetchDepartments();
expect(mocks.getDepartmentsGuest).toHaveBeenCalledWith({ include_lanes: true });
expect(departments.nearestDepartment.value).toMatchObject({ id: 2, name: "South", distance: 2 });
expect(departments.availableProductIds.value).toEqual([2, 3]);
expect(departments.doesCurrentDepartmentSelectionHaveSelfServeEnabled.value).toBe(true);
});
it("allows forced selection and resets back to geolocation ordering", async () => {
mocks.getDepartmentsGuest.mockResolvedValue([
{ id: 1, name: "North", address: "A", latitude: 55.6, longitude: 12.5, lanes: [], self_serve_enabled: true },
{ id: 2, name: "South", address: "B", latitude: 55.7, longitude: 12.6, lanes: [], self_serve_enabled: true },
]);
const departments = mountDepartments({
canAccessSuperUser: () => true,
});
await departments.fetchDepartments();
departments.startDepartmentSearch();
departments.selectDepartment(1);
expect(departments.isSearchingDepartments.value).toBe(false);
expect(departments.isForcingNearestDepartment.value).toBe(true);
expect(departments.nearestDepartment.value).toMatchObject({ id: 1, name: "North" });
departments.clearForcedDepartment();
await nextTick();
expect(departments.isForcingNearestDepartment.value).toBe(false);
expect(departments.forceNearestDepartmentEvaluationId.value).toBe(0);
expect(departments.nearestDepartment.value).toMatchObject({ id: 2, name: "South" });
});
it("keeps the wash start page usable when the guest departments request fails", async () => {
vi.spyOn(console, "warn").mockImplementation(() => {});
mocks.getDepartmentsGuest.mockRejectedValueOnce(new Error("Network Error"));
const departments = mountDepartments({ includeLanes: true });
await expect(departments.fetchDepartments()).resolves.toEqual([]);
expect(departments.guestDepartments.value).toEqual([]);
expect(departments.nearestDepartment.value).toBeNull();
expect(departments.departmentFetchError.value).toBe("Network Error");
expect(mocks.getDepartmentsGuest).toHaveBeenCalledWith({ include_lanes: true });
});
it("clears the department fetch error after a successful retry", async () => {
vi.spyOn(console, "warn").mockImplementation(() => {});
const department = {
id: 3,
name: "Glostrup",
address: "Fabriksparken 18",
latitude: 55.6,
longitude: 12.5,
self_serve_enabled: true,
lanes: [{ id: 7, status: "AVAILABLE", selfserve_enabled: true }],
};
mocks.getDepartmentsGuest.mockRejectedValueOnce(new Error("Gateway Timeout")).mockResolvedValueOnce([department]);
const departments = mountDepartments({ includeLanes: true });
await departments.fetchDepartments();
await departments.fetchDepartments();
expect(departments.departmentFetchError.value).toBeNull();
expect(departments.guestDepartments.value).toEqual([department]);
expect(departments.nearestDepartment.value).toMatchObject({
id: 3,
name: "Glostrup",
lanes: [{ id: 7 }],
});
});
it("uses the first self-serve department with an enabled lane as fallback when coordinates are missing", async () => {
mocks.locationRef.value = null;
mocks.getDepartmentsGuest.mockResolvedValue([
{
id: 1,
name: "Disabled module",
address: "A",
latitude: 55.6,
longitude: 12.5,
lanes: [{ id: 10, selfserve_enabled: true }],
self_serve_enabled: false,
},
{
id: 2,
name: "Disabled lane",
address: "B",
latitude: 55.7,
longitude: 12.6,
lanes: [{ id: 11, selfserve_enabled: false }],
self_serve_enabled: true,
},
{
id: 3,
name: "Fallback",
address: "C",
latitude: 55.8,
longitude: 12.7,
lanes: [
{ id: 12, selfserve_enabled: "off" },
{ id: 13, selfserve_enabled: true, products: [4] },
],
self_serve_enabled: true,
},
]);
const departments = mountDepartments({ includeLanes: true });
await departments.fetchDepartments();
expect(mocks.getDistance).not.toHaveBeenCalled();
expect(departments.nearestDepartment.value).toMatchObject({
id: 3,
name: "Fallback",
distance: null,
lanes: [{ id: 13 }],
});
expect(departments.departmentSelectionStrategy.value).toBe("fallback");
expect(departments.isDepartmentSelectionFallbackBased.value).toBe(true);
expect(departments.isDepartmentSelectionDistanceBased.value).toBe(false);
});
it("treats non-finite browser coordinates as missing instead of distance-selecting a department", async () => {
mocks.locationRef.value = {
coords: {
latitude: Number.POSITIVE_INFINITY,
longitude: Number.POSITIVE_INFINITY,
},
};
mocks.getDepartmentsGuest.mockResolvedValue([
{
id: 1,
name: "Fallback",
address: "A",
latitude: 55.6,
longitude: 12.5,
lanes: [{ id: 10, selfserve_enabled: true }],
self_serve_enabled: true,
},
{
id: 2,
name: "Other",
address: "B",
latitude: 55.7,
longitude: 12.6,
lanes: [{ id: 11, selfserve_enabled: true }],
self_serve_enabled: true,
},
]);
const departments = mountDepartments({ includeLanes: true });
await departments.fetchDepartments();
expect(mocks.getDistance).not.toHaveBeenCalled();
expect(departments.nearestDepartment.value).toMatchObject({ id: 1, name: "Fallback", distance: null });
expect(departments.departmentSelectionStrategy.value).toBe("fallback");
});
it("skips default zero department coordinates when selecting by GPS", async () => {
mocks.getDistance.mockImplementation((_from, to) => {
if (to.latitude === 55.7) {
return 12;
}
return 1;
});
mocks.getDepartmentsGuest.mockResolvedValue([
{
id: 1,
name: "Default coordinate row",
address: "A",
latitude: 0,
longitude: 0,
lanes: [{ id: 10, selfserve_enabled: true }],
self_serve_enabled: true,
},
{
id: 2,
name: "Real coordinate row",
address: "B",
latitude: 55.7,
longitude: 12.6,
lanes: [{ id: 11, selfserve_enabled: true }],
self_serve_enabled: true,
},
]);
const departments = mountDepartments({ includeLanes: true });
await departments.fetchDepartments();
expect(mocks.getDistance.mock.calls.every(([, to]) => to.latitude === 55.7 && to.longitude === 12.6)).toBe(true);
expect(departments.nearestDepartment.value).toMatchObject({
id: 2,
name: "Real coordinate row",
distance: 12,
});
expect(departments.departmentSelectionStrategy.value).toBe("distance");
});
it("pushes departments with default zero coordinates after valid coordinates when sorting by distance", async () => {
mocks.getDistance.mockImplementation((_from, to) => {
if (to.latitude === 55.7) {
return 12;
}
if (to.latitude === 55.8) {
return 8;
}
return 1;
});
const departments = mountDepartments({ includeLanes: true });
const unsortedDepartments = [
{ id: 1, name: "Default coordinate row", latitude: 0, longitude: 0 },
{ id: 2, name: "Farther valid row", latitude: 55.7, longitude: 12.6 },
{ id: 3, name: "Nearer valid row", latitude: 55.8, longitude: 12.7 },
];
expect(departments.orderDepartmentsByDistance(unsortedDepartments).map((department) => department.id)).toEqual([
3, 2, 1,
]);
});
it("clears the selected department for empty department responses", async () => {
mocks.getDepartmentsGuest.mockResolvedValueOnce([
{ id: 1, name: "North", address: "A", latitude: 55.6, longitude: 12.5, lanes: [], self_serve_enabled: true },
]);
const departments = mountDepartments();
await departments.fetchDepartments();
expect(departments.nearestDepartment.value).toMatchObject({ id: 1 });
mocks.getDepartmentsGuest.mockResolvedValueOnce([]);
await departments.fetchDepartments();
expect(departments.guestDepartments.value).toEqual([]);
expect(departments.nearestDepartment.value).toBeNull();
expect(departments.departmentSelectionStrategy.value).toBeNull();
});
it("does not fallback to disabled departments when coordinates are missing", async () => {
mocks.locationRef.value = { value: null };
mocks.getDepartmentsGuest.mockResolvedValue([
{
id: 1,
name: "Module disabled",
address: "A",
latitude: 55.6,
longitude: 12.5,
lanes: [{ id: 10, selfserve_enabled: true }],
self_serve_enabled: false,
},
{
id: 2,
name: "Lane disabled",
address: "B",
latitude: 55.7,
longitude: 12.6,
lanes: [{ id: 11, selfserve_enabled: "no" }],
self_serve_enabled: true,
},
]);
const departments = mountDepartments({ includeLanes: true });
await departments.fetchDepartments();
expect(departments.nearestDepartment.value).toBeNull();
expect(departments.departmentSelectionStrategy.value).toBeNull();
});
it("allows regular users to force a department when coordinates are missing", async () => {
mocks.locationRef.value = {};
mocks.getDepartmentsGuest.mockResolvedValue([
{
id: 1,
name: "Fallback",
address: "A",
latitude: 55.6,
longitude: 12.5,
lanes: [{ id: 10 }],
self_serve_enabled: true,
},
{
id: 2,
name: "Manual choice",
address: "B",
latitude: 55.7,
longitude: 12.6,
lanes: [{ id: 11 }],
self_serve_enabled: true,
},
]);
const departments = mountDepartments({ canAccessSuperUser: () => false });
await departments.fetchDepartments();
departments.startDepartmentSearch();
departments.selectDepartment(2);
expect(departments.isSearchingDepartments.value).toBe(false);
expect(departments.isForcingNearestDepartment.value).toBe(true);
expect(departments.nearestDepartment.value).toMatchObject({ id: 2, name: "Manual choice" });
expect(departments.departmentSelectionStrategy.value).toBe("forced");
});
});