@remit/calendar-service 0.0.18 → 0.0.19

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.
Files changed (73) hide show
  1. package/dist/accept.d.ts +71 -0
  2. package/dist/accept.js +159 -0
  3. package/dist/build.d.ts +79 -0
  4. package/dist/build.js +196 -0
  5. package/dist/correct.d.ts +4 -0
  6. package/dist/correct.js +22 -0
  7. package/dist/errors.d.ts +56 -0
  8. package/dist/errors.js +1 -0
  9. package/dist/etag.d.ts +9 -0
  10. package/{src/etag.ts → dist/etag.js} +1 -3
  11. package/dist/expand.d.ts +73 -0
  12. package/dist/expand.js +188 -0
  13. package/dist/feed.d.ts +81 -0
  14. package/dist/feed.js +157 -0
  15. package/dist/fixtures.d.ts +19 -0
  16. package/dist/fixtures.js +37 -0
  17. package/dist/index.d.ts +16 -0
  18. package/dist/index.js +15 -0
  19. package/dist/memory-store.d.ts +105 -0
  20. package/dist/memory-store.js +319 -0
  21. package/dist/parse.d.ts +39 -0
  22. package/dist/parse.js +87 -0
  23. package/dist/project.d.ts +30 -0
  24. package/dist/project.js +57 -0
  25. package/dist/put.d.ts +52 -0
  26. package/dist/put.js +78 -0
  27. package/dist/reopen.d.ts +14 -0
  28. package/dist/reopen.js +92 -0
  29. package/dist/scope.d.ts +70 -0
  30. package/dist/scope.js +687 -0
  31. package/dist/subscribe.d.ts +96 -0
  32. package/dist/subscribe.js +298 -0
  33. package/dist/suggest.d.ts +74 -0
  34. package/dist/suggest.js +138 -0
  35. package/dist/time.d.ts +76 -0
  36. package/dist/time.js +164 -0
  37. package/dist/window.d.ts +85 -0
  38. package/dist/window.js +161 -0
  39. package/package.json +40 -35
  40. package/src/accept.test.ts +0 -403
  41. package/src/accept.ts +0 -250
  42. package/src/build.test.ts +0 -113
  43. package/src/build.ts +0 -311
  44. package/src/correct.test.ts +0 -173
  45. package/src/correct.ts +0 -40
  46. package/src/errors.ts +0 -62
  47. package/src/etag.test.ts +0 -54
  48. package/src/expand.test.ts +0 -361
  49. package/src/expand.ts +0 -303
  50. package/src/feed.test.ts +0 -334
  51. package/src/feed.ts +0 -203
  52. package/src/feeds/google-secret-address.ics +0 -99
  53. package/src/fixtures.ts +0 -52
  54. package/src/index.ts +0 -128
  55. package/src/memory-store.ts +0 -485
  56. package/src/parse.test.ts +0 -264
  57. package/src/parse.ts +0 -138
  58. package/src/project.test.ts +0 -247
  59. package/src/project.ts +0 -122
  60. package/src/put.test.ts +0 -318
  61. package/src/put.ts +0 -133
  62. package/src/reopen.test.ts +0 -529
  63. package/src/reopen.ts +0 -199
  64. package/src/scope.test.ts +0 -1421
  65. package/src/scope.ts +0 -1003
  66. package/src/subscribe.test.ts +0 -582
  67. package/src/subscribe.ts +0 -433
  68. package/src/suggest.test.ts +0 -376
  69. package/src/suggest.ts +0 -244
  70. package/src/time.ts +0 -260
  71. package/src/window.test.ts +0 -648
  72. package/src/window.ts +0 -281
  73. package/tsconfig.json +0 -8
@@ -1,648 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import type {
4
- CalendarCollectionItem,
5
- CalendarEventIndexItem,
6
- CalendarObjectItem,
7
- } from "@remit/data-ports";
8
- import { deriveCalendarObjectId } from "@remit/data-ports/id";
9
- import {
10
- CalendarColor,
11
- CalendarComponentSet,
12
- CalendarSource,
13
- } from "@remit/domain-enums";
14
- import { computeEtag } from "./etag.js";
15
- import { expandCalendar } from "./expand.js";
16
- import { AMSTERDAM_VTIMEZONE, ical, singleEvent } from "./fixtures.js";
17
- import { parseCalendar } from "./parse.js";
18
- import { projectCalendar } from "./project.js";
19
- import {
20
- type CalendarWindowRepositories,
21
- listBusySpans,
22
- listCalendarInstances,
23
- listObjectInstances,
24
- mergeBusySpans,
25
- } from "./window.js";
26
-
27
- const collection = (
28
- overrides: Partial<CalendarCollectionItem> = {},
29
- ): CalendarCollectionItem => ({
30
- calendarId: "cal-1",
31
- accountConfigId: "acc-1",
32
- urlSegment: "default",
33
- displayName: "Calendar",
34
- color: CalendarColor.Cal1,
35
- componentSet: CalendarComponentSet.VeventOnly,
36
- source: CalendarSource.Default,
37
- timezone: "",
38
- syncSequence: 1,
39
- subscriptionUrl: "",
40
- subscriptionEnabled: false,
41
- subscriptionCheckedAt: 0,
42
- subscriptionFetchedAt: 0,
43
- subscriptionError: "",
44
- createdAt: 0,
45
- updatedAt: 0,
46
- ...overrides,
47
- });
48
-
49
- interface StoredResource {
50
- object: CalendarObjectItem;
51
- rows: CalendarEventIndexItem[];
52
- }
53
-
54
- /**
55
- * A resource as the write path would have stored it — the projected columns and
56
- * the occurrence rows both computed from the bytes, so a read test can never
57
- * pass against a row the writer would not have produced.
58
- */
59
- const store = async (
60
- calendar: CalendarCollectionItem,
61
- resourceName: string,
62
- icalData: string,
63
- ): Promise<StoredResource> => {
64
- const parsed = await parseCalendar(icalData);
65
- assert.ok(parsed.ok, `expected a parse, got ${JSON.stringify(parsed)}`);
66
- const projection = projectCalendar(parsed.value, calendar.timezone);
67
- assert.ok(projection.ok);
68
- const expansion = expandCalendar(parsed.value, calendar.timezone);
69
- const calendarObjectId = deriveCalendarObjectId(
70
- calendar.calendarId,
71
- resourceName,
72
- );
73
-
74
- return {
75
- object: {
76
- ...projection.value,
77
- calendarObjectId,
78
- calendarId: calendar.calendarId,
79
- resourceName,
80
- icalData,
81
- etag: computeEtag(icalData),
82
- syncSequence: 1,
83
- expandedThrough: expansion.expandedThrough,
84
- createdAt: 0,
85
- updatedAt: 0,
86
- },
87
- rows: expansion.occurrences.map((occurrence) => ({
88
- ...occurrence,
89
- calendarId: calendar.calendarId,
90
- calendarObjectId,
91
- createdAt: 0,
92
- updatedAt: 0,
93
- })),
94
- };
95
- };
96
-
97
- const repositories = (
98
- resources: StoredResource[],
99
- ): CalendarWindowRepositories => ({
100
- calendarObject: {
101
- find: async (calendarId, calendarObjectId) =>
102
- resources
103
- .map((resource) => resource.object)
104
- .find(
105
- (object) =>
106
- object.calendarId === calendarId &&
107
- object.calendarObjectId === calendarObjectId,
108
- ) ?? null,
109
- listIncompleteExpansions: async (calendarId, instant) =>
110
- resources
111
- .map((resource) => resource.object)
112
- .filter(
113
- (object) =>
114
- object.calendarId === calendarId &&
115
- object.expandedThrough !== "" &&
116
- object.expandedThrough < instant,
117
- ),
118
- },
119
- calendarEventIndex: {
120
- listByStartRange: async (calendarId, startAt, endAt) =>
121
- resources
122
- .flatMap((resource) => resource.rows)
123
- .filter(
124
- (row) =>
125
- row.calendarId === calendarId &&
126
- row.startAt >= startAt &&
127
- row.startAt < endAt,
128
- )
129
- .sort((left, right) => left.startAt.localeCompare(right.startAt)),
130
- },
131
- });
132
-
133
- describe("listCalendarInstances", () => {
134
- it("returns the occurrences that start in the window and nothing after it", async () => {
135
- const calendar = collection();
136
- const weekly = await store(
137
- calendar,
138
- "weekly.ics",
139
- ical(
140
- "BEGIN:VCALENDAR",
141
- "VERSION:2.0",
142
- "BEGIN:VEVENT",
143
- "UID:weekly@example.com",
144
- "DTSTART:20260907T090000Z",
145
- "DTEND:20260907T100000Z",
146
- "SUMMARY:Stand-up",
147
- "RRULE:FREQ=WEEKLY;COUNT=5",
148
- "END:VEVENT",
149
- "END:VCALENDAR",
150
- ),
151
- );
152
-
153
- const instances = await listCalendarInstances(
154
- repositories([weekly]),
155
- [calendar],
156
- { from: "2026-09-14T00:00:00Z", to: "2026-09-28T00:00:00Z" },
157
- );
158
-
159
- assert.deepEqual(
160
- instances.map((instance) => instance.startAt),
161
- ["2026-09-14T09:00:00Z", "2026-09-21T09:00:00Z"],
162
- );
163
- assert.equal(instances[0]?.summary, "Stand-up");
164
- assert.equal(instances[0]?.etag, weekly.object.etag);
165
- assert.equal(instances[0]?.hasRecurrence, true);
166
- });
167
-
168
- it("keeps an occurrence that began before the window and runs into it", async () => {
169
- const calendar = collection();
170
- const overnight = await store(
171
- calendar,
172
- "overnight.ics",
173
- singleEvent(
174
- "DTSTART:20260913T230000Z",
175
- "DTEND:20260914T010000Z",
176
- "SUMMARY:Deploy window",
177
- ),
178
- );
179
-
180
- const instances = await listCalendarInstances(
181
- repositories([overnight]),
182
- [calendar],
183
- { from: "2026-09-14T00:00:00Z", to: "2026-09-15T00:00:00Z" },
184
- );
185
-
186
- assert.deepEqual(
187
- instances.map((instance) => instance.summary),
188
- ["Deploy window"],
189
- );
190
- });
191
-
192
- it("leaves out an event that ended before the window opened", async () => {
193
- const calendar = collection();
194
- const earlier = await store(
195
- calendar,
196
- "earlier.ics",
197
- singleEvent("DTSTART:20260913T090000Z", "DTEND:20260913T100000Z"),
198
- );
199
-
200
- const instances = await listCalendarInstances(
201
- repositories([earlier]),
202
- [calendar],
203
- { from: "2026-09-14T00:00:00Z", to: "2026-09-15T00:00:00Z" },
204
- );
205
-
206
- assert.deepEqual(instances, []);
207
- });
208
-
209
- it("expands a series live when the stored index stops before the window", async () => {
210
- const calendar = collection();
211
- // Open-ended and old: the horizon anchors at the series' own start, so the
212
- // index runs out two years in and today is nowhere in it.
213
- const openEnded = await store(
214
- calendar,
215
- "open.ics",
216
- ical(
217
- "BEGIN:VCALENDAR",
218
- "VERSION:2.0",
219
- "BEGIN:VEVENT",
220
- "UID:open@example.com",
221
- "DTSTART:20200907T090000Z",
222
- "DTEND:20200907T100000Z",
223
- "SUMMARY:Weekly one-to-one",
224
- "RRULE:FREQ=WEEKLY",
225
- "END:VEVENT",
226
- "END:VCALENDAR",
227
- ),
228
- );
229
-
230
- assert.notEqual(
231
- openEnded.object.expandedThrough,
232
- "",
233
- "the fixture only tests anything if the index really is incomplete",
234
- );
235
- assert.equal(
236
- openEnded.rows.some((row) => row.startAt >= "2026-09-14T00:00:00Z"),
237
- false,
238
- "and the index really does stop before the window",
239
- );
240
-
241
- const instances = await listCalendarInstances(
242
- repositories([openEnded]),
243
- [calendar],
244
- { from: "2026-09-14T00:00:00Z", to: "2026-09-28T00:00:00Z" },
245
- );
246
-
247
- assert.deepEqual(
248
- instances.map((instance) => instance.startAt),
249
- ["2026-09-14T09:00:00Z", "2026-09-21T09:00:00Z"],
250
- );
251
- assert.equal(instances[0]?.summary, "Weekly one-to-one");
252
- });
253
-
254
- it("expands a series whose index is empty because its first occurrence is past the horizon", async () => {
255
- const calendar = collection();
256
- // The rule's own first instance is excluded, so the first occurrence is in
257
- // 2029 and the horizon closed in 2027. The index holds no row at all, and
258
- // the only thing that can serve the event is the live expansion.
259
- const distant = await store(
260
- calendar,
261
- "distant.ics",
262
- ical(
263
- "BEGIN:VCALENDAR",
264
- "VERSION:2.0",
265
- "BEGIN:VEVENT",
266
- "UID:distant@example.com",
267
- "DTSTART:20250301T090000Z",
268
- "DTEND:20250301T100000Z",
269
- "SUMMARY:Leap day review",
270
- "RRULE:FREQ=YEARLY;INTERVAL=4",
271
- "EXDATE:20250301T090000Z",
272
- "END:VEVENT",
273
- "END:VCALENDAR",
274
- ),
275
- );
276
-
277
- assert.deepEqual(distant.rows, [], "the index really is empty");
278
- assert.deepEqual(
279
- await repositories([distant]).calendarObject.listIncompleteExpansions(
280
- calendar.calendarId,
281
- "2029-04-01T00:00:00Z",
282
- ),
283
- [distant.object],
284
- "and the resource is still offered to the live expansion",
285
- );
286
-
287
- const instances = await listCalendarInstances(
288
- repositories([distant]),
289
- [calendar],
290
- { from: "2029-03-01T00:00:00Z", to: "2029-04-01T00:00:00Z" },
291
- );
292
-
293
- assert.deepEqual(
294
- instances.map((instance) => instance.startAt),
295
- ["2029-03-01T09:00:00Z"],
296
- );
297
- assert.equal(instances[0]?.summary, "Leap day review");
298
- });
299
-
300
- it("serves a live-expanded series from one source, never twice", async () => {
301
- const calendar = collection();
302
- const openEnded = await store(
303
- calendar,
304
- "open.ics",
305
- ical(
306
- "BEGIN:VCALENDAR",
307
- "VERSION:2.0",
308
- "BEGIN:VEVENT",
309
- "UID:open@example.com",
310
- "DTSTART:20200907T090000Z",
311
- "DTEND:20200907T100000Z",
312
- "RRULE:FREQ=WEEKLY",
313
- "END:VEVENT",
314
- "END:VCALENDAR",
315
- ),
316
- );
317
-
318
- // A window the index does cover, for a resource still marked incomplete.
319
- const instances = await listCalendarInstances(
320
- repositories([openEnded]),
321
- [calendar],
322
- { from: "2020-09-14T00:00:00Z", to: "2020-09-28T00:00:00Z" },
323
- );
324
-
325
- assert.deepEqual(
326
- instances.map((instance) => instance.startAt),
327
- ["2020-09-14T09:00:00Z", "2020-09-21T09:00:00Z"],
328
- );
329
- });
330
-
331
- it("keeps a series at its local hour across a DST transition", async () => {
332
- const calendar = collection({ timezone: "Europe/Amsterdam" });
333
- const amsterdam = await store(
334
- calendar,
335
- "amsterdam.ics",
336
- ical(
337
- "BEGIN:VCALENDAR",
338
- "VERSION:2.0",
339
- ...AMSTERDAM_VTIMEZONE,
340
- "BEGIN:VEVENT",
341
- "UID:amsterdam@example.com",
342
- "DTSTART;TZID=Europe/Amsterdam:20261015T090000",
343
- "DTEND;TZID=Europe/Amsterdam:20261015T100000",
344
- "SUMMARY:Stand-up",
345
- "RRULE:FREQ=WEEKLY;COUNT=4",
346
- "END:VEVENT",
347
- "END:VCALENDAR",
348
- ),
349
- );
350
-
351
- const instances = await listCalendarInstances(
352
- repositories([amsterdam]),
353
- [calendar],
354
- { from: "2026-10-01T00:00:00Z", to: "2026-11-30T00:00:00Z" },
355
- );
356
-
357
- assert.deepEqual(
358
- instances.map((instance) => instance.start),
359
- [
360
- "2026-10-15T09:00:00+02:00",
361
- "2026-10-22T09:00:00+02:00",
362
- "2026-10-29T09:00:00+01:00",
363
- "2026-11-05T09:00:00+01:00",
364
- ],
365
- );
366
- assert.deepEqual(
367
- instances.map((instance) => instance.startAt),
368
- [
369
- "2026-10-15T07:00:00Z",
370
- "2026-10-22T07:00:00Z",
371
- "2026-10-29T08:00:00Z",
372
- "2026-11-05T08:00:00Z",
373
- ],
374
- );
375
- });
376
- });
377
-
378
- describe("listObjectInstances", () => {
379
- const ownRows = (resource: StoredResource) => ({
380
- calendarEventIndex: {
381
- listForObject: async () => resource.rows,
382
- },
383
- });
384
-
385
- it("reads one resource's own rows where its index reaches the window", async () => {
386
- const calendar = collection();
387
- const weekly = await store(
388
- calendar,
389
- "weekly.ics",
390
- ical(
391
- "BEGIN:VCALENDAR",
392
- "VERSION:2.0",
393
- "BEGIN:VEVENT",
394
- "UID:weekly@example.com",
395
- "DTSTART:20260907T090000Z",
396
- "DTEND:20260907T100000Z",
397
- "SUMMARY:Stand-up",
398
- "RRULE:FREQ=WEEKLY;COUNT=4",
399
- "END:VEVENT",
400
- "END:VCALENDAR",
401
- ),
402
- );
403
-
404
- const instances = await listObjectInstances(
405
- ownRows(weekly),
406
- calendar,
407
- weekly.object,
408
- { from: "2026-09-10T00:00:00Z", to: "2026-09-25T00:00:00Z" },
409
- );
410
-
411
- assert.deepEqual(
412
- instances.map((instance) => instance.startAt),
413
- ["2026-09-14T09:00:00Z", "2026-09-21T09:00:00Z"],
414
- );
415
- });
416
-
417
- it("expands the resource live where its index stops before the window", async () => {
418
- const calendar = collection();
419
- const openEnded = await store(
420
- calendar,
421
- "open.ics",
422
- ical(
423
- "BEGIN:VCALENDAR",
424
- "VERSION:2.0",
425
- "BEGIN:VEVENT",
426
- "UID:open@example.com",
427
- "DTSTART:20200907T090000Z",
428
- "DTEND:20200907T100000Z",
429
- "SUMMARY:Weekly one-to-one",
430
- "RRULE:FREQ=WEEKLY",
431
- "END:VEVENT",
432
- "END:VCALENDAR",
433
- ),
434
- );
435
-
436
- const instances = await listObjectInstances(
437
- ownRows(openEnded),
438
- calendar,
439
- openEnded.object,
440
- { from: "2026-09-14T00:00:00Z", to: "2026-09-28T00:00:00Z" },
441
- );
442
-
443
- assert.deepEqual(
444
- instances.map((instance) => instance.startAt),
445
- ["2026-09-14T09:00:00Z", "2026-09-21T09:00:00Z"],
446
- );
447
- });
448
- });
449
-
450
- describe("mergeBusySpans", () => {
451
- it("collapses overlapping and touching spans", () => {
452
- assert.deepEqual(
453
- mergeBusySpans([
454
- { startMs: 30, endMs: 40 },
455
- { startMs: 0, endMs: 10 },
456
- { startMs: 5, endMs: 20 },
457
- { startMs: 40, endMs: 50 },
458
- ]),
459
- [
460
- { startMs: 0, endMs: 20 },
461
- { startMs: 30, endMs: 50 },
462
- ],
463
- );
464
- });
465
-
466
- it("keeps a span that a longer one already covers out of the result", () => {
467
- assert.deepEqual(
468
- mergeBusySpans([
469
- { startMs: 0, endMs: 100 },
470
- { startMs: 20, endMs: 30 },
471
- ]),
472
- [{ startMs: 0, endMs: 100 }],
473
- );
474
- });
475
- });
476
-
477
- describe("listBusySpans", () => {
478
- it("merges overlapping events across calendars into one stretch", async () => {
479
- const work = collection({ calendarId: "cal-work", urlSegment: "work" });
480
- const home = collection({ calendarId: "cal-home", urlSegment: "home" });
481
- const standup = await store(
482
- work,
483
- "standup.ics",
484
- singleEvent("DTSTART:20260914T090000Z", "DTEND:20260914T100000Z"),
485
- );
486
- const review = await store(
487
- home,
488
- "review.ics",
489
- singleEvent("DTSTART:20260914T093000Z", "DTEND:20260914T110000Z"),
490
- );
491
-
492
- const spans = await listBusySpans(
493
- repositories([standup, review]),
494
- [work, home],
495
- { from: "2026-09-14T00:00:00Z", to: "2026-09-15T00:00:00Z" },
496
- );
497
-
498
- assert.deepEqual(spans, [
499
- {
500
- startMs: Date.parse("2026-09-14T09:00:00Z"),
501
- endMs: Date.parse("2026-09-14T11:00:00Z"),
502
- },
503
- ]);
504
- });
505
-
506
- it("leaves out a transparent event and a cancelled one", async () => {
507
- const calendar = collection();
508
- const transparent = await store(
509
- calendar,
510
- "transparent.ics",
511
- singleEvent(
512
- "DTSTART:20260914T090000Z",
513
- "DTEND:20260914T100000Z",
514
- "TRANSP:TRANSPARENT",
515
- ),
516
- );
517
- const cancelled = await store(
518
- calendar,
519
- "cancelled.ics",
520
- singleEvent(
521
- "DTSTART:20260914T110000Z",
522
- "DTEND:20260914T120000Z",
523
- "STATUS:CANCELLED",
524
- ),
525
- );
526
- const busy = await store(
527
- calendar,
528
- "busy.ics",
529
- singleEvent("DTSTART:20260914T140000Z", "DTEND:20260914T150000Z"),
530
- );
531
-
532
- const spans = await listBusySpans(
533
- repositories([transparent, cancelled, busy]),
534
- [calendar],
535
- { from: "2026-09-14T00:00:00Z", to: "2026-09-15T00:00:00Z" },
536
- );
537
-
538
- assert.deepEqual(spans, [
539
- {
540
- startMs: Date.parse("2026-09-14T14:00:00Z"),
541
- endMs: Date.parse("2026-09-14T15:00:00Z"),
542
- },
543
- ]);
544
- });
545
-
546
- it("clips a span that runs past the window to the window", async () => {
547
- const calendar = collection();
548
- const overnight = await store(
549
- calendar,
550
- "overnight.ics",
551
- singleEvent("DTSTART:20260913T230000Z", "DTEND:20260914T010000Z"),
552
- );
553
-
554
- const spans = await listBusySpans(repositories([overnight]), [calendar], {
555
- from: "2026-09-14T00:00:00Z",
556
- to: "2026-09-15T00:00:00Z",
557
- });
558
-
559
- assert.deepEqual(spans, [
560
- {
561
- startMs: Date.parse("2026-09-14T00:00:00Z"),
562
- endMs: Date.parse("2026-09-14T01:00:00Z"),
563
- },
564
- ]);
565
- });
566
- });
567
-
568
- describe("an occurrence a client edited on its own", () => {
569
- const withOverride = (...overrideLines: string[]) =>
570
- ical(
571
- "BEGIN:VCALENDAR",
572
- "VERSION:2.0",
573
- "BEGIN:VEVENT",
574
- "UID:weekly@example.com",
575
- "DTSTART:20260907T090000Z",
576
- "DTEND:20260907T100000Z",
577
- "SUMMARY:Stand-up",
578
- "RRULE:FREQ=WEEKLY;COUNT=3",
579
- "END:VEVENT",
580
- "BEGIN:VEVENT",
581
- "UID:weekly@example.com",
582
- "RECURRENCE-ID:20260914T090000Z",
583
- "DTSTART:20260914T090000Z",
584
- "DTEND:20260914T100000Z",
585
- ...overrideLines,
586
- "END:VEVENT",
587
- "END:VCALENDAR",
588
- );
589
-
590
- const window = { from: "2026-09-01T00:00:00Z", to: "2026-09-30T00:00:00Z" };
591
-
592
- it("shows the override's summary, not the one the series carries", async () => {
593
- const calendar = collection();
594
- const stored = await store(
595
- calendar,
596
- "weekly.ics",
597
- withOverride("SUMMARY:Stand-up (with the client team)"),
598
- );
599
-
600
- const instances = await listCalendarInstances(
601
- repositories([stored]),
602
- [calendar],
603
- window,
604
- );
605
-
606
- assert.deepEqual(
607
- instances.map((instance) => instance.summary),
608
- ["Stand-up", "Stand-up (with the client team)", "Stand-up"],
609
- );
610
- });
611
-
612
- it("stops counting one cancelled occurrence as busy time", async () => {
613
- const calendar = collection();
614
- const stored = await store(
615
- calendar,
616
- "weekly.ics",
617
- withOverride("SUMMARY:Stand-up", "STATUS:CANCELLED"),
618
- );
619
-
620
- const spans = await listBusySpans(
621
- repositories([stored]),
622
- [calendar],
623
- window,
624
- );
625
-
626
- assert.deepEqual(
627
- spans.map((span) => new Date(span.startMs).toISOString()),
628
- ["2026-09-07T09:00:00.000Z", "2026-09-21T09:00:00.000Z"],
629
- );
630
- });
631
-
632
- it("stops counting one occurrence marked transparent as busy time", async () => {
633
- const calendar = collection();
634
- const stored = await store(
635
- calendar,
636
- "weekly.ics",
637
- withOverride("SUMMARY:Stand-up", "TRANSP:TRANSPARENT"),
638
- );
639
-
640
- const spans = await listBusySpans(
641
- repositories([stored]),
642
- [calendar],
643
- window,
644
- );
645
-
646
- assert.equal(spans.length, 2);
647
- });
648
- });