fuzzyworld

when-am-i / files / src / utils / resolve.test.ts

resolve.test.ts

7.0 KB · history · blame

1import { describe, it, expect } from 'vitest';
2import { resolve } from './resolve';
3import type { CalendarRegion, Geometry } from '../types';
4
5const square: Geometry = {
6 type: 'Polygon',
7 coordinates: [
8 [
9 [0, 0],
10 [10, 0],
11 [10, 10],
12 [0, 10],
13 [0, 0],
14 ],
15 ],
16};
17
18const regions: CalendarRegion[] = [
19 {
20 region: { name: 'Testland', geo: square },
21 sourceFile: 'src/data/calendars/testland.json',
22 eras: [
23 { name: 'Modern', start: '1750-01-01', end: null, calendar: 'Gregorian', usedBy: 'settlers' },
24 { name: 'Old Ways', start: null, end: '1750-01-01', calendar: 'Algonquian lunar calendar', usedBy: 'locals' },
25 ],
26 },
27];
28
29describe('resolve', () => {
30 it('returns no-region when the point is outside every region', () => {
31 expect(resolve(50, 50, { year: 1700, month: 1, day: 1 }, regions)).toEqual({
32 answers: [],
33 missReason: 'no-region',
34 });
35 });
36
37 it('returns no-era when a region matches but no era covers the date', () => {
38 const bounded: CalendarRegion[] = [
39 { region: { name: 'X', geo: square }, eras: [{ name: 'E', start: '1000-01-01', end: '1100-01-01', calendar: 'Julian' }] },
40 ];
41 expect(resolve(5, 5, { year: 1700, month: 1, day: 1 }, bounded)).toEqual({
42 answers: [],
43 missReason: 'no-era',
44 });
45 });
46
47 it('returns the matching answer with conversion', () => {
48 const { answers, missReason } = resolve(5, 5, { year: 1900, month: 1, day: 1 }, regions);
49 expect(missReason).toBeUndefined();
50 expect(answers).toHaveLength(1);
51 expect(answers[0].era.name).toBe('Modern');
52 expect(answers[0].sourceFile).toBe('src/data/calendars/testland.json');
53 expect(answers[0].conversion).toMatchObject({ system: 'Gregorian', localYearLabel: '1900', convertible: true });
54 });
55
56 it('gives the descriptive-only answer before the boundary', () => {
57 const { answers } = resolve(5, 5, { year: 1700, month: 1, day: 1 }, regions);
58 expect(answers).toHaveLength(1);
59 expect(answers[0].era.usedBy).toBe('locals');
60 expect(answers[0].conversion.convertible).toBe(false);
61 });
62
63 it('attaches the era\'s "winters since" reckoning to the conversion', () => {
64 const anchored: CalendarRegion[] = [
65 {
66 region: { name: 'Anchorland', geo: square },
67 eras: [
68 {
69 name: 'Reckoning', start: null, end: null,
70 calendar: 'Algonquian lunar calendar', usedBy: 'locals',
71 anchorEvents: [{ name: 'the great dying', date: '1616-01-01', sources: ['https://example.org/x'] }],
72 },
73 ],
74 },
75 ];
76 const { answers } = resolve(5, 5, { year: 1700, month: 7, day: 1 }, anchored);
77 expect(answers[0].conversion.reckoning).toEqual({
78 label: 'about 84 winters since the great dying',
79 sources: ['https://example.org/x'],
80 });
81 });
82
83 it('omits the reckoning when no anchor event precedes the date', () => {
84 const anchored: CalendarRegion[] = [
85 {
86 region: { name: 'Anchorland', geo: square },
87 eras: [
88 {
89 name: 'Reckoning', start: null, end: null,
90 calendar: 'Algonquian lunar calendar',
91 anchorEvents: [{ name: 'a future event', date: '1900-01-01', sources: ['https://example.org/y'] }],
92 },
93 ],
94 },
95 ];
96 const { answers } = resolve(5, 5, { year: 1700, month: 7, day: 1 }, anchored);
97 expect(answers[0].conversion.reckoning).toBeUndefined();
98 });
99
100 describe('modern global default', () => {
101 it('adds a Gregorian default anywhere for modern dates with no region', () => {
102 const { answers, missReason } = resolve(50, 50, { year: 2000, month: 6, day: 1 }, regions);
103 expect(missReason).toBeUndefined();
104 expect(answers).toHaveLength(1);
105 expect(answers[0].era.name).toBe('Modern (global civil calendar)');
106 expect(answers[0].era.usedBy).toBe('most of the world');
107 expect(answers[0].conversion).toMatchObject({ system: 'Gregorian', convertible: true });
108 expect(answers[0].sourceFile).toBeUndefined(); // not from a data file
109 });
110
111 it('still misses (no-region) for pre-modern dates outside every region', () => {
112 expect(resolve(50, 50, { year: 1700, month: 1, day: 1 }, regions)).toEqual({
113 answers: [],
114 missReason: 'no-region',
115 });
116 });
117
118 it('does not duplicate Gregorian when a region already covers the date', () => {
119 const { answers } = resolve(5, 5, { year: 1990, month: 1, day: 1 }, regions);
120 expect(answers).toHaveLength(1); // the region's own "Modern" era, no extra default
121 expect(answers[0].era.name).toBe('Modern');
122 expect(answers[0].sourceFile).toBe('src/data/calendars/testland.json');
123 });
124
125 it('shows a local calendar AND the Gregorian default together', () => {
126 const persia: CalendarRegion[] = [
127 { region: { name: 'Persia', geo: square }, eras: [{ name: 'Solar Hijri', start: '1925-03-21', end: null, calendar: 'Persian', usedBy: 'Iranians' }] },
128 ];
129 const { answers } = resolve(5, 5, { year: 2025, month: 6, day: 1 }, persia);
130 expect(answers.map((a) => a.conversion.system)).toEqual(['Persian (Solar Hijri)', 'Gregorian']);
131 });
132
133 it('falls back to the default when a region matches but its eras have lapsed', () => {
134 const bounded: CalendarRegion[] = [
135 { region: { name: 'X', geo: square }, eras: [{ name: 'E', start: '1000-01-01', end: '1100-01-01', calendar: 'Julian' }] },
136 ];
137 const { answers, missReason } = resolve(5, 5, { year: 2000, month: 1, day: 1 }, bounded);
138 expect(missReason).toBeUndefined();
139 expect(answers).toHaveLength(1);
140 expect(answers[0].era.name).toBe('Modern (global civil calendar)');
141 });
142
143 it('suppresses the default entirely when the matched era opts out (e.g. North Sentinel Island)', () => {
144 const isolated: CalendarRegion[] = [
145 {
146 region: { name: 'Isolated', geo: square },
147 eras: [{ name: 'No outside calendar', start: null, end: null, calendar: 'None documented', suppressModernDefault: true }],
148 },
149 ];
150 const { answers, missReason } = resolve(5, 5, { year: 2025, month: 6, day: 1 }, isolated);
151 expect(missReason).toBeUndefined();
152 expect(answers).toHaveLength(1); // only the region's own era, no Gregorian default appended
153 expect(answers[0].era.name).toBe('No outside calendar');
154 });
155 });
156});