1465 lines
52 KiB
JavaScript
1465 lines
52 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { mount } from "@vue/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
class ResizeObserver {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
}
|
|
global.ResizeObserver = ResizeObserver;
|
|
|
|
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
|
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
import { CUSTOMER_RULE_DEFINITIONS } from "@/features/customer/customerRuleRegistry.js";
|
|
import { createTestI18n } from "./helpers/mountWithApp.js";
|
|
|
|
const swalFireMock = vi.hoisted(() => vi.fn(() => Promise.resolve({ isConfirmed: true, value: "Needs review" })));
|
|
const getUserIdMock = vi.hoisted(() => vi.fn(() => Promise.resolve({ data: { data: { user_id: 777 } } })));
|
|
const listCustomerAttributesMock = vi.hoisted(() => vi.fn(() => Promise.resolve({ data: { data: [] } })));
|
|
const createCustomerAttributeMock = vi.hoisted(() => vi.fn(() => Promise.resolve({ data: { data: null } })));
|
|
const deleteCustomerAttributeMock = vi.hoisted(() => vi.fn(() => Promise.resolve({ data: { data: null } })));
|
|
|
|
vi.mock("sweetalert2", () => ({
|
|
default: {
|
|
fire: swalFireMock,
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/features/customer/customerAttributeService.js", () => ({
|
|
buildCustomerAttributeTargetPayload: (target = {}) => {
|
|
const userId = Number.parseInt(String(target.userId ?? target.user_id ?? ""), 10);
|
|
if (Number.isInteger(userId) && userId > 0) {
|
|
return { user_id: userId };
|
|
}
|
|
|
|
const customerNumber = Number.parseInt(String(target.customerNumber ?? target.customer_number ?? ""), 10);
|
|
if (Number.isInteger(customerNumber) && customerNumber > 0) {
|
|
return { customer_number: customerNumber };
|
|
}
|
|
|
|
return {};
|
|
},
|
|
createCustomerAttribute: createCustomerAttributeMock,
|
|
deleteCustomerAttribute: deleteCustomerAttributeMock,
|
|
extractCustomerAttributesData: (response) => response?.data?.data ?? response?.data ?? response ?? [],
|
|
listCustomerAttributes: listCustomerAttributesMock,
|
|
}));
|
|
|
|
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),
|
|
hasPermission: vi.fn(() => true),
|
|
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: {
|
|
resendBookingCompletionConfirmation: vi.fn(() => Promise.resolve()),
|
|
resendBookingConfirmation: 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()),
|
|
},
|
|
},
|
|
department_gates: {
|
|
showCreateObjectForm: vi.fn(() => Promise.resolve()),
|
|
},
|
|
department_relays: {
|
|
showCreateObjectForm: vi.fn(() => Promise.resolve()),
|
|
},
|
|
orders: {
|
|
meta: {
|
|
labels: {
|
|
single: "Order",
|
|
},
|
|
},
|
|
functions: {
|
|
fetchAttachments: vi.fn(() => Promise.resolve([])),
|
|
assignDraftCustomer: vi.fn(() => Promise.resolve()),
|
|
get_department_id: vi.fn(() => Promise.resolve(1)),
|
|
removeAttachment: vi.fn(() => Promise.resolve({ success: true })),
|
|
resendWashCertificate: vi.fn(() => Promise.resolve()),
|
|
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",
|
|
},
|
|
add: vi.fn(() => Promise.resolve({ data: { data: { id: 901 } } })),
|
|
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 CUSTOMER_RULE_TRANSLATION_KEYS = CUSTOMER_RULE_DEFINITIONS.flatMap((rule) => [
|
|
rule.labelKey,
|
|
rule.descriptionKey,
|
|
]);
|
|
|
|
const SETTINGS_WHEEL_TRANSLATION_KEYS = [
|
|
"admin.pos.settings_wheel.associate_order",
|
|
"admin.pos.settings_wheel.accept_self_serve_wash",
|
|
"admin.pos.settings_wheel.accept_self_serve_wash_confirm",
|
|
"admin.pos.settings_wheel.accept_self_serve_wash_success",
|
|
"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.email_notifications_section",
|
|
"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.customer_section",
|
|
"admin.pos.settings_wheel.gates_section",
|
|
"admin.pos.settings_wheel.open_attached_file",
|
|
"admin.pos.settings_wheel.open_fleet_landing",
|
|
"admin.pos.settings_wheel.open_gateways_tab",
|
|
"admin.pos.settings_wheel.open_gates_tab",
|
|
"admin.pos.settings_wheel.open_hardware_workspace_lanes",
|
|
"admin.pos.settings_wheel.open_legacy_gates",
|
|
"admin.pos.settings_wheel.open_legacy_relays",
|
|
"admin.pos.settings_wheel.open_legacy_self_serve",
|
|
"admin.pos.settings_wheel.open_primary_gateway",
|
|
"admin.pos.settings_wheel.rules_section",
|
|
"admin.pos.settings_wheel.open_relays_tab",
|
|
"admin.pos.settings_wheel.open_studio",
|
|
"admin.pos.settings_wheel.password_changed",
|
|
"admin.pos.settings_wheel.password_changed_text",
|
|
"admin.pos.settings_wheel.please_enter_password",
|
|
"admin.pos.settings_wheel.relays_section",
|
|
"admin.pos.settings_wheel.resend_booking_completion_confirmation",
|
|
"admin.pos.settings_wheel.resend_booking_completion_confirmation_error",
|
|
"admin.pos.settings_wheel.resend_booking_completion_confirmation_success",
|
|
"admin.pos.settings_wheel.resend_booking_confirmation",
|
|
"admin.pos.settings_wheel.resend_booking_confirmation_error",
|
|
"admin.pos.settings_wheel.resend_booking_confirmation_success",
|
|
"admin.pos.settings_wheel.resend_wash_certificate",
|
|
"admin.pos.settings_wheel.resend_wash_certificate_error",
|
|
"admin.pos.settings_wheel.resend_wash_certificate_success",
|
|
"admin.pos.settings_wheel.scan_qr_to_login",
|
|
"admin.pos.settings_wheel.self_serve_customer",
|
|
"admin.pos.settings_wheel.self_serve_driver",
|
|
"admin.pos.settings_wheel.self_serve_elapsed",
|
|
"admin.pos.settings_wheel.self_serve_wash_attachment",
|
|
"admin.pos.settings_wheel.self_serve_wash_attachment_for_customer",
|
|
"admin.pos.settings_wheel.self_serve_studio_section",
|
|
"admin.pos.settings_wheel.shortcuts_section",
|
|
"admin.pos.settings_wheel.shortcut_orders",
|
|
"admin.pos.settings_wheel.shortcut_other",
|
|
"admin.pos.settings_wheel.shortcut_overview",
|
|
"admin.pos.settings_wheel.shortcut_pricing",
|
|
"admin.pos.settings_wheel.shortcut_vehicles",
|
|
"admin.pos.settings_wheel.show_customer",
|
|
"admin.pos.settings_wheel.show_qr_code",
|
|
"admin.pos.settings_wheel.add_gate",
|
|
"admin.pos.settings_wheel.add_flag",
|
|
"admin.pos.settings_wheel.add_flag_customer",
|
|
"admin.pos.settings_wheel.add_flag_invoice_collection",
|
|
"admin.pos.settings_wheel.add_flag_order",
|
|
"admin.pos.settings_wheel.add_flag_order_customer",
|
|
"admin.pos.settings_wheel.add_flag_order_item",
|
|
"admin.pos.settings_wheel.add_flag_order_item_notes",
|
|
"admin.pos.settings_wheel.add_flag_order_item_price",
|
|
"admin.pos.settings_wheel.add_flag_order_item_quantity",
|
|
"admin.pos.settings_wheel.add_flag_order_item_reference",
|
|
"admin.pos.settings_wheel.add_flag_order_notes",
|
|
"admin.pos.settings_wheel.add_flag_order_po",
|
|
"admin.pos.settings_wheel.add_flag_order_reference",
|
|
"admin.pos.settings_wheel.add_flag_reason_placeholder",
|
|
"admin.pos.settings_wheel.add_flag_reason_required",
|
|
"admin.pos.settings_wheel.add_flag_target",
|
|
"admin.pos.settings_wheel.add_relay",
|
|
"admin.pos.settings_wheel.flags_section",
|
|
"admin.pos.settings_wheel.gateways_section",
|
|
"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",
|
|
"admin.pos.drafts_assignment.error",
|
|
"admin.pos.drafts_assignment.invoice_collection_empty",
|
|
"admin.pos.drafts_assignment.new_collection_description",
|
|
"admin.pos.drafts_assignment.new_collection_name",
|
|
"common.cancel",
|
|
"global.delete",
|
|
"global.download",
|
|
"global.loading",
|
|
"global.preview",
|
|
"global.print",
|
|
"common.confirm",
|
|
"pos.license_plate",
|
|
"superuser.department_lane.force_disable_machine",
|
|
"superuser.department_lane.force_enable_machine",
|
|
...CUSTOMER_RULE_TRANSLATION_KEYS,
|
|
];
|
|
|
|
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 setCompactViewport = () => {
|
|
Object.defineProperty(window, "innerWidth", {
|
|
configurable: true,
|
|
writable: true,
|
|
value: 1280,
|
|
});
|
|
Object.defineProperty(window, "innerHeight", {
|
|
configurable: true,
|
|
writable: true,
|
|
value: 900,
|
|
});
|
|
window.matchMedia = vi.fn().mockImplementation(() => ({
|
|
matches: false,
|
|
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 mountFlatDropdownButton = (props = {}) => {
|
|
setCompactViewport();
|
|
|
|
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();
|
|
listCustomerAttributesMock.mockReset();
|
|
listCustomerAttributesMock.mockResolvedValue({
|
|
data: {
|
|
data: [{ id: 1, customer_number: 123456, attribute: "invoiceAllOrdersIndividually" }],
|
|
},
|
|
});
|
|
createCustomerAttributeMock.mockReset();
|
|
createCustomerAttributeMock.mockResolvedValue({ data: { data: null } });
|
|
deleteCustomerAttributeMock.mockReset();
|
|
deleteCustomerAttributeMock.mockResolvedValue({ data: { data: null } });
|
|
swalFireMock.mockReset();
|
|
swalFireMock.mockResolvedValue({ isConfirmed: true, value: "Needs review" });
|
|
SessionUser.request.mockReset();
|
|
SessionUser.request.mockResolvedValue({ data: { data: { summary: { primary_gateway: { id: 701 } } } } });
|
|
SessionUser.hasPermission.mockReset();
|
|
SessionUser.hasPermission.mockImplementation((permission) =>
|
|
["add_customer_attribute", "delete_customer_attribute", "list_customer_attributes", "superuser"].includes(
|
|
permission
|
|
)
|
|
);
|
|
SessionUser.objects.orders.functions.showChangeInvoiceCollectionForm.mockClear();
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockReset();
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([]);
|
|
SessionUser.objects.orders.functions.assignDraftCustomer.mockReset();
|
|
SessionUser.objects.orders.functions.assignDraftCustomer.mockResolvedValue({ data: { data: null } });
|
|
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 });
|
|
SessionUser.objects.orders.functions.resendWashCertificate.mockReset();
|
|
SessionUser.objects.orders.functions.resendWashCertificate.mockResolvedValue({});
|
|
SessionUser.objects.order_bookings.functions.resendBookingConfirmation.mockReset();
|
|
SessionUser.objects.order_bookings.functions.resendBookingConfirmation.mockResolvedValue({});
|
|
SessionUser.objects.order_bookings.functions.resendBookingCompletionConfirmation.mockReset();
|
|
SessionUser.objects.order_bookings.functions.resendBookingCompletionConfirmation.mockResolvedValue({});
|
|
SessionUser.objects.department_gates.showCreateObjectForm.mockClear();
|
|
SessionUser.objects.department_relays.showCreateObjectForm.mockClear();
|
|
SessionUser.objects.collectedOrderInvoices.add.mockReset();
|
|
SessionUser.objects.collectedOrderInvoices.add.mockResolvedValue({ data: { data: { id: 901 } } });
|
|
SessionUser.functions.redirectTo.department.mockClear();
|
|
SessionUser.functions.redirectTo.superUser.mockClear();
|
|
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("marks the trigger as active while its dropdown is open", async () => {
|
|
const wrapper = mountFlatDropdownButton();
|
|
await flushMicrotasks();
|
|
|
|
const trigger = wrapper.get(".action-settings-wheel-trigger");
|
|
expect(trigger.classes()).not.toContain("action-settings-wheel-trigger--active");
|
|
expect(trigger.attributes("aria-expanded")).toBe("false");
|
|
|
|
await trigger.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(trigger.classes()).toContain("action-settings-wheel-trigger--active");
|
|
expect(trigger.attributes("aria-expanded")).toBe("true");
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it("keeps only the latest opened action settings wheel active", async () => {
|
|
setCompactViewport();
|
|
|
|
const wrapper = mount(
|
|
{
|
|
components: { ActionSettingsWheelButton },
|
|
template: `
|
|
<div>
|
|
<ActionSettingsWheelButton :order_id="41" />
|
|
<ActionSettingsWheelButton :order_id="42" />
|
|
</div>
|
|
`,
|
|
},
|
|
{
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
await flushMicrotasks();
|
|
const triggers = wrapper.findAll(".action-settings-wheel-trigger");
|
|
|
|
await triggers[0].trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(triggers[0].attributes("aria-expanded")).toBe("true");
|
|
expect(triggers[0].classes()).toContain("action-settings-wheel-trigger--active");
|
|
|
|
await triggers[1].trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(triggers[0].attributes("aria-expanded")).toBe("false");
|
|
expect(triggers[0].classes()).not.toContain("action-settings-wheel-trigger--active");
|
|
expect(triggers[1].attributes("aria-expanded")).toBe("true");
|
|
expect(triggers[1].classes()).toContain("action-settings-wheel-trigger--active");
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
it("resolves customer user id and reloads customer rules only when the target changes in direct mode", 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);
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledTimes(1);
|
|
expect(listCustomerAttributesMock).toHaveBeenLastCalledWith({ customer_number: 123456 });
|
|
|
|
await wrapper.setProps({ icon: "fas fa-ellipsis-v" });
|
|
await flushMicrotasks();
|
|
expect(getUserIdMock).toHaveBeenCalledTimes(1);
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledTimes(1);
|
|
|
|
await wrapper.setProps({ customer_number: 654321 });
|
|
await flushMicrotasks();
|
|
expect(getUserIdMock).toHaveBeenCalledTimes(2);
|
|
expect(getUserIdMock).toHaveBeenLastCalledWith(654321);
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledTimes(2);
|
|
expect(listCustomerAttributesMock).toHaveBeenLastCalledWith({ customer_number: 654321 });
|
|
});
|
|
|
|
it("does not perform customer lookup when explicit user id is provided and uses the user target for rules", 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();
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledTimes(1);
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledWith({ user_id: 77 });
|
|
});
|
|
|
|
it("lazy loads customer rules when a flat dropdown opens and toggles them by customer number", async () => {
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: null,
|
|
customer_number: 123456,
|
|
reg_1: null,
|
|
reg_2: null,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(getUserIdMock).toHaveBeenCalledTimes(1);
|
|
expect(listCustomerAttributesMock).not.toHaveBeenCalled();
|
|
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledTimes(1);
|
|
expect(listCustomerAttributesMock).toHaveBeenCalledWith({ customer_number: 123456 });
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.customer_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.rules_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.shortcuts_section");
|
|
|
|
const ruleToggle = wrapper.get(
|
|
'[data-testid="action-settings-wheel-toggle-customer-rule-invoiceAllOrdersIndividually"]'
|
|
);
|
|
expect(ruleToggle.attributes("aria-pressed")).toBe("true");
|
|
|
|
await ruleToggle.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(deleteCustomerAttributeMock).toHaveBeenCalledTimes(1);
|
|
expect(deleteCustomerAttributeMock).toHaveBeenCalledWith(
|
|
{ customer_number: 123456 },
|
|
"invoiceAllOrdersIndividually"
|
|
);
|
|
expect(
|
|
wrapper
|
|
.get('[data-testid="action-settings-wheel-toggle-customer-rule-invoiceAllOrdersIndividually"]')
|
|
.attributes("aria-pressed")
|
|
).toBe("false");
|
|
});
|
|
|
|
it("renders customer, rules, and shortcuts sections in direct mode and enables disabled rules", async () => {
|
|
listCustomerAttributesMock.mockResolvedValueOnce({ data: { data: [] } });
|
|
|
|
const wrapper = mount(ActionSettingsWheelButton, {
|
|
props: {
|
|
displayActionsDirectly: true,
|
|
order_id: null,
|
|
customer_number: 123456,
|
|
reg_1: null,
|
|
reg_2: null,
|
|
},
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
CustomerModal: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.customer_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.rules_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.shortcuts_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.shortcut_overview");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.shortcut_orders");
|
|
|
|
const ruleToggle = wrapper.get(
|
|
'[data-testid="action-settings-wheel-toggle-customer-rule-invoiceAllOrdersIndividually"]'
|
|
);
|
|
expect(ruleToggle.attributes("aria-pressed")).toBe("false");
|
|
|
|
await ruleToggle.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(createCustomerAttributeMock).toHaveBeenCalledTimes(1);
|
|
expect(createCustomerAttributeMock).toHaveBeenCalledWith(
|
|
{ customer_number: 123456 },
|
|
"invoiceAllOrdersIndividually"
|
|
);
|
|
expect(
|
|
wrapper
|
|
.get('[data-testid="action-settings-wheel-toggle-customer-rule-invoiceAllOrdersIndividually"]')
|
|
.attributes("aria-pressed")
|
|
).toBe("true");
|
|
});
|
|
|
|
it("renders customer, rules, and shortcuts sections in desktop flyout mode", async () => {
|
|
const geometrySpy = mockMenuGeometry();
|
|
const wrapper = mountDesktopFlyoutButton({
|
|
order_id: null,
|
|
customer_number: 123456,
|
|
reg_1: null,
|
|
reg_2: null,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-section-customer"]').exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-section-rules"]').exists()).toBe(true);
|
|
expect(wrapper.find('[data-testid="action-settings-wheel-section-shortcuts"]').exists()).toBe(true);
|
|
|
|
await wrapper.get('[data-testid="action-settings-wheel-section-rules"]').trigger("mouseenter");
|
|
await flushMicrotasks();
|
|
|
|
const ruleToggle = wrapper.get(
|
|
'[data-testid="action-settings-wheel-toggle-customer-rule-invoiceAllOrdersIndividually"]'
|
|
);
|
|
expect(ruleToggle.attributes("aria-pressed")).toBe("true");
|
|
|
|
await ruleToggle.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(deleteCustomerAttributeMock).toHaveBeenCalledWith(
|
|
{ customer_number: 123456 },
|
|
"invoiceAllOrdersIndividually"
|
|
);
|
|
|
|
geometrySpy.mockRestore();
|
|
});
|
|
|
|
it("does not render standalone booking completion for unlinked bookings", async () => {
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: null,
|
|
order_booking_id: 123,
|
|
department_id: 1,
|
|
customer_number: 123456,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.booking");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.view_booking_new_tab");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.mark_as_completed");
|
|
});
|
|
|
|
it("hides email notifications when the order has no booking or wash certificate", async () => {
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: 42,
|
|
order_booking_id: null,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.email_notifications_section");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_booking_confirmation");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_wash_certificate");
|
|
});
|
|
|
|
it("shows booking email notification actions for booking-only orders", async () => {
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: 42,
|
|
order_booking_id: 123,
|
|
department_id: 1,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.email_notifications_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.resend_booking_confirmation");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_booking_completion_confirmation");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_wash_certificate");
|
|
|
|
const button = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) => buttonWrapper.text().includes("admin.pos.settings_wheel.resend_booking_confirmation"));
|
|
|
|
expect(button).toBeTruthy();
|
|
await button.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.objects.order_bookings.functions.resendBookingConfirmation).toHaveBeenCalledWith(123);
|
|
expect(swalFireMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
title: "admin.pos.settings_wheel.resend_booking_confirmation_success",
|
|
icon: "success",
|
|
})
|
|
);
|
|
});
|
|
|
|
it("shows booking completion email action when a booking has a wash certificate", async () => {
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([
|
|
{
|
|
id: 301,
|
|
content: {
|
|
document: "wash_certificate_42.pdf",
|
|
other: "WASH_CERTIFICATE",
|
|
},
|
|
},
|
|
]);
|
|
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: 42,
|
|
order_booking_id: 123,
|
|
department_id: 1,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.email_notifications_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.resend_booking_confirmation");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.resend_booking_completion_confirmation");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_wash_certificate");
|
|
|
|
const button = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) =>
|
|
buttonWrapper.text().includes("admin.pos.settings_wheel.resend_booking_completion_confirmation")
|
|
);
|
|
|
|
expect(button).toBeTruthy();
|
|
await button.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.objects.order_bookings.functions.resendBookingCompletionConfirmation).toHaveBeenCalledWith(123);
|
|
expect(swalFireMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
title: "admin.pos.settings_wheel.resend_booking_completion_confirmation_success",
|
|
icon: "success",
|
|
})
|
|
);
|
|
});
|
|
|
|
it("shows wash certificate email action when an order has a certificate without booking", async () => {
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([
|
|
{
|
|
id: 302,
|
|
content: {
|
|
document: "wash_certificate_42_302.pdf",
|
|
other: "wash_certificate_42_302.pdf",
|
|
},
|
|
},
|
|
]);
|
|
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: 42,
|
|
order_booking_id: null,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.email_notifications_section");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_booking_confirmation");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.resend_booking_completion_confirmation");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.resend_wash_certificate");
|
|
|
|
const button = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) => buttonWrapper.text().includes("admin.pos.settings_wheel.resend_wash_certificate"));
|
|
|
|
expect(button).toBeTruthy();
|
|
await button.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.objects.orders.functions.resendWashCertificate).toHaveBeenCalledWith(42);
|
|
expect(swalFireMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
title: "admin.pos.settings_wheel.resend_wash_certificate_success",
|
|
icon: "success",
|
|
})
|
|
);
|
|
});
|
|
|
|
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, auto-renders previews on hover, and opens the full preview action explicitly", 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 windowOpenSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
|
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(previewPanel.find("img").exists()).toBe(false);
|
|
expect(SessionUser.objects.orders.functions.downloadAttachment).toHaveBeenCalledWith(42, 301, false);
|
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
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 wrapper.get('[data-testid="action-settings-wheel-attachment-action-preview-301"]').trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(windowOpenSpy).toHaveBeenCalledTimes(1);
|
|
|
|
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);
|
|
|
|
wrapper.unmount();
|
|
windowOpenSpy.mockRestore();
|
|
geometrySpy.mockRestore();
|
|
});
|
|
|
|
it("renders self-serve wash object attachments as readable text", async () => {
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([
|
|
{
|
|
id: 401,
|
|
content: {
|
|
image: null,
|
|
document: null,
|
|
other: {
|
|
type: "SELF_SERVE_WASH",
|
|
customer_number: 123456,
|
|
subuser_id: 77,
|
|
subuser: {
|
|
name: "Driver One",
|
|
username: "driver-one",
|
|
},
|
|
license_plate: "AB12345",
|
|
elapsed_wash_time_seconds: 601,
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
const geometrySpy = mockMenuGeometry();
|
|
const wrapper = mountDesktopFlyoutButton({
|
|
customer_number: 999999,
|
|
});
|
|
|
|
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();
|
|
|
|
const attachmentRow = wrapper.get('[data-testid="action-settings-wheel-attachment-row-401"]');
|
|
expect(attachmentRow.text()).toContain("admin.pos.settings_wheel.self_serve_wash_attachment_for_customer");
|
|
expect(attachmentRow.text()).not.toContain("[object Object]");
|
|
|
|
await attachmentRow.trigger("mouseenter");
|
|
await flushMicrotasks();
|
|
|
|
const previewText = wrapper.get('[data-testid="action-settings-wheel-attachment-panel"]').text();
|
|
expect(previewText).toContain("admin.pos.settings_wheel.self_serve_wash_attachment");
|
|
expect(previewText).toContain("admin.pos.settings_wheel.self_serve_customer: #123456");
|
|
expect(previewText).toContain("admin.pos.settings_wheel.self_serve_driver: Driver One");
|
|
expect(previewText).toContain("pos.license_plate: AB12345");
|
|
expect(previewText).toContain("admin.pos.settings_wheel.self_serve_elapsed: 11 min");
|
|
expect(previewText).not.toContain("[object Object]");
|
|
|
|
wrapper.unmount();
|
|
geometrySpy.mockRestore();
|
|
});
|
|
|
|
it("accepts self-serve wash drafts by assigning the original customer and collection", async () => {
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([
|
|
{
|
|
id: 402,
|
|
content: {
|
|
other: {
|
|
type: "SELF_SERVE_WASH",
|
|
customer_number: 123456,
|
|
draft_customer_number: 999999,
|
|
subuser_id: 77,
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
SessionUser.request.mockResolvedValueOnce({
|
|
data: {
|
|
data: [{ id: 888, closed_at: null }],
|
|
},
|
|
});
|
|
|
|
const refreshFunction = vi.fn(() => Promise.resolve());
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: 42,
|
|
customer_number: 999999,
|
|
department_id: 5,
|
|
refreshFunction,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
const acceptButton = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) => buttonWrapper.text().includes("admin.pos.settings_wheel.accept_self_serve_wash"));
|
|
|
|
expect(acceptButton).toBeTruthy();
|
|
await acceptButton.trigger("click");
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.request).toHaveBeenCalledWith("/collected-invoices", "GET", {
|
|
page: 1,
|
|
limit: 100,
|
|
order: "closed_at:asc",
|
|
filters: "customer_number:123456,booked_invoice_id:is_null",
|
|
});
|
|
expect(SessionUser.objects.collectedOrderInvoices.add).not.toHaveBeenCalled();
|
|
expect(SessionUser.objects.orders.functions.assignDraftCustomer).toHaveBeenCalledWith({
|
|
order_id: 42,
|
|
customer_id: 123456,
|
|
invoice_collection_id: 888,
|
|
department_id: 5,
|
|
recalculate_prices: true,
|
|
});
|
|
expect(refreshFunction).toHaveBeenCalledTimes(1);
|
|
expect(swalFireMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
title: "admin.pos.settings_wheel.accept_self_serve_wash",
|
|
icon: "question",
|
|
})
|
|
);
|
|
expect(swalFireMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
title: "admin.pos.settings_wheel.accept_self_serve_wash_success",
|
|
icon: "success",
|
|
})
|
|
);
|
|
|
|
wrapper.unmount();
|
|
});
|
|
|
|
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();
|
|
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();
|
|
});
|
|
|
|
it("prints document attachments from the resolved preview source instead of a blank wrapper window", async () => {
|
|
SessionUser.objects.orders.functions.fetchAttachments.mockResolvedValue([
|
|
{
|
|
id: 301,
|
|
content: {
|
|
document: "safety-seal.pdf",
|
|
other: "safety-seal.pdf",
|
|
},
|
|
},
|
|
]);
|
|
SessionUser.objects.orders.functions.downloadAttachment.mockResolvedValue(
|
|
"https://cdn.example.test/orders/42/attachments/301"
|
|
);
|
|
|
|
const printWindow = {
|
|
focus: vi.fn(),
|
|
print: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
};
|
|
const windowOpenSpy = vi.spyOn(window, "open").mockImplementation(() => printWindow);
|
|
const geometrySpy = mockMenuGeometry();
|
|
const wrapper = mountDesktopFlyoutButton();
|
|
|
|
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();
|
|
await flushMicrotasks();
|
|
|
|
await wrapper.get('[data-testid="action-settings-wheel-attachment-action-print-301"]').trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(windowOpenSpy).toHaveBeenCalledWith(expect.stringMatching(/^blob:/), "_blank", "noopener,noreferrer");
|
|
expect(printWindow.addEventListener).toHaveBeenCalledWith("load", expect.any(Function), { once: true });
|
|
|
|
windowOpenSpy.mockRestore();
|
|
geometrySpy.mockRestore();
|
|
});
|
|
|
|
it("adds department hardware sections for a lane and routes the new actions correctly", async () => {
|
|
const geometrySpy = mockMenuGeometry();
|
|
const refreshFunction = vi.fn(() => Promise.resolve());
|
|
const wrapper = mountDesktopFlyoutButton({
|
|
order_id: null,
|
|
department_id: 1,
|
|
department_lane_id: 8,
|
|
refreshFunction,
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
const sectionLabels = wrapper
|
|
.findAll(".action-settings-wheel-section-trigger__label")
|
|
.map((section) => section.text())
|
|
.filter(Boolean);
|
|
|
|
expect(sectionLabels).toEqual([
|
|
"Lane",
|
|
"admin.pos.settings_wheel.self_serve_studio_section",
|
|
"admin.pos.settings_wheel.gates_section",
|
|
"admin.pos.settings_wheel.relays_section",
|
|
"admin.pos.settings_wheel.gateways_section",
|
|
]);
|
|
|
|
const reopenDropdown = async () => {
|
|
if (!wrapper.find('[data-testid="action-settings-wheel-sections"]').exists()) {
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
}
|
|
};
|
|
const getActionButton = async (label) => {
|
|
await reopenDropdown();
|
|
const button = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) => buttonWrapper.text().includes(label));
|
|
expect(button).toBeTruthy();
|
|
return button;
|
|
};
|
|
|
|
await (await getActionButton("admin.pos.settings_wheel.open_studio")).trigger("click");
|
|
expect(SessionUser.functions.redirectTo.department).toHaveBeenCalledWith(1, "modules/self-serve/studio", true);
|
|
|
|
await (await getActionButton("admin.pos.settings_wheel.add_gate")).trigger("click");
|
|
expect(SessionUser.objects.department_gates.showCreateObjectForm).toHaveBeenCalledWith(expect.any(Function), {
|
|
department: 1,
|
|
});
|
|
|
|
await (await getActionButton("admin.pos.settings_wheel.add_relay")).trigger("click");
|
|
expect(SessionUser.objects.department_relays.showCreateObjectForm).toHaveBeenCalledWith(expect.any(Function), {
|
|
department: 1,
|
|
});
|
|
|
|
await (await getActionButton("admin.pos.settings_wheel.open_primary_gateway")).trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(SessionUser.request).toHaveBeenCalledWith("/modules/edge-gateways/workspace/departments/1", "GET");
|
|
expect(SessionUser.functions.redirectTo.superUser).toHaveBeenCalledWith(
|
|
"/selfserve/edge-agents/701/overview",
|
|
true
|
|
);
|
|
|
|
wrapper.unmount();
|
|
geometrySpy.mockRestore();
|
|
});
|
|
|
|
it("creates invoice period flags for supported order and order item targets", async () => {
|
|
SessionUser.hasPermission.mockImplementation((permission) =>
|
|
[
|
|
"add_customer_attribute",
|
|
"delete_customer_attribute",
|
|
"list_customer_attributes",
|
|
"add_invoice_period_flag",
|
|
"superuser",
|
|
].includes(permission)
|
|
);
|
|
SessionUser.request.mockResolvedValueOnce({
|
|
data: {
|
|
data: {
|
|
id: 901,
|
|
source: "manual",
|
|
target_type: "order",
|
|
target_id: 42,
|
|
reason: "Needs review",
|
|
},
|
|
},
|
|
});
|
|
|
|
const wrapper = mountFlatDropdownButton({
|
|
order_id: 42,
|
|
customer_number: 123456,
|
|
flagTarget: {
|
|
target_type: "order_item",
|
|
target_id: 77,
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.flags_section");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.add_flag_order");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.add_flag_order_item_price");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.add_flag_customer");
|
|
|
|
const addOrderFlagButton = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) => buttonWrapper.text().includes("admin.pos.settings_wheel.add_flag_order"));
|
|
|
|
expect(addOrderFlagButton).toBeTruthy();
|
|
await addOrderFlagButton.trigger("click");
|
|
await flushMicrotasks();
|
|
|
|
expect(swalFireMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
input: "textarea",
|
|
inputPlaceholder: "admin.pos.settings_wheel.add_flag_reason_placeholder",
|
|
})
|
|
);
|
|
expect(SessionUser.request).toHaveBeenCalledWith("/superuser/invoicing/period/flags", "POST", {
|
|
target_type: "order",
|
|
target_id: 42,
|
|
reason: "Needs review",
|
|
});
|
|
expect(wrapper.emitted("flagCreated")?.[0]?.[0]).toEqual(
|
|
expect.objectContaining({
|
|
id: 901,
|
|
source: "manual",
|
|
})
|
|
);
|
|
});
|
|
|
|
it("renders customer flag creation as a red bottom section with a customer label", async () => {
|
|
SessionUser.hasPermission.mockImplementation((permission) =>
|
|
[
|
|
"add_customer_attribute",
|
|
"delete_customer_attribute",
|
|
"list_customer_attributes",
|
|
"add_invoice_period_flag",
|
|
"superuser",
|
|
].includes(permission)
|
|
);
|
|
|
|
const geometrySpy = mockMenuGeometry();
|
|
const wrapper = mountDesktopFlyoutButton({
|
|
order_id: null,
|
|
customer_number: 123456,
|
|
reg_1: null,
|
|
reg_2: null,
|
|
flagTarget: {
|
|
target_type: "customer",
|
|
target_id: 123456,
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
await wrapper.find(".dropdown-trigger button").trigger("click");
|
|
await flushMicrotasks();
|
|
await flushMicrotasks();
|
|
|
|
const sectionIds = wrapper
|
|
.findAll('[data-testid^="action-settings-wheel-section-"]')
|
|
.map((section) => section.attributes("data-testid"));
|
|
|
|
expect(sectionIds.at(-1)).toBe("action-settings-wheel-section-invoice-period-flags");
|
|
expect(wrapper.text()).toContain("admin.pos.settings_wheel.add_flag_customer");
|
|
expect(wrapper.text()).not.toContain("admin.pos.settings_wheel.add_flag_target");
|
|
|
|
const addCustomerFlagButton = wrapper
|
|
.findAll("button.dropdown-item-action")
|
|
.find((buttonWrapper) => buttonWrapper.text().includes("admin.pos.settings_wheel.add_flag_customer"));
|
|
|
|
expect(addCustomerFlagButton).toBeTruthy();
|
|
expect(addCustomerFlagButton.find(".has-text-danger").exists()).toBe(true);
|
|
expect(addCustomerFlagButton.find(".has-text-warning").exists()).toBe(false);
|
|
|
|
geometrySpy.mockRestore();
|
|
});
|
|
});
|