when-am-i / files / src / utils / timelineBands.test.ts
timelineBands.test.ts
| 1 | import { describe, it, expect } from 'vitest'; |
| 2 | import { effectiveErasForPoint, yearBandsForPoint, dayBandsForPoint } from './timelineBands'; |
| 3 | import { daysInYear } from './historicalDate'; |
| 4 | import type { CalendarRegion, Geometry } from '../types'; |
| 5 | |
| 6 | const square: Geometry = { |
| 7 | type: 'Polygon', |
| 8 | coordinates: [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], |
| 9 | }; |
| 10 | |
| 11 | // A region that switches calendars mid-year, on 22 September 1792 (the French |
| 12 | // Republican start) — a real intra-year changeover for the day-band tests. |
| 13 | const switchover: CalendarRegion[] = [ |
| 14 | { |
| 15 | region: { name: 'France', geo: square }, |
| 16 | sourceFile: 'src/data/calendars/france.json', |
| 17 | eras: [ |
| 18 | { name: 'Ancien régime', start: null, end: '1792-09-22', calendar: 'Gregorian' }, |
| 19 | { name: 'Republic', start: '1792-09-22', end: '1806-01-01', calendar: 'French Republican calendar' }, |
| 20 | ], |
| 21 | }, |
| 22 | ]; |
| 23 | |
| 24 | describe('effectiveErasForPoint', () => { |
| 25 | it('returns just the modern Gregorian default outside every region', () => { |
| 26 | // The default is appended unconditionally (mirroring the old year-band |
| 27 | // behavior); the day/year mappers clip it out of pre-modern years. |
| 28 | const eras = effectiveErasForPoint(50, 50, switchover); |
| 29 | expect(eras).toHaveLength(1); |
| 30 | expect(eras[0].name).toBe('Modern (global civil calendar)'); |
| 31 | expect(eras[0].calendar).toBe('Gregorian'); |
| 32 | }); |
| 33 | |
| 34 | it('appends the modern Gregorian default for a region without modern Gregorian', () => { |
| 35 | const eras = effectiveErasForPoint(5, 5, switchover); |
| 36 | expect(eras.map((e) => e.calendar)).toEqual([ |
| 37 | 'Gregorian', // ancien régime, ends 1792 (does not reach modern) |
| 38 | 'French Republican calendar', |
| 39 | 'Gregorian', // appended global default |
| 40 | ]); |
| 41 | expect(eras[eras.length - 1].name).toBe('Modern (global civil calendar)'); |
| 42 | }); |
| 43 | |
| 44 | it('does not append the default when a region already has modern Gregorian', () => { |
| 45 | const modern: CalendarRegion[] = [ |
| 46 | { region: { name: 'X', geo: square }, eras: [{ name: 'Now', start: '1900-01-01', end: null, calendar: 'Gregorian' }] }, |
| 47 | ]; |
| 48 | expect(effectiveErasForPoint(5, 5, modern)).toHaveLength(1); |
| 49 | }); |
| 50 | |
| 51 | it('suppresses the default when an era opts out and reaches modern times', () => { |
| 52 | const isolated: CalendarRegion[] = [ |
| 53 | { region: { name: 'I', geo: square }, eras: [{ name: 'None', start: null, end: null, calendar: 'None', suppressModernDefault: true }] }, |
| 54 | ]; |
| 55 | const eras = effectiveErasForPoint(5, 5, isolated); |
| 56 | expect(eras).toHaveLength(1); |
| 57 | expect(eras[0].calendar).toBe('None'); |
| 58 | }); |
| 59 | }); |
| 60 | |
| 61 | describe('yearBandsForPoint', () => { |
| 62 | it('maps eras to year spans with ±Infinity for open ends', () => { |
| 63 | const bands = yearBandsForPoint(5, 5, switchover); |
| 64 | expect(bands[0]).toEqual({ start: -Infinity, end: 1792, calendar: 'Gregorian' }); |
| 65 | expect(bands[1]).toEqual({ start: 1792, end: 1806, calendar: 'French Republican calendar' }); |
| 66 | // appended modern default |
| 67 | expect(bands[2]).toEqual({ start: 1927, end: Infinity, calendar: 'Gregorian' }); |
| 68 | }); |
| 69 | }); |
| 70 | |
| 71 | describe('dayBandsForPoint', () => { |
| 72 | it('splits the changeover year at the exact day', () => { |
| 73 | // 1792 is a leap year; 22 Sept is day 266. The half-open boundary means |
| 74 | // the old calendar covers days 1–265 and the new one 266–366. |
| 75 | const bands = dayBandsForPoint(5, 5, switchover, 1792); |
| 76 | expect(daysInYear(1792)).toBe(366); |
| 77 | expect(bands).toEqual([ |
| 78 | { startDoy: 1, endDoy: 265, calendar: 'Gregorian' }, |
| 79 | { startDoy: 266, endDoy: 366, calendar: 'French Republican calendar' }, |
| 80 | ]); |
| 81 | }); |
| 82 | |
| 83 | it('yields a single full-year band for a year with no changeover', () => { |
| 84 | const bands = dayBandsForPoint(5, 5, switchover, 1800); |
| 85 | expect(bands).toEqual([{ startDoy: 1, endDoy: 365, calendar: 'French Republican calendar' }]); |
| 86 | }); |
| 87 | |
| 88 | it('drops eras that do not reach into the selected year', () => { |
| 89 | // 1850: the region's eras have lapsed and the global default starts 1927, |
| 90 | // so no band covers this year here. |
| 91 | expect(dayBandsForPoint(5, 5, switchover, 1850)).toEqual([]); |
| 92 | }); |
| 93 | |
| 94 | it('covers the whole year with the modern default once it applies', () => { |
| 95 | // 2000 is a leap year (divisible by 400), so 366 days. |
| 96 | const bands = dayBandsForPoint(5, 5, switchover, 2000); |
| 97 | expect(bands).toEqual([{ startDoy: 1, endDoy: 366, calendar: 'Gregorian' }]); |
| 98 | }); |
| 99 | }); |