@open-webapp/calendar 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from 'react';
3
+ import { filterCalendarsByName, summarizeCalendarSelection } from '../core/events.js';
4
+ export function CalendarSelector({ calendars, selectedIds, onChange, heading }) {
5
+ const [open, setOpen] = useState(false);
6
+ const [query, setQuery] = useState('');
7
+ const [activeIndex, setActiveIndex] = useState(0);
8
+ const buttonRef = useRef(null);
9
+ const searchRef = useRef(null);
10
+ const rootRef = useRef(null);
11
+ const listboxId = useId();
12
+ const visibleCalendars = filterCalendarsByName(calendars, query);
13
+ const primaryId = calendars.find((calendar) => calendar.primary)?.id ?? calendars[0]?.id;
14
+ useEffect(() => {
15
+ if (open)
16
+ searchRef.current?.focus();
17
+ }, [open]);
18
+ useEffect(() => {
19
+ if (!open)
20
+ return;
21
+ function onMouseDown(event) {
22
+ if (rootRef.current && !rootRef.current.contains(event.target))
23
+ setOpen(false);
24
+ }
25
+ function onKeyDown(event) {
26
+ if (event.key === 'Escape')
27
+ setOpen(false);
28
+ }
29
+ document.addEventListener('mousedown', onMouseDown);
30
+ document.addEventListener('keydown', onKeyDown);
31
+ return () => {
32
+ document.removeEventListener('mousedown', onMouseDown);
33
+ document.removeEventListener('keydown', onKeyDown);
34
+ };
35
+ }, [open]);
36
+ function close() {
37
+ setOpen(false);
38
+ buttonRef.current?.focus();
39
+ }
40
+ function toggle(id) {
41
+ if (selectedIds.includes(id)) {
42
+ if (selectedIds.length === 1)
43
+ return;
44
+ onChange(selectedIds.filter((selectedId) => selectedId !== id));
45
+ return;
46
+ }
47
+ onChange([...selectedIds, id]);
48
+ }
49
+ function selectAll() {
50
+ const visibleIds = visibleCalendars.map((calendar) => calendar.id);
51
+ onChange([...selectedIds, ...visibleIds.filter((id) => !selectedIds.includes(id))]);
52
+ }
53
+ function clear() {
54
+ onChange(primaryId ? [primaryId] : []);
55
+ }
56
+ function handleListboxKeyDown(event) {
57
+ if (event.key === 'Escape') {
58
+ event.preventDefault();
59
+ close();
60
+ return;
61
+ }
62
+ if (visibleCalendars.length === 0)
63
+ return;
64
+ if (event.key === 'ArrowDown') {
65
+ event.preventDefault();
66
+ setActiveIndex((index) => (index + 1) % visibleCalendars.length);
67
+ return;
68
+ }
69
+ if (event.key === 'ArrowUp') {
70
+ event.preventDefault();
71
+ setActiveIndex((index) => (index - 1 + visibleCalendars.length) % visibleCalendars.length);
72
+ return;
73
+ }
74
+ if (event.key === ' ' || event.key === 'Enter') {
75
+ event.preventDefault();
76
+ const activeCalendar = visibleCalendars[activeIndex];
77
+ if (activeCalendar)
78
+ toggle(activeCalendar.id);
79
+ }
80
+ }
81
+ const label = summarizeCalendarSelection(selectedIds, calendars);
82
+ return (_jsxs("div", { className: "calendar-selector", ref: rootRef, children: [_jsx("button", { ref: buttonRef, type: "button", role: "combobox", "aria-label": label, "aria-expanded": open, "aria-controls": listboxId, "aria-haspopup": "listbox", onClick: () => {
83
+ setOpen((isOpen) => !isOpen);
84
+ setQuery('');
85
+ setActiveIndex(0);
86
+ }, children: label }), open && (_jsxs("div", { className: "calendar-selector-dropdown calendar-selector-dropdown--upward", onKeyDown: handleListboxKeyDown, children: [heading && (_jsx("div", { role: "heading", "aria-level": 3, className: "calendar-selector-heading", children: heading })), _jsx("input", { ref: searchRef, type: "search", role: "searchbox", "aria-label": "Search calendars", value: query, onChange: (event) => {
87
+ setQuery(event.target.value);
88
+ setActiveIndex(0);
89
+ } }), _jsxs("div", { className: "calendar-selector-actions", children: [_jsx("button", { type: "button", onClick: selectAll, children: "Select all" }), _jsx("button", { type: "button", onClick: clear, children: "Clear" }), _jsx("button", { type: "button", onClick: close, children: "Done" })] }), _jsx("div", { id: listboxId, role: "listbox", "aria-multiselectable": "true", "aria-activedescendant": visibleCalendars[activeIndex] ? `${listboxId}-${visibleCalendars[activeIndex].id}` : undefined, tabIndex: -1, children: calendars.length === 0 ? (_jsx("p", { children: "No calendars available" })) : visibleCalendars.length === 0 ? (_jsx("p", { children: "No calendars found" })) : (visibleCalendars.map((calendar, index) => (_jsxs("button", { type: "button", id: `${listboxId}-${calendar.id}`, role: "option", title: calendar.summary, "aria-selected": selectedIds.includes(calendar.id), "data-active": index === activeIndex || undefined, onClick: () => toggle(calendar.id), children: [_jsx("input", { type: "checkbox", className: "calendar-selector-checkbox", checked: selectedIds.includes(calendar.id), readOnly: true, tabIndex: -1, "aria-hidden": "true" }), _jsx("span", { "aria-hidden": "true", className: "calendar-selector-dot", "data-testid": `calendar-color-${calendar.id}`, style: { backgroundColor: calendar.backgroundColor } }), _jsx("span", { className: "calendar-selector-name", title: calendar.summary, children: calendar.summary })] }, calendar.id)))) })] }))] }));
90
+ }
@@ -0,0 +1,3 @@
1
+ export { CalendarPane } from './CalendarPane.js';
2
+ export type { CalendarPaneProps, RsvpSelection } from './CalendarPane.js';
3
+ export { CalendarSelector } from './CalendarSelector.js';
@@ -0,0 +1,2 @@
1
+ export { CalendarPane } from './CalendarPane.js';
2
+ export { CalendarSelector } from './CalendarSelector.js';
@@ -0,0 +1,503 @@
1
+ .calendar-pane {
2
+ --calendar-footer-height: 64px;
3
+ display: flex;
4
+ flex-direction: column;
5
+ min-height: 0;
6
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
7
+ }
8
+
9
+ .calendar-pane-scroll {
10
+ flex: 1;
11
+ min-height: 0;
12
+ overflow-y: auto;
13
+ }
14
+
15
+ .calendar-pane:has(.calendar-pane-footer) .calendar-pane-scroll {
16
+ padding-bottom: var(--calendar-footer-height);
17
+ }
18
+
19
+ .calendar-pane-footer {
20
+ position: sticky;
21
+ bottom: 0;
22
+ z-index: 3;
23
+ box-sizing: border-box;
24
+ min-height: var(--calendar-footer-height);
25
+ padding: 12px 16px;
26
+ border-top: 1px solid #eee;
27
+ background: #ffffff;
28
+ }
29
+
30
+ .calendar-pane-prompt {
31
+ display: flex;
32
+ flex-direction: column;
33
+ align-items: flex-start;
34
+ gap: 12px;
35
+ padding: 24px 16px;
36
+ }
37
+
38
+ .calendar-pane-prompt p {
39
+ margin: 0;
40
+ color: #555;
41
+ font-size: 14px;
42
+ }
43
+
44
+ .calendar-connect-button {
45
+ padding: 10px 16px;
46
+ font-size: 14px;
47
+ font-weight: 500;
48
+ background: #ffffff;
49
+ color: #00a9ce;
50
+ border: 1px solid #00a9ce;
51
+ border-radius: 6px;
52
+ cursor: pointer;
53
+ }
54
+
55
+ .calendar-connect-button:hover {
56
+ background: #f0f8fb;
57
+ }
58
+
59
+ .calendar-rsvp-chips {
60
+ display: flex;
61
+ gap: 8px;
62
+ padding: 12px 16px;
63
+ flex-wrap: wrap;
64
+ position: sticky;
65
+ top: 0;
66
+ background: #ffffff;
67
+ z-index: 2;
68
+ }
69
+
70
+ .calendar-rsvp-chip {
71
+ width: 32px;
72
+ height: 32px;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ padding: 0;
77
+ background: transparent;
78
+ color: #333;
79
+ border: 1px solid #ccc;
80
+ border-radius: 999px;
81
+ cursor: pointer;
82
+ }
83
+
84
+ .calendar-scroll-today-btn {
85
+ margin-right: 4px;
86
+ padding-right: 4px;
87
+ border-right: 1px solid #ccc;
88
+ }
89
+
90
+ .calendar-rsvp-chip:hover {
91
+ border-color: #00a9ce;
92
+ }
93
+
94
+ .calendar-rsvp-chip.selected {
95
+ background: #081a59;
96
+ color: #ffffff;
97
+ border-color: #081a59;
98
+ }
99
+
100
+ .calendar-empty-state {
101
+ padding: 24px 16px;
102
+ color: #999;
103
+ font-size: 14px;
104
+ }
105
+
106
+ .calendar-day-groups {
107
+ display: flex;
108
+ flex-direction: column;
109
+ }
110
+
111
+ .calendar-day-group {
112
+ display: flex;
113
+ flex-direction: column;
114
+ }
115
+
116
+ .calendar-day-header {
117
+ position: sticky;
118
+ top: var(--chip-row-height, 44px);
119
+ background: #f5f5f5;
120
+ color: #333;
121
+ font-size: 13px;
122
+ font-weight: 600;
123
+ padding: 8px 16px;
124
+ z-index: 1;
125
+ }
126
+
127
+ .calendar-event-allday {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 8px;
131
+ padding: 10px 16px;
132
+ border-left: 3px solid var(--rsvp-bar, transparent);
133
+ border-bottom: 1px solid #eee;
134
+ }
135
+
136
+ .calendar-event-row {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 8px;
140
+ padding: 10px 16px;
141
+ border-left: 3px solid var(--rsvp-bar, transparent);
142
+ border-bottom: 1px solid #eee;
143
+ }
144
+
145
+ .calendar-event-allday {
146
+ background: #fdf6e3;
147
+ font-style: italic;
148
+ }
149
+
150
+ .calendar-event-row--rsvp-accepted {
151
+ --rsvp-bar: #2e7d32;
152
+ }
153
+
154
+ .calendar-event-row--rsvp-tentative {
155
+ --rsvp-bar: #d97706;
156
+ }
157
+
158
+ .calendar-event-row--rsvp-needsAction {
159
+ --rsvp-bar: #2563eb;
160
+ }
161
+
162
+ .calendar-event-row--rsvp-declined {
163
+ --rsvp-bar: #dc2626;
164
+ }
165
+
166
+ .calendar-event-header {
167
+ display: flex;
168
+ justify-content: space-between;
169
+ align-items: center;
170
+ gap: 8px;
171
+ }
172
+
173
+ .calendar-event-title-link {
174
+ align-self: flex-start;
175
+ text-decoration: none;
176
+ color: inherit;
177
+ cursor: pointer;
178
+ }
179
+
180
+ .calendar-event-title-link:hover .calendar-event-title {
181
+ text-decoration: underline;
182
+ }
183
+
184
+ .calendar-event-title-wrap {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 6px;
188
+ min-width: 0;
189
+ align-self: flex-start;
190
+ }
191
+
192
+ .calendar-event-insert-link {
193
+ display: inline-flex;
194
+ align-items: center;
195
+ justify-content: center;
196
+ flex-shrink: 0;
197
+ width: 24px;
198
+ height: 24px;
199
+ padding: 0;
200
+ border: none;
201
+ border-radius: 4px;
202
+ background: transparent;
203
+ color: #888;
204
+ cursor: pointer;
205
+ }
206
+
207
+ .calendar-event-insert-link:hover {
208
+ color: #222;
209
+ background: rgba(0, 0, 0, 0.06);
210
+ }
211
+
212
+ .calendar-event-insert-link:focus-visible {
213
+ outline: 2px solid #1a73e8;
214
+ outline-offset: 1px;
215
+ color: #222;
216
+ }
217
+
218
+ .calendar-event-time {
219
+ font-size: 12px;
220
+ color: #666;
221
+ min-width: 110px;
222
+ flex-shrink: 0;
223
+ }
224
+
225
+ .calendar-event-title {
226
+ font-size: 14px;
227
+ color: #222;
228
+ }
229
+
230
+ .calendar-event-organizer {
231
+ font-size: 12px;
232
+ color: #888;
233
+ }
234
+
235
+ .calendar-event-calendar {
236
+ display: inline-flex;
237
+ align-items: center;
238
+ gap: 5px;
239
+ font-size: 12px;
240
+ color: #888;
241
+ }
242
+
243
+ .calendar-event-calendar-dot {
244
+ width: 8px;
245
+ height: 8px;
246
+ flex: 0 0 auto;
247
+ border-radius: 50%;
248
+ }
249
+
250
+ .calendar-event-join {
251
+ font-size: 12px;
252
+ font-weight: 500;
253
+ color: #00a9ce;
254
+ cursor: pointer;
255
+ text-decoration: none;
256
+ padding: 4px 8px;
257
+ border: 1px solid #00a9ce;
258
+ border-radius: 4px;
259
+ }
260
+
261
+ .calendar-event-join:hover {
262
+ background: #f0f8fb;
263
+ }
264
+
265
+ .calendar-event-row--current,
266
+ .calendar-event-row--selected {
267
+ background: #f0f8fb;
268
+ }
269
+
270
+ @media (prefers-color-scheme: dark) {
271
+ .calendar-pane-prompt p {
272
+ color: #ccc;
273
+ }
274
+
275
+ .calendar-connect-button {
276
+ background: #1a1a1a;
277
+ color: #22d0ef;
278
+ border-color: #22d0ef;
279
+ }
280
+
281
+ .calendar-connect-button:hover {
282
+ background: #2a2a2a;
283
+ }
284
+
285
+ .calendar-rsvp-chips {
286
+ background: #1a1a1a;
287
+ }
288
+
289
+ .calendar-pane-footer {
290
+ border-top-color: #333;
291
+ background: #1a1a1a;
292
+ }
293
+
294
+ .calendar-rsvp-chip {
295
+ color: #eee;
296
+ border-color: #444;
297
+ }
298
+
299
+ .calendar-scroll-today-btn {
300
+ border-right-color: #444;
301
+ }
302
+
303
+ .calendar-rsvp-chip.selected {
304
+ background: #22d0ef;
305
+ color: #081a59;
306
+ border-color: #22d0ef;
307
+ }
308
+
309
+ .calendar-day-header {
310
+ background: #222;
311
+ color: #eee;
312
+ }
313
+
314
+ .calendar-event-allday {
315
+ border-bottom-color: #333;
316
+ }
317
+
318
+ .calendar-event-row {
319
+ border-bottom-color: #333;
320
+ }
321
+
322
+ .calendar-event-allday {
323
+ background: #2a2510;
324
+ }
325
+
326
+ .calendar-event-row--rsvp-accepted {
327
+ --rsvp-bar: #4ade80;
328
+ }
329
+
330
+ .calendar-event-row--rsvp-tentative {
331
+ --rsvp-bar: #fbbf24;
332
+ }
333
+
334
+ .calendar-event-row--rsvp-needsAction {
335
+ --rsvp-bar: #60a5fa;
336
+ }
337
+
338
+ .calendar-event-row--rsvp-declined {
339
+ --rsvp-bar: #f87171;
340
+ }
341
+
342
+ .calendar-event-title {
343
+ color: #eee;
344
+ }
345
+
346
+ .calendar-event-join {
347
+ color: #22d0ef;
348
+ border-color: #22d0ef;
349
+ }
350
+
351
+ .calendar-event-join:hover {
352
+ background: #2a2a2a;
353
+ }
354
+
355
+ .calendar-event-row--current,
356
+ .calendar-event-row--selected {
357
+ background: #10262b;
358
+ }
359
+
360
+ .calendar-event-insert-link {
361
+ color: #999;
362
+ }
363
+
364
+ .calendar-event-insert-link:hover {
365
+ color: #eee;
366
+ background: rgba(255, 255, 255, 0.1);
367
+ }
368
+
369
+ .calendar-event-insert-link:focus-visible {
370
+ outline-color: #22d0ef;
371
+ color: #eee;
372
+ }
373
+ }
374
+
375
+ .calendar-selector {
376
+ position: relative;
377
+ }
378
+
379
+ .calendar-selector-dropdown {
380
+ position: absolute;
381
+ z-index: 3;
382
+ box-sizing: border-box;
383
+ min-width: 220px;
384
+ max-width: min(320px, calc(100vw - 32px));
385
+ padding: 8px;
386
+ border: 1px solid #ccc;
387
+ border-radius: 6px;
388
+ background: #fff;
389
+ color: #1a1a1a;
390
+ box-shadow: 0 4px 14px rgb(0 0 0 / 15%);
391
+ }
392
+
393
+ .calendar-selector-dropdown--upward {
394
+ right: 0;
395
+ bottom: calc(100% + 6px);
396
+ }
397
+
398
+ .calendar-selector-dropdown input[type='search'] {
399
+ box-sizing: border-box;
400
+ width: 100%;
401
+ }
402
+
403
+ .calendar-selector-checkbox {
404
+ flex: none;
405
+ margin: 0;
406
+ pointer-events: none;
407
+ accent-color: #22a6c0;
408
+ }
409
+
410
+ .calendar-selector-actions {
411
+ display: flex;
412
+ gap: 8px;
413
+ margin: 8px 0;
414
+ }
415
+
416
+ .calendar-selector-dropdown [role='listbox'] {
417
+ display: grid;
418
+ gap: 2px;
419
+ max-height: 240px;
420
+ overflow-y: auto;
421
+ }
422
+
423
+ .calendar-selector-dropdown [role='option'] {
424
+ display: flex;
425
+ align-items: center;
426
+ gap: 8px;
427
+ width: 100%;
428
+ padding: 6px;
429
+ border: 0;
430
+ background: transparent;
431
+ text-align: left;
432
+ }
433
+
434
+ .calendar-selector-dropdown [role='option'][data-active='true'] {
435
+ background: #e8f5f8;
436
+ }
437
+
438
+ .calendar-selector-dot {
439
+ flex: none;
440
+ width: 10px;
441
+ height: 10px;
442
+ border-radius: 50%;
443
+ }
444
+
445
+ .calendar-selector-name {
446
+ min-width: 0;
447
+ overflow: hidden;
448
+ text-overflow: ellipsis;
449
+ white-space: nowrap;
450
+ }
451
+
452
+ @media (prefers-color-scheme: dark) {
453
+ .calendar-selector-dropdown {
454
+ border-color: #333;
455
+ background: #1a1a1a;
456
+ color: #eee;
457
+ box-shadow: 0 4px 14px rgb(0 0 0 / 50%);
458
+ }
459
+
460
+ .calendar-selector-dropdown input[type='search'] {
461
+ border: 1px solid #444;
462
+ background: #2a2a2a;
463
+ color: #eee;
464
+ }
465
+
466
+ .calendar-selector-dropdown button {
467
+ border: 1px solid #444;
468
+ background: #2a2a2a;
469
+ color: #eee;
470
+ }
471
+
472
+ .calendar-selector-dropdown [role='option'] {
473
+ border: 0;
474
+ background: transparent;
475
+ color: #eee;
476
+ }
477
+
478
+ .calendar-selector-dropdown [role='option']:hover {
479
+ background: #2a2a2a;
480
+ }
481
+
482
+ .calendar-selector-dropdown [role='option'][data-active='true'] {
483
+ background: #10262b;
484
+ }
485
+ }
486
+
487
+ .calendar-event-source-dot {
488
+ display: inline-block;
489
+ width: 8px;
490
+ height: 8px;
491
+ border-radius: 50%;
492
+ margin-right: 5px;
493
+ flex: none;
494
+ }
495
+
496
+ .calendar-selector-heading {
497
+ margin: 0;
498
+ padding: 6px 8px 2px;
499
+ font-size: 0.75rem;
500
+ font-weight: 600;
501
+ opacity: 0.7;
502
+ overflow-wrap: anywhere;
503
+ }
@@ -0,0 +1,21 @@
1
+ import type { CryptoAdapter, StorageAdapter, StorageRow } from '../core/storage.js';
2
+ export declare function createMemoryStorage(): StorageAdapter & {
3
+ rows: Map<string, StorageRow>;
4
+ };
5
+ export declare const identityCrypto: CryptoAdapter;
6
+ export type FakeReply = {
7
+ status: number;
8
+ body?: unknown;
9
+ } | 'network-error';
10
+ /** Stubs global fetch with a router; returns the request log. Restore via the returned `restore`. */
11
+ export declare function installFakeFetch(router: (req: {
12
+ url: string;
13
+ token: string;
14
+ }, u: URL) => FakeReply): {
15
+ log: {
16
+ url: string;
17
+ token: string;
18
+ }[];
19
+ restore: () => void;
20
+ };
21
+ export * from './storageContract.js';
@@ -0,0 +1,42 @@
1
+ export function createMemoryStorage() {
2
+ const rows = new Map();
3
+ return {
4
+ rows,
5
+ async get(id) {
6
+ return rows.get(id) ?? null;
7
+ },
8
+ async put(id, row) {
9
+ rows.set(id, row);
10
+ },
11
+ async delete(id) {
12
+ rows.delete(id);
13
+ },
14
+ async clear(id) {
15
+ rows.delete(id);
16
+ },
17
+ };
18
+ }
19
+ export const identityCrypto = {
20
+ encrypt: async (s) => s,
21
+ decrypt: async (s) => s,
22
+ };
23
+ /** Stubs global fetch with a router; returns the request log. Restore via the returned `restore`. */
24
+ export function installFakeFetch(router) {
25
+ const original = globalThis.fetch;
26
+ const log = [];
27
+ globalThis.fetch = (async (input, init) => {
28
+ const url = String(input);
29
+ const auth = init?.headers?.Authorization ?? init?.headers?.authorization ?? '';
30
+ const req = { url, token: auth.replace(/^Bearer /, '') };
31
+ log.push(req);
32
+ const reply = router(req, new URL(url));
33
+ if (reply === 'network-error')
34
+ throw new TypeError('Failed to fetch');
35
+ return new Response(JSON.stringify(reply.body ?? {}), {
36
+ status: reply.status,
37
+ headers: { 'content-type': 'application/json' },
38
+ });
39
+ });
40
+ return { log, restore: () => void (globalThis.fetch = original) };
41
+ }
42
+ export * from './storageContract.js';
@@ -0,0 +1,27 @@
1
+ import type { CryptoAdapter, StorageAdapter, StorageRow } from '../core/storage.js';
2
+ import type { CalendarEvent } from '../core/types.js';
3
+ /**
4
+ * What a host supplies to run the contract.
5
+ *
6
+ * - `storage` / `crypto`: the adapters under test (fresh, empty per factory call).
7
+ * - `seedRaw` (optional): writes a row EXACTLY as given, bypassing any normalisation the
8
+ * adapter's `put` performs. Hosts whose adapter stores a different on-disk shape (e.g. an
9
+ * IndexedDB store) use this to plant legacy-format and corrupt rows. Defaults to `storage.put`.
10
+ * - `wrongKeyCrypto` (optional): a crypto adapter whose decrypt fails (or yields garbage) for
11
+ * data written by `crypto`. Omit when `crypto` is an identity transform (wrong-key case skipped).
12
+ * - `encryptedFixture` (optional): a LITERAL ciphertext string (copied from a real legacy
13
+ * capture, never generated here) that `crypto.decrypt` turns into JSON of `[LEGACY_EVENT]`.
14
+ * Defaults to the plain JSON string, which is correct for identity crypto.
15
+ * - `corruptRow` (optional): a row the host considers corrupt on disk. Defaults to garbage strings.
16
+ */
17
+ export interface StorageContractHarness {
18
+ storage: StorageAdapter;
19
+ crypto: CryptoAdapter;
20
+ seedRaw?: (sourceId: string, row: StorageRow) => Promise<void>;
21
+ wrongKeyCrypto?: CryptoAdapter;
22
+ encryptedFixture?: string;
23
+ corruptRow?: StorageRow;
24
+ }
25
+ /** Literal fixture event (values copied from the notesdiary calendarCache test row()). */
26
+ export declare const LEGACY_EVENT: CalendarEvent;
27
+ export declare function runStorageAdapterContract(factory: () => StorageContractHarness | Promise<StorageContractHarness>): void;