- Introduced a new Vue component supporting versioned configuration authoring for questions, conditions, tasks, and rules. - Implemented scoped editing with lane and machine type-specific configurations. - Added version tracking features, including validation, publication, and rollback. - Enhanced UI with tabbed navigation, modals for adding/editing records, and notifications for actions.
383 lines
12 KiB
JavaScript
383 lines
12 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(),
|
|
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,
|
|
}),
|
|
}));
|
|
|
|
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.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.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("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("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",
|
|
});
|
|
});
|
|
});
|