when-am-i / files / src / data / index.test.ts
index.test.ts
| 1 | import { describe, it, expect } from 'vitest'; |
| 2 | import { regions } from './index'; |
| 3 | import { convert } from '../utils/calendars'; |
| 4 | import { parseGregorianDate } from '../utils/julianDay'; |
| 5 | |
| 6 | // Smoke test: the data loader must validate and expose the calendar files. |
| 7 | // As more regions are added this also acts as a basic data sanity check. |
| 8 | describe('calendar data loader', () => { |
| 9 | it('loads at least one region', () => { |
| 10 | expect(regions.length).toBeGreaterThan(0); |
| 11 | }); |
| 12 | |
| 13 | it('loads the Massachusett region with named eras and Polygon geometry', () => { |
| 14 | const massachusett = regions.find((r) => r.region.name === 'Massachusett homeland'); |
| 15 | expect(massachusett).toBeDefined(); |
| 16 | expect(massachusett!.region.geo.type).toBe('Polygon'); |
| 17 | expect(massachusett!.sourceFile).toBe('src/data/calendars/massachusett.json'); |
| 18 | expect(massachusett!.eras.length).toBeGreaterThan(0); |
| 19 | for (const era of massachusett!.eras) { |
| 20 | expect(era.name).toBeTruthy(); |
| 21 | expect(era.calendar).toBeTruthy(); |
| 22 | } |
| 23 | }); |
| 24 | |
| 25 | it('every era has string|null start/end and a calendar', () => { |
| 26 | for (const { eras } of regions) { |
| 27 | for (const era of eras) { |
| 28 | expect(['string', 'object']).toContain(typeof era.start); // string or null |
| 29 | expect(['string', 'object']).toContain(typeof era.end); |
| 30 | expect(typeof era.calendar).toBe('string'); |
| 31 | } |
| 32 | } |
| 33 | }); |
| 34 | |
| 35 | // Regression guard: validate:data does not whitelist calendar names, so a |
| 36 | // typo (e.g. "Persain") would pass validation but silently make a region |
| 37 | // descriptive-only. These regions are meant to convert to a numeric year, so |
| 38 | // pin that here. The date is the midpoint of each era's covered range. |
| 39 | it.each([ |
| 40 | ['Iran (historic Persia)', 'Lunar Hijri era', 'Islamic (Umm al-Qura)'], |
| 41 | ['Iran (historic Persia)', 'Solar Hijri era', 'Persian (Solar Hijri)'], |
| 42 | ['Russian America (Alaska)', 'Russian America (Old Style)', 'Julian'], |
| 43 | ['Russian America (Alaska)', 'Alaska (United States)', 'Gregorian'], |
| 44 | ['Oregon Country', 'Joint occupation (Hudson\'s Bay Company)', 'Gregorian'], |
| 45 | ])('%s / %s converts to a numeric year (%s)', (regionName, eraName, system) => { |
| 46 | const region = regions.find((r) => r.region.name === regionName); |
| 47 | expect(region, `region "${regionName}" not loaded`).toBeDefined(); |
| 48 | const era = region!.eras.find((e) => e.name === eraName); |
| 49 | expect(era, `era "${eraName}" not found`).toBeDefined(); |
| 50 | |
| 51 | // Pick a date inside the era's range (midpoint, or near a null bound). |
| 52 | const startJd = era!.start ? parseGregorianDate(era!.start) : null; |
| 53 | const endJd = era!.end ? parseGregorianDate(era!.end) : null; |
| 54 | const year = startJd && endJd |
| 55 | ? Math.floor((startJd.year + endJd.year) / 2) |
| 56 | : startJd ? startJd.year + 10 |
| 57 | : endJd ? endJd.year - 10 |
| 58 | : 1850; |
| 59 | |
| 60 | const conversion = convert({ year, month: 7, day: 1 }, era!.calendar); |
| 61 | expect(conversion.convertible, `${regionName}/${eraName} should be convertible`).toBe(true); |
| 62 | expect(conversion.system).toBe(system); |
| 63 | }); |
| 64 | }); |