- Consolidate route search handling in `PosDepartmentMVP.vue` using `applyPosRouteSearch` for improved maintainability. - Refactor tab visibility logic in `ElementTabsBox.vue` with reusable `isHidden` utility, removing redundant code. - Enhance `SessionUser.vue` and `superUserObject.vue` modules with lazy bindings for improved initialization and performance. - Update `NavigationMenuItemsAdmin.vue` to validate department IDs with `hasValidDepartmentId` and watchers for reactive booking count updates. - Improve error handling logic in `HandleGlobalError.vue` and `loadOrderItems` for robustness against invalid inputs. - Add and update regression tests for session and superuser initialization to ensure compliance with binding contracts. - Refactor POS step components to streamline order-related logic and support immediate reactivity.
178 lines
5.9 KiB
JavaScript
178 lines
5.9 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
const root = process.cwd();
|
|
|
|
const normalizeSource = (source) => source.replace(/\r\n/g, '\n');
|
|
const readSource = (relativePath) =>
|
|
normalizeSource(readFileSync(join(root, relativePath), 'utf8'));
|
|
|
|
const sessionUserSource = readSource(
|
|
'src/components/session/token/SessionUser.vue'
|
|
);
|
|
const superUserObjectSource = readSource(
|
|
'src/components/session/token/superUserObject.vue'
|
|
);
|
|
|
|
const extractBlock = (source, startToken, endToken) => {
|
|
const startIndex = source.indexOf(startToken);
|
|
if (startIndex === -1) {
|
|
throw new Error(`Unable to find start token: ${startToken}`);
|
|
}
|
|
|
|
const contentStartIndex = startIndex + startToken.length;
|
|
const endIndex = source.indexOf(endToken, contentStartIndex);
|
|
if (endIndex === -1) {
|
|
throw new Error(`Unable to find end token: ${endToken}`);
|
|
}
|
|
|
|
return source.slice(contentStartIndex, endIndex);
|
|
};
|
|
|
|
const parseDirectBindings = (sourceBlock) => {
|
|
const bindings = new Map();
|
|
const pattern = /^\s*([A-Za-z0-9_]+)\s*:\s*([A-Za-z0-9_]+)\s*,\s*$/gm;
|
|
|
|
for (const match of sourceBlock.matchAll(pattern)) {
|
|
bindings.set(match[1], match[2]);
|
|
}
|
|
|
|
return bindings;
|
|
};
|
|
|
|
const parseGetterBindings = (sourceBlock) => {
|
|
const bindings = new Map();
|
|
const pattern = /^\s*get\s+([A-Za-z0-9_]+)\s*\(\)\s*{\s*return\s+([A-Za-z0-9_]+);\s*},?\s*$/gm;
|
|
|
|
for (const match of sourceBlock.matchAll(pattern)) {
|
|
bindings.set(match[1], match[2]);
|
|
}
|
|
|
|
return bindings;
|
|
};
|
|
|
|
const expectedSessionObjectBindings = {
|
|
categories: 'Categories',
|
|
products: 'Products',
|
|
product_options: 'ProductOptions',
|
|
departments: 'Departments',
|
|
department_categories: 'DepartmentCategories',
|
|
economic_departments: 'EconomicDepartments',
|
|
economic_products: 'EconomicProducts',
|
|
roles: 'Roles',
|
|
permissions: 'Permissions',
|
|
collectedOrderInvoices: 'CollectedOrderInvoices',
|
|
department_daily_reports: 'DepartmentDailyReports',
|
|
goals: 'Goals',
|
|
notifications: 'Notifications',
|
|
orders: 'Orders',
|
|
forms: 'Forms',
|
|
bookings: 'Bookings',
|
|
Branding: 'Branding',
|
|
vehicles: 'Vehicles',
|
|
department_notification_sms: 'DepartmentNotificationSms',
|
|
department_time_bookings_opening_hours: 'DepartmentTimeBookingsOpeningHours',
|
|
department_time_bookings_types: 'DepartmentTimeBookingsTypes',
|
|
department_time_bookings_entries: 'DepartmentTimeBookingsEntries',
|
|
department_variables: 'DepartmentVariables',
|
|
xlvask_vehicle_types: 'XLVaskVehicleTypes',
|
|
openai: 'OpenAI',
|
|
department_lanes: 'DepartmentLanes',
|
|
department_gates: 'DepartmentGates',
|
|
department_relays: 'DepartmentRelays',
|
|
order_bookings: 'OrderBookings',
|
|
module_action_logs: 'ModuleActionLogs',
|
|
self_serve_questions: 'SelfServeQuestions',
|
|
self_serve_tasks: 'SelfServeTasks',
|
|
self_serve_conditions: 'SelfServeConditions',
|
|
self_serve_vehicle_conditions: 'SelfServeVehicleConditions',
|
|
self_serve_condition_rules: 'SelfServeConditionRules',
|
|
self_serve_machine_types: 'SelfServeMachineTypes',
|
|
subuser_grants: 'SubuserGrants',
|
|
global: 'ObjectsGlobal'
|
|
};
|
|
|
|
const expectedSuperUserModuleBindings = {
|
|
economic: 'Economic',
|
|
reCAPTCHA: 'reCAPTCHA',
|
|
email: 'Email',
|
|
backups: 'Backups',
|
|
motorapi: 'MotorAPI',
|
|
stripe: 'Stripe',
|
|
fxratesapi: 'FXRatesAPI',
|
|
weatherapi: 'WeatherAPI',
|
|
gatewayapi: 'GatewayAPI',
|
|
xlvask: 'XLVask',
|
|
entra: 'Entra',
|
|
limble: 'Limble',
|
|
ocrspace: 'OcrSpace',
|
|
openai: 'OpenAI',
|
|
licenseplaterecognizer: 'LicensePlateRecognizer',
|
|
virkdata: 'VirkData',
|
|
shelly: 'Shelly',
|
|
selfserve: 'SelfServe',
|
|
bird: 'Bird'
|
|
};
|
|
|
|
const superUserModulesBlock = extractBlock(
|
|
superUserObjectSource,
|
|
'modules: {',
|
|
'\n },\n /** Intimidate a user */'
|
|
);
|
|
const superUserSystemBlock = extractBlock(
|
|
superUserObjectSource,
|
|
'system: {',
|
|
'\n },\n modules: {'
|
|
);
|
|
|
|
const sessionObjectsContentBlock = extractBlock(
|
|
sessionUserSource,
|
|
'objects: {',
|
|
'\n },\n /** The user\'s token and authentication status */'
|
|
);
|
|
|
|
describe('session token initialization regression contract', () => {
|
|
it('keeps department id lookup decoupled from router module initialization', () => {
|
|
expect(sessionUserSource).not.toContain('import router from "@/router.js";');
|
|
expect(sessionUserSource).not.toContain('router.currentRoute.value.params.departmentId');
|
|
expect(sessionUserSource).toContain("const path = window.location.pathname || '';");
|
|
expect(sessionUserSource).toContain("const match = path.match(/^\\/admin\\/(\\d+)(?:\\/|$)/);");
|
|
expect(sessionUserSource).toContain('const departmentId = Number.parseInt(match[1], 10);');
|
|
expect(sessionUserSource).toContain(
|
|
'return Number.isInteger(departmentId) && departmentId > 0 ? departmentId : undefined;'
|
|
);
|
|
});
|
|
|
|
it('keeps SessionUser.objects eagerly bound for all shared object modules', () => {
|
|
const directBindings = parseDirectBindings(sessionObjectsContentBlock);
|
|
const getterBindings = parseGetterBindings(sessionObjectsContentBlock);
|
|
|
|
expect(getterBindings.size).toBe(0);
|
|
|
|
for (const [key, expectedBinding] of Object.entries(expectedSessionObjectBindings)) {
|
|
expect(directBindings.get(key)).toBe(expectedBinding);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('superuser module initialization regression contract', () => {
|
|
it('keeps superuser system binding lazy to avoid circular-import TDZ errors', () => {
|
|
const systemBindings = parseGetterBindings(superUserSystemBlock);
|
|
expect(systemBindings.get('database')).toBe('DatabaseSystemObject');
|
|
});
|
|
|
|
it('keeps all superuser module bindings lazy at object construction time', () => {
|
|
const moduleGetterBindings = parseGetterBindings(superUserModulesBlock);
|
|
const moduleDirectBindings = parseDirectBindings(superUserModulesBlock);
|
|
|
|
expect(moduleDirectBindings.size).toBe(0);
|
|
|
|
for (const [key, expectedBinding] of Object.entries(expectedSuperUserModuleBindings)) {
|
|
expect(moduleGetterBindings.get(key)).toBe(expectedBinding);
|
|
}
|
|
|
|
expect(superUserObjectSource).toContain('get cron() { return Cron; }');
|
|
});
|
|
});
|