- Introduced `.prettierrc.json` to enforce consistent code formatting across the project. - Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
308 lines
13 KiB
JavaScript
308 lines
13 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { flushPromises } from "@vue/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { mountWithApp } from "./helpers/mountWithApp.js";
|
|
|
|
const createComplaintMock = vi.hoisted(() => vi.fn());
|
|
const refreshOverviewMock = vi.hoisted(() => vi.fn());
|
|
const searchComplaintCustomersMock = vi.hoisted(() => vi.fn());
|
|
const permissionState = vi.hoisted(() => ({ canCreate: true }));
|
|
const sharedState = vi.hoisted(() => ({
|
|
selected_date: null,
|
|
selected_date_to: null,
|
|
selected_department_ids: null,
|
|
}));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
hasPermission: (permission) =>
|
|
permission === "create_department_daily_report_complaints" && permissionState.canCreate,
|
|
objects: {
|
|
department_daily_reports: {
|
|
functions: {
|
|
createComplaint: createComplaintMock,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/components/pagination/departmentTabs.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
return {
|
|
departments: ref([
|
|
{ id: 1, name: "North" },
|
|
{ id: 2, name: "South" },
|
|
]),
|
|
};
|
|
});
|
|
|
|
vi.mock("@/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue", async () => {
|
|
const { ref } = await import("vue");
|
|
|
|
sharedState.selected_date = ref("2026-03-23");
|
|
sharedState.selected_date_to = ref("2026-03-23");
|
|
sharedState.selected_department_ids = ref([1, 2]);
|
|
|
|
return {
|
|
refreshOverview: refreshOverviewMock,
|
|
selected_date: sharedState.selected_date,
|
|
selected_date_to: sharedState.selected_date_to,
|
|
selected_department_ids: sharedState.selected_department_ids,
|
|
};
|
|
});
|
|
|
|
vi.mock("@/services/departmentDailyReportComplaintCustomers.js", () => ({
|
|
COMPLAINT_CUSTOMER_SEARCH_DEBOUNCE_MS: 250,
|
|
COMPLAINT_CUSTOMER_SEARCH_MIN_LENGTH: 2,
|
|
formatComplaintCustomerLabel: (customer) =>
|
|
customer?.customer_name
|
|
? `${customer.customer_name} - ${customer.customer_number}`
|
|
: String(customer?.customer_number ?? ""),
|
|
normalizeComplaintCustomer: (customer) => customer,
|
|
searchComplaintCustomers: searchComplaintCustomersMock,
|
|
}));
|
|
|
|
vi.mock("@/services/departmentDailyReportComplaintCategories.js", () => ({
|
|
DEPARTMENT_DAILY_REPORT_COMPLAINT_CATEGORY_OPTIONS: [
|
|
{ value: "wash_quality", label: "Vaskekvalitet" },
|
|
{ value: "service", label: "Service" },
|
|
],
|
|
}));
|
|
|
|
import DepartmentDashboardDailyReportComplaints from "@/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportComplaints.vue";
|
|
|
|
const mountTile = (props = {}) =>
|
|
mountWithApp(DepartmentDashboardDailyReportComplaints, {
|
|
props: {
|
|
count: 3,
|
|
isLoading: false,
|
|
state: "ready",
|
|
unavailableMessage: null,
|
|
dataTestid: "daily-report-tile-complaints",
|
|
...props,
|
|
},
|
|
});
|
|
|
|
const formatDisplayedDate = (isoDate) => {
|
|
const [year, month, day] = String(isoDate || "").split("-");
|
|
if (!year || !month || !day) {
|
|
return "";
|
|
}
|
|
|
|
return `${day}/${month}/${year}`;
|
|
};
|
|
|
|
describe("DepartmentDashboardDailyReportComplaints", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
createComplaintMock.mockReset();
|
|
refreshOverviewMock.mockReset();
|
|
searchComplaintCustomersMock.mockReset();
|
|
refreshOverviewMock.mockResolvedValue(undefined);
|
|
searchComplaintCustomersMock.mockResolvedValue([]);
|
|
permissionState.canCreate = true;
|
|
sharedState.selected_date.value = "2026-03-23";
|
|
sharedState.selected_date_to.value = "2026-03-23";
|
|
sharedState.selected_department_ids.value = [1, 2];
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.runOnlyPendingTimers();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("shows the plus action only when the user has create permission", async () => {
|
|
const allowedWrapper = mountTile();
|
|
expect(allowedWrapper.find('[data-testid="daily-report-complaints-add-button"]').exists()).toBe(true);
|
|
|
|
permissionState.canCreate = false;
|
|
const deniedWrapper = mountTile();
|
|
expect(deniedWrapper.find('[data-testid="daily-report-complaints-add-button"]').exists()).toBe(false);
|
|
});
|
|
|
|
it("defaults the modal department and wash date to the selected day for single-day ranges", async () => {
|
|
const wrapper = mountTile();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-department-select"]').element.value).toBe("1");
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-wash-date-input"]').element.value).toBe("23/03/2026");
|
|
expect(wrapper.find('[data-testid="daily-report-complaints-date-note"]').exists()).toBe(false);
|
|
});
|
|
|
|
it("requires an explicit wash date for multi-day ranges and shows the updated date guidance", async () => {
|
|
sharedState.selected_department_ids.value = [2, 1];
|
|
sharedState.selected_date.value = "2026-03-20";
|
|
sharedState.selected_date_to.value = "2026-03-21";
|
|
|
|
const wrapper = mountTile();
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-department-select"]').element.value).toBe("2");
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-wash-date-input"]').element.value).toBe("");
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-date-note"]').text()).toContain("konkrete vask");
|
|
});
|
|
|
|
it("resets wash date, category, customer, and description state when the modal is reopened", async () => {
|
|
const wrapper = mountTile();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
await wrapper.get('[data-testid="daily-report-complaints-category-select"]').setValue("service");
|
|
await wrapper.get('[data-testid="daily-report-complaints-description-textarea"]').setValue("Temporary complaint");
|
|
await wrapper.get('[data-testid="daily-report-complaints-cancel"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-wash-date-input"]').element.value).toBe("23/03/2026");
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-category-select"]').element.value).toBe("");
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-description-textarea"]').element.value).toBe("");
|
|
expect(wrapper.find('[data-testid="daily-report-complaints-customer-selected"]').exists()).toBe(false);
|
|
});
|
|
|
|
it("requires wash date, category, and description before submitting", async () => {
|
|
sharedState.selected_date.value = "2026-03-20";
|
|
sharedState.selected_date_to.value = "2026-03-21";
|
|
|
|
const wrapper = mountTile();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(createComplaintMock).not.toHaveBeenCalled();
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-validation-error"]').text()).toContain("Dato for vask");
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-wash-date-input"]').setValue("21/03/2026");
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-validation-error"]').text()).toContain("Kategori");
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-category-select"]').setValue("service");
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-validation-error"]').text()).toContain("Beskrivelse");
|
|
});
|
|
|
|
it("submits complaints without a customer number and refreshes the overview on success", async () => {
|
|
createComplaintMock.mockResolvedValue({ data: { data: { id: 1 } } });
|
|
const today = new Date().toISOString().split("T")[0];
|
|
sharedState.selected_date.value = today;
|
|
sharedState.selected_date_to.value = today;
|
|
|
|
const wrapper = mountTile({ count: 5 });
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-wash-date-input"]').element.value).toBe(
|
|
formatDisplayedDate(today)
|
|
);
|
|
await wrapper.get('[data-testid="daily-report-complaints-category-select"]').setValue("service");
|
|
await wrapper.get('[data-testid="daily-report-complaints-description-textarea"]').setValue("Machine damaged cargo");
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(createComplaintMock).toHaveBeenCalledWith({
|
|
department_id: 1,
|
|
customer_number: null,
|
|
wash_date: today,
|
|
category: "service",
|
|
description: "Machine damaged cargo",
|
|
});
|
|
expect(refreshOverviewMock).toHaveBeenCalledTimes(1);
|
|
expect(wrapper.find('[data-testid="daily-report-complaints-modal"]').exists()).toBe(false);
|
|
});
|
|
|
|
it("searches and selects an optional customer before submitting", async () => {
|
|
createComplaintMock.mockResolvedValue({ data: { data: { id: 2 } } });
|
|
searchComplaintCustomersMock.mockResolvedValue([{ customer_number: 12345, customer_name: "Acme Transport" }]);
|
|
|
|
const wrapper = mountTile();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-customer-search-input"]').setValue("ac");
|
|
await vi.advanceTimersByTimeAsync(250);
|
|
await flushPromises();
|
|
|
|
expect(searchComplaintCustomersMock).toHaveBeenCalledWith("ac");
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-customer-option-12345"]').trigger("mousedown");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-customer-selected"]').element.value).toBe(
|
|
"Acme Transport - 12345"
|
|
);
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-wash-date-input"]').element.value).toBe("23/03/2026");
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-category-select"]').setValue("wash_quality");
|
|
await wrapper
|
|
.get('[data-testid="daily-report-complaints-description-textarea"]')
|
|
.setValue("Complaint with customer number");
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(createComplaintMock).toHaveBeenCalledWith({
|
|
department_id: 1,
|
|
customer_number: 12345,
|
|
wash_date: "2026-03-23",
|
|
category: "wash_quality",
|
|
description: "Complaint with customer number",
|
|
});
|
|
});
|
|
|
|
it("requires the user to select a customer or clear the field when search text remains", async () => {
|
|
searchComplaintCustomersMock.mockResolvedValue([]);
|
|
|
|
const wrapper = mountTile();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
await wrapper.get('[data-testid="daily-report-complaints-customer-search-input"]').setValue("12345");
|
|
await vi.advanceTimersByTimeAsync(250);
|
|
await flushPromises();
|
|
await wrapper.get('[data-testid="daily-report-complaints-category-select"]').setValue("service");
|
|
await wrapper
|
|
.get('[data-testid="daily-report-complaints-description-textarea"]')
|
|
.setValue("Complaint with unresolved customer");
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(createComplaintMock).not.toHaveBeenCalled();
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-validation-error"]').text()).toContain("Vaelg en kunde");
|
|
});
|
|
|
|
it("keeps the modal open and shows an error message when submission fails", async () => {
|
|
createComplaintMock.mockRejectedValue({
|
|
response: {
|
|
data: {
|
|
message: "Customer not found",
|
|
},
|
|
},
|
|
});
|
|
|
|
const wrapper = mountTile();
|
|
|
|
await wrapper.get('[data-testid="daily-report-complaints-add-button"]').trigger("click");
|
|
await flushPromises();
|
|
await wrapper.get('[data-testid="daily-report-complaints-category-select"]').setValue("service");
|
|
await wrapper.get('[data-testid="daily-report-complaints-description-textarea"]').setValue("Broken mirror");
|
|
await wrapper.get('[data-testid="daily-report-complaints-submit"]').trigger("click");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-modal"]').exists()).toBe(true);
|
|
expect(wrapper.get('[data-testid="daily-report-complaints-error"]').text()).toContain("Customer not found");
|
|
expect(refreshOverviewMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|