@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.
- package/README.md +17 -3
- package/dist/src/actions/dispatch.d.ts +2 -2
- package/dist/src/actions/dispatch.d.ts.map +1 -1
- package/dist/src/actions/dispatch.js +4 -1
- package/dist/src/actions/dispatch.js.map +1 -1
- package/dist/src/actions/document.d.ts.map +1 -1
- package/dist/src/actions/document.js +23 -26
- package/dist/src/actions/document.js.map +1 -1
- package/dist/src/document-cache.d.ts +17 -0
- package/dist/src/document-cache.d.ts.map +1 -0
- package/dist/src/document-cache.js +143 -0
- package/dist/src/document-cache.js.map +1 -0
- package/dist/src/errors.js +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/hooks/add-ph-event-handlers.d.ts.map +1 -1
- package/dist/src/hooks/add-ph-event-handlers.js +2 -0
- package/dist/src/hooks/add-ph-event-handlers.js.map +1 -1
- package/dist/src/hooks/dispatch.d.ts +1 -1
- package/dist/src/hooks/dispatch.d.ts.map +1 -1
- package/dist/src/hooks/dispatch.js +2 -2
- package/dist/src/hooks/dispatch.js.map +1 -1
- package/dist/src/hooks/document-by-id.d.ts +1 -1
- package/dist/src/hooks/document-by-id.d.ts.map +1 -1
- package/dist/src/hooks/document-by-id.js +3 -3
- package/dist/src/hooks/document-by-id.js.map +1 -1
- package/dist/src/hooks/document-cache.d.ts +19 -3
- package/dist/src/hooks/document-cache.d.ts.map +1 -1
- package/dist/src/hooks/document-cache.js +41 -14
- package/dist/src/hooks/document-cache.js.map +1 -1
- package/dist/src/hooks/index.d.ts +5 -4
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +5 -4
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/selected-document.d.ts +3 -1
- package/dist/src/hooks/selected-document.d.ts.map +1 -1
- package/dist/src/hooks/selected-document.js +9 -0
- package/dist/src/hooks/selected-document.js.map +1 -1
- package/dist/src/hooks/toast.d.ts +7 -0
- package/dist/src/hooks/toast.d.ts.map +1 -0
- package/dist/src/hooks/toast.js +9 -0
- package/dist/src/hooks/toast.js.map +1 -0
- package/dist/src/hooks/vetra-packages.d.ts.map +1 -1
- package/dist/src/hooks/vetra-packages.js +3 -1
- package/dist/src/hooks/vetra-packages.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/pglite/drop.d.ts +1 -1
- package/dist/src/pglite/drop.d.ts.map +1 -1
- package/dist/src/pglite/drop.js +3 -2
- package/dist/src/pglite/drop.js.map +1 -1
- package/dist/src/reactor-client-document-cache.d.ts +31 -0
- package/dist/src/reactor-client-document-cache.d.ts.map +1 -0
- package/dist/src/reactor-client-document-cache.js +142 -0
- package/dist/src/reactor-client-document-cache.js.map +1 -0
- package/dist/src/reactor.d.ts +0 -3
- package/dist/src/reactor.d.ts.map +1 -1
- package/dist/src/reactor.js +0 -56
- package/dist/src/reactor.js.map +1 -1
- package/dist/src/renown/utils.d.ts.map +1 -1
- package/dist/src/renown/utils.js +1 -1
- package/dist/src/renown/utils.js.map +1 -1
- package/dist/src/types/documents.d.ts +10 -5
- package/dist/src/types/documents.d.ts.map +1 -1
- package/dist/src/types/global.d.ts +2 -0
- package/dist/src/types/global.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +1 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/toast.d.ts +9 -0
- package/dist/src/types/toast.d.ts.map +1 -0
- package/dist/src/types/toast.js +2 -0
- package/dist/src/types/toast.js.map +1 -0
- package/dist/test/document-cache.test.d.ts +2 -0
- package/dist/test/document-cache.test.d.ts.map +1 -0
- package/dist/test/document-cache.test.js +457 -0
- package/dist/test/document-cache.test.js.map +1 -0
- package/dist/test/drop.test.js +7 -9
- package/dist/test/drop.test.js.map +1 -1
- package/dist/test/getSwitchboardUrl.test.js +3 -1
- package/dist/test/getSwitchboardUrl.test.js.map +1 -1
- package/dist/test/hooks/document-cache.test.d.ts +2 -0
- package/dist/test/hooks/document-cache.test.d.ts.map +1 -0
- package/dist/test/hooks/document-cache.test.js +642 -0
- package/dist/test/hooks/document-cache.test.js.map +1 -0
- package/dist/test/switchboard.test.js +4 -2
- package/dist/test/switchboard.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- 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
|