@ng-org/alien-deepsignals 0.1.2-alpha.10 → 0.1.2-alpha.12
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/dist/core.d.ts +5 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +11 -11
- package/dist/deepSignal.d.ts +1 -1
- package/dist/deepSignal.d.ts.map +1 -1
- package/dist/deepSignal.js +16 -27
- package/dist/hooks/react/index.js +2 -8
- package/dist/hooks/react/useDeepSignal.d.ts +2 -2
- package/dist/hooks/react/useDeepSignal.d.ts.map +1 -1
- package/dist/hooks/react/useDeepSignal.js +11 -13
- package/dist/hooks/svelte/index.js +2 -8
- package/dist/hooks/svelte/useDeepSignal.svelte.d.ts +1 -1
- package/dist/hooks/svelte/useDeepSignal.svelte.d.ts.map +1 -1
- package/dist/hooks/svelte/useDeepSignal.svelte.js +6 -9
- package/dist/hooks/svelte4/index.js +2 -8
- package/dist/hooks/svelte4/useDeepSignal.svelte.d.ts +1 -1
- package/dist/hooks/svelte4/useDeepSignal.svelte.d.ts.map +1 -1
- package/dist/hooks/svelte4/useDeepSignal.svelte.js +14 -17
- package/dist/hooks/vue/index.d.ts +1 -1
- package/dist/hooks/vue/index.d.ts.map +1 -1
- package/dist/hooks/vue/index.js +2 -8
- package/dist/hooks/vue/useDeepSignal.d.ts +1 -1
- package/dist/hooks/vue/useDeepSignal.d.ts.map +1 -1
- package/dist/hooks/vue/useDeepSignal.js +6 -9
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -27
- package/dist/iteratorHelpers.js +2 -6
- package/dist/test/frontend/astro-app/src/components/ReactPanel.d.ts.map +1 -1
- package/dist/test/frontend/astro-app/src/components/ReactPanel.jsx +13 -51
- package/dist/test/frontend/playwright/crossFrameworkHooks.spec.js +24 -26
- package/dist/test/frontend/utils/mockData.js +16 -21
- package/dist/test/frontend/utils/paths.js +2 -6
- package/dist/test/frontend/utils/renderMetrics.js +5 -12
- package/dist/test/frontend/utils/state.d.ts +1 -1
- package/dist/test/frontend/utils/state.js +13 -17
- package/dist/test/lib/deepSignalOptions.test.js +66 -68
- package/dist/test/lib/index.test.js +373 -375
- package/dist/test/lib/misc.test.js +98 -100
- package/dist/test/lib/watch.test.js +355 -357
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -2
- package/dist/watch.d.ts +1 -1
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +6 -9
- package/package.json +2 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Copyright (c) 2025 Laurin Weger, Par le Peuple, NextGraph.org developers
|
|
3
2
|
// All rights reserved.
|
|
4
3
|
// Licensed under the Apache License, Version 2.0
|
|
@@ -8,153 +7,152 @@
|
|
|
8
7
|
// notice may not be copied, modified, or distributed except
|
|
9
8
|
// according to those terms.
|
|
10
9
|
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(
|
|
17
|
-
|
|
18
|
-
const store = (0, __1.deepSignal)({
|
|
10
|
+
import { describe, it, expect } from "vitest";
|
|
11
|
+
import { deepSignal, addWithId, } from "../../index.js";
|
|
12
|
+
import { watch } from "../../watch.js";
|
|
13
|
+
import { setSetEntrySyntheticId } from "../../deepSignal.js";
|
|
14
|
+
describe("watch", () => {
|
|
15
|
+
it("watch immediate", () => {
|
|
16
|
+
const store = deepSignal({
|
|
19
17
|
userinfo: {
|
|
20
18
|
name: "tom",
|
|
21
19
|
},
|
|
22
20
|
});
|
|
23
21
|
let val;
|
|
24
|
-
|
|
22
|
+
watch(store, ({ newValue }) => {
|
|
25
23
|
val = newValue.userinfo.name;
|
|
26
24
|
}, { immediate: true });
|
|
27
|
-
|
|
25
|
+
expect(val).toEqual("tom");
|
|
28
26
|
});
|
|
29
|
-
|
|
30
|
-
const store =
|
|
27
|
+
it("watch deep", () => {
|
|
28
|
+
const store = deepSignal({
|
|
31
29
|
userinfo: {
|
|
32
30
|
name: "tom",
|
|
33
31
|
},
|
|
34
32
|
});
|
|
35
33
|
let val;
|
|
36
|
-
|
|
34
|
+
watch(store, ({ newValue }) => {
|
|
37
35
|
val = newValue.userinfo.name;
|
|
38
36
|
}, { immediate: true });
|
|
39
37
|
let value2;
|
|
40
|
-
|
|
38
|
+
watch(store, ({ newValue }) => {
|
|
41
39
|
value2 = newValue.userinfo.name;
|
|
42
40
|
}, { immediate: true });
|
|
43
|
-
|
|
41
|
+
expect(val).toEqual("tom");
|
|
44
42
|
store.userinfo.name = "jon";
|
|
45
43
|
// patch delivery async (microtask)
|
|
46
44
|
return Promise.resolve().then(() => {
|
|
47
|
-
|
|
45
|
+
expect(val).toEqual("jon");
|
|
48
46
|
// With refactored watch using native effect, shallow watcher now also updates root reference
|
|
49
|
-
|
|
47
|
+
expect(value2).toEqual("jon");
|
|
50
48
|
});
|
|
51
49
|
});
|
|
52
|
-
|
|
53
|
-
const store =
|
|
50
|
+
it("watch once", () => {
|
|
51
|
+
const store = deepSignal({
|
|
54
52
|
userinfo: {
|
|
55
53
|
name: "tom",
|
|
56
54
|
},
|
|
57
55
|
});
|
|
58
56
|
let val;
|
|
59
|
-
|
|
57
|
+
watch(store, ({ newValue }) => {
|
|
60
58
|
val = newValue.userinfo.name;
|
|
61
59
|
}, { immediate: true, once: true });
|
|
62
|
-
|
|
60
|
+
expect(val).toEqual("tom");
|
|
63
61
|
store.userinfo.name = "jon";
|
|
64
62
|
// once watcher shouldn't update after first run
|
|
65
|
-
|
|
63
|
+
expect(val).toEqual("tom");
|
|
66
64
|
});
|
|
67
|
-
|
|
68
|
-
const state =
|
|
65
|
+
it("delivers immediate snapshot", () => {
|
|
66
|
+
const state = deepSignal({ user: { name: "tom" } });
|
|
69
67
|
let observed = "";
|
|
70
|
-
|
|
68
|
+
watch(state, ({ newValue }) => {
|
|
71
69
|
observed = newValue.user.name;
|
|
72
70
|
}, { immediate: true });
|
|
73
|
-
|
|
71
|
+
expect(observed).toEqual("tom");
|
|
74
72
|
});
|
|
75
|
-
|
|
76
|
-
const state =
|
|
73
|
+
it("emits patches with version", async () => {
|
|
74
|
+
const state = deepSignal({ count: 0 });
|
|
77
75
|
const versions = [];
|
|
78
|
-
|
|
76
|
+
watch(state, ({ version }) => {
|
|
79
77
|
versions.push(version);
|
|
80
78
|
});
|
|
81
79
|
state.count = 1;
|
|
82
80
|
await Promise.resolve();
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
expect(versions.length).toBe(1);
|
|
82
|
+
expect(versions[0]).toBeGreaterThan(0);
|
|
85
83
|
});
|
|
86
84
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const state =
|
|
85
|
+
describe("watch (patch mode)", () => {
|
|
86
|
+
it("emits set patches with correct paths and batching", async () => {
|
|
87
|
+
const state = deepSignal({ a: { b: 1 }, arr: [1, { x: 2 }] });
|
|
90
88
|
const received = [];
|
|
91
|
-
const { stopListening: stop } =
|
|
89
|
+
const { stopListening: stop } = watch(state, ({ patches }) => {
|
|
92
90
|
received.push(patches);
|
|
93
91
|
});
|
|
94
92
|
state.a.b = 2;
|
|
95
93
|
state.arr[1].x = 3;
|
|
96
94
|
state.arr.push(5);
|
|
97
95
|
await Promise.resolve();
|
|
98
|
-
|
|
96
|
+
expect(received).toHaveLength(1);
|
|
99
97
|
const batch = received[0];
|
|
100
|
-
|
|
98
|
+
expect(batch).toEqual([
|
|
101
99
|
{ op: "add", path: ["a", "b"], value: 2 },
|
|
102
100
|
{ op: "add", path: ["arr", "1", "x"], value: 3 },
|
|
103
101
|
{ op: "add", path: ["arr", "2"], value: 5 },
|
|
104
102
|
]);
|
|
105
103
|
stop();
|
|
106
104
|
});
|
|
107
|
-
|
|
108
|
-
const state =
|
|
105
|
+
it("emits delete patches without value", async () => {
|
|
106
|
+
const state = deepSignal({
|
|
109
107
|
a: { b: 1 },
|
|
110
108
|
c: 2,
|
|
111
109
|
});
|
|
112
110
|
const out = [];
|
|
113
|
-
const { stopListening: stop } =
|
|
111
|
+
const { stopListening: stop } = watch(state, ({ patches }) => out.push(patches));
|
|
114
112
|
delete state.a.b;
|
|
115
113
|
delete state.c;
|
|
116
114
|
await Promise.resolve();
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
expect(out).toHaveLength(1);
|
|
116
|
+
expect(out[0]).toEqual([
|
|
119
117
|
{ op: "remove", path: ["a", "b"] },
|
|
120
118
|
{ op: "remove", path: ["c"] },
|
|
121
119
|
]);
|
|
122
120
|
stop();
|
|
123
121
|
});
|
|
124
|
-
|
|
125
|
-
const a =
|
|
126
|
-
const b =
|
|
122
|
+
it("filters out patches from other roots", async () => {
|
|
123
|
+
const a = deepSignal({ x: 1 });
|
|
124
|
+
const b = deepSignal({ y: 2 });
|
|
127
125
|
const out = [];
|
|
128
|
-
const { stopListening: stop } =
|
|
126
|
+
const { stopListening: stop } = watch(a, ({ patches }) => out.push(patches));
|
|
129
127
|
b.y = 3;
|
|
130
128
|
a.x = 2;
|
|
131
129
|
await Promise.resolve();
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
expect(out).toHaveLength(1);
|
|
131
|
+
expect(out[0]).toEqual([{ op: "add", path: ["x"], value: 2 }]);
|
|
134
132
|
stop();
|
|
135
133
|
});
|
|
136
|
-
|
|
137
|
-
const state =
|
|
134
|
+
it("emits patches for Set structural mutations (add/delete)", async () => {
|
|
135
|
+
const state = deepSignal({ s: new Set([1, 2]) });
|
|
138
136
|
const batches = [];
|
|
139
|
-
const { stopListening: stop } =
|
|
137
|
+
const { stopListening: stop } = watch(state, ({ patches }) => batches.push(patches));
|
|
140
138
|
state.s.add(3);
|
|
141
139
|
state.s.delete(1);
|
|
142
140
|
await Promise.resolve();
|
|
143
141
|
const flattened = batches.flat();
|
|
144
|
-
|
|
142
|
+
expect(flattened).toEqual([
|
|
145
143
|
{ op: "add", path: ["s"], type: "set", value: [3] },
|
|
146
144
|
{ op: "remove", path: ["s"], type: "set", value: 1 },
|
|
147
145
|
]);
|
|
148
146
|
stop();
|
|
149
147
|
});
|
|
150
|
-
|
|
151
|
-
const state =
|
|
148
|
+
it("emits patches for nested objects added after initialization", async () => {
|
|
149
|
+
const state = deepSignal({ root: {} });
|
|
152
150
|
const patches = [];
|
|
153
|
-
const { stopListening: stop } =
|
|
151
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
154
152
|
state.root.child = { level: { value: 1 }, l1: "val" };
|
|
155
153
|
await Promise.resolve();
|
|
156
154
|
const flattened = patches.flat();
|
|
157
|
-
|
|
155
|
+
expect(flattened).toEqual([
|
|
158
156
|
{ op: "add", path: ["root", "child"], value: {} },
|
|
159
157
|
{ op: "add", path: ["root", "child", "level"], value: {} },
|
|
160
158
|
{
|
|
@@ -166,28 +164,28 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
166
164
|
]);
|
|
167
165
|
stop();
|
|
168
166
|
});
|
|
169
|
-
|
|
170
|
-
const state =
|
|
167
|
+
it("emits patches for Set with primitives added as one operation", async () => {
|
|
168
|
+
const state = deepSignal({ container: {} }, {
|
|
171
169
|
syntheticIdPropertyName: "id",
|
|
172
170
|
});
|
|
173
171
|
const patches = [];
|
|
174
|
-
const { stopListening: stop } =
|
|
172
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(...batch));
|
|
175
173
|
state.container.items = new Set(["item1"]);
|
|
176
174
|
state.container.items.add("item2");
|
|
177
175
|
await Promise.resolve();
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
expect(patches).toHaveLength(3);
|
|
177
|
+
expect(patches[0]).toMatchObject({
|
|
180
178
|
op: "add",
|
|
181
179
|
path: ["container", "items"],
|
|
182
180
|
type: "set",
|
|
183
181
|
});
|
|
184
|
-
|
|
182
|
+
expect(patches[1]).toMatchObject({
|
|
185
183
|
op: "add",
|
|
186
184
|
path: ["container", "items"],
|
|
187
185
|
type: "set",
|
|
188
186
|
value: ["item1"],
|
|
189
187
|
});
|
|
190
|
-
|
|
188
|
+
expect(patches[2]).toMatchObject({
|
|
191
189
|
op: "add",
|
|
192
190
|
path: ["container", "items"],
|
|
193
191
|
type: "set",
|
|
@@ -195,10 +193,10 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
195
193
|
});
|
|
196
194
|
stop();
|
|
197
195
|
});
|
|
198
|
-
|
|
199
|
-
const state =
|
|
196
|
+
it("emits patches for deeply nested arrays and objects", async () => {
|
|
197
|
+
const state = deepSignal({ data: null });
|
|
200
198
|
const patches = [];
|
|
201
|
-
const { stopListening: stop } =
|
|
199
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
202
200
|
state.data = {
|
|
203
201
|
users: [
|
|
204
202
|
{
|
|
@@ -214,7 +212,7 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
214
212
|
};
|
|
215
213
|
await Promise.resolve();
|
|
216
214
|
const flattened = patches.flat();
|
|
217
|
-
|
|
215
|
+
expect(flattened).toEqual([
|
|
218
216
|
{ op: "add", path: ["data"], value: {}, type: undefined },
|
|
219
217
|
{ op: "add", path: ["data", "users"], value: [], type: undefined },
|
|
220
218
|
{
|
|
@@ -285,20 +283,20 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
285
283
|
]);
|
|
286
284
|
stop();
|
|
287
285
|
});
|
|
288
|
-
|
|
289
|
-
const state =
|
|
286
|
+
it("emits patches for Set with nested objects added as one operation", async () => {
|
|
287
|
+
const state = deepSignal({ container: {} }, {
|
|
290
288
|
syntheticIdPropertyName: "id",
|
|
291
289
|
propGenerator: ({ object }) => ({ syntheticId: object.id }),
|
|
292
290
|
});
|
|
293
291
|
const patches = [];
|
|
294
|
-
const { stopListening: stop } =
|
|
292
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
295
293
|
state.container.items = new Set([
|
|
296
294
|
{ id: "a", data: { nested: { value: 1 } } },
|
|
297
295
|
{ id: "b", data: { nested: { value: 2 } } },
|
|
298
296
|
]);
|
|
299
297
|
await Promise.resolve();
|
|
300
298
|
const flattened = patches.flat();
|
|
301
|
-
|
|
299
|
+
expect(flattened).toEqual([
|
|
302
300
|
{ op: "add", path: ["container", "items"], type: "set", value: [] },
|
|
303
301
|
{ op: "add", path: ["container", "items", "a"], value: {} },
|
|
304
302
|
{ op: "add", path: ["container", "items", "a", "id"], value: "a" },
|
|
@@ -337,28 +335,28 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
337
335
|
]);
|
|
338
336
|
stop();
|
|
339
337
|
});
|
|
340
|
-
|
|
338
|
+
it("emits structural patches for sets of sets", async () => {
|
|
341
339
|
const innerA = new Set([{ id: "node1", x: 1 }]);
|
|
342
340
|
const s = new Set([innerA]);
|
|
343
|
-
const state =
|
|
341
|
+
const state = deepSignal({ graph: s });
|
|
344
342
|
const batches = [];
|
|
345
|
-
const { stopListening: stop } =
|
|
343
|
+
const { stopListening: stop } = watch(state, ({ patches }) => batches.push(patches));
|
|
346
344
|
const innerB = new Set([{ id: "node2", x: 5 }]);
|
|
347
345
|
state.graph.add(innerB);
|
|
348
346
|
[...innerA][0].x = 2;
|
|
349
347
|
await Promise.resolve();
|
|
350
348
|
const pathStrings = batches.flat().map((p) => p.path.join("."));
|
|
351
|
-
|
|
349
|
+
expect(pathStrings.some((p) => p.startsWith("graph."))).toBe(true);
|
|
352
350
|
stop();
|
|
353
351
|
});
|
|
354
|
-
|
|
352
|
+
it("tracks deep nested object mutation inside a Set entry after iteration", async () => {
|
|
355
353
|
const rawEntry = { id: "n1", data: { val: 1 } };
|
|
356
|
-
const st =
|
|
354
|
+
const st = deepSignal({ bag: new Set([rawEntry]) }, {
|
|
357
355
|
syntheticIdPropertyName: "id",
|
|
358
356
|
propGenerator: ({ object }) => ({ syntheticId: object.id }),
|
|
359
357
|
});
|
|
360
358
|
const collected = [];
|
|
361
|
-
const { stopListening: stop } =
|
|
359
|
+
const { stopListening: stop } = watch(st, ({ patches }) => collected.push(patches));
|
|
362
360
|
let proxied;
|
|
363
361
|
for (const e of st.bag.values()) {
|
|
364
362
|
proxied = e;
|
|
@@ -367,21 +365,21 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
367
365
|
proxied.data.val = 2;
|
|
368
366
|
await Promise.resolve();
|
|
369
367
|
const flat = collected.flat().map((p) => p.path.join("."));
|
|
370
|
-
|
|
368
|
+
expect(flat.some((p) => p.endsWith("n1.data.val"))).toBe(true);
|
|
371
369
|
stop();
|
|
372
370
|
});
|
|
373
|
-
|
|
371
|
+
it("allows custom synthetic id for Set entry", async () => {
|
|
374
372
|
const node = { name: "x" };
|
|
375
|
-
const state =
|
|
373
|
+
const state = deepSignal({ s: new Set() });
|
|
376
374
|
const collected2 = [];
|
|
377
|
-
const { stopListening: stop } =
|
|
378
|
-
|
|
375
|
+
const { stopListening: stop } = watch(state, ({ patches }) => collected2.push(patches));
|
|
376
|
+
addWithId(state.s, node, "custom123");
|
|
379
377
|
await Promise.resolve();
|
|
380
378
|
const flat = collected2.flat().map((p) => p.path.join("."));
|
|
381
|
-
|
|
379
|
+
expect(flat.some((p) => p === "s.custom123")).toBe(true);
|
|
382
380
|
stop();
|
|
383
381
|
});
|
|
384
|
-
|
|
382
|
+
it("should apply extraProps returned from propGenerator when adding objects to Set", async () => {
|
|
385
383
|
const options = {
|
|
386
384
|
propGenerator: ({ path, object }) => {
|
|
387
385
|
// Generate @graph and @id if not present or empty
|
|
@@ -397,123 +395,123 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
397
395
|
};
|
|
398
396
|
},
|
|
399
397
|
};
|
|
400
|
-
const state =
|
|
398
|
+
const state = deepSignal(new Set(), options);
|
|
401
399
|
const patches = [];
|
|
402
|
-
const { stopListening: stop } =
|
|
400
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
403
401
|
// Add object with empty @graph and @id (should be replaced)
|
|
404
402
|
const newObj = { "@graph": "", "@id": "", name: "Test Object" };
|
|
405
403
|
state.add(newObj);
|
|
406
404
|
await Promise.resolve();
|
|
407
405
|
// Check that extraProps were applied to the object
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
406
|
+
expect(newObj["@id"]).toBeDefined();
|
|
407
|
+
expect(newObj["@graph"]).toBeDefined();
|
|
408
|
+
expect(newObj["@graph"]).toBe("did:ng:test-graph");
|
|
411
409
|
// Check that patches were emitted with the generated values
|
|
412
410
|
const allPatches = patches.flat();
|
|
413
411
|
const idPatch = allPatches.find((p) => p.path.length === 2 && p.path[1] === "@id");
|
|
414
412
|
const graphPatch = allPatches.find((p) => p.path.length === 2 && p.path[1] === "@graph");
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
413
|
+
expect(idPatch).toBeDefined();
|
|
414
|
+
expect(idPatch?.op).toBe("add");
|
|
415
|
+
expect(idPatch?.value).toBeDefined();
|
|
416
|
+
expect(idPatch?.value?.startsWith("did:ng:test-subject")).toBeTruthy();
|
|
417
|
+
expect(graphPatch).toBeDefined();
|
|
418
|
+
expect(graphPatch?.op).toBe("add");
|
|
419
|
+
expect(graphPatch?.value).toBe("did:ng:test-graph");
|
|
422
420
|
stop();
|
|
423
421
|
});
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const st =
|
|
422
|
+
describe("Set", () => {
|
|
423
|
+
it("emits patches for primitive adds", async () => {
|
|
424
|
+
const st = deepSignal({ s: new Set() });
|
|
427
425
|
const batches = [];
|
|
428
|
-
const { stopListening: stop } =
|
|
426
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
429
427
|
st.s.add(true);
|
|
430
428
|
st.s.add(2);
|
|
431
429
|
st.s.add("3");
|
|
432
430
|
await Promise.resolve();
|
|
433
|
-
|
|
431
|
+
expect(batches.length).toBe(1);
|
|
434
432
|
const patches = batches[0];
|
|
435
|
-
|
|
433
|
+
expect(patches.length).toBe(3);
|
|
436
434
|
// All patches should have the same path (the Set itself)
|
|
437
435
|
patches.forEach((p) => {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
436
|
+
expect(p.path.join(".")).toBe("s");
|
|
437
|
+
expect(p.op).toBe("add");
|
|
438
|
+
expect(p.type).toBe("set");
|
|
441
439
|
});
|
|
442
440
|
// Check that values are in the value field, not in path
|
|
443
441
|
const values = patches.map((p) => p.value[0]);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
442
|
+
expect(values).toContain(true);
|
|
443
|
+
expect(values).toContain(2);
|
|
444
|
+
expect(values).toContain("3");
|
|
447
445
|
stop();
|
|
448
446
|
});
|
|
449
|
-
|
|
450
|
-
const st =
|
|
447
|
+
it("emits patches for primitive deletes", async () => {
|
|
448
|
+
const st = deepSignal({ s: new Set([true, 2, "3"]) });
|
|
451
449
|
const batches = [];
|
|
452
|
-
const { stopListening: stop } =
|
|
450
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
453
451
|
st.s.delete(true);
|
|
454
452
|
st.s.delete(2);
|
|
455
453
|
await Promise.resolve();
|
|
456
|
-
|
|
454
|
+
expect(batches.length).toBe(1);
|
|
457
455
|
const patches = batches[0];
|
|
458
|
-
|
|
456
|
+
expect(patches.length).toBe(2);
|
|
459
457
|
// All patches should have the same path (the Set itself)
|
|
460
458
|
patches.forEach((p) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
expect(p.path.join(".")).toBe("s");
|
|
460
|
+
expect(p.op).toBe("remove");
|
|
461
|
+
expect(p.type).toBe("set");
|
|
464
462
|
});
|
|
465
463
|
// Check that values are in the value field
|
|
466
464
|
const values = patches.map((p) => p.value);
|
|
467
|
-
|
|
468
|
-
|
|
465
|
+
expect(values).toContain(true);
|
|
466
|
+
expect(values).toContain(2);
|
|
469
467
|
stop();
|
|
470
468
|
});
|
|
471
|
-
|
|
472
|
-
const st =
|
|
469
|
+
it("does not emit patches for non-existent primitives", async () => {
|
|
470
|
+
const st = deepSignal({ s: new Set([1, 2]) });
|
|
473
471
|
const batches = [];
|
|
474
|
-
const { stopListening: stop } =
|
|
472
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
475
473
|
st.s.delete("nonexistent");
|
|
476
474
|
st.s.delete(999);
|
|
477
475
|
await Promise.resolve();
|
|
478
|
-
|
|
476
|
+
expect(batches.length).toBe(0);
|
|
479
477
|
stop();
|
|
480
478
|
});
|
|
481
|
-
|
|
482
|
-
const st =
|
|
479
|
+
it("does not emit patches for already added primitive", async () => {
|
|
480
|
+
const st = deepSignal({ s: new Set([1, "test", true]) });
|
|
483
481
|
const batches = [];
|
|
484
|
-
const { stopListening: stop } =
|
|
482
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
485
483
|
st.s.add(1);
|
|
486
484
|
st.s.add("test");
|
|
487
485
|
st.s.add(true);
|
|
488
486
|
await Promise.resolve();
|
|
489
|
-
|
|
487
|
+
expect(batches.length).toBe(0);
|
|
490
488
|
stop();
|
|
491
489
|
});
|
|
492
|
-
|
|
493
|
-
const st =
|
|
494
|
-
|
|
495
|
-
|
|
490
|
+
it("emits single structural patch on Set.clear()", async () => {
|
|
491
|
+
const st = deepSignal({ s: new Set() });
|
|
492
|
+
addWithId(st.s, { id: "a", x: 1 }, "a");
|
|
493
|
+
addWithId(st.s, { id: "b", x: 2 }, "b");
|
|
496
494
|
const batches = [];
|
|
497
|
-
const { stopListening: stop } =
|
|
495
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
498
496
|
st.s.clear();
|
|
499
497
|
await Promise.resolve();
|
|
500
498
|
// clear() emits a single structural patch for the Set itself (op: "add", value: [])
|
|
501
499
|
const structuralPatches = batches
|
|
502
500
|
.flat()
|
|
503
501
|
.filter((p) => p.path.length === 1 && p.path[0] === "s");
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
502
|
+
expect(structuralPatches.length).toBe(1);
|
|
503
|
+
expect(structuralPatches[0].op).toBe("add");
|
|
504
|
+
expect(structuralPatches[0].value).toEqual([]);
|
|
507
505
|
stop();
|
|
508
506
|
});
|
|
509
|
-
|
|
510
|
-
const st =
|
|
507
|
+
it("emits delete patch for object entry", async () => {
|
|
508
|
+
const st = deepSignal({ s: new Set() }, {
|
|
511
509
|
syntheticIdPropertyName: "id",
|
|
512
510
|
propGenerator: ({ object }) => ({ syntheticId: object.id }),
|
|
513
511
|
});
|
|
514
512
|
const obj = { id: "n1", x: 1 };
|
|
515
513
|
const patches = [];
|
|
516
|
-
const { stopListening: stop } =
|
|
514
|
+
const { stopListening: stop } = watch(st, ({ patches: batch }) => patches.push(batch));
|
|
517
515
|
st.s.add(obj);
|
|
518
516
|
st.s.delete(obj);
|
|
519
517
|
await Promise.resolve();
|
|
@@ -521,94 +519,94 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
521
519
|
.flat()
|
|
522
520
|
.filter((p) => p.op === "remove")
|
|
523
521
|
.map((p) => p.path.join("."));
|
|
524
|
-
|
|
522
|
+
expect(all).toContain("s.n1");
|
|
525
523
|
stop();
|
|
526
524
|
});
|
|
527
|
-
|
|
528
|
-
const st =
|
|
525
|
+
it("does not emit patch for duplicate add", async () => {
|
|
526
|
+
const st = deepSignal({ s: new Set([1]) });
|
|
529
527
|
const patches = [];
|
|
530
|
-
const { stopListening: stop } =
|
|
528
|
+
const { stopListening: stop } = watch(st, ({ patches: batch }) => patches.push(batch));
|
|
531
529
|
st.s.add(1);
|
|
532
530
|
await Promise.resolve();
|
|
533
|
-
|
|
531
|
+
expect(patches.length).toBe(0);
|
|
534
532
|
stop();
|
|
535
533
|
});
|
|
536
|
-
|
|
537
|
-
const st =
|
|
534
|
+
it("does not emit patch deleting non-existent entry", async () => {
|
|
535
|
+
const st = deepSignal({ s: new Set([1]) });
|
|
538
536
|
const patches = [];
|
|
539
|
-
const { stopListening: stop } =
|
|
537
|
+
const { stopListening: stop } = watch(st, ({ patches: batch }) => patches.push(batch));
|
|
540
538
|
st.s.delete(2);
|
|
541
539
|
await Promise.resolve();
|
|
542
|
-
|
|
540
|
+
expect(patches.length).toBe(0);
|
|
543
541
|
stop();
|
|
544
542
|
});
|
|
545
|
-
|
|
546
|
-
const st =
|
|
543
|
+
it("addWithId primitive returns primitive and emits patch with primitive key", async () => {
|
|
544
|
+
const st = deepSignal({ s: new Set() });
|
|
547
545
|
const patches = [];
|
|
548
|
-
const { stopListening: stop } =
|
|
549
|
-
const ret =
|
|
550
|
-
|
|
546
|
+
const { stopListening: stop } = watch(st, ({ patches: batch }) => patches.push(batch));
|
|
547
|
+
const ret = addWithId(st.s, 5, "ignored");
|
|
548
|
+
expect(ret).toBe(5);
|
|
551
549
|
await Promise.resolve();
|
|
552
550
|
// For primitives, path should be just "s" and value should be in the value field
|
|
553
551
|
const paths = patches.flat().map((p) => p.path.join("."));
|
|
554
|
-
|
|
552
|
+
expect(paths).toContain("s");
|
|
555
553
|
const values = patches.flat().map((p) => p.value?.[0]);
|
|
556
|
-
|
|
554
|
+
expect(values).toContain(5);
|
|
557
555
|
stop();
|
|
558
556
|
});
|
|
559
|
-
|
|
560
|
-
const st =
|
|
557
|
+
it("setSetEntrySyntheticId applies custom id without helper", async () => {
|
|
558
|
+
const st = deepSignal({ s: new Set() });
|
|
561
559
|
const obj = { name: "x" };
|
|
562
|
-
|
|
560
|
+
setSetEntrySyntheticId(obj, "customX");
|
|
563
561
|
const patches = [];
|
|
564
|
-
const { stopListening: stop } =
|
|
562
|
+
const { stopListening: stop } = watch(st, ({ patches: batch }) => patches.push(batch));
|
|
565
563
|
st.s.add(obj);
|
|
566
564
|
await Promise.resolve();
|
|
567
565
|
const paths = patches.flat().map((p) => p.path.join("."));
|
|
568
|
-
|
|
566
|
+
expect(paths).toContain("s.customX");
|
|
569
567
|
stop();
|
|
570
568
|
});
|
|
571
|
-
|
|
572
|
-
const st =
|
|
573
|
-
const entry =
|
|
569
|
+
it("values/entries/forEach proxy nested mutation", async () => {
|
|
570
|
+
const st = deepSignal({ s: new Set() });
|
|
571
|
+
const entry = addWithId(st.s, { id: "e1", inner: { v: 1 } }, "e1");
|
|
574
572
|
const batches = [];
|
|
575
|
-
const { stopListening: stop } =
|
|
573
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
576
574
|
for (const e of st.s.values()) {
|
|
577
575
|
e.inner.v;
|
|
578
576
|
}
|
|
579
577
|
entry.inner.v = 2;
|
|
580
578
|
await Promise.resolve();
|
|
581
579
|
const vPaths = batches.flat().map((p) => p.path.join("."));
|
|
582
|
-
|
|
580
|
+
expect(vPaths.some((p) => p.endsWith("e1.inner.v"))).toBe(true);
|
|
583
581
|
stop();
|
|
584
582
|
});
|
|
585
|
-
|
|
583
|
+
it("raw reference mutation produces no deep patch while proxied does", async () => {
|
|
586
584
|
const raw = { id: "id1", data: { x: 1 } };
|
|
587
|
-
const st =
|
|
585
|
+
const st = deepSignal({ s: new Set([raw]) }, {
|
|
588
586
|
syntheticIdPropertyName: "id",
|
|
589
587
|
propGenerator: ({ object }) => ({ syntheticId: object.id }),
|
|
590
588
|
});
|
|
591
589
|
const batches = [];
|
|
592
|
-
const { stopListening: stop } =
|
|
590
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
593
591
|
raw.data.x = 2;
|
|
594
592
|
await Promise.resolve();
|
|
595
593
|
const afterRaw = batches.flat().map((p) => p.path.join("."));
|
|
596
|
-
|
|
594
|
+
expect(afterRaw.some((p) => p.endsWith("id1.data.x"))).toBe(false);
|
|
597
595
|
let proxied;
|
|
598
596
|
for (const e of st.s.values())
|
|
599
597
|
proxied = e;
|
|
600
598
|
proxied.data.x = 3;
|
|
601
599
|
await Promise.resolve();
|
|
602
600
|
const afterProxied = batches.flat().map((p) => p.path.join("."));
|
|
603
|
-
|
|
601
|
+
expect(afterProxied.some((p) => p.endsWith("id1.data.x"))).toBe(true);
|
|
604
602
|
stop();
|
|
605
603
|
});
|
|
606
|
-
|
|
607
|
-
const st =
|
|
604
|
+
it("synthetic id collision assigns unique blank node id", async () => {
|
|
605
|
+
const st = deepSignal({ s: new Set() });
|
|
608
606
|
const a1 = { id: "dup", v: 1 };
|
|
609
607
|
const a2 = { id: "dup", v: 2 };
|
|
610
608
|
const patches = [];
|
|
611
|
-
const { stopListening: stop } =
|
|
609
|
+
const { stopListening: stop } = watch(st, ({ patches: batch }) => patches.push(batch));
|
|
612
610
|
st.s.add(a1);
|
|
613
611
|
st.s.add(a2);
|
|
614
612
|
await Promise.resolve();
|
|
@@ -620,11 +618,11 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
620
618
|
p.path[0] === "s");
|
|
621
619
|
const keys = setAddPatches.map((p) => p.path.slice(-1)[0]);
|
|
622
620
|
// Both objects should have unique synthetic IDs despite id collision
|
|
623
|
-
|
|
621
|
+
expect(new Set(keys).size).toBe(2);
|
|
624
622
|
stop();
|
|
625
623
|
});
|
|
626
|
-
|
|
627
|
-
const st =
|
|
624
|
+
it("allows Array.from() and spread on Set without brand errors and tracks nested mutation", async () => {
|
|
625
|
+
const st = deepSignal({
|
|
628
626
|
s: new Set([{ id: "eIter", inner: { v: 1 } }]),
|
|
629
627
|
}, {
|
|
630
628
|
syntheticIdPropertyName: "id",
|
|
@@ -632,50 +630,50 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
632
630
|
});
|
|
633
631
|
// Regression: previously 'values method called on incompatible Proxy' was thrown here.
|
|
634
632
|
const arr = Array.from(st.s);
|
|
635
|
-
|
|
636
|
-
|
|
633
|
+
expect(arr.length).toBe(1);
|
|
634
|
+
expect(arr[0].inner.v).toBe(1);
|
|
637
635
|
const spread = [...st.s];
|
|
638
|
-
|
|
636
|
+
expect(spread[0].inner.v).toBe(1);
|
|
639
637
|
const batches = [];
|
|
640
|
-
const { stopListening: stop } =
|
|
638
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
641
639
|
spread[0].inner.v = 2; // mutate nested field of iterated (proxied) entry
|
|
642
640
|
await Promise.resolve();
|
|
643
641
|
const flat = batches.flat().map((p) => p.path.join("."));
|
|
644
|
-
|
|
642
|
+
expect(flat.some((p) => p.endsWith("eIter.inner.v"))).toBe(true);
|
|
645
643
|
stop();
|
|
646
644
|
});
|
|
647
|
-
|
|
648
|
-
const rootSet =
|
|
645
|
+
it("generates correct patches when root is a Set (primitive entries)", async () => {
|
|
646
|
+
const rootSet = deepSignal(new Set());
|
|
649
647
|
const batches = [];
|
|
650
|
-
const { stopListening: stop } =
|
|
648
|
+
const { stopListening: stop } = watch(rootSet, ({ patches }) => batches.push(patches));
|
|
651
649
|
rootSet.add(1);
|
|
652
650
|
rootSet.add("test");
|
|
653
651
|
rootSet.add(true);
|
|
654
652
|
await Promise.resolve();
|
|
655
|
-
|
|
653
|
+
expect(batches.length).toBe(1);
|
|
656
654
|
const patches = batches[0];
|
|
657
|
-
|
|
655
|
+
expect(patches.length).toBe(3);
|
|
658
656
|
// When root is a Set, path should be empty array for primitive adds
|
|
659
657
|
patches.forEach((p) => {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
658
|
+
expect(p.path).toEqual([]);
|
|
659
|
+
expect(p.op).toBe("add");
|
|
660
|
+
expect(p.type).toBe("set");
|
|
663
661
|
});
|
|
664
662
|
const values = patches.map((p) => p.value[0]);
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
663
|
+
expect(values).toContain(1);
|
|
664
|
+
expect(values).toContain("test");
|
|
665
|
+
expect(values).toContain(true);
|
|
668
666
|
stop();
|
|
669
667
|
});
|
|
670
|
-
|
|
671
|
-
const rootSet =
|
|
668
|
+
it("generates correct patches when root is a Set (object entries)", async () => {
|
|
669
|
+
const rootSet = deepSignal(new Set(), {
|
|
672
670
|
propGenerator: ({ object }) => ({
|
|
673
671
|
syntheticId: object["@id"] || `fallback-${Math.random()}`,
|
|
674
672
|
}),
|
|
675
673
|
syntheticIdPropertyName: "@id",
|
|
676
674
|
});
|
|
677
675
|
const batches = [];
|
|
678
|
-
const { stopListening: stop } =
|
|
676
|
+
const { stopListening: stop } = watch(rootSet, ({ patches }) => batches.push(patches));
|
|
679
677
|
const obj1 = { "@id": "obj1", value: 1 };
|
|
680
678
|
const obj2 = { "@id": "obj2", value: 2 };
|
|
681
679
|
rootSet.add(obj1);
|
|
@@ -683,23 +681,23 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
683
681
|
await Promise.resolve();
|
|
684
682
|
const flat = batches.flat().map((p) => p.path.join("."));
|
|
685
683
|
// When root is a Set, first element of path should be synthetic id
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
684
|
+
expect(flat).toContain("obj1");
|
|
685
|
+
expect(flat).toContain("obj1.@id");
|
|
686
|
+
expect(flat).toContain("obj1.value");
|
|
687
|
+
expect(flat).toContain("obj2");
|
|
688
|
+
expect(flat).toContain("obj2.@id");
|
|
689
|
+
expect(flat).toContain("obj2.value");
|
|
692
690
|
stop();
|
|
693
691
|
});
|
|
694
|
-
|
|
695
|
-
const rootSet =
|
|
692
|
+
it("tracks nested mutations when root is a Set", async () => {
|
|
693
|
+
const rootSet = deepSignal(new Set(), {
|
|
696
694
|
syntheticIdPropertyName: "id",
|
|
697
695
|
propGenerator: ({ object }) => ({ syntheticId: object.id }),
|
|
698
696
|
});
|
|
699
697
|
const obj = { id: "nested", data: { x: 1 } };
|
|
700
698
|
rootSet.add(obj);
|
|
701
699
|
const batches = [];
|
|
702
|
-
const { stopListening: stop } =
|
|
700
|
+
const { stopListening: stop } = watch(rootSet, ({ patches }) => batches.push(patches));
|
|
703
701
|
// Get the proxied entry
|
|
704
702
|
let proxied;
|
|
705
703
|
for (const e of rootSet.values()) {
|
|
@@ -708,23 +706,23 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
708
706
|
proxied.data.x = 2;
|
|
709
707
|
await Promise.resolve();
|
|
710
708
|
const flat = batches.flat().map((p) => p.path.join("."));
|
|
711
|
-
|
|
709
|
+
expect(flat.some((p) => p === "nested.data.x")).toBe(true);
|
|
712
710
|
stop();
|
|
713
711
|
});
|
|
714
712
|
});
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const st =
|
|
713
|
+
describe("Arrays & mixed batch", () => {
|
|
714
|
+
it("emits patches for splice/unshift/shift in single batch", async () => {
|
|
715
|
+
const st = deepSignal({ arr: [1, 2, 3] });
|
|
718
716
|
const batches = [];
|
|
719
|
-
const { stopListening: stop } =
|
|
717
|
+
const { stopListening: stop } = watch(st, ({ patches }) => {
|
|
720
718
|
batches.push(patches);
|
|
721
719
|
});
|
|
722
720
|
st.arr.splice(1, 1, 99, 100);
|
|
723
721
|
st.arr.unshift(0);
|
|
724
722
|
st.arr.shift();
|
|
725
723
|
await Promise.resolve();
|
|
726
|
-
|
|
727
|
-
|
|
724
|
+
expect(st.arr).toEqual([1, 99, 100, 3]);
|
|
725
|
+
expect(batches[0]).toEqual([
|
|
728
726
|
{ path: ["arr", "1"], op: "remove" },
|
|
729
727
|
{ path: ["arr", "1"], op: "add", value: 100 },
|
|
730
728
|
{ path: ["arr", "1"], op: "add", value: 99 },
|
|
@@ -733,15 +731,15 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
733
731
|
]);
|
|
734
732
|
stop();
|
|
735
733
|
});
|
|
736
|
-
|
|
737
|
-
const obj =
|
|
734
|
+
it("emits patches adding new array", async () => {
|
|
735
|
+
const obj = deepSignal({});
|
|
738
736
|
const batches = [];
|
|
739
|
-
const { stopListening: stop } =
|
|
737
|
+
const { stopListening: stop } = watch(obj, ({ patches }) => {
|
|
740
738
|
batches.push(patches);
|
|
741
739
|
});
|
|
742
740
|
obj.arr = [1, 2, 3];
|
|
743
741
|
await Promise.resolve();
|
|
744
|
-
|
|
742
|
+
expect(batches[0]).toEqual([
|
|
745
743
|
{ path: ["arr"], op: "add", value: [] },
|
|
746
744
|
{ path: ["arr", 0], op: "add", value: 1 },
|
|
747
745
|
{ path: ["arr", 1], op: "add", value: 2 },
|
|
@@ -749,42 +747,42 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
749
747
|
]);
|
|
750
748
|
stop();
|
|
751
749
|
});
|
|
752
|
-
|
|
753
|
-
const st =
|
|
750
|
+
it("emits remove patches when array length shrinks", async () => {
|
|
751
|
+
const st = deepSignal([1, 0, 3, -1]);
|
|
754
752
|
const batches = [];
|
|
755
|
-
const { stopListening: stop } =
|
|
753
|
+
const { stopListening: stop } = watch(st, ({ patches }) => {
|
|
756
754
|
batches.push(patches);
|
|
757
755
|
});
|
|
758
756
|
st.length = 2;
|
|
759
757
|
await Promise.resolve();
|
|
760
|
-
|
|
761
|
-
|
|
758
|
+
expect(st).toEqual([1, 0]);
|
|
759
|
+
expect(batches[0]).toEqual([
|
|
762
760
|
{ path: ["3"], op: "remove" },
|
|
763
761
|
{ path: ["2"], op: "remove" },
|
|
764
762
|
]);
|
|
765
763
|
stop();
|
|
766
764
|
});
|
|
767
|
-
|
|
768
|
-
const st =
|
|
765
|
+
it("mixed object/array/Set mutations batch together", async () => {
|
|
766
|
+
const st = deepSignal({ o: { a: 1 }, arr: [1], s: new Set() });
|
|
769
767
|
const batches = [];
|
|
770
|
-
const { stopListening: stop } =
|
|
768
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
771
769
|
st.o.a = 2;
|
|
772
770
|
st.arr.push(2);
|
|
773
|
-
|
|
771
|
+
addWithId(st.s, { id: "z", v: 1 }, "z");
|
|
774
772
|
await Promise.resolve();
|
|
775
|
-
|
|
773
|
+
expect(batches.length).toBe(1);
|
|
776
774
|
const paths = batches[0].map((p) => p.path.join("."));
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
775
|
+
expect(paths).toContain("o.a");
|
|
776
|
+
expect(paths).toContain("arr.1");
|
|
777
|
+
expect(paths.some((p) => p.startsWith("s."))).toBe(true);
|
|
780
778
|
stop();
|
|
781
779
|
});
|
|
782
|
-
|
|
780
|
+
it("mutating existing Set property emits correct patches", async () => {
|
|
783
781
|
const obj2 = {
|
|
784
782
|
"@id": "urn:test:obj2|did:ng:o:xypN3xdPs4ozIXaCFOQ5tDmq86q0szbqtCQmcqTcMTcA:v:QvO7rDIp1MCUs3Xec-yQBl0kHjMmLzoUOU4m6qy6b4EA",
|
|
785
783
|
setProperty: new Set([1, 2, 3]),
|
|
786
784
|
};
|
|
787
|
-
const st =
|
|
785
|
+
const st = deepSignal({ items: new Set([obj2]) }, {
|
|
788
786
|
syntheticIdPropertyName: "@id",
|
|
789
787
|
propGenerator: ({ object }) => ({
|
|
790
788
|
syntheticId: object["@id"],
|
|
@@ -796,7 +794,7 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
796
794
|
proxiedObj = item;
|
|
797
795
|
}
|
|
798
796
|
const batches = [];
|
|
799
|
-
const { stopListening: stop } =
|
|
797
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
800
798
|
// Modify the Set property: remove one value and add another
|
|
801
799
|
proxiedObj.setProperty.delete(3);
|
|
802
800
|
proxiedObj.setProperty.add(4);
|
|
@@ -805,25 +803,25 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
805
803
|
const patches = batches.flat();
|
|
806
804
|
// Should NOT have an object patch for the Set itself when modifying existing Set
|
|
807
805
|
const objectPatches = patches.filter((p) => p.type === "object");
|
|
808
|
-
|
|
806
|
+
expect(objectPatches.length).toBe(0);
|
|
809
807
|
// Should have remove patch for deleted value
|
|
810
808
|
const removePatches = patches.filter((p) => p.op === "remove");
|
|
811
|
-
|
|
812
|
-
|
|
809
|
+
expect(removePatches.length).toBe(1);
|
|
810
|
+
expect(removePatches[0].value).toBe(3);
|
|
813
811
|
// Should have add patches for new values (primitives in Set)
|
|
814
812
|
const addPatches = patches.filter((p) => p.op === "add");
|
|
815
|
-
|
|
813
|
+
expect(addPatches.length).toBe(2);
|
|
816
814
|
const addedValues = addPatches.map((p) => p.value[0]);
|
|
817
|
-
|
|
818
|
-
|
|
815
|
+
expect(addedValues).toContain(4);
|
|
816
|
+
expect(addedValues).toContain(5);
|
|
819
817
|
stop();
|
|
820
818
|
});
|
|
821
|
-
|
|
819
|
+
it("reassigning Set property should not emit deep patches for all elements", async () => {
|
|
822
820
|
const obj2 = {
|
|
823
821
|
"@id": "urn:test:obj2|did:ng:o:xypN3xdPs4ozIXaCFOQ5tDmq86q0szbqtCQmcqTcMTcA:v:QvO7rDIp1MCUs3Xec-yQBl0kHjMmLzoUOU4m6qy6b4EA",
|
|
824
822
|
setProperty: new Set([1, 2, 3]),
|
|
825
823
|
};
|
|
826
|
-
const st =
|
|
824
|
+
const st = deepSignal({ items: new Set([obj2]) }, {
|
|
827
825
|
syntheticIdPropertyName: "@id",
|
|
828
826
|
propGenerator: ({ object }) => ({
|
|
829
827
|
syntheticId: object["@id"],
|
|
@@ -835,7 +833,7 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
835
833
|
proxiedObj = item;
|
|
836
834
|
}
|
|
837
835
|
const batches = [];
|
|
838
|
-
const { stopListening: stop } =
|
|
836
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
839
837
|
// Reassign the Set property to a new Set with different values
|
|
840
838
|
proxiedObj.setProperty = new Set([4, 5]);
|
|
841
839
|
await Promise.resolve();
|
|
@@ -853,22 +851,22 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
853
851
|
// The correct behavior: no deep patches for the reassignment.
|
|
854
852
|
// Subsequent mutations (add/delete) on the new Set will emit proper patches.
|
|
855
853
|
const objectPatches = patches.filter((p) => p.type === "object");
|
|
856
|
-
|
|
854
|
+
expect(objectPatches.length).toBe(0);
|
|
857
855
|
// No individual element patches should be emitted for the initial elements
|
|
858
856
|
const elementPatches = patches.filter((p) => (p.path.length > 2 && p.path[p.path.length - 1] === 4) ||
|
|
859
857
|
p.path[p.path.length - 1] === 5);
|
|
860
|
-
|
|
858
|
+
expect(elementPatches.length).toBe(0);
|
|
861
859
|
// Verify that subsequent operations on the new Set DO emit patches
|
|
862
860
|
proxiedObj.setProperty.add(6);
|
|
863
861
|
await Promise.resolve();
|
|
864
862
|
const laterPatches = batches.flat();
|
|
865
863
|
const patch6 = laterPatches.find((p) => p.value?.[0] === 6 || p.value === 6);
|
|
866
|
-
|
|
864
|
+
expect(patch6).toBeDefined();
|
|
867
865
|
stop();
|
|
868
866
|
});
|
|
869
|
-
|
|
867
|
+
it("calls propGenerator for objects with nested Set structures (Set inside Set)", async () => {
|
|
870
868
|
const propGeneratorCalls = [];
|
|
871
|
-
const st =
|
|
869
|
+
const st = deepSignal({ items: new Set() }, {
|
|
872
870
|
syntheticIdPropertyName: "@id",
|
|
873
871
|
propGenerator: ({ object, path, inSet }) => {
|
|
874
872
|
propGeneratorCalls.push({ object, path, inSet });
|
|
@@ -878,7 +876,7 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
878
876
|
},
|
|
879
877
|
});
|
|
880
878
|
const batches = [];
|
|
881
|
-
const { stopListening: stop } =
|
|
879
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
882
880
|
// Create nested objects that will be in the inner Set
|
|
883
881
|
const innerObj1 = { "@id": "inner1", value: "nested1" };
|
|
884
882
|
const innerObj2 = { "@id": "inner2", value: "nested2" };
|
|
@@ -892,29 +890,29 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
892
890
|
await Promise.resolve();
|
|
893
891
|
// Verify propGenerator was called for the outer object
|
|
894
892
|
const outerCall = propGeneratorCalls.find((call) => call.object === outerObj);
|
|
895
|
-
|
|
896
|
-
|
|
893
|
+
expect(outerCall).toBeDefined();
|
|
894
|
+
expect(outerCall.object["@id"]).toBe("outer-with-nested-set");
|
|
897
895
|
// Verify propGenerator was called for inner objects
|
|
898
896
|
const inner1Call = propGeneratorCalls.find((call) => call.object === innerObj1);
|
|
899
|
-
|
|
900
|
-
|
|
897
|
+
expect(inner1Call).toBeDefined();
|
|
898
|
+
expect(inner1Call.object["@id"]).toBe("inner1");
|
|
901
899
|
const inner2Call = propGeneratorCalls.find((call) => call.object === innerObj2);
|
|
902
|
-
|
|
903
|
-
|
|
900
|
+
expect(inner2Call).toBeDefined();
|
|
901
|
+
expect(inner2Call.object["@id"]).toBe("inner2");
|
|
904
902
|
// Verify patches were emitted correctly
|
|
905
903
|
const patches = batches.flat();
|
|
906
904
|
const flat = patches.map((p) => p.path.join("."));
|
|
907
905
|
// Should have patch for the outer object
|
|
908
|
-
|
|
906
|
+
expect(flat.some((p) => p.startsWith("items.outer-with-nested-set"))).toBe(true);
|
|
909
907
|
// Should have patches for the nested Set and its objects
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
908
|
+
expect(flat.some((p) => p.includes("nestedSet"))).toBe(true);
|
|
909
|
+
expect(flat.some((p) => p.includes("inner1"))).toBe(true);
|
|
910
|
+
expect(flat.some((p) => p.includes("inner2"))).toBe(true);
|
|
913
911
|
stop();
|
|
914
912
|
});
|
|
915
|
-
|
|
913
|
+
it("calls propGenerator for deeply nested Sets (multiple levels)", async () => {
|
|
916
914
|
const propGeneratorCalls = [];
|
|
917
|
-
const st =
|
|
915
|
+
const st = deepSignal(new Set(), {
|
|
918
916
|
syntheticIdPropertyName: "@id",
|
|
919
917
|
propGenerator: ({ object, path, inSet }) => {
|
|
920
918
|
propGeneratorCalls.push({ object, path, inSet });
|
|
@@ -924,7 +922,7 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
924
922
|
},
|
|
925
923
|
});
|
|
926
924
|
const batches = [];
|
|
927
|
-
const { stopListening: stop } =
|
|
925
|
+
const { stopListening: stop } = watch(st, ({ patches }) => batches.push(patches));
|
|
928
926
|
// Create structure matching real use case:
|
|
929
927
|
// Root Set contains objects with did:ng:o: IDs
|
|
930
928
|
// Those objects have properties that are Sets of more objects
|
|
@@ -937,8 +935,8 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
937
935
|
st.add(outerObj);
|
|
938
936
|
await Promise.resolve();
|
|
939
937
|
// Verify propGenerator was called for all objects
|
|
940
|
-
|
|
941
|
-
|
|
938
|
+
expect(propGeneratorCalls.some((call) => call.object === outerObj)).toBe(true);
|
|
939
|
+
expect(propGeneratorCalls.some((call) => call.object === innerObj)).toBe(true);
|
|
942
940
|
// Clear batches for mutation test
|
|
943
941
|
batches.length = 0;
|
|
944
942
|
// Get proxied objects through iteration
|
|
@@ -957,49 +955,49 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
957
955
|
const mutationPaths = mutationPatches.map((p) => p.path.join("."));
|
|
958
956
|
// The path should match: ["rootId", "anotherObject", "innerId", "prop2"]
|
|
959
957
|
const prop2Path = mutationPaths.find((p) => p.endsWith("prop2"));
|
|
960
|
-
|
|
958
|
+
expect(prop2Path).toBeDefined();
|
|
961
959
|
// Verify the path segments
|
|
962
960
|
const pathSegments = prop2Path?.split(".");
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
961
|
+
expect(pathSegments?.length).toBe(4);
|
|
962
|
+
expect(pathSegments?.[0]).toBe("outerId-withCustomString");
|
|
963
|
+
expect(pathSegments?.[1]).toBe("anotherObject");
|
|
964
|
+
expect(pathSegments?.[2]).toBe("innerId-withCustomString");
|
|
965
|
+
expect(pathSegments?.[3]).toBe("prop2");
|
|
968
966
|
stop();
|
|
969
967
|
});
|
|
970
|
-
|
|
971
|
-
const signalObject =
|
|
968
|
+
it("Does not mutate calling .sort() and .reverse()", async () => {
|
|
969
|
+
const signalObject = deepSignal([1, 3, 2]);
|
|
972
970
|
const batches = [];
|
|
973
|
-
const { stopListening: stop } =
|
|
971
|
+
const { stopListening: stop } = watch(signalObject, ({ patches }) => batches.push(patches));
|
|
974
972
|
signalObject.sort();
|
|
975
973
|
signalObject.reverse();
|
|
976
|
-
|
|
974
|
+
expect(signalObject).toEqual([1, 3, 2]);
|
|
977
975
|
await Promise.resolve();
|
|
978
|
-
|
|
976
|
+
expect(batches).toHaveLength(0);
|
|
979
977
|
stop();
|
|
980
978
|
});
|
|
981
|
-
|
|
982
|
-
const signalObject =
|
|
979
|
+
it("Emits patches on .shift()", async () => {
|
|
980
|
+
const signalObject = deepSignal([1, 3, 2]);
|
|
983
981
|
const batches = [];
|
|
984
|
-
const { stopListening: stop } =
|
|
982
|
+
const { stopListening: stop } = watch(signalObject, ({ patches }) => batches.push(patches));
|
|
985
983
|
signalObject.shift();
|
|
986
|
-
|
|
984
|
+
expect(signalObject).toEqual([3, 2]);
|
|
987
985
|
await Promise.resolve();
|
|
988
|
-
|
|
986
|
+
expect(batches[0]).toEqual([
|
|
989
987
|
{
|
|
990
988
|
op: "remove",
|
|
991
989
|
path: ["0"],
|
|
992
990
|
},
|
|
993
991
|
]);
|
|
994
992
|
});
|
|
995
|
-
|
|
996
|
-
const signalObject =
|
|
993
|
+
it("Emits patches on .splice()", async () => {
|
|
994
|
+
const signalObject = deepSignal([1, 3, 2]);
|
|
997
995
|
const batches = [];
|
|
998
|
-
const { stopListening: stop } =
|
|
996
|
+
const { stopListening: stop } = watch(signalObject, ({ patches }) => batches.push(patches));
|
|
999
997
|
signalObject.splice(1, 1, 10);
|
|
1000
998
|
await Promise.resolve();
|
|
1001
|
-
|
|
1002
|
-
|
|
999
|
+
expect(signalObject).toEqual([1, 10, 2]);
|
|
1000
|
+
expect(batches[0]).toEqual([
|
|
1003
1001
|
{
|
|
1004
1002
|
op: "remove",
|
|
1005
1003
|
path: ["1"],
|
|
@@ -1011,14 +1009,14 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
1011
1009
|
},
|
|
1012
1010
|
]);
|
|
1013
1011
|
});
|
|
1014
|
-
|
|
1015
|
-
const signalObject =
|
|
1012
|
+
it("Does not mutate calling .unshift()", async () => {
|
|
1013
|
+
const signalObject = deepSignal([1, 3, 2]);
|
|
1016
1014
|
const batches = [];
|
|
1017
|
-
const { stopListening: stop } =
|
|
1015
|
+
const { stopListening: stop } = watch(signalObject, ({ patches }) => batches.push(patches));
|
|
1018
1016
|
signalObject.unshift(-1, 0);
|
|
1019
|
-
|
|
1017
|
+
expect(signalObject).toEqual([-1, 0, 1, 3, 2]);
|
|
1020
1018
|
await Promise.resolve();
|
|
1021
|
-
|
|
1019
|
+
expect(batches[0]).toEqual([
|
|
1022
1020
|
{
|
|
1023
1021
|
op: "add",
|
|
1024
1022
|
path: ["0"],
|
|
@@ -1032,17 +1030,17 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
1032
1030
|
]);
|
|
1033
1031
|
});
|
|
1034
1032
|
});
|
|
1035
|
-
|
|
1036
|
-
|
|
1033
|
+
describe("delete patches", () => {
|
|
1034
|
+
it("emits delete patch when removing objects with @id from Sets", async () => {
|
|
1037
1035
|
const options = {
|
|
1038
1036
|
propGenerator: ({ object }) => ({
|
|
1039
1037
|
syntheticId: object["@id"] || `fallback-${Math.random()}`,
|
|
1040
1038
|
}),
|
|
1041
1039
|
syntheticIdPropertyName: "@id",
|
|
1042
1040
|
};
|
|
1043
|
-
const state =
|
|
1041
|
+
const state = deepSignal({ s: new Set() }, options);
|
|
1044
1042
|
const patches = [];
|
|
1045
|
-
const { stopListening: stop } =
|
|
1043
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
1046
1044
|
// Add objects with @id
|
|
1047
1045
|
const obj1 = { "@id": "obj-1", value: 1 };
|
|
1048
1046
|
const obj2 = { "@id": "obj-2", value: 2 };
|
|
@@ -1064,19 +1062,19 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
1064
1062
|
.flat()
|
|
1065
1063
|
.filter((p) => p.op === "remove")
|
|
1066
1064
|
.map((p) => p.path.join("."));
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1065
|
+
expect(deletePaths).toContain("s.obj-2");
|
|
1066
|
+
expect(deletePaths).not.toContain("s.obj-1");
|
|
1067
|
+
expect(deletePaths).not.toContain("s.obj-3");
|
|
1070
1068
|
stop();
|
|
1071
1069
|
});
|
|
1072
|
-
|
|
1070
|
+
it("emits delete patches when removing objects without explicit @id from Sets", async () => {
|
|
1073
1071
|
const options = {
|
|
1074
1072
|
propGenerator: () => ({
|
|
1075
1073
|
syntheticId: `gen-${Math.random().toString(36).substr(2, 9)}`,
|
|
1076
1074
|
}),
|
|
1077
1075
|
syntheticIdPropertyName: "@id",
|
|
1078
1076
|
};
|
|
1079
|
-
const state =
|
|
1077
|
+
const state = deepSignal({ s: new Set() }, options);
|
|
1080
1078
|
// Add objects without @id - they should get generated IDs
|
|
1081
1079
|
const obj1 = { value: 1 };
|
|
1082
1080
|
const obj2 = { value: 2 };
|
|
@@ -1088,10 +1086,10 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
1088
1086
|
const proxiedObj2 = proxiedObjs[1];
|
|
1089
1087
|
const id1 = proxiedObj1["@id"];
|
|
1090
1088
|
const id2 = proxiedObj2["@id"];
|
|
1091
|
-
|
|
1092
|
-
|
|
1089
|
+
expect(id1).toBeDefined();
|
|
1090
|
+
expect(id2).toBeDefined();
|
|
1093
1091
|
const patches = [];
|
|
1094
|
-
const { stopListening: stop } =
|
|
1092
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
1095
1093
|
// Delete one object using the proxied object
|
|
1096
1094
|
state.s.delete(proxiedObj1);
|
|
1097
1095
|
await Promise.resolve();
|
|
@@ -1100,172 +1098,172 @@ const deepSignal_1 = require("../../deepSignal");
|
|
|
1100
1098
|
.flat()
|
|
1101
1099
|
.filter((p) => p.op === "remove")
|
|
1102
1100
|
.map((p) => p.path.join("."));
|
|
1103
|
-
|
|
1104
|
-
|
|
1101
|
+
expect(deletePaths).toContain(`s.${id1}`);
|
|
1102
|
+
expect(deletePaths).not.toContain(`s.${id2}`);
|
|
1105
1103
|
stop();
|
|
1106
1104
|
});
|
|
1107
1105
|
});
|
|
1108
1106
|
});
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const state =
|
|
1107
|
+
describe("watch (triggerInstantly / JIT listeners)", () => {
|
|
1108
|
+
it("triggers callback synchronously when triggerInstantly is true", () => {
|
|
1109
|
+
const state = deepSignal({ count: 0 });
|
|
1112
1110
|
const callTimes = [];
|
|
1113
1111
|
let syncMarker = 0;
|
|
1114
|
-
const { stopListening: stop } =
|
|
1112
|
+
const { stopListening: stop } = watch(state, ({ patches }) => {
|
|
1115
1113
|
callTimes.push(syncMarker);
|
|
1116
1114
|
}, { triggerInstantly: true });
|
|
1117
1115
|
syncMarker = 1;
|
|
1118
1116
|
state.count = 1;
|
|
1119
1117
|
syncMarker = 2;
|
|
1120
1118
|
// Callback should have been called synchronously (at syncMarker = 1)
|
|
1121
|
-
|
|
1119
|
+
expect(callTimes).toEqual([1]);
|
|
1122
1120
|
stop();
|
|
1123
1121
|
});
|
|
1124
|
-
|
|
1125
|
-
const state =
|
|
1122
|
+
it("triggers callback asynchronously when triggerInstantly is false", async () => {
|
|
1123
|
+
const state = deepSignal({ count: 0 });
|
|
1126
1124
|
const callTimes = [];
|
|
1127
1125
|
let syncMarker = 0;
|
|
1128
|
-
const { stopListening: stop } =
|
|
1126
|
+
const { stopListening: stop } = watch(state, ({ patches }) => {
|
|
1129
1127
|
callTimes.push(syncMarker);
|
|
1130
1128
|
});
|
|
1131
1129
|
syncMarker = 1;
|
|
1132
1130
|
state.count = 1;
|
|
1133
1131
|
syncMarker = 2;
|
|
1134
1132
|
// Callback should NOT have been called yet (batched in microtask)
|
|
1135
|
-
|
|
1133
|
+
expect(callTimes).toEqual([]);
|
|
1136
1134
|
await Promise.resolve();
|
|
1137
1135
|
// Now callback should have been called (at syncMarker = 2)
|
|
1138
|
-
|
|
1136
|
+
expect(callTimes).toEqual([2]);
|
|
1139
1137
|
stop();
|
|
1140
1138
|
});
|
|
1141
|
-
|
|
1142
|
-
const state =
|
|
1139
|
+
it("JIT listener receives patches without version", () => {
|
|
1140
|
+
const state = deepSignal({ value: "initial" });
|
|
1143
1141
|
let receivedVersion = 999; // sentinel value
|
|
1144
|
-
const { stopListening: stop } =
|
|
1142
|
+
const { stopListening: stop } = watch(state, ({ patches, version }) => {
|
|
1145
1143
|
receivedVersion = version;
|
|
1146
1144
|
}, { triggerInstantly: true });
|
|
1147
1145
|
state.value = "changed";
|
|
1148
1146
|
// JIT listeners should not receive a version (it's undefined)
|
|
1149
|
-
|
|
1147
|
+
expect(receivedVersion).toBeUndefined();
|
|
1150
1148
|
stop();
|
|
1151
1149
|
});
|
|
1152
|
-
|
|
1153
|
-
const state =
|
|
1150
|
+
it("batched listener receives patches with version", async () => {
|
|
1151
|
+
const state = deepSignal({ value: "initial" });
|
|
1154
1152
|
let receivedVersion;
|
|
1155
|
-
const { stopListening: stop } =
|
|
1153
|
+
const { stopListening: stop } = watch(state, ({ patches, version }) => {
|
|
1156
1154
|
receivedVersion = version;
|
|
1157
1155
|
});
|
|
1158
1156
|
state.value = "changed";
|
|
1159
1157
|
await Promise.resolve();
|
|
1160
1158
|
// Batched listeners should receive a version number
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1159
|
+
expect(receivedVersion).toBeDefined();
|
|
1160
|
+
expect(typeof receivedVersion).toBe("number");
|
|
1161
|
+
expect(receivedVersion).toBeGreaterThan(0);
|
|
1164
1162
|
stop();
|
|
1165
1163
|
});
|
|
1166
|
-
|
|
1167
|
-
const state =
|
|
1164
|
+
it("JIT listener is called for each mutation, not batched", () => {
|
|
1165
|
+
const state = deepSignal({ a: 1, b: 2 });
|
|
1168
1166
|
const patchCalls = [];
|
|
1169
|
-
const { stopListening: stop } =
|
|
1167
|
+
const { stopListening: stop } = watch(state, ({ patches }) => {
|
|
1170
1168
|
patchCalls.push([...patches]);
|
|
1171
1169
|
}, { triggerInstantly: true });
|
|
1172
1170
|
state.a = 10;
|
|
1173
1171
|
state.b = 20;
|
|
1174
1172
|
// Should be called twice (once per mutation), not once with batched patches
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1173
|
+
expect(patchCalls).toHaveLength(2);
|
|
1174
|
+
expect(patchCalls[0]).toEqual([{ op: "add", path: ["a"], value: 10 }]);
|
|
1175
|
+
expect(patchCalls[1]).toEqual([{ op: "add", path: ["b"], value: 20 }]);
|
|
1178
1176
|
stop();
|
|
1179
1177
|
});
|
|
1180
|
-
|
|
1181
|
-
const state =
|
|
1178
|
+
it("batched listener receives all mutations in one batch", async () => {
|
|
1179
|
+
const state = deepSignal({ a: 1, b: 2 });
|
|
1182
1180
|
const patchCalls = [];
|
|
1183
|
-
const { stopListening: stop } =
|
|
1181
|
+
const { stopListening: stop } = watch(state, ({ patches }) => {
|
|
1184
1182
|
patchCalls.push([...patches]);
|
|
1185
1183
|
});
|
|
1186
1184
|
state.a = 10;
|
|
1187
1185
|
state.b = 20;
|
|
1188
1186
|
await Promise.resolve();
|
|
1189
1187
|
// Should be called once with all patches batched
|
|
1190
|
-
|
|
1191
|
-
|
|
1188
|
+
expect(patchCalls).toHaveLength(1);
|
|
1189
|
+
expect(patchCalls[0]).toEqual([
|
|
1192
1190
|
{ op: "add", path: ["a"], value: 10 },
|
|
1193
1191
|
{ op: "add", path: ["b"], value: 20 },
|
|
1194
1192
|
]);
|
|
1195
1193
|
stop();
|
|
1196
1194
|
});
|
|
1197
|
-
|
|
1198
|
-
const state =
|
|
1195
|
+
it("both JIT and batched listeners can coexist on the same root", async () => {
|
|
1196
|
+
const state = deepSignal({ count: 0 });
|
|
1199
1197
|
const jitPatches = [];
|
|
1200
1198
|
const batchedPatches = [];
|
|
1201
|
-
const { stopListening: stopJit } =
|
|
1199
|
+
const { stopListening: stopJit } = watch(state, ({ patches }) => {
|
|
1202
1200
|
jitPatches.push([...patches]);
|
|
1203
1201
|
}, { triggerInstantly: true });
|
|
1204
|
-
const { stopListening: stopBatched } =
|
|
1202
|
+
const { stopListening: stopBatched } = watch(state, ({ patches }) => {
|
|
1205
1203
|
batchedPatches.push([...patches]);
|
|
1206
1204
|
});
|
|
1207
1205
|
state.count = 1;
|
|
1208
1206
|
state.count = 2;
|
|
1209
1207
|
// JIT should have received 2 calls immediately
|
|
1210
|
-
|
|
1208
|
+
expect(jitPatches).toHaveLength(2);
|
|
1211
1209
|
// Batched should have received 0 calls so far
|
|
1212
|
-
|
|
1210
|
+
expect(batchedPatches).toHaveLength(0);
|
|
1213
1211
|
await Promise.resolve();
|
|
1214
1212
|
// Now batched should have received 1 call with all patches
|
|
1215
|
-
|
|
1216
|
-
|
|
1213
|
+
expect(batchedPatches).toHaveLength(1);
|
|
1214
|
+
expect(batchedPatches[0]).toEqual([
|
|
1217
1215
|
{ op: "add", path: ["count"], value: 1 },
|
|
1218
1216
|
{ op: "add", path: ["count"], value: 2 },
|
|
1219
1217
|
]);
|
|
1220
1218
|
stopJit();
|
|
1221
1219
|
stopBatched();
|
|
1222
1220
|
});
|
|
1223
|
-
|
|
1224
|
-
const state =
|
|
1221
|
+
it("JIT listener works with nested object mutations", () => {
|
|
1222
|
+
const state = deepSignal({ user: { name: "Alice", age: 30 } });
|
|
1225
1223
|
const patches = [];
|
|
1226
|
-
const { stopListening: stop } =
|
|
1224
|
+
const { stopListening: stop } = watch(state, ({ patches: p }) => {
|
|
1227
1225
|
patches.push([...p]);
|
|
1228
1226
|
}, { triggerInstantly: true });
|
|
1229
1227
|
state.user.name = "Bob";
|
|
1230
1228
|
state.user.age = 31;
|
|
1231
|
-
|
|
1232
|
-
|
|
1229
|
+
expect(patches).toHaveLength(2);
|
|
1230
|
+
expect(patches[0]).toEqual([
|
|
1233
1231
|
{ op: "add", path: ["user", "name"], value: "Bob" },
|
|
1234
1232
|
]);
|
|
1235
|
-
|
|
1233
|
+
expect(patches[1]).toEqual([
|
|
1236
1234
|
{ op: "add", path: ["user", "age"], value: 31 },
|
|
1237
1235
|
]);
|
|
1238
1236
|
stop();
|
|
1239
1237
|
});
|
|
1240
|
-
|
|
1241
|
-
const state =
|
|
1238
|
+
it("JIT listener works with Set mutations", () => {
|
|
1239
|
+
const state = deepSignal({ items: new Set([1, 2]) });
|
|
1242
1240
|
const patches = [];
|
|
1243
|
-
const { stopListening: stop } =
|
|
1241
|
+
const { stopListening: stop } = watch(state, ({ patches: p }) => {
|
|
1244
1242
|
patches.push([...p]);
|
|
1245
1243
|
}, { triggerInstantly: true });
|
|
1246
1244
|
state.items.add(3);
|
|
1247
1245
|
state.items.delete(1);
|
|
1248
|
-
|
|
1249
|
-
|
|
1246
|
+
expect(patches).toHaveLength(2);
|
|
1247
|
+
expect(patches[0]).toEqual([
|
|
1250
1248
|
{ op: "add", path: ["items"], type: "set", value: [3] },
|
|
1251
1249
|
]);
|
|
1252
|
-
|
|
1250
|
+
expect(patches[1]).toEqual([
|
|
1253
1251
|
{ op: "remove", path: ["items"], type: "set", value: 1 },
|
|
1254
1252
|
]);
|
|
1255
1253
|
stop();
|
|
1256
1254
|
});
|
|
1257
|
-
|
|
1258
|
-
const state =
|
|
1255
|
+
it("stopping JIT listener prevents further callbacks", () => {
|
|
1256
|
+
const state = deepSignal({ value: 0 });
|
|
1259
1257
|
let callCount = 0;
|
|
1260
|
-
const { stopListening: stop } =
|
|
1258
|
+
const { stopListening: stop } = watch(state, () => {
|
|
1261
1259
|
callCount++;
|
|
1262
1260
|
}, { triggerInstantly: true });
|
|
1263
1261
|
state.value = 1;
|
|
1264
|
-
|
|
1262
|
+
expect(callCount).toBe(1);
|
|
1265
1263
|
stop();
|
|
1266
1264
|
state.value = 2;
|
|
1267
1265
|
state.value = 3;
|
|
1268
1266
|
// Should still be 1, no more calls after stop
|
|
1269
|
-
|
|
1267
|
+
expect(callCount).toBe(1);
|
|
1270
1268
|
});
|
|
1271
1269
|
});
|