/** * header-title.test.ts — the fiche's title cascade (§ the driver titled by * their mobile number). The identity the BA authored wins over spec order: * person.identityFields > summary.titleField > tabs[0].fields[0] > * name-shaped field > legacy first-non-formula fallback. */ import { describe, it, expect } from 'vitest' import { generate } from '../generate.js' import type { ScaffoldComponentInput, PageSpecMin } from '../types.js' function detailOf( fields: ScaffoldComponentInput['fields'], pageSpecExtra: Record = {}, ): string { const files = generate({ module: 'conducteurs', appCode: 'flotte', entity: 'Driver', section: 'annuaire', views: ['detail'], entityViews: ['list', 'detail', 'form'], fields, projectPath: '/web', pageSpec: { screenCode: 'SCR-DRIVER-DETAIL', module: 'conducteurs', appCode: 'flotte', section: 'annuaire', entity: 'Driver', view: 'detail', filePath: 'src/pages/flotte/conducteurs/annuaire/DriverDetailPage.tsx', permission: 'conducteurs.annuaire.read', actions: [], i18nKeys: { fr: {}, en: {}, it: {}, de: {} }, specHash: 'test-hash', ...pageSpecExtra, } as unknown as PageSpecMin, }) return files.find((f) => /DetailPage\.tsx$/.test(f.path))!.content } // The reported shape: entité.md listed Mobile before the name fields, so the // fiche's title was the phone number. const DRIVER_FIELDS: ScaffoldComponentInput['fields'] = [ { name: 'MobilePhone', type: 'string', required: false }, { name: 'FirstName', type: 'string', required: true }, { name: 'LastName', type: 'string', required: true }, { name: 'Email', type: 'string', required: false }, ] describe('scaffold-component / detail header title cascade', () => { it('person.identityFields compose the title (names joined, email as fallback) — never the mobile', () => { const c = detailOf(DRIVER_FIELDS, { person: { mode: 'mandatory', identityFields: ['FirstName', 'LastName', 'Email'] }, }) expect(c).toContain( "const headerTitle = String([data.firstName, data.lastName].filter(Boolean).join(' ') || data.email || '—')", ) expect(c).not.toContain('headerTitle = String(data.mobilePhone') }) it('summary.titleField wins when no person block exists', () => { const c = detailOf(DRIVER_FIELDS, { summary: { titleField: 'lastName' } }) expect(c).toContain("const headerTitle = String(data.lastName ?? '')") }) it('the first field of the first tab is the identifier the BA put first', () => { const c = detailOf(DRIVER_FIELDS, { tabs: [{ key: 'identite', fields: ['firstName', 'mobilePhone'] }], }) expect(c).toContain("const headerTitle = String(data.firstName ?? '')") }) it('a name-shaped field (code/label/name/…) beats spec order', () => { const c = detailOf([ { name: 'Vin', type: 'string', required: true }, { name: 'Label', type: 'string', required: true }, ]) expect(c).toContain("const headerTitle = String(data.label ?? '')") }) it('legacy fallback: first non-formula field in spec order (unchanged output)', () => { const c = detailOf([ { name: 'Numero', type: 'string', required: true }, { name: 'Statut', type: 'string', required: true }, ]) expect(c).toContain("const headerTitle = String(data.numero ?? '')") }) it('an unresolvable identityFields entry falls through the cascade instead of emitting a dead accessor', () => { const c = detailOf(DRIVER_FIELDS, { person: { mode: 'mandatory', identityFields: ['GhostField'] }, summary: { titleField: 'lastName' }, }) expect(c).toContain("const headerTitle = String(data.lastName ?? '')") expect(c).not.toContain('ghostField') }) })