import { describe, it, expect } from 'vitest' import { DETAIL_TAB_BUDGET_ERR, DETAIL_TAB_BUDGET_WARN, DETAIL_SUMMARY_DEMAND_THRESHOLD, entityViewsFromPagespecFilenames, resolveDetailStrip, } from '../detail-tab-strip.js' const relTab = (key: string, extra: Record = {}) => ({ key, displayMode: 'table', relatedEntity: 'Invoice', relationFk: 'clientId', relatedModule: 'crm', relatedSection: 'invoices', ...extra, }) describe('entityViewsFromPagespecFilenames', () => { it('groups ..md basenames into per-entity view sets', () => { const map = entityViewsFromPagespecFilenames([ 'Client.list.md', 'Client.detail.md', 'Client.form.md', 'Invoice.list.md', 'notes.md', 'README.md', ]) expect([...map.get('Client')!].sort()).toEqual(['detail', 'form', 'list']) expect([...map.get('Invoice')!]).toEqual(['list']) expect(map.has('notes')).toBe(false) }) }) describe('resolveDetailStrip — the rendered bar, never the authored JSON', () => { it('legacy strip: field tabs + strip related tabs, no synthetic info tab', () => { const r = resolveDetailStrip( { tabs: [{ key: 'a' }, { key: 'b' }], relatedTabs: [relTab('invoices')] }, ['list', 'detail'], ) expect(r.surface).toBe('legacy') expect(r.fieldTabCount).toBe(2) expect(r.syntheticInfoTab).toBe(false) expect(r.stripTotal).toBe(3) }) it('the synthetic « info » trigger counts — it is a real trigger the reader sees', () => { const r = resolveDetailStrip( { relatedTabs: [relTab('invoices'), relTab('addresses', { relatedSection: 'addresses' })] }, ['list', 'detail'], ) expect(r.syntheticInfoTab).toBe(true) expect(r.stripTotal).toBe(3) // info + 2 related }) it('band tabs never occupy the bar — derived (summary) and explicit placement alike', () => { const r = resolveDetailStrip( { tabs: [{ key: 'a' }], relatedTabs: [ relTab('invoices'), relTab('insurances', { displayMode: 'summary' }), relTab('registrations', { displayMode: 'summary', placement: 'band' }), ], }, ['list', 'detail'], ) expect(r.bandCount).toBe(2) expect(r.stripRelatedCount).toBe(1) expect(r.stripTotal).toBe(2) }) it("an explicit placement 'tab' opts a summary back into the bar", () => { const r = resolveDetailStrip( { tabs: [], relatedTabs: [relTab('insurances', { displayMode: 'summary', placement: 'tab' })] }, ['list', 'detail'], ) expect(r.bandCount).toBe(0) expect(r.stripRelatedCount).toBe(1) // Sole strip content is related → the synthetic info trigger appears too. expect(r.stripTotal).toBe(2) }) it('a unified fiche zeroes the field-tab term (tabs[] render as section cards)', () => { const r = resolveDetailStrip( { tabs: [{ key: 'a' }, { key: 'b' }, { key: 'c' }], relatedTabs: [relTab('invoices')] }, ['list', 'detail', 'form'], ) expect(r.surface).toBe('unified') expect(r.fieldTabCount).toBe(0) expect(r.syntheticInfoTab).toBe(false) expect(r.stripTotal).toBe(1) }) it("editExperience 'direct' keeps the legacy surface — field tabs count again", () => { const r = resolveDetailStrip( { editExperience: 'direct', tabs: [{ key: 'a' }], relatedTabs: [relTab('invoices')] }, ['list', 'detail', 'form'], ) expect(r.surface).toBe('legacy') expect(r.stripTotal).toBe(2) }) it('undefined entityViews resolves legacy (the renderer contract for an unknown view set)', () => { const r = resolveDetailStrip({ tabs: [{ key: 'a' }] }, undefined) expect(r.surface).toBe('legacy') expect(r.stripTotal).toBe(1) }) it('Zod-rejected related tabs are counted apart — never budget mass', () => { const r = resolveDetailStrip( { relatedTabs: [relTab('invoices'), { key: 'BAD KEY' }] }, ['list', 'detail'], ) expect(r.rejectedCount).toBe(1) expect(r.stripTotal).toBe(2) // info + invoices }) it('exposes the budget thresholds as the numeric SSOT', () => { expect(DETAIL_TAB_BUDGET_WARN).toBe(7) expect(DETAIL_TAB_BUDGET_ERR).toBe(9) expect(DETAIL_SUMMARY_DEMAND_THRESHOLD).toBe(4) }) })