UNPKG

@irrelon/path

Version:

A powerful JSON path processor. Allows you to drill into and manipulate JSON objects with a simple dot-delimited path format e.g. "obj.name".

1,862 lines (1,632 loc) 104 kB
import assert from "node:assert"; import { countLeafNodes, diff, diffValues, down, escape, findOnePath, findPath, flatten, flattenValues, furthest, get, getMany, isNotEqual, join, joinEscaped, leafNodes, match, merge, mergeImmutable, PathData, pop, pullVal, pushVal, query, set, setImmutable, shift, splicePath, split, traverse, type, unSet, unSetImmutable, up, update, updateImmutable, values } from "../src/path"; describe("Path", () => { describe("join()", () => { it("Will join multiple paths together", () => { const result = join("test1", "test2"); assert.strictEqual(result, "test1.test2", "Path is correct"); }); }); describe("joinEscape()", () => { it("Will join multiple paths together escaped", () => { const result = joinEscaped("test1.com", "test2.com"); assert.strictEqual(result, "test1\\.com.test2\\.com", "Path is correct"); }); }); describe("values()", () => { it("Will traverse the tree and find all values for each part of the path", () => { const obj = { "obj": [{ "other": {} }] }; const result = values(obj, "obj.0.other.val.another"); assert.strictEqual(result.obj instanceof Array, true, "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0"], "object", "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0.other"], "object", "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0.other.val"], "undefined", "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0.other.val.another"], "undefined", "The value was retrieved correctly"); }); it("Will handle infinite recursive structures", () => { const obj: any = { "obj": [{ "other": {} }] }; // Create an infinite recursion obj.obj[0].other.obj = obj; const result = values(obj, "obj.0.other.obj.0.other"); assert.strictEqual(result.obj instanceof Array, true, "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0"], "object", "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0.other"], "object", "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0.other.val"], "undefined", "The value was retrieved correctly"); assert.strictEqual(typeof result["obj.0.other.val.another"], "undefined", "The value was retrieved correctly"); }); }); describe("countLeafNodes()", () => { it("Will count leaf nodes of an object", () => { const obj = { "obj": [{ "other": { moo: "foo" } }] }; const result = countLeafNodes(obj); assert.strictEqual(result, 1, "The test value is correct"); }); it("Will count leaf nodes of an object with infinite recursive structure", () => { const obj: any = { "obj": [{ "other": { moo: "foo" } }] }; // Create an infinite recursion obj.obj[0].other.obj = obj; const result = countLeafNodes(obj); assert.strictEqual(result, 2, "The test value is correct"); }); it("Will count null value leaf nodes of an object", () => { const obj = { "obj": [{ "other": { "moo": "foo", "bar": null } }] }; const result = countLeafNodes(obj); assert.strictEqual(result, 2, "The test value is correct"); }); }); describe("flatten()", () => { it("Will flatten an object structure to array of keys", () => { const obj = { "obj": [{ "other": { moo: "foo" } }] }; const result = flatten(obj); assert.ok(result instanceof Array, "The test type is correct"); assert.strictEqual(result.length, 4, "The array length is correct"); assert.ok(result.indexOf("obj") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0.other") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0.other.moo") > -1, "The test type is correct"); }); it("Will handle an infinite recursive structure", () => { const obj: any = { "obj": [{ "other": { "moo": "foo" } }] }; // Create an infinite recursion obj.obj[0].other.obj = obj; const result = flatten(obj); assert.ok(result instanceof Array, "The test type is correct"); assert.strictEqual(result.length, 5, "The array length is correct"); assert.ok(result.indexOf("obj") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0.other") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0.other.moo") > -1, "The test type is correct"); assert.ok(result.indexOf("obj.0.other.obj") > -1, "The test type is correct"); }); }); describe("flattenValues()", () => { it("Will flatten an object structure to keys and values", () => { const obj = { "obj": [{ "other": { "moo": "foo" } }] }; const result = flattenValues(obj); assert.strictEqual(typeof result, "object", "The test type is correct"); assert.strictEqual(result["obj"] instanceof Array, true, "The test type is correct"); assert.strictEqual(typeof result["obj.0.other"], "object", "The test type is correct"); assert.strictEqual(typeof result["obj.0.other.moo"], "string", "The test type is correct"); assert.strictEqual(result["obj.0.other.moo"], "foo", "The test value is correct"); }); it("Will flatten an object structure with custom key transformer", () => { const obj = { "obj": [{ "other": { "moo": "foo" } }] }; const result = flattenValues(obj, undefined, "", { transformKey: (key, info) => info.isArrayIndex ? "$" : key }); assert.strictEqual(typeof result, "object", "The test type is correct"); assert.strictEqual(result["obj"] instanceof Array, true, "The test type is correct"); assert.strictEqual(typeof result["obj.$.other"], "object", "The test type is correct"); assert.strictEqual(typeof result["obj.$.other.moo"], "string", "The test type is correct"); assert.strictEqual(result["obj.$.other.moo"], "foo", "The test value is correct"); }); it("Will flatten an object structure with custom key transformer", () => { const obj = { "obj": [{ "other": { "moo": "foo" } }] }; const expected = { "obj.$.other.moo": "foo" }; const result = flattenValues(obj, undefined, "", { transformKey: (key, info) => info.isArrayIndex ? "$" : key, leavesOnly: true }); assert.deepStrictEqual(result, expected, "The test type is correct"); }); it("Will handle an infinite recursive structure", () => { const obj: any = { "obj": [{ "other": { moo: "foo" } }] }; // Create an infinite recursion obj.obj[0].other.obj = obj; const result = flattenValues(obj); assert.strictEqual(typeof result, "object", "The test type is correct"); assert.strictEqual(result["obj"] instanceof Array, true, "The test type is correct"); assert.strictEqual(typeof result["obj.0.other"], "object", "The test type is correct"); assert.strictEqual(typeof result["obj.0.other.moo"], "string", "The test type is correct"); assert.strictEqual(result["obj.0.other.moo"], "foo", "The test value is correct"); assert.strictEqual(typeof result["obj.0.other.obj"], "object", "The test value is correct"); assert.strictEqual(typeof result["obj.0.other.obj.0"], "undefined", "The test value is correct"); }); }); describe("furthest()", () => { it("Will traverse the tree and find the last available leaf", () => { const obj = { "obj": [{ "other": {} }] }; const result = furthest(obj, "obj.0.other.val.another"); assert.strictEqual(result, "obj.0.other", "The value was retrieved correctly"); }); it("Will accept an array index wildcard", () => { const obj = { "obj": [{ "other": {} }] }; const result = furthest(obj, "obj.$.other.val.another"); assert.strictEqual(result, "obj.$.other", "The value was retrieved correctly"); }); it("Will work with a single leaf and no dot notation", () => { const obj = { "name": "Foo" }; const result = furthest(obj, "name"); assert.strictEqual(result, "name", "The value was retrieved correctly"); }); }); describe("split()", () => { it("Will split non-escaped strings correctly", () => { const path = "foo.test.bar"; const result = split(path); assert.strictEqual(result.length, 3, "The result length is correct"); assert.strictEqual(result[0], "foo", "The result is correct"); assert.strictEqual(result[1], "test", "The result is correct"); assert.strictEqual(result[2], "bar", "The result is correct"); }); it("Will split escaped strings correctly", () => { const path = "foo.test@test\\.com"; const result = split(path); assert.strictEqual(result.length, 2, "The result length is correct"); assert.strictEqual(result[0], "foo", "The result is correct"); assert.strictEqual(result[1], "test@test\\.com", "The result is correct"); }); }); describe("getMany()", () => { it("Can get a field value from an object path", () => { const obj = { "obj": { "val": "foo" } }; const result = getMany(obj, "obj.val"); assert.deepStrictEqual(result, ["foo"], "The value was retrieved correctly"); }); it("Can get a field value from an array path", () => { const obj = { "arr": [{ "val": "foo" }] }; const result = getMany(obj, "arr.0.val"); assert.deepStrictEqual(result, ["foo"], "The value was retrieved correctly"); }); it("Can return the default value when a path does not exist", () => { const obj = { "arr": [{ "val": "foo" }] }; const result = getMany(obj, "arr.0.nonExistent", "defaultVal"); assert.deepStrictEqual(result, ["defaultVal"], "The value was retrieved correctly"); }); it("Can return the default value when a sub-path does not exist", () => { const obj = { "arr": [{ "val": "foo" }] }; const result = getMany(obj, "arr.3.nonExistent", "defaultVal"); assert.deepStrictEqual(result, ["defaultVal"], "The value was retrieved correctly"); }); it("Will return undefined when the full path is non-existent", () => { const obj = { "obj": { "val": null } }; const result = getMany(obj, "obj.val.roo.foo.moo"); assert.deepStrictEqual(result, [], "The value was retrieved correctly"); }); it("Supports escaped paths to get data correctly", () => { const obj = { "foo": { "jim@jones.com": "bar" } }; const path = joinEscaped("foo", "jim@jones.com"); const result = getMany(obj, path); assert.deepStrictEqual(result, ["bar"], "The value is correct"); }); it("Supports auto-expanding arrays when arrayExpansion is true and root is not an array", () => { const obj = { "innerObj": { "arr": [{ "thing": "thought" }, { "otherThing": "otherThought" }, { "subArr": [{ "value": "bar" }, { "value": "ram" }] }, { "subArr": [{ "value": "you" }, { "value": undefined }] }, { "subArr": [{ "value": "too" }] }] } }; const path = "innerObj.arr.subArr.value"; const result = getMany(obj, path, undefined, {arrayTraversal: true, arrayExpansion: true}); assert.deepStrictEqual(result, ["bar", "ram", "you", "too"], "The value is correct"); }); it("Correctly returns undefined when arrayTraversal is true and leaf nodes produce no result and no default value is provided", () => { const obj = { "arr": [{ "someValue": "bar" }, { "someValue": "ram" }, { "someValue": "you" }] }; const path = "arr.value"; const result = getMany(obj, path, undefined, {arrayTraversal: true}); assert.deepStrictEqual(result, [], "The value is correct"); }); it("Correctly expands result when a wildcard is used", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all docs in subSubArr from all docs in // subArr from all docs in arr const path = "arr.$.subArr.$.subSubArr.$"; const result = getMany(obj, path, undefined, {arrayTraversal: false, wildcardExpansion: true}); const expected = [{ "label": "one" }, { "label": "two" }, { "label": "three" }]; assert.deepStrictEqual(result, expected, "The value is correct"); }); it("Correctly expands result when a wildcard is used with arrayTraversal enabled and a sub-document positional terminator", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all docs in subSubArr from all docs in // subArr from all docs in arr const path = "arr.$.subArr.$.subSubArr.$"; const result = getMany(obj, path, undefined, {arrayTraversal: true, wildcardExpansion: true}); const expected = [{ "label": "one" }, { "label": "two" }, { "label": "three" }]; assert.deepStrictEqual(result, expected, "The value is correct"); }); it("Correctly expands result when a wildcard is used with arrayTraversal enabled and a non-positional terminator", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all subSubArr from all docs in // subArr from all docs in arr const path = "arr.$.subArr.$.subSubArr"; const result = getMany(obj, path, undefined, {arrayTraversal: true, wildcardExpansion: true}); const expected = [[{ "label": "one" }], [{ "label": "two" }], [{ "label": "three" }]]; assert.deepStrictEqual(result, expected, "The value is correct"); }); }); describe("get()", () => { it("Can get a field value from an object path", () => { const obj = { "obj": { "val": "foo" } }; const result = get(obj, "obj.val"); assert.strictEqual(result, "foo", "The value was retrieved correctly"); }); it("Can get a field value from an array path", () => { const obj = { "arr": [{ "val": "foo" }] }; const result = get(obj, "arr.0.val"); assert.strictEqual(result, "foo", "The value was retrieved correctly"); }); it("Can return the default value when a path does not exist", () => { const obj = { "arr": [{ "val": "foo" }] }; const result = get(obj, "arr.0.nonExistent", "defaultVal"); assert.strictEqual(result, "defaultVal", "The value was retrieved correctly"); }); it("Can return the default value when a sub-path does not exist", () => { const obj = { "arr": [{ "val": "foo" }] }; const result = get(obj, "arr.3.nonExistent", "defaultVal"); assert.strictEqual(result, "defaultVal", "The value was retrieved correctly"); }); it("Will return undefined when the full path is non-existent", () => { const obj = { "obj": { "val": null } }; const result = get(obj, "obj.val.roo.foo.moo"); assert.strictEqual(result, undefined, "The value was retrieved correctly"); }); it("Supports escaped paths to get data correctly", () => { const obj = { "foo": { "jim@jones.com": "bar" } }; const path = joinEscaped("foo", "jim@jones.com"); const result = get(obj, path); assert.strictEqual(result, "bar", "The value is correct"); }); it("Supports auto-traversing arrays when arrayTraversal is true", () => { const obj = { "arr": [{ "thing": "thought" }, { "otherThing": "otherThought" }, { "value": "bar" }, { "value": "ram" }, { "value": "you" }] }; const path = "arr.value"; const result = get(obj, path, undefined, {arrayTraversal: true}); assert.strictEqual(result, "bar", "The value is correct"); }); it("Supports auto-expanding arrays when arrayExpansion is true", () => { const obj = { "arr": [{ "thing": "thought" }, { "otherThing": "otherThought" }, { "value": "bar" }, { "value": "ram" }, { "value": "you" }, { "value": undefined }, { "value": "too" }] }; const path = "arr.value"; const result = get(obj, path, undefined, {arrayTraversal: true, arrayExpansion: true}); assert.deepStrictEqual(result, ["bar", "ram", "you", "too"], "The value is correct"); }); it("Supports nested auto-expanding arrays when arrayExpansion is true", () => { const obj = { "arr": [{ "thing": "thought" }, { "otherThing": "otherThought" }, { "subArr": [{ "value": "bar" }, { "value": "ram" }] }, { "subArr": [{ "value": "you" }, { "value": undefined }] }, { "subArr": [{ "value": "too" }] }] }; const path = "arr.subArr.value"; const result = get(obj, path, undefined, {arrayTraversal: true, arrayExpansion: true}); assert.deepStrictEqual(result, ["bar", "ram", "you", "too"], "The value is correct"); }); it("Correctly returns default value when arrayTraversal is true and leaf nodes produce no result", () => { const obj = { "arr": [{ "someValue": "bar" }, { "someValue": "ram" }, { "someValue": "you" }] }; const path = "arr.value"; const result = get(obj, path, "myDefaultVal", {arrayTraversal: true}); assert.strictEqual(result, "myDefaultVal", "The value is correct"); }); it("Correctly returns undefined when arrayTraversal is true and leaf nodes produce no result and no default value is provided", () => { const obj = { "arr": [{ "someValue": "bar" }, { "someValue": "ram" }, { "someValue": "you" }] }; const path = "arr.value"; const result = get(obj, path, undefined, {arrayTraversal: true}); assert.strictEqual(result, undefined, "The value is correct"); }); it("Correctly expands result when a wildcard is used", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all docs in subSubArr from all docs in // subArr from all docs in arr const path = "arr.$.subArr.$.subSubArr.$"; const result = get(obj, path, undefined, {arrayTraversal: false, wildcardExpansion: true}); const expected = [{ "label": "one" }, { "label": "two" }, { "label": "three" }]; assert.deepStrictEqual(result, expected, "The value is correct"); }); it("Correctly expands result when a wildcard is used with arrayTraversal enabled and a sub-document positional terminator", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all docs in subSubArr from all docs in // subArr from all docs in arr const path = "arr.$.subArr.$.subSubArr.$"; const result = get(obj, path, undefined, {arrayTraversal: true, wildcardExpansion: true}); const expected = [{ "label": "one" }, { "label": "two" }, { "label": "three" }]; assert.deepStrictEqual(result, expected, "The value is correct"); }); it("Correctly expands result when a wildcard is used with arrayTraversal enabled and a non-positional terminator", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all subSubArr from all docs in // subArr from all docs in arr const path = "arr.$.subArr.$.subSubArr"; const result = get(obj, path, undefined, {arrayTraversal: true, wildcardExpansion: true}); const expected = [[{ "label": "one" }], [{ "label": "two" }], [{ "label": "three" }]]; assert.deepStrictEqual(result, expected, "The value is correct"); }); it("Correctly assigns path data when array expansion occurs", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }, { "label": "two-point-two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all subSubArr from all docs in // subArr from all docs in arr const path = "arr.subArr.subSubArr"; const pathData: PathData = { directPaths: [] }; // We are passing this in by reference and it will be modified by get() const result = get(obj, path, undefined, {arrayTraversal: true, arrayExpansion: true, pathData}); const expected = [{ "label": "one" }, { "label": "two" }, { "label": "two-point-two" }, { "label": "three" }]; const pathDataExpected = [ "arr.0.subArr.0.subSubArr.0", "arr.1.subArr.0.subSubArr.0", "arr.1.subArr.0.subSubArr.1", "arr.2.subArr.0.subSubArr.0" ]; assert.deepStrictEqual(result, expected, "The value is correct"); assert.deepStrictEqual(pathData.directPaths, pathDataExpected, "The value is correct"); }); it("Correctly assigns path data when array expansion occurs with a target leaf node", () => { const obj = { "arr": [{ "subArr": [{ "label": "thought", "subSubArr": [{ "label": "one" }] }] }, { "subArr": [{ "label": "is", "subSubArr": [{ "label": "two" }, { "label": "two-point-two" }] }] }, { "subArr": [{ "label": "good", "subSubArr": [{ "label": "three" }] }] }] }; // Meaning get me all label from all docs in subSubArr from all docs in // subArr from all docs in arr const path = "arr.subArr.subSubArr.label"; const pathData: PathData = { directPaths: [] }; // We are passing this in by reference, and it will be modified by get() const result = get(obj, path, undefined, {arrayTraversal: true, arrayExpansion: true, pathData}); const expected = [ "one", "two", "two-point-two", "three" ]; const pathDataExpected = [ "arr.0.subArr.0.subSubArr.0.label", "arr.1.subArr.0.subSubArr.0.label", "arr.1.subArr.0.subSubArr.1.label", "arr.2.subArr.0.subSubArr.0.label" ]; assert.deepStrictEqual(result, expected, "The value is correct"); assert.deepStrictEqual(pathData.directPaths, pathDataExpected, "The value is correct"); }); }); describe("set()", () => { it("Can set a value on the passed object at the correct path with auto-created objects", () => { const obj: any = {}; const newObj = set(obj, "foo.bar.thing", "foo"); assert.strictEqual(obj.foo.bar.thing, "foo", "The value was set correctly"); assert.strictEqual(newObj, obj, "The object reference is the same"); }); it("Can set a value on the passed object with an array index at the correct path", () => { const obj = { "arr": [1] }; const newObj = set(obj, "arr.1", "foo"); assert.strictEqual(obj.arr[0], 1, "The value was set correctly"); assert.strictEqual(obj.arr[1], "foo", "The value was set correctly"); assert.strictEqual(newObj, obj, "The object reference is the same"); }); it("Can set a value on the passed object with an array index when no array currently exists at the correct path", () => { const obj: any = {}; const newObj = set(obj, "arr.0", "foo"); assert.strictEqual(obj.arr[0], "foo", "The value was set correctly"); assert.strictEqual(newObj, obj, "The object reference is the same"); }); it("Can set a value on the passed object where the leaf node is an object", () => { const obj: any = { "foo": { "bar": { "ram": { original: true } } } }; const newObj = set(obj, "foo.bar.ram", {copy: true}); assert.strictEqual(obj.foo.bar.ram.copy, true, "The value was set correctly"); assert.strictEqual(newObj, obj, "The object reference is the same"); }); it("Can set a value on the passed object with a single path key", () => { const obj = { "foo": true }; const newObj = set(obj, "foo", false); assert.strictEqual(obj.foo, false, "The value was set correctly"); assert.strictEqual(newObj, obj, "The object reference is the same"); }); it("Supports escaped paths to set data correctly", () => { const obj = { "foo": true }; const path = joinEscaped("foo", "jim@jones.com"); const newObj = set(obj, path, false); assert.strictEqual(newObj.foo["jim@jones.com"], false, "The value was set correctly"); }); it("Handles trying to set a value on a null correctly", () => { const obj: any = { "foo": null }; set(obj, "foo.bar", true); assert.strictEqual(typeof obj.foo, "object", "The value is correct"); assert.strictEqual(obj.foo.bar, true, "The value is correct"); }); it("Is not vulnerable to __proto__ pollution", () => { const obj: any = {}; set(obj, "__proto__.polluted", true); assert.strictEqual(obj.polluted, undefined, "The object prototype cannot be polluted"); }); }); describe("setImmutable()", () => { it("Can de-reference all objects which contain a change (useful for state management systems)", () => { const obj = { "shouldNotChange1": {}, "shouldNotChange2": {}, "shouldChange1": { "shouldChange2": { "shouldNotChange3": {}, "shouldChange3": { "value": false } } } }; const shouldNotChange1 = obj.shouldNotChange1; const shouldNotChange2 = obj.shouldNotChange2; const shouldNotChange3 = obj.shouldChange1.shouldChange2.shouldNotChange3; const shouldChange1 = obj.shouldChange1; const shouldChange2 = obj.shouldChange1.shouldChange2; const shouldChange3 = obj.shouldChange1.shouldChange2.shouldChange3; const newObj = set(obj, "shouldChange1.shouldChange2.shouldChange3.value", true, {immutable: true}); assert.notStrictEqual(newObj, obj, "Root object is not the same"); assert.strictEqual(obj.shouldChange1.shouldChange2.shouldChange3.value, false, "The value of the original object was not changed"); assert.strictEqual(obj.shouldNotChange1, shouldNotChange1, "Value did not change"); assert.strictEqual(obj.shouldNotChange2, shouldNotChange2, "Value did not change"); assert.strictEqual(obj.shouldChange1.shouldChange2.shouldNotChange3, shouldNotChange3, "Value did not change"); assert.strictEqual(obj.shouldChange1, shouldChange1, "Value did not change"); assert.strictEqual(obj.shouldChange1.shouldChange2, shouldChange2, "Value did not change"); assert.strictEqual(obj.shouldChange1.shouldChange2.shouldChange3, shouldChange3, "Value did not change"); assert.strictEqual(newObj.shouldChange1.shouldChange2.shouldChange3.value, true, "The value of the new object was changed"); assert.strictEqual(newObj.shouldNotChange1, shouldNotChange1, "Value did not change"); assert.strictEqual(newObj.shouldNotChange2, shouldNotChange2, "Value did not change"); assert.strictEqual(newObj.shouldChange1.shouldChange2.shouldNotChange3, shouldNotChange3, "Value did not change"); assert.notStrictEqual(newObj.shouldChange1, shouldChange1, "Value did not change"); assert.notStrictEqual(newObj.shouldChange1.shouldChange2, shouldChange2, "Value did not change"); assert.notStrictEqual(newObj.shouldChange1.shouldChange2.shouldChange3, shouldChange3, "Value did not change"); }); it("Can update a value in an object within a nested array", () => { const obj = { "foo": [{ "value": false }] }; const foo = obj.foo; const fooType = type(obj.foo); const newObj = set(obj, "foo.0.value", true, {immutable: true}); const newFooType = type(newObj.foo); assert.notStrictEqual(newObj, obj, "Root object is not the same"); assert.strictEqual(fooType, newFooType, "Array type has not changed"); }); it("Is not vulnerable to __proto__ pollution", () => { const obj: any = {}; setImmutable(obj, "__proto__.polluted", true); assert.strictEqual(obj.polluted, undefined, "The object prototype cannot be polluted"); }); }); describe("unSet()", () => { describe("Mutable", () => { it("Will remove the required key from an object", () => { const obj = { "foo": { "bar": [{ "moo": true, "baa": "ram you" }] } }; assert.strictEqual(obj.foo.bar[0].baa, "ram you", "Object has value"); const newObj = unSet(obj, "foo.bar.0.baa"); assert.strictEqual(obj.foo.bar[0].baa, undefined, "Object does not have value"); assert.strictEqual(obj.foo.bar[0].hasOwnProperty("baa"), false, "Object does not have key"); assert.strictEqual(newObj, obj, "Root object is the same"); }); it("Will correctly handle a path to nowhere", () => { const obj = { "foo": { "bar": [{ "moo": true, "baa": "ram you" }] } }; const newObj = unSet(obj, "foo.bar.2.baa"); assert.strictEqual(obj.foo.bar.hasOwnProperty("2"), false, "Object does not have key"); assert.strictEqual(newObj, obj, "Root object is the same"); }); it("Is not vulnerable to __proto__ pollution", () => { const obj: any = {}; obj.__proto__.unsetPolluted = false; unSet(obj, "__proto__.unsetPolluted"); assert.strictEqual(obj.unsetPolluted, false, "The object prototype cannot be polluted"); }); }); describe("Immutable", () => { it("Will remove the required key from an object", () => { const obj = { "foo": { "bar": [{ "moo": true, "baa": "ram you" }, { "two": {}, "three": [] }] } }; assert.strictEqual(obj.foo.bar[0].baa, "ram you", "Object has value"); const newObj = unSet(obj, "foo.bar.0.baa", {immutable: true}); // Check existing object is unchanged assert.strictEqual(obj.foo.bar[0].baa, "ram you", "Data integrity"); assert.notStrictEqual(newObj, obj, "Root object should be different"); assert.notStrictEqual(newObj.foo, obj.foo, "foo object should be different"); assert.notStrictEqual(newObj.foo.bar, obj.foo.bar, "foo.bar object should be different"); assert.notStrictEqual(newObj.foo.bar[0], obj.foo.bar[0], "foo.bar[0] object should be different"); // Check new object is changed assert.strictEqual(newObj.foo.bar[0].baa, undefined, "Object does not have value"); assert.strictEqual(newObj.foo.bar[0].hasOwnProperty("baa"), false, "Object does not have key"); // Check that new and old object do not share references to changed data assert.notStrictEqual(newObj, obj, "Root object should be different"); assert.notStrictEqual(newObj.foo, obj.foo, "foo object should be different"); assert.notStrictEqual(newObj.foo.bar, obj.foo.bar, "foo.bar object should be different"); assert.notStrictEqual(newObj.foo.bar[0], obj.foo.bar[0], "foo.bar[0] object should be different"); // Check that new and old object share references to unchanged data assert.strictEqual(newObj.foo.bar[1], obj.foo.bar[1], "foo.bar[1] object should be same"); }); it("Will correctly handle a path to nowhere", () => { const obj = { "foo": { "bar": [{ "moo": true, "baa": "ram you" }] } }; const newObj = unSet(obj, "foo.bar.2.baa", {immutable: true}); assert.strictEqual(obj.foo.bar.hasOwnProperty("2"), false, "Object does not have key"); assert.strictEqual(newObj, obj, "Root object is the same because nothing changed"); }); it("Is not vulnerable to __proto__ pollution", () => { const obj: any = {}; obj.__proto__.unsetPolluted = false; unSetImmutable(obj, "__proto__.unsetPolluted"); assert.strictEqual(obj.unsetPolluted, false, "The object prototype cannot be polluted"); }); }); describe("Escape at root", () => { it("Will remove the required key from an object", () => { const obj = { "foo@foo.com": { "bar": [{ "moo": true, "baa": "ram you" }] } }; assert.strictEqual(obj["foo@foo.com"].bar[0].baa, "ram you", "Object has value"); const newObj = unSet(obj, `${escape("foo@foo.com")}.bar.0.baa`); assert.strictEqual(obj["foo@foo.com"].bar[0].baa, undefined, "Object does not have value"); assert.strictEqual(obj["foo@foo.com"].bar[0].hasOwnProperty("baa"), false, "Object does not have key"); assert.strictEqual(newObj, obj, "Root object is the same"); }); it("Will correctly handle a path to nowhere", () => { const obj = { "foo@foo.com": { "bar": [{ "moo": true, "baa": "ram you" }] } }; const newObj = unSet(obj, `${escape("foo@foo.com")}.bar.2.baa`); assert.strictEqual(obj["foo@foo.com"].bar.hasOwnProperty("2"), false, "Object does not have key"); assert.strictEqual(newObj, obj, "Root object is the same"); }); }); describe("Escape in child", () => { it("Will remove the required key from an object", () => { const obj = { "foo": { "bar": [{ "moo": true, "baa@foo.com": "ram you" }] } }; assert.strictEqual(obj.foo.bar[0]["baa@foo.com"], "ram you", "Object has value"); const newObj = unSet(obj, `foo.bar.0.${escape("baa@foo.com")}`); assert.strictEqual(obj.foo.bar[0]["baa@foo.com"], undefined, "Object does not have value"); assert.strictEqual(obj.foo.bar[0].hasOwnProperty("baa@foo.com"), false, "Object does not have key"); assert.strictEqual(newObj, obj, "Root object is the same"); }); }); }); describe("match()", () => { describe("Positive tests", () => { it("Will return true for matching string", () => { const result = match("Bookshop1", "Bookshop1"); assert.strictEqual(result, true); }); it("Will return true for matching two objects with a matching query", () => { const result = match({"profile": {"_id": "Bookshop1"}}, {"profile": {"_id": "Bookshop1"}}); assert.strictEqual(result, true); }); it("Will return true for matching two objects with a matching query and extended source", () => { const result = match({test: "Bookshop1", foo: true}, {test: "Bookshop1"}); assert.strictEqual(result, true); }); it("Will match multiple keys and values", () => { const result = match({test: "Bookshop1", foo: true}, {test: "Bookshop1", foo: true}); assert.strictEqual(result, true); }); }); describe("Negative tests", () => { it("Will return false for non-matching string", () => { const result = match("Bookshop1", "Bookshop2"); assert.strictEqual(result, false); }); it("Will return false for matching two objects with a matching query", () => { const result = match({test: "Bookshop1"}, {test: "Bookshop2"}); assert.strictEqual(result, false); }); it("Will return false for matching two objects with a matching query and extended source", () => { const result = match({test: "Bookshop1", foo: true}, {test: "Bookshop2"}); assert.strictEqual(result, false); }); it("Will match multiple keys and values", () => { const result = match({test: "Bookshop1", foo: true}, {test: "Bookshop1", foo: false}); assert.strictEqual(result, false); }); }); }); describe("findPath()", () => { describe("Positive tests", () => { it("Will return the correct path for a root string", () => { const result: any = findPath("Bookshop1", "Bookshop1"); assert.deepEqual(result.path, [""]); }); it("Will return the correct path for an object nested string", () => { const result = findPath([{"_id": "Bookshop1"}], "Bookshop1"); assert.deepEqual(result.path, ["0._id"]); }); it("Will return the correct path for a nested equal object", () => { const result = findPath({"profile": {"_id": "Bookshop1"}}, {"profile": {"_id": "Bookshop1"}}); assert.deepEqual(result.path, [""]); }); it("Will return the correct path for an array nested string", () => { const result = findPath([{"_id": "Bookshop1"}], {"_id": "Bookshop1"}); assert.deepEqual(result.path, ["0"]); }); it("Will return the correct path for a single-level nested string", () => { const result = findPath({"profile": {"_id": "Bookshop1"}}, {"_id": "Bookshop1"}); assert.deepEqual(result.path, ["profile"]); }); it("Will return the correct path for a complex nested string", () => { const testObj = { "items": [{ "_id": 1, "title": "A night to remember", "stockedBy": ["Bookshop1", "Bookshop4"], "show": true }, { "_id": 2, "title": "Dream a little dream", "stockedBy": ["Bookshop1", "Bookshop2"], "show": true }] }; const result = findPath(testObj, {"show": true}); assert.deepEqual(result.path, ["items.0", "items.1"]); }); it("Will return the correct path for a complex nested object", () => { const testObj = { "items": [{ "_id": 1, "title": "A night to remember", "stockedBy": ["Bookshop1", "Bookshop4"] }, { "_id": 2, "title": "Dream a little dream", "stockedBy": ["Bookshop1", "Bookshop2"] }] }; const result = findPath(testObj, {_id: 2}); assert.deepEqual(result.path, ["items.1"]); }); it("Will return the correct path/s for very complex objects", () => { const data = { "_id": "3310a727ba01440", "label": "Project 1", "type": "project", "category": "root", "acceptsCategory": [ "object", "boolean" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "data": {}, "items": [ { "_id": "pagesFolder", "label": "Pages", "type": "folder", "category": "folder", "acceptsCategory": [ "page" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "isSelectable": false, "data": {}, "items": [ { "_id": "page1", "label": "Page 1", "type": "page", "category": "component", "acceptsCategory": [ "layout" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "data": { "schema": { "selectedItem": { "type": "Object", "required": false }, "hoveredItem": { "type": "Object", "required": false }, "currentTool": { "type": "String", "required": false, "default": "select" }, "lang": { "type": "String", "required": false, "default": "en" }, "user": { "type": "Object", "required": false, "default": {} } }, "stateToProps": { "pipeline": [ { "sessionState": { "type": "globalState", "fromId": "session", "fromMethod": "get" }, "setSessionState": { "type": "globalState", "fromId": "session", "fromMethod": "set" } }, { "lang": { "type": "pipelineFunction", "function": { "type": "Program", "ast": true } } } ] } }, "items": [ { "_id": "37df5e624ad4800", "label": "Layer", "isEditing": false, "type": "layer", "category": "layout", "acceptsCategory": [ "layout" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "data": { "display": { "type": "flex" }, "position": { "type": "none", "left": 0, "top": 0, "right": 0, "bottom": 0 }, "flex": { "direction": "row", "flexGrow": 1, "flexShrink": 1, "flexBasis": 0, "flexBasisUnit": "px" }, "isPureLayout": false }, "items": [ { "_id": "292a82d000fdb60", "label": "Layer", "isEditing": false, "type": "layer", "category": "layout", "acceptsCategory": [ "layout" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": true, "data": { "display": { "type": "flex" }, "position": { "type": "none", "left": 0, "top": 0, "right": 0, "bottom": 0 }, "flex": { "direction": "column", "flexGrow": 1, "flexShrink": 1, "flexBasis": 0, "flexBasisUnit": "px" }, "isPureLayout": false }, "items": [] }, { "_id": "22f4f59e9045740", "label": "Layer", "isEditing": false, "type": "layer", "category": "layout", "acceptsCategory": [ "layout" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "data": { "display": { "type": "flex" }, "position": { "type": "none", "left": 0, "top": 0, "right": 0, "bottom": 0 }, "flex": { "direction": "column", "flexGrow": 1, "flexShrink": 1, "flexBasis": 0, "flexBasisUnit": "px" }, "isPureLayout": false }, "items": [ { "_id": "375a0c74c911ce0", "label": "Layer", "isEditing": false, "type": "layer", "category": "layout", "acceptsCategory": [ "layout" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "data": { "display": { "type": "flex" }, "position": { "type": "none", "left": 0, "top": 0, "right": 0, "bottom": 0 }, "flex": { "direction": "row", "grow": 1, "shrink": 1, "basis": 0, "basisUnit": "px" }, "isPureLayout": false }, "items": [ { "_id": "396c2e9b36f31a0", "label": "CrumbBar", "isEditing": false, "type": "component", "component": "CrumbBar", "category": "component", "acceptsCategory": [], "acceptsChildren": false, "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": false, "data": { "display": { "type": "flex" }, "position": { "type": "none", "left": 0, "top": 0, "right": 0, "bottom": 0 }, "flex": { "direction": "row", "grow": 1, "shrink": 1, "basis": 0, "basisUnit": "px" }, "isPureLayout": false, "props": {} }, "items": [] } ] } ] } ] } ] } ] }, { "_id": "stateFolder", "label": "State", "type": "folder", "category": "folder", "acceptsCategory": [ "state" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": true, "isSelectable": false, "data": {}, "items": [ { "_id": "user", "label": "User State", "type": "globalState", "category": "state", "acceptsCategory": [], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": true, "data": { "schema": { "firstName": { "type": "String", "required": true }, "lastName": { "type": "String", "required": true }, "email": { "type": "String", "required": true }, "dob": { "type": "Date", "required": false } }, "initialState": {} } }, { "_id": "ui", "label": "UI State", "type": "globalState", "category": "state", "acceptsCategory": [], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": true, "data": { "schema": { "selectedItemId": { "type": "String", "default": "" }, "hoveredItemId": { "type": "String", "default": "" }, "currentTool": { "type": "String", "default": "select" } }, "initialState": {} } }, { "_id": "lang", "label": "Language State", "type": "globalState", "category": "state", "acceptsCategory": [], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": true, "data": { "schema": { "lang": { "type": "String", "default": "en" }, "available": { "type": "Array", "elementType": { "_id": { "type": "String", "required": true }, "label": { "type": "String", "required": true }, "enabled": { "type": "Boolean", "required": true } } } }, "initialState": { "lang": "en", "available": [ { "_id": "en", "label": "English", "enabled": true } ] } } } ] }, { "_id": "servicesFolder", "label": "Services", "type": "folder", "category": "folder", "acceptsCategory": [ "service" ], "isExpanded": true, "isEnabled": true, "isSelected": false, "isHovered": true, "isSelectable": false, "data": {}, "items": [ { "_id": "utils.js", "label": "utils.js", "type": "file", "category": "service", "middleTabId": "codeEditor", "isExpanded": true, "isEnabled":