// @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(); }); });