import { describe, it, expect } from 'vitest'; import { fmtValue, fmtPct, fmtInt, fmtNum, scopeLabel, unitLabel, parseScopeKey, toDate, isoDate, humanize, phaseLabel, truncate, pluralize } from '@/lib/format'; describe('fmtValue', () => { it('formats counts as integers with thousands separators', () => { expect(fmtValue(1234567, 'count')).toBe('1,234,567'); expect(fmtValue('42', 'count')).toBe('42'); expect(fmtValue(3.7, 'count')).toBe('4'); }); it('formats rates per 100k with one decimal', () => { expect(fmtValue(12.345, 'per_100k')).toBe('12.3'); expect(fmtValue(0, 'per_100k')).toBe('0'); }); it('formats ratios with two decimals and probabilities as percentages', () => { expect(fmtValue(0.4567, 'ratio')).toBe('0.46'); expect(fmtValue(0.4567, 'probability')).toBe('45.7%'); expect(fmtValue(1, 'probability')).toBe('100%'); }); it('signs percentile points', () => { expect(fmtValue(3.21, 'percentile_points')).toBe('+3.2'); expect(fmtValue(-3.21, 'percentile_points')).toBe('-3.2'); expect(fmtValue(0, 'percentile_points')).toBe('0'); }); it('falls back sensibly for unknown units', () => { expect(fmtValue(12345.6, null)).toBe('12,346'); expect(fmtValue(1.2345, 'unknown')).toBe('1.23'); }); it('never invents a number for missing values', () => { expect(fmtValue(null, 'count')).toBe('—'); expect(fmtValue(undefined, 'ratio')).toBe('—'); expect(fmtValue('abc', 'count')).toBe('—'); expect(fmtValue(Number.NaN, 'per_100k')).toBe('—'); }); }); describe('fmtPct / fmtInt / fmtNum', () => { it('fmtPct multiplies by 100 and respects digits', () => { expect(fmtPct(0.12345)).toBe('12.3%'); expect(fmtPct(0.12345, 2)).toBe('12.35%'); expect(fmtPct(null)).toBe('—'); expect(fmtPct(Number.POSITIVE_INFINITY)).toBe('—'); }); it('fmtInt handles strings, empties and non-finite values', () => { expect(fmtInt('1000')).toBe('1,000'); expect(fmtInt('')).toBe('—'); expect(fmtInt(null)).toBe('—'); expect(fmtInt('x')).toBe('—'); }); it('fmtNum caps at 3 decimals', () => { expect(fmtNum(1.23456, 3)).toBe('1.235'); expect(fmtNum(1.23456, 9)).toBe('1.235'); expect(fmtNum(1.5, 0)).toBe('2'); }); }); describe('scope keys', () => { it('parses a scope key into a record', () => { expect(parseScopeKey('geo=WORLD|sex=all|age=all|year=latest|level=top')).toEqual({ geo: 'WORLD', sex: 'all', age: 'all', year: 'latest', level: 'top' }); expect(parseScopeKey('')).toEqual({}); expect(parseScopeKey('geo=USA|broken')).toEqual({ geo: 'USA' }); }); it('labels the default world scope', () => { expect(scopeLabel('geo=WORLD|sex=all|age=all|year=latest|level=top')).toBe('World · both sexes · all ages · latest · top-level cancers'); }); it('labels a specific scope', () => { expect(scopeLabel('geo=USA|sex=female|age=65+|year=2021|level=all')).toBe('USA · Female · ages 65+ · 2021 · all malignant entities'); expect(scopeLabel('geo=USA|sex=all|age=all|year=2021|level=subtype')).toContain('subtype level'); }); }); describe('unitLabel', () => { it('maps known units', () => { expect(unitLabel('count')).toBe('count'); expect(unitLabel('per_100k')).toBe('per 100,000'); expect(unitLabel('ratio')).toBe('ratio'); expect(unitLabel('probability')).toBe('%'); expect(unitLabel('percentile_points')).toBe('percentile points'); }); it('passes unknown units through and empties for null', () => { expect(unitLabel('years')).toBe('years'); expect(unitLabel(null)).toBe(''); expect(unitLabel(undefined)).toBe(''); }); }); describe('dates (drizzle execute returns timestamp strings)', () => { it('parses postgres timestamp strings with a numeric offset', () => { const d = toDate('2026-09-08 05:07:09.317-04'); expect(d).not.toBeNull(); expect(d!.toISOString()).toBe('2026-09-08T09:07:09.317Z'); }); it('parses plain dates as UTC midnight and rejects garbage', () => { expect(isoDate('2024-02-29')).toBe('2024-02-29'); expect(toDate('not a date')).toBeNull(); expect(toDate('')).toBeNull(); expect(toDate(null)).toBeNull(); expect(isoDate(new Date(Number.NaN))).toBe('—'); }); }); describe('labels', () => { it('humanizes enums', () => { expect(humanize('NOT_YET_RECRUITING')).toBe('Not Yet Recruiting'); expect(humanize('adenocarcinoma_nos')).toBe('Adenocarcinoma NOS'); expect(humanize(null)).toBe('—'); }); it('labels phases', () => { expect(phaseLabel('PHASE3')).toBe('Phase 3'); expect(phaseLabel('EARLY_PHASE1')).toBe('Early Phase 1'); expect(phaseLabel('NA')).toBe('N/A'); expect(phaseLabel('SOMETHING_ELSE')).toBe('Something Else'); }); it('truncates on word boundaries and pluralizes', () => { expect(truncate('The quick brown fox jumps over the lazy dog', 20)).toBe('The quick brown fox…'); expect(truncate('short', 20)).toBe('short'); expect(truncate(null, 5)).toBe(''); expect(pluralize(1, 'study', 'studies')).toBe('study'); expect(pluralize(2, 'study', 'studies')).toBe('studies'); expect(pluralize(0, 'gene')).toBe('genes'); }); });