/** * detail.summary — the header summary band (plan UI 3.2). * * A card above the tabs/sections: big title value, status Badge, up to 4 * label/value meta pairs. Values go through renderReadValueExpr (same pills / * FK labels / date formatting as the body). The uiDesign.detail.summary * overlay wins key-by-key; unknown field keys warn + drop. Absent → no band. */ import { describe, it, expect } from 'vitest' import { generate } from '../generate.js' import type { ScaffoldComponentInput, PageSpecMin } from '../types.js' const I18N: Record = { 'detail.title': 'Dossier', 'detail.fields.amount': 'Montant', 'detail.fields.dueDate': 'Échéance', } function input(ps: Partial = {}): ScaffoldComponentInput { return { module: 'claims', appCode: 'TestV2', entity: 'Claim', section: 'claims', views: ['detail'], fields: [ { name: 'reference', type: 'string', required: true }, { name: 'status', type: 'string', required: true }, { name: 'amount', type: 'number', required: false }, { name: 'dueDate', type: 'date', required: false }, ], projectPath: '/web', pageSpec: { screenCode: 'SCR-CLAIMS-CLAIMS-DETAIL-001', module: 'claims', appCode: 'testv2', section: 'claims', entity: 'Claim', view: 'detail', filePath: 'src/pages/testv2/claims/claims/ClaimDetailPage.tsx', permission: 'claims.claims.read', actions: [], i18nKeys: { fr: I18N, en: I18N, it: I18N, de: I18N }, needsRefinement: false, specHash: 'y'.repeat(64), ...ps, } as PageSpecMin, } } const pageOf = (i: ScaffoldComponentInput, warnings: string[] = []) => generate(i, { warnings }).find((f) => f.path.endsWith('ClaimDetailPage.tsx'))!.content describe('scaffold-component / detail.summary — header band', () => { it('renders the band: title value, status Badge, meta pairs through the shared read exprs', () => { const c = pageOf(input({ summary: { titleField: 'reference', statusField: 'status', fields: ['amount', 'dueDate'] }, } as Partial)) expect(c).toMatch(/data-testid="detail-summary"/) expect(c).toMatch(/text-xl font-semibold text-\[var\(--text-primary\)\]">\{String\(data\.reference \?\? ''\)\}/) // Status renders the same Badge pill as everywhere else. expect(c).toMatch(/\{String\(data\.status \?\? ''\)\}<\/Badge>/) // Meta pairs: label from the detail.fields.* namespace, date through the platform formatter. expect(c).toMatch(/\{t\('claim\.detail\.fields\.amount'\)\}/) expect(c).toMatch(/\{formatDate\(data\.dueDate\)\}/) // The band sits between the header Slot and the body. expect(c.indexOf('detail-summary')).toBeGreaterThan(c.indexOf('claim.detail.header')) expect(c.indexOf('detail-summary')).toBeLessThan(c.indexOf('claim.detail.sidebar')) }) it('the uiDesign.detail.summary overlay wins key-by-key over the pagespec', () => { const c = pageOf(input({ summary: { titleField: 'reference' }, uiDesign: { detail: { summary: { titleField: 'status' } } }, } as unknown as Partial)) expect(c).toMatch(/text-xl font-semibold[^>]*>\{? { const warnings: string[] = [] const c = pageOf(input({ summary: { titleField: 'ghost', fields: ['amount'] }, } as Partial), warnings) expect(c).toMatch(/text-xl font-semibold text-\[var\(--text-primary\)\]">\{headerTitle\}/) expect(warnings.some((w) => w.includes("summary.titleField 'ghost'"))).toBe(true) }) it('no summary authored → no band (legacy output unchanged)', () => { const c = pageOf(input()) expect(c).not.toMatch(/detail-summary/) }) })