@powerhousedao/reactor-browser 5.2.0-staging.8 → 5.3.0-staging.1

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 (89) hide show
  1. package/README.md +17 -3
  2. package/dist/src/actions/dispatch.d.ts +2 -2
  3. package/dist/src/actions/dispatch.d.ts.map +1 -1
  4. package/dist/src/actions/dispatch.js +4 -1
  5. package/dist/src/actions/dispatch.js.map +1 -1
  6. package/dist/src/actions/document.d.ts.map +1 -1
  7. package/dist/src/actions/document.js +23 -26
  8. package/dist/src/actions/document.js.map +1 -1
  9. package/dist/src/document-cache.d.ts +17 -0
  10. package/dist/src/document-cache.d.ts.map +1 -0
  11. package/dist/src/document-cache.js +143 -0
  12. package/dist/src/document-cache.js.map +1 -0
  13. package/dist/src/errors.js +1 -1
  14. package/dist/src/errors.js.map +1 -1
  15. package/dist/src/hooks/add-ph-event-handlers.d.ts.map +1 -1
  16. package/dist/src/hooks/add-ph-event-handlers.js +2 -0
  17. package/dist/src/hooks/add-ph-event-handlers.js.map +1 -1
  18. package/dist/src/hooks/dispatch.d.ts +1 -1
  19. package/dist/src/hooks/dispatch.d.ts.map +1 -1
  20. package/dist/src/hooks/dispatch.js +2 -2
  21. package/dist/src/hooks/dispatch.js.map +1 -1
  22. package/dist/src/hooks/document-by-id.d.ts +1 -1
  23. package/dist/src/hooks/document-by-id.d.ts.map +1 -1
  24. package/dist/src/hooks/document-by-id.js +3 -3
  25. package/dist/src/hooks/document-by-id.js.map +1 -1
  26. package/dist/src/hooks/document-cache.d.ts +19 -3
  27. package/dist/src/hooks/document-cache.d.ts.map +1 -1
  28. package/dist/src/hooks/document-cache.js +41 -14
  29. package/dist/src/hooks/document-cache.js.map +1 -1
  30. package/dist/src/hooks/index.d.ts +5 -4
  31. package/dist/src/hooks/index.d.ts.map +1 -1
  32. package/dist/src/hooks/index.js +5 -4
  33. package/dist/src/hooks/index.js.map +1 -1
  34. package/dist/src/hooks/selected-document.d.ts +3 -1
  35. package/dist/src/hooks/selected-document.d.ts.map +1 -1
  36. package/dist/src/hooks/selected-document.js +9 -0
  37. package/dist/src/hooks/selected-document.js.map +1 -1
  38. package/dist/src/hooks/toast.d.ts +7 -0
  39. package/dist/src/hooks/toast.d.ts.map +1 -0
  40. package/dist/src/hooks/toast.js +9 -0
  41. package/dist/src/hooks/toast.js.map +1 -0
  42. package/dist/src/hooks/vetra-packages.d.ts.map +1 -1
  43. package/dist/src/hooks/vetra-packages.js +3 -1
  44. package/dist/src/hooks/vetra-packages.js.map +1 -1
  45. package/dist/src/index.d.ts +2 -0
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +2 -0
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/pglite/drop.d.ts +1 -1
  50. package/dist/src/pglite/drop.d.ts.map +1 -1
  51. package/dist/src/pglite/drop.js +3 -2
  52. package/dist/src/pglite/drop.js.map +1 -1
  53. package/dist/src/reactor-client-document-cache.d.ts +31 -0
  54. package/dist/src/reactor-client-document-cache.d.ts.map +1 -0
  55. package/dist/src/reactor-client-document-cache.js +142 -0
  56. package/dist/src/reactor-client-document-cache.js.map +1 -0
  57. package/dist/src/reactor.d.ts +0 -3
  58. package/dist/src/reactor.d.ts.map +1 -1
  59. package/dist/src/reactor.js +0 -56
  60. package/dist/src/reactor.js.map +1 -1
  61. package/dist/src/renown/utils.d.ts.map +1 -1
  62. package/dist/src/renown/utils.js +1 -1
  63. package/dist/src/renown/utils.js.map +1 -1
  64. package/dist/src/types/documents.d.ts +10 -5
  65. package/dist/src/types/documents.d.ts.map +1 -1
  66. package/dist/src/types/global.d.ts +2 -0
  67. package/dist/src/types/global.d.ts.map +1 -1
  68. package/dist/src/types/index.d.ts +1 -0
  69. package/dist/src/types/index.d.ts.map +1 -1
  70. package/dist/src/types/toast.d.ts +9 -0
  71. package/dist/src/types/toast.d.ts.map +1 -0
  72. package/dist/src/types/toast.js +2 -0
  73. package/dist/src/types/toast.js.map +1 -0
  74. package/dist/test/document-cache.test.d.ts +2 -0
  75. package/dist/test/document-cache.test.d.ts.map +1 -0
  76. package/dist/test/document-cache.test.js +457 -0
  77. package/dist/test/document-cache.test.js.map +1 -0
  78. package/dist/test/drop.test.js +7 -9
  79. package/dist/test/drop.test.js.map +1 -1
  80. package/dist/test/getSwitchboardUrl.test.js +3 -1
  81. package/dist/test/getSwitchboardUrl.test.js.map +1 -1
  82. package/dist/test/hooks/document-cache.test.d.ts +2 -0
  83. package/dist/test/hooks/document-cache.test.d.ts.map +1 -0
  84. package/dist/test/hooks/document-cache.test.js +642 -0
  85. package/dist/test/hooks/document-cache.test.js.map +1 -0
  86. package/dist/test/switchboard.test.js +4 -2
  87. package/dist/test/switchboard.test.js.map +1 -1
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +8 -8
@@ -0,0 +1,642 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { driveDocumentModelModule, ReactorBuilder, } from "document-drive";
3
+ import { documentModelDocumentModelModule, setName, } from "document-model";
4
+ import { Suspense } from "react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { renderHook } from "vitest-browser-react";
7
+ import { DocumentCache } from "../../src/document-cache.js";
8
+ import { addDocumentCacheEventHandler, setDocumentCache, useDocument, useDocumentCache, useDocuments, useGetDocument, useGetDocumentAsync, useGetDocuments, } from "../../src/hooks/document-cache.js";
9
+ function createMockDocument(id, name = "Test Document") {
10
+ const document = documentModelDocumentModelModule.utils.createDocument();
11
+ document.header.id = id;
12
+ document.header.name = name;
13
+ return document;
14
+ }
15
+ async function createDocumentCache(documents = []) {
16
+ const legacyReactor = new ReactorBuilder([
17
+ driveDocumentModelModule,
18
+ documentModelDocumentModelModule,
19
+ ]).build();
20
+ for (const document of documents) {
21
+ await legacyReactor.addDocument(document);
22
+ }
23
+ return {
24
+ reactor: legacyReactor,
25
+ cache: new DocumentCache(legacyReactor),
26
+ };
27
+ }
28
+ function SuspenseWrapper({ children }) {
29
+ return _jsx(Suspense, { fallback: _jsx("div", { children: "Loading..." }), children: children });
30
+ }
31
+ describe("document-cache hooks", () => {
32
+ beforeEach(() => {
33
+ window.ph = {};
34
+ addDocumentCacheEventHandler();
35
+ });
36
+ afterEach(() => {
37
+ delete window.ph;
38
+ });
39
+ describe("useDocumentCache", () => {
40
+ it("should return undefined when document cache is not set", () => {
41
+ const { result } = renderHook(() => useDocumentCache());
42
+ expect(result.current).toBeUndefined();
43
+ });
44
+ it("should return document cache when set", async () => {
45
+ const { cache } = await createDocumentCache();
46
+ setDocumentCache(cache);
47
+ const { result } = renderHook(() => useDocumentCache());
48
+ expect(result.current).toBe(cache);
49
+ });
50
+ it("should update when document cache changes", async () => {
51
+ const { cache: cache1 } = await createDocumentCache();
52
+ const { cache: cache2 } = await createDocumentCache();
53
+ setDocumentCache(cache1);
54
+ const { result } = renderHook(() => useDocumentCache());
55
+ expect(result.current).toBe(cache1);
56
+ setDocumentCache(cache2);
57
+ await vi.waitFor(() => {
58
+ expect(result.current).toBe(cache2);
59
+ });
60
+ });
61
+ });
62
+ describe("useDocument", () => {
63
+ it("should return undefined when id is null", async () => {
64
+ const { cache } = await createDocumentCache();
65
+ setDocumentCache(cache);
66
+ const { result } = renderHook(() => useDocument(null), {
67
+ wrapper: SuspenseWrapper,
68
+ });
69
+ expect(result.current).toBeUndefined();
70
+ });
71
+ it("should return undefined when id is undefined", async () => {
72
+ const { cache } = await createDocumentCache();
73
+ setDocumentCache(cache);
74
+ const { result } = renderHook(() => useDocument(undefined), {
75
+ wrapper: SuspenseWrapper,
76
+ });
77
+ expect(result.current).toBeUndefined();
78
+ });
79
+ it("should return document when id is valid", async () => {
80
+ const mockDoc = createMockDocument("doc-1", "Test Document 1");
81
+ const { cache } = await createDocumentCache([mockDoc]);
82
+ setDocumentCache(cache);
83
+ const { result, rerender } = renderHook(() => useDocument("doc-1"), {
84
+ wrapper: SuspenseWrapper,
85
+ });
86
+ // Suspense hooks need rerender after the promise resolves
87
+ await vi.waitFor(() => {
88
+ rerender();
89
+ expect(result.current).toBeDefined();
90
+ expect(result.current?.header.name).toBe("Test Document 1");
91
+ });
92
+ });
93
+ it("should update when document receives new operation", async () => {
94
+ const mockDoc = createMockDocument("doc-1", "Test Document 1");
95
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
96
+ setDocumentCache(cache);
97
+ const { result, rerender } = renderHook(() => useDocument("doc-1"), {
98
+ wrapper: SuspenseWrapper,
99
+ });
100
+ // Suspense hooks need rerender after the promise resolves
101
+ await vi.waitFor(() => {
102
+ rerender();
103
+ expect(result.current).toBeDefined();
104
+ expect(result.current?.header.name).toBe("Test Document 1");
105
+ });
106
+ await reactor.addAction(mockDoc.header.id, setName("Updated Name"));
107
+ await vi.waitFor(() => {
108
+ rerender();
109
+ expect(result.current).toBeDefined();
110
+ expect(result.current?.header.name).toBe("Updated Name");
111
+ });
112
+ });
113
+ it("should throw when document is deleted", async () => {
114
+ const mockDoc = createMockDocument("doc-1", "Test Document 1");
115
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
116
+ setDocumentCache(cache);
117
+ // Track errors thrown during rendering
118
+ const errors = [];
119
+ const handler = (event) => {
120
+ const reason = event.reason;
121
+ if (reason?.message.includes("doc-1")) {
122
+ errors.push(reason);
123
+ event.preventDefault();
124
+ }
125
+ };
126
+ window.addEventListener("unhandledrejection", handler);
127
+ const { result, rerender } = renderHook(() => useDocument("doc-1"), {
128
+ wrapper: SuspenseWrapper,
129
+ });
130
+ // Wait for document to load
131
+ await vi.waitFor(() => {
132
+ rerender();
133
+ expect(result.current).toBeDefined();
134
+ expect(result.current?.header.name).toBe("Test Document 1");
135
+ });
136
+ // Delete the document
137
+ await reactor.deleteDocument(mockDoc.header.id);
138
+ // Wait for error to be thrown when trying to refetch deleted document
139
+ await vi.waitFor(() => {
140
+ expect(errors.length).toBeGreaterThan(0);
141
+ expect(errors[0].message).toContain("doc-1");
142
+ });
143
+ window.removeEventListener("unhandledrejection", handler);
144
+ });
145
+ });
146
+ describe("useDocuments", () => {
147
+ it("should return empty array when ids is null", async () => {
148
+ const { cache } = await createDocumentCache();
149
+ setDocumentCache(cache);
150
+ const { result } = renderHook(() => useDocuments(null), {
151
+ wrapper: SuspenseWrapper,
152
+ });
153
+ expect(result.current).toEqual([]);
154
+ });
155
+ it("should return empty array when ids is undefined", async () => {
156
+ const { cache } = await createDocumentCache();
157
+ setDocumentCache(cache);
158
+ const { result } = renderHook(() => useDocuments(undefined), {
159
+ wrapper: SuspenseWrapper,
160
+ });
161
+ expect(result.current).toEqual([]);
162
+ });
163
+ it("should return empty array when ids is empty", async () => {
164
+ const { cache } = await createDocumentCache();
165
+ setDocumentCache(cache);
166
+ const { result } = renderHook(() => useDocuments([]), {
167
+ wrapper: SuspenseWrapper,
168
+ });
169
+ expect(result.current).toEqual([]);
170
+ });
171
+ it("should return documents when ids are valid", async () => {
172
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
173
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
174
+ const { cache } = await createDocumentCache([mockDoc1, mockDoc2]);
175
+ setDocumentCache(cache);
176
+ const { result, rerender } = renderHook(() => useDocuments(["doc-1", "doc-2"]), {
177
+ wrapper: SuspenseWrapper,
178
+ });
179
+ // Suspense hooks need rerender after the promise resolves
180
+ await vi.waitFor(() => {
181
+ rerender();
182
+ expect(result.current).toHaveLength(2);
183
+ expect(result.current[0]?.header.name).toBe("Document 1");
184
+ expect(result.current[1]?.header.name).toBe("Document 2");
185
+ });
186
+ });
187
+ it("should update when one of the documents receives a new operation", async () => {
188
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
189
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
190
+ const { cache, reactor } = await createDocumentCache([
191
+ mockDoc1,
192
+ mockDoc2,
193
+ ]);
194
+ setDocumentCache(cache);
195
+ const { result, rerender } = renderHook(() => useDocuments(["doc-1", "doc-2"]), {
196
+ wrapper: SuspenseWrapper,
197
+ });
198
+ // Suspense hooks need rerender after the promise resolves
199
+ await vi.waitFor(() => {
200
+ rerender();
201
+ expect(result.current).toHaveLength(2);
202
+ expect(result.current[0]?.header.name).toBe("Document 1");
203
+ expect(result.current[1]?.header.name).toBe("Document 2");
204
+ });
205
+ // Update only the second document
206
+ await reactor.addAction(mockDoc2.header.id, setName("Updated Document 2"));
207
+ await vi.waitFor(() => {
208
+ rerender();
209
+ expect(result.current[0]?.header.name).toBe("Document 1");
210
+ expect(result.current[1]?.header.name).toBe("Updated Document 2");
211
+ });
212
+ });
213
+ it("should throw when one of the documents is deleted", async () => {
214
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
215
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
216
+ const { cache, reactor } = await createDocumentCache([
217
+ mockDoc1,
218
+ mockDoc2,
219
+ ]);
220
+ setDocumentCache(cache);
221
+ // Track errors thrown during rendering
222
+ const errors = [];
223
+ const handler = (event) => {
224
+ const reason = event.reason;
225
+ if (reason?.message.includes("doc-2")) {
226
+ errors.push(reason);
227
+ event.preventDefault();
228
+ }
229
+ };
230
+ window.addEventListener("unhandledrejection", handler);
231
+ const { result, rerender } = renderHook(() => useDocuments(["doc-1", "doc-2"]), {
232
+ wrapper: SuspenseWrapper,
233
+ });
234
+ // Wait for documents to load
235
+ await vi.waitFor(() => {
236
+ rerender();
237
+ expect(result.current).toHaveLength(2);
238
+ expect(result.current[0]?.header.name).toBe("Document 1");
239
+ expect(result.current[1]?.header.name).toBe("Document 2");
240
+ });
241
+ // Delete the second document
242
+ await reactor.deleteDocument(mockDoc2.header.id);
243
+ // Wait for error to be thrown when trying to refetch deleted document
244
+ await vi.waitFor(() => {
245
+ expect(errors.length).toBeGreaterThan(0);
246
+ expect(errors[0].message).toContain("doc-2");
247
+ });
248
+ window.removeEventListener("unhandledrejection", handler);
249
+ });
250
+ });
251
+ describe("useGetDocument", () => {
252
+ it("should return a function that rejects when cache is not initialized", async () => {
253
+ const { result } = renderHook(() => useGetDocument());
254
+ const getDocument = result.current;
255
+ await expect(getDocument("doc-1")).rejects.toThrow("Document cache not initialized");
256
+ });
257
+ it("should return a function that gets a document from cache", async () => {
258
+ const mockDoc = createMockDocument("doc-1", "Test Document");
259
+ const { cache } = await createDocumentCache([mockDoc]);
260
+ setDocumentCache(cache);
261
+ const { result } = renderHook(() => useGetDocument());
262
+ const getDocument = result.current;
263
+ const doc = await getDocument("doc-1");
264
+ expect(doc.header.name).toBe("Test Document");
265
+ });
266
+ it("should return the updated document when called after an operation is added", async () => {
267
+ const mockDoc = createMockDocument("doc-1", "Test Document");
268
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
269
+ setDocumentCache(cache);
270
+ const { result, act } = renderHook(() => useGetDocument());
271
+ const getDocument = result.current;
272
+ const doc = await getDocument("doc-1");
273
+ expect(doc.header.name).toBe("Test Document");
274
+ act(() => {
275
+ reactor
276
+ .addAction(mockDoc.header.id, setName("Updated Document"))
277
+ .catch((error) => {
278
+ throw error;
279
+ });
280
+ });
281
+ await vi.waitFor(async () => {
282
+ const updatedDoc = await getDocument("doc-1");
283
+ expect(updatedDoc.header.name).toBe("Updated Document");
284
+ });
285
+ });
286
+ it("should return same object returned by useDocument", async () => {
287
+ const mockDoc = createMockDocument("doc-1", "Test Document");
288
+ const { cache } = await createDocumentCache([mockDoc]);
289
+ setDocumentCache(cache);
290
+ const { result, rerender } = renderHook(() => useDocument("doc-1"), {
291
+ wrapper: SuspenseWrapper,
292
+ });
293
+ const { result: getResult } = renderHook(() => useGetDocument());
294
+ // Suspense hooks need rerender after the promise resolves
295
+ await vi.waitFor(() => {
296
+ rerender();
297
+ expect(result.current).toBeDefined();
298
+ expect(result.current?.header.name).toBe("Test Document");
299
+ });
300
+ const getDocument = getResult.current;
301
+ const doc = await getDocument("doc-1");
302
+ expect(doc.header.name).toBe("Test Document");
303
+ expect(doc).toBe(result.current);
304
+ });
305
+ it("should return latest state of document", async () => {
306
+ const mockDoc = createMockDocument("doc-1", "Test Document");
307
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
308
+ setDocumentCache(cache);
309
+ const { result, rerender, unmount } = renderHook(() => useDocument("doc-1"), {
310
+ wrapper: SuspenseWrapper,
311
+ });
312
+ await vi.waitFor(() => {
313
+ rerender();
314
+ expect(result.current).toBeDefined();
315
+ expect(result.current?.header.name).toBe("Test Document");
316
+ });
317
+ unmount();
318
+ const { result: getResult, rerender: getRerender } = renderHook(() => useGetDocument());
319
+ const getDocument = getResult.current;
320
+ await reactor.addAction(mockDoc.header.id, setName("Updated Document"));
321
+ await vi.waitFor(async () => {
322
+ getRerender();
323
+ const doc = await getDocument("doc-1");
324
+ expect(doc.header.name).toBe("Updated Document");
325
+ });
326
+ });
327
+ it("should reject when document is deleted", async () => {
328
+ const mockDoc = createMockDocument("doc-1", "Test Document");
329
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
330
+ setDocumentCache(cache);
331
+ const { result } = renderHook(() => useGetDocument());
332
+ const getDocument = result.current;
333
+ const doc = await getDocument("doc-1");
334
+ expect(doc.header.name).toBe("Test Document");
335
+ // Delete the document
336
+ await reactor.deleteDocument(mockDoc.header.id);
337
+ // Subsequent calls should reject
338
+ await expect(getDocument("doc-1")).rejects.toThrow();
339
+ });
340
+ });
341
+ describe("useGetDocuments", () => {
342
+ it("should return a function that rejects when cache is not initialized", async () => {
343
+ const { result } = renderHook(() => useGetDocuments());
344
+ const getDocuments = result.current;
345
+ await expect(getDocuments(["doc-1"])).rejects.toThrow("Document cache not initialized");
346
+ });
347
+ it("should return a function that gets documents from cache", async () => {
348
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
349
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
350
+ const { cache } = await createDocumentCache([mockDoc1, mockDoc2]);
351
+ setDocumentCache(cache);
352
+ const { result } = renderHook(() => useGetDocuments());
353
+ const getDocuments = result.current;
354
+ const docs = await getDocuments(["doc-1", "doc-2"]);
355
+ expect(docs[0].header.name).toBe("Document 1");
356
+ expect(docs[1].header.name).toBe("Document 2");
357
+ });
358
+ it("should return the updated documents when called after an operation is added", async () => {
359
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
360
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
361
+ const { cache, reactor } = await createDocumentCache([
362
+ mockDoc1,
363
+ mockDoc2,
364
+ ]);
365
+ setDocumentCache(cache);
366
+ const { result, act } = renderHook(() => useGetDocuments());
367
+ const getDocuments = result.current;
368
+ const docs = await getDocuments(["doc-1", "doc-2"]);
369
+ expect(docs[0].header.name).toBe("Document 1");
370
+ expect(docs[1].header.name).toBe("Document 2");
371
+ act(() => {
372
+ reactor
373
+ .addAction(mockDoc2.header.id, setName("Updated Document 2"))
374
+ .catch((error) => {
375
+ throw error;
376
+ });
377
+ });
378
+ await vi.waitFor(async () => {
379
+ const updatedDocs = await getDocuments(["doc-1", "doc-2"]);
380
+ expect(updatedDocs[0].header.name).toBe("Document 1");
381
+ expect(updatedDocs[1].header.name).toBe("Updated Document 2");
382
+ });
383
+ });
384
+ it("should return same objects returned by useDocuments", async () => {
385
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
386
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
387
+ const { cache } = await createDocumentCache([mockDoc1, mockDoc2]);
388
+ setDocumentCache(cache);
389
+ const { result, rerender } = renderHook(() => useDocuments(["doc-1", "doc-2"]), {
390
+ wrapper: SuspenseWrapper,
391
+ });
392
+ const { result: getResult } = renderHook(() => useGetDocuments());
393
+ // Suspense hooks need rerender after the promise resolves
394
+ await vi.waitFor(() => {
395
+ rerender();
396
+ expect(result.current).toHaveLength(2);
397
+ expect(result.current[0]?.header.name).toBe("Document 1");
398
+ expect(result.current[1]?.header.name).toBe("Document 2");
399
+ });
400
+ const getDocuments = getResult.current;
401
+ const docs = await getDocuments(["doc-1", "doc-2"]);
402
+ expect(docs[0].header.name).toBe("Document 1");
403
+ expect(docs[1].header.name).toBe("Document 2");
404
+ expect(docs[0]).toBe(result.current[0]);
405
+ expect(docs[1]).toBe(result.current[1]);
406
+ });
407
+ it("should return latest state of documents", async () => {
408
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
409
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
410
+ const { cache, reactor } = await createDocumentCache([
411
+ mockDoc1,
412
+ mockDoc2,
413
+ ]);
414
+ setDocumentCache(cache);
415
+ const { result, rerender, unmount } = renderHook(() => useDocuments(["doc-1", "doc-2"]), {
416
+ wrapper: SuspenseWrapper,
417
+ });
418
+ await vi.waitFor(() => {
419
+ rerender();
420
+ expect(result.current).toHaveLength(2);
421
+ expect(result.current[0]?.header.name).toBe("Document 1");
422
+ expect(result.current[1]?.header.name).toBe("Document 2");
423
+ });
424
+ unmount();
425
+ const { result: getResult, rerender: getRerender } = renderHook(() => useGetDocuments());
426
+ const getDocuments = getResult.current;
427
+ await reactor.addAction(mockDoc2.header.id, setName("Updated Document 2"));
428
+ await vi.waitFor(async () => {
429
+ getRerender();
430
+ const docs = await getDocuments(["doc-1", "doc-2"]);
431
+ expect(docs[0].header.name).toBe("Document 1");
432
+ expect(docs[1].header.name).toBe("Updated Document 2");
433
+ });
434
+ });
435
+ it("should reject when one of the documents is deleted", async () => {
436
+ const mockDoc1 = createMockDocument("doc-1", "Document 1");
437
+ const mockDoc2 = createMockDocument("doc-2", "Document 2");
438
+ const { cache, reactor } = await createDocumentCache([
439
+ mockDoc1,
440
+ mockDoc2,
441
+ ]);
442
+ setDocumentCache(cache);
443
+ const { result } = renderHook(() => useGetDocuments());
444
+ const getDocuments = result.current;
445
+ const docs = await getDocuments(["doc-1", "doc-2"]);
446
+ expect(docs[0].header.name).toBe("Document 1");
447
+ expect(docs[1].header.name).toBe("Document 2");
448
+ // Delete the second document
449
+ await reactor.deleteDocument(mockDoc2.header.id);
450
+ // Subsequent calls should reject because doc-2 no longer exists
451
+ await expect(getDocuments(["doc-1", "doc-2"])).rejects.toThrow();
452
+ });
453
+ });
454
+ describe("useGetDocumentAsync", () => {
455
+ it("should return initial state when id is null", async () => {
456
+ const { cache } = await createDocumentCache();
457
+ setDocumentCache(cache);
458
+ const { result } = renderHook(() => useGetDocumentAsync(null));
459
+ expect(result.current).toEqual({
460
+ status: "initial",
461
+ data: undefined,
462
+ isPending: false,
463
+ error: undefined,
464
+ reload: undefined,
465
+ });
466
+ });
467
+ it("should return initial state when id is undefined", async () => {
468
+ const { cache } = await createDocumentCache();
469
+ setDocumentCache(cache);
470
+ const { result } = renderHook(() => useGetDocumentAsync(undefined));
471
+ expect(result.current).toEqual({
472
+ status: "initial",
473
+ data: undefined,
474
+ isPending: false,
475
+ error: undefined,
476
+ reload: undefined,
477
+ });
478
+ });
479
+ it("should return initial state when document cache is not set", () => {
480
+ const { result } = renderHook(() => useGetDocumentAsync("doc-1"));
481
+ expect(result.current).toEqual({
482
+ status: "initial",
483
+ data: undefined,
484
+ isPending: false,
485
+ error: undefined,
486
+ reload: undefined,
487
+ });
488
+ });
489
+ it("should return success state when document is loaded", async () => {
490
+ const mockDoc = createMockDocument("doc-1", "Test Document");
491
+ const { cache } = await createDocumentCache([mockDoc]);
492
+ setDocumentCache(cache);
493
+ const { result, rerender } = renderHook(() => useGetDocumentAsync("doc-1"));
494
+ // Wait for the promise to settle, then rerender to get updated state
495
+ await vi.waitFor(() => {
496
+ rerender();
497
+ expect(result.current.status).toBe("success");
498
+ });
499
+ expect(result.current.data?.header.name).toBe("Test Document");
500
+ expect(result.current.isPending).toBe(false);
501
+ expect(result.current.error).toBeUndefined();
502
+ expect(result.current.reload).toBeDefined();
503
+ });
504
+ it("should return error state for non-existent document", async () => {
505
+ const { cache } = await createDocumentCache();
506
+ setDocumentCache(cache);
507
+ // Suppress unhandled rejection warning for this test
508
+ const handler = (event) => {
509
+ const reason = event.reason;
510
+ if (reason?.message.includes("non-existent")) {
511
+ event.preventDefault();
512
+ }
513
+ };
514
+ window.addEventListener("unhandledrejection", handler);
515
+ const { result, rerender } = renderHook(() => useGetDocumentAsync("non-existent"));
516
+ // Initially returns pending state while fetching
517
+ expect(result.current.status).toBe("pending");
518
+ expect(result.current.data).toBeUndefined();
519
+ expect(result.current.isPending).toBe(true);
520
+ // Wait for the error state after the promise rejects
521
+ await vi.waitFor(() => {
522
+ rerender();
523
+ expect(result.current.status).toBe("error");
524
+ });
525
+ expect(result.current.error).toBeDefined();
526
+ expect(result.current.isPending).toBe(false);
527
+ window.removeEventListener("unhandledrejection", handler);
528
+ });
529
+ it("should provide reload function that refetches document", async () => {
530
+ const mockDoc = createMockDocument("doc-1", "Test Document");
531
+ const { cache } = await createDocumentCache([mockDoc]);
532
+ setDocumentCache(cache);
533
+ const { result, rerender } = renderHook(() => useGetDocumentAsync("doc-1"));
534
+ // Wait for the promise to settle, then rerender to get updated state
535
+ await vi.waitFor(() => {
536
+ rerender();
537
+ expect(result.current.reload).toBeDefined();
538
+ });
539
+ // Calling reload should not throw
540
+ expect(() => result.current.reload()).not.toThrow();
541
+ });
542
+ it("should return updated document after reload when operation is added", async () => {
543
+ const mockDoc = createMockDocument("doc-1", "Test Document");
544
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
545
+ setDocumentCache(cache);
546
+ const { result, rerender } = renderHook(() => useGetDocumentAsync("doc-1"));
547
+ // Wait for the initial load
548
+ await vi.waitFor(() => {
549
+ rerender();
550
+ expect(result.current.status).toBe("success");
551
+ expect(result.current.data?.header.name).toBe("Test Document");
552
+ });
553
+ // Add an operation to update the document
554
+ await reactor.addAction(mockDoc.header.id, setName("Updated Document"));
555
+ // Call reload to refetch the document
556
+ result.current.reload();
557
+ // Wait for the updated document
558
+ await vi.waitFor(() => {
559
+ rerender();
560
+ expect(result.current.data?.header.name).toBe("Updated Document");
561
+ });
562
+ });
563
+ it("should return same object returned by useDocument", async () => {
564
+ const mockDoc = createMockDocument("doc-1", "Test Document");
565
+ const { cache } = await createDocumentCache([mockDoc]);
566
+ setDocumentCache(cache);
567
+ const { result: docResult, rerender: docRerender } = renderHook(() => useDocument("doc-1"), {
568
+ wrapper: SuspenseWrapper,
569
+ });
570
+ const { result: asyncResult, rerender: asyncRerender } = renderHook(() => useGetDocumentAsync("doc-1"));
571
+ // Wait for both hooks to load
572
+ await vi.waitFor(() => {
573
+ docRerender();
574
+ expect(docResult.current).toBeDefined();
575
+ expect(docResult.current?.header.name).toBe("Test Document");
576
+ });
577
+ await vi.waitFor(() => {
578
+ asyncRerender();
579
+ expect(asyncResult.current.status).toBe("success");
580
+ expect(asyncResult.current.data?.header.name).toBe("Test Document");
581
+ });
582
+ expect(asyncResult.current.data).toBe(docResult.current);
583
+ });
584
+ it("should return latest state of document after reload", async () => {
585
+ const mockDoc = createMockDocument("doc-1", "Test Document");
586
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
587
+ setDocumentCache(cache);
588
+ const { result: docResult, rerender: docRerender, unmount, } = renderHook(() => useDocument("doc-1"), {
589
+ wrapper: SuspenseWrapper,
590
+ });
591
+ await vi.waitFor(() => {
592
+ docRerender();
593
+ expect(docResult.current).toBeDefined();
594
+ expect(docResult.current?.header.name).toBe("Test Document");
595
+ });
596
+ unmount();
597
+ const { result, rerender } = renderHook(() => useGetDocumentAsync("doc-1"));
598
+ await vi.waitFor(() => {
599
+ rerender();
600
+ expect(result.current.status).toBe("success");
601
+ });
602
+ await reactor.addAction(mockDoc.header.id, setName("Updated Document"));
603
+ result.current.reload();
604
+ await vi.waitFor(() => {
605
+ rerender();
606
+ expect(result.current.data?.header.name).toBe("Updated Document");
607
+ });
608
+ });
609
+ it("should return error state after reload when document is deleted", async () => {
610
+ const mockDoc = createMockDocument("doc-1", "Test Document");
611
+ const { cache, reactor } = await createDocumentCache([mockDoc]);
612
+ setDocumentCache(cache);
613
+ // Suppress unhandled rejection warning for this test
614
+ const handler = (event) => {
615
+ const reason = event.reason;
616
+ if (reason?.message.includes("doc-1")) {
617
+ event.preventDefault();
618
+ }
619
+ };
620
+ window.addEventListener("unhandledrejection", handler);
621
+ const { result, rerender } = renderHook(() => useGetDocumentAsync("doc-1"));
622
+ // Wait for the initial load
623
+ await vi.waitFor(() => {
624
+ rerender();
625
+ expect(result.current.status).toBe("success");
626
+ expect(result.current.data?.header.name).toBe("Test Document");
627
+ });
628
+ // Delete the document
629
+ await reactor.deleteDocument(mockDoc.header.id);
630
+ // Call reload to refetch the document
631
+ result.current.reload();
632
+ // Should return error state after reload
633
+ await vi.waitFor(() => {
634
+ rerender();
635
+ expect(result.current.status).toBe("error");
636
+ expect(result.current.error).toBeDefined();
637
+ });
638
+ window.removeEventListener("unhandledrejection", handler);
639
+ });
640
+ });
641
+ });
642
+ //# sourceMappingURL=document-cache.test.js.map