522 lines
16 KiB
JavaScript
522 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("renders the preview controls in Danish", async () => {
|
|
const wrapper = mountModal();
|
|
|
|
await flushPromises();
|
|
|
|
const text = wrapper.text();
|
|
expect(text).toContain("Forhåndsvisning af selvvask");
|
|
expect(text).toContain("Køretøjstype");
|
|
expect(text).toContain("Hent forhåndsvisning");
|
|
expect(text).toContain("Maskine tilgængelig");
|
|
expect(text).toContain("Alle synlige spørgsmål besvaret");
|
|
expect(text).toContain("Opgaver og session");
|
|
expect(text).toContain("Seneste hændelser");
|
|
expect(text).toContain("Evalueringsspor");
|
|
expect(text).toContain("Besvarede spørgsmål");
|
|
});
|
|
|
|
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"), "http://localhost");
|
|
|
|
expect(imageUrl.pathname).toMatch(/\/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"), "http://localhost");
|
|
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 spørgsmål 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",
|
|
});
|
|
});
|
|
});
|