import { describe, it, expect } from 'vitest' import { generate } from '../generate.js' import type { ScaffoldComponentInput, PageSpecMin } from '../types.js' /** * §28 — the CustomActionDialog `lookup` parameter endpoint is READ, not * rebuilt. The historical shape reconstructed `{action's module}/{english * plural}` — 9/9 literals wrong on one project (wrong module, English plural * instead of the menu section slug, missing routeFamily segment), every * dialog combobox empty on a 404. Resolution order: explicit apiEndpoint > * Core adapter/SSOT > the RESOLVED navRoute (spliced from derive-action-specs' * dialogLookupParams — the [NavRoute] mirror) > last-resort plural guess. */ const I18N: Record = { 'list.title': 'Affectations', 'list.subtitle': 'x', 'list.create': 'x', 'list.search': 'x', 'list.loading': 'x', 'list.empty': 'x', 'list.transfer': 'Transférer', } function pageSpec(ps: Partial = {}): PageSpecMin { return { screenCode: 'SCR-FLOTTE-PARC-AFFECTATIONS-001', module: 'parc', appCode: 'flotte', section: 'affectations', entity: 'Assignment', view: 'list', filePath: 'src/pages/flotte/parc/affectations/AssignmentsListPage.tsx', permission: 'parc.affectations.read', actions: [], i18nKeys: { fr: I18N, en: I18N, it: I18N, de: I18N }, needsRefinement: false, specHash: 'r'.repeat(64), ...ps, } as PageSpecMin } function input(ps: Partial = {}): ScaffoldComponentInput { return { module: 'parc', appCode: 'Flotte', entity: 'Assignment', section: 'affectations', views: ['list'], fields: [{ name: 'code', type: 'string', required: true }], projectPath: '/web', pageSpec: pageSpec(ps), } as ScaffoldComponentInput } function transferAction(param: Record) { return { code: 'transfer', scope: 'row' as const, labelKey: 'list.transfer', permission: 'parc.affectations.update', payloadDto: 'TransferRequest', payloadParameters: [param], } } const pageOf = (i: ScaffoldComponentInput) => generate(i, { warnings: [] }).find((f) => f.path.endsWith('AssignmentsListPage.tsx'))!.content describe('scaffold-component / dialog lookup endpoint (§28)', () => { it('a spliced navRoute drives the endpoint — cross-module target, section slug ≠ English plural', () => { const c = pageOf(input({ actions: [transferAction({ name: 'targetDriverId', type: 'lookup', required: true, entity: 'Driver', module: 'conducteurs', navRoute: 'conducteurs.annuaire', })], })) // The [NavRoute] mirror — NEVER /api/parc/drivers/lookup (the 9/9-wrong shape). expect(c).toContain("apiEndpoint: '/api/conducteurs/annuaire/lookup'") expect(c).not.toContain('/api/parc/drivers/lookup') }) it('a satellite navRoute keeps its routeFamily segment', () => { const c = pageOf(input({ actions: [transferAction({ name: 'documentId', type: 'lookup', entity: 'VehicleDocument', module: 'parc', navRoute: 'parc.vehicules.documents', })], })) expect(c).toContain("apiEndpoint: '/api/parc/vehicules/documents/lookup'") }) it('an explicit apiEndpoint always wins over navRoute', () => { const c = pageOf(input({ actions: [transferAction({ name: 'officeId', type: 'lookup', entity: 'Office', module: 'core', apiEndpoint: '/api/core/offices/lookup', navRoute: 'never.used', })], })) expect(c).toContain("apiEndpoint: '/api/core/offices/lookup'") expect(c).not.toContain('never/used') }) it('without navRoute the last-resort plural guess is unchanged (audit-dev-wire net)', () => { const c = pageOf(input({ actions: [transferAction({ name: 'targetId', type: 'lookup', entity: 'Widget', module: 'parc', })], })) expect(c).toContain("apiEndpoint: '/api/parc/widgets/lookup'") }) })