996 lines
33 KiB
JavaScript
996 lines
33 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
previewAllowed: vi.fn(),
|
|
washSummary: vi.fn(),
|
|
add: vi.fn(),
|
|
getAll: vi.fn(),
|
|
deleteCondition: vi.fn(),
|
|
attachmentsList: vi.fn(),
|
|
attachmentsDownload: vi.fn(),
|
|
request: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
request: mocks.request,
|
|
objects: {
|
|
self_serve_vehicle_conditions: {
|
|
get: {
|
|
previewAllowed: mocks.previewAllowed,
|
|
washSummary: mocks.washSummary,
|
|
all: mocks.getAll,
|
|
},
|
|
add: mocks.add,
|
|
delete: mocks.deleteCondition,
|
|
},
|
|
self_serve_tasks: {
|
|
attachments: {
|
|
list: mocks.attachmentsList,
|
|
download: mocks.attachmentsDownload,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
import { useSelfServeLogic } from "@/composables/useSelfServeLogic.js";
|
|
|
|
describe("useSelfServeLogic", () => {
|
|
beforeEach(() => {
|
|
mocks.previewAllowed.mockReset();
|
|
mocks.washSummary.mockReset();
|
|
mocks.add.mockReset();
|
|
mocks.getAll.mockReset();
|
|
mocks.deleteCondition.mockReset();
|
|
mocks.attachmentsList.mockReset();
|
|
mocks.attachmentsDownload.mockReset();
|
|
mocks.request.mockReset();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("uses task attachments supplied by preview and summary data", async () => {
|
|
const taskAttachments = [
|
|
{ id: 201, content: { other: "manual.pdf" }, download_link: "https://cdn.example.test/manual.pdf" },
|
|
{ id: 202, content: { other: "photo.jpg" }, download_link: "https://cdn.example.test/photo.jpg" },
|
|
];
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
machine_available: true,
|
|
lane: { id: 7, name: "Lane 7" },
|
|
session: { id: 91, status: "IN_PROGRESS" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: null, order_priority: 1 }],
|
|
tasks: [{ id: 5, task: "Prepare", order_priority: 1, services: ["MACHINE"], attachments: taskAttachments }],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: ["MACHINE"],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 91, status: "IN_PROGRESS" },
|
|
lane: { id: 7, name: "Lane 7" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: true, order_priority: 1 }],
|
|
tasks: [{ id: 5, task: "Prepare", order_priority: 1, services: ["MACHINE"], attachments: taskAttachments }],
|
|
events: [{ id: 1, type: "STARTED", created_at: "2026-01-01T00:00:00.000Z" }],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(mocks.previewAllowed).toHaveBeenCalledWith(7, "AB12345", 9);
|
|
expect(mocks.washSummary).toHaveBeenCalledWith({ session_id: 91 });
|
|
expect(logic.allowed.value).toBe(true);
|
|
expect(logic.machineAvailable.value).toBe(true);
|
|
expect(logic.answers.value[1]).toBe(true);
|
|
expect(logic.events.value).toHaveLength(1);
|
|
expect(mocks.attachmentsList).not.toHaveBeenCalled();
|
|
expect(mocks.attachmentsDownload).not.toHaveBeenCalled();
|
|
expect(logic.tasks.value[0].attachments[1]).toMatchObject({
|
|
id: 202,
|
|
download_link: "https://cdn.example.test/photo.jpg",
|
|
});
|
|
});
|
|
|
|
it("clears stale wash summary machine availability when a fresh preview has no session", async () => {
|
|
mocks.previewAllowed
|
|
.mockResolvedValueOnce({
|
|
allowed: true,
|
|
machine_available: true,
|
|
lane: { id: 7, name: "Lane 7" },
|
|
session: { id: 91, status: "READY_FOR_MACHINE_START" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: true, order_priority: 1 }],
|
|
tasks: [{ id: 5, task: "Prepare", order_priority: 1, services: ["MACHINE"] }],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: ["MACHINE"],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
allowed: true,
|
|
machine_available: true,
|
|
lane: { id: 7, name: "Lane 7" },
|
|
session: null,
|
|
questions: [{ id: 1, question: "Machine wash?", answer: true, order_priority: 1 }],
|
|
tasks: [{ id: 5, task: "Prepare", order_priority: 1, services: ["MACHINE"] }],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: ["MACHINE"],
|
|
});
|
|
mocks.washSummary.mockResolvedValueOnce({
|
|
machine_available: false,
|
|
session: { id: 91, status: "READY_FOR_MACHINE_START" },
|
|
lane: { id: 7, name: "Lane 7" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: true, order_priority: 1 }],
|
|
tasks: [{ id: 5, task: "Prepare", order_priority: 1, services: ["MACHINE"] }],
|
|
events: [],
|
|
allowed_services: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
expect(logic.machineAvailable.value).toBe(false);
|
|
|
|
await logic.fetchSelfServeData(1, 9, 7, "cd12345");
|
|
|
|
expect(logic.summary.value).toBeNull();
|
|
expect(logic.machineAvailable.value).toBe(true);
|
|
expect(logic.allowedServices.value).toEqual(["MACHINE"]);
|
|
});
|
|
|
|
it("preserves preview task ids when wash summary omits task ids", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
machine_available: true,
|
|
lane: { id: 7, name: "Lane 7" },
|
|
session: { id: 91, status: "READY_FOR_MACHINE_START" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: false, order_priority: 1 }],
|
|
tasks: [
|
|
{
|
|
id: 802001,
|
|
task: "Vælg program #2 på dial",
|
|
condition_id: 702001,
|
|
order_priority: 10,
|
|
services: ["MACHINE"],
|
|
},
|
|
{
|
|
id: 802006,
|
|
task: "Tryk knap #6 (tagbørste program)",
|
|
condition_id: 702003,
|
|
order_priority: 36,
|
|
services: ["MACHINE"],
|
|
},
|
|
],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: ["MACHINE"],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 91, status: "READY_FOR_MACHINE_START" },
|
|
lane: { id: 7, name: "Lane 7" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: false, order_priority: 1 }],
|
|
tasks: [
|
|
{ task: "Vælg program #2 på dial", condition_id: 702001, order_priority: 10, services: ["MACHINE"] },
|
|
{ task: "Tryk knap #6 (tagbørste program)", condition_id: 702003, order_priority: 36, services: ["MACHINE"] },
|
|
],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(logic.tasks.value.map((task) => task.id)).toEqual([802001, 802006]);
|
|
expect(logic.activeTasks.value.map((task) => task.id)).toEqual([802001, 802006]);
|
|
});
|
|
|
|
it("does not infer machine permission from summary tasks when allowed services are explicitly empty", async () => {
|
|
const machineButtonTask = {
|
|
id: 5,
|
|
task: "Press reset",
|
|
order_priority: 1,
|
|
services: ["MACHINE"],
|
|
buttons: ["reset"],
|
|
dynamic_images_vehicle_type: 2,
|
|
};
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: false,
|
|
machine_available: true,
|
|
lane: { id: 7, name: "Lane 7" },
|
|
session: { id: 91, status: "MACHINE_NOT_ALLOWED", allowed: false },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: false, order_priority: 1 }],
|
|
tasks: [machineButtonTask],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 91, status: "MACHINE_NOT_ALLOWED", allowed: false },
|
|
lane: { id: 7, name: "Lane 7" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: false, order_priority: 1 }],
|
|
tasks: [machineButtonTask],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(logic.allowed.value).toBe(false);
|
|
expect(logic.allowedServices.value).toEqual([]);
|
|
expect(logic.activeTaskServices.value).toEqual([]);
|
|
expect(logic.isServiceAllowed("MACHINE")).toBe(false);
|
|
expect(logic.tasks.value.map((task) => task.id)).toEqual([5]);
|
|
});
|
|
|
|
it("opens an existing task attachment link without requesting a new download link", async () => {
|
|
const openSpy = vi.fn();
|
|
vi.stubGlobal("window", { open: openSpy });
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.downloadAttachment(5, 202, {
|
|
id: 202,
|
|
download_link: "https://api-v2.truckwash.io/master/api/attachments/photo.jpg",
|
|
content: { other: "photo.jpg" },
|
|
});
|
|
|
|
expect(mocks.attachmentsDownload).not.toHaveBeenCalled();
|
|
expect(openSpy).toHaveBeenCalledWith(
|
|
"https://api-v2.truckwash.io/master/api/attachments/photo.jpg",
|
|
"_blank",
|
|
"noopener"
|
|
);
|
|
});
|
|
|
|
it("does not open untrusted embedded task attachment links", async () => {
|
|
const openSpy = vi.fn();
|
|
vi.stubGlobal("window", { open: openSpy });
|
|
mocks.attachmentsDownload.mockResolvedValue({ download_link: "javascript:alert(document.domain)" });
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.downloadAttachment(5, 202, {
|
|
id: 202,
|
|
download_link: "data:text/html,<script>alert(document.domain)</script>",
|
|
content: { other: "photo.jpg" },
|
|
});
|
|
|
|
expect(mocks.attachmentsDownload).toHaveBeenCalledWith(5, 202);
|
|
expect(openSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("falls back to lane/reg summary when session summary does not include questions", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 91, status: "IN_PROGRESS" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary
|
|
.mockResolvedValueOnce({
|
|
session: { id: 91, status: "IN_PROGRESS" },
|
|
events: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
session: { id: 91, status: "IN_PROGRESS" },
|
|
questions: [{ id: 1, question: "Machine wash?", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(mocks.washSummary).toHaveBeenNthCalledWith(1, { session_id: 91 });
|
|
expect(mocks.washSummary).toHaveBeenNthCalledWith(2, { lane_id: 7, reg: "AB12345", vehicle_type: 9 });
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1]);
|
|
});
|
|
|
|
it("keeps preview questions visible when no summary session exists yet", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: null,
|
|
questions: [
|
|
{ id: 1, question: "Machine wash?", answer: null, order_priority: 1 },
|
|
{ id: 2, question: "Doors closed?", answer: null, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(mocks.washSummary).not.toHaveBeenCalled();
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1, 2]);
|
|
});
|
|
|
|
it("keeps the newest task list when overlapping preview requests resolve out of order", async () => {
|
|
const createDeferred = () => {
|
|
let resolve;
|
|
const promise = new Promise((res) => {
|
|
resolve = res;
|
|
});
|
|
|
|
return { promise, resolve };
|
|
};
|
|
|
|
const firstPreview = createDeferred();
|
|
const secondPreview = createDeferred();
|
|
|
|
mocks.previewAllowed
|
|
.mockImplementationOnce(() => firstPreview.promise)
|
|
.mockImplementationOnce(() => secondPreview.promise);
|
|
mocks.attachmentsList.mockResolvedValue({ data: [] });
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
const firstFetch = logic.fetchSelfServeData(1, null, 7, "ab12345");
|
|
const secondFetch = logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
secondPreview.resolve({
|
|
allowed: true,
|
|
session: null,
|
|
questions: [],
|
|
tasks: [
|
|
{ id: 1001, task: "Task A", order_priority: 1 },
|
|
{ id: 1002, task: "Task B", order_priority: 2 },
|
|
],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: [],
|
|
});
|
|
await secondFetch;
|
|
|
|
expect(logic.tasks.value.map((task) => task.id)).toEqual([1001, 1002]);
|
|
|
|
firstPreview.resolve({
|
|
allowed: true,
|
|
session: null,
|
|
questions: [],
|
|
tasks: [{ id: 1001, task: "Task A", order_priority: 1 }],
|
|
conditions: [],
|
|
rules: [],
|
|
allowed_services: [],
|
|
});
|
|
await firstFetch;
|
|
|
|
expect(logic.tasks.value.map((task) => task.id)).toEqual([1001, 1002]);
|
|
});
|
|
|
|
it("exposes preview errors for retry UI", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
mocks.previewAllowed.mockRejectedValue({
|
|
response: {
|
|
data: {
|
|
data: {
|
|
message: "Edge gateway command timed out",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
const result = await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(result).toBeNull();
|
|
expect(logic.loading.value).toBe(false);
|
|
expect(logic.error.value).toBe("Edge gateway command timed out");
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("surfaces edge gateway failures when updating lane allowed services", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
mocks.request
|
|
.mockResolvedValueOnce({
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Edge gateway command timed out",
|
|
},
|
|
},
|
|
})
|
|
.mockResolvedValueOnce({
|
|
data: {
|
|
success: true,
|
|
data: {
|
|
allowed_services: ["GATE"],
|
|
},
|
|
},
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
logic.tasks.value = [{ id: 5, task_id: 5, task: "Machine step", order_priority: 1, services: ["MACHINE"] }];
|
|
|
|
await expect(logic.updateLaneAllowedServices(7)).rejects.toThrow("Edge gateway command timed out");
|
|
expect(mocks.request).toHaveBeenNthCalledWith(1, "/modules/self-serve/lane/services/allowed", "post", {
|
|
lane_id: 7,
|
|
task_ids: [5],
|
|
});
|
|
expect(logic.error.value).toBe("Edge gateway command timed out");
|
|
expect(logic.allowedServices.value).toEqual(["MACHINE"]);
|
|
|
|
await logic.updateLaneAllowedServices(7);
|
|
|
|
expect(logic.error.value).toBeNull();
|
|
expect(logic.allowedServices.value).toEqual(["GATE"]);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("evaluates conditional rules for task activation", () => {
|
|
const logic = useSelfServeLogic();
|
|
|
|
logic.questions.value = [
|
|
{ id: 1, question: "Question 1", order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Question 2", order_priority: 2, condition_id: 100 },
|
|
];
|
|
logic.conditions.value = [{ id: 100, name: "Question 1 yes" }];
|
|
logic.rules.value = [
|
|
{ id: 501, condition_id: 100, object_type: "question", object_id: 1, type: "IS_TRUE", name: "Q1 true" },
|
|
];
|
|
logic.tasks.value = [
|
|
{ id: 8, task: "Machine step", order_priority: 1, condition_id: 100, services: ["MACHINE"] },
|
|
{ id: 9, task: "Always visible", order_priority: 2, condition_id: null, services: [] },
|
|
];
|
|
logic.answers.value = { 1: false };
|
|
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([]);
|
|
expect(logic.evaluateCondition(100)).toBe(false);
|
|
expect(logic.activeTasks.value.map((task) => task.id)).toEqual([9]);
|
|
|
|
logic.answerQuestion(1, true);
|
|
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([]);
|
|
expect(logic.evaluateCondition(100)).toBe(true);
|
|
expect(logic.activeTasks.value.map((task) => task.id)).toEqual([8, 9]);
|
|
expect(logic.isServiceAllowed("MACHINE")).toBe(true);
|
|
});
|
|
|
|
it("does not show any questions before summary visibility is available", () => {
|
|
const logic = useSelfServeLogic();
|
|
|
|
logic.questions.value = [
|
|
{ id: 1, question: "Always visible", order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Included conditional", order_priority: 2, condition_id: 100 },
|
|
];
|
|
logic.rules.value = [];
|
|
logic.answers.value = { 1: true };
|
|
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([]);
|
|
});
|
|
|
|
it("synchronizes answers and only keeps questions listed in summary response", async () => {
|
|
mocks.previewAllowed.mockResolvedValueOnce({
|
|
allowed: true,
|
|
session: { id: 44, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 2, question: "Doors closed?", answer: null, order_priority: 1 },
|
|
{ id: 3, question: "Windows closed?", answer: null, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValueOnce({
|
|
session: { id: 44, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 2, question: "Doors closed?", answer: null, order_priority: 1 },
|
|
{ id: 3, question: "Windows closed?", answer: null, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
|
|
mocks.add.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
selfserve: {
|
|
session: { id: 44, status: "IN_PROGRESS" },
|
|
questions: [{ id: 2, question: "Doors closed?", answer: true, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 44, status: "IN_PROGRESS" },
|
|
questions: [{ id: 2, question: "Doors closed?", answer: true, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 44, status: "IN_PROGRESS" },
|
|
questions: [{ id: 2, question: "Doors closed?", answer: true, order_priority: 1 }],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(3, null, 7, "cd67890");
|
|
|
|
await logic.syncVehicleAnswer({
|
|
departmentId: 3,
|
|
laneId: 7,
|
|
customerNumber: 12345,
|
|
reg: "cd67890",
|
|
questionId: 2,
|
|
value: true,
|
|
});
|
|
|
|
expect(mocks.add).toHaveBeenCalledWith(3, 7, 12345, "CD67890", 2, true, {
|
|
activate_machine: false,
|
|
sync_relay_state: false,
|
|
});
|
|
expect(mocks.previewAllowed).toHaveBeenCalledWith(7, "CD67890");
|
|
expect(logic.answers.value[2]).toBe(true);
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([2]);
|
|
});
|
|
|
|
it("preserves the selected vehicle-type override when refreshing after answer sync", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 66, status: "IN_PROGRESS" },
|
|
questions: [{ id: 1, question: "Primary question", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 66, status: "IN_PROGRESS" },
|
|
questions: [{ id: 1, question: "Primary question", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
mocks.add.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
selfserve: {
|
|
session: { id: 66, status: "IN_PROGRESS" },
|
|
questions: [{ id: 1, question: "Primary question", answer: true, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
await logic.syncVehicleAnswer({
|
|
departmentId: 1,
|
|
laneId: 7,
|
|
customerNumber: 12345,
|
|
reg: "ab12345",
|
|
questionId: 1,
|
|
value: true,
|
|
});
|
|
|
|
expect(mocks.previewAllowed).toHaveBeenNthCalledWith(1, 7, "AB12345", 9);
|
|
expect(mocks.previewAllowed).toHaveBeenNthCalledWith(2, 7, "AB12345", 9);
|
|
expect(mocks.add).toHaveBeenCalledWith(1, 7, 12345, "AB12345", 1, true, {
|
|
activate_machine: false,
|
|
sync_relay_state: false,
|
|
vehicle_type: 9,
|
|
});
|
|
});
|
|
|
|
it("uses the resolved auto vehicle type for lane/reg summary fallback", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 91, status: "IN_PROGRESS", vehicle_type_id: 5 },
|
|
questions: [{ id: 1, question: "Primary question", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 91, status: "IN_PROGRESS", vehicle_type_id: 5 },
|
|
questions: [{ id: 1, question: "Primary question", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, null, 7, "ab12345");
|
|
|
|
expect(logic.resolvedVehicleTypeId.value).toBe(5);
|
|
expect(mocks.washSummary).toHaveBeenNthCalledWith(1, { session_id: 91 });
|
|
expect(mocks.washSummary).toHaveBeenNthCalledWith(2, { lane_id: 7, reg: "AB12345", vehicle_type: 5 });
|
|
});
|
|
|
|
it("reuses the resolved auto vehicle type when refreshing after answer sync", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 66, status: "IN_PROGRESS", vehicle_type_id: 5 },
|
|
questions: [{ id: 1, question: "Primary question", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 66, status: "IN_PROGRESS", vehicle_type_id: 5 },
|
|
questions: [{ id: 1, question: "Primary question", answer: null, order_priority: 1 }],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
mocks.add.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
selfserve: {
|
|
session: { id: 66, status: "IN_PROGRESS", vehicle_type_id: 5 },
|
|
questions: [{ id: 1, question: "Primary question", answer: true, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
|
|
await logic.fetchSelfServeData(1, null, 7, "ab12345");
|
|
await logic.syncVehicleAnswer({
|
|
departmentId: 1,
|
|
laneId: 7,
|
|
customerNumber: 12345,
|
|
reg: "ab12345",
|
|
questionId: 1,
|
|
value: true,
|
|
});
|
|
|
|
expect(mocks.previewAllowed).toHaveBeenNthCalledWith(1, 7, "AB12345");
|
|
expect(mocks.previewAllowed).toHaveBeenNthCalledWith(2, 7, "AB12345", 5);
|
|
});
|
|
|
|
it("keeps answered questions visible when summary returns no currently-visible question list", async () => {
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: false,
|
|
session: { id: 99, status: "MACHINE_NOT_ALLOWED" },
|
|
questions: [{ id: 1, question: "Primary question", answer: true, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 99, status: "MACHINE_NOT_ALLOWED" },
|
|
questions: [],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, 9, 7, "ab12345");
|
|
|
|
expect(logic.answers.value[1]).toBe(true);
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1]);
|
|
});
|
|
|
|
it("hides condition-dependent questions again when the parent answer no longer satisfies the condition", async () => {
|
|
const baseConditions = [{ id: 100, name: "Primary answer is yes" }];
|
|
const baseRules = [
|
|
{ id: 501, condition_id: 100, object_type: "question", object_id: 2, type: "IS_TRUE", name: "Q2 yes" },
|
|
];
|
|
|
|
mocks.previewAllowed.mockResolvedValueOnce({
|
|
allowed: true,
|
|
session: { id: 55, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 2, question: "Primary question", answer: true, order_priority: 1, condition_id: null },
|
|
{ id: 3, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: baseConditions,
|
|
rules: baseRules,
|
|
});
|
|
mocks.washSummary
|
|
.mockResolvedValueOnce({
|
|
session: { id: 55, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 2, question: "Primary question", answer: true, order_priority: 1, condition_id: null },
|
|
{ id: 3, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: baseConditions,
|
|
rules: baseRules,
|
|
events: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
session: { id: 55, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 2, question: "Primary question", answer: true, order_priority: 1, condition_id: null },
|
|
{ id: 3, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: baseConditions,
|
|
rules: baseRules,
|
|
events: [],
|
|
});
|
|
|
|
mocks.add.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
selfserve: {
|
|
session: { id: 55, status: "IN_PROGRESS" },
|
|
questions: [{ id: 2, question: "Primary question", answer: false, order_priority: 1 }],
|
|
conditions: [],
|
|
rules: [],
|
|
tasks: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 55, status: "IN_PROGRESS" },
|
|
questions: [{ id: 2, question: "Primary question", answer: false, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 55, status: "IN_PROGRESS" },
|
|
questions: [{ id: 2, question: "Primary question", answer: false, order_priority: 1 }],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, null, 7, "ab12345");
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([2, 3]);
|
|
|
|
await logic.syncVehicleAnswer({
|
|
departmentId: 1,
|
|
laneId: 7,
|
|
customerNumber: 12345,
|
|
reg: "ab12345",
|
|
questionId: 2,
|
|
value: false,
|
|
});
|
|
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([2]);
|
|
});
|
|
|
|
it("keeps condition-dependent questions visible in stable order when the condition remains true", async () => {
|
|
const baseConditions = [{ id: 100, name: "Primary answer is yes" }];
|
|
const baseRules = [
|
|
{ id: 501, condition_id: 100, object_type: "question", object_id: 1, type: "IS_TRUE", name: "Q1 yes" },
|
|
];
|
|
|
|
mocks.previewAllowed.mockResolvedValueOnce({
|
|
allowed: true,
|
|
session: { id: 77, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Primary question", answer: false, order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: baseConditions,
|
|
rules: baseRules,
|
|
});
|
|
mocks.washSummary.mockResolvedValueOnce({
|
|
session: { id: 77, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Primary question", answer: false, order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: baseConditions,
|
|
rules: baseRules,
|
|
events: [],
|
|
});
|
|
|
|
mocks.add.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
selfserve: {
|
|
session: { id: 77, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Primary question", answer: true, order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
conditions: [],
|
|
rules: [],
|
|
tasks: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 77, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Primary question", answer: true, order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 77, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Primary question", answer: true, order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Conditional follow-up", answer: null, order_priority: 2, condition_id: 100 },
|
|
],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, null, 7, "ab12345");
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1, 2]);
|
|
|
|
await logic.syncVehicleAnswer({
|
|
departmentId: 1,
|
|
laneId: 7,
|
|
customerNumber: 12345,
|
|
reg: "ab12345",
|
|
questionId: 1,
|
|
value: true,
|
|
});
|
|
|
|
expect(logic.answers.value[1]).toBe(true);
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1, 2]);
|
|
});
|
|
|
|
it("keeps visible question order stable when answer refreshes return the same questions reordered", async () => {
|
|
const initialQuestions = [
|
|
{ id: 1, question: "Primary question", answer: null, order_priority: 1, condition_id: null },
|
|
{ id: 2, question: "Follow-up question", answer: null, order_priority: 2, condition_id: null },
|
|
];
|
|
const refreshedQuestions = [{ ...initialQuestions[1] }, { ...initialQuestions[0], answer: true }];
|
|
|
|
mocks.previewAllowed.mockResolvedValueOnce({
|
|
allowed: true,
|
|
session: { id: 78, status: "IN_PROGRESS" },
|
|
questions: initialQuestions,
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValueOnce({
|
|
session: { id: 78, status: "IN_PROGRESS" },
|
|
questions: initialQuestions,
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
events: [],
|
|
});
|
|
|
|
mocks.add.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
selfserve: {
|
|
session: { id: 78, status: "IN_PROGRESS" },
|
|
questions: refreshedQuestions,
|
|
conditions: [],
|
|
rules: [],
|
|
tasks: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
mocks.previewAllowed.mockResolvedValue({
|
|
allowed: true,
|
|
session: { id: 78, status: "IN_PROGRESS" },
|
|
questions: refreshedQuestions,
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary.mockResolvedValue({
|
|
session: { id: 78, status: "IN_PROGRESS" },
|
|
questions: refreshedQuestions,
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
events: [],
|
|
});
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(1, null, 7, "ab12345");
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1, 2]);
|
|
|
|
await logic.syncVehicleAnswer({
|
|
departmentId: 1,
|
|
laneId: 7,
|
|
customerNumber: 12345,
|
|
reg: "ab12345",
|
|
questionId: 1,
|
|
value: true,
|
|
});
|
|
|
|
expect(logic.answers.value[1]).toBe(true);
|
|
expect(logic.visibleQuestions.value.map((question) => question.id)).toEqual([1, 2]);
|
|
});
|
|
|
|
it("clears persisted answers for a lane/reg and refreshes preview state", async () => {
|
|
mocks.previewAllowed
|
|
.mockResolvedValueOnce({
|
|
allowed: true,
|
|
session: { id: 88, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Doors closed?", answer: true, order_priority: 1 },
|
|
{ id: 2, question: "Windows closed?", answer: false, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
allowed: true,
|
|
session: { id: 88, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Doors closed?", answer: null, order_priority: 1 },
|
|
{ id: 2, question: "Windows closed?", answer: null, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
conditions: [],
|
|
rules: [],
|
|
});
|
|
mocks.washSummary
|
|
.mockResolvedValueOnce({
|
|
session: { id: 88, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Doors closed?", answer: true, order_priority: 1 },
|
|
{ id: 2, question: "Windows closed?", answer: false, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
events: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
session: { id: 88, status: "IN_PROGRESS" },
|
|
questions: [
|
|
{ id: 1, question: "Doors closed?", answer: null, order_priority: 1 },
|
|
{ id: 2, question: "Windows closed?", answer: null, order_priority: 2 },
|
|
],
|
|
tasks: [],
|
|
events: [],
|
|
});
|
|
mocks.getAll.mockResolvedValue([
|
|
{ id: 701, question: 1 },
|
|
{ id: 702, question: 2 },
|
|
]);
|
|
mocks.deleteCondition.mockResolvedValue({ data: { message: "Condition deleted" } });
|
|
|
|
const logic = useSelfServeLogic();
|
|
await logic.fetchSelfServeData(3, null, 7, "ab12345");
|
|
|
|
expect(logic.answers.value[1]).toBe(true);
|
|
expect(logic.answers.value[2]).toBe(false);
|
|
|
|
const result = await logic.clearVehicleAnswers({
|
|
departmentId: 3,
|
|
laneId: 7,
|
|
reg: "ab12345",
|
|
});
|
|
|
|
expect(result).toEqual({ deletedCount: 2 });
|
|
expect(mocks.getAll).toHaveBeenCalledWith({
|
|
lane: 7,
|
|
reg: "AB12345",
|
|
department: 3,
|
|
});
|
|
expect(mocks.deleteCondition).toHaveBeenCalledTimes(2);
|
|
expect(mocks.deleteCondition).toHaveBeenNthCalledWith(1, 701);
|
|
expect(mocks.deleteCondition).toHaveBeenNthCalledWith(2, 702);
|
|
expect(logic.answers.value[1]).toBe(null);
|
|
expect(logic.answers.value[2]).toBe(null);
|
|
expect(logic.allVisibleQuestionsAnswered.value).toBe(false);
|
|
});
|
|
});
|