@abreen/tada 1.5.1 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/globals.ts +1 -1
- package/package.json +3 -2
- package/src/code/index.ts +5 -4
- package/src/index.ts +1 -1
- package/src/navigate/index.ts +27 -21
- package/src/navigate/lifecycle.ts +1 -1
- package/src/navigate/style.scss +2 -2
- package/src/search/index.ts +19 -16
- package/src/toc/index.ts +1 -1
- package/src/top/index.ts +3 -3
- package/src/util.ts +7 -6
- package/templates/_nav.html +1 -0
- package/bin/validators.test.ts +0 -205
- package/build/build-manifest.test.ts +0 -369
- package/build/code.test.ts +0 -272
- package/build/colors.test.ts +0 -66
- package/build/globals.test.ts +0 -100
- package/build/json-schema.test.ts +0 -57
- package/build/log.test.ts +0 -117
- package/build/markdown-plugins.test.ts +0 -456
- package/build/pagefind.test.ts +0 -192
- package/build/pdf-text.test.ts +0 -52
- package/build/reachability.test.ts +0 -80
- package/build/serve.test.ts +0 -59
- package/build/toc-plugin.test.ts +0 -120
- package/build/utils/content-files.test.ts +0 -56
- package/build/utils/derive-theme.test.ts +0 -111
- package/build/utils/file-types.test.ts +0 -79
- package/build/utils/front-matter.test.ts +0 -77
- package/build/utils/include.test.ts +0 -117
- package/build/utils/paths.test.ts +0 -91
- package/build/utils/trace-layout.test.ts +0 -570
- package/build/utils/trace-svg.test.ts +0 -425
- package/build/utils/trace.test.ts +0 -169
- package/build/validate-config-links.test.ts +0 -239
- package/build/validate-internal-links-plugin.test.ts +0 -134
- package/build/watch-reachability-state.test.ts +0 -198
- package/fonts/google-sans-code/ttf/GoogleSansCodeVariable-Italic.ttf +0 -0
- package/fonts/google-sans-code/ttf/GoogleSansCodeVariable.ttf +0 -0
- package/fonts/inter/ttf/InterVariable-Italic.ttf +0 -0
- package/src/anchor/index.test.ts +0 -64
- package/src/code/index.test.ts +0 -71
- package/src/header/index.test.ts +0 -89
- package/src/navigate/eligible.test.ts +0 -101
- package/src/navigate/index.test.ts +0 -82
- package/src/navigate/lifecycle.test.ts +0 -26
- package/src/print/index.test.ts +0 -71
- package/src/question/index.test.ts +0 -82
- package/src/search/index.test.ts +0 -203
- package/src/timezone/index.test.ts +0 -400
- package/src/toc/index.test.ts +0 -237
- package/src/top/index.test.ts +0 -46
- package/src/trace/index.test.ts +0 -554
- package/src/util.test.ts +0 -135
package/src/search/index.test.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { getPdfPageNumber, getPdfBaseUrl, groupPdfResults } from './pdf-utils';
|
|
3
|
-
import type { Result } from './pdf-utils';
|
|
4
|
-
|
|
5
|
-
describe('getPdfPageNumber', () => {
|
|
6
|
-
test('returns page number from meta string', () => {
|
|
7
|
-
expect(getPdfPageNumber('/doc.pdf#page=3', '3')).toBe(3);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('prefers meta over URL hash', () => {
|
|
11
|
-
expect(getPdfPageNumber('/doc.pdf#page=5', '2')).toBe(2);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test('falls back to URL hash when meta is undefined', () => {
|
|
15
|
-
expect(getPdfPageNumber('/doc.pdf#page=7', undefined)).toBe(7);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('falls back to URL hash when meta is empty', () => {
|
|
19
|
-
expect(getPdfPageNumber('/doc.pdf#page=4', '')).toBe(4);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('falls back to URL hash when meta is not a number', () => {
|
|
23
|
-
expect(getPdfPageNumber('/doc.pdf#page=4', 'abc')).toBe(4);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('returns null when neither meta nor hash has a page', () => {
|
|
27
|
-
expect(getPdfPageNumber('/doc.pdf', undefined)).toBeNull();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('returns null for non-positive meta page number', () => {
|
|
31
|
-
expect(getPdfPageNumber('/doc.pdf', '0')).toBeNull();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('returns null for negative meta page number', () => {
|
|
35
|
-
expect(getPdfPageNumber('/doc.pdf', '-1')).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('returns null for hash page=0', () => {
|
|
39
|
-
expect(getPdfPageNumber('/doc.pdf#page=0', undefined)).toBeNull();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('parses page from hash with other params', () => {
|
|
43
|
-
expect(getPdfPageNumber('/doc.pdf#zoom=100&page=12', undefined)).toBe(12);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('handles case-insensitive page hash', () => {
|
|
47
|
-
expect(getPdfPageNumber('/doc.pdf#Page=9', undefined)).toBe(9);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('getPdfBaseUrl', () => {
|
|
52
|
-
test('returns base URL for a PDF with hash', () => {
|
|
53
|
-
expect(getPdfBaseUrl('/docs/guide.pdf#page=3')).toBe('/docs/guide.pdf');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('returns URL unchanged when no hash present', () => {
|
|
57
|
-
expect(getPdfBaseUrl('/docs/guide.pdf')).toBe('/docs/guide.pdf');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('returns null for non-PDF URL', () => {
|
|
61
|
-
expect(getPdfBaseUrl('/about/')).toBeNull();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('returns null for non-PDF URL with hash', () => {
|
|
65
|
-
expect(getPdfBaseUrl('/about/#section')).toBeNull();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('handles .PDF extension case-insensitively', () => {
|
|
69
|
-
expect(getPdfBaseUrl('/docs/GUIDE.PDF#page=1')).toBe('/docs/GUIDE.PDF');
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('groupPdfResults', () => {
|
|
74
|
-
function makeResult(overrides: Partial<Result>): Result {
|
|
75
|
-
return {
|
|
76
|
-
title: 'doc.pdf',
|
|
77
|
-
url: '/doc.pdf',
|
|
78
|
-
excerpt: '',
|
|
79
|
-
score: 1,
|
|
80
|
-
subResults: [],
|
|
81
|
-
pageNumber: null,
|
|
82
|
-
...overrides,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
test('passes through non-PDF results unchanged', () => {
|
|
87
|
-
const results: Result[] = [
|
|
88
|
-
makeResult({ title: 'About', url: '/about/', score: 5 }),
|
|
89
|
-
makeResult({ title: 'Home', url: '/', score: 3 }),
|
|
90
|
-
];
|
|
91
|
-
const grouped = groupPdfResults(results);
|
|
92
|
-
expect(grouped).toEqual(results);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('groups multiple PDF page results into one result with sub-results', () => {
|
|
96
|
-
const results: Result[] = [
|
|
97
|
-
makeResult({
|
|
98
|
-
url: '/doc.pdf#page=3',
|
|
99
|
-
score: 10,
|
|
100
|
-
pageNumber: 3,
|
|
101
|
-
excerpt: 'page 3 text',
|
|
102
|
-
}),
|
|
103
|
-
makeResult({
|
|
104
|
-
url: '/doc.pdf#page=1',
|
|
105
|
-
score: 5,
|
|
106
|
-
pageNumber: 1,
|
|
107
|
-
excerpt: 'page 1 text',
|
|
108
|
-
}),
|
|
109
|
-
];
|
|
110
|
-
const grouped = groupPdfResults(results);
|
|
111
|
-
expect(grouped).toHaveLength(1);
|
|
112
|
-
expect(grouped[0].url).toBe('/doc.pdf');
|
|
113
|
-
expect(grouped[0].score).toBe(10);
|
|
114
|
-
expect(grouped[0].subResults).toEqual([
|
|
115
|
-
{ title: 'Page 1', url: '/doc.pdf#page=1', excerpt: 'page 1 text' },
|
|
116
|
-
{ title: 'Page 3', url: '/doc.pdf#page=3', excerpt: 'page 3 text' },
|
|
117
|
-
]);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('sub-results are sorted by page number', () => {
|
|
121
|
-
const results: Result[] = [
|
|
122
|
-
makeResult({ url: '/doc.pdf#page=5', score: 1, pageNumber: 5 }),
|
|
123
|
-
makeResult({ url: '/doc.pdf#page=2', score: 8, pageNumber: 2 }),
|
|
124
|
-
makeResult({ url: '/doc.pdf#page=9', score: 3, pageNumber: 9 }),
|
|
125
|
-
];
|
|
126
|
-
const grouped = groupPdfResults(results);
|
|
127
|
-
expect(grouped[0].subResults.map(s => s.title)).toEqual([
|
|
128
|
-
'Page 2',
|
|
129
|
-
'Page 5',
|
|
130
|
-
'Page 9',
|
|
131
|
-
]);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('primary result uses highest-scoring page', () => {
|
|
135
|
-
const results: Result[] = [
|
|
136
|
-
makeResult({
|
|
137
|
-
url: '/doc.pdf#page=1',
|
|
138
|
-
score: 2,
|
|
139
|
-
pageNumber: 1,
|
|
140
|
-
excerpt: 'low',
|
|
141
|
-
}),
|
|
142
|
-
makeResult({
|
|
143
|
-
url: '/doc.pdf#page=4',
|
|
144
|
-
score: 9,
|
|
145
|
-
pageNumber: 4,
|
|
146
|
-
excerpt: 'high',
|
|
147
|
-
}),
|
|
148
|
-
];
|
|
149
|
-
const grouped = groupPdfResults(results);
|
|
150
|
-
expect(grouped[0].excerpt).toBe('high');
|
|
151
|
-
expect(grouped[0].score).toBe(9);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('mixes non-PDF and grouped PDF results sorted by score', () => {
|
|
155
|
-
const results: Result[] = [
|
|
156
|
-
makeResult({
|
|
157
|
-
title: 'About',
|
|
158
|
-
url: '/about/',
|
|
159
|
-
score: 7,
|
|
160
|
-
pageNumber: null,
|
|
161
|
-
}),
|
|
162
|
-
makeResult({ url: '/doc.pdf#page=1', score: 10, pageNumber: 1 }),
|
|
163
|
-
makeResult({ url: '/doc.pdf#page=2', score: 3, pageNumber: 2 }),
|
|
164
|
-
];
|
|
165
|
-
const grouped = groupPdfResults(results);
|
|
166
|
-
expect(grouped).toHaveLength(2);
|
|
167
|
-
expect(grouped[0].url).toBe('/doc.pdf');
|
|
168
|
-
expect(grouped[0].score).toBe(10);
|
|
169
|
-
expect(grouped[1].url).toBe('/about/');
|
|
170
|
-
expect(grouped[1].score).toBe(7);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('does not group PDF result without a page number', () => {
|
|
174
|
-
const results: Result[] = [
|
|
175
|
-
makeResult({ url: '/doc.pdf', score: 5, pageNumber: null }),
|
|
176
|
-
];
|
|
177
|
-
const grouped = groupPdfResults(results);
|
|
178
|
-
expect(grouped).toHaveLength(1);
|
|
179
|
-
expect(grouped[0].url).toBe('/doc.pdf');
|
|
180
|
-
expect(grouped[0].subResults).toEqual([]);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('groups results from different PDFs separately', () => {
|
|
184
|
-
const results: Result[] = [
|
|
185
|
-
makeResult({
|
|
186
|
-
title: 'a.pdf',
|
|
187
|
-
url: '/a.pdf#page=1',
|
|
188
|
-
score: 5,
|
|
189
|
-
pageNumber: 1,
|
|
190
|
-
}),
|
|
191
|
-
makeResult({
|
|
192
|
-
title: 'b.pdf',
|
|
193
|
-
url: '/b.pdf#page=1',
|
|
194
|
-
score: 8,
|
|
195
|
-
pageNumber: 1,
|
|
196
|
-
}),
|
|
197
|
-
];
|
|
198
|
-
const grouped = groupPdfResults(results);
|
|
199
|
-
expect(grouped).toHaveLength(2);
|
|
200
|
-
const urls = grouped.map(r => r.url).sort();
|
|
201
|
-
expect(urls).toEqual(['/a.pdf', '/b.pdf']);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
// Set globals before importing the module, since it captures
|
|
2
|
-
// __SITE_TIMEZONES__ at load time.
|
|
3
|
-
const TIMEZONES: TimeZone[] = [
|
|
4
|
-
{ value: 'America/New_York', label: 'US Eastern', abbreviation: 'ET' },
|
|
5
|
-
{ value: 'America/Chicago', label: 'US Central', abbreviation: 'CT' },
|
|
6
|
-
{ value: 'America/Los_Angeles', label: 'US Pacific', abbreviation: 'PT' },
|
|
7
|
-
];
|
|
8
|
-
(globalThis as Record<string, unknown>).__SITE_TIMEZONES__ = TIMEZONES;
|
|
9
|
-
(globalThis as Record<string, unknown>).__SITE_DEFAULT_TIMEZONE__ =
|
|
10
|
-
'America/New_York';
|
|
11
|
-
|
|
12
|
-
import { describe, expect, test } from 'bun:test';
|
|
13
|
-
import { JSDOM } from 'jsdom';
|
|
14
|
-
import mount, { detectPeriodStyle, to12Hour, normalizeHM } from './index';
|
|
15
|
-
|
|
16
|
-
function dom(bodyHtml: string) {
|
|
17
|
-
return new JSDOM(`<body>${bodyHtml}</body>`, { url: 'http://localhost/' });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function createPage(times: string[], selectedTz?: string) {
|
|
21
|
-
const options = TIMEZONES.map(
|
|
22
|
-
tz => `<option value="${tz.value}">${tz.label}</option>`,
|
|
23
|
-
).join('');
|
|
24
|
-
const timeEls = times
|
|
25
|
-
.map(t => `<time datetime="${t}">${to12Hour(...parseHM(t))}</time>`)
|
|
26
|
-
.join('\n');
|
|
27
|
-
const html = `<select class="time-zone">${options}</select>${timeEls}`;
|
|
28
|
-
const jsdom = dom(html);
|
|
29
|
-
if (selectedTz) {
|
|
30
|
-
try {
|
|
31
|
-
jsdom.window.localStorage.setItem('timezoneSelection', selectedTz);
|
|
32
|
-
} catch {
|
|
33
|
-
// ignored
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return jsdom.window;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parseHM(hhmm: string): [number, number] {
|
|
40
|
-
const [h, m] = hhmm.split(':').map(Number);
|
|
41
|
-
return [h, m];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe('detectPeriodStyle', () => {
|
|
45
|
-
test('detects uppercase "PM"', () => {
|
|
46
|
-
expect(detectPeriodStyle('5:40 PM')).toEqual(['AM', 'PM']);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('detects uppercase "AM"', () => {
|
|
50
|
-
expect(detectPeriodStyle('10:00 AM')).toEqual(['AM', 'PM']);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('detects lowercase dotted "p.m."', () => {
|
|
54
|
-
expect(detectPeriodStyle('5:40 p.m.')).toEqual(['a.m.', 'p.m.']);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('detects lowercase dotted "a.m."', () => {
|
|
58
|
-
expect(detectPeriodStyle('9:15 a.m.')).toEqual(['a.m.', 'p.m.']);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('detects lowercase "pm"', () => {
|
|
62
|
-
expect(detectPeriodStyle('5:40 pm')).toEqual(['am', 'pm']);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('detects lowercase "am"', () => {
|
|
66
|
-
expect(detectPeriodStyle('8:00 am')).toEqual(['am', 'pm']);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('detects uppercase dotted "P.M."', () => {
|
|
70
|
-
expect(detectPeriodStyle('5:40 P.M.')).toEqual(['A.M.', 'P.M.']);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('detects uppercase dotted "A.M."', () => {
|
|
74
|
-
expect(detectPeriodStyle('7:30 A.M.')).toEqual(['A.M.', 'P.M.']);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('returns default for text with no AM/PM', () => {
|
|
78
|
-
expect(detectPeriodStyle('no time here')).toBeNull();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('returns default for empty string', () => {
|
|
82
|
-
expect(detectPeriodStyle('')).toBeNull();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('to12Hour', () => {
|
|
87
|
-
test('formats afternoon time with default style', () => {
|
|
88
|
-
expect(to12Hour(17, 40)).toBe('5:40 p.m.');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('formats morning time with default style', () => {
|
|
92
|
-
expect(to12Hour(9, 5)).toBe('9:05 a.m.');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('formats with uppercase style', () => {
|
|
96
|
-
expect(to12Hour(17, 40, ['AM', 'PM'])).toBe('5:40 PM');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('formats with lowercase style', () => {
|
|
100
|
-
expect(to12Hour(0, 0, ['am', 'pm'])).toBe('12:00 am');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('noon is p.m.', () => {
|
|
104
|
-
expect(to12Hour(12, 0)).toBe('12:00 p.m.');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('formats 12:30 p.m.', () => {
|
|
108
|
-
expect(to12Hour(12, 30)).toBe('12:30 p.m.');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('midnight is a.m.', () => {
|
|
112
|
-
expect(to12Hour(0, 0)).toBe('12:00 a.m.');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('formats 23:59', () => {
|
|
116
|
-
expect(to12Hour(23, 59)).toBe('11:59 p.m.');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('omits period when style is null', () => {
|
|
120
|
-
expect(to12Hour(14, 0, null)).toBe('2:00');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('normalizeHM', () => {
|
|
125
|
-
test('normalizes minutes exceeding a day', () => {
|
|
126
|
-
expect(normalizeHM(1500)).toEqual([1, 0]);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('normalizes negative minutes', () => {
|
|
130
|
-
expect(normalizeHM(-60)).toEqual([23, 0]);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('handles zero', () => {
|
|
134
|
-
expect(normalizeHM(0)).toEqual([0, 0]);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('handles exact day boundary', () => {
|
|
138
|
-
expect(normalizeHM(1440)).toEqual([0, 0]);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('handles normal value', () => {
|
|
142
|
-
expect(normalizeHM(750)).toEqual([12, 30]);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('timezone mount', () => {
|
|
147
|
-
test('unhides and enables the select element', () => {
|
|
148
|
-
const win = createPage(['09:00']);
|
|
149
|
-
mount(win);
|
|
150
|
-
|
|
151
|
-
const sel = win.document.querySelector(
|
|
152
|
-
'select.time-zone',
|
|
153
|
-
) as HTMLSelectElement;
|
|
154
|
-
expect(sel.hidden).toBe(false);
|
|
155
|
-
expect(sel.disabled).toBe(false);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test('sets select value to default timezone', () => {
|
|
159
|
-
const win = createPage(['09:00']);
|
|
160
|
-
mount(win);
|
|
161
|
-
|
|
162
|
-
const sel = win.document.querySelector(
|
|
163
|
-
'select.time-zone',
|
|
164
|
-
) as HTMLSelectElement;
|
|
165
|
-
expect(sel.value).toBe('America/New_York');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('wraps select in a timezone-wrapper div', () => {
|
|
169
|
-
const win = createPage(['09:00']);
|
|
170
|
-
mount(win);
|
|
171
|
-
|
|
172
|
-
const wrapper = win.document.querySelector('.timezone-wrapper');
|
|
173
|
-
expect(wrapper).not.toBeNull();
|
|
174
|
-
expect(wrapper!.querySelector('select.time-zone')).not.toBeNull();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test('adds a reset button inside the wrapper', () => {
|
|
178
|
-
const win = createPage(['09:00']);
|
|
179
|
-
mount(win);
|
|
180
|
-
|
|
181
|
-
const btn = win.document.querySelector('.timezone-wrapper button');
|
|
182
|
-
expect(btn).not.toBeNull();
|
|
183
|
-
expect(btn!.className).toBe('icon-button');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('reset button is initially invisible for default timezone', () => {
|
|
187
|
-
const win = createPage(['09:00']);
|
|
188
|
-
mount(win);
|
|
189
|
-
|
|
190
|
-
const btn = win.document.querySelector(
|
|
191
|
-
'.timezone-wrapper button',
|
|
192
|
-
) as HTMLButtonElement;
|
|
193
|
-
expect(btn.style.opacity).toBe('0');
|
|
194
|
-
expect(btn.style.pointerEvents).toBe('none');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('restores timezone from localStorage', () => {
|
|
198
|
-
const win = createPage(['09:00'], 'America/Chicago');
|
|
199
|
-
mount(win);
|
|
200
|
-
|
|
201
|
-
const sel = win.document.querySelector(
|
|
202
|
-
'select.time-zone',
|
|
203
|
-
) as HTMLSelectElement;
|
|
204
|
-
expect(sel.value).toBe('America/Chicago');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test('reset button is visible when non-default timezone is stored', () => {
|
|
208
|
-
const win = createPage(['09:00'], 'America/Chicago');
|
|
209
|
-
mount(win);
|
|
210
|
-
|
|
211
|
-
const btn = win.document.querySelector(
|
|
212
|
-
'.timezone-wrapper button',
|
|
213
|
-
) as HTMLButtonElement;
|
|
214
|
-
expect(btn.style.opacity).toBe('1');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('changing select updates time elements', () => {
|
|
218
|
-
const win = createPage(['12:00']);
|
|
219
|
-
mount(win);
|
|
220
|
-
|
|
221
|
-
const sel = win.document.querySelector(
|
|
222
|
-
'select.time-zone',
|
|
223
|
-
) as HTMLSelectElement;
|
|
224
|
-
const time = win.document.querySelector('time') as HTMLTimeElement;
|
|
225
|
-
|
|
226
|
-
const before = time.innerHTML;
|
|
227
|
-
|
|
228
|
-
sel.value = 'America/Los_Angeles';
|
|
229
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
230
|
-
|
|
231
|
-
// The time text should have changed
|
|
232
|
-
expect(time.innerHTML).not.toBe(before);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('changing to non-default timezone adds is-modified class', () => {
|
|
236
|
-
const win = createPage(['12:00']);
|
|
237
|
-
mount(win);
|
|
238
|
-
|
|
239
|
-
const sel = win.document.querySelector(
|
|
240
|
-
'select.time-zone',
|
|
241
|
-
) as HTMLSelectElement;
|
|
242
|
-
const time = win.document.querySelector('time') as HTMLTimeElement;
|
|
243
|
-
|
|
244
|
-
sel.value = 'America/Los_Angeles';
|
|
245
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
246
|
-
|
|
247
|
-
expect(time.classList.contains('is-modified')).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test('changing back to default removes is-modified class', () => {
|
|
251
|
-
const win = createPage(['12:00']);
|
|
252
|
-
mount(win);
|
|
253
|
-
|
|
254
|
-
const sel = win.document.querySelector(
|
|
255
|
-
'select.time-zone',
|
|
256
|
-
) as HTMLSelectElement;
|
|
257
|
-
const time = win.document.querySelector('time') as HTMLTimeElement;
|
|
258
|
-
|
|
259
|
-
sel.value = 'America/Los_Angeles';
|
|
260
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
261
|
-
expect(time.classList.contains('is-modified')).toBe(true);
|
|
262
|
-
|
|
263
|
-
sel.value = 'America/New_York';
|
|
264
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
265
|
-
expect(time.classList.contains('is-modified')).toBe(false);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test('reset button restores default timezone', () => {
|
|
269
|
-
const win = createPage(['12:00'], 'America/Chicago');
|
|
270
|
-
mount(win);
|
|
271
|
-
|
|
272
|
-
const sel = win.document.querySelector(
|
|
273
|
-
'select.time-zone',
|
|
274
|
-
) as HTMLSelectElement;
|
|
275
|
-
const btn = win.document.querySelector(
|
|
276
|
-
'.timezone-wrapper button',
|
|
277
|
-
) as HTMLButtonElement;
|
|
278
|
-
|
|
279
|
-
expect(sel.value).toBe('America/Chicago');
|
|
280
|
-
|
|
281
|
-
btn.click();
|
|
282
|
-
|
|
283
|
-
expect(sel.value).toBe('America/New_York');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test('reset button removes localStorage entry', () => {
|
|
287
|
-
const win = createPage(['12:00'], 'America/Chicago');
|
|
288
|
-
mount(win);
|
|
289
|
-
|
|
290
|
-
const btn = win.document.querySelector(
|
|
291
|
-
'.timezone-wrapper button',
|
|
292
|
-
) as HTMLButtonElement;
|
|
293
|
-
btn.click();
|
|
294
|
-
|
|
295
|
-
expect(win.localStorage.getItem('timezoneSelection')).toBeNull();
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('changing timezone saves to localStorage', () => {
|
|
299
|
-
const win = createPage(['12:00']);
|
|
300
|
-
mount(win);
|
|
301
|
-
|
|
302
|
-
const sel = win.document.querySelector(
|
|
303
|
-
'select.time-zone',
|
|
304
|
-
) as HTMLSelectElement;
|
|
305
|
-
sel.value = 'America/Chicago';
|
|
306
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
307
|
-
|
|
308
|
-
expect(win.localStorage.getItem('timezoneSelection')).toBe(
|
|
309
|
-
'America/Chicago',
|
|
310
|
-
);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
test('selecting default timezone removes localStorage entry', () => {
|
|
314
|
-
const win = createPage(['12:00'], 'America/Chicago');
|
|
315
|
-
mount(win);
|
|
316
|
-
|
|
317
|
-
const sel = win.document.querySelector(
|
|
318
|
-
'select.time-zone',
|
|
319
|
-
) as HTMLSelectElement;
|
|
320
|
-
sel.value = 'America/New_York';
|
|
321
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
322
|
-
|
|
323
|
-
expect(win.localStorage.getItem('timezoneSelection')).toBeNull();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('time element gets title with original time on change', () => {
|
|
327
|
-
const win = createPage(['14:00']);
|
|
328
|
-
mount(win);
|
|
329
|
-
|
|
330
|
-
const sel = win.document.querySelector(
|
|
331
|
-
'select.time-zone',
|
|
332
|
-
) as HTMLSelectElement;
|
|
333
|
-
const time = win.document.querySelector('time') as HTMLTimeElement;
|
|
334
|
-
|
|
335
|
-
sel.value = 'America/Chicago';
|
|
336
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
337
|
-
|
|
338
|
-
expect(time.title).toContain('ET');
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
test('time element title is cleared when returning to default', () => {
|
|
342
|
-
const win = createPage(['14:00']);
|
|
343
|
-
mount(win);
|
|
344
|
-
|
|
345
|
-
const sel = win.document.querySelector(
|
|
346
|
-
'select.time-zone',
|
|
347
|
-
) as HTMLSelectElement;
|
|
348
|
-
const time = win.document.querySelector('time') as HTMLTimeElement;
|
|
349
|
-
|
|
350
|
-
sel.value = 'America/Chicago';
|
|
351
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
352
|
-
expect(time.title).not.toBe('');
|
|
353
|
-
|
|
354
|
-
sel.value = 'America/New_York';
|
|
355
|
-
sel.dispatchEvent(new win.Event('change'));
|
|
356
|
-
expect(time.title).toBe('');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test('ignores stored timezone not in the timezone list', () => {
|
|
360
|
-
const win = createPage(['09:00']);
|
|
361
|
-
try {
|
|
362
|
-
win.localStorage.setItem('timezoneSelection', 'Mars/Olympus_Mons');
|
|
363
|
-
} catch {
|
|
364
|
-
// ignored
|
|
365
|
-
}
|
|
366
|
-
mount(win);
|
|
367
|
-
|
|
368
|
-
const sel = win.document.querySelector(
|
|
369
|
-
'select.time-zone',
|
|
370
|
-
) as HTMLSelectElement;
|
|
371
|
-
expect(sel.value).toBe('America/New_York');
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test('returns a cleanup function', () => {
|
|
375
|
-
const win = createPage(['09:00']);
|
|
376
|
-
const cleanup = mount(win);
|
|
377
|
-
expect(typeof cleanup).toBe('function');
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
test('multiple select elements stay in sync', () => {
|
|
381
|
-
const options = TIMEZONES.map(
|
|
382
|
-
tz => `<option value="${tz.value}">${tz.label}</option>`,
|
|
383
|
-
).join('');
|
|
384
|
-
const html =
|
|
385
|
-
`<select class="time-zone">${options}</select>` +
|
|
386
|
-
`<select class="time-zone">${options}</select>` +
|
|
387
|
-
'<time datetime="12:00">12:00 p.m.</time>';
|
|
388
|
-
const win = dom(html).window;
|
|
389
|
-
mount(win);
|
|
390
|
-
|
|
391
|
-
const selects = win.document.querySelectorAll(
|
|
392
|
-
'select.time-zone',
|
|
393
|
-
) as NodeListOf<HTMLSelectElement>;
|
|
394
|
-
|
|
395
|
-
selects[0].value = 'America/Chicago';
|
|
396
|
-
selects[0].dispatchEvent(new win.Event('change'));
|
|
397
|
-
|
|
398
|
-
expect(selects[1].value).toBe('America/Chicago');
|
|
399
|
-
});
|
|
400
|
-
});
|