when-am-i / files / src / utils / yearScale.test.ts
yearScale.test.ts
| 1 | import { describe, it, expect } from 'vitest'; |
| 2 | import { buildYearScale } from './yearScale'; |
| 3 | |
| 4 | describe('buildYearScale', () => { |
| 5 | it('places the endpoints at 0 and 1', () => { |
| 6 | const s = buildYearScale(-243, 2026, [1954]); |
| 7 | expect(s.toFraction(-243)).toBe(0); |
| 8 | expect(s.toFraction(2026)).toBe(1); |
| 9 | }); |
| 10 | |
| 11 | it('clamps years outside the range (including ±Infinity for open-ended eras)', () => { |
| 12 | const s = buildYearScale(-243, 2026, [1954]); |
| 13 | expect(s.toFraction(-Infinity)).toBe(0); |
| 14 | expect(s.toFraction(-5000)).toBe(0); |
| 15 | expect(s.toFraction(Infinity)).toBe(1); |
| 16 | expect(s.toFraction(9999)).toBe(1); |
| 17 | }); |
| 18 | |
| 19 | it('anchors on the region key years (a single boundary lands dead-centre)', () => { |
| 20 | const s = buildYearScale(-243, 2026, [1954]); |
| 21 | expect(s.anchors).toEqual([-243, 1954, 2026]); |
| 22 | expect(s.toFraction(1954)).toBeCloseTo(0.5, 6); |
| 23 | }); |
| 24 | |
| 25 | it('sorts and de-dupes key years and drops any outside the range', () => { |
| 26 | const s = buildYearScale(-243, 2026, [1910, 1896, 1896, 9999, -500, 1907, 1897]); |
| 27 | expect(s.anchors).toEqual([-243, 1896, 1897, 1907, 1910, 2026]); |
| 28 | }); |
| 29 | |
| 30 | it('spreads clustered key dates evenly so each is readable (Korea 1896–1910)', () => { |
| 31 | const s = buildYearScale(-243, 2026, [1896, 1897, 1907, 1910]); |
| 32 | const fracs = s.anchors.map((y) => s.toFraction(y)); |
| 33 | const step = 1 / (s.anchors.length - 1); |
| 34 | for (let i = 1; i < fracs.length; i++) { |
| 35 | // Equal-width segments → even spacing, even though 1896→1897 is one |
| 36 | // year and 1910→2026 is 116 (proportional spacing would collide them). |
| 37 | expect(fracs[i] - fracs[i - 1]).toBeCloseTo(step, 6); |
| 38 | } |
| 39 | }); |
| 40 | |
| 41 | it('falls back to round epoch anchors when the region has no key years', () => { |
| 42 | const s = buildYearScale(-243, 2026, []); |
| 43 | expect(s.anchors).toEqual([-243, 1500, 1800, 1900, 2026]); |
| 44 | }); |
| 45 | |
| 46 | it('is monotonic increasing in year', () => { |
| 47 | const s = buildYearScale(-243, 2026, [1896, 1897, 1907, 1910]); |
| 48 | let prev = -Infinity; |
| 49 | for (let y = -243; y <= 2026; y += 7) { |
| 50 | const f = s.toFraction(y); |
| 51 | expect(f).toBeGreaterThanOrEqual(prev); |
| 52 | prev = f; |
| 53 | } |
| 54 | }); |
| 55 | |
| 56 | it('round-trips year → fraction → year', () => { |
| 57 | const s = buildYearScale(-243, 2026, [1792, 1806, 1871]); |
| 58 | for (const y of [-243, 500, 1792, 1806, 1850, 1871, 1945, 2026]) { |
| 59 | expect(s.toYear(s.toFraction(y))).toBeCloseTo(y, 6); |
| 60 | } |
| 61 | }); |
| 62 | |
| 63 | it('degrades to a single linear segment when there are no usable anchors', () => { |
| 64 | const s = buildYearScale(1980, 2026, []); // no epoch anchors in range either |
| 65 | expect(s.anchors).toEqual([1980, 2026]); |
| 66 | expect(s.toFraction(2003)).toBeCloseTo((2003 - 1980) / (2026 - 1980), 6); |
| 67 | }); |
| 68 | }); |