429 lines
12 KiB
JavaScript
429 lines
12 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { defineComponent, h, ref } from "vue";
|
|
import { flushPromises, mount } from "@vue/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const getSingleMock = vi.hoisted(() => vi.fn());
|
|
const sessionState = vi.hoisted(() => ({
|
|
canAccessSuperUser: false,
|
|
}));
|
|
|
|
vi.mock("vue-i18n", () => ({
|
|
useI18n: () => ({
|
|
t: (key) => key,
|
|
}),
|
|
createI18n: () => ({
|
|
global: {
|
|
t: (key) => key,
|
|
},
|
|
install: () => {},
|
|
}),
|
|
}));
|
|
|
|
vi.mock("vue-router", () => ({
|
|
useRoute: () => ({
|
|
path: "/admin/12/modules/pos/orders",
|
|
fullPath: "/admin/12/modules/pos/orders",
|
|
name: "posorders",
|
|
params: {
|
|
departmentId: "12",
|
|
},
|
|
}),
|
|
}));
|
|
|
|
vi.mock("sweetalert2", () => ({
|
|
default: {
|
|
fire: vi.fn(() => Promise.resolve()),
|
|
close: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/components/shop/POSDepartmentProcess.vue", () => ({
|
|
downloadAttachment: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/components/pagination/departmentTabs.vue", () => ({
|
|
departments: ref([{ id: 12, name: "Demo" }]),
|
|
getDepartments: vi.fn(),
|
|
isLoading: ref(false),
|
|
getDepartmentName: (departmentId) => (Number(departmentId) === 12 ? "Demo" : `Department ${departmentId}`),
|
|
}));
|
|
|
|
vi.mock("@/components/displays/PopperDefault.vue", () => ({
|
|
showPopper: vi.fn(),
|
|
removePopperIfOpen: vi.fn(),
|
|
popperBox: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/components/pagination/paginatedList.vue", () => ({
|
|
orderBy: ref("id"),
|
|
orderDirection: ref("asc"),
|
|
setOrder: vi.fn(),
|
|
loadList: vi.fn(),
|
|
}));
|
|
|
|
vi.mock(
|
|
"@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportInvoiceQueue.vue",
|
|
() => ({
|
|
invoiceQueue: {
|
|
invoiceCollectionQueueInProgress: ref([]),
|
|
invoiceCollectionQueueFailed: ref([]),
|
|
invoiceCollectionQueueSuccess: ref([]),
|
|
invoiceCollectionQueue: ref([]),
|
|
collectionQueueLog: ref([]),
|
|
addInvoiceCollectionsToQueue: vi.fn(),
|
|
processInvoiceCollectionQueue: vi.fn(),
|
|
retryInvoiceCollection: vi.fn(),
|
|
},
|
|
})
|
|
);
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
canAccessDepartment: vi.fn(() => false),
|
|
canAccessAdmin: vi.fn(() => false),
|
|
canAccessSuperUser: vi.fn(() => sessionState.canAccessSuperUser),
|
|
hasPermission: vi.fn(() => false),
|
|
hasAttribute: vi.fn(() => false),
|
|
objects: {
|
|
orders: {
|
|
columns: {
|
|
id: { label: "ID" },
|
|
customer_id: { label: "Customer" },
|
|
cashier_id: { label: "Cashier" },
|
|
department_id: { label: "Department" },
|
|
reference: { label: "Reference" },
|
|
notes: { label: "Notes" },
|
|
po: { label: "PO" },
|
|
created_at: { label: "Created" },
|
|
},
|
|
meta: {
|
|
labels: {
|
|
single: "Order",
|
|
multiple: "Orders",
|
|
entries: "Orders",
|
|
},
|
|
},
|
|
showEditObjectFieldForm: vi.fn(() => Promise.resolve()),
|
|
},
|
|
vehicles: {
|
|
meta: {
|
|
labels: {
|
|
multiple: "Vehicles",
|
|
},
|
|
},
|
|
},
|
|
departments: {
|
|
meta: {
|
|
labels: {
|
|
single: "department",
|
|
},
|
|
},
|
|
},
|
|
collectedOrderInvoices: {
|
|
get: {
|
|
single: getSingleMock,
|
|
},
|
|
},
|
|
global: {
|
|
language: {
|
|
showing: "Showing",
|
|
showing_of_separator: "of",
|
|
unselect: "Unselect",
|
|
select: "Select",
|
|
all: "All",
|
|
invoice: "Invoice",
|
|
collection: "Collection",
|
|
yes: "Yes",
|
|
no: "No",
|
|
handheld_pending_order: "Pending handheld",
|
|
confirmation_needed: "Confirmation needed",
|
|
},
|
|
system_user_ids: [],
|
|
},
|
|
},
|
|
functions: {
|
|
ucFirst: (value) => {
|
|
const normalized = String(value || "");
|
|
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
},
|
|
currency: {
|
|
toLocal: (value) => String(value ?? ""),
|
|
},
|
|
text: {
|
|
truncate: (text, length = 25) => (text.length > length ? `${text.substring(0, length)}...` : text),
|
|
},
|
|
date: {
|
|
timeAgo: () => "just now",
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
|
|
|
|
const ViewportResponsiveWrapperStub = {
|
|
template: `
|
|
<div class="viewport-wrapper-stub">
|
|
<slot name="desktop"></slot>
|
|
</div>
|
|
`,
|
|
};
|
|
|
|
const EditableTableColumnStub = defineComponent({
|
|
name: "EditableTableColumn",
|
|
props: {
|
|
object: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
column: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
parseFunction: {
|
|
type: Function,
|
|
default: null,
|
|
},
|
|
virtualColumn: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
componentWrapper: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
},
|
|
setup(props) {
|
|
return () => {
|
|
const tagName = props.componentWrapper || "td";
|
|
const rawValue = props.virtualColumn ? props.object : props.object?.[props.column];
|
|
const content = props.parseFunction ? props.parseFunction(rawValue) : rawValue;
|
|
return h(tagName, { class: "editable-table-column-stub" }, String(content ?? ""));
|
|
};
|
|
},
|
|
});
|
|
|
|
const ColorIndicatorStub = defineComponent({
|
|
name: "ColorIndicator",
|
|
props: {
|
|
label: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
},
|
|
setup(props) {
|
|
return () => h("span", { class: "color-indicator-stub" }, String(props.label?.text ?? ""));
|
|
},
|
|
});
|
|
|
|
const ActionSettingsWheelButtonStub = {
|
|
template: '<div class="action-settings-wheel-button-stub"><slot name="actions"></slot></div>',
|
|
};
|
|
|
|
const ActionSettingsWheelItemStub = {
|
|
template: '<div class="action-settings-wheel-item-stub"></div>',
|
|
};
|
|
|
|
const OrderContentTableStub = {
|
|
template: '<div class="order-content-table-stub"></div>',
|
|
};
|
|
|
|
const PosDepartmentStepMobileAttachmentStub = {
|
|
template: '<div class="pos-mobile-attachment-stub"></div>',
|
|
};
|
|
|
|
const OrderAttachmentsActionButtonStub = {
|
|
template: '<div class="order-attachments-action-button-stub"></div>',
|
|
};
|
|
|
|
const AssignDraftOrderCustomerModalStub = {
|
|
template: '<div class="assign-draft-order-customer-modal-stub"></div>',
|
|
};
|
|
|
|
const WhiteBoxCardStub = {
|
|
template:
|
|
'<div class="white-box-card-stub"><slot name="header"></slot><slot></slot><slot name="footer"></slot></div>',
|
|
};
|
|
|
|
const InvoiceMultipleCollectionsModalStub = {
|
|
template: '<div class="invoice-multiple-collections-modal-stub"></div>',
|
|
};
|
|
|
|
const InvoicingBillingPeriodInvoiceProgressBarStub = {
|
|
template: '<div class="invoice-progress-bar-stub"></div>',
|
|
};
|
|
|
|
const createOrder = (overrides = {}) => ({
|
|
id: 1,
|
|
customer_id: 12345,
|
|
department_id: 12,
|
|
reference: "",
|
|
notes: "",
|
|
po: "",
|
|
reg_1: "AB12345",
|
|
reg_2: "",
|
|
reg_3: "",
|
|
created_at: "2026-04-13 11:00:00",
|
|
total_net_amount: 100,
|
|
invoice_collection_id: null,
|
|
invoice_collection: null,
|
|
attachments: [],
|
|
economic_invoice_module: null,
|
|
stripe_invoice_module: null,
|
|
error_message: null,
|
|
pending_handheld: false,
|
|
completed_at: null,
|
|
customer_name: "Demo Customer",
|
|
cashier_id: 7,
|
|
cashier_name: "Demo Cashier",
|
|
...overrides,
|
|
});
|
|
|
|
const mountOrdersTable = (props = {}) => {
|
|
return mount(OrdersTable, {
|
|
props: {
|
|
orders: [],
|
|
isCustomerView: true,
|
|
invoiceView: false,
|
|
allowSelectMultiple: false,
|
|
groupInvoiceCollection: false,
|
|
showOnlyWithIds: [],
|
|
autoExpandAll: false,
|
|
excludedOrderIds: [],
|
|
...props,
|
|
},
|
|
global: {
|
|
stubs: {
|
|
ViewportResponsiveWrapper: ViewportResponsiveWrapperStub,
|
|
EditableTableColumn: EditableTableColumnStub,
|
|
ColorIndicator: ColorIndicatorStub,
|
|
ActionSettingsWheelButton: ActionSettingsWheelButtonStub,
|
|
ActionSettingsWheelItem: ActionSettingsWheelItemStub,
|
|
OrderContentTable: OrderContentTableStub,
|
|
PosDepartmentStepMobileAttachment: PosDepartmentStepMobileAttachmentStub,
|
|
OrderAttachmentsActionButton: OrderAttachmentsActionButtonStub,
|
|
AssignDraftOrderCustomerModal: AssignDraftOrderCustomerModalStub,
|
|
WhiteBoxCard: WhiteBoxCardStub,
|
|
InvoiceMultipleCollectionsModal: InvoiceMultipleCollectionsModalStub,
|
|
InvoicingBillingPeriodInvoiceProgressBar: InvoicingBillingPeriodInvoiceProgressBarStub,
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
describe("OrdersTable", () => {
|
|
beforeEach(() => {
|
|
sessionState.canAccessSuperUser = false;
|
|
getSingleMock.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("renders customer orders with sparse text fields without crashing or preloading invoice collections", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
const wrapper = mountOrdersTable({
|
|
orders: [
|
|
createOrder({
|
|
id: 10,
|
|
reference: undefined,
|
|
notes: null,
|
|
po: undefined,
|
|
invoice_collection_id: undefined,
|
|
}),
|
|
],
|
|
});
|
|
|
|
await flushPromises();
|
|
|
|
expect(wrapper.text()).toContain("10");
|
|
expect(getSingleMock).not.toHaveBeenCalled();
|
|
expect(consoleErrorSpy.mock.calls.map((call) => call.join(" ")).join("\n")).not.toContain(
|
|
"Cannot read properties of undefined"
|
|
);
|
|
});
|
|
|
|
it("preloads only valid invoice collection ids and keeps rendering when a lookup fails", async () => {
|
|
sessionState.canAccessSuperUser = true;
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
getSingleMock.mockImplementation(async (invoiceCollectionId) => {
|
|
if (invoiceCollectionId === 14) {
|
|
return {
|
|
id: 14,
|
|
orders: [{ id: 1001 }, { id: 1002 }, { id: 3000 }],
|
|
};
|
|
}
|
|
|
|
throw new Error("Forbidden");
|
|
});
|
|
|
|
const wrapper = mountOrdersTable({
|
|
groupInvoiceCollection: true,
|
|
orders: [
|
|
createOrder({
|
|
id: 20,
|
|
invoice_collection_id: 14,
|
|
invoice_collection: { id: 14, closed_at: null, booked_invoice_id: null },
|
|
}),
|
|
createOrder({
|
|
id: 21,
|
|
invoice_collection_id: "14",
|
|
invoice_collection: { id: 14, closed_at: null, booked_invoice_id: null },
|
|
}),
|
|
createOrder({
|
|
id: 22,
|
|
invoice_collection_id: undefined,
|
|
}),
|
|
createOrder({
|
|
id: 23,
|
|
invoice_collection_id: "",
|
|
}),
|
|
createOrder({
|
|
id: 24,
|
|
invoice_collection_id: -1,
|
|
}),
|
|
createOrder({
|
|
id: 25,
|
|
invoice_collection_id: 21,
|
|
invoice_collection: { id: 21, closed_at: null, booked_invoice_id: null },
|
|
}),
|
|
],
|
|
excludedOrderIds: [3000],
|
|
});
|
|
|
|
await flushPromises();
|
|
await flushPromises();
|
|
|
|
const requestedIds = getSingleMock.mock.calls
|
|
.map(([invoiceCollectionId]) => invoiceCollectionId)
|
|
.sort((left, right) => left - right);
|
|
|
|
expect(requestedIds).toEqual([14, 21]);
|
|
expect(wrapper.text()).toContain("Faktura samling ID: 14");
|
|
expect(wrapper.text()).toContain("Faktura samling ID: 21");
|
|
expect(consoleErrorSpy.mock.calls.map((call) => call.join(" ")).join("\n")).not.toContain(
|
|
"Unhandled error during execution of component update"
|
|
);
|
|
});
|
|
|
|
it("renders the draft assignment action when explicitly enabled", async () => {
|
|
const wrapper = mountOrdersTable({
|
|
showDraftAssignmentActions: true,
|
|
orders: [
|
|
createOrder({
|
|
id: 56625,
|
|
}),
|
|
],
|
|
});
|
|
|
|
await flushPromises();
|
|
|
|
expect(wrapper.find('[data-testid="draft-order-assign-customer-button-56625"]').exists()).toBe(true);
|
|
expect(wrapper.text()).toContain("admin.pos.drafts_assignment.button");
|
|
});
|
|
});
|