import { describe, it, expect } from 'vitest'; import { groupComparable, explainSplit, differingDimensions, splitIntoMultiples, shouldUseMultiples, groupKey, latestPoint, type ComparableObs } from '@/lib/explorer-series'; function obs(over: Partial & { year: number; value: number }): ComparableObs { return { cancer_slug: 'lung', cancer_name: 'Lung', geography_slug: 'united-states', geography_name: 'United States', sex: 'all', age_group: 'all', metric: 'as_mortality_rate', unit: 'per_100k', standard_population: 'US 2000 standard', estimate_type: 'observed', source_slug: 'cdc-uscs', source_name: 'USCS', provenance_id: 1, ...over, }; } describe('groupComparable', () => { it('overlays observations that share metric, unit, geography, source, standard population and age group', () => { const rows = [obs({ year: 2000, value: 50 }), obs({ year: 2001, value: 48 }), obs({ cancer_slug: 'breast', cancer_name: 'Breast', year: 2000, value: 20 }), obs({ cancer_slug: 'breast', cancer_name: 'Breast', year: 2001, value: 19 })]; const g = groupComparable(rows); expect(g).toHaveLength(1); expect(g[0]!.series.map((s) => s.name)).toEqual(['Breast', 'Lung']); // sex omitted when every series shares it expect(g[0]!.series[1]!.points.map((p) => p.x)).toEqual([2000, 2001]); expect(g[0]!.n_obs).toBe(4); expect([g[0]!.year_min, g[0]!.year_max]).toEqual([2000, 2001]); expect(g[0]!.y_max).toBe(50); }); it('separates different standard populations and different sources — never overlaid', () => { const rows = [obs({ year: 2000, value: 50 }), obs({ year: 2000, value: 52, standard_population: 'World (Segi)' }), obs({ year: 2000, value: 51, source_slug: 'cdc-wonder', standard_population: 'US 2000 Std. Population' })]; const g = groupComparable(rows); expect(g).toHaveLength(3); const dims = differingDimensions(g); expect(dims).toEqual(['source_slug', 'standard_population']); const why = explainSplit(g)!; expect(why).toMatch(/3 separate charts/); expect(why).toMatch(/standard population \(.*US 2000 standard.*vs.*World \(Segi\)/); expect(why).toMatch(/source \(cdc-uscs vs cdc-wonder\)/); }); it('separates age groups and metrics/units; counts get "no standard population" in the caption', () => { const rows = [obs({ year: 2000, value: 50 }), obs({ year: 2000, value: 5, age_group: '65+' }), obs({ year: 2000, value: 1000, metric: 'mortality_count', unit: 'count', standard_population: null })]; const g = groupComparable(rows); expect(g).toHaveLength(3); expect(explainSplit(g)).toMatch(/no standard population \(crude or count\)/); expect(explainSplit(g)).toMatch(/age group \(all ages vs 65\+\)/); }); it('returns null explanation for a single group', () => { expect(explainSplit(groupComparable([obs({ year: 2000, value: 1 })]))).toBeNull(); }); it('makes cancer × sex series, dashed when estimated, and names them accordingly', () => { const rows = [obs({ year: 2000, value: 50, sex: 'male' }), obs({ year: 2000, value: 40, sex: 'female' }), obs({ year: 2001, value: 39, sex: 'female', estimate_type: 'estimated' })]; const g = groupComparable(rows); expect(g).toHaveLength(1); const names = g[0]!.series.map((s) => `${s.name}${s.dashed ? '*' : ''}`); expect(names).toEqual(['Lung · male', 'Lung · female', 'Lung · female · estimated*']); }); it('disambiguates two site definitions of one cancer inside one source', () => { const rows = [obs({ year: 2000, value: 50, site_definition: 'C33-C34' }), obs({ year: 2000, value: 45, site_definition: 'C34' })]; const g = groupComparable(rows); expect(g[0]!.series.map((s) => s.name).sort()).toEqual(['Lung · C33-C34', 'Lung · C34']); }); it('orders groups by size and skips non-finite values', () => { const rows = [obs({ year: 2000, value: 1, source_slug: 'x' }), obs({ year: 2000, value: 2 }), obs({ year: 2001, value: 3 }), obs({ year: 2002, value: Number.NaN })]; const g = groupComparable(rows); expect(g.map((x) => x.source_slug)).toEqual(['cdc-uscs', 'x']); expect(g[0]!.n_obs).toBe(2); }); it('groupKey is stable and null-safe', () => { expect(groupKey(obs({ year: 1, value: 1, standard_population: null }))).toBe('as_mortality_rate|per_100k|united-states|cdc-uscs||all'); }); }); describe('small multiples', () => { const rows = ['a', 'b', 'c', 'd', 'e'].flatMap((c) => [obs({ cancer_slug: c, cancer_name: c.toUpperCase(), year: 2000, value: 10 }), obs({ cancer_slug: c, cancer_name: c.toUpperCase(), year: 2001, value: c === 'e' ? 99 : 12 })]); const g = groupComparable(rows)[0]!; it('forces multiples beyond 4 overlaid series, honours the explicit view otherwise', () => { expect(shouldUseMultiples(g, 'lines')).toBe(true); const small = groupComparable(rows.slice(0, 4))[0]!; expect(shouldUseMultiples(small, 'lines')).toBe(false); expect(shouldUseMultiples(small, 'multiples')).toBe(true); const single = groupComparable(rows.slice(0, 2))[0]!; expect(shouldUseMultiples(single, 'multiples')).toBe(false); // one cancer → nothing to split }); it('splits per cancer and keeps the shared y_max', () => { const panels = splitIntoMultiples(g); expect(panels).toHaveLength(5); expect(panels.every((p) => p.y_max === 99)).toBe(true); expect(panels.map((p) => p.series[0]!.cancer_slug)).toEqual(['a', 'b', 'c', 'd', 'e']); expect(panels[0]!.n_obs).toBe(2); }); it('latestPoint returns the last chronological point', () => { expect(latestPoint(g.series[4]!)).toEqual({ x: 2001, y: 99 }); }); });