jazz-tools 0.19.1 → 0.19.3
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/.turbo/turbo-build.log +63 -63
- package/CHANGELOG.md +21 -0
- package/dist/{chunk-NCNM6UDZ.js → chunk-JPWM4CS2.js} +4 -2
- package/dist/{chunk-NCNM6UDZ.js.map → chunk-JPWM4CS2.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/inspector/{custom-element-QESCMFY7.js → custom-element-3JAYHXWQ.js} +1134 -464
- package/dist/inspector/custom-element-3JAYHXWQ.js.map +1 -0
- package/dist/inspector/index.js +1104 -434
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/tests/utils/history.test.d.ts +2 -0
- package/dist/inspector/tests/utils/history.test.d.ts.map +1 -0
- package/dist/inspector/tests/viewer/co-value-editor.test.d.ts +2 -0
- package/dist/inspector/tests/viewer/co-value-editor.test.d.ts.map +1 -0
- package/dist/inspector/tests/viewer/comap-view.test.d.ts +2 -0
- package/dist/inspector/tests/viewer/comap-view.test.d.ts.map +1 -0
- package/dist/inspector/ui/icon.d.ts +6 -0
- package/dist/inspector/ui/icon.d.ts.map +1 -1
- package/dist/inspector/ui/icons/add-icon.d.ts +2 -0
- package/dist/inspector/ui/icons/add-icon.d.ts.map +1 -0
- package/dist/inspector/ui/icons/edit-icon.d.ts +2 -0
- package/dist/inspector/ui/icons/edit-icon.d.ts.map +1 -0
- package/dist/inspector/ui/icons/history.d.ts +2 -0
- package/dist/inspector/ui/icons/history.d.ts.map +1 -0
- package/dist/inspector/utils/history.d.ts +3 -0
- package/dist/inspector/utils/history.d.ts.map +1 -0
- package/dist/inspector/utils/permissions.d.ts +3 -0
- package/dist/inspector/utils/permissions.d.ts.map +1 -0
- package/dist/inspector/utils/transactions-changes.d.ts +38 -0
- package/dist/inspector/utils/transactions-changes.d.ts.map +1 -0
- package/dist/inspector/viewer/co-map-view.d.ts +9 -0
- package/dist/inspector/viewer/co-map-view.d.ts.map +1 -0
- package/dist/inspector/viewer/co-value-editor.d.ts +10 -0
- package/dist/inspector/viewer/co-value-editor.d.ts.map +1 -0
- package/dist/inspector/viewer/grid-view.d.ts +3 -2
- package/dist/inspector/viewer/grid-view.d.ts.map +1 -1
- package/dist/inspector/viewer/history-view.d.ts.map +1 -1
- package/dist/inspector/viewer/page.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/CoFieldInit.d.ts +2 -1
- package/dist/tools/coValues/CoFieldInit.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts +3 -2
- package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/inspector/tests/utils/history.test.ts +401 -0
- package/src/inspector/tests/viewer/co-value-editor.test.tsx +903 -0
- package/src/inspector/tests/viewer/comap-view.test.tsx +889 -0
- package/src/inspector/ui/icon.tsx +6 -0
- package/src/inspector/ui/icons/add-icon.tsx +21 -0
- package/src/inspector/ui/icons/edit-icon.tsx +17 -0
- package/src/inspector/ui/icons/history.tsx +28 -0
- package/src/inspector/ui/modal.tsx +3 -3
- package/src/inspector/utils/history.ts +49 -0
- package/src/inspector/utils/permissions.ts +10 -0
- package/src/inspector/utils/transactions-changes.ts +98 -0
- package/src/inspector/viewer/co-map-view.tsx +324 -0
- package/src/inspector/viewer/co-value-editor.tsx +164 -0
- package/src/inspector/viewer/grid-view.tsx +140 -10
- package/src/inspector/viewer/history-view.tsx +19 -119
- package/src/inspector/viewer/page.tsx +13 -0
- package/src/react-core/tests/usePassPhraseAuth.test.ts +1 -1
- package/src/tools/coValues/CoFieldInit.ts +6 -3
- package/src/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.ts +12 -7
- package/src/tools/implementation/zodSchema/unionUtils.ts +3 -4
- package/src/tools/tests/coVector.test.ts +43 -0
- package/dist/inspector/custom-element-QESCMFY7.js.map +0 -1
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, assert, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import { createJazzTestAccount, setupJazzTestSync } from "jazz-tools/testing";
|
|
4
|
+
import { co, z } from "jazz-tools";
|
|
5
|
+
import {
|
|
6
|
+
cleanup,
|
|
7
|
+
fireEvent,
|
|
8
|
+
render,
|
|
9
|
+
screen,
|
|
10
|
+
waitFor,
|
|
11
|
+
} from "@testing-library/react";
|
|
12
|
+
import { CoMapView } from "../../viewer/co-map-view";
|
|
13
|
+
import { setup } from "goober";
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { JsonObject } from "cojson";
|
|
16
|
+
|
|
17
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
|
|
19
|
+
describe("CoMapView", async () => {
|
|
20
|
+
const account = await setupJazzTestSync();
|
|
21
|
+
|
|
22
|
+
beforeAll(() => {
|
|
23
|
+
setup(React.createElement);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
cleanup();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("Basic Rendering", () => {
|
|
31
|
+
it("should render GridView with data", async () => {
|
|
32
|
+
const value = co
|
|
33
|
+
.map({
|
|
34
|
+
pet: z.string(),
|
|
35
|
+
age: z.number(),
|
|
36
|
+
})
|
|
37
|
+
.create({ pet: "dog", age: 10 }, account);
|
|
38
|
+
|
|
39
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
40
|
+
|
|
41
|
+
render(
|
|
42
|
+
<CoMapView
|
|
43
|
+
coValue={value.$jazz.raw}
|
|
44
|
+
data={data}
|
|
45
|
+
node={account.$jazz.localNode}
|
|
46
|
+
onNavigate={() => {}}
|
|
47
|
+
/>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(screen.getByText("pet")).toBeDefined();
|
|
51
|
+
expect(screen.getByText("age")).toBeDefined();
|
|
52
|
+
expect(screen.getByText("dog")).toBeDefined();
|
|
53
|
+
expect(screen.getByText("10")).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should render restore button", async () => {
|
|
57
|
+
const value = co
|
|
58
|
+
.map({
|
|
59
|
+
foo: z.string(),
|
|
60
|
+
})
|
|
61
|
+
.create({ foo: "bar" }, account);
|
|
62
|
+
|
|
63
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
64
|
+
|
|
65
|
+
render(
|
|
66
|
+
<CoMapView
|
|
67
|
+
coValue={value.$jazz.raw}
|
|
68
|
+
data={data}
|
|
69
|
+
node={account.$jazz.localNode}
|
|
70
|
+
onNavigate={() => {}}
|
|
71
|
+
/>,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
75
|
+
expect(restoreButton).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("Timestamp Selection", () => {
|
|
80
|
+
it("should display timestamps and allow selection", async () => {
|
|
81
|
+
const value = co
|
|
82
|
+
.map({
|
|
83
|
+
pet: z.string(),
|
|
84
|
+
})
|
|
85
|
+
.create({ pet: "dog" }, account);
|
|
86
|
+
|
|
87
|
+
await sleep(2);
|
|
88
|
+
value.$jazz.set("pet", "cat");
|
|
89
|
+
await sleep(2);
|
|
90
|
+
value.$jazz.set("pet", "bird");
|
|
91
|
+
|
|
92
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
93
|
+
|
|
94
|
+
render(
|
|
95
|
+
<CoMapView
|
|
96
|
+
coValue={value.$jazz.raw}
|
|
97
|
+
data={data}
|
|
98
|
+
node={account.$jazz.localNode}
|
|
99
|
+
onNavigate={() => {}}
|
|
100
|
+
/>,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
104
|
+
fireEvent.click(restoreButton);
|
|
105
|
+
|
|
106
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
107
|
+
expect(slider).toBeDefined();
|
|
108
|
+
expect(slider.max).toBe("2");
|
|
109
|
+
|
|
110
|
+
fireEvent.change(slider, { target: { value: "0" } });
|
|
111
|
+
expect(slider.value).toBe("0");
|
|
112
|
+
|
|
113
|
+
fireEvent.change(slider, { target: { value: "1" } });
|
|
114
|
+
expect(slider.value).toBe("1");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should show timestamp in ISO format", async () => {
|
|
118
|
+
const value = co
|
|
119
|
+
.map({
|
|
120
|
+
foo: z.string(),
|
|
121
|
+
})
|
|
122
|
+
.create({ foo: "bar" }, account);
|
|
123
|
+
|
|
124
|
+
value.$jazz.set("foo", "baz");
|
|
125
|
+
|
|
126
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
127
|
+
|
|
128
|
+
render(
|
|
129
|
+
<CoMapView
|
|
130
|
+
coValue={value.$jazz.raw}
|
|
131
|
+
data={data}
|
|
132
|
+
node={account.$jazz.localNode}
|
|
133
|
+
onNavigate={() => {}}
|
|
134
|
+
/>,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
138
|
+
fireEvent.click(restoreButton);
|
|
139
|
+
|
|
140
|
+
const timestampDisplay = screen.getAllByText(/\d{4}-\d{2}-\d{2}T/)[0];
|
|
141
|
+
expect(timestampDisplay).toBeDefined();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should update preview when timestamp changes", async () => {
|
|
145
|
+
const value = co
|
|
146
|
+
.map({
|
|
147
|
+
pet: z.string(),
|
|
148
|
+
})
|
|
149
|
+
.create({ pet: "dog" }, account);
|
|
150
|
+
|
|
151
|
+
// wait to have different timestamps in transactions
|
|
152
|
+
await sleep(2);
|
|
153
|
+
value.$jazz.set("pet", "cat");
|
|
154
|
+
await sleep(2);
|
|
155
|
+
value.$jazz.set("pet", "bird");
|
|
156
|
+
|
|
157
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
158
|
+
|
|
159
|
+
render(
|
|
160
|
+
<CoMapView
|
|
161
|
+
coValue={value.$jazz.raw}
|
|
162
|
+
data={data}
|
|
163
|
+
node={account.$jazz.localNode}
|
|
164
|
+
onNavigate={() => {}}
|
|
165
|
+
/>,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
169
|
+
fireEvent.click(restoreButton);
|
|
170
|
+
|
|
171
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
172
|
+
const preview = screen.getByText(/State at that time:/);
|
|
173
|
+
expect(preview).toBeDefined();
|
|
174
|
+
|
|
175
|
+
// Modal starts at the most recent timestamp (last index)
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
178
|
+
expect(previewPre?.textContent).toContain("bird");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
fireEvent.change(slider, { target: { value: 0 } });
|
|
182
|
+
await waitFor(() => {
|
|
183
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
184
|
+
expect(previewPre?.textContent).toContain("dog");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("Remove Unknown Properties Checkbox", () => {
|
|
190
|
+
it("should toggle checkbox state", async () => {
|
|
191
|
+
const value = co
|
|
192
|
+
.map({
|
|
193
|
+
foo: z.string(),
|
|
194
|
+
})
|
|
195
|
+
.create({ foo: "bar" }, account);
|
|
196
|
+
|
|
197
|
+
value.$jazz.set("foo", "baz");
|
|
198
|
+
|
|
199
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
200
|
+
|
|
201
|
+
render(
|
|
202
|
+
<CoMapView
|
|
203
|
+
coValue={value.$jazz.raw}
|
|
204
|
+
data={data}
|
|
205
|
+
node={account.$jazz.localNode}
|
|
206
|
+
onNavigate={() => {}}
|
|
207
|
+
/>,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
211
|
+
fireEvent.click(restoreButton);
|
|
212
|
+
|
|
213
|
+
const checkbox = screen.getByRole("checkbox") as HTMLInputElement;
|
|
214
|
+
expect(checkbox.checked).toBe(false);
|
|
215
|
+
|
|
216
|
+
fireEvent.click(checkbox);
|
|
217
|
+
expect(checkbox.checked).toBe(true);
|
|
218
|
+
|
|
219
|
+
fireEvent.click(checkbox);
|
|
220
|
+
expect(checkbox.checked).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("Restore Functionality", () => {
|
|
225
|
+
it("should show preview of state to restore", async () => {
|
|
226
|
+
const value = co
|
|
227
|
+
.map({
|
|
228
|
+
pet: z.string(),
|
|
229
|
+
age: z.number(),
|
|
230
|
+
})
|
|
231
|
+
.create({ pet: "dog", age: 10 }, account);
|
|
232
|
+
|
|
233
|
+
await sleep(2);
|
|
234
|
+
value.$jazz.set("pet", "cat");
|
|
235
|
+
value.$jazz.set("age", 20);
|
|
236
|
+
|
|
237
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
238
|
+
|
|
239
|
+
render(
|
|
240
|
+
<CoMapView
|
|
241
|
+
coValue={value.$jazz.raw}
|
|
242
|
+
data={data}
|
|
243
|
+
node={account.$jazz.localNode}
|
|
244
|
+
onNavigate={() => {}}
|
|
245
|
+
/>,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
249
|
+
fireEvent.click(restoreButton);
|
|
250
|
+
|
|
251
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
252
|
+
fireEvent.change(slider, { target: { value: 0 } });
|
|
253
|
+
|
|
254
|
+
await waitFor(() => {
|
|
255
|
+
const preview = screen.getByText(/State at that time:/);
|
|
256
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
257
|
+
expect(previewPre?.textContent).toContain("dog");
|
|
258
|
+
expect(previewPre?.textContent).toContain("10");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should close modal when restore is clicked", async () => {
|
|
263
|
+
const value = co
|
|
264
|
+
.map({
|
|
265
|
+
pet: z.string(),
|
|
266
|
+
age: z.number(),
|
|
267
|
+
})
|
|
268
|
+
.create({ pet: "dog", age: 10 }, account);
|
|
269
|
+
|
|
270
|
+
await sleep(2);
|
|
271
|
+
value.$jazz.set("pet", "cat");
|
|
272
|
+
|
|
273
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
274
|
+
|
|
275
|
+
render(
|
|
276
|
+
<CoMapView
|
|
277
|
+
coValue={value.$jazz.raw}
|
|
278
|
+
data={data}
|
|
279
|
+
node={account.$jazz.localNode}
|
|
280
|
+
onNavigate={() => {}}
|
|
281
|
+
/>,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
285
|
+
fireEvent.click(restoreButton);
|
|
286
|
+
|
|
287
|
+
expect(screen.getByText("Select Timestamp")).toBeDefined();
|
|
288
|
+
|
|
289
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
290
|
+
fireEvent.change(slider, { target: { value: 0 } });
|
|
291
|
+
|
|
292
|
+
const restoreActionButton = screen.getByText("Restore");
|
|
293
|
+
fireEvent.click(restoreActionButton);
|
|
294
|
+
|
|
295
|
+
await waitFor(() => {
|
|
296
|
+
expect(screen.queryByText("Select Timestamp")).toBeNull();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should allow selecting timestamp and checking remove properties option", async () => {
|
|
301
|
+
const value = co
|
|
302
|
+
.map({
|
|
303
|
+
pet: z.string(),
|
|
304
|
+
age: z.number().optional(),
|
|
305
|
+
})
|
|
306
|
+
.create({ pet: "dog" }, account);
|
|
307
|
+
|
|
308
|
+
await sleep(2);
|
|
309
|
+
value.$jazz.set("age", 10);
|
|
310
|
+
await sleep(2);
|
|
311
|
+
value.$jazz.set("pet", "cat");
|
|
312
|
+
|
|
313
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
314
|
+
|
|
315
|
+
render(
|
|
316
|
+
<CoMapView
|
|
317
|
+
coValue={value.$jazz.raw}
|
|
318
|
+
data={data}
|
|
319
|
+
node={account.$jazz.localNode}
|
|
320
|
+
onNavigate={() => {}}
|
|
321
|
+
/>,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
325
|
+
fireEvent.click(restoreButton);
|
|
326
|
+
|
|
327
|
+
const checkbox = screen.getByRole("checkbox") as HTMLInputElement;
|
|
328
|
+
expect(checkbox.checked).toBe(false);
|
|
329
|
+
|
|
330
|
+
fireEvent.click(checkbox);
|
|
331
|
+
expect(checkbox.checked).toBe(true);
|
|
332
|
+
|
|
333
|
+
// Change to earlier timestamp
|
|
334
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
335
|
+
fireEvent.change(slider, { target: { value: "0" } });
|
|
336
|
+
|
|
337
|
+
await waitFor(() => {
|
|
338
|
+
const preview = screen.getByText(/State at that time:/);
|
|
339
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
340
|
+
expect(previewPre?.textContent).toContain("dog");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const restoreActionButton = screen.getByText(
|
|
344
|
+
"Restore",
|
|
345
|
+
) as HTMLButtonElement;
|
|
346
|
+
expect(restoreActionButton.disabled).toBe(false);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("Edge Cases", () => {
|
|
351
|
+
it("should handle empty CoMap", async () => {
|
|
352
|
+
const value = co.map({}).create({}, account);
|
|
353
|
+
|
|
354
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
355
|
+
|
|
356
|
+
render(
|
|
357
|
+
<CoMapView
|
|
358
|
+
coValue={value.$jazz.raw}
|
|
359
|
+
data={data}
|
|
360
|
+
node={account.$jazz.localNode}
|
|
361
|
+
onNavigate={() => {}}
|
|
362
|
+
/>,
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
expect(screen.getByTitle("Timeline")).toBeDefined();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should handle complex data types", async () => {
|
|
369
|
+
const value = co
|
|
370
|
+
.map({
|
|
371
|
+
obj: z.object({
|
|
372
|
+
name: z.string(),
|
|
373
|
+
count: z.number(),
|
|
374
|
+
}),
|
|
375
|
+
date: z.date(),
|
|
376
|
+
bool: z.boolean(),
|
|
377
|
+
})
|
|
378
|
+
.create(
|
|
379
|
+
{
|
|
380
|
+
obj: { name: "test", count: 42 },
|
|
381
|
+
date: new Date("2024-01-01"),
|
|
382
|
+
bool: true,
|
|
383
|
+
},
|
|
384
|
+
account,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
388
|
+
|
|
389
|
+
render(
|
|
390
|
+
<CoMapView
|
|
391
|
+
coValue={value.$jazz.raw}
|
|
392
|
+
data={data}
|
|
393
|
+
node={account.$jazz.localNode}
|
|
394
|
+
onNavigate={() => {}}
|
|
395
|
+
/>,
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
expect(screen.getByText("obj")).toBeDefined();
|
|
399
|
+
expect(screen.getByText("date")).toBeDefined();
|
|
400
|
+
expect(screen.getByText("bool")).toBeDefined();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("should not allow restoring to same state (no changes)", async () => {
|
|
404
|
+
const value = co
|
|
405
|
+
.map({
|
|
406
|
+
pet: z.string(),
|
|
407
|
+
})
|
|
408
|
+
.create({ pet: "dog" }, account);
|
|
409
|
+
|
|
410
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
411
|
+
|
|
412
|
+
render(
|
|
413
|
+
<CoMapView
|
|
414
|
+
coValue={value.$jazz.raw}
|
|
415
|
+
data={data}
|
|
416
|
+
node={account.$jazz.localNode}
|
|
417
|
+
onNavigate={() => {}}
|
|
418
|
+
/>,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
422
|
+
fireEvent.click(restoreButton);
|
|
423
|
+
|
|
424
|
+
expect(screen.queryByRole("slider")).toBeNull();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should handle multiple property changes at different times", async () => {
|
|
428
|
+
const value = co
|
|
429
|
+
.map({
|
|
430
|
+
a: z.string(),
|
|
431
|
+
b: z.string(),
|
|
432
|
+
c: z.string(),
|
|
433
|
+
})
|
|
434
|
+
.create({ a: "1", b: "2", c: "3" }, account);
|
|
435
|
+
|
|
436
|
+
await sleep(2);
|
|
437
|
+
value.$jazz.set("a", "4");
|
|
438
|
+
await sleep(2);
|
|
439
|
+
value.$jazz.set("b", "5");
|
|
440
|
+
await sleep(2);
|
|
441
|
+
value.$jazz.set("c", "6");
|
|
442
|
+
|
|
443
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
444
|
+
|
|
445
|
+
render(
|
|
446
|
+
<CoMapView
|
|
447
|
+
coValue={value.$jazz.raw}
|
|
448
|
+
data={data}
|
|
449
|
+
node={account.$jazz.localNode}
|
|
450
|
+
onNavigate={() => {}}
|
|
451
|
+
/>,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
455
|
+
fireEvent.click(restoreButton);
|
|
456
|
+
|
|
457
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
458
|
+
|
|
459
|
+
// Verify we can navigate to initial state
|
|
460
|
+
fireEvent.change(slider, { target: { value: 0 } });
|
|
461
|
+
await waitFor(() => {
|
|
462
|
+
const preview = screen.getByText(/State at that time:/);
|
|
463
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
464
|
+
expect(previewPre?.textContent).toContain("1");
|
|
465
|
+
expect(previewPre?.textContent).toContain("2");
|
|
466
|
+
expect(previewPre?.textContent).toContain("3");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Verify we can navigate to latest state
|
|
470
|
+
fireEvent.change(slider, { target: { value: slider.max } });
|
|
471
|
+
await waitFor(() => {
|
|
472
|
+
const preview = screen.getByText(/State at that time:/);
|
|
473
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
474
|
+
expect(previewPre?.textContent).toContain("4");
|
|
475
|
+
expect(previewPre?.textContent).toContain("5");
|
|
476
|
+
expect(previewPre?.textContent).toContain("6");
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("Preview Display", () => {
|
|
482
|
+
it("should show JSON preview of selected state", async () => {
|
|
483
|
+
const value = co
|
|
484
|
+
.map({
|
|
485
|
+
pet: z.string(),
|
|
486
|
+
age: z.number(),
|
|
487
|
+
})
|
|
488
|
+
.create({ pet: "dog", age: 10 }, account);
|
|
489
|
+
|
|
490
|
+
// wait to have different timestamps in transactions
|
|
491
|
+
await sleep(2);
|
|
492
|
+
|
|
493
|
+
value.$jazz.set("pet", "cat");
|
|
494
|
+
|
|
495
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
496
|
+
|
|
497
|
+
render(
|
|
498
|
+
<CoMapView
|
|
499
|
+
coValue={value.$jazz.raw}
|
|
500
|
+
data={data}
|
|
501
|
+
node={account.$jazz.localNode}
|
|
502
|
+
onNavigate={() => {}}
|
|
503
|
+
/>,
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
507
|
+
fireEvent.click(restoreButton);
|
|
508
|
+
|
|
509
|
+
// Modal starts at most recent timestamp
|
|
510
|
+
const preview = screen.getByText(/State at that time:/);
|
|
511
|
+
await waitFor(() => {
|
|
512
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
513
|
+
expect(previewPre?.textContent).toContain("cat");
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
517
|
+
fireEvent.change(slider, { target: { value: "0" } });
|
|
518
|
+
|
|
519
|
+
await waitFor(
|
|
520
|
+
() => {
|
|
521
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
522
|
+
expect(previewPre?.textContent).toContain("dog");
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
timeout: 1000,
|
|
526
|
+
},
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("should update preview when slider moves", async () => {
|
|
531
|
+
const value = co
|
|
532
|
+
.map({
|
|
533
|
+
counter: z.number(),
|
|
534
|
+
})
|
|
535
|
+
.create({ counter: 1 }, account);
|
|
536
|
+
|
|
537
|
+
// wait to have different timestamps in transactions
|
|
538
|
+
await sleep(2);
|
|
539
|
+
value.$jazz.set("counter", 2);
|
|
540
|
+
await sleep(2);
|
|
541
|
+
value.$jazz.set("counter", 3);
|
|
542
|
+
await sleep(2);
|
|
543
|
+
value.$jazz.set("counter", 4);
|
|
544
|
+
|
|
545
|
+
const data = value.$jazz.raw.toJSON() as JsonObject;
|
|
546
|
+
|
|
547
|
+
render(
|
|
548
|
+
<CoMapView
|
|
549
|
+
coValue={value.$jazz.raw}
|
|
550
|
+
data={data}
|
|
551
|
+
node={account.$jazz.localNode}
|
|
552
|
+
onNavigate={() => {}}
|
|
553
|
+
/>,
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
557
|
+
fireEvent.click(restoreButton);
|
|
558
|
+
|
|
559
|
+
const slider = screen.getByRole("slider") as HTMLInputElement;
|
|
560
|
+
const preview = screen.getByText(/State at that time:/);
|
|
561
|
+
|
|
562
|
+
// Modal starts at most recent timestamp (counter: 4)
|
|
563
|
+
await waitFor(() => {
|
|
564
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
565
|
+
expect(previewPre?.textContent).toContain('"counter": 4');
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
fireEvent.change(slider, { target: { value: "0" } });
|
|
569
|
+
await waitFor(() => {
|
|
570
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
571
|
+
expect(previewPre?.textContent).toContain('"counter": 1');
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
fireEvent.change(slider, { target: { value: "2" } });
|
|
575
|
+
await waitFor(() => {
|
|
576
|
+
const previewPre = preview.parentElement?.querySelector("pre");
|
|
577
|
+
expect(previewPre?.textContent).toContain('"counter": 3');
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
describe("Permissions", () => {
|
|
583
|
+
it("should disable Add Property button for reader account", async () => {
|
|
584
|
+
const reader = await createJazzTestAccount();
|
|
585
|
+
const group = co.group().create({ owner: account });
|
|
586
|
+
group.addMember(reader, "reader");
|
|
587
|
+
|
|
588
|
+
const schema = co.map({
|
|
589
|
+
pet: z.string(),
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
593
|
+
|
|
594
|
+
const valueOnReader = await schema.load(value.$jazz.id, {
|
|
595
|
+
loadAs: reader,
|
|
596
|
+
});
|
|
597
|
+
assert(valueOnReader.$isLoaded);
|
|
598
|
+
const data = valueOnReader.$jazz.raw.toJSON() as JsonObject;
|
|
599
|
+
|
|
600
|
+
render(
|
|
601
|
+
<CoMapView
|
|
602
|
+
coValue={valueOnReader.$jazz.raw}
|
|
603
|
+
data={data}
|
|
604
|
+
node={reader.$jazz.localNode}
|
|
605
|
+
onNavigate={() => {}}
|
|
606
|
+
/>,
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
const addButton = screen.getByTitle("Add Property");
|
|
610
|
+
expect(addButton).toBeDefined();
|
|
611
|
+
expect((addButton as HTMLButtonElement).disabled).toBe(true);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it("should enable Add Property button for writer account", async () => {
|
|
615
|
+
const writer = await createJazzTestAccount();
|
|
616
|
+
const group = co.group().create({ owner: account });
|
|
617
|
+
group.addMember(writer, "writer");
|
|
618
|
+
|
|
619
|
+
const schema = co.map({
|
|
620
|
+
pet: z.string(),
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
624
|
+
|
|
625
|
+
const valueOnWriter = await schema.load(value.$jazz.id, {
|
|
626
|
+
loadAs: writer,
|
|
627
|
+
});
|
|
628
|
+
assert(valueOnWriter.$isLoaded);
|
|
629
|
+
const data = valueOnWriter.$jazz.raw.toJSON() as JsonObject;
|
|
630
|
+
|
|
631
|
+
render(
|
|
632
|
+
<CoMapView
|
|
633
|
+
coValue={valueOnWriter.$jazz.raw}
|
|
634
|
+
data={data}
|
|
635
|
+
node={writer.$jazz.localNode}
|
|
636
|
+
onNavigate={() => {}}
|
|
637
|
+
/>,
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
const addButton = screen.getByTitle("Add Property");
|
|
641
|
+
expect(addButton).toBeDefined();
|
|
642
|
+
expect((addButton as HTMLButtonElement).disabled).toBe(false);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it("should hide restore buttons for reader account when multiple timestamps exist", async () => {
|
|
646
|
+
const reader = await createJazzTestAccount();
|
|
647
|
+
const group = co.group().create({ owner: account });
|
|
648
|
+
group.addMember(reader, "reader");
|
|
649
|
+
|
|
650
|
+
const schema = co.map({
|
|
651
|
+
pet: z.string(),
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
655
|
+
await sleep(2);
|
|
656
|
+
value.$jazz.set("pet", "cat");
|
|
657
|
+
|
|
658
|
+
const valueOnReader = await schema.load(value.$jazz.id, {
|
|
659
|
+
loadAs: reader,
|
|
660
|
+
});
|
|
661
|
+
assert(valueOnReader.$isLoaded);
|
|
662
|
+
const data = valueOnReader.$jazz.raw.toJSON() as JsonObject;
|
|
663
|
+
|
|
664
|
+
render(
|
|
665
|
+
<CoMapView
|
|
666
|
+
coValue={valueOnReader.$jazz.raw}
|
|
667
|
+
data={data}
|
|
668
|
+
node={reader.$jazz.localNode}
|
|
669
|
+
onNavigate={() => {}}
|
|
670
|
+
/>,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
674
|
+
fireEvent.click(restoreButton);
|
|
675
|
+
|
|
676
|
+
await waitFor(() => {
|
|
677
|
+
expect(screen.getByText("Select Timestamp")).toBeDefined();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
expect(screen.queryByText("Restore")).toBeNull();
|
|
681
|
+
expect(screen.queryByRole("checkbox")).toBeNull();
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it("should show restore buttons for writer account when multiple timestamps exist", async () => {
|
|
685
|
+
const writer = await createJazzTestAccount();
|
|
686
|
+
const group = co.group().create({ owner: account });
|
|
687
|
+
group.addMember(writer, "writer");
|
|
688
|
+
|
|
689
|
+
const schema = co.map({
|
|
690
|
+
pet: z.string(),
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
694
|
+
await sleep(2);
|
|
695
|
+
value.$jazz.set("pet", "cat");
|
|
696
|
+
|
|
697
|
+
const valueOnWriter = await schema.load(value.$jazz.id, {
|
|
698
|
+
loadAs: writer,
|
|
699
|
+
});
|
|
700
|
+
assert(valueOnWriter.$isLoaded);
|
|
701
|
+
const data = valueOnWriter.$jazz.raw.toJSON() as JsonObject;
|
|
702
|
+
|
|
703
|
+
render(
|
|
704
|
+
<CoMapView
|
|
705
|
+
coValue={valueOnWriter.$jazz.raw}
|
|
706
|
+
data={data}
|
|
707
|
+
node={writer.$jazz.localNode}
|
|
708
|
+
onNavigate={() => {}}
|
|
709
|
+
/>,
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
const restoreButton = screen.getByTitle("Timeline");
|
|
713
|
+
fireEvent.click(restoreButton);
|
|
714
|
+
|
|
715
|
+
await waitFor(() => {
|
|
716
|
+
expect(screen.getByText("Restore")).toBeDefined();
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
expect(screen.getByRole("checkbox")).toBeDefined();
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it("should hide edit buttons in GridView for reader account", async () => {
|
|
723
|
+
const reader = await createJazzTestAccount();
|
|
724
|
+
const group = co.group().create({ owner: account });
|
|
725
|
+
group.addMember(reader, "reader");
|
|
726
|
+
|
|
727
|
+
const schema = co.map({
|
|
728
|
+
pet: z.string(),
|
|
729
|
+
age: z.number(),
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const value = schema.create({ pet: "dog", age: 10 }, group);
|
|
733
|
+
|
|
734
|
+
const valueOnReader = await schema.load(value.$jazz.id, {
|
|
735
|
+
loadAs: reader,
|
|
736
|
+
});
|
|
737
|
+
assert(valueOnReader.$isLoaded);
|
|
738
|
+
const data = valueOnReader.$jazz.raw.toJSON() as JsonObject;
|
|
739
|
+
|
|
740
|
+
render(
|
|
741
|
+
<CoMapView
|
|
742
|
+
coValue={valueOnReader.$jazz.raw}
|
|
743
|
+
data={data}
|
|
744
|
+
node={reader.$jazz.localNode}
|
|
745
|
+
onNavigate={() => {}}
|
|
746
|
+
/>,
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
expect(screen.getByText("pet")).toBeDefined();
|
|
750
|
+
expect(screen.getByText("age")).toBeDefined();
|
|
751
|
+
|
|
752
|
+
const editButtons = screen.queryAllByLabelText("Edit");
|
|
753
|
+
const deleteButtons = screen.queryAllByLabelText("Delete");
|
|
754
|
+
|
|
755
|
+
expect(editButtons).toHaveLength(0);
|
|
756
|
+
expect(deleteButtons).toHaveLength(0);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
it("should show edit buttons in GridView for writer account", async () => {
|
|
760
|
+
const writer = await createJazzTestAccount();
|
|
761
|
+
const group = co.group().create({ owner: account });
|
|
762
|
+
group.addMember(writer, "writer");
|
|
763
|
+
|
|
764
|
+
const schema = co.map({
|
|
765
|
+
pet: z.string(),
|
|
766
|
+
age: z.number(),
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
const value = schema.create({ pet: "dog", age: 10 }, group);
|
|
770
|
+
|
|
771
|
+
const valueOnWriter = await schema.load(value.$jazz.id, {
|
|
772
|
+
loadAs: writer,
|
|
773
|
+
});
|
|
774
|
+
assert(valueOnWriter.$isLoaded);
|
|
775
|
+
const data = valueOnWriter.$jazz.raw.toJSON() as JsonObject;
|
|
776
|
+
|
|
777
|
+
render(
|
|
778
|
+
<CoMapView
|
|
779
|
+
coValue={valueOnWriter.$jazz.raw}
|
|
780
|
+
data={data}
|
|
781
|
+
node={writer.$jazz.localNode}
|
|
782
|
+
onNavigate={() => {}}
|
|
783
|
+
/>,
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
expect(screen.getByText("pet")).toBeDefined();
|
|
787
|
+
expect(screen.getByText("age")).toBeDefined();
|
|
788
|
+
|
|
789
|
+
const editButtons = screen.queryAllByLabelText("Edit");
|
|
790
|
+
const deleteButtons = screen.queryAllByLabelText("Delete");
|
|
791
|
+
|
|
792
|
+
expect(editButtons.length).toBeGreaterThan(0);
|
|
793
|
+
expect(deleteButtons.length).toBeGreaterThan(0);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
it("should enable Add Property button for admin account", async () => {
|
|
797
|
+
const admin = await createJazzTestAccount();
|
|
798
|
+
const group = co.group().create({ owner: account });
|
|
799
|
+
group.addMember(admin, "admin");
|
|
800
|
+
|
|
801
|
+
const schema = co.map({
|
|
802
|
+
pet: z.string(),
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
806
|
+
|
|
807
|
+
const valueOnAdmin = await schema.load(value.$jazz.id, {
|
|
808
|
+
loadAs: admin,
|
|
809
|
+
});
|
|
810
|
+
assert(valueOnAdmin.$isLoaded);
|
|
811
|
+
const data = valueOnAdmin.$jazz.raw.toJSON() as JsonObject;
|
|
812
|
+
|
|
813
|
+
render(
|
|
814
|
+
<CoMapView
|
|
815
|
+
coValue={valueOnAdmin.$jazz.raw}
|
|
816
|
+
data={data}
|
|
817
|
+
node={admin.$jazz.localNode}
|
|
818
|
+
onNavigate={() => {}}
|
|
819
|
+
/>,
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
const addButton = screen.getByTitle("Add Property");
|
|
823
|
+
expect(addButton).toBeDefined();
|
|
824
|
+
expect((addButton as HTMLButtonElement).disabled).toBe(false);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("should enable Add Property button for manager account", async () => {
|
|
828
|
+
const manager = await createJazzTestAccount();
|
|
829
|
+
const group = co.group().create({ owner: account });
|
|
830
|
+
group.addMember(manager, "manager");
|
|
831
|
+
|
|
832
|
+
const schema = co.map({
|
|
833
|
+
pet: z.string(),
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
837
|
+
|
|
838
|
+
const valueOnManager = await schema.load(value.$jazz.id, {
|
|
839
|
+
loadAs: manager,
|
|
840
|
+
});
|
|
841
|
+
assert(valueOnManager.$isLoaded);
|
|
842
|
+
const data = valueOnManager.$jazz.raw.toJSON() as JsonObject;
|
|
843
|
+
|
|
844
|
+
render(
|
|
845
|
+
<CoMapView
|
|
846
|
+
coValue={valueOnManager.$jazz.raw}
|
|
847
|
+
data={data}
|
|
848
|
+
node={manager.$jazz.localNode}
|
|
849
|
+
onNavigate={() => {}}
|
|
850
|
+
/>,
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
const addButton = screen.getByTitle("Add Property");
|
|
854
|
+
expect(addButton).toBeDefined();
|
|
855
|
+
expect((addButton as HTMLButtonElement).disabled).toBe(false);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
it("should enable Add Property button for writeOnly account", async () => {
|
|
859
|
+
const writeOnly = await createJazzTestAccount();
|
|
860
|
+
const group = co.group().create({ owner: account });
|
|
861
|
+
group.addMember(writeOnly, "writeOnly");
|
|
862
|
+
|
|
863
|
+
const schema = co.map({
|
|
864
|
+
pet: z.string(),
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
const value = schema.create({ pet: "dog" }, group);
|
|
868
|
+
|
|
869
|
+
const valueOnWriteOnly = await schema.load(value.$jazz.id, {
|
|
870
|
+
loadAs: writeOnly,
|
|
871
|
+
});
|
|
872
|
+
assert(valueOnWriteOnly.$isLoaded);
|
|
873
|
+
const data = valueOnWriteOnly.$jazz.raw.toJSON() as JsonObject;
|
|
874
|
+
|
|
875
|
+
render(
|
|
876
|
+
<CoMapView
|
|
877
|
+
coValue={valueOnWriteOnly.$jazz.raw}
|
|
878
|
+
data={data}
|
|
879
|
+
node={writeOnly.$jazz.localNode}
|
|
880
|
+
onNavigate={() => {}}
|
|
881
|
+
/>,
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
const addButton = screen.getByTitle("Add Property");
|
|
885
|
+
expect(addButton).toBeDefined();
|
|
886
|
+
expect((addButton as HTMLButtonElement).disabled).toBe(false);
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
});
|