// @vitest-environment jsdom import { flushPromises, mount } from "@vue/test-utils"; import { describe, expect, it, vi, beforeEach } from "vitest"; const mocks = vi.hoisted(() => { const order = { id: 9001, invoice_collection_id: 3001, department_id: 75, created_at: "2026-06-18", total_net_amount: 240, reference: "REF-9001", notes: "", po: "", reg_1: "AB12345", }; return { order, economicDetails: vi.fn(async () => ({ data: { data: { collected_invoice_id: 3001, economic: { draft_id: 88, booked_id: 99, }, internal: { normalized: { totals: { net_total: 240, line_count: 1, }, }, }, draft: { exists: false, normalized: null, }, booked: { exists: true, normalized: { totals: { net_total: 240, line_count: 1, }, }, }, warnings: [], }, }, })), economicPdf: vi.fn(async () => ({ data: { data: { url: "https://example.test/invoice.pdf" } } })), request: vi.fn(async (url, method) => { if (url === "/order/items" && method === "GET") { return { data: { data: [ { id: 501, order_id: 9001, product_name: "Premium wash", quantity: 2, price: 120, reference: "", notes: "", }, ], }, }; } return { data: { data: [] } }; }), }; }); vi.mock("buefy", () => ({ BCheckbox: { name: "BCheckbox", props: { modelValue: Boolean, indeterminate: Boolean, disabled: Boolean, }, emits: ["update:modelValue"], template: ` `, }, })); vi.mock("sweetalert2", () => ({ default: { fire: vi.fn(async () => ({ isConfirmed: false })), }, })); vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key) => key, locale: { value: "da-DK" }, }), })); vi.mock("@/components/displays/buttons/EditableTableColumn.vue", () => ({ default: { name: "EditableTableColumn", props: ["object", "column", "parseFunction"], template: `{{ parseFunction ? parseFunction(object?.[column]) : object?.[column] }}`, }, })); vi.mock("@/components/displays/buttons/ActionSettingsWheelButton.vue", () => ({ default: { name: "ActionSettingsWheelButton", props: ["menuSections", "order_id", "department_id", "invoice_collection_id", "customer_number"], template: ` `, }, })); vi.mock("@/components/session/token/SessionUser.vue", () => ({ SessionUser: { request: mocks.request, functions: { currency: { toLocal: (value) => `${Number(value || 0)} DKK`, }, parseErrorMessage: (error) => error?.message || String(error), }, hasPermission: () => true, canAccessAdmin: () => true, canAccessSuperUser: () => true, canAccessDepartment: () => true, objects: { orders: { columns: { reference: { label: "Reference" }, notes: { label: "Noter" }, po: { label: "PO" }, reg_1: { label: "Reg 1" }, reg_2: { label: "Reg 2" }, reg_3: { label: "Reg 3" }, include_in_invoice: { label: "Faktura" }, total_net_amount: { label: "Total" }, }, get: { single: vi.fn(async () => mocks.order), }, functions: { fetchAttachments: vi.fn(async () => []), downloadAttachment: vi.fn(async () => "https://example.test/attachment.pdf"), removeAttachment: vi.fn(), resendWashCertificate: vi.fn(), mark_as_completed: vi.fn(), }, set: { include_in_invoice: vi.fn(), invoice_collection_id: vi.fn(), booking_id: vi.fn(), wash_id: vi.fn(), }, delete: { single: vi.fn(), }, showEditObjectFieldForm: vi.fn(), }, collectedOrderInvoices: { columns: { name: { label: "Navn" }, notes: { label: "Noter" }, po_number: { label: "PO" }, customer_number: { label: "Kundenr." }, external_id: { label: "Eksternt ID" }, closed_at: { label: "Lukket" }, total_net_amount: { label: "Total" }, }, functions: { economic: { v2: { details: mocks.economicDetails, }, pdf: mocks.economicPdf, }, bulk_action_preview: vi.fn(), bulk_action_apply: vi.fn(), }, showEditObjectFieldForm: vi.fn(), }, order_bookings: { get: { single: vi.fn(), }, functions: { resendBookingConfirmation: vi.fn(), resendBookingCompletionConfirmation: vi.fn(), }, delete: { single: vi.fn(), }, }, }, }, })); vi.mock("@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportInvoiceQueue.vue", () => ({ invoiceQueue: { addInvoiceCollectionsToQueue: vi.fn(), processInvoiceCollectionQueue: vi.fn(), }, default: {}, })); vi.mock("@/components/displays/department/pos/orders/invoiceCollectionBulkActions.js", () => ({ INVOICE_COLLECTION_BULK_ACTIONS: { QUEUE_ECONOMIC: "queue_economic", CLEAN_CUSTOMER_RULES: "clean_customer_rules", MERGE: "merge", SPLIT_BY_MONTH: "split_by_month", RESET_HIDDEN_PRICES: "reset_hidden_prices", }, })); import InvoicingPeriodObjectTree from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodObjectTree.vue"; const mountTree = () => mount(InvoicingPeriodObjectTree, { props: { customer: { customer_number: 2001, customer_name: "ACME", draft: { has_valid_draft: true, invoice_collection_ids: [3001], }, }, transactions: [ { ...mocks.order, booked: true, }, ], excludedOrderIds: [], dates: { dateFrom: "2026-06-01", dateTo: "2026-06-30", }, invoicePeriodFlags: [], }, }); const expandNode = async (wrapper, key) => { const node = wrapper.find(`[data-node-key="${key}"]`); expect(node.exists()).toBe(true); await node.find(".b-tree-node-toggle").trigger("click"); await flushPromises(); }; describe("InvoicingPeriodObjectTree", () => { beforeEach(() => { vi.clearAllMocks(); Object.defineProperty(window, "open", { configurable: true, value: vi.fn(), }); }); it("auto-expands order items when an order node is expanded", async () => { const wrapper = mountTree(); await expandNode(wrapper, "collected_order_invoice:3001"); await expandNode(wrapper, "category:3001:collection_orders"); await expandNode(wrapper, "order:9001"); expect(mocks.request).toHaveBeenCalledWith("/order/items", "GET", { order_id: 9001 }); expect(wrapper.find('[data-node-key="category:9001:order_items"]').classes()).toContain("is-expanded"); expect(wrapper.text()).toContain("Premium wash"); }); it("renders row action wheels for actionable collection, order, and order item nodes", async () => { const wrapper = mountTree(); await expandNode(wrapper, "collected_order_invoice:3001"); await expandNode(wrapper, "category:3001:collection_orders"); await expandNode(wrapper, "order:9001"); const collectionWheel = wrapper.find('[data-testid="invoice-period-tree-action-wheel-collected_order_invoice:3001"]'); const orderWheel = wrapper.find('[data-testid="invoice-period-tree-action-wheel-order:9001"]'); const orderItemWheel = wrapper.find('[data-testid="invoice-period-tree-action-wheel-order_item:501"]'); expect(collectionWheel.exists()).toBe(true); expect(orderWheel.exists()).toBe(true); expect(orderItemWheel.exists()).toBe(true); expect(collectionWheel.attributes("data-action-count")).toBe("4"); expect(collectionWheel.attributes("data-invoice-collection-id")).toBe("3001"); expect(orderWheel.attributes("data-action-count")).toBe("3"); expect(orderWheel.attributes("data-order-id")).toBe("9001"); expect(orderWheel.attributes("data-department-id")).toBe("75"); expect(orderItemWheel.attributes("data-action-count")).toBe("1"); expect(wrapper.find('[data-testid="invoice-period-tree-action-wheel-category:9001:order_items"]').exists()).toBe(false); }); it("shows empty editable fields without a reveal toggle", async () => { const wrapper = mountTree(); await expandNode(wrapper, "collected_order_invoice:3001"); await expandNode(wrapper, "category:3001:collection_orders"); expect(wrapper.find('[data-testid="invoice-period-tree-field-order:9001-notes"]').exists()).toBe(true); expect(wrapper.find('[data-testid="invoice-period-tree-field-value-order:9001-notes"]').text()).toContain("Tom"); expect(wrapper.find('[data-testid="invoice-period-tree-field-order:9001-reg_2"]').exists()).toBe(true); expect(wrapper.find('[data-testid^="invoice-period-tree-field-empty-toggle-"]').exists()).toBe(false); }); it("omits redundant collection name and customer number fields", async () => { const wrapper = mountTree(); expect(wrapper.find('[data-testid="invoice-period-tree-field-collected_order_invoice:3001-notes"]').exists()).toBe(true); expect(wrapper.find('[data-testid="invoice-period-tree-field-collected_order_invoice:3001-name"]').exists()).toBe(false); expect(wrapper.find('[data-testid="invoice-period-tree-field-collected_order_invoice:3001-customer_number"]').exists()).toBe(false); }); it("renders only resolved draft/booked e-conomic invoice children", async () => { const wrapper = mountTree(); await expandNode(wrapper, "collected_order_invoice:3001"); await expandNode(wrapper, "category:3001:collection_economic"); expect(mocks.economicDetails).toHaveBeenCalledWith(3001); expect(wrapper.text()).not.toContain("E-conomic kladde #88"); expect(wrapper.text()).toContain("E-conomic faktura #99"); }); it("downloads booked e-conomic invoice pdfs for booked invoice nodes", async () => { const wrapper = mountTree(); await expandNode(wrapper, "collected_order_invoice:3001"); await expandNode(wrapper, "category:3001:collection_economic"); const bookedInvoiceNode = wrapper.find('[data-node-key="economic_invoice:3001:booked:99"]'); expect(bookedInvoiceNode.exists()).toBe(true); await bookedInvoiceNode.find(".b-tree-node-content").trigger("click"); await flushPromises(); expect(mocks.economicPdf).toHaveBeenCalledWith(3001, "booked"); expect(window.open).toHaveBeenCalledWith("https://example.test/invoice.pdf", "_blank", "noopener,noreferrer"); }); });