557 lines
18 KiB
JavaScript
557 lines
18 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { mount } from "@vue/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
|
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
import { createTestI18n } from "./helpers/mountWithApp.js";
|
|
|
|
const getUserIdMock = vi.hoisted(() => vi.fn(() => Promise.resolve({ data: { data: { user_id: 777 } } })));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
adminUser: {
|
|
customers: {
|
|
fromCustomerNumber: {
|
|
getUserId: getUserIdMock,
|
|
},
|
|
},
|
|
},
|
|
request: vi.fn(() => Promise.resolve()),
|
|
canAccessAdmin: vi.fn(() => false),
|
|
canAccessSuperUser: vi.fn(() => true),
|
|
canAccessUser: vi.fn(() => false),
|
|
functions: {
|
|
ucFirst: (value) => value,
|
|
parseErrorMessage: () => "error",
|
|
device: {
|
|
isMobile: vi.fn(() => false),
|
|
},
|
|
redirectTo: {
|
|
department: vi.fn(),
|
|
superUser: vi.fn(),
|
|
user: vi.fn(),
|
|
},
|
|
},
|
|
superUser: {
|
|
intimidate: {
|
|
intimidateUser: vi.fn(() => Promise.resolve()),
|
|
getImpersonationToken: vi.fn(() => Promise.resolve("token")),
|
|
showImpersonationQRCode: vi.fn(() => Promise.resolve({ value: "qr" })),
|
|
},
|
|
},
|
|
objects: {
|
|
order_bookings: {
|
|
meta: {
|
|
labels: {
|
|
single: "Booking",
|
|
multiple: "Bookings",
|
|
},
|
|
},
|
|
showEditObjectFieldForm: vi.fn(() => Promise.resolve()),
|
|
functions: {
|
|
showCompleteConfirmationModal: vi.fn(() => Promise.resolve()),
|
|
showDeleteConfirmationModal: vi.fn(() => Promise.resolve()),
|
|
},
|
|
},
|
|
department_lanes: {
|
|
meta: {
|
|
labels: {
|
|
single: "Lane",
|
|
},
|
|
},
|
|
functions: {
|
|
forceEnableMachine: vi.fn(() => Promise.resolve()),
|
|
forceDisableMachine: vi.fn(() => Promise.resolve()),
|
|
},
|
|
},
|
|
orders: {
|
|
meta: {
|
|
labels: {
|
|
single: "Order",
|
|
},
|
|
},
|
|
functions: {
|
|
fetchAttachments: vi.fn(() => Promise.resolve([])),
|
|
get_department_id: vi.fn(() => Promise.resolve(1)),
|
|
removeAttachment: vi.fn(() => Promise.resolve({ success: true })),
|
|
showAttachWashCertificateForm: vi.fn(() => Promise.resolve()),
|
|
showChangeCustomerForm: vi.fn(() => Promise.resolve()),
|
|
showChangeInvoiceCollectionForm: vi.fn(() => Promise.resolve()),
|
|
showDeleteConfirmationModal: vi.fn(() => Promise.resolve()),
|
|
downloadAttachment: vi.fn(() => Promise.resolve()),
|
|
},
|
|
},
|
|
collectedOrderInvoices: {
|
|
meta: {
|
|
title: "Invoices",
|
|
},
|
|
functions: {
|
|
download: vi.fn(() => Promise.resolve()),
|
|
},
|
|
},
|
|
vehicles: {
|
|
meta: {
|
|
labels: {
|
|
single: "Vehicle",
|
|
multiple: "Vehicles",
|
|
},
|
|
},
|
|
},
|
|
global: {
|
|
language: {
|
|
customer: "Customer",
|
|
},
|
|
},
|
|
subuser_grants: {
|
|
functions: {
|
|
showPermissionEditForm: vi.fn(() => Promise.resolve()),
|
|
},
|
|
delete: vi.fn(() => Promise.resolve()),
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const SETTINGS_WHEEL_TRANSLATION_KEYS = [
|
|
"admin.pos.settings_wheel.associate_order",
|
|
"admin.pos.settings_wheel.attach_wash_certificate",
|
|
"admin.pos.settings_wheel.attached_files",
|
|
"admin.pos.settings_wheel.booking",
|
|
"admin.pos.settings_wheel.change_association",
|
|
"admin.pos.settings_wheel.change_customer",
|
|
"admin.pos.settings_wheel.change_invoice_collection",
|
|
"admin.pos.settings_wheel.change_order",
|
|
"admin.pos.settings_wheel.change_password",
|
|
"admin.pos.settings_wheel.close",
|
|
"admin.pos.settings_wheel.copy_link",
|
|
"admin.pos.settings_wheel.delete_booking",
|
|
"admin.pos.settings_wheel.delete_order",
|
|
"admin.pos.settings_wheel.delete_user",
|
|
"admin.pos.settings_wheel.download_invoice",
|
|
"admin.pos.settings_wheel.edit_permissions",
|
|
"admin.pos.settings_wheel.enter_password",
|
|
"admin.pos.settings_wheel.error",
|
|
"admin.pos.settings_wheel.error_changing_password",
|
|
"admin.pos.settings_wheel.error_downloading_attachment",
|
|
"admin.pos.settings_wheel.link_copied",
|
|
"admin.pos.settings_wheel.login_as_customer",
|
|
"admin.pos.settings_wheel.login_as_user",
|
|
"admin.pos.settings_wheel.login_as_user_qr",
|
|
"admin.pos.settings_wheel.mark_as_completed",
|
|
"admin.pos.settings_wheel.no_actions_defined",
|
|
"admin.pos.settings_wheel.open_attached_file",
|
|
"admin.pos.settings_wheel.password_changed",
|
|
"admin.pos.settings_wheel.password_changed_text",
|
|
"admin.pos.settings_wheel.please_enter_password",
|
|
"admin.pos.settings_wheel.scan_qr_to_login",
|
|
"admin.pos.settings_wheel.show_customer",
|
|
"admin.pos.settings_wheel.show_qr_code",
|
|
"admin.pos.settings_wheel.view_booking_new_tab",
|
|
"admin.pos.settings_wheel.view_customer_new_tab",
|
|
"admin.pos.settings_wheel.view_invoice_collection_new_tab",
|
|
"admin.pos.settings_wheel.view_lane_new_tab",
|
|
"admin.pos.settings_wheel.view_lane_setup_new_tab",
|
|
"admin.pos.settings_wheel.view_order_new_tab",
|
|
"admin.pos.settings_wheel.view_vehicle_new_tab",
|
|
"admin.pos.attachments_no_preview",
|
|
"admin.pos.attachments_office_preview_unavailable",
|
|
"global.delete",
|
|
"global.download",
|
|
"global.loading",
|
|
"global.preview",
|
|
"global.print",
|
|
"superuser.department_lane.force_disable_machine",
|
|
"superuser.department_lane.force_enable_machine",
|
|
];
|
|
|
|
const createKeyValueMessages = (keys) => {
|
|
const enMessages = {};
|
|
|
|
for (const key of keys) {
|
|
const parts = key.split(".");
|
|
let cursor = enMessages;
|
|
|
|
for (let i = 0; i < parts.length; i += 1) {
|
|
const part = parts[i];
|
|
const isLeaf = i === parts.length - 1;
|
|
|
|
if (isLeaf) {
|
|
cursor[part] = key;
|
|
} else {
|
|
cursor[part] = cursor[part] ?? {};
|
|
cursor = cursor[part];
|
|
}
|
|
}
|
|
}
|
|
|
|
return { en: enMessages };
|
|
};
|
|
|
|
const i18n = createTestI18n(createKeyValueMessages(SETTINGS_WHEEL_TRANSLATION_KEYS));
|
|
|
|
const flushMicrotasks = async () => {
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
};
|
|
|
|
const setDesktopFlyoutViewport = () => {
|
|
Object.defineProperty(window, "innerWidth", {
|
|
configurable: true,
|
|
writable: true,
|
|
value: 1900,
|
|
});
|
|
Object.defineProperty(window, "innerHeight", {
|
|
configurable: true,
|
|
writable: true,
|
|
value: 900,
|
|
});
|
|
window.matchMedia = vi.fn().mockImplementation(() => ({
|
|
matches: true,
|
|
media: "",
|
|
onchange: null,
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
}));
|
|
};
|
|
|
|
const mockMenuGeometry = () =>
|
|
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(function getBoundingClientRectMock() {
|
|
if (this.classList?.contains("dropdown-trigger")) {
|
|
return {
|
|
x: 1750,
|
|
y: 96,
|
|
top: 96,
|
|
left: 1750,
|
|
right: 1850,
|
|
bottom: 136,
|
|
width: 100,
|
|
height: 40,
|
|
toJSON() {
|
|
return this;
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
x: 1320,
|
|
y: 136,
|
|
top: 136,
|
|
left: 1320,
|
|
right: 1850,
|
|
bottom: 560,
|
|
width: 530,
|
|
height: 424,
|
|
toJSON() {
|
|
return this;
|
|
},
|
|
};
|
|
});
|
|
|
|
const mountDesktopFlyoutButton = (props = {}) => {
|
|
setDesktopFlyoutViewport();
|
|
|
|
return mount(ActionSettingsWheelButton, {
|
|
props: {
|
|
order_id: 42,
|
|
refreshFunction: vi.fn(() => Promise.resolve()),
|
|
...props,
|
|
},
|
|
slots: {
|
|
actions: "",
|
|
},
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
const originalFetch = global.fetch;
|
|
const originalCreateObjectURL = global.URL.createObjectURL;
|
|
const originalRevokeObjectURL = global.URL.revokeObjectURL;
|
|
const originalMatchMedia = window.matchMedia;
|
|
|
|
describe("ActionSettingsWheelButton", () => {
|
|
beforeEach(() => {
|
|
getUserIdMock.mockClear();
|
|
SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm.mockClear();
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockReset();
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([]);
|
|
SessionUser.objects.orders.functions.downloadAttachment.mockReset();
|
|
SessionUser.objects.orders.functions.downloadAttachment.mockResolvedValue("https://cdn.example.test/attachment");
|
|
SessionUser.objects.orders.functions.removeAttachment.mockReset();
|
|
SessionUser.objects.orders.functions.removeAttachment.mockResolvedValue({ success: true });
|
|
global.fetch = vi.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
blob: () => Promise.resolve(new Blob(["preview"], { type: "application/pdf" })),
|
|
})
|
|
);
|
|
global.URL.createObjectURL = vi.fn((blob) => `blob:${blob.type}:${Date.now()}`);
|
|
global.URL.revokeObjectURL = vi.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
global.URL.createObjectURL = originalCreateObjectURL;
|
|
global.URL.revokeObjectURL = originalRevokeObjectURL;
|
|
window.matchMedia = originalMatchMedia;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("resolves customer user id once on mount and not on unrelated rerenders", async () => {
|
|
const wrapper = mount(ActionSettingsWheelButton, {
|
|
props: {
|
|
displayActionsDirectly: true,
|
|
customer_number: 123456,
|
|
order_id: null,
|
|
},
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
ActionSettingsWheelItem: { template: "<div />" },
|
|
ActionSettingsWheelItemLabel: { template: "<div />" },
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
expect(getUserIdMock).toHaveBeenCalledTimes(1);
|
|
expect(getUserIdMock).toHaveBeenLastCalledWith(123456);
|
|
|
|
await wrapper.setProps({ icon: "fas fa-ellipsis-v" });
|
|
await flushMicrotasks();
|
|
expect(getUserIdMock).toHaveBeenCalledTimes(1);
|
|
|
|
await wrapper.setProps({ customer_number: 654321 });
|
|
await flushMicrotasks();
|
|
expect(getUserIdMock).toHaveBeenCalledTimes(2);
|
|
expect(getUserIdMock).toHaveBeenLastCalledWith(654321);
|
|
});
|
|
|
|
it("does not perform customer lookup when explicit user id is provided", async () => {
|
|
mount(ActionSettingsWheelButton, {
|
|
props: {
|
|
displayActionsDirectly: true,
|
|
user_id: 77,
|
|
customer_number: null,
|
|
},
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
ActionSettingsWheelItem: { template: "<div />" },
|
|
ActionSettingsWheelItemLabel: { template: "<div />" },
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
expect(getUserIdMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("routes 'change invoice collection' action without opening a new tab", async () => {
|
|
const windowOpenSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
|
|
|
const wrapper = mount(ActionSettingsWheelButton, {
|
|
props: {
|
|
order_id: 42,
|
|
invoice_collection_id: 991,
|
|
},
|
|
slots: {
|
|
actions: "",
|
|
},
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
|
|
const buttons = wrapper.findAll("button.dropdown-item-action");
|
|
const changeInvoiceCollectionButton = buttons.find((buttonWrapper) =>
|
|
buttonWrapper.text().includes("admin.pos.settings_wheel.change_invoice_collection")
|
|
);
|
|
|
|
expect(changeInvoiceCollectionButton).toBeTruthy();
|
|
await changeInvoiceCollectionButton.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm).toHaveBeenCalledTimes(1);
|
|
expect(SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm).toHaveBeenCalledWith(
|
|
42,
|
|
expect.any(Function)
|
|
);
|
|
expect(windowOpenSpy).not.toHaveBeenCalled();
|
|
|
|
windowOpenSpy.mockRestore();
|
|
});
|
|
|
|
it("routes 'view invoice collection in new tab' action to window.open only", async () => {
|
|
const windowOpenSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
|
|
|
const wrapper = mount(ActionSettingsWheelButton, {
|
|
props: {
|
|
order_id: 42,
|
|
invoice_collection_id: 777,
|
|
},
|
|
slots: {
|
|
actions: "",
|
|
},
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm.mockClear();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
|
|
const buttons = wrapper.findAll("button.dropdown-item-action");
|
|
const viewInvoiceCollectionButton = buttons.find((buttonWrapper) =>
|
|
buttonWrapper.text().includes("admin.pos.settings_wheel.view_invoice_collection_new_tab")
|
|
);
|
|
|
|
expect(viewInvoiceCollectionButton).toBeTruthy();
|
|
await viewInvoiceCollectionButton.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(windowOpenSpy).toHaveBeenCalledWith("/superuser/invoices/777", "_blank");
|
|
expect(SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm).not.toHaveBeenCalled();
|
|
|
|
windowOpenSpy.mockRestore();
|
|
});
|
|
|
|
it("builds attachment flyout rows and switches the preview panel on hover and focus", async () => {
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([
|
|
{
|
|
id: 301,
|
|
content: {
|
|
document: "safety-seal.pdf",
|
|
other: "safety-seal.pdf",
|
|
},
|
|
},
|
|
{
|
|
id: 302,
|
|
content: {
|
|
image: "truck.jpg",
|
|
other: "truck.jpg",
|
|
},
|
|
},
|
|
]);
|
|
SessionUser.objects.orders.functions.downloadAttachment.mockImplementation((orderId, attachmentId) =>
|
|
Promise.resolve(`https://cdn.example.test/orders/${orderId}/attachments/${attachmentId}`)
|
|
);
|
|
global.fetch = vi.fn((url) =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
blob: () =>
|
|
Promise.resolve(
|
|
new Blob(["preview"], {
|
|
type: String(url).includes("/301") ? "application/pdf" : "image/png",
|
|
})
|
|
),
|
|
})
|
|
);
|
|
|
|
const geometrySpy = mockMenuGeometry();
|
|
const wrapper = mountDesktopFlyoutButton();
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
const attachmentsSection = wrapper.get('[data-testid="action-settings-wheel-section-attachments"]');
|
|
await attachmentsSection.trigger("mouseenter");
|
|
await flushMicrotasks();
|
|
|
|
const pdfRow = wrapper.get('[data-testid="action-settings-wheel-attachment-row-301"]');
|
|
const imageRow = wrapper.get('[data-testid="action-settings-wheel-attachment-row-302"]');
|
|
|
|
expect(pdfRow.text()).toContain("safety-seal.pdf");
|
|
expect(imageRow.text()).toContain("truck.jpg");
|
|
|
|
await pdfRow.trigger("mouseenter");
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
const previewPanel = wrapper.get('[data-testid="action-settings-wheel-attachment-panel"]');
|
|
expect(previewPanel.find("iframe").exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-attachment-action-preview-301"]').exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-attachment-action-download-301"]').exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-attachment-action-print-301"]').exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-attachment-action-delete-301"]').exists()).toBe(true);
|
|
|
|
await imageRow.trigger("focus");
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.get('[data-testid="action-settings-wheel-attachment-panel"]').find("img").exists()).toBe(true);
|
|
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
|
|
geometrySpy.mockRestore();
|
|
});
|
|
|
|
it("deletes attachments, refreshes the list, and clears preview object URLs on close", async () => {
|
|
const attachmentsBeforeDelete = [
|
|
{
|
|
id: 301,
|
|
content: {
|
|
document: "safety-seal.pdf",
|
|
other: "safety-seal.pdf",
|
|
},
|
|
},
|
|
];
|
|
SessionUser.objects.orders.functions.fetchAttachments
|
|
.mockResolvedValueOnce(attachmentsBeforeDelete)
|
|
.mockResolvedValueOnce([]);
|
|
SessionUser.objects.orders.functions.downloadAttachment.mockResolvedValue(
|
|
"https://cdn.example.test/orders/42/attachments/301"
|
|
);
|
|
|
|
const refreshFunction = vi.fn(() => Promise.resolve());
|
|
const geometrySpy = mockMenuGeometry();
|
|
const wrapper = mountDesktopFlyoutButton({ refreshFunction });
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
await wrapper.get('[data-testid="action-settings-wheel-section-attachments"]').trigger("mouseenter");
|
|
await flushMicrotasks();
|
|
await wrapper.get('[data-testid="action-settings-wheel-attachment-row-301"]').trigger("mouseenter");
|
|
await flushMicrotasks();
|
|
|
|
expect(global.URL.createObjectURL).toHaveBeenCalledTimes(1);
|
|
|
|
await wrapper.get('[data-testid="action-settings-wheel-attachment-action-delete-301"]').trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.objects.orders.functions.removeAttachment).toHaveBeenCalledTimes(1);
|
|
expect(SessionUser.objects.orders.functions.removeAttachment).toHaveBeenCalledWith(42, 301);
|
|
expect(refreshFunction).toHaveBeenCalledTimes(1);
|
|
expect(SessionUser.objects.orders.functions.fetchAttachments).toHaveBeenCalledTimes(2);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-section-attachments"]').exists()).toBe(false);
|
|
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(global.URL.revokeObjectURL).toHaveBeenCalledTimes(1);
|
|
|
|
wrapper.unmount();
|
|
geometrySpy.mockRestore();
|
|
});
|
|
});
|