|
|
|
|
@ -3,14 +3,14 @@ import { |
|
|
|
|
deepSignal, |
|
|
|
|
setSetEntrySyntheticId, |
|
|
|
|
addWithId, |
|
|
|
|
DeepPatchInternal, |
|
|
|
|
DeepPatch, |
|
|
|
|
} from "../deepSignal"; |
|
|
|
|
import { watch, observe } from "../watch"; |
|
|
|
|
|
|
|
|
|
describe("watch (patch mode)", () => { |
|
|
|
|
it("emits set patches with correct paths and batching", async () => { |
|
|
|
|
const state = deepSignal({ a: { b: 1 }, arr: [1, { x: 2 }] }); |
|
|
|
|
const received: DeepPatchInternal[][] = []; |
|
|
|
|
const received: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches }) => { |
|
|
|
|
received.push(patches); |
|
|
|
|
}); |
|
|
|
|
@ -34,7 +34,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
a: { b: 1 }, |
|
|
|
|
c: 2, |
|
|
|
|
}); |
|
|
|
|
const out: DeepPatchInternal[][] = []; |
|
|
|
|
const out: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches }) => |
|
|
|
|
out.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -52,8 +52,8 @@ describe("watch (patch mode)", () => { |
|
|
|
|
|
|
|
|
|
it("observe patch mode mirrors watch patch mode", async () => { |
|
|
|
|
const state = deepSignal({ a: 1 }); |
|
|
|
|
const wp: DeepPatchInternal[][] = []; |
|
|
|
|
const ob: DeepPatchInternal[][] = []; |
|
|
|
|
const wp: DeepPatch[][] = []; |
|
|
|
|
const ob: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop1 } = watch(state, ({ patches }) => |
|
|
|
|
wp.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -72,7 +72,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
it("filters out patches from other roots", async () => { |
|
|
|
|
const a = deepSignal({ x: 1 }); |
|
|
|
|
const b = deepSignal({ y: 2 }); |
|
|
|
|
const out: DeepPatchInternal[][] = []; |
|
|
|
|
const out: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(a, ({ patches }) => |
|
|
|
|
out.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -86,7 +86,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
|
|
|
|
|
it("emits patches for Set structural mutations (add/delete)", async () => { |
|
|
|
|
const state = deepSignal<{ s: Set<number> }>({ s: new Set([1, 2]) }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -110,7 +110,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
|
|
|
|
|
it("emits patches for nested objects added after initialization", async () => { |
|
|
|
|
const state = deepSignal<{ root: any }>({ root: {} }); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -124,7 +124,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
|
|
|
|
|
it("emits patches for deeply nested arrays and objects", async () => { |
|
|
|
|
const state = deepSignal<{ data: any }>({ data: null }); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -161,7 +161,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
|
|
|
|
|
it("emits patches for Set with nested objects added as one operation", async () => { |
|
|
|
|
const state = deepSignal<{ container: any }>({ container: {} }); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -188,7 +188,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
const innerA = new Set<any>([{ id: "node1", x: 1 }]); |
|
|
|
|
const s = new Set<any>([innerA]); |
|
|
|
|
const state = deepSignal<{ graph: Set<any> }>({ graph: s }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -204,7 +204,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
it("tracks deep nested object mutation inside a Set entry after iteration", async () => { |
|
|
|
|
const rawEntry = { id: "n1", data: { val: 1 } }; |
|
|
|
|
const st = deepSignal({ bag: new Set<any>([rawEntry]) }); |
|
|
|
|
const collected: DeepPatchInternal[][] = []; |
|
|
|
|
const collected: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
collected.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -215,9 +215,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
} |
|
|
|
|
proxied.data.val = 2; |
|
|
|
|
await Promise.resolve(); |
|
|
|
|
const flat = collected |
|
|
|
|
.flat() |
|
|
|
|
.map((p: DeepPatchInternal) => p.path.join(".")); |
|
|
|
|
const flat = collected.flat().map((p: DeepPatch) => p.path.join(".")); |
|
|
|
|
expect(flat.some((p: string) => p.endsWith("n1.data.val"))).toBe(true); |
|
|
|
|
stop(); |
|
|
|
|
}); |
|
|
|
|
@ -225,15 +223,13 @@ describe("watch (patch mode)", () => { |
|
|
|
|
it("allows custom synthetic id for Set entry", async () => { |
|
|
|
|
const node = { name: "x" }; |
|
|
|
|
const state = deepSignal({ s: new Set<any>() }); |
|
|
|
|
const collected2: DeepPatchInternal[][] = []; |
|
|
|
|
const collected2: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(state, ({ patches }) => |
|
|
|
|
collected2.push(patches) |
|
|
|
|
); |
|
|
|
|
addWithId(state.s as any, node, "custom123"); |
|
|
|
|
await Promise.resolve(); |
|
|
|
|
const flat = collected2 |
|
|
|
|
.flat() |
|
|
|
|
.map((p: DeepPatchInternal) => p.path.join(".")); |
|
|
|
|
const flat = collected2.flat().map((p: DeepPatch) => p.path.join(".")); |
|
|
|
|
expect(flat.some((p: string) => p === "s.custom123")).toBe(true); |
|
|
|
|
stop(); |
|
|
|
|
}); |
|
|
|
|
@ -241,7 +237,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
describe("Set", () => { |
|
|
|
|
it("emits patches for primitive adds", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>() }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -270,7 +266,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("emits patches for primitive deletes", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>([true, 2, "3"]) }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -297,7 +293,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("does not emit patches for non-existent primitives", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>([1, 2]) }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -310,7 +306,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("does not emit patches for already added primitive", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>([1, "test", true]) }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -326,7 +322,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>() }); |
|
|
|
|
addWithId(st.s as any, { id: "a", x: 1 }, "a"); |
|
|
|
|
addWithId(st.s as any, { id: "b", x: 2 }, "b"); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -344,7 +340,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
it("emits delete patch for object entry", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>() }); |
|
|
|
|
const obj = { id: "n1", x: 1 }; |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -360,7 +356,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("does not emit patch for duplicate add", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<number>([1]) }); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -371,7 +367,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("does not emit patch deleting non-existent entry", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<number>([1]) }); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -382,7 +378,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("addWithId primitive returns primitive and emits patch with primitive key", async () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>() }); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -400,7 +396,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>() }); |
|
|
|
|
const obj = { name: "x" }; |
|
|
|
|
setSetEntrySyntheticId(obj, "customX"); |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -417,7 +413,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
{ id: "e1", inner: { v: 1 } }, |
|
|
|
|
"e1" |
|
|
|
|
); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -433,7 +429,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
it("raw reference mutation produces no deep patch while proxied does", async () => { |
|
|
|
|
const raw = { id: "id1", data: { x: 1 } }; |
|
|
|
|
const st = deepSignal({ s: new Set<any>([raw]) }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -455,7 +451,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
const st = deepSignal({ s: new Set<any>() }); |
|
|
|
|
const a1 = { id: "dup", v: 1 }; |
|
|
|
|
const a2 = { id: "dup", v: 2 }; |
|
|
|
|
const patches: DeepPatchInternal[][] = []; |
|
|
|
|
const patches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches: batch }) => |
|
|
|
|
patches.push(batch) |
|
|
|
|
); |
|
|
|
|
@ -487,7 +483,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
expect(arr[0].inner.v).toBe(1); |
|
|
|
|
const spread = [...st.s]; |
|
|
|
|
expect(spread[0].inner.v).toBe(1); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -497,12 +493,90 @@ describe("watch (patch mode)", () => { |
|
|
|
|
expect(flat.some((p) => p.endsWith("eIter.inner.v"))).toBe(true); |
|
|
|
|
stop(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
it("generates correct patches when root is a Set (primitive entries)", async () => { |
|
|
|
|
const rootSet = deepSignal(new Set<any>()); |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(rootSet, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
rootSet.add(1); |
|
|
|
|
rootSet.add("test"); |
|
|
|
|
rootSet.add(true); |
|
|
|
|
await Promise.resolve(); |
|
|
|
|
|
|
|
|
|
expect(batches.length).toBe(1); |
|
|
|
|
const patches = batches[0]; |
|
|
|
|
expect(patches.length).toBe(3); |
|
|
|
|
|
|
|
|
|
// When root is a Set, path should be empty array for primitive adds
|
|
|
|
|
patches.forEach((p) => { |
|
|
|
|
expect(p.path).toEqual([]); |
|
|
|
|
expect(p.op).toBe("add"); |
|
|
|
|
expect((p as any).type).toBe("set"); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const values = patches.map((p: any) => p.value[0]); |
|
|
|
|
expect(values).toContain(1); |
|
|
|
|
expect(values).toContain("test"); |
|
|
|
|
expect(values).toContain(true); |
|
|
|
|
stop(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
it("generates correct patches when root is a Set (object entries)", async () => { |
|
|
|
|
const rootSet = deepSignal(new Set<any>()); |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(rootSet, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const obj1 = { "@id": "obj1", value: 1 }; |
|
|
|
|
const obj2 = { "@id": "obj2", value: 2 }; |
|
|
|
|
rootSet.add(obj1); |
|
|
|
|
rootSet.add(obj2); |
|
|
|
|
await Promise.resolve(); |
|
|
|
|
|
|
|
|
|
const flat = batches.flat().map((p) => p.path.join(".")); |
|
|
|
|
|
|
|
|
|
// When root is a Set, first element of path should be synthetic id
|
|
|
|
|
expect(flat).toContain("obj1"); |
|
|
|
|
expect(flat).toContain("obj1.@id"); |
|
|
|
|
expect(flat).toContain("obj1.value"); |
|
|
|
|
expect(flat).toContain("obj2"); |
|
|
|
|
expect(flat).toContain("obj2.@id"); |
|
|
|
|
expect(flat).toContain("obj2.value"); |
|
|
|
|
stop(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
it("tracks nested mutations when root is a Set", async () => { |
|
|
|
|
const rootSet = deepSignal(new Set<any>()); |
|
|
|
|
const obj = { id: "nested", data: { x: 1 } }; |
|
|
|
|
rootSet.add(obj); |
|
|
|
|
|
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(rootSet, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Get the proxied entry
|
|
|
|
|
let proxied: any; |
|
|
|
|
for (const e of rootSet.values()) { |
|
|
|
|
proxied = e; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
proxied.data.x = 2; |
|
|
|
|
await Promise.resolve(); |
|
|
|
|
|
|
|
|
|
const flat = batches.flat().map((p) => p.path.join(".")); |
|
|
|
|
expect(flat.some((p) => p === "nested.data.x")).toBe(true); |
|
|
|
|
stop(); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
describe("Arrays & mixed batch", () => { |
|
|
|
|
it("emits patches for splice/unshift/shift in single batch", async () => { |
|
|
|
|
const st = deepSignal({ arr: [1, 2, 3] }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
@ -516,7 +590,7 @@ describe("watch (patch mode)", () => { |
|
|
|
|
}); |
|
|
|
|
it("mixed object/array/Set mutations batch together", async () => { |
|
|
|
|
const st = deepSignal({ o: { a: 1 }, arr: [1], s: new Set<any>() }); |
|
|
|
|
const batches: DeepPatchInternal[][] = []; |
|
|
|
|
const batches: DeepPatch[][] = []; |
|
|
|
|
const { stopListening: stop } = watch(st, ({ patches }) => |
|
|
|
|
batches.push(patches) |
|
|
|
|
); |
|
|
|
|
|