// @vitest-environment jsdom import { flushPromises, shallowMount } from "@vue/test-utils"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { ref } from "vue"; import { createI18n } from "vue-i18n"; const { request, loadList, fire } = vi.hoisted(() => ({ request: vi.fn(), loadList: vi.fn(), fire: vi.fn(), })); vi.mock("sweetalert2", () => ({ default: { fire } })); vi.mock("@/components/session/token/SessionUser.vue", () => ({ SessionUser: { request, functions: { currency: { toLocal: (value) => String(value) }, parseErrorMessage: (error) => String(error), }, objects: { global: { language: { completed: "Completed", generated: "Generated", possible_duplicates: "Possible duplicates", price_match: "Price match", price_unmatch: "Price mismatch", }, }, orders: { meta: { title: "Orders" } }, }, }, })); vi.mock("@/components/pagination/paginatedList.vue", () => ({ usePaginatedListInstance: () => ({ loadList }), })); vi.mock("@/components/pagination/departmentTabs.vue", () => ({ departments: ref([{ id: 1, name: "Hall 1" }]), getDepartments: vi.fn(), getDepartmentName: () => "Hall 1", })); vi.mock("@/components/displays/department/pos/sync/xlvaskUsageAmountCache.js", () => ({ clearCachedXlvaskUsageAmount: vi.fn(), getCachedXlvaskUsageAmount: vi.fn(() => null), setCachedXlvaskUsageAmount: vi.fn(), })); import XlvaskUsageOrdersTable from "@/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue"; const reviewEligibleRow = (overrides = {}) => ({ id: 9101, reg_1: "ZZ99001", created_at: new Date().toISOString(), customer_name: "Test customer", department_id: 1, lane: 1, total_net_amount: 250, import_state: "new", resolution_state: "needs_review", certainty: "uncertain", planned_action: "none", // No AI suggestion yet — automation is empty (the autopilot hasn't run). automation: { status: "none", review_eligible: true, }, ...overrides, }); const mountTable = (props = {}) => shallowMount(XlvaskUsageOrdersTable, { props: { objects: [reviewEligibleRow()], allowReviewActions: true, allowSelectMultiple: true, ...props, }, global: { plugins: [ createI18n({ legacy: false, locale: "en", missingWarn: false, fallbackWarn: false, messages: { en: {} } }), ], stubs: { WhiteBoxCard: { template: "
", }, OrderItemsTable: true, }, }, }); describe("XL-Vask manual review buttons", () => { beforeEach(() => { request.mockReset(); loadList.mockReset(); fire.mockReset(); }); it("exposes accept / reject / ignore buttons even when the autopilot has no suggestion", () => { const wrapper = mountTable(); expect(wrapper.find("[data-testid='xlvask-accept-9101']").exists()).toBe(true); expect(wrapper.find("[data-testid='xlvask-reject-9101']").exists()).toBe(true); expect(wrapper.find("[data-testid='xlvask-ignore-9101']").exists()).toBe(true); }); it("sends force_manual=true when accepting without an AI suggestion", async () => { const wrapper = mountTable(); request.mockResolvedValue({ data: { data: { preview: { id: "preview-id", selection_hash: "abc12345".repeat(8) }, }, }, }); await wrapper.find("[data-testid='xlvask-accept-9101']").trigger("click"); await flushPromises(); const previewCall = request.mock.calls.find( ([endpoint, method]) => endpoint === "/modules/xlvask/services/usage/automation/decisions/preview" && method === "POST" ); expect(previewCall).toBeDefined(); const payload = previewCall[2]; expect(payload).toMatchObject({ usage_log_ids: [9101], action: "create_order", force_manual: true, }); }); it("does not send force_manual when the AI already has a matching suggestion", async () => { const row = reviewEligibleRow({ automation: { id: 5001, status: "suggested", action: "create_order", can_accept: true, can_deny: true, can_ignore: true, can_attach_order: true, can_create_order: true, review_eligible: true, }, }); const wrapper = mountTable({ objects: [row] }); request.mockResolvedValue({ data: { data: { preview: { id: "preview-id-2", selection_hash: "def67890".repeat(8) }, }, }, }); await wrapper.find("[data-testid='xlvask-accept-9101']").trigger("click"); await flushPromises(); const previewCall = request.mock.calls.find( ([endpoint, method]) => endpoint === "/modules/xlvask/services/usage/automation/decisions/preview" && method === "POST" ); expect(previewCall).toBeDefined(); const payload = previewCall[2]; expect(payload.action).toBe("create_order"); expect(payload).not.toHaveProperty("force_manual"); }); it("hides the buttons once the row is already resolved (no double-action)", () => { const row = reviewEligibleRow({ resolution_state: "auto_linked", automation: { status: "accepted", action: "attach_order", review_eligible: false }, }); const wrapper = mountTable({ objects: [row] }); expect(wrapper.find("[data-testid='xlvask-accept-9101']").exists()).toBe(false); expect(wrapper.find("[data-testid='xlvask-reject-9101']").exists()).toBe(false); expect(wrapper.find("[data-testid='xlvask-ignore-9101']").exists()).toBe(false); }); it("surfaces the backend error when the manual review is rejected", async () => { const wrapper = mountTable(); request.mockRejectedValue(new Error("Wash-id uniqueness activation is blocked")); await wrapper.find("[data-testid='xlvask-accept-9101']").trigger("click"); await flushPromises(); expect(fire).toHaveBeenCalledWith( expect.objectContaining({ icon: "error", title: "invoicing_period.xlvask_autopilot.preview.error_title", }) ); }); });