404 lines
12 KiB
JavaScript
404 lines
12 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { defineComponent, h, nextTick } from "vue";
|
|
import { mount } from "@vue/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("vue-router", () => {
|
|
const push = vi.fn();
|
|
return {
|
|
useRouter: () => ({ push }),
|
|
__routerPush: push,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/global/PageLoader.vue", () => {
|
|
return {
|
|
default: defineComponent({
|
|
name: "MockPageLoader",
|
|
props: {
|
|
title: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
},
|
|
setup(props) {
|
|
return () => h("div", { "data-testid": "page-loader" }, props.title);
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1Location.vue", () => {
|
|
return {
|
|
default: defineComponent({
|
|
name: "MockPosDepartmentStepMobile1Location",
|
|
props: {
|
|
enableHighAccuracy: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
maximumAge: {
|
|
type: Number,
|
|
default: 30000,
|
|
},
|
|
timeout: {
|
|
type: Number,
|
|
default: 27000,
|
|
},
|
|
},
|
|
setup(props) {
|
|
return () =>
|
|
h("div", {
|
|
"data-testid": "location-probe",
|
|
"data-enable-high-accuracy": String(props.enableHighAccuracy),
|
|
"data-maximum-age": String(props.maximumAge),
|
|
"data-timeout": String(props.timeout),
|
|
});
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/viewport/conditions/ViewportTypes.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
const isMobile = ref(false);
|
|
return {
|
|
isMobile,
|
|
__isMobileRef: isMobile,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/pagination/departmentTabs.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
const departments = ref([]);
|
|
const getDepartments = vi.fn(() => Promise.resolve());
|
|
return {
|
|
departments,
|
|
getDepartments,
|
|
__departmentsRef: departments,
|
|
__getDepartmentsMock: getDepartments,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
const location = ref(null);
|
|
const defaultTimeout = ref(500);
|
|
const locations = {
|
|
location,
|
|
defaultTimeout,
|
|
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 };
|
|
},
|
|
set: (newLocation) => {
|
|
location.value = newLocation;
|
|
},
|
|
get: () => location.value,
|
|
clear: () => {
|
|
location.value = null;
|
|
},
|
|
getDistance: (from, to) => {
|
|
return Math.abs(from.latitude - to.latitude) + Math.abs(from.longitude - to.longitude);
|
|
},
|
|
};
|
|
|
|
return {
|
|
locations,
|
|
__locationRef: location,
|
|
__defaultTimeoutRef: defaultTimeout,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
const initiated = ref(false);
|
|
const permissions = ref([]);
|
|
const isSubuser = ref(false);
|
|
const token = ref(null);
|
|
|
|
const hasPermission = (permission) => permissions.value.includes(permission);
|
|
const SessionUser = {
|
|
initiated,
|
|
permissions,
|
|
isSubuser,
|
|
token,
|
|
isInitiated: () => initiated.value,
|
|
hasToken: () => token.value !== null,
|
|
canAccessSuperUser: () => hasPermission("superuser"),
|
|
canAccessAdmin: () => hasPermission("admin") || hasPermission("superuser"),
|
|
canAccessUser: () => hasPermission("user"),
|
|
canAccessGuest: () => true,
|
|
canAccessAssignedDepartment: (id) => hasPermission(`department_access_${Number.parseInt(id, 10)}`),
|
|
};
|
|
|
|
return {
|
|
SessionUser,
|
|
__sessionState: {
|
|
initiated,
|
|
permissions,
|
|
isSubuser,
|
|
token,
|
|
},
|
|
};
|
|
});
|
|
|
|
import DefaultPage from "@/views/DefaultPage.vue";
|
|
import { __routerPush } from "vue-router";
|
|
import { __isMobileRef } from "@/components/viewport/conditions/ViewportTypes.vue";
|
|
import { __departmentsRef, __getDepartmentsMock } from "@/components/pagination/departmentTabs.vue";
|
|
import {
|
|
__defaultTimeoutRef,
|
|
__locationRef,
|
|
} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
|
|
import { __sessionState } from "@/components/session/token/SessionUser.vue";
|
|
|
|
let wrapper = null;
|
|
|
|
const mountDefaultPage = () => {
|
|
wrapper = mount(DefaultPage, {
|
|
global: {
|
|
mocks: {
|
|
$t: (key, params = {}) => (params.name ? `${key}:${params.name}` : key),
|
|
},
|
|
},
|
|
});
|
|
return wrapper;
|
|
};
|
|
|
|
const setAdminSession = (permissions = ["admin"]) => {
|
|
__sessionState.initiated.value = true;
|
|
__sessionState.permissions.value = permissions;
|
|
__sessionState.isSubuser.value = false;
|
|
__sessionState.token.value = "unit-token";
|
|
};
|
|
|
|
const setLocation = ({ latitude, longitude, timestamp = Date.now() }) => {
|
|
__locationRef.value = {
|
|
coords: {
|
|
latitude,
|
|
longitude,
|
|
},
|
|
timestamp: new Date(timestamp),
|
|
locatedAt: timestamp,
|
|
};
|
|
};
|
|
|
|
describe("DefaultPage mobile department redirect", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date("2026-06-11T10:00:00.000Z"));
|
|
__routerPush.mockReset();
|
|
__getDepartmentsMock.mockReset();
|
|
__getDepartmentsMock.mockResolvedValue(undefined);
|
|
__isMobileRef.value = true;
|
|
__departmentsRef.value = [];
|
|
__locationRef.value = null;
|
|
__defaultTimeoutRef.value = 500;
|
|
setAdminSession(["admin", "department_access_1", "department_access_2"]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
wrapper?.unmount();
|
|
wrapper = null;
|
|
vi.clearAllTimers();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("routes when geolocation arrives before departments finish loading", async () => {
|
|
mountDefaultPage();
|
|
await nextTick();
|
|
|
|
setLocation({
|
|
latitude: 55.5,
|
|
longitude: 12.5,
|
|
});
|
|
await nextTick();
|
|
|
|
expect(__routerPush).not.toHaveBeenCalledWith({ name: "pos", params: { departmentId: 2 } });
|
|
|
|
__departmentsRef.value = [
|
|
{ id: 1, name: "Far", latitude: 55.0, longitude: 12.0 },
|
|
{ id: 2, name: "Near", latitude: 55.51, longitude: 12.51 },
|
|
];
|
|
await nextTick();
|
|
|
|
expect(__getDepartmentsMock).toHaveBeenCalledTimes(1);
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "pos", params: { departmentId: 2 } });
|
|
});
|
|
|
|
it("requests a fresh geolocation fix for mobile department auto-selection", async () => {
|
|
const mounted = mountDefaultPage();
|
|
await nextTick();
|
|
|
|
const probe = mounted.find('[data-testid="location-probe"]');
|
|
expect(probe.attributes("data-enable-high-accuracy")).toBe("true");
|
|
expect(probe.attributes("data-maximum-age")).toBe("0");
|
|
expect(probe.attributes("data-timeout")).toBe("27000");
|
|
});
|
|
|
|
it("shows manual department selection after five seconds", async () => {
|
|
const mounted = mountDefaultPage();
|
|
await nextTick();
|
|
|
|
expect(mounted.find('[data-testid="redirect-manual-department-button"]').exists()).toBe(false);
|
|
|
|
await vi.advanceTimersByTimeAsync(4999);
|
|
await nextTick();
|
|
|
|
expect(mounted.find('[data-testid="redirect-manual-department-button"]').exists()).toBe(false);
|
|
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
await nextTick();
|
|
|
|
expect(mounted.find('[data-testid="redirect-manual-department-button"]').exists()).toBe(true);
|
|
});
|
|
|
|
it("lets the user manually select a department after GPS wait", async () => {
|
|
setAdminSession(["admin", "department_access_4", "department_access_6"]);
|
|
__departmentsRef.value = [
|
|
{ id: 4, name: "Køge", latitude: 55.458, longitude: 12.182 },
|
|
{ id: 6, name: "Roskilde", latitude: 55.642, longitude: 12.08 },
|
|
];
|
|
|
|
const mounted = mountDefaultPage();
|
|
await nextTick();
|
|
|
|
await vi.advanceTimersByTimeAsync(5000);
|
|
await nextTick();
|
|
|
|
await mounted.get('[data-testid="redirect-manual-department-button"]').trigger("click");
|
|
await nextTick();
|
|
|
|
expect(mounted.find('[data-testid="redirect-manual-department-options"]').exists()).toBe(true);
|
|
|
|
setLocation({
|
|
latitude: 55.458,
|
|
longitude: 12.182,
|
|
});
|
|
await nextTick();
|
|
|
|
expect(__routerPush).not.toHaveBeenCalledWith({ name: "pos", params: { departmentId: 4 } });
|
|
|
|
await vi.advanceTimersByTimeAsync(27000);
|
|
await nextTick();
|
|
|
|
expect(__routerPush).not.toHaveBeenCalledWith({ name: "admin" });
|
|
|
|
await mounted.get('[data-testid="redirect-manual-department-option-6"]').trigger("click");
|
|
await nextTick();
|
|
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "pos", params: { departmentId: 6 } });
|
|
});
|
|
|
|
it("ignores stale cached location and waits for a fresh Roskilde location", async () => {
|
|
setAdminSession(["admin", "department_access_4", "department_access_6"]);
|
|
__departmentsRef.value = [
|
|
{ id: 4, name: "Køge", latitude: 55.458, longitude: 12.182 },
|
|
{ id: 6, name: "Roskilde", latitude: 55.642, longitude: 12.08 },
|
|
];
|
|
setLocation({
|
|
latitude: 55.458,
|
|
longitude: 12.182,
|
|
timestamp: Date.now() - 30000,
|
|
});
|
|
|
|
mountDefaultPage();
|
|
await nextTick();
|
|
|
|
expect(__locationRef.value).toBeNull();
|
|
expect(__routerPush).not.toHaveBeenCalledWith({ name: "pos", params: { departmentId: 4 } });
|
|
|
|
await vi.advanceTimersByTimeAsync(100);
|
|
setLocation({
|
|
latitude: 55.642,
|
|
longitude: 12.08,
|
|
});
|
|
await nextTick();
|
|
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "pos", params: { departmentId: 6 } });
|
|
});
|
|
|
|
it("does not redirect from a location timestamp older than the auto-select attempt", async () => {
|
|
setAdminSession(["admin", "department_access_4", "department_access_6"]);
|
|
__departmentsRef.value = [
|
|
{ id: 4, name: "Køge", latitude: 55.458, longitude: 12.182 },
|
|
{ id: 6, name: "Roskilde", latitude: 55.642, longitude: 12.08 },
|
|
];
|
|
const staleTimestamp = Date.now() - 1000;
|
|
|
|
mountDefaultPage();
|
|
await nextTick();
|
|
|
|
setLocation({
|
|
latitude: 55.458,
|
|
longitude: 12.182,
|
|
timestamp: staleTimestamp,
|
|
});
|
|
await nextTick();
|
|
|
|
expect(__routerPush).not.toHaveBeenCalledWith({ name: "pos", params: { departmentId: 4 } });
|
|
|
|
await vi.advanceTimersByTimeAsync(27000);
|
|
await nextTick();
|
|
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "admin" });
|
|
});
|
|
|
|
it("routes immediately when only one accessible department exists", async () => {
|
|
setAdminSession(["admin", "department_access_6"]);
|
|
__departmentsRef.value = [
|
|
{ id: 4, name: "Køge", latitude: 55.458, longitude: 12.182 },
|
|
{ id: 6, name: "Roskilde", latitude: 55.642, longitude: 12.08 },
|
|
];
|
|
|
|
mountDefaultPage();
|
|
await nextTick();
|
|
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "pos", params: { departmentId: 6 } });
|
|
});
|
|
|
|
it("falls back after timeout even when location exists but nearest cannot be resolved", async () => {
|
|
__departmentsRef.value = [
|
|
{ id: 1, name: "Missing coordinates" },
|
|
{ id: 2, name: "Also missing coordinates" },
|
|
];
|
|
|
|
mountDefaultPage();
|
|
await nextTick();
|
|
|
|
setLocation({
|
|
latitude: 55.5,
|
|
longitude: 12.5,
|
|
});
|
|
await nextTick();
|
|
|
|
expect(__routerPush).not.toHaveBeenCalled();
|
|
|
|
await vi.advanceTimersByTimeAsync(27000);
|
|
await nextTick();
|
|
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "admin" });
|
|
});
|
|
|
|
it("does not auto-select departments for desktop admin users", async () => {
|
|
__isMobileRef.value = false;
|
|
|
|
mountDefaultPage();
|
|
await nextTick();
|
|
|
|
expect(__getDepartmentsMock).not.toHaveBeenCalled();
|
|
expect(__routerPush).toHaveBeenCalledWith({ name: "admin" });
|
|
});
|
|
});
|