|
|
@ -52,74 +52,169 @@ describe("applyDiff - set operations (primitives)", () => { |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe("applyDiff - set operations (object sets)", () => { |
|
|
|
describe("applyDiff - multi-valued objects (Set-based)", () => { |
|
|
|
test("add object entries to new object-set", () => { |
|
|
|
test("create multi-object container (Set) without @id", () => { |
|
|
|
const state: any = {}; |
|
|
|
const state: any = { "urn:person1": {} }; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
op: "add", |
|
|
|
op: "add", |
|
|
|
valType: "set", |
|
|
|
valType: "object", |
|
|
|
path: p("users"), |
|
|
|
path: p("urn:person1", "children"), |
|
|
|
value: { u1: { id: "u1", n: 1 }, u2: { id: "u2", n: 2 } }, |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
applyDiff(state, diff); |
|
|
|
expect(state.users.u1).toEqual({ id: "u1", n: 1 }); |
|
|
|
expect(state["urn:person1"].children).toBeInstanceOf(Set); |
|
|
|
expect(state.users.u2).toEqual({ id: "u2", n: 2 }); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
test("merge object entries into existing object-set", () => { |
|
|
|
|
|
|
|
const state: any = { users: { u1: { id: "u1", n: 1 } } }; |
|
|
|
test("add object to Set with @id", () => { |
|
|
|
|
|
|
|
const state: any = { "urn:person1": { children: new Set() } }; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
// First patch creates the object in the Set
|
|
|
|
{ |
|
|
|
{ |
|
|
|
op: "add", |
|
|
|
op: "add", |
|
|
|
valType: "set", |
|
|
|
valType: "object", |
|
|
|
path: p("users"), |
|
|
|
path: p("urn:person1", "children", "urn:child1"), |
|
|
|
value: { u2: { id: "u2", n: 2 } }, |
|
|
|
}, |
|
|
|
|
|
|
|
// Second patch adds the @id property
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child1", "@id"), |
|
|
|
|
|
|
|
value: "urn:child1", |
|
|
|
}, |
|
|
|
}, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
applyDiff(state, diff); |
|
|
|
expect(Object.keys(state.users).sort()).toEqual(["u1", "u2"]); |
|
|
|
const children = state["urn:person1"].children; |
|
|
|
|
|
|
|
expect(children).toBeInstanceOf(Set); |
|
|
|
|
|
|
|
expect(children.size).toBe(1); |
|
|
|
|
|
|
|
const child = [...children][0]; |
|
|
|
|
|
|
|
expect(child["@id"]).toBe("urn:child1"); |
|
|
|
}); |
|
|
|
}); |
|
|
|
test("remove object entries from object-set", () => { |
|
|
|
|
|
|
|
const state: any = { users: { u1: {}, u2: {}, u3: {} } }; |
|
|
|
test("add properties to object in Set", () => { |
|
|
|
|
|
|
|
const obj = { "@id": "urn:child1" }; |
|
|
|
|
|
|
|
const state: any = { "urn:person1": { children: new Set([obj]) } }; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
op: "remove", |
|
|
|
op: "add", |
|
|
|
valType: "set", |
|
|
|
path: p("urn:person1", "children", "urn:child1", "name"), |
|
|
|
path: p("users"), |
|
|
|
value: "Alice", |
|
|
|
value: ["u1", "u3"], |
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child1", "age"), |
|
|
|
|
|
|
|
value: 10, |
|
|
|
}, |
|
|
|
}, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
applyDiff(state, diff); |
|
|
|
expect(Object.keys(state.users)).toEqual(["u2"]); |
|
|
|
const child = [...state["urn:person1"].children][0]; |
|
|
|
|
|
|
|
expect(child.name).toBe("Alice"); |
|
|
|
|
|
|
|
expect(child.age).toBe(10); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test("remove object from Set by @id", () => { |
|
|
|
|
|
|
|
const obj1 = { "@id": "urn:child1", name: "Alice" }; |
|
|
|
|
|
|
|
const obj2 = { "@id": "urn:child2", name: "Bob" }; |
|
|
|
|
|
|
|
const state: any = { |
|
|
|
|
|
|
|
"urn:person1": { children: new Set([obj1, obj2]) }, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
{ op: "remove", path: p("urn:person1", "children", "urn:child1") }, |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
applyDiff(state, diff); |
|
|
|
|
|
|
|
const children = state["urn:person1"].children; |
|
|
|
|
|
|
|
expect(children.size).toBe(1); |
|
|
|
|
|
|
|
const remaining = [...children][0]; |
|
|
|
|
|
|
|
expect(remaining["@id"]).toBe("urn:child2"); |
|
|
|
}); |
|
|
|
}); |
|
|
|
test("adding primitives to existing object-set replaces with Set", () => { |
|
|
|
|
|
|
|
const state: any = { mixed: { a: {}, b: {} } }; |
|
|
|
test("create nested Set (multi-valued property within object in Set)", () => { |
|
|
|
|
|
|
|
const parent: any = { "@id": "urn:parent1" }; |
|
|
|
|
|
|
|
const state: any = { root: { parents: new Set([parent]) } }; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
{ op: "add", valType: "set", path: p("mixed"), value: [1, 2] }, |
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
path: p("root", "parents", "urn:parent1", "children"), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
path: p( |
|
|
|
|
|
|
|
"root", |
|
|
|
|
|
|
|
"parents", |
|
|
|
|
|
|
|
"urn:parent1", |
|
|
|
|
|
|
|
"children", |
|
|
|
|
|
|
|
"urn:child1" |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p( |
|
|
|
|
|
|
|
"root", |
|
|
|
|
|
|
|
"parents", |
|
|
|
|
|
|
|
"urn:parent1", |
|
|
|
|
|
|
|
"children", |
|
|
|
|
|
|
|
"urn:child1", |
|
|
|
|
|
|
|
"@id" |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
value: "urn:child1", |
|
|
|
|
|
|
|
}, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
applyDiff(state, diff); |
|
|
|
expect(state.mixed).toBeInstanceOf(Set); |
|
|
|
const nestedChildren = parent.children; |
|
|
|
expect([...state.mixed]).toEqual([1, 2]); |
|
|
|
expect(nestedChildren).toBeInstanceOf(Set); |
|
|
|
|
|
|
|
expect(nestedChildren.size).toBe(1); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe("applyDiff - object & literal operations", () => { |
|
|
|
describe("applyDiff - object & literal operations", () => { |
|
|
|
test("add object (create empty object)", () => { |
|
|
|
test("create single object (with @id)", () => { |
|
|
|
|
|
|
|
const state: any = { "urn:person1": {} }; |
|
|
|
|
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
{ op: "add", path: p("urn:person1", "address"), valType: "object" }, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "address", "@id"), |
|
|
|
|
|
|
|
value: "urn:addr1", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
applyDiff(state, diff); |
|
|
|
|
|
|
|
expect(state["urn:person1"].address).toEqual({ "@id": "urn:addr1" }); |
|
|
|
|
|
|
|
expect(state["urn:person1"].address).not.toBeInstanceOf(Set); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test("create multi-object container (without @id) -> Set", () => { |
|
|
|
|
|
|
|
const state: any = { "urn:person1": {} }; |
|
|
|
|
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "addresses"), |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
applyDiff(state, diff); |
|
|
|
|
|
|
|
expect(state["urn:person1"].addresses).toBeInstanceOf(Set); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test("add object (create empty object with @id)", () => { |
|
|
|
const state: any = {}; |
|
|
|
const state: any = {}; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
{ op: "add", path: p("address"), valType: "object" }, |
|
|
|
{ op: "add", path: p("address"), valType: "object" }, |
|
|
|
|
|
|
|
{ op: "add", path: p("address", "@id"), value: "urn:addr1" }, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
applyDiff(state, diff); |
|
|
|
expect(state.address).toEqual({}); |
|
|
|
expect(state.address).toEqual({ "@id": "urn:addr1" }); |
|
|
|
|
|
|
|
expect(state.address).not.toBeInstanceOf(Set); |
|
|
|
}); |
|
|
|
}); |
|
|
|
test("add nested object path with ensurePathExists", () => { |
|
|
|
test("add nested object path with ensurePathExists and @id", () => { |
|
|
|
const state: any = {}; |
|
|
|
const state: any = {}; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
{ op: "add", path: p("a", "b", "c"), valType: "object" }, |
|
|
|
{ op: "add", path: p("a", "b", "c"), valType: "object" }, |
|
|
|
|
|
|
|
{ op: "add", path: p("a", "b", "c", "@id"), value: "urn:c1" }, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff, true); |
|
|
|
applyDiff(state, diff, true); |
|
|
|
expect(state.a.b.c).toEqual({}); |
|
|
|
expect(state.a.b.c).toEqual({ "@id": "urn:c1" }); |
|
|
|
|
|
|
|
expect(state.a.b.c).not.toBeInstanceOf(Set); |
|
|
|
}); |
|
|
|
}); |
|
|
|
test("add primitive value", () => { |
|
|
|
test("add primitive value", () => { |
|
|
|
const state: any = { address: {} }; |
|
|
|
const state: any = { address: {} }; |
|
|
@ -156,23 +251,46 @@ describe("applyDiff - object & literal operations", () => { |
|
|
|
describe("applyDiff - multiple mixed patches in a single diff", () => { |
|
|
|
describe("applyDiff - multiple mixed patches in a single diff", () => { |
|
|
|
test("sequence of mixed set/object/literal add & remove", () => { |
|
|
|
test("sequence of mixed set/object/literal add & remove", () => { |
|
|
|
const state: any = { |
|
|
|
const state: any = { |
|
|
|
users: { u1: { id: "u1" } }, |
|
|
|
"urn:person1": {}, |
|
|
|
tags: new Set(["old"]), |
|
|
|
tags: new Set(["old"]), |
|
|
|
}; |
|
|
|
}; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
// Create multi-object Set
|
|
|
|
{ |
|
|
|
{ |
|
|
|
op: "add", |
|
|
|
op: "add", |
|
|
|
valType: "set", |
|
|
|
valType: "object", |
|
|
|
path: p("users"), |
|
|
|
path: p("urn:person1", "addresses"), |
|
|
|
value: { u2: { id: "u2" } }, |
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
path: p("urn:person1", "addresses", "urn:addr1"), |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "addresses", "urn:addr1", "@id"), |
|
|
|
|
|
|
|
value: "urn:addr1", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "addresses", "urn:addr1", "street"), |
|
|
|
|
|
|
|
value: "Main St", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
// Create single object
|
|
|
|
{ op: "add", path: p("profile"), valType: "object" }, |
|
|
|
{ op: "add", path: p("profile"), valType: "object" }, |
|
|
|
|
|
|
|
{ op: "add", path: p("profile", "@id"), value: "urn:profile1" }, |
|
|
|
{ op: "add", path: p("profile", "name"), value: "Alice" }, |
|
|
|
{ op: "add", path: p("profile", "name"), value: "Alice" }, |
|
|
|
|
|
|
|
// Primitive set operations
|
|
|
|
{ op: "add", valType: "set", path: p("tags"), value: ["new"] }, |
|
|
|
{ op: "add", valType: "set", path: p("tags"), value: ["new"] }, |
|
|
|
{ op: "remove", valType: "set", path: p("tags"), value: "old" }, |
|
|
|
{ op: "remove", valType: "set", path: p("tags"), value: "old" }, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
applyDiff(state, diff); // Enable ensurePathExists for nested object creation
|
|
|
|
expect(Object.keys(state.users).sort()).toEqual(["u1", "u2"]); |
|
|
|
expect(state["urn:person1"].addresses).toBeInstanceOf(Set); |
|
|
|
|
|
|
|
expect(state["urn:person1"].addresses.size).toBe(1); |
|
|
|
|
|
|
|
const addr = [...state["urn:person1"].addresses][0]; |
|
|
|
|
|
|
|
expect(addr["@id"]).toBe("urn:addr1"); |
|
|
|
|
|
|
|
expect(addr.street).toBe("Main St"); |
|
|
|
|
|
|
|
expect(state.profile["@id"]).toBe("urn:profile1"); |
|
|
|
expect(state.profile.name).toBe("Alice"); |
|
|
|
expect(state.profile.name).toBe("Alice"); |
|
|
|
expect([...state.tags]).toEqual(["new"]); |
|
|
|
expect([...state.tags]).toEqual(["new"]); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -180,8 +298,11 @@ describe("applyDiff - multiple mixed patches in a single diff", () => { |
|
|
|
test("complex nested path creation and mutations with ensurePathExists", () => { |
|
|
|
test("complex nested path creation and mutations with ensurePathExists", () => { |
|
|
|
const state: any = {}; |
|
|
|
const state: any = {}; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
// Create b as a single object (with @id)
|
|
|
|
{ op: "add", path: p("a", "b"), valType: "object" }, |
|
|
|
{ op: "add", path: p("a", "b"), valType: "object" }, |
|
|
|
|
|
|
|
{ op: "add", path: p("a", "b", "@id"), value: "urn:b1" }, |
|
|
|
{ op: "add", path: p("a", "b", "c"), value: 1 }, |
|
|
|
{ op: "add", path: p("a", "b", "c"), value: 1 }, |
|
|
|
|
|
|
|
// Create a primitive set
|
|
|
|
{ |
|
|
|
{ |
|
|
|
op: "add", |
|
|
|
op: "add", |
|
|
|
valType: "set", |
|
|
|
valType: "set", |
|
|
@ -193,6 +314,7 @@ describe("applyDiff - multiple mixed patches in a single diff", () => { |
|
|
|
{ op: "remove", path: p("a", "b", "c") }, |
|
|
|
{ op: "remove", path: p("a", "b", "c") }, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff, true); |
|
|
|
applyDiff(state, diff, true); |
|
|
|
|
|
|
|
expect(state.a.b["@id"]).toBe("urn:b1"); |
|
|
|
expect(state.a.b.c).toBeUndefined(); |
|
|
|
expect(state.a.b.c).toBeUndefined(); |
|
|
|
expect(state.a.b.d).toBe(2); |
|
|
|
expect(state.a.b.d).toBe(2); |
|
|
|
expect(state.a.nums).toBeInstanceOf(Set); |
|
|
|
expect(state.a.nums).toBeInstanceOf(Set); |
|
|
@ -200,20 +322,166 @@ describe("applyDiff - multiple mixed patches in a single diff", () => { |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe("applyDiff - ignored / invalid scenarios", () => { |
|
|
|
describe("applyDiff - complete workflow example", () => { |
|
|
|
test("skip patch with non-leading slash path", () => { |
|
|
|
test("full example: create person with single address and multiple children", () => { |
|
|
|
const state: any = {}; |
|
|
|
const state: any = {}; |
|
|
|
const diff: Patch[] = [ |
|
|
|
const diff: Patch[] = [ |
|
|
|
{ op: "add", path: "address/street", value: "x" }, |
|
|
|
// Create root person object
|
|
|
|
|
|
|
|
{ op: "add", path: p("urn:person1"), valType: "object" }, |
|
|
|
|
|
|
|
{ op: "add", path: p("urn:person1", "@id"), value: "urn:person1" }, |
|
|
|
|
|
|
|
{ op: "add", path: p("urn:person1", "name"), value: "John" }, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add single address object
|
|
|
|
|
|
|
|
{ op: "add", path: p("urn:person1", "address"), valType: "object" }, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "address", "@id"), |
|
|
|
|
|
|
|
value: "urn:addr1", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "address", "street"), |
|
|
|
|
|
|
|
value: "1st Street", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "address", "country"), |
|
|
|
|
|
|
|
value: "Greece", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create multi-valued children Set
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children"), |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add first child
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child1"), |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child1", "@id"), |
|
|
|
|
|
|
|
value: "urn:child1", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child1", "name"), |
|
|
|
|
|
|
|
value: "Alice", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add second child
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child2"), |
|
|
|
|
|
|
|
valType: "object", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child2", "@id"), |
|
|
|
|
|
|
|
value: "urn:child2", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child2", "name"), |
|
|
|
|
|
|
|
value: "Bob", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add primitive set (tags)
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
valType: "set", |
|
|
|
|
|
|
|
path: p("urn:person1", "tags"), |
|
|
|
|
|
|
|
value: ["developer", "parent"], |
|
|
|
|
|
|
|
}, |
|
|
|
]; |
|
|
|
]; |
|
|
|
applyDiff(state, diff); |
|
|
|
|
|
|
|
expect(state).toEqual({}); |
|
|
|
applyDiff(state, diff); // Enable ensurePathExists to create nested objects
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Verify person
|
|
|
|
|
|
|
|
expect(state["urn:person1"]["@id"]).toBe("urn:person1"); |
|
|
|
|
|
|
|
expect(state["urn:person1"].name).toBe("John"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Verify single address (plain object)
|
|
|
|
|
|
|
|
expect(state["urn:person1"].address).not.toBeInstanceOf(Set); |
|
|
|
|
|
|
|
expect(state["urn:person1"].address["@id"]).toBe("urn:addr1"); |
|
|
|
|
|
|
|
expect(state["urn:person1"].address.street).toBe("1st Street"); |
|
|
|
|
|
|
|
expect(state["urn:person1"].address.country).toBe("Greece"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Verify children Set
|
|
|
|
|
|
|
|
const children = state["urn:person1"].children; |
|
|
|
|
|
|
|
expect(children).toBeInstanceOf(Set); |
|
|
|
|
|
|
|
expect(children.size).toBe(2); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const childrenArray = [...children]; |
|
|
|
|
|
|
|
const alice = childrenArray.find((c: any) => c["@id"] === "urn:child1"); |
|
|
|
|
|
|
|
const bob = childrenArray.find((c: any) => c["@id"] === "urn:child2"); |
|
|
|
|
|
|
|
expect(alice.name).toBe("Alice"); |
|
|
|
|
|
|
|
expect(bob.name).toBe("Bob"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Verify primitive set
|
|
|
|
|
|
|
|
expect(state["urn:person1"].tags).toBeInstanceOf(Set); |
|
|
|
|
|
|
|
expect([...state["urn:person1"].tags].sort()).toEqual([ |
|
|
|
|
|
|
|
"developer", |
|
|
|
|
|
|
|
"parent", |
|
|
|
|
|
|
|
]); |
|
|
|
}); |
|
|
|
}); |
|
|
|
test("missing parent without ensurePathExists -> patch skipped and no mutation", () => { |
|
|
|
|
|
|
|
const state: any = {}; |
|
|
|
test("update and remove operations on complex structure", () => { |
|
|
|
const diff: Patch[] = [{ op: "add", path: p("a", "b", "c"), value: 1 }]; |
|
|
|
// Start with pre-existing structure
|
|
|
|
applyDiff(state, diff, false); |
|
|
|
const child1 = { "@id": "urn:child1", name: "Alice" }; |
|
|
|
expect(state).toEqual({}); |
|
|
|
const child2 = { "@id": "urn:child2", name: "Bob" }; |
|
|
|
|
|
|
|
const state: any = { |
|
|
|
|
|
|
|
"urn:person1": { |
|
|
|
|
|
|
|
"@id": "urn:person1", |
|
|
|
|
|
|
|
name: "John", |
|
|
|
|
|
|
|
address: { |
|
|
|
|
|
|
|
"@id": "urn:addr1", |
|
|
|
|
|
|
|
street: "1st Street", |
|
|
|
|
|
|
|
country: "Greece", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
children: new Set([child1, child2]), |
|
|
|
|
|
|
|
tags: new Set(["developer", "parent"]), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const diff: Patch[] = [ |
|
|
|
|
|
|
|
// Update address property
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "address", "street"), |
|
|
|
|
|
|
|
value: "2nd Street", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remove one child
|
|
|
|
|
|
|
|
{ op: "remove", path: p("urn:person1", "children", "urn:child1") }, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update child property
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "add", |
|
|
|
|
|
|
|
path: p("urn:person1", "children", "urn:child2", "age"), |
|
|
|
|
|
|
|
value: 12, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remove tag
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
op: "remove", |
|
|
|
|
|
|
|
valType: "set", |
|
|
|
|
|
|
|
path: p("urn:person1", "tags"), |
|
|
|
|
|
|
|
value: "developer", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
applyDiff(state, diff); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
expect(state["urn:person1"].address.street).toBe("2nd Street"); |
|
|
|
|
|
|
|
expect(state["urn:person1"].children.size).toBe(1); |
|
|
|
|
|
|
|
expect([...state["urn:person1"].children][0]["@id"]).toBe("urn:child2"); |
|
|
|
|
|
|
|
expect([...state["urn:person1"].children][0].age).toBe(12); |
|
|
|
|
|
|
|
expect([...state["urn:person1"].tags]).toEqual(["parent"]); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|