Files
pleno-vue/tests/unit/system-search-support-registry.spec.js
T

458 lines
16 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
SYSTEM_SEARCH_ENTITY_TYPES,
SYSTEM_SEARCH_SUPPORT_REGISTRY,
buildSystemSearchViewModel,
resolveSystemSearchNavigationTarget,
} from "@/components/viewport/page/headers/menu/systemSearchSupport.ts";
const superuserContext = {
canAccessSuperUser: true,
canAccessAdmin: true,
canAccessUser: true,
canAccessDepartment: () => true,
};
const adminContext = {
canAccessSuperUser: false,
canAccessAdmin: true,
canAccessUser: false,
canAccessDepartment: (departmentId) => departmentId === 4,
};
const userContext = {
canAccessSuperUser: false,
canAccessAdmin: false,
canAccessUser: true,
canAccessDepartment: () => false,
};
const restrictedContext = {
canAccessSuperUser: false,
canAccessAdmin: false,
canAccessUser: false,
canAccessDepartment: () => false,
};
const row = (entityType, overrides = {}) => ({
entity_type: entityType,
entity_id: "11",
title: "11",
score: 120,
...overrides,
});
describe("system search support registry coverage", () => {
it("contains exactly all 59 known entity types", () => {
expect(SYSTEM_SEARCH_ENTITY_TYPES).toHaveLength(59);
expect(new Set(SYSTEM_SEARCH_ENTITY_TYPES).size).toBe(59);
const registryKeys = Object.keys(SYSTEM_SEARCH_SUPPORT_REGISTRY).sort();
const entityKeys = [...SYSTEM_SEARCH_ENTITY_TYPES].sort();
expect(registryKeys).toEqual(entityKeys);
});
it("provides render and navigation strategy for every type", () => {
for (const entityType of SYSTEM_SEARCH_ENTITY_TYPES) {
const definition = SYSTEM_SEARCH_SUPPORT_REGISTRY[entityType];
expect(definition).toBeTruthy();
expect(["deep-link", "module", "generic"]).toContain(definition.navigationStrategy);
expect(definition.titleKeys.length).toBeGreaterThan(0);
expect(definition.descriptionKeys.length).toBeGreaterThan(0);
expect(definition.keyFieldSelectors.length).toBeGreaterThan(0);
const vm = buildSystemSearchViewModel(row(entityType, { payload: null }));
expect(typeof vm.title).toBe("string");
expect(Array.isArray(vm.badges)).toBe(true);
expect(Array.isArray(vm.keyFields)).toBe(true);
const nav = resolveSystemSearchNavigationTarget(row(entityType), superuserContext);
expect(nav).not.toBeNull();
}
});
});
describe("system search navigation resolver behavior", () => {
it("deep-links order details for admin/superuser with department context", () => {
const nav = resolveSystemSearchNavigationTarget(
row("orders", {
entity_id: "34455",
payload: { id: 34455, department: "4" },
}),
adminContext
);
expect(nav.strategy).toBe("deep-link");
expect(nav.to.path).toBe("/admin/4/modules/pos/orders/34455");
});
it("opens user order detail for user role", () => {
const nav = resolveSystemSearchNavigationTarget(
row("orders", { entity_id: "6078", payload: { id: 6078 } }),
userContext
);
expect(nav.to.path).toBe("/user/orders/6078");
});
it("falls back to generic detail when bookings are missing department context", () => {
const nav = resolveSystemSearchNavigationTarget(row("bookings", { payload: null }), adminContext);
expect(nav.strategy).toBe("generic");
expect(nav.to.path).toContain("/search/system/record/bookings/11");
});
it("routes department self-serve entities to module pages when department is known", () => {
const nav = resolveSystemSearchNavigationTarget(
row("department_selfserve_questions", {
payload: { id: "19", department: "4" },
}),
adminContext
);
expect(nav.strategy).toBe("module");
expect(nav.to.path).toBe("/admin/4/modules/self-serve/questions");
});
it("routes customers and vehicles based on user role context", () => {
const customerForSuper = resolveSystemSearchNavigationTarget(row("customers"), superuserContext);
const customerForUser = resolveSystemSearchNavigationTarget(row("customers"), userContext);
const vehicleForSuper = resolveSystemSearchNavigationTarget(
row("vehicles", { payload: { registration_number: "AB12345" } }),
superuserContext
);
const vehicleForUser = resolveSystemSearchNavigationTarget(
row("vehicles", { payload: { registration_number: "AB12345" } }),
userContext
);
expect(customerForSuper.to.path).toBe("/superuser/customers");
expect(customerForUser.to.path).toBe("/user/profile");
expect(vehicleForSuper.to.path).toBe("/superuser/vehicles/AB12345");
expect(vehicleForUser.to.path).toBe("/user/vehicles");
});
it("supports relation-target payload identifiers for orders, departments, and vehicles", () => {
const orderFromRelation = resolveSystemSearchNavigationTarget(
row("orders", {
entity_id: "34455",
payload: { order_id: "34455", department_id: 4 },
}),
adminContext
);
const departmentFromRelation = resolveSystemSearchNavigationTarget(
row("departments", {
entity_id: "4",
payload: { department_id: 4 },
}),
adminContext
);
const vehicleFromRelation = resolveSystemSearchNavigationTarget(
row("vehicles", {
entity_id: "AB12345",
payload: { reg: "AB12345" },
}),
superuserContext
);
expect(orderFromRelation.to.path).toBe("/admin/4/modules/pos/orders/34455");
expect(departmentFromRelation.to.path).toBe("/admin/4");
expect(vehicleFromRelation.to.path).toBe("/superuser/vehicles/AB12345");
});
it("routes xlvask and config/module types to their module pages with superuser access", () => {
const usage = resolveSystemSearchNavigationTarget(row("xlvask_usage_logs"), superuserContext);
const moduleConfig = resolveSystemSearchNavigationTarget(row("module_config"), superuserContext);
const fxrates = resolveSystemSearchNavigationTarget(row("fxratesapi_conversion_rates"), superuserContext);
expect(usage.to.path).toBe("/superuser/xlvask/usagelogs");
expect(moduleConfig.to.path).toBe("/superuser/configuration");
expect(fxrates.to.path).toBe("/superuser/configuration/fxratesapi");
});
it("delegates object item aliases to order-item routing behavior", () => {
const nav = resolveSystemSearchNavigationTarget(
row("objects", {
entity_id: "998",
payload: {
object_type: "item",
order_id: "34455",
department: "4",
},
}),
adminContext
);
expect(nav.strategy).toBe("deep-link");
expect(nav.to.path).toBe("/admin/4/modules/pos/orders/34455");
});
it("routes object discount/fixed-price aliases to user pricing routes", () => {
const withUser = resolveSystemSearchNavigationTarget(
row("objects", {
payload: {
object_type: "discount",
user_id: "91",
},
}),
superuserContext
);
const noUser = resolveSystemSearchNavigationTarget(
row("objects", {
payload: {
object_type: "fixed_pricing",
},
}),
superuserContext
);
expect(withUser.to.path).toBe("/superuser/users/91/pricing");
expect(noUser.to.path).toBe("/superuser/users");
});
it("routes customer object aliases to the linked superuser user page when customer_user_id is present", () => {
const nav = resolveSystemSearchNavigationTarget(
row("objects", {
entity_id: "1952",
payload: {
object_type: "customer",
id: 1952,
customer_number: 12345679,
display_name: "Demo",
customer_name: "(TEST) Pleno Vognmandsforretning",
customer_context: {
customer_number: 12345679,
user_id: 1953,
name: "(TEST) Pleno Vognmandsforretning",
},
customer_user_id: 1953,
},
}),
superuserContext
);
expect(nav.strategy).toBe("deep-link");
expect(nav.to.path).toBe("/superuser/users/1953");
});
it("routes noisy attachment-like object aliases to the linked order page", () => {
const nav = resolveSystemSearchNavigationTarget(
row("objects", {
payload: {
object_type: "`DOCUMENT`",
order_id: "34455",
department_id: 4,
},
}),
adminContext
);
expect(nav.strategy).toBe("deep-link");
expect(nav.to.path).toBe("/admin/4/modules/pos/orders/34455");
});
it("routes object question/task/condition aliases to self-serve module pages", () => {
const questionNav = resolveSystemSearchNavigationTarget(
row("objects", {
payload: { object_type: "question", department_id: 4 },
}),
adminContext
);
const taskNav = resolveSystemSearchNavigationTarget(
row("objects", {
payload: { object_type: "task", department_id: 4 },
}),
adminContext
);
const conditionNav = resolveSystemSearchNavigationTarget(
row("objects", {
payload: { object_type: "condition", department_id: 4 },
}),
adminContext
);
expect(questionNav.to.path).toBe("/admin/4/modules/self-serve/questions");
expect(taskNav.to.path).toBe("/admin/4/modules/self-serve/tasks");
expect(conditionNav.to.path).toBe("/admin/4/modules/self-serve/conditions");
});
it("keeps generic fallback when object subtype lacks required routing context", () => {
const nav = resolveSystemSearchNavigationTarget(
row("objects", {
payload: { object_type: "discount" },
}),
restrictedContext
);
expect(nav.strategy).toBe("generic");
expect(nav.to.path).toContain("/search/system/record/customer_discounts/11");
});
it("routes department variables by role and department context", () => {
const adminNav = resolveSystemSearchNavigationTarget(
row("department_variables", {
payload: { department_id: 4 },
}),
adminContext
);
const superNav = resolveSystemSearchNavigationTarget(
row("department_variables", {
payload: { department_id: 4 },
}),
superuserContext
);
expect(adminNav.to.path).toBe("/admin/4");
expect(superNav.to.path).toBe("/superuser/departments/4");
});
it("handles missing payload safely and still opens generic detail", () => {
const nav = resolveSystemSearchNavigationTarget(row("module_action_logs", { payload: null }), userContext);
expect(nav.strategy).toBe("generic");
expect(nav.to.path).toContain("/search/system/record/module_action_logs/11");
});
});
describe("system search card view-model behavior", () => {
it("creates readable key-field summaries for json-like payload strings", () => {
const vm = buildSystemSearchViewModel(
row("order_bookings", {
payload: {
items: '[{"id": 10, "name": "Indvendig vask Trailer"}, {"id": 41, "name": "Safety Seal"}]',
datetime: "2025-11-24 23:00:00",
reg_1: "FC2861",
},
})
);
const itemsField = vm.keyFields.find((entry) => entry.label === "Items");
expect(itemsField.value).toContain("2 items");
expect(itemsField.value).toContain("(complete)");
expect(itemsField.value).toContain("Indvendig vask Trailer");
expect(itemsField.value).toContain("Safety Seal");
expect(vm.parsedPayload.items).toBeTypeOf("object");
});
it("picks up nested booking items and reports missing details when content is incomplete", () => {
const vm = buildSystemSearchViewModel(
row("order_bookings", {
payload: {
content: {
items: [{ id: 10, name: "Bus" }, { id: 41, name: "Spot Free- Lastbil" }, { id: 91 }],
},
datetime: "2025-11-24 23:00:00",
reg_1: "FC2861",
},
})
);
const itemsField = vm.keyFields.find((entry) => entry.label === "Items");
expect(itemsField.value).toContain("3 items");
expect(itemsField.value).toContain("missing details: 1");
expect(itemsField.value).toContain("Bus");
expect(itemsField.value).toContain("Spot Free- Lastbil");
expect(itemsField.value).toContain("#91");
});
it("shows customer discount value and timestamps in key fields", () => {
const vm = buildSystemSearchViewModel(
row("customer_discounts", {
entity_id: "326",
title: "Discount #326",
payload: {
id: 326,
customer_number: 12345679,
discount: 12.5,
fixed_price: 350,
object_id: "global",
is_category: true,
created_at: "2026-03-12T09:15:00Z",
},
})
);
expect(vm.keyFields.some((entry) => entry.label === "Discount" && entry.value === "12.5%")).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Fixed price" && entry.value === "350")).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Target" && entry.value === "global")).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Category" && entry.value === "Yes")).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Created" && entry.value.includes("2026-03-12"))).toBe(true);
});
it("reuses customer-discount render fields for objects discount aliases", () => {
const vm = buildSystemSearchViewModel(
row("objects", {
entity_id: "326",
title: "326",
payload: {
object_type: "discount",
customer_number: 12345679,
percentage: 8,
product_or_category_id: "42",
is_category: false,
},
})
);
expect(vm.keyFields.some((entry) => entry.label === "Discount" && entry.value === "8%")).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Target" && entry.value === "42")).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Category" && entry.value === "No")).toBe(true);
});
it("surfaces nested goal criteria and progress details for department goals", () => {
const vm = buildSystemSearchViewModel(
row("department_goals", {
entity_id: "1",
title: "1",
payload: {
id: 1,
criteria: {
label: "Monthly Revenue Goal",
type: "REVENUE",
start: "2026-03-01",
end: "2026-03-31",
target: 100000,
},
progress: {
to_date: {
count: 42000,
target: 100000,
},
},
},
})
);
expect(vm.title).toBe("Monthly Revenue Goal");
expect(vm.description).toBe("REVENUE");
expect(vm.keyFields.some((entry) => entry.label === "Start" && entry.value.includes("2026-03-01"))).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "End" && entry.value.includes("2026-03-31"))).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Target" && entry.value.includes("100000"))).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Progress" && entry.value.includes("42%"))).toBe(true);
});
it("reuses department-goal render fields for objects goal aliases", () => {
const vm = buildSystemSearchViewModel(
row("objects", {
entity_id: "1",
title: "1",
payload: {
object_type: "`goal`",
criteria: {
label: "Weekly Throughput Goal",
type: "ORDERS",
start: "2026-03-10",
end: "2026-03-17",
target: 50,
},
progress: {
this_week: {
count: 21,
target: 50,
},
},
},
})
);
expect(vm.title).toBe("Weekly Throughput Goal");
expect(vm.description).toBe("ORDERS");
expect(vm.keyFields.some((entry) => entry.label === "Start" && entry.value.includes("2026-03-10"))).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "End" && entry.value.includes("2026-03-17"))).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Target" && entry.value.includes("50"))).toBe(true);
expect(vm.keyFields.some((entry) => entry.label === "Progress" && entry.value.includes("42%"))).toBe(true);
});
});