import { canAggregate, aggregateCells, canRenderBar, toBarSegments, CARD_GROUPS, type DestinationCell, type DestinationGroup, type DestinationCategory, } from '@/lib/destinations'; const pub = (category: DestinationCategory, pupils: number, cohort: number): DestinationCell => ({ category, pupils, percentage: (pupils / cohort) * 100, status: 'published', }); const sup = (category: DestinationCategory): DestinationCell => ({ category, pupils: null, percentage: null, status: 'suppressed', }); const fullGroup = (): DestinationGroup => ({ cohort: 180, cells: [ pub('school_sixth_form', 75, 180), pub('sixth_form_college', 21, 180), pub('further_education', 55, 180), pub('other_education', 6, 180), pub('apprenticeship', 8, 180), pub('employment', 6, 180), pub('not_sustained', 5, 180), pub('not_captured', 4, 180), ], }); describe('canAggregate — R2, computing from components', () => { it('allows a sum when every component is published', () => { expect(canAggregate([pub('apprenticeship', 8, 180), pub('employment', 6, 180)])).toBe(true); }); it('refuses a sum when any component is suppressed', () => { expect(canAggregate([pub('apprenticeship', 8, 180), sup('employment')])).toBe(false); }); it('refuses a sum when every component is suppressed', () => { expect(canAggregate([sup('apprenticeship'), sup('employment')])).toBe(false); }); }); describe('aggregateCells', () => { it('sums published cells and derives a percentage from the cohort', () => { expect(aggregateCells([pub('apprenticeship', 8, 180), pub('employment', 6, 180)], 180)) .toEqual({ pupils: 14, percentage: (14 / 180) * 100 }); }); it('returns null rather than a partial sum when a component is suppressed', () => { expect(aggregateCells([pub('apprenticeship', 8, 180), sup('employment')], 180)).toBeNull(); }); }); describe('canRenderBar — R1', () => { it('allows a bar when the whole group is published', () => { expect(canRenderBar(fullGroup())).toBe(true); }); it('refuses a bar when a single category is suppressed', () => { const g = fullGroup(); g.cells[1] = sup('sixth_form_college'); expect(canRenderBar(g)).toBe(false); }); }); describe('toBarSegments', () => { it('derives widths from counts, not from rounded percentages', () => { const segs = toBarSegments(fullGroup()); expect(segs).toHaveLength(8); expect(segs[0].widthPct).toBeCloseTo((75 / 180) * 100, 10); expect(segs.reduce((a, s) => a + s.widthPct, 0)).toBeCloseTo(100, 6); }); it('throws rather than silently leaving a gap when the group is suppressed', () => { const g = fullGroup(); g.cells[1] = sup('sixth_form_college'); expect(() => toBarSegments(g)).toThrow(/suppressed/i); }); }); describe('CARD_GROUPS', () => { it('partitions every destination category exactly once, plus the absence', () => { const grouped = Object.values(CARD_GROUPS).flat(); expect(new Set(grouped).size).toBe(grouped.length); expect(grouped).toEqual(expect.arrayContaining([ 'school_sixth_form', 'sixth_form_college', 'further_education', 'other_education', 'apprenticeship', 'employment', ])); expect(grouped).not.toContain('not_sustained'); expect(grouped).not.toContain('not_captured'); }); });