Files
pleno-vue/tests/unit/self-serve-try-modal.spec.js
T

505 lines
16 KiB
JavaScript

// @vitest-environment jsdom
import { flushPromises } from "@vue/test-utils";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
fetchSelfServeData: vi.fn(),
syncVehicleAnswer: vi.fn(),
clearVehicleAnswers: vi.fn(),
downloadAttachment: vi.fn(),
swalFire: vi.fn(),
getLanes: vi.fn(),
getVehicleTypeOptions: vi.fn(),
loading: { value: false, __v_isRef: true },
lane: { value: { id: 3, name: "Lane 3" }, __v_isRef: true },
machineType: { value: null, __v_isRef: true },
session: { value: null, __v_isRef: true },
events: { value: [], __v_isRef: true },
questions: { value: [], __v_isRef: true },
answers: { value: {}, __v_isRef: true },
allowedServices: { value: [], __v_isRef: true },
configVersionId: { value: null, __v_isRef: true },
evaluationTrace: { value: null, __v_isRef: true },
visibleQuestions: { value: [], __v_isRef: true },
activeTasks: { value: [], __v_isRef: true },
currentQuestion: { value: null, __v_isRef: true },
allVisibleQuestionsAnswered: { value: false, __v_isRef: true },
machineAvailable: { value: true, __v_isRef: true },
allowed: { value: true, __v_isRef: true },
evaluateCondition: vi.fn(() => true),
}));
vi.mock("@/components/session/token/SessionUser.vue", () => ({
SessionUser: {
user: {
customer_number: { value: 12345 },
},
objects: {
department_lanes: {
get: {
all: mocks.getLanes,
},
},
vehicles: {
columns: {
type: {
options: mocks.getVehicleTypeOptions,
},
},
},
},
},
}));
vi.mock("sweetalert2", () => ({
default: {
fire: mocks.swalFire,
},
}));
vi.mock("@/composables/useSelfServeLogic", () => ({
useSelfServeLogic: () => ({
loading: mocks.loading,
lane: mocks.lane,
machineType: mocks.machineType,
session: mocks.session,
events: mocks.events,
questions: mocks.questions,
answers: mocks.answers,
allowedServices: mocks.allowedServices,
configVersionId: mocks.configVersionId,
evaluationTrace: mocks.evaluationTrace,
visibleQuestions: mocks.visibleQuestions,
activeTasks: mocks.activeTasks,
currentQuestion: mocks.currentQuestion,
allVisibleQuestionsAnswered: mocks.allVisibleQuestionsAnswered,
machineAvailable: mocks.machineAvailable,
allowed: mocks.allowed,
fetchSelfServeData: mocks.fetchSelfServeData,
syncVehicleAnswer: mocks.syncVehicleAnswer,
clearVehicleAnswers: mocks.clearVehicleAnswers,
evaluateCondition: mocks.evaluateCondition,
downloadAttachment: mocks.downloadAttachment,
}),
}));
import { mountWithApp } from "./helpers/mountWithApp.js";
import SelfServeTryModal from "@/components/displays/department/tables/SelfServeTryModal.vue";
describe("SelfServeTryModal", () => {
beforeEach(() => {
mocks.fetchSelfServeData.mockReset();
mocks.syncVehicleAnswer.mockReset();
mocks.clearVehicleAnswers.mockReset();
mocks.downloadAttachment.mockReset();
mocks.swalFire.mockReset();
mocks.getLanes.mockReset();
mocks.getVehicleTypeOptions.mockReset();
mocks.evaluateCondition.mockReset();
mocks.getLanes.mockResolvedValue([{ id: 3, department: 9, name: "Lane 3" }]);
mocks.getVehicleTypeOptions.mockResolvedValue([
{ id: 0, name: "Ukendt" },
{ id: 2, name: "Truck" },
{ id: 3, name: "Van" },
]);
mocks.clearVehicleAnswers.mockResolvedValue({ deletedCount: 1 });
mocks.swalFire.mockResolvedValue({ isConfirmed: true });
mocks.loading.value = false;
mocks.questions.value = [];
mocks.visibleQuestions.value = [];
mocks.answers.value = {};
mocks.activeTasks.value = [];
mocks.currentQuestion.value = null;
mocks.evaluateCondition.mockReturnValue(true);
});
const mountModal = () =>
mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: true,
SelfServeTaskList: true,
},
},
});
it("keeps clear answers enabled for valid lane/reg even when no visible answers exist", async () => {
const wrapper = mountModal();
await flushPromises();
const clearButton = wrapper.get('[data-testid="self-serve-try-clear-answers"]');
expect(clearButton.attributes("disabled")).toBeUndefined();
});
it("allows selecting vehicle type and refreshes preview with the selected type", async () => {
const wrapper = mountModal();
await flushPromises();
mocks.fetchSelfServeData.mockClear();
await wrapper.get('[data-testid="self-serve-try-vehicle-type"]').setValue("3");
await flushPromises();
expect(mocks.fetchSelfServeData).toHaveBeenCalledWith(9, 3, 3, "AB12345");
});
it("passes simulator task attachment downloads to self-serve logic", async () => {
const attachment = {
id: 201,
content: { other: "manual.pdf" },
download_link: "https://cdn.example.test/manual.pdf",
};
mocks.activeTasks.value = [
{
id: 5,
task: "Prepare",
attachments: [attachment],
},
];
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: true,
SelfServeTaskList: {
props: ["tasks"],
emits: ["download-attachment"],
template:
"<button data-testid='emit-task-download' @click=\"$emit('download-attachment', tasks[0].id, tasks[0].attachments[0].id, tasks[0].attachments[0])\">download</button>",
},
},
},
});
await flushPromises();
await wrapper.get("[data-testid='emit-task-download']").trigger("click");
expect(mocks.downloadAttachment).toHaveBeenCalledWith(5, 201, attachment);
});
it("syncs answers with the selected vehicle type context", async () => {
mocks.visibleQuestions.value = [{ id: 77, question: "Question 77" }];
mocks.currentQuestion.value = null;
mocks.syncVehicleAnswer.mockResolvedValue({});
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: {
props: ["visibleQuestions"],
emits: ["answer-question"],
template:
"<button data-testid='emit-card-answer' @click=\"$emit('answer-question', visibleQuestions[0]?.id, true)\">answer</button>",
},
SelfServeTaskList: true,
},
},
});
await flushPromises();
await wrapper.get('[data-testid="self-serve-try-vehicle-type"]').setValue("3");
await flushPromises();
await wrapper.get("[data-testid='emit-card-answer']").trigger("click");
await flushPromises();
expect(mocks.syncVehicleAnswer).toHaveBeenCalledWith({
departmentId: 9,
laneId: 3,
customerNumber: 12345,
reg: "AB12345",
questionId: 77,
value: true,
vehicleTypeId: 3,
});
});
it("renders dynamic image preview with expected query parameters when context exists", async () => {
mocks.activeTasks.value = [
{
id: 1,
buttons: ["reset", 0, 2, 2, "start", -1, 99],
dynamic_images_vehicle_type: 5,
},
];
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
vehicleTypeId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: true,
SelfServeTaskList: true,
},
},
});
await flushPromises();
const image = wrapper.get('[data-testid="self-serve-try-dynamic-image"]');
const imageUrl = new URL(image.attributes("src"));
expect(imageUrl.pathname).toBe("/department/lanes/dynamic-image");
expect(imageUrl.searchParams.get("department")).toBe("9");
expect(imageUrl.searchParams.get("lane")).toBe("3");
expect(imageUrl.searchParams.get("current_step")).toBe("0");
expect(imageUrl.searchParams.get("vehicle_type")).toBe("3");
expect(imageUrl.searchParams.get("thumb_position")).toBe("5");
expect(JSON.parse(imageUrl.searchParams.get("buttons"))).toEqual(["reset", 0, 2, "start"]);
});
it("does not render dynamic image preview without dynamic-image context", async () => {
mocks.activeTasks.value = [
{
id: 1,
buttons: [-1, 12, 99],
dynamic_images_vehicle_type: null,
},
];
const wrapper = mountModal();
await flushPromises();
expect(wrapper.find('[data-testid="self-serve-try-dynamic-image"]').exists()).toBe(false);
});
it("hides dynamic image on error and restores it when image context changes", async () => {
mocks.activeTasks.value = [
{
id: 1,
buttons: [1],
dynamic_images_vehicle_type: null,
},
];
const wrapper = mountModal();
await flushPromises();
await wrapper.get('[data-testid="self-serve-try-dynamic-image"]').trigger("error");
await flushPromises();
expect(wrapper.find('[data-testid="self-serve-try-dynamic-image"]').exists()).toBe(false);
await wrapper.get('[data-testid="self-serve-try-vehicle-type"]').setValue("3");
await flushPromises();
const image = wrapper.get('[data-testid="self-serve-try-dynamic-image"]');
const imageUrl = new URL(image.attributes("src"));
expect(imageUrl.searchParams.get("vehicle_type")).toBe("3");
expect(JSON.parse(imageUrl.searchParams.get("buttons"))).toEqual([1]);
});
it("does not duplicate the current question in the question cards list", async () => {
mocks.currentQuestion.value = { id: 11, question: "Question 11" };
mocks.visibleQuestions.value = [
{ id: 11, question: "Question 11" },
{ id: 22, question: "Question 22" },
];
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: {
props: ["visibleQuestions"],
template:
"<div data-testid='question-cards-probe'>{{ visibleQuestions.map(q => q.question).join('|') }}</div>",
},
SelfServeTaskList: true,
},
},
});
await flushPromises();
expect(wrapper.find(".notification.is-info").exists()).toBe(false);
expect(wrapper.get("[data-testid='question-cards-probe']").text()).toBe("Question 11|Question 22");
});
it("keeps rendering known questions when visible list is temporarily empty but unanswered questions still exist", async () => {
mocks.visibleQuestions.value = [];
mocks.questions.value = [
{ id: 11, question: "Question 11", order_priority: 1 },
{ id: 22, question: "Question 22", order_priority: 2 },
];
mocks.answers.value = { 11: true, 22: null };
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: {
props: ["visibleQuestions"],
template:
"<div data-testid='question-cards-probe'>{{ visibleQuestions.map(q => q.question).join('|') }}</div>",
},
SelfServeTaskList: true,
},
},
});
await flushPromises();
expect(wrapper.get("[data-testid='question-cards-probe']").text()).toBe("Question 11|Question 22");
expect(wrapper.text()).toContain("Alle synlige sporgsmal besvaret: Nej");
});
it("keeps condition-true unanswered follow-up visible when server visible list is stale", async () => {
mocks.visibleQuestions.value = [{ id: 11, question: "Question 11", order_priority: 1, condition_id: null }];
mocks.questions.value = [
{ id: 11, question: "Question 11", order_priority: 1, condition_id: null },
{ id: 12, question: "Follow-up question", order_priority: 2, condition_id: 100 },
];
mocks.answers.value = { 11: true, 12: null };
mocks.evaluateCondition.mockImplementation((conditionId) => parseInt(conditionId) === 100);
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: {
props: ["visibleQuestions"],
template:
"<div data-testid='question-cards-probe'>{{ visibleQuestions.map(q => q.question).join('|') }}</div>",
},
SelfServeTaskList: true,
},
},
});
await flushPromises();
expect(wrapper.get("[data-testid='question-cards-probe']").text()).toBe("Question 11|Follow-up question");
});
it("does not add follow-up questions when their condition is false", async () => {
mocks.visibleQuestions.value = [{ id: 11, question: "Question 11", order_priority: 1, condition_id: null }];
mocks.questions.value = [
{ id: 11, question: "Question 11", order_priority: 1, condition_id: null },
{ id: 12, question: "Follow-up question", order_priority: 2, condition_id: 100 },
];
mocks.answers.value = { 11: false, 12: null };
mocks.evaluateCondition.mockReturnValue(false);
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: {
props: ["visibleQuestions"],
template:
"<div data-testid='question-cards-probe'>{{ visibleQuestions.map(q => q.question).join('|') }}</div>",
},
SelfServeTaskList: true,
},
},
});
await flushPromises();
expect(wrapper.get("[data-testid='question-cards-probe']").text()).toBe("Question 11");
});
it("disables clear answers when registration is invalid", async () => {
const wrapper = mountModal();
await flushPromises();
await wrapper.get('[data-testid="self-serve-try-reg"]').setValue("A");
await flushPromises();
const clearButton = wrapper.get('[data-testid="self-serve-try-clear-answers"]');
expect(clearButton.attributes("disabled")).toBeDefined();
});
it("disables clear answers while loading", async () => {
mocks.loading.value = true;
const wrapper = mountModal();
await flushPromises();
const clearButton = wrapper.get('[data-testid="self-serve-try-clear-answers"]');
expect(clearButton.attributes("disabled")).toBeDefined();
});
it("does not clear answers when confirmation is cancelled", async () => {
mocks.swalFire.mockResolvedValue({ isConfirmed: false });
const wrapper = mountModal();
await flushPromises();
await wrapper.get('[data-testid="self-serve-try-clear-answers"]').trigger("click");
expect(mocks.swalFire).toHaveBeenCalledTimes(1);
expect(mocks.clearVehicleAnswers).not.toHaveBeenCalled();
});
it("clears answers for the selected lane and registration when confirmed", async () => {
const wrapper = mountWithApp(SelfServeTryModal, {
props: {
departmentId: 9,
laneId: 3,
},
global: {
stubs: {
SelfServeQuestionCards: true,
SelfServeTaskList: true,
},
},
});
await flushPromises();
const clearButton = wrapper.get('[data-testid="self-serve-try-clear-answers"]');
expect(clearButton.attributes("disabled")).toBeUndefined();
await clearButton.trigger("click");
expect(mocks.swalFire).toHaveBeenCalledTimes(1);
expect(mocks.swalFire.mock.calls[0][0]).toMatchObject({
title: "Ryd besvarelser?",
icon: "warning",
});
expect(mocks.swalFire.mock.calls[0][0].text).toContain("AB12345");
expect(mocks.swalFire.mock.calls[0][0].text).toContain("3");
expect(mocks.clearVehicleAnswers).toHaveBeenCalledWith({
departmentId: 9,
laneId: 3,
vehicleTypeId: null,
reg: "AB12345",
});
});
});