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

409 lines
12 KiB
JavaScript

import { ref } from "vue";
import { describe, expect, it, vi } from "vitest";
import { useWashSessionActions } from "@/composables/useWashSessionActions.js";
const createActions = (overrides = {}) => {
const request = vi.fn(async () => ({ data: { success: true } }));
const alertFn = vi.fn();
const options = {
request,
alertFn,
nearestDepartment: ref({
id: 1,
lanes: [{ id: 7 }],
}),
vehicleTypeSelect: ref(2),
washLaneId: ref(7),
washInProgress: ref(true),
washStartTime: ref(Date.now() - 30_000),
completedDurationMs: ref(null),
now: ref(Date.now()),
currentStep: ref(4),
steps: {
VEHICLE: 0,
QUESTIONS: 1,
SELECT_LANE: 2,
TASKS: 3,
WASH_IN_PROGRESS: 4,
COMPLETED: 5,
},
radioWashType: ref("Manual"),
activeTasks: ref([]),
completedTasks: ref({}),
saveProgress: vi.fn(),
clearProgress: vi.fn(),
startElapsedTimer: vi.fn(),
stopElapsedTimer: vi.fn(),
updateLaneAllowedServices: vi.fn(),
fetchWashSummary: vi.fn(),
enableMachineRelay: vi.fn(),
isServiceAllowed: vi.fn(() => true),
...overrides,
};
return {
request: options.request,
alertFn,
options,
actions: useWashSessionActions(options),
};
};
describe("useWashSessionActions property gate commands", () => {
it("opens property access gate with explicit lane id and tracks loading state", async () => {
const deferred = {};
deferred.promise = new Promise((resolve) => {
deferred.resolve = resolve;
});
const { actions, request } = createActions({
request: vi.fn(() => deferred.promise),
});
const commandPromise = actions.openPropertyAccessGate(9);
expect(actions.openingPropertyAccessGate.value).toBe(true);
deferred.resolve({ data: { success: true } });
await commandPromise;
expect(actions.openingPropertyAccessGate.value).toBe(false);
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
lane_id: 9,
command: "OPEN_PROPERTY_ACCESS_GATE",
customer_number: null,
license_plate: null,
});
});
it("opens property exit gate using active wash lane when lane argument is omitted", async () => {
const { actions, request } = createActions();
await actions.openPropertyExitGate();
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
lane_id: 7,
command: "OPEN_PROPERTY_EXIT_GATE",
customer_number: null,
license_plate: null,
});
});
it("warns and skips command when no lane id can be resolved", async () => {
const { actions, request, alertFn } = createActions({
washLaneId: ref(null),
});
const result = await actions.openPropertyAccessGate();
expect(result).toBeNull();
expect(request).not.toHaveBeenCalled();
expect(alertFn).toHaveBeenCalledWith("Ingen vaskebane valgt.");
});
it("keeps flow non-blocking when property gate command fails", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const { actions, alertFn } = createActions({
request: vi.fn(async () => {
throw new Error("command failed");
}),
});
const result = await actions.openPropertyAccessGate();
expect(result).toBeNull();
expect(actions.openingPropertyAccessGate.value).toBe(false);
expect(alertFn).toHaveBeenCalledWith("command failed");
consoleErrorSpy.mockRestore();
});
it("can suppress property exit gate alerts for automatic wash completion", async () => {
const { actions, alertFn } = createActions({
request: vi.fn(async () => ({
data: {
success: false,
data: {
message: "No exit gate configured",
},
},
})),
});
const result = await actions.openPropertyExitGate(7, { suppressAlert: true });
expect(result).toBeNull();
expect(alertFn).not.toHaveBeenCalled();
});
it("builds dynamic image URLs from ordered task button steps", () => {
const activeTasks = ref([
{
id: 101,
services: ["PROGRAM_PICKER"],
buttons: ["reset", 0],
dynamic_images_vehicle_type: 2,
},
{
id: 102,
buttons: [2, "start", 5],
dynamic_images_vehicle_type: 3,
},
]);
const completedTasks = ref({ 101: true });
const { actions } = createActions({
activeTasks,
completedTasks,
});
const url = new URL(actions.dynamicImageUrl.value, "https://app.example.test");
expect(JSON.parse(url.searchParams.get("buttons"))).toEqual(["program_picker", "reset", 0, 2, "start", 5]);
expect(url.searchParams.get("current_step")).toBe("3");
expect(url.searchParams.get("thumb_position")).toBe("2");
expect(actions.machineStartCurrentStep.value).toBe(3);
});
it("does not enter wash-in-progress state when the start command fails", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const washInProgress = ref(false);
const washLaneId = ref(null);
const washStartTime = ref(null);
const currentStep = ref(2);
const request = vi.fn(async (_url, _method, body) => {
if (body.command === "START") {
return {
data: {
success: false,
data: {
message: "Edge gateway command timed out",
},
},
};
}
return { data: { success: true } };
});
const { actions, alertFn } = createActions({
request,
washInProgress,
washLaneId,
washStartTime,
currentStep,
});
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
expect(result).toBe(false);
expect(washInProgress.value).toBe(false);
expect(washLaneId.value).toBeNull();
expect(washStartTime.value).toBeNull();
expect(currentStep.value).toBe(2);
expect(actions.isStartingWash.value).toBe(false);
expect(alertFn).toHaveBeenCalledWith("Edge gateway command timed out");
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
lane_id: 7,
command: "START",
customer_number: 12345679,
license_plate: "AB12345",
defer_relay_side_effects: true,
});
consoleErrorSpy.mockRestore();
});
it("does not duplicate action alerts when allowed-service setup fails before start", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const washInProgress = ref(false);
const washLaneId = ref(null);
const washStartTime = ref(null);
const currentStep = ref(2);
const updateLaneAllowedServices = vi.fn(async () => {
throw new Error("Edge gateway command timed out");
});
const { actions, request, alertFn } = createActions({
updateLaneAllowedServices,
washInProgress,
washLaneId,
washStartTime,
currentStep,
});
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
expect(result).toBe(false);
expect(updateLaneAllowedServices).toHaveBeenCalledWith(7);
expect(request).not.toHaveBeenCalled();
expect(alertFn).not.toHaveBeenCalled();
expect(washInProgress.value).toBe(false);
expect(currentStep.value).toBe(2);
consoleErrorSpy.mockRestore();
});
it("keeps the wash started when the post-start machine relay retry fails", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const washInProgress = ref(false);
const washLaneId = ref(null);
const washStartTime = ref(null);
const currentStep = ref(2);
const enableMachineRelay = vi.fn(async () => {
throw new Error("Edge gateway command timed out");
});
const { actions, request, alertFn } = createActions({
washInProgress,
washLaneId,
washStartTime,
currentStep,
radioWashType: ref("Machine"),
enableMachineRelay,
isServiceAllowed: vi.fn(() => true),
});
const result = await actions.onStartWash(7, "ab12345", 12345679, 4);
expect(result).toBe(true);
expect(washInProgress.value).toBe(true);
expect(washLaneId.value).toBe(7);
expect(currentStep.value).toBe(4);
expect(enableMachineRelay).toHaveBeenCalledWith(7);
expect(alertFn).not.toHaveBeenCalled();
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
lane_id: 7,
command: "START",
customer_number: 12345679,
license_plate: "AB12345",
defer_relay_side_effects: true,
});
consoleErrorSpy.mockRestore();
});
it("keeps the active wash open when the stop command fails", async () => {
const washInProgress = ref(true);
const washLaneId = ref(7);
const washStartTime = ref(Date.now() - 45_000);
const completedDurationMs = ref(null);
const clearProgress = vi.fn();
const stopElapsedTimer = vi.fn();
const request = vi.fn(async (_url, _method, body) => {
if (body.command === "STOP") {
return {
data: {
success: false,
data: {
message: "Stop command failed",
},
},
};
}
return { data: { success: true } };
});
const { actions, alertFn } = createActions({
request,
washInProgress,
washLaneId,
washStartTime,
completedDurationMs,
clearProgress,
stopElapsedTimer,
});
const result = await actions.onStopWash(7);
expect(result).toBe(false);
expect(alertFn).toHaveBeenCalledWith("Stop command failed");
expect(washInProgress.value).toBe(true);
expect(washLaneId.value).toBe(7);
expect(completedDurationMs.value).toBeNull();
expect(stopElapsedTimer).not.toHaveBeenCalled();
expect(clearProgress).not.toHaveBeenCalled();
});
it("clears active wash state when stop reports the lane is already unoccupied", async () => {
const washInProgress = ref(true);
const washLaneId = ref(7);
const washStartTime = ref(1_000);
const completedDurationMs = ref(null);
const now = ref(46_000);
const clearProgress = vi.fn();
const stopElapsedTimer = vi.fn();
const request = vi.fn(async (_url, _method, body) => {
if (body.command === "STOP") {
return {
data: {
success: false,
data: {
message: "Failed to execute command: Cannot stop lane: Lane is not occupied.",
},
},
};
}
return { data: { success: true } };
});
const { actions, alertFn } = createActions({
request,
washInProgress,
washLaneId,
washStartTime,
completedDurationMs,
now,
clearProgress,
stopElapsedTimer,
});
const result = await actions.onStopWash(7);
expect(result).toBe(true);
expect(alertFn).not.toHaveBeenCalled();
expect(completedDurationMs.value).toBe(45_000);
expect(washInProgress.value).toBe(false);
expect(washLaneId.value).toBeNull();
expect(stopElapsedTimer).toHaveBeenCalledTimes(1);
expect(clearProgress).toHaveBeenCalledTimes(1);
});
it("clears active wash state only after the stop command succeeds", async () => {
const washInProgress = ref(true);
const washLaneId = ref(7);
const washStartTime = ref(1_000);
const completedDurationMs = ref(null);
const now = ref(61_000);
const clearProgress = vi.fn();
const stopElapsedTimer = vi.fn();
const { actions, request, alertFn } = createActions({
washInProgress,
washLaneId,
washStartTime,
completedDurationMs,
now,
clearProgress,
stopElapsedTimer,
});
const result = await actions.onStopWash(7);
expect(result).toBe(true);
expect(alertFn).not.toHaveBeenCalled();
expect(request).toHaveBeenCalledWith("/modules/self-serve/lane/command", "post", {
lane_id: 7,
command: "STOP",
customer_number: null,
license_plate: null,
});
expect(completedDurationMs.value).toBe(60_000);
expect(washInProgress.value).toBe(false);
expect(washLaneId.value).toBeNull();
expect(stopElapsedTimer).toHaveBeenCalledTimes(1);
expect(clearProgress).toHaveBeenCalledTimes(1);
});
});