import { describe, it } from "@effect/vitest" import { deepStrictEqual, strictEqual, throws } from "@effect/vitest/utils" import * as JsonPatch from "effect/JsonPatch" import type * as Schema from "effect/Schema" const expectMessage = (f: () => unknown, includes: string) => { throws(f, (e) => { if (e instanceof Error && e.message.includes(includes)) return undefined throw e }) } const expectAppliesTo = (patch: JsonPatch.JsonPatch, oldValue: Schema.Json, newValue: Schema.Json) => { deepStrictEqual(JsonPatch.apply(patch, oldValue), newValue) } describe("JsonPatch", () => { describe("get", () => { describe("root values", () => { it("returns [] for identical values", () => { const cases: ReadonlyArray = [ 1, "hello", true, null, [1, 2, 3], { a: 1 } ] for (const v of cases) { deepStrictEqual(JsonPatch.get(v, v), []) } }) it("emits a root replace for primitive changes", () => { const cases: ReadonlyArray<[Schema.Json, Schema.Json]> = [ [1, 2], ["hello", "world"], [true, false], [null, 42] ] for (const [from, to] of cases) { deepStrictEqual(JsonPatch.get(from, to), [{ op: "replace", path: "", value: to }]) } }) it("emits a root replace for type changes", () => { deepStrictEqual(JsonPatch.get(1, "string"), [{ op: "replace", path: "", value: "string" }]) deepStrictEqual(JsonPatch.get([1, 2], { a: 1 }), [{ op: "replace", path: "", value: { a: 1 } }]) deepStrictEqual(JsonPatch.get({ a: 1 }, [1, 2]), [{ op: "replace", path: "", value: [1, 2] }]) }) }) describe("arrays", () => { it("adds new elements (append semantics)", () => { deepStrictEqual(JsonPatch.get([1, 2], [1, 2, 3]), [{ op: "add", path: "/2", value: 3 }]) deepStrictEqual(JsonPatch.get([], [1, 2, 3]), [ { op: "add", path: "/0", value: 1 }, { op: "add", path: "/1", value: 2 }, { op: "add", path: "/2", value: 3 } ]) }) it("removes elements in descending index order (no index shifting)", () => { deepStrictEqual(JsonPatch.get([1, 2, 3], [1, 2]), [{ op: "remove", path: "/2" }]) const patch = JsonPatch.get([0, 1, 2, 3, 4, 5], [0, 1, 3]) const removeIdx = patch .filter((op) => op.op === "remove") .map((op) => Number(op.path.slice(1))) deepStrictEqual(removeIdx, [...removeIdx].sort((a, b) => b - a)) expectAppliesTo(patch, [0, 1, 2, 3, 4, 5], [0, 1, 3]) }) it("replaces modified elements", () => { deepStrictEqual(JsonPatch.get([1, 2, 3], [1, 4, 3]), [{ op: "replace", path: "/1", value: 4 }]) }) it("handles mixed operations", () => { deepStrictEqual(JsonPatch.get([1, 2, 3], [1, 4, 5, 6]), [ { op: "replace", path: "/1", value: 4 }, { op: "replace", path: "/2", value: 5 }, { op: "add", path: "/3", value: 6 } ]) deepStrictEqual(JsonPatch.get([1, 2, 3, 4], [1, 5]), [ { op: "replace", path: "/1", value: 5 }, { op: "remove", path: "/3" }, { op: "remove", path: "/2" } ]) }) it("supports nested arrays", () => { deepStrictEqual(JsonPatch.get([[1, 2], [3, 4]], [[1, 2], [3, 5]]), [ { op: "replace", path: "/1/1", value: 5 } ]) deepStrictEqual(JsonPatch.get([[1, 2]], [[1, 2], [3, 4]]), [ { op: "add", path: "/1", value: [3, 4] } ]) }) }) describe("objects", () => { it("uses stable key order for deterministic patches", () => { const patch = JsonPatch.get({ b: 1, a: 1 }, { a: 2, b: 2 }) deepStrictEqual( patch.filter((op) => op.op === "replace").map((op) => op.path), ["/a", "/b"] ) expectAppliesTo(patch, { b: 1, a: 1 }, { a: 2, b: 2 }) }) it("adds / removes / replaces properties", () => { deepStrictEqual(JsonPatch.get({ a: 1 }, { a: 1, b: 2 }), [{ op: "add", path: "/b", value: 2 }]) deepStrictEqual(JsonPatch.get({ a: 1, b: 2 }, { a: 1 }), [{ op: "remove", path: "/b" }]) deepStrictEqual(JsonPatch.get({ a: 1, b: 2 }, { a: 1, b: 3 }), [{ op: "replace", path: "/b", value: 3 }]) }) it("removes properties in sorted key order", () => { deepStrictEqual(JsonPatch.get({ a: 1, b: 2, c: 3 }, {}), [ { op: "remove", path: "/a" }, { op: "remove", path: "/b" }, { op: "remove", path: "/c" } ]) }) it("handles mixed object operations", () => { deepStrictEqual(JsonPatch.get({ a: 1, b: 2 }, { a: 1, c: 3, d: 4 }), [ { op: "remove", path: "/b" }, { op: "add", path: "/c", value: 3 }, { op: "add", path: "/d", value: 4 } ]) }) it("supports nested objects", () => { deepStrictEqual(JsonPatch.get({ a: { b: 1 } }, { a: { b: 2 } }), [ { op: "replace", path: "/a/b", value: 2 } ]) deepStrictEqual(JsonPatch.get({ a: { b: 1 } }, { a: { b: 1, c: 2 } }), [ { op: "add", path: "/a/c", value: 2 } ]) deepStrictEqual(JsonPatch.get({ a: { b: 1, c: 2 } }, { a: { b: 1 } }), [ { op: "remove", path: "/a/c" } ]) }) describe("JSON Pointer escaping in keys", () => { it("escapes '/' as '~1' and '~' as '~0'", () => { deepStrictEqual(JsonPatch.get({ "a/b": 1 }, { "a/b": 2 }), [ { op: "replace", path: "/a~1b", value: 2 } ]) deepStrictEqual(JsonPatch.get({ "a~b": 1 }, { "a~b": 2 }), [ { op: "replace", path: "/a~0b", value: 2 } ]) }) it("represents a literal key '~1' as token '~01'", () => { deepStrictEqual(JsonPatch.get({}, { "~1": 0 }), [ { op: "add", path: "/~01", value: 0 } ]) }) it("does not confuse '~01' with '/' (unescape order)", () => { deepStrictEqual(JsonPatch.get({ "a~1b": 1 }, { "a~1b": 2 }), [ { op: "replace", path: "/a~01b", value: 2 } ]) }) }) }) describe("complex nested structures", () => { it("emits a minimal-ish patch for deep updates (and is applicable)", () => { const oldValue: Schema.Json = { users: [ { id: 1, name: "Alice", tags: ["admin"] }, { id: 2, name: "Bob", tags: [] } ], metadata: { version: 1 } } const newValue: Schema.Json = { users: [ { id: 1, name: "Alice", tags: ["admin", "moderator"] }, { id: 2, name: "Bob", tags: [] }, { id: 3, name: "Charlie", tags: [] } ], metadata: { version: 2 } } const patch = JsonPatch.get(oldValue, newValue) deepStrictEqual(patch, [ { op: "replace", path: "/metadata/version", value: 2 }, { op: "add", path: "/users/0/tags/1", value: "moderator" }, { op: "add", path: "/users/2", value: { id: 3, name: "Charlie", tags: [] } } ]) expectAppliesTo(patch, oldValue, newValue) }) }) describe("immutability", () => { it("does not mutate input values", () => { const oldArray = [1, 2, 3] const newArray = [1, 2, 4] const originalArray = [...oldArray] JsonPatch.get(oldArray, newArray) deepStrictEqual(oldArray, originalArray) const oldObject = { a: 1, b: 2 } const newObject = { a: 1, b: 3, c: 4 } const originalObject = { ...oldObject } JsonPatch.get(oldObject, newObject) deepStrictEqual(oldObject, originalObject) }) it("returns empty array for identical references", () => { const value = { a: 1, b: [2, 3] } const patch = JsonPatch.get(value, value) deepStrictEqual(patch, []) }) }) describe("empty structures", () => { it("handles empty array to empty array", () => { deepStrictEqual(JsonPatch.get([], []), []) }) it("handles empty object to empty object", () => { deepStrictEqual(JsonPatch.get({}, {}), []) }) it("handles empty array to non-empty array", () => { deepStrictEqual(JsonPatch.get([], [1, 2]), [ { op: "add", path: "/0", value: 1 }, { op: "add", path: "/1", value: 2 } ]) }) it("handles non-empty array to empty array", () => { const patch = JsonPatch.get([1, 2], []) deepStrictEqual(patch, [{ op: "remove", path: "/1" }, { op: "remove", path: "/0" }]) expectAppliesTo(patch, [1, 2], []) }) it("handles empty object to non-empty object", () => { deepStrictEqual(JsonPatch.get({}, { a: 1, b: 2 }), [ { op: "add", path: "/a", value: 1 }, { op: "add", path: "/b", value: 2 } ]) }) it("handles non-empty object to empty object", () => { const patch = JsonPatch.get({ a: 1, b: 2 }, {}) deepStrictEqual(patch, [ { op: "remove", path: "/a" }, { op: "remove", path: "/b" } ]) expectAppliesTo(patch, { a: 1, b: 2 }, {}) }) }) describe("reference equality optimization", () => { it("returns empty patch for structurally equal objects and same references", () => { const value1 = { a: 1 } const value2 = { a: 1 } const value3 = value1 const patch1 = JsonPatch.get(value1, value2) deepStrictEqual(patch1, []) expectAppliesTo(patch1, value1, value2) deepStrictEqual(JsonPatch.get(value1, value3), []) }) it("returns empty patch for structurally equal but different objects", () => { const value1 = { a: 1, b: { c: 2 } } const value2 = { a: 1, b: { c: 2 } } deepStrictEqual(JsonPatch.get(value1, value2), []) }) it("emits the property replace for structurally different objects", () => { const value1 = { a: 1 } const value2 = { a: 2 } const patch = JsonPatch.get(value1, value2) deepStrictEqual(patch.length > 0, true) expectAppliesTo(patch, value1, value2) }) it("returns empty patch for same array reference", () => { const arr = [1, 2, 3] deepStrictEqual(JsonPatch.get(arr, arr), []) }) it("returns empty patch for same object reference", () => { const obj = { a: 1, b: 2 } deepStrictEqual(JsonPatch.get(obj, obj), []) }) }) describe("edge cases", () => { it("handles single-element arrays", () => { deepStrictEqual(JsonPatch.get([1], [2]), [{ op: "replace", path: "/0", value: 2 }]) deepStrictEqual(JsonPatch.get([1], []), [{ op: "remove", path: "/0" }]) deepStrictEqual(JsonPatch.get([], [1]), [{ op: "add", path: "/0", value: 1 }]) }) it("handles single-property objects", () => { deepStrictEqual(JsonPatch.get({ a: 1 }, { a: 2 }), [{ op: "replace", path: "/a", value: 2 }]) deepStrictEqual(JsonPatch.get({ a: 1 }, {}), [{ op: "remove", path: "/a" }]) deepStrictEqual(JsonPatch.get({}, { a: 1 }), [{ op: "add", path: "/a", value: 1 }]) }) it("handles array index 0 operations", () => { deepStrictEqual(JsonPatch.get([1, 2, 3], [0, 2, 3]), [{ op: "replace", path: "/0", value: 0 }]) const patch = JsonPatch.get([1, 2, 3], [2, 3]) expectAppliesTo(patch, [1, 2, 3], [2, 3]) deepStrictEqual(patch, [ { op: "replace", path: "/0", value: 2 }, { op: "replace", path: "/1", value: 3 }, { op: "remove", path: "/2" } ]) }) it("handles operations at last array index", () => { deepStrictEqual(JsonPatch.get([1, 2, 3], [1, 2, 4]), [{ op: "replace", path: "/2", value: 4 }]) deepStrictEqual(JsonPatch.get([1, 2, 3], [1, 2]), [{ op: "remove", path: "/2" }]) }) }) describe("deep nesting", () => { it("handles deeply nested structures", () => { const oldValue: Schema.Json = { level1: { level2: { level3: { level4: { level5: { value: "old" } } } } } } const newValue: Schema.Json = { level1: { level2: { level3: { level4: { level5: { value: "new", extra: "added" } } } } } } const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) }) it("handles nested arrays within objects", () => { const oldValue: Schema.Json = { items: [ [1, 2], [3, 4] ] } const newValue: Schema.Json = { items: [ [1, 2], [3, 5], [6, 7] ] } const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) }) }) }) describe("apply", () => { describe("happy paths", () => { it("applies operations after a root replace", () => { deepStrictEqual( JsonPatch.apply( [ { op: "replace", path: "", value: {} }, { op: "add", path: "/a", value: 1 } ], { old: true } ), { a: 1 } ) }) it("replace", () => { deepStrictEqual(JsonPatch.apply([{ op: "replace", path: "", value: 42 }], 1), 42) deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/a", value: 2 }], { a: 1 }), { a: 2 } ) deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/1", value: 20 }], [1, 2, 3]), [1, 20, 3] ) deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/a/b", value: 2 }], { a: { b: 1 } }), { a: { b: 2 } } ) }) it("add", () => { deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/b", value: 2 }], { a: 1 }), { a: 1, b: 2 } ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/1", value: 10 }], [1, 2, 3]), [1, 10, 2, 3] ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/-", value: 4 }], [1, 2, 3]), [1, 2, 3, 4] ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/users/0/tags/-", value: "admin" }], { users: [{ id: 1, tags: [] }] }), { users: [{ id: 1, tags: ["admin"] }] } ) }) it("remove", () => { deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/a" }], { a: 1, b: 2 }), { b: 2 } ) deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/1" }], [1, 2, 3]), [1, 3] ) deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/a/b" }], { a: { b: 1, c: 2 } }), { a: { c: 2 } } ) }) it("applies multiple operations in sequence", () => { deepStrictEqual( JsonPatch.apply( [ { op: "add", path: "/c", value: 3 }, { op: "replace", path: "/a", value: 10 }, { op: "remove", path: "/b" } ], { a: 1, b: 2 } ), { a: 10, c: 3 } ) }) }) describe("JSON Pointer decoding", () => { it("decodes '~1' as '/' and '~0' as '~'", () => { deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/a~1b", value: 2 }], { "a/b": 1 }), { "a/b": 2 } ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/a~0b", value: 1 }], {}), { "a~b": 1 } ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/path~1to~0key", value: "value" }], {}), { "path/to~key": "value" } ) }) it("decodes '~01' as a literal '~1' (unescape order)", () => { deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/a~01b", value: 1 }], {}), { "a~1b": 1 } ) }) it("addresses a literal key '~1' via token '~01'", () => { deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/~01", value: 0 }], {}), { "~1": 0 } ) }) }) describe("errors", () => { it("rejects non-empty pointers that do not start with '/'", () => { expectMessage( () => JsonPatch.apply([{ op: "add", path: "invalid", value: 1 }], {}), `must start with "/"` ) }) it("rejects invalid array indices", () => { expectMessage( () => JsonPatch.apply([{ op: "add", path: "/abc", value: 1 }], []), `Invalid array index` ) expectMessage( () => JsonPatch.apply([{ op: "replace", path: "/-1", value: 1 }], [1, 2, 3]), `Invalid array index` ) }) it("rejects out-of-bounds array access", () => { expectMessage( () => JsonPatch.apply([{ op: "replace", path: "/10", value: 1 }], [1, 2, 3]), "Array index out of bounds" ) expectMessage( () => JsonPatch.apply([{ op: "add", path: "/10", value: 1 }], [1, 2, 3]), "Array index out of bounds" ) expectMessage( () => JsonPatch.apply([{ op: "remove", path: "/10" }], [1, 2, 3]), "Array index out of bounds" ) }) it("rejects '-' for replace/remove", () => { expectMessage( () => JsonPatch.apply([{ op: "replace", path: "/-", value: 1 }], [1, 2, 3]), `"-" is not valid for replace` ) expectMessage( () => JsonPatch.apply([{ op: "remove", path: "/-" }], [1, 2, 3]), `"-" is not valid for remove` ) }) it("rejects replace/remove of non-existent object members", () => { expectMessage( () => JsonPatch.apply([{ op: "replace", path: "/nonexistent", value: 1 }], { a: 1 }), `does not exist` ) expectMessage( () => JsonPatch.apply([{ op: "remove", path: "/nonexistent" }], { a: 1 }), `does not exist` ) }) it("rejects add/replace when the parent is missing or not a container", () => { expectMessage( () => JsonPatch.apply([{ op: "add", path: "/a/b", value: 1 }], { a: null }), "Cannot add at" ) expectMessage( () => JsonPatch.apply([{ op: "add", path: "/a/b", value: 1 }], {}), "Cannot add at" ) expectMessage( () => JsonPatch.apply([{ op: "add", path: "/a/b", value: 1 }], { a: "string" }), "not a container" ) expectMessage( () => JsonPatch.apply([{ op: "replace", path: "/a/b", value: 1 }], { a: 42 }), "not a container" ) expectMessage( () => JsonPatch.apply([{ op: "add", path: "/a/b/c", value: 1 }], { a: { b: "not-object" } }), "not a container" ) }) it("rejects remove at the root", () => { expectMessage( () => JsonPatch.apply([{ op: "remove", path: "" }], { a: 1 }), "root" ) }) }) describe("immutability", () => { it("does not mutate input document", () => { const original = { a: 1, b: [2, 3] } const originalCopy = { a: 1, b: [2, 3] } JsonPatch.apply([{ op: "replace", path: "/a", value: 10 }], original) deepStrictEqual(original, originalCopy) const originalArray = [1, 2, 3] const originalArrayCopy = [1, 2, 3] JsonPatch.apply([{ op: "add", path: "/1", value: 99 }], originalArray) deepStrictEqual(originalArray, originalArrayCopy) }) it("does not mutate patch array", () => { const patch: JsonPatch.JsonPatch = [{ op: "add", path: "/b", value: 2 }] const patchCopy = JSON.parse(JSON.stringify(patch)) JsonPatch.apply(patch, { a: 1 }) deepStrictEqual(patch, patchCopy) }) }) describe("empty patch optimization", () => { it("returns original reference for empty patch", () => { const doc = { a: 1, b: 2 } const result = JsonPatch.apply([], doc) strictEqual(result, doc) }) it("returns original reference for empty patch with arrays", () => { const arr = [1, 2, 3] const result = JsonPatch.apply([], arr) strictEqual(result, arr) }) it("returns original reference for empty patch with primitives", () => { const primitives: ReadonlyArray = [1, "hello", true, null] for (const value of primitives) { const result = JsonPatch.apply([], value) strictEqual(result, value) } }) }) describe("sequential path dependencies", () => { it("applies operations on paths created by earlier operations", () => { const result = JsonPatch.apply( [ { op: "add", path: "/new", value: {} }, { op: "add", path: "/new/nested", value: { value: 1 } }, { op: "replace", path: "/new/nested/value", value: 2 } ], {} ) deepStrictEqual(result, { new: { nested: { value: 2 } } }) }) it("applies operations on array paths created by earlier operations", () => { const result = JsonPatch.apply( [ { op: "add", path: "/items", value: [] }, { op: "add", path: "/items/-", value: 1 }, { op: "add", path: "/items/-", value: 2 }, { op: "replace", path: "/items/0", value: 10 } ], {} ) deepStrictEqual(result, { items: [10, 2] }) }) it("applies multiple operations on same path", () => { const result = JsonPatch.apply( [ { op: "add", path: "/counter", value: 0 }, { op: "replace", path: "/counter", value: 1 }, { op: "replace", path: "/counter", value: 2 } ], {} ) deepStrictEqual(result, { counter: 2 }) }) it("applies remove then add on same path", () => { const result = JsonPatch.apply( [ { op: "remove", path: "/a" }, { op: "add", path: "/a", value: "new" } ], { a: "old", b: 2 } ) deepStrictEqual(result, { a: "new", b: 2 }) }) }) describe("edge cases", () => { it("handles operations on empty arrays", () => { deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/-", value: 1 }], []), [1] ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/0", value: 1 }], []), [1] ) }) it("handles operations on empty objects", () => { deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/key", value: "value" }], {}), { key: "value" } ) }) it("handles array operations at index 0", () => { deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/0", value: 10 }], [1, 2, 3]), [10, 2, 3] ) deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/0" }], [1, 2, 3]), [2, 3] ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/0", value: 0 }], [1, 2, 3]), [0, 1, 2, 3] ) }) it("handles array operations at last index", () => { const arr = [1, 2, 3] deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/2", value: 30 }], arr), [1, 2, 30] ) deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/2" }], arr), [1, 2] ) }) it("handles single-element array operations", () => { deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/0", value: 2 }], [1]), [2] ) deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/0" }], [1]), [] ) deepStrictEqual( JsonPatch.apply([{ op: "add", path: "/0", value: 0 }], [1]), [0, 1] ) }) it("handles single-property object operations", () => { deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "/a", value: 2 }], { a: 1 }), { a: 2 } ) deepStrictEqual( JsonPatch.apply([{ op: "remove", path: "/a" }], { a: 1 }), {} ) }) }) describe("root operations", () => { it("applies root replace followed by nested operations", () => { const result = JsonPatch.apply( [ { op: "replace", path: "", value: {} }, { op: "add", path: "/a", value: 1 }, { op: "add", path: "/b", value: 2 } ], { old: "data" } ) deepStrictEqual(result, { a: 1, b: 2 }) }) it("applies root replace to different types", () => { deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "", value: [] }], { a: 1 }), [] ) deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "", value: "string" }], 42), "string" ) deepStrictEqual( JsonPatch.apply([{ op: "replace", path: "", value: null }], true), null ) }) }) }) describe("round-trip", () => { const cases: ReadonlyArray<[Schema.Json, Schema.Json]> = [ [ { users: [ { id: 1, name: "Alice", active: true }, { id: 2, name: "Bob", active: false } ], metadata: { version: 1, tags: ["v1"] } }, { users: [ { id: 1, name: "Alice Updated", active: true }, { id: 3, name: "Charlie", active: true } ], metadata: { version: 2, tags: ["v2", "latest"] } } ], [ [1, 2, 3, 4, 5], [1, 20, 3, 40, 50, 60] ], [ { a: 1, b: 2, c: 3 }, { a: 10, d: 4, e: 5 } ], [ { level1: { level2: { level3: { value: "old" } } } }, { level1: { level2: { level3: { value: "new", extra: "added" } } } } ] ] it("apply(get(old, new), old) === new", () => { for (const [oldValue, newValue] of cases) { const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) } }) it("round-trip with empty structures", () => { const emptyCases: ReadonlyArray<[Schema.Json, Schema.Json]> = [ [[], [1, 2, 3]], [[1, 2, 3], []], [{}, { a: 1, b: 2 }], [{ a: 1, b: 2 }, {}] ] for (const [oldValue, newValue] of emptyCases) { const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) } }) it("round-trip with nested arrays and objects", () => { const oldValue: Schema.Json = { matrix: [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ], config: { settings: { nested: { deep: { value: "old" } } } } } const newValue: Schema.Json = { matrix: [ [1, 2, 3], [4, 50, 6], [7, 8, 9], [10, 11, 12] ], config: { settings: { nested: { deep: { value: "new", extra: "field" } } } } } const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) }) it("round-trip with complex array operations", () => { const oldValue: Schema.Json = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] const newValue: Schema.Json = [10, 1, 20, 3, 40, 50, 6, 70, 8] const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) }) it("round-trip with escaped keys", () => { const oldValue: Schema.Json = { "key/with/slash": 1, "key~with~tilde": 2, "normal": 3 } const newValue: Schema.Json = { "key/with/slash": 10, "key~with~tilde": 20, "normal": 30, "new/key": 40 } const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) }) it("round-trip with type changes", () => { const typeChangeCases: ReadonlyArray<[Schema.Json, Schema.Json]> = [ [1, "string"], ["string", 42], [true, null], [null, []], [[], {}], [{}, []] ] for (const [oldValue, newValue] of typeChangeCases) { const patch = JsonPatch.get(oldValue, newValue) expectAppliesTo(patch, oldValue, newValue) } }) it("round-trip preserves identical values (empty patch)", () => { const identicalCases: ReadonlyArray = [ { a: 1, b: 2 }, [1, 2, 3], "string", 42, true, null, { nested: { deep: { value: "test" } } }, [[1, 2], [3, 4]] ] for (const value of identicalCases) { const patch = JsonPatch.get(value, value) deepStrictEqual(patch, []) const result = JsonPatch.apply(patch, value) deepStrictEqual(result, value) } }) }) })