@craftguild/jscalendar 0.5.2 → 0.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/README.md +130 -94
- package/dist/index.cjs +3923 -0
- package/dist/index.d.cts +802 -0
- package/dist/index.d.mts +802 -0
- package/dist/index.mjs +3927 -0
- package/package.json +46 -31
- package/dist/__tests__/builders.test.d.ts +0 -1
- package/dist/__tests__/builders.test.js +0 -101
- package/dist/__tests__/calendar-extra.test.d.ts +0 -1
- package/dist/__tests__/calendar-extra.test.js +0 -221
- package/dist/__tests__/calendar.test.d.ts +0 -1
- package/dist/__tests__/calendar.test.js +0 -97
- package/dist/__tests__/ical-extra.test.d.ts +0 -1
- package/dist/__tests__/ical-extra.test.js +0 -87
- package/dist/__tests__/ical.test.d.ts +0 -1
- package/dist/__tests__/ical.test.js +0 -72
- package/dist/__tests__/index.test.d.ts +0 -1
- package/dist/__tests__/index.test.js +0 -9
- package/dist/__tests__/patch.test.d.ts +0 -1
- package/dist/__tests__/patch.test.js +0 -47
- package/dist/__tests__/recurrence.test.d.ts +0 -1
- package/dist/__tests__/recurrence.test.js +0 -640
- package/dist/__tests__/search.test.d.ts +0 -1
- package/dist/__tests__/search.test.js +0 -264
- package/dist/__tests__/timezones.test.d.ts +0 -1
- package/dist/__tests__/timezones.test.js +0 -12
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -127
- package/dist/__tests__/validation.test.d.ts +0 -1
- package/dist/__tests__/validation.test.js +0 -224
- package/dist/ical.d.ts +0 -13
- package/dist/ical.js +0 -270
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -2
- package/dist/jscal/base.d.ts +0 -89
- package/dist/jscal/base.js +0 -173
- package/dist/jscal/builders.d.ts +0 -183
- package/dist/jscal/builders.js +0 -287
- package/dist/jscal/constants.d.ts +0 -11
- package/dist/jscal/constants.js +0 -11
- package/dist/jscal/datetime.d.ts +0 -14
- package/dist/jscal/datetime.js +0 -42
- package/dist/jscal/defaults.d.ts +0 -31
- package/dist/jscal/defaults.js +0 -102
- package/dist/jscal/duration.d.ts +0 -43
- package/dist/jscal/duration.js +0 -75
- package/dist/jscal/event.d.ts +0 -23
- package/dist/jscal/event.js +0 -78
- package/dist/jscal/group.d.ts +0 -31
- package/dist/jscal/group.js +0 -69
- package/dist/jscal/guards.d.ts +0 -19
- package/dist/jscal/guards.js +0 -25
- package/dist/jscal/ids.d.ts +0 -11
- package/dist/jscal/ids.js +0 -77
- package/dist/jscal/normalize.d.ts +0 -32
- package/dist/jscal/normalize.js +0 -45
- package/dist/jscal/task.d.ts +0 -23
- package/dist/jscal/task.js +0 -67
- package/dist/jscal/types.d.ts +0 -38
- package/dist/jscal/types.js +0 -1
- package/dist/jscal.d.ts +0 -145
- package/dist/jscal.js +0 -126
- package/dist/patch.d.ts +0 -18
- package/dist/patch.js +0 -216
- package/dist/recurrence/constants.d.ts +0 -13
- package/dist/recurrence/constants.js +0 -13
- package/dist/recurrence/date-utils.d.ts +0 -125
- package/dist/recurrence/date-utils.js +0 -259
- package/dist/recurrence/expand.d.ts +0 -23
- package/dist/recurrence/expand.js +0 -315
- package/dist/recurrence/rule-candidates.d.ts +0 -21
- package/dist/recurrence/rule-candidates.js +0 -120
- package/dist/recurrence/rule-generate.d.ts +0 -11
- package/dist/recurrence/rule-generate.js +0 -36
- package/dist/recurrence/rule-matchers.d.ts +0 -34
- package/dist/recurrence/rule-matchers.js +0 -120
- package/dist/recurrence/rule-normalize.d.ts +0 -9
- package/dist/recurrence/rule-normalize.js +0 -57
- package/dist/recurrence/rule-selectors.d.ts +0 -7
- package/dist/recurrence/rule-selectors.js +0 -21
- package/dist/recurrence/rules.d.ts +0 -14
- package/dist/recurrence/rules.js +0 -57
- package/dist/recurrence/types.d.ts +0 -27
- package/dist/recurrence/types.js +0 -1
- package/dist/recurrence.d.ts +0 -2
- package/dist/recurrence.js +0 -1
- package/dist/search.d.ts +0 -44
- package/dist/search.js +0 -292
- package/dist/timezones/chunk_1.d.ts +0 -2
- package/dist/timezones/chunk_1.js +0 -72
- package/dist/timezones/chunk_2.d.ts +0 -2
- package/dist/timezones/chunk_2.js +0 -72
- package/dist/timezones/chunk_3.d.ts +0 -2
- package/dist/timezones/chunk_3.js +0 -72
- package/dist/timezones/chunk_4.d.ts +0 -2
- package/dist/timezones/chunk_4.js +0 -72
- package/dist/timezones/chunk_5.d.ts +0 -2
- package/dist/timezones/chunk_5.js +0 -72
- package/dist/timezones/chunk_6.d.ts +0 -2
- package/dist/timezones/chunk_6.js +0 -72
- package/dist/timezones/chunk_7.d.ts +0 -2
- package/dist/timezones/chunk_7.js +0 -6
- package/dist/timezones.d.ts +0 -9
- package/dist/timezones.js +0 -452
- package/dist/types.d.ts +0 -246
- package/dist/types.js +0 -1
- package/dist/utils.d.ts +0 -82
- package/dist/utils.js +0 -164
- package/dist/validate/asserts.d.ts +0 -155
- package/dist/validate/asserts.js +0 -381
- package/dist/validate/constants.d.ts +0 -25
- package/dist/validate/constants.js +0 -33
- package/dist/validate/error.d.ts +0 -19
- package/dist/validate/error.js +0 -25
- package/dist/validate/validators-common.d.ts +0 -64
- package/dist/validate/validators-common.js +0 -390
- package/dist/validate/validators-objects.d.ts +0 -8
- package/dist/validate/validators-objects.js +0 -70
- package/dist/validate/validators-recurrence.d.ts +0 -15
- package/dist/validate/validators-recurrence.js +0 -115
- package/dist/validate/validators.d.ts +0 -1
- package/dist/validate/validators.js +0 -1
- package/dist/validate.d.ts +0 -2
- package/dist/validate.js +0 -2
|
@@ -1,640 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { JsCal } from "../jscal.js";
|
|
3
|
-
function collect(gen) {
|
|
4
|
-
const result = [];
|
|
5
|
-
for (const item of gen) {
|
|
6
|
-
if (item["@type"] === "Event")
|
|
7
|
-
result.push(item);
|
|
8
|
-
}
|
|
9
|
-
return result;
|
|
10
|
-
}
|
|
11
|
-
describe("recurrence expansion", () => {
|
|
12
|
-
it("sorts occurrences by recurrenceId across items", () => {
|
|
13
|
-
const first = new JsCal.Event({
|
|
14
|
-
title: "First",
|
|
15
|
-
start: "2026-02-01T09:00:00",
|
|
16
|
-
});
|
|
17
|
-
const second = new JsCal.Event({
|
|
18
|
-
title: "Second",
|
|
19
|
-
start: "2026-02-03T09:00:00",
|
|
20
|
-
});
|
|
21
|
-
const occ = collect(JsCal.expandRecurrence([second, first], {
|
|
22
|
-
from: new Date("2026-02-01"),
|
|
23
|
-
to: new Date("2026-02-10"),
|
|
24
|
-
}));
|
|
25
|
-
const starts = occ.map((o) => o.recurrenceId ?? ("start" in o ? o.start : undefined));
|
|
26
|
-
expect(starts).toEqual([
|
|
27
|
-
"2026-02-01T09:00:00",
|
|
28
|
-
"2026-02-03T09:00:00",
|
|
29
|
-
]);
|
|
30
|
-
});
|
|
31
|
-
it("expands weekly byDay", () => {
|
|
32
|
-
const event = new JsCal.Event({
|
|
33
|
-
title: "Weekly",
|
|
34
|
-
start: "2026-02-04T10:30:00",
|
|
35
|
-
recurrenceRules: [
|
|
36
|
-
{ "@type": "RecurrenceRule", frequency: "weekly", byDay: [{ "@type": "NDay", day: "we" }] },
|
|
37
|
-
],
|
|
38
|
-
});
|
|
39
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
40
|
-
from: new Date("2026-02-01"),
|
|
41
|
-
to: new Date("2026-02-28"),
|
|
42
|
-
}));
|
|
43
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
44
|
-
expect(starts).toEqual([
|
|
45
|
-
"2026-02-04T10:30:00",
|
|
46
|
-
"2026-02-11T10:30:00",
|
|
47
|
-
"2026-02-18T10:30:00",
|
|
48
|
-
"2026-02-25T10:30:00",
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
it("adds implicit byDay for weekly rules", () => {
|
|
52
|
-
const event = new JsCal.Event({
|
|
53
|
-
title: "Weekly",
|
|
54
|
-
start: "2026-02-02T09:00:00",
|
|
55
|
-
recurrenceRules: [
|
|
56
|
-
{ "@type": "RecurrenceRule", frequency: "weekly" },
|
|
57
|
-
],
|
|
58
|
-
});
|
|
59
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
60
|
-
from: new Date("2026-02-01"),
|
|
61
|
-
to: new Date("2026-02-20"),
|
|
62
|
-
}));
|
|
63
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
64
|
-
expect(starts).toEqual([
|
|
65
|
-
"2026-02-02T09:00:00",
|
|
66
|
-
"2026-02-09T09:00:00",
|
|
67
|
-
"2026-02-16T09:00:00",
|
|
68
|
-
]);
|
|
69
|
-
});
|
|
70
|
-
it("respects count including the anchor", () => {
|
|
71
|
-
const event = new JsCal.Event({
|
|
72
|
-
title: "Weekly",
|
|
73
|
-
start: "2026-02-02T09:00:00",
|
|
74
|
-
recurrenceRules: [
|
|
75
|
-
{ "@type": "RecurrenceRule", frequency: "weekly", count: 2 },
|
|
76
|
-
],
|
|
77
|
-
});
|
|
78
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
79
|
-
from: new Date("2026-02-01"),
|
|
80
|
-
to: new Date("2026-03-01"),
|
|
81
|
-
}));
|
|
82
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
83
|
-
expect(starts).toEqual([
|
|
84
|
-
"2026-02-02T09:00:00",
|
|
85
|
-
"2026-02-09T09:00:00",
|
|
86
|
-
]);
|
|
87
|
-
});
|
|
88
|
-
it("applies overrides and exclusions", () => {
|
|
89
|
-
const event = new JsCal.Event({
|
|
90
|
-
title: "Weekly",
|
|
91
|
-
start: "2026-02-04T10:30:00",
|
|
92
|
-
recurrenceRules: [
|
|
93
|
-
{ "@type": "RecurrenceRule", frequency: "weekly", byDay: [{ "@type": "NDay", day: "we" }] },
|
|
94
|
-
],
|
|
95
|
-
recurrenceOverrides: {
|
|
96
|
-
"2026-02-11T10:30:00": { title: "Override" },
|
|
97
|
-
"2026-02-18T10:30:00": { excluded: true },
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
101
|
-
from: new Date("2026-02-01"),
|
|
102
|
-
to: new Date("2026-02-28"),
|
|
103
|
-
}));
|
|
104
|
-
const titles = occ.map((o) => o.title);
|
|
105
|
-
expect(titles).toEqual(["Weekly", "Override", "Weekly"]);
|
|
106
|
-
});
|
|
107
|
-
it("adds overrides outside the rule set as extra occurrences", () => {
|
|
108
|
-
const event = new JsCal.Event({
|
|
109
|
-
title: "Weekly",
|
|
110
|
-
start: "2026-02-04T10:30:00",
|
|
111
|
-
recurrenceRules: [
|
|
112
|
-
{ "@type": "RecurrenceRule", frequency: "weekly", byDay: [{ "@type": "NDay", day: "we" }] },
|
|
113
|
-
],
|
|
114
|
-
recurrenceOverrides: {
|
|
115
|
-
"2026-02-03T10:30:00": { title: "Extra" },
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
119
|
-
from: new Date("2026-02-01"),
|
|
120
|
-
to: new Date("2026-02-10"),
|
|
121
|
-
}));
|
|
122
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
123
|
-
expect(starts).toEqual([
|
|
124
|
-
"2026-02-03T10:30:00",
|
|
125
|
-
"2026-02-04T10:30:00",
|
|
126
|
-
]);
|
|
127
|
-
});
|
|
128
|
-
it("removes anchor when excluded rule matches it", () => {
|
|
129
|
-
const event = new JsCal.Event({
|
|
130
|
-
title: "Weekly",
|
|
131
|
-
start: "2026-02-04T10:30:00",
|
|
132
|
-
recurrenceRules: [
|
|
133
|
-
{ "@type": "RecurrenceRule", frequency: "weekly", byDay: [{ "@type": "NDay", day: "we" }] },
|
|
134
|
-
],
|
|
135
|
-
excludedRecurrenceRules: [
|
|
136
|
-
{ "@type": "RecurrenceRule", frequency: "weekly", byDay: [{ "@type": "NDay", day: "we" }] },
|
|
137
|
-
],
|
|
138
|
-
});
|
|
139
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
140
|
-
from: new Date("2026-02-01"),
|
|
141
|
-
to: new Date("2026-02-10"),
|
|
142
|
-
}));
|
|
143
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
144
|
-
expect(starts).toEqual([]);
|
|
145
|
-
});
|
|
146
|
-
it("supports bySetPosition on monthly rules", () => {
|
|
147
|
-
const event = new JsCal.Event({
|
|
148
|
-
title: "First Wednesday",
|
|
149
|
-
start: "2026-01-07T10:00:00",
|
|
150
|
-
recurrenceRules: [
|
|
151
|
-
{
|
|
152
|
-
"@type": "RecurrenceRule",
|
|
153
|
-
frequency: "monthly",
|
|
154
|
-
byDay: [{ "@type": "NDay", day: "we" }],
|
|
155
|
-
bySetPosition: [1],
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
});
|
|
159
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
160
|
-
from: new Date("2026-01-01"),
|
|
161
|
-
to: new Date("2026-03-31"),
|
|
162
|
-
}));
|
|
163
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
164
|
-
expect(starts).toEqual([
|
|
165
|
-
"2026-01-07T10:00:00",
|
|
166
|
-
"2026-02-04T10:00:00",
|
|
167
|
-
"2026-03-04T10:00:00",
|
|
168
|
-
]);
|
|
169
|
-
});
|
|
170
|
-
it("supports negative byMonthDay values", () => {
|
|
171
|
-
const event = new JsCal.Event({
|
|
172
|
-
title: "Last Day",
|
|
173
|
-
start: "2026-01-31T09:00:00",
|
|
174
|
-
recurrenceRules: [
|
|
175
|
-
{ "@type": "RecurrenceRule", frequency: "monthly", byMonthDay: [-1] },
|
|
176
|
-
],
|
|
177
|
-
});
|
|
178
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
179
|
-
from: new Date("2026-01-01"),
|
|
180
|
-
to: new Date("2026-03-31"),
|
|
181
|
-
}));
|
|
182
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
183
|
-
expect(starts).toEqual([
|
|
184
|
-
"2026-01-31T09:00:00",
|
|
185
|
-
"2026-02-28T09:00:00",
|
|
186
|
-
"2026-03-31T09:00:00",
|
|
187
|
-
]);
|
|
188
|
-
});
|
|
189
|
-
it("applies skip=forward for invalid month days", () => {
|
|
190
|
-
const event = new JsCal.Event({
|
|
191
|
-
title: "Day 31",
|
|
192
|
-
start: "2026-01-31T09:00:00",
|
|
193
|
-
recurrenceRules: [
|
|
194
|
-
{ "@type": "RecurrenceRule", frequency: "monthly", byMonthDay: [31], skip: "forward" },
|
|
195
|
-
],
|
|
196
|
-
});
|
|
197
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
198
|
-
from: new Date("2026-02-01"),
|
|
199
|
-
to: new Date("2026-03-31"),
|
|
200
|
-
}));
|
|
201
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
202
|
-
expect(starts).toEqual([
|
|
203
|
-
"2026-03-01T09:00:00",
|
|
204
|
-
"2026-03-31T09:00:00",
|
|
205
|
-
]);
|
|
206
|
-
});
|
|
207
|
-
it("applies skip=backward for invalid month days", () => {
|
|
208
|
-
const event = new JsCal.Event({
|
|
209
|
-
title: "Day 31",
|
|
210
|
-
start: "2026-01-31T09:00:00",
|
|
211
|
-
recurrenceRules: [
|
|
212
|
-
{ "@type": "RecurrenceRule", frequency: "monthly", byMonthDay: [31], skip: "backward" },
|
|
213
|
-
],
|
|
214
|
-
});
|
|
215
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
216
|
-
from: new Date("2026-02-01"),
|
|
217
|
-
to: new Date("2026-03-31"),
|
|
218
|
-
}));
|
|
219
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
220
|
-
expect(starts).toEqual([
|
|
221
|
-
"2026-02-28T09:00:00",
|
|
222
|
-
"2026-03-31T09:00:00",
|
|
223
|
-
]);
|
|
224
|
-
});
|
|
225
|
-
it("sets recurrenceIdTimeZone on instances", () => {
|
|
226
|
-
const event = new JsCal.Event({
|
|
227
|
-
title: "Weekly",
|
|
228
|
-
start: "2026-02-02T09:00:00",
|
|
229
|
-
timeZone: "Asia/Tokyo",
|
|
230
|
-
recurrenceRules: [
|
|
231
|
-
{ "@type": "RecurrenceRule", frequency: "weekly" },
|
|
232
|
-
],
|
|
233
|
-
});
|
|
234
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
235
|
-
from: new Date("2026-02-01"),
|
|
236
|
-
to: new Date("2026-02-10"),
|
|
237
|
-
}));
|
|
238
|
-
const zones = occ.map((o) => o.recurrenceIdTimeZone);
|
|
239
|
-
expect(zones).toEqual(["Asia/Tokyo", "Asia/Tokyo"]);
|
|
240
|
-
});
|
|
241
|
-
it("supports hourly frequency with implicit minutes and seconds", () => {
|
|
242
|
-
const event = new JsCal.Event({
|
|
243
|
-
title: "Hourly",
|
|
244
|
-
start: "2026-02-01T10:15:30",
|
|
245
|
-
recurrenceRules: [
|
|
246
|
-
{ "@type": "RecurrenceRule", frequency: "hourly", count: 3 },
|
|
247
|
-
],
|
|
248
|
-
});
|
|
249
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
250
|
-
from: new Date("2026-02-01T10:00:00"),
|
|
251
|
-
to: new Date("2026-02-01T12:30:00"),
|
|
252
|
-
}));
|
|
253
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
254
|
-
expect(starts).toEqual([
|
|
255
|
-
"2026-02-01T10:15:30",
|
|
256
|
-
"2026-02-01T11:15:30",
|
|
257
|
-
"2026-02-01T12:15:30",
|
|
258
|
-
]);
|
|
259
|
-
});
|
|
260
|
-
it("supports minutely frequency with implicit seconds", () => {
|
|
261
|
-
const event = new JsCal.Event({
|
|
262
|
-
title: "Minutely",
|
|
263
|
-
start: "2026-02-01T10:15:45",
|
|
264
|
-
recurrenceRules: [
|
|
265
|
-
{ "@type": "RecurrenceRule", frequency: "minutely", count: 2 },
|
|
266
|
-
],
|
|
267
|
-
});
|
|
268
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
269
|
-
from: new Date("2026-02-01T10:15:00"),
|
|
270
|
-
to: new Date("2026-02-01T10:17:00"),
|
|
271
|
-
}));
|
|
272
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
273
|
-
expect(starts).toEqual([
|
|
274
|
-
"2026-02-01T10:15:45",
|
|
275
|
-
"2026-02-01T10:16:45",
|
|
276
|
-
]);
|
|
277
|
-
});
|
|
278
|
-
it("supports yearly byYearDay rules", () => {
|
|
279
|
-
const event = new JsCal.Event({
|
|
280
|
-
title: "Day 32",
|
|
281
|
-
start: "2026-01-01T09:00:00",
|
|
282
|
-
recurrenceRules: [
|
|
283
|
-
{ "@type": "RecurrenceRule", frequency: "yearly", byYearDay: [32] },
|
|
284
|
-
],
|
|
285
|
-
});
|
|
286
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
287
|
-
from: new Date("2026-01-01"),
|
|
288
|
-
to: new Date("2026-02-10"),
|
|
289
|
-
}));
|
|
290
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
291
|
-
expect(starts).toEqual([
|
|
292
|
-
"2026-01-01T09:00:00",
|
|
293
|
-
"2026-02-01T09:00:00",
|
|
294
|
-
]);
|
|
295
|
-
});
|
|
296
|
-
it("supports yearly byWeekNo rules", () => {
|
|
297
|
-
const event = new JsCal.Event({
|
|
298
|
-
title: "Week 1 Thursday",
|
|
299
|
-
start: "2026-01-01T09:00:00",
|
|
300
|
-
recurrenceRules: [
|
|
301
|
-
{
|
|
302
|
-
"@type": "RecurrenceRule",
|
|
303
|
-
frequency: "yearly",
|
|
304
|
-
byWeekNo: [1],
|
|
305
|
-
byDay: [{ "@type": "NDay", day: "th" }],
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
});
|
|
309
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
310
|
-
from: new Date("2026-01-01"),
|
|
311
|
-
to: new Date("2026-01-10"),
|
|
312
|
-
}));
|
|
313
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
314
|
-
expect(starts).toEqual([
|
|
315
|
-
"2026-01-01T09:00:00",
|
|
316
|
-
]);
|
|
317
|
-
});
|
|
318
|
-
it("supports secondly frequency", () => {
|
|
319
|
-
const event = new JsCal.Event({
|
|
320
|
-
title: "Secondly",
|
|
321
|
-
start: "2026-02-01T10:15:30",
|
|
322
|
-
recurrenceRules: [
|
|
323
|
-
{ "@type": "RecurrenceRule", frequency: "secondly", count: 2 },
|
|
324
|
-
],
|
|
325
|
-
});
|
|
326
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
327
|
-
from: new Date("2026-02-01T10:15:29"),
|
|
328
|
-
to: new Date("2026-02-01T10:15:31"),
|
|
329
|
-
}));
|
|
330
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
331
|
-
expect(starts).toEqual([
|
|
332
|
-
"2026-02-01T10:15:30",
|
|
333
|
-
"2026-02-01T10:15:31",
|
|
334
|
-
]);
|
|
335
|
-
});
|
|
336
|
-
it("throws on invalid LocalDateTime", () => {
|
|
337
|
-
const event = new JsCal.Event({
|
|
338
|
-
title: "Invalid",
|
|
339
|
-
start: "2026-02-01T10:15:30",
|
|
340
|
-
recurrenceRules: [
|
|
341
|
-
{ "@type": "RecurrenceRule", frequency: "daily" },
|
|
342
|
-
],
|
|
343
|
-
});
|
|
344
|
-
const bad = { ...event.eject(), start: "invalid" };
|
|
345
|
-
expect(() => {
|
|
346
|
-
for (const _ of JsCal.expandRecurrence([bad], { from: new Date("2026-02-01"), to: new Date("2026-02-02") })) {
|
|
347
|
-
void 0;
|
|
348
|
-
}
|
|
349
|
-
}).toThrow();
|
|
350
|
-
});
|
|
351
|
-
it("supports byDay with nthOfPeriod (monthly)", () => {
|
|
352
|
-
const event = new JsCal.Event({
|
|
353
|
-
title: "Second Monday",
|
|
354
|
-
start: "2026-02-09T09:00:00",
|
|
355
|
-
recurrenceRules: [
|
|
356
|
-
{
|
|
357
|
-
"@type": "RecurrenceRule",
|
|
358
|
-
frequency: "monthly",
|
|
359
|
-
byDay: [{ "@type": "NDay", day: "mo", nthOfPeriod: 2 }],
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
});
|
|
363
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
364
|
-
from: new Date("2026-02-01"),
|
|
365
|
-
to: new Date("2026-03-31"),
|
|
366
|
-
}));
|
|
367
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
368
|
-
expect(starts).toEqual([
|
|
369
|
-
"2026-02-09T09:00:00",
|
|
370
|
-
"2026-03-09T09:00:00",
|
|
371
|
-
]);
|
|
372
|
-
});
|
|
373
|
-
it("supports byDay with nthOfPeriod (yearly)", () => {
|
|
374
|
-
const event = new JsCal.Event({
|
|
375
|
-
title: "Last Sunday of year",
|
|
376
|
-
start: "2026-12-27T09:00:00",
|
|
377
|
-
recurrenceRules: [
|
|
378
|
-
{
|
|
379
|
-
"@type": "RecurrenceRule",
|
|
380
|
-
frequency: "yearly",
|
|
381
|
-
byDay: [{ "@type": "NDay", day: "su", nthOfPeriod: -1 }],
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
});
|
|
385
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
386
|
-
from: new Date("2026-12-01"),
|
|
387
|
-
to: new Date("2026-12-31"),
|
|
388
|
-
}));
|
|
389
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
390
|
-
expect(starts).toEqual([
|
|
391
|
-
"2026-12-27T09:00:00",
|
|
392
|
-
]);
|
|
393
|
-
});
|
|
394
|
-
it("supports byMonth filters", () => {
|
|
395
|
-
const event = new JsCal.Event({
|
|
396
|
-
title: "ByMonth",
|
|
397
|
-
start: "2026-03-01T09:00:00",
|
|
398
|
-
recurrenceRules: [
|
|
399
|
-
{ "@type": "RecurrenceRule", frequency: "yearly", byMonth: ["3"], count: 2 },
|
|
400
|
-
],
|
|
401
|
-
});
|
|
402
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
403
|
-
from: new Date("2026-03-01"),
|
|
404
|
-
to: new Date("2027-03-02"),
|
|
405
|
-
}));
|
|
406
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
407
|
-
expect(starts).toEqual([
|
|
408
|
-
"2026-03-01T09:00:00",
|
|
409
|
-
"2027-03-01T09:00:00",
|
|
410
|
-
]);
|
|
411
|
-
});
|
|
412
|
-
it("converts range dates into event time zone", () => {
|
|
413
|
-
const event = new JsCal.Event({
|
|
414
|
-
title: "TZ Range",
|
|
415
|
-
start: "2026-02-01T10:00:00",
|
|
416
|
-
timeZone: "Asia/Tokyo",
|
|
417
|
-
recurrenceRules: [
|
|
418
|
-
{ "@type": "RecurrenceRule", frequency: "daily", count: 1 },
|
|
419
|
-
],
|
|
420
|
-
});
|
|
421
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
422
|
-
from: new Date("2026-02-01T01:00:00Z"),
|
|
423
|
-
to: new Date("2026-02-01T01:00:00Z"),
|
|
424
|
-
}));
|
|
425
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
426
|
-
expect(starts).toEqual([
|
|
427
|
-
"2026-02-01T10:00:00",
|
|
428
|
-
]);
|
|
429
|
-
});
|
|
430
|
-
it("supports daily frequency", () => {
|
|
431
|
-
const event = new JsCal.Event({
|
|
432
|
-
title: "Daily",
|
|
433
|
-
start: "2026-02-01T09:00:00",
|
|
434
|
-
recurrenceRules: [
|
|
435
|
-
{ "@type": "RecurrenceRule", frequency: "daily", count: 3 },
|
|
436
|
-
],
|
|
437
|
-
});
|
|
438
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
439
|
-
from: new Date("2026-02-01"),
|
|
440
|
-
to: new Date("2026-02-04"),
|
|
441
|
-
}));
|
|
442
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
443
|
-
expect(starts).toEqual([
|
|
444
|
-
"2026-02-01T09:00:00",
|
|
445
|
-
"2026-02-02T09:00:00",
|
|
446
|
-
"2026-02-03T09:00:00",
|
|
447
|
-
]);
|
|
448
|
-
});
|
|
449
|
-
it("ignores nthOfPeriod for weekly rules", () => {
|
|
450
|
-
const event = new JsCal.Event({
|
|
451
|
-
title: "Weekly Nth",
|
|
452
|
-
start: "2026-02-02T09:00:00",
|
|
453
|
-
recurrenceRules: [
|
|
454
|
-
{
|
|
455
|
-
"@type": "RecurrenceRule",
|
|
456
|
-
frequency: "weekly",
|
|
457
|
-
byDay: [{ "@type": "NDay", day: "mo", nthOfPeriod: 1 }],
|
|
458
|
-
count: 2,
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
});
|
|
462
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
463
|
-
from: new Date("2026-02-01"),
|
|
464
|
-
to: new Date("2026-02-10"),
|
|
465
|
-
}));
|
|
466
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
467
|
-
expect(starts).toEqual([
|
|
468
|
-
"2026-02-02T09:00:00",
|
|
469
|
-
]);
|
|
470
|
-
});
|
|
471
|
-
it("uses implicit byMonthDay for monthly rules", () => {
|
|
472
|
-
const event = new JsCal.Event({
|
|
473
|
-
title: "Monthly Default",
|
|
474
|
-
start: "2026-02-10T09:00:00",
|
|
475
|
-
recurrenceRules: [
|
|
476
|
-
{ "@type": "RecurrenceRule", frequency: "monthly", count: 2 },
|
|
477
|
-
],
|
|
478
|
-
});
|
|
479
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
480
|
-
from: new Date("2026-02-01"),
|
|
481
|
-
to: new Date("2026-03-31"),
|
|
482
|
-
}));
|
|
483
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
484
|
-
expect(starts).toEqual([
|
|
485
|
-
"2026-02-10T09:00:00",
|
|
486
|
-
"2026-03-10T09:00:00",
|
|
487
|
-
]);
|
|
488
|
-
});
|
|
489
|
-
it("adds byMonth defaults for yearly rules with byMonthDay", () => {
|
|
490
|
-
const event = new JsCal.Event({
|
|
491
|
-
title: "Yearly ByMonthDay",
|
|
492
|
-
start: "2026-02-01T09:00:00",
|
|
493
|
-
recurrenceRules: [
|
|
494
|
-
{ "@type": "RecurrenceRule", frequency: "yearly", byMonthDay: [1], count: 2 },
|
|
495
|
-
],
|
|
496
|
-
});
|
|
497
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
498
|
-
from: new Date("2026-02-01"),
|
|
499
|
-
to: new Date("2027-02-02"),
|
|
500
|
-
}));
|
|
501
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
502
|
-
expect(starts).toEqual([
|
|
503
|
-
"2026-02-01T09:00:00",
|
|
504
|
-
"2027-02-01T09:00:00",
|
|
505
|
-
]);
|
|
506
|
-
});
|
|
507
|
-
it("adds byDay defaults for yearly byWeekNo rules", () => {
|
|
508
|
-
const event = new JsCal.Event({
|
|
509
|
-
title: "Week 1 Default Day",
|
|
510
|
-
start: "2026-01-01T09:00:00",
|
|
511
|
-
recurrenceRules: [
|
|
512
|
-
{ "@type": "RecurrenceRule", frequency: "yearly", byWeekNo: [1], count: 1 },
|
|
513
|
-
],
|
|
514
|
-
});
|
|
515
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
516
|
-
from: new Date("2026-01-01"),
|
|
517
|
-
to: new Date("2026-01-10"),
|
|
518
|
-
}));
|
|
519
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
520
|
-
expect(starts).toEqual([
|
|
521
|
-
"2026-01-01T09:00:00",
|
|
522
|
-
]);
|
|
523
|
-
});
|
|
524
|
-
it("supports negative bySetPosition values", () => {
|
|
525
|
-
const event = new JsCal.Event({
|
|
526
|
-
title: "Last Wednesday",
|
|
527
|
-
start: "2026-01-07T10:00:00",
|
|
528
|
-
recurrenceRules: [
|
|
529
|
-
{
|
|
530
|
-
"@type": "RecurrenceRule",
|
|
531
|
-
frequency: "monthly",
|
|
532
|
-
byDay: [{ "@type": "NDay", day: "we" }],
|
|
533
|
-
bySetPosition: [-1],
|
|
534
|
-
},
|
|
535
|
-
],
|
|
536
|
-
});
|
|
537
|
-
const occ = collect(JsCal.expandRecurrence([event], {
|
|
538
|
-
from: new Date("2026-01-01"),
|
|
539
|
-
to: new Date("2026-03-31"),
|
|
540
|
-
}));
|
|
541
|
-
const starts = occ.map((o) => o.recurrenceId);
|
|
542
|
-
expect(starts).toEqual([
|
|
543
|
-
"2026-01-07T10:00:00",
|
|
544
|
-
"2026-01-28T10:00:00",
|
|
545
|
-
"2026-02-25T10:00:00",
|
|
546
|
-
"2026-03-25T10:00:00",
|
|
547
|
-
]);
|
|
548
|
-
});
|
|
549
|
-
it("expands tasks that only have due dates", () => {
|
|
550
|
-
const task = new JsCal.Task({
|
|
551
|
-
title: "Due Only",
|
|
552
|
-
due: "2026-02-01T10:00:00",
|
|
553
|
-
recurrenceRules: [
|
|
554
|
-
{ "@type": "RecurrenceRule", frequency: "daily", count: 2 },
|
|
555
|
-
],
|
|
556
|
-
recurrenceOverrides: {
|
|
557
|
-
"2026-02-02T10:00:00": { due: "2026-02-05T10:00:00" },
|
|
558
|
-
},
|
|
559
|
-
});
|
|
560
|
-
const occ = Array.from(JsCal.expandRecurrence([task], {
|
|
561
|
-
from: new Date("2026-02-01"),
|
|
562
|
-
to: new Date("2026-02-03"),
|
|
563
|
-
}));
|
|
564
|
-
expect(occ.map((o) => o.recurrenceId)).toEqual([
|
|
565
|
-
"2026-02-01T10:00:00",
|
|
566
|
-
"2026-02-02T10:00:00",
|
|
567
|
-
]);
|
|
568
|
-
// Intentional cast to Task for test-only access to due.
|
|
569
|
-
const second = occ[1];
|
|
570
|
-
expect(second?.due).toBe("2026-02-05T10:00:00");
|
|
571
|
-
});
|
|
572
|
-
it("skips tasks without start or due dates", () => {
|
|
573
|
-
const task = new JsCal.Task({ title: "No Dates" });
|
|
574
|
-
const occ = Array.from(JsCal.expandRecurrence([task], {
|
|
575
|
-
from: new Date("2026-02-01"),
|
|
576
|
-
to: new Date("2026-02-02"),
|
|
577
|
-
}));
|
|
578
|
-
expect(occ.length).toBe(0);
|
|
579
|
-
});
|
|
580
|
-
it("throws on unsupported rscale during expansion", () => {
|
|
581
|
-
const event = new JsCal.Event({
|
|
582
|
-
title: "Bad Rscale",
|
|
583
|
-
start: "2026-02-01T10:00:00",
|
|
584
|
-
recurrenceRules: [
|
|
585
|
-
{ "@type": "RecurrenceRule", frequency: "daily", rscale: "hebrew" },
|
|
586
|
-
],
|
|
587
|
-
}, { validate: false });
|
|
588
|
-
expect(() => {
|
|
589
|
-
for (const _ of JsCal.expandRecurrence([event], { from: new Date("2026-02-01"), to: new Date("2026-02-02") })) {
|
|
590
|
-
void 0;
|
|
591
|
-
}
|
|
592
|
-
}).toThrow("Unsupported rscale");
|
|
593
|
-
});
|
|
594
|
-
it("pages recurrence expansion with cursor and limit", () => {
|
|
595
|
-
const event = new JsCal.Event({
|
|
596
|
-
title: "Weekly",
|
|
597
|
-
start: "2026-02-02T09:00:00",
|
|
598
|
-
recurrenceRules: [
|
|
599
|
-
{ "@type": "RecurrenceRule", frequency: "weekly" },
|
|
600
|
-
],
|
|
601
|
-
});
|
|
602
|
-
const range = {
|
|
603
|
-
from: new Date("2026-02-01"),
|
|
604
|
-
to: new Date("2026-03-01"),
|
|
605
|
-
};
|
|
606
|
-
const page1 = JsCal.expandRecurrencePaged([event], range, { limit: 2 });
|
|
607
|
-
const page1Starts = page1.items.map((o) => o.recurrenceId);
|
|
608
|
-
expect(page1Starts).toEqual([
|
|
609
|
-
"2026-02-02T09:00:00",
|
|
610
|
-
"2026-02-09T09:00:00",
|
|
611
|
-
]);
|
|
612
|
-
expect(page1.nextCursor).toBe("2026-02-09T09:00:00");
|
|
613
|
-
const page2 = JsCal.expandRecurrencePaged([event], range, { limit: 2, cursor: page1.nextCursor });
|
|
614
|
-
const page2Starts = page2.items.map((o) => o.recurrenceId);
|
|
615
|
-
expect(page2Starts).toEqual([
|
|
616
|
-
"2026-02-16T09:00:00",
|
|
617
|
-
"2026-02-23T09:00:00",
|
|
618
|
-
]);
|
|
619
|
-
expect(page2.nextCursor).toBe("2026-02-23T09:00:00");
|
|
620
|
-
});
|
|
621
|
-
it("returns empty page when cursor is beyond range", () => {
|
|
622
|
-
const event = new JsCal.Event({
|
|
623
|
-
title: "Weekly",
|
|
624
|
-
start: "2026-02-02T09:00:00",
|
|
625
|
-
recurrenceRules: [
|
|
626
|
-
{ "@type": "RecurrenceRule", frequency: "weekly" },
|
|
627
|
-
],
|
|
628
|
-
});
|
|
629
|
-
const range = {
|
|
630
|
-
from: new Date("2026-02-01"),
|
|
631
|
-
to: new Date("2026-02-10"),
|
|
632
|
-
};
|
|
633
|
-
const page = JsCal.expandRecurrencePaged([event], range, {
|
|
634
|
-
limit: 2,
|
|
635
|
-
cursor: "2026-02-20T09:00:00",
|
|
636
|
-
});
|
|
637
|
-
expect(page.items).toEqual([]);
|
|
638
|
-
expect(page.nextCursor).toBeUndefined();
|
|
639
|
-
});
|
|
640
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|