@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,110 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createCalendarController } from '../core/controller.js';
3
+ import { installFakeFetch } from './index.js';
4
+ /** Literal fixture event (values copied from the notesdiary calendarCache test row()). */
5
+ export const LEGACY_EVENT = {
6
+ id: 'instanceId',
7
+ sourceId: 'src',
8
+ calendarId: 'cal',
9
+ summary: 'Original',
10
+ start: { dateTime: '2026-10-09T15:00:00.000Z' },
11
+ end: { dateTime: '2026-10-09T16:00:00.000Z' },
12
+ isAllDay: false,
13
+ selfResponseStatus: 'accepted',
14
+ joinUrl: null,
15
+ organizer: { self: false },
16
+ htmlLink: '',
17
+ };
18
+ const LEGACY_JSON = '[{"id":"instanceId","sourceId":"src","calendarId":"cal","summary":"Original","start":{"dateTime":"2026-10-09T15:00:00.000Z"},"end":{"dateTime":"2026-10-09T16:00:00.000Z"},"isAllDay":false,"selfResponseStatus":"accepted","joinUrl":null,"organizer":{"self":false},"htmlLink":""}]';
19
+ export function runStorageAdapterContract(factory) {
20
+ describe('StorageAdapter contract', () => {
21
+ const ev = (id) => ({ ...LEGACY_EVENT, id });
22
+ const row = (ids, tokens, selectedIds) => ({
23
+ events: ids.map(ev),
24
+ syncState: { tokens },
25
+ selectedIds,
26
+ });
27
+ /** Loads a source through the real controller (network down) and returns what it read back. */
28
+ async function readBack(h, crypto = h.crypto) {
29
+ const f = installFakeFetch(() => 'network-error');
30
+ try {
31
+ const c = createCalendarController({
32
+ sources: [{ id: 'src', email: 'a@b.c' }],
33
+ getToken: async () => 't',
34
+ storage: h.storage,
35
+ crypto,
36
+ });
37
+ await c.refresh();
38
+ return { events: c.getEvents(), selected: c.getSelectedIds('src') };
39
+ }
40
+ finally {
41
+ f.restore();
42
+ }
43
+ }
44
+ const seed = (h, id, r) => (h.seedRaw ?? ((i, x) => h.storage.put(i, x)))(id, r);
45
+ it('put/get round-trips events, syncState and selectedIds', async () => {
46
+ const { storage } = await factory();
47
+ const r = row(['a', 'b'], { cal: 'tok' }, ['cal']);
48
+ await storage.put('s1', r);
49
+ expect(await storage.get('s1')).toEqual(r);
50
+ });
51
+ it('isolates sources from each other', async () => {
52
+ const { storage } = await factory();
53
+ await storage.put('s1', row(['a'], { c: '1' }, ['c']));
54
+ await storage.put('s2', row(['b'], { c: '2' }, []));
55
+ await storage.delete('s1');
56
+ expect(await storage.get('s1')).toBeNull();
57
+ expect(await storage.get('s2')).toEqual(row(['b'], { c: '2' }, []));
58
+ });
59
+ it('delete and clear remove only the given source', async () => {
60
+ const { storage } = await factory();
61
+ await storage.put('s1', row(['a'], {}, []));
62
+ await storage.put('s2', row(['b'], {}, []));
63
+ await storage.clear('s1');
64
+ expect(await storage.get('s1')).toBeNull();
65
+ expect(await storage.get('s2')).not.toBeNull();
66
+ await storage.delete('s2');
67
+ expect(await storage.get('s2')).toBeNull();
68
+ });
69
+ it('get on a missing source resolves null', async () => {
70
+ const { storage } = await factory();
71
+ expect(await storage.get('nope')).toBeNull();
72
+ });
73
+ it('missing source reads back as empty', async () => {
74
+ const h = await factory();
75
+ expect(await readBack(h)).toEqual({ events: [], selected: [] });
76
+ });
77
+ it('reads a legacy plain (unencrypted) row', async () => {
78
+ const h = await factory();
79
+ await seed(h, 'src', { events: [LEGACY_EVENT], syncState: { tokens: { cal: 't' } }, selectedIds: ['cal'] });
80
+ expect(await readBack(h)).toEqual({ events: [LEGACY_EVENT], selected: ['cal'] });
81
+ });
82
+ it('reads a legacy encrypted row from a literal fixture', async () => {
83
+ const h = await factory();
84
+ await seed(h, 'src', {
85
+ events: h.encryptedFixture ?? LEGACY_JSON,
86
+ syncState: h.encryptedFixture ? null : '{"tokens":{"cal":"t"}}',
87
+ selectedIds: ['cal'],
88
+ });
89
+ const out = await readBack(h);
90
+ expect(out.events).toEqual([LEGACY_EVENT]);
91
+ expect(out.selected).toEqual(['cal']);
92
+ });
93
+ it('a corrupt row reads back empty without throwing', async () => {
94
+ const h = await factory();
95
+ await seed(h, 'src', h.corruptRow ?? { events: '%%not-json%%', syncState: '{{', selectedIds: 'bad' });
96
+ await expect(readBack(h)).resolves.toEqual({ events: [], selected: [] });
97
+ });
98
+ it('a wrong key reads back empty without throwing', async () => {
99
+ const h = await factory();
100
+ if (!h.wrongKeyCrypto)
101
+ return;
102
+ await h.storage.put('src', {
103
+ events: await h.crypto.encrypt(JSON.stringify([LEGACY_EVENT])),
104
+ syncState: await h.crypto.encrypt(JSON.stringify({ tokens: {} })),
105
+ selectedIds: [],
106
+ });
107
+ await expect(readBack(h, h.wrongKeyCrypto)).resolves.toEqual({ events: [], selected: [] });
108
+ });
109
+ });
110
+ }
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@open-webapp/calendar",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/open-webapp/owa",
8
+ "directory": "packages/calendar"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./testing": {
16
+ "types": "./dist/testing/index.d.ts",
17
+ "default": "./dist/testing/index.js"
18
+ },
19
+ "./styles.css": "./dist/styles.css"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc && cp src/react/styles.css dist/styles.css",
27
+ "test": "vitest run"
28
+ },
29
+ "peerDependencies": {
30
+ "react": "^19",
31
+ "vitest": "^2.1.0"
32
+ },
33
+ "devDependencies": {
34
+ "@testing-library/dom": "^10.4.0",
35
+ "@testing-library/jest-dom": "^6.6.3",
36
+ "@testing-library/react": "^16.1.0",
37
+ "@testing-library/user-event": "^14.6.8",
38
+ "@types/react": "^19.0.0",
39
+ "@types/react-dom": "^19.0.0",
40
+ "fake-indexeddb": "^6.0.0",
41
+ "jsdom": "^30.0.1",
42
+ "react": "^19.0.0",
43
+ "react-dom": "^19.0.0",
44
+ "typescript": "^5.6.0",
45
+ "vitest": "^2.1.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "vitest": {
52
+ "optional": true
53
+ }
54
+ }
55
+ }