diff --git a/src/Path.js b/src/Path.js index 36d3d33..2a27a27 100644 --- a/src/Path.js +++ b/src/Path.js @@ -11,7 +11,7 @@ const _iterableKeys = (obj) => { if (valType === "object" || valType === "array") { arr.push(key); } - + return arr; }, []); }; @@ -29,25 +29,25 @@ const _iterableKeys = (obj) => { */ const _newInstance = (item, key = undefined, val = undefined) => { const objType = type(item); - + let newObj; - + if (objType === "object") { newObj = { ...item }; } - + if (objType === "array") { newObj = [ ...item ]; } - + if (key !== undefined) { newObj[key] = val; } - + return newObj; }; @@ -62,11 +62,11 @@ const _newInstance = (item, key = undefined, val = undefined) => { */ const up = (path, levels = 1) => { const parts = split(path); - + for (let i = 0; i < levels; i++) { parts.pop(); } - + return parts.join("."); }; @@ -81,11 +81,11 @@ const up = (path, levels = 1) => { */ const down = (path, levels = 1) => { const parts = split(path); - + for (let i = 0; i < levels; i++) { parts.shift(); } - + return parts.join("."); }; @@ -100,11 +100,11 @@ const down = (path, levels = 1) => { const pop = (path, levels = 1) => { const parts = split(path); let part; - + for (let i = 0; i < levels; i++) { part = parts.pop(); } - + return part || ""; }; @@ -132,11 +132,11 @@ const push = (path, val = "") => { const shift = (path, levels = 1) => { const parts = split(path); let part; - + for (let i = 0; i < levels; i++) { part = parts.shift(); } - + return part || ""; }; @@ -168,25 +168,8 @@ const numberToWildcard = (key) => { // The key is a number, convert to a wildcard return "$"; } - - return key; -}; -/** - * Removes leading period (.) from string and returns new string. - * @param {String} str The string to clean. - * @returns {*} The cleaned string. - */ -const clean = (str) => { - if (!str) { - return str; - } - - if (str.substr(0, 1) === ".") { - str = str.substr(1, str.length - 1); - } - - return str; + return key; }; /** @@ -201,13 +184,13 @@ const split = (path) => { // temporarily const escapedPath = path.replace(/\\\./g, "[--]"); const splitPath = escapedPath.split("."); - + // Loop the split path array and convert any escaped period // placeholders back to their real period characters for (let i = 0; i < splitPath.length; i++) { splitPath[i] = splitPath[i].replace(/\[--]/g, "."); } - + return splitPath; }; @@ -244,53 +227,51 @@ const escape = (str) => { const get = (obj, path, defaultVal = undefined, options = {}) => { let internalPath = path, objPart; - + if (path instanceof Array) { return path.map((individualPath) => { get(obj, individualPath, defaultVal, options); }); } - + options = { "transformRead": returnWhatWasGiven, "transformKey": returnWhatWasGiven, "transformWrite": returnWhatWasGiven, ...options }; - + // No object data, return undefined if (obj === undefined || obj === null) { return defaultVal; } - + // No path string, return the base obj if (!internalPath) { return obj; } - - internalPath = clean(internalPath); - + // Path is not a string, throw error if (typeof internalPath !== "string") { throw new Error("Path argument must be a string"); } - + // Path has no dot-notation, return key/value if (internalPath.indexOf(".") === -1) { return obj[internalPath]; } - + if (typeof obj !== "object") { return undefined; } - + const pathParts = split(internalPath); objPart = obj; - + for (let i = 0; i < pathParts.length; i++) { const pathPart = pathParts[i]; objPart = objPart[options.transformKey(pathPart)]; - + if (!objPart || typeof(objPart) !== "object") { if (i !== pathParts.length - 1) { // The path terminated in the object before we reached @@ -300,7 +281,7 @@ const get = (obj, path, defaultVal = undefined, options = {}) => { break; } } - + return objPart !== undefined ? objPart : defaultVal; }; @@ -317,48 +298,46 @@ const get = (obj, path, defaultVal = undefined, options = {}) => { const set = (obj, path, val, options = {}) => { let internalPath = path, objPart; - + options = { "transformRead": returnWhatWasGiven, "transformKey": returnWhatWasGiven, "transformWrite": returnWhatWasGiven, ...options }; - + // No object data if (obj === undefined || obj === null) { return; } - + // No path string - if (!internalPath) { + if (internalPath == null) { return; } - - internalPath = clean(internalPath); - + // Path is not a string, throw error if (typeof internalPath !== "string") { throw new Error("Path argument must be a string"); } - + if (typeof obj !== "object") { return; } - + // Path has no dot-notation, set key/value if (internalPath.indexOf(".") === -1) { obj = decouple(obj, options); obj[options.transformKey(internalPath)] = val; return obj; } - + const newObj = decouple(obj, options); const pathParts = split(internalPath); const pathPart = pathParts.shift(); const transformedPathPart = options.transformKey(pathPart); let childPart = newObj[transformedPathPart]; - + if (!childPart) { // Create an object or array on the path if (String(parseInt(transformedPathPart, 10)) === transformedPathPart) { @@ -367,12 +346,12 @@ const set = (obj, path, val, options = {}) => { } else { newObj[transformedPathPart] = {}; } - + objPart = newObj[transformedPathPart]; } else { objPart = childPart; } - + return set(newObj, transformedPathPart, set(objPart, pathParts.join('.'), val, options), options); }; @@ -385,66 +364,64 @@ const set = (obj, path, val, options = {}) => { */ const unSet = (obj, path, options = {}, tracking = {}) => { let internalPath = path; - + options = { "transformRead": returnWhatWasGiven, "transformKey": returnWhatWasGiven, "transformWrite": returnWhatWasGiven, ...options }; - + // No object data if (obj === undefined || obj === null) { return; } - + // No path string - if (!internalPath) { + if (internalPath == null) { return; } - - internalPath = clean(internalPath); - + // Path is not a string, throw error if (typeof internalPath !== "string") { throw new Error("Path argument must be a string"); } - + if (typeof obj !== "object") { return; } - + const newObj = decouple(obj, options); - + // Path has no dot-notation, set key/value if (internalPath.indexOf(".") === -1) { if (newObj.hasOwnProperty(internalPath)) { delete newObj[options.transformKey(internalPath)]; return newObj; } - + tracking.returnOriginal = true; return obj; } - - + + const pathParts = split(internalPath); const pathPart = pathParts.shift(); const transformedPathPart = options.transformKey(pathPart); let childPart = newObj[transformedPathPart]; - + if (!childPart) { // No child part available, nothing to unset! tracking.returnOriginal = true; return obj; } - + newObj[transformedPathPart] = unSet(childPart, pathParts.join('.'), options, tracking); - + if (tracking.returnOriginal) { return obj; } - + return newObj; }; @@ -463,14 +440,14 @@ const unSet = (obj, path, options = {}, tracking = {}) => { */ const update = (obj, updateData, options = {}) => { let newObj = obj; - + for (let path in updateData) { if (updateData.hasOwnProperty(path)) { const data = updateData[path]; newObj = set(newObj, path, data, options); } } - + return newObj; }; @@ -488,7 +465,7 @@ const decouple = (obj, options = {}) => { if (!options.immutable) { return obj; } - + return _newInstance(obj); }; @@ -505,30 +482,27 @@ const pushVal = (obj, path, val, options = {}) => { if (obj === undefined || obj === null || path === undefined) { return obj; } - - // Clean the path - path = clean(path); - + const pathParts = split(path); const part = pathParts.shift(); - + if (pathParts.length) { // Generate the path part in the object if it does not already exist obj[part] = decouple(obj[part], options) || {}; - + // Recurse pushVal(obj[part], pathParts.join("."), val); } else { // We have found the target array, push the value obj[part] = decouple(obj[part], options) || []; - + if (obj[part] instanceof Array) { obj[part].push(val); } else { throw("Cannot push to a path whose leaf node is not an array!"); } } - + return decouple(obj, options); }; @@ -545,30 +519,27 @@ const pullVal = (obj, path, val, options = {}) => { if (obj === undefined || obj === null || path === undefined) { return obj; } - - // Clean the path - path = clean(path); - + const pathParts = split(path); const part = pathParts.shift(); - + if (pathParts.length) { // Generate the path part in the object if it does not already exist obj[part] = decouple(obj[part], options) || {}; - + // Recurse pushVal(obj[part], pathParts.join("."), val); } else { // We have found the target array, push the value obj[part] = decouple(obj[part], options) || []; - + if (obj[part] instanceof Array) { obj[part].push(val); } else { throw("Cannot push to a path whose leaf node is not an array!"); } } - + return decouple(obj, options); }; @@ -583,55 +554,53 @@ const pullVal = (obj, path, val, options = {}) => { const furthest = (obj, path, options = {}) => { let internalPath = path, objPart; - + options = { "transformRead": returnWhatWasGiven, "transformKey": wildcardToZero, // Any path that has a wildcard will essentially check the first array item to continue down the tree "transformWrite": returnWhatWasGiven, ...options }; - + const finalPath = []; - + // No path string, return the base obj if (!internalPath) { return finalPath.join("."); } - - internalPath = clean(internalPath); - + // Path is not a string, throw error if (typeof internalPath !== "string") { throw new Error("Path argument must be a string"); } - + if (typeof obj !== "object") { return finalPath.join("."); } - + // Path has no dot-notation, return key/value if (internalPath.indexOf(".") === -1) { if (obj[internalPath] !== undefined) { return internalPath; } - + return finalPath.join("."); } - + const pathParts = split(internalPath); objPart = obj; - + for (let i = 0; i < pathParts.length; i++) { const pathPart = pathParts[i]; objPart = objPart[options.transformKey(pathPart)]; - + if (objPart === undefined) { break; } - + finalPath.push(pathPart); } - + return finalPath.join("."); }; @@ -647,27 +616,27 @@ const furthest = (obj, path, options = {}) => { * @returns {Object|Array} The result of the traversal. */ const values = (obj, path, options = {}) => { - const internalPath = clean(path); + const internalPath = path; const pathParts = split(internalPath); const currentPath = []; const valueData = {}; - + options = { "transformRead": returnWhatWasGiven, "transformKey": returnWhatWasGiven, "transformWrite": returnWhatWasGiven, ...options }; - + for (let i = 0; i < pathParts.length; i++) { const pathPart = options.transformKey(pathParts[i]); currentPath.push(pathPart); - + const tmpPath = currentPath.join("."); - + valueData[tmpPath] = get(obj, tmpPath); } - + return valueData; }; @@ -689,36 +658,36 @@ const flatten = (obj, finalArr = [], parentPath = "", options = {}, objCache = [ "transformWrite": returnWhatWasGiven, ...options }; - + const transformedObj = options.transformRead(obj); - + // Check that we haven't visited this object before (avoid infinite recursion) if (objCache.indexOf(transformedObj) > -1) { return finalArr; } - + // Add object to cache to make sure we don't traverse it twice objCache.push(transformedObj); - + const currentPath = (i) => { const tKey = options.transformKey(i); return parentPath ? parentPath + "." + tKey : tKey; }; - + for (const i in transformedObj) { if (transformedObj.hasOwnProperty(i)) { if (options.ignore && options.ignore.test(i)) { continue; } - + if (typeof transformedObj[i] === "object") { flatten(transformedObj[i], finalArr, currentPath(i), options, objCache); } - + finalArr.push(currentPath(i)); } } - + return finalArr; }; @@ -740,32 +709,32 @@ const flattenValues = (obj, finalObj = {}, parentPath = "", options = {}, objCac "transformWrite": returnWhatWasGiven, ...options }; - + const transformedObj = options.transformRead(obj); - + // Check that we haven't visited this object before (avoid infinite recursion) if (objCache.indexOf(transformedObj) > -1) { return finalObj; } - + // Add object to cache to make sure we don't traverse it twice objCache.push(transformedObj); - + const currentPath = (i) => { const tKey = options.transformKey(i); return parentPath ? parentPath + "." + tKey : tKey; }; - + for (const i in transformedObj) { if (transformedObj.hasOwnProperty(i)) { if (typeof transformedObj[i] === "object") { flattenValues(transformedObj[i], finalObj, currentPath(i), options, objCache); } - + finalObj[currentPath(i)] = options.transformWrite(transformedObj[i]); } } - + return finalObj; }; @@ -783,7 +752,7 @@ const join = (...args) => { if (item !== undefined && String(item)) { arr.push(item); } - + return arr; }, []).join("."); }; @@ -801,7 +770,7 @@ const joinEscaped = (...args) => { const escapedArgs = args.map((item) => { return escape(item); }); - + return join(...escapedArgs); }; @@ -814,10 +783,10 @@ const joinEscaped = (...args) => { */ const countLeafNodes = (obj, objCache = []) => { let totalKeys = 0; - + // Add object to cache to make sure we don't traverse it twice objCache.push(obj); - + for (const i in obj) { if (obj.hasOwnProperty(i)) { if (obj[i] !== undefined) { @@ -829,21 +798,21 @@ const countLeafNodes = (obj, objCache = []) => { } } } - + return totalKeys; }; const leafNodes = (obj, parentPath = "", objCache = []) => { const paths = []; - + // Add object to cache to make sure we don't traverse it twice objCache.push(obj); - + for (const i in obj) { if (obj.hasOwnProperty(i)) { if (obj[i] !== undefined) { const currentPath = join(parentPath, i); - + if (typeof obj[i] !== "object" || objCache.indexOf(obj[i]) > -1) { paths.push(currentPath); } else { @@ -852,7 +821,7 @@ const leafNodes = (obj, parentPath = "", objCache = []) => { } } } - + return paths; }; @@ -865,17 +834,17 @@ const leafNodes = (obj, parentPath = "", objCache = []) => { */ const hasMatchingPathsInObject = function (testKeys, testObj) { let result = true; - + for (const i in testKeys) { if (testKeys.hasOwnProperty(i)) { if (testObj[i] === undefined) { return false; } - + if (typeof testKeys[i] === "object") { // Recurse object result = hasMatchingPathsInObject(testKeys[i], testObj[i]); - + // Should we exit early? if (!result) { return false; @@ -883,7 +852,7 @@ const hasMatchingPathsInObject = function (testKeys, testObj) { } } } - + return result; }; @@ -897,24 +866,24 @@ const hasMatchingPathsInObject = function (testKeys, testObj) { */ const countMatchingPathsInObject = (testKeys, testObj) => { const matchedKeys = {}; - + let matchData, matchedKeyCount = 0, totalKeyCount = 0; - + for (const i in testObj) { if (testObj.hasOwnProperty(i)) { if (typeof testObj[i] === "object") { // The test / query object key is an object, recurse matchData = countMatchingPathsInObject(testKeys[i], testObj[i]); - + matchedKeys[i] = matchData.matchedKeys; totalKeyCount += matchData.totalKeyCount; matchedKeyCount += matchData.matchedKeyCount; } else { // The test / query object has a property that is not an object so add it as a key totalKeyCount++; - + // Check if the test keys also have this key and it is also not an object if (testKeys && testKeys[i] && typeof testKeys[i] !== "object") { matchedKeys[i] = true; @@ -925,7 +894,7 @@ const countMatchingPathsInObject = (testKeys, testObj) => { } } } - + return { matchedKeys, matchedKeyCount, @@ -947,7 +916,7 @@ const type = (item) => { if (Array.isArray(item)) { return 'array'; } - + return typeof item; }; @@ -961,28 +930,28 @@ const type = (item) => { const match = (source, query) => { const sourceType = typeof source; const queryType = typeof query; - + if (sourceType !== queryType) { return false; } - + if (sourceType !== "object") { // Simple test return source === query; } - + // The source is an object-like (array or object) structure const entries = Object.entries(query); - + const foundNonMatch = entries.find(([key, val]) => { // Recurse if type is array or object if (typeof val === "object") { return !match(source[key], val); } - + return source[key] !== val; }); - + return !foundNonMatch; }; @@ -998,24 +967,24 @@ const match = (source, query) => { const findPath = (source, query, parentPath = "") => { const resultArr = []; const sourceType = typeof source; - + if (match(source, query)) { resultArr.push(parentPath); } - + if (sourceType === "object") { const entries = Object.entries(source); - + entries.forEach(([key, val]) => { // Recurse down object to find more instances const result = findPath(val, query, join(parentPath, key)); - + if (result.match) { resultArr.push(...result.path); } }); } - + return {match: resultArr.length > 0, path: resultArr}; }; @@ -1031,7 +1000,7 @@ const findPath = (source, query, parentPath = "") => { const findOnePath = (source, query, parentPath = "") => { const sourceType = type(source); const queryType = type(query); - + // Early exits if (source === query) { return {match: true, path: parentPath}; @@ -1039,97 +1008,97 @@ const findOnePath = (source, query, parentPath = "") => { if (source === undefined && query !== undefined) { return {match: false}; } - + if (sourceType === "array") { // Loop source and compare each item with query for (let i = 0; i < source.length; i++) { const result = findOnePath(source[i], query, join(parentPath, String(i))); - + if (result.match) { return result; } } - + return {match: false}; } - + if (sourceType === "object" && queryType === "object") { const keys = Object.keys(query); - + let result = { match: false }; - + for (let i = 0; i < keys.length; i++) { const key = keys[i]; result = findOnePath(source[key], query[key], join(parentPath, key)); - + if (result.match) { return {match: true, path: parentPath}; } } - + // If we don't have a match, check if we should drill down if (!result.match) { const subSearch = _iterableKeys(source); - + // Drill down into each sub-object to see if we have a match for (let i = 0; i < subSearch.length; i++) { const key = subSearch[i]; const subSearchResult = findOnePath(source[key], query, join(parentPath, key)); - + if (subSearchResult.match) { return subSearchResult; } } } - + // All keys in the query matched the source, return our current path return {match: false}; } - + if (sourceType === "object" && (queryType === "string" || queryType === "number" || queryType === "null")) { const keys = Object.keys(source); let result = { match: false }; - + for (let i = 0; i < keys.length; i++) { const key = keys[i]; - + result = findOnePath(source[key], query, join(parentPath, key)); - + // If we find a single non-matching key, return false if (result.match) { return result; } } - + // If we don't have a match, check if we should drill down if (!result.match) { const subSearch = _iterableKeys(source); - + // Drill down into each sub-object to see if we have a match for (let i = 0; i < subSearch.length; i++) { const key = subSearch[i]; const subSearchResult = findOnePath(source[key], query, join(parentPath, key)); - + if (subSearchResult.match) { return subSearchResult; } } } - + // All keys in the query matched the source, return our current path return result; } - + return {match: false}; }; const diff = (obj1, obj2, path = "", strict = false, parentPath = "") => { const paths = []; - + if (path instanceof Array) { // We were given an array of paths, check each path return path.reduce((arr, individualPath) => { @@ -1141,30 +1110,30 @@ const diff = (obj1, obj2, path = "", strict = false, parentPath = "") => { if (result && result.length) { arr.push(...result); } - + return arr; }, []); } - + const currentPath = join(parentPath, path); const val1 = get(obj1, path); const val2 = get(obj2, path); - + if (typeof val1 === "object") { return Object.keys(val1).reduce((arr, key) => { const result = diff(val1, val2, key, strict, currentPath); if (result && result.length) { arr.push(...result); } - + return arr; }, []); } - + if ((strict && val1 !== val2) || (!strict && val1 != val2)) { paths.push(currentPath); } - + return paths; }; @@ -1193,10 +1162,10 @@ const isEqual = (obj1, obj2, path, deep = false, strict = false) => { return isNotEqual(obj1, obj2, individualPath, deep, strict); }) === -1; } - + const val1 = get(obj1, path); const val2 = get(obj2, path); - + if (deep) { if (typeof val1 === "object") { return Object.keys(val1).findIndex((key) => { @@ -1204,7 +1173,7 @@ const isEqual = (obj1, obj2, path, deep = false, strict = false) => { }) === -1; } } - + return (strict && val1 === val2) || (!strict && val1 == val2); }; @@ -1281,7 +1250,6 @@ const updateImmutable = (obj, updateData, options) => { module.exports = { wildcardToZero, numberToWildcard, - clean, split, escape, get, @@ -1316,4 +1284,4 @@ module.exports = { diff, update, updateImmutable -}; \ No newline at end of file +}; diff --git a/tests/path.test.js b/tests/path.test.js index 1a69681..eb3baf0 100644 --- a/tests/path.test.js +++ b/tests/path.test.js @@ -32,28 +32,28 @@ describe("Path", () => { "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 = { "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"); @@ -61,7 +61,7 @@ describe("Path", () => { 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 = { @@ -71,12 +71,12 @@ describe("Path", () => { } }] }; - + 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 = { "obj": [{ @@ -85,16 +85,16 @@ describe("Path", () => { } }] }; - + // Create an infinite recursion obj.obj[0].other.obj = obj; - + 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 = { @@ -104,9 +104,9 @@ describe("Path", () => { } }] }; - + 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"); @@ -114,7 +114,7 @@ describe("Path", () => { 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 = { "obj": [{ @@ -123,12 +123,12 @@ describe("Path", () => { } }] }; - + // 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"); @@ -138,7 +138,7 @@ describe("Path", () => { 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 = { @@ -148,16 +148,16 @@ describe("Path", () => { } }] }; - + 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 handle an infinite recursive structure", () => { const obj = { "obj": [{ @@ -166,12 +166,12 @@ describe("Path", () => { } }] }; - + // 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"); @@ -181,7 +181,7 @@ describe("Path", () => { 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 = { @@ -189,32 +189,32 @@ describe("Path", () => { "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("get()", () => { it("Can get a field value from an object path", () => { const obj = { @@ -222,87 +222,114 @@ describe("Path", () => { "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 a path with empty string components", () => { + const obj = { + "a": { + "": 1 + }, + "": { + "val": 2, + "obj": { + "": 3 + }, + "": 4, + "b": { + "": { + "": 5 + } + } + } + }; + + assert.strictEqual(get(obj, "a."), 1, "The value was retrieved correctly"); + assert.strictEqual(get(obj, ".val"), 2, "The value was retrieved correctly"); + assert.strictEqual(get(obj, ".obj."), 3, "The value was retrieved correctly"); + assert.strictEqual(get(obj, "."), 4, "The value was retrieved correctly"); + assert.strictEqual(get(obj, ".b.."), 5, "The value was retrieved correctly"); + + }); }); - + describe("set()", () => { it("Can set a value on the passed object at the correct path with auto-created objects", () => { const obj = {}; - + 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 = {}; - + 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 = { "foo": { @@ -313,25 +340,25 @@ describe("Path", () => { } } }; - + 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"); }); }); - + describe("setImmutable()", () => { it("Can de-reference all objects which contain a change (useful for state management systems)", () => { const obj = { @@ -346,19 +373,19 @@ describe("Path", () => { } } }; - + 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"); @@ -366,7 +393,7 @@ describe("Path", () => { 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"); @@ -375,25 +402,25 @@ describe("Path", () => { 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"); }); }); - + describe("unSet()", () => { describe("Mutable", () => { it("Will remove the required key from an object", () => { @@ -405,16 +432,16 @@ describe("Path", () => { }] } }; - + 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": { @@ -424,14 +451,14 @@ describe("Path", () => { }] } }; - + 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"); }); }); - + describe("Immutable", () => { it("Will remove the required key from an object", () => { const obj = { @@ -445,32 +472,32 @@ describe("Path", () => { }] } }; - + 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": { @@ -480,101 +507,101 @@ describe("Path", () => { }] } }; - + 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"); }); }); }); - + 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 = 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": [{ @@ -589,12 +616,12 @@ describe("Path", () => { "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": [{ @@ -607,38 +634,38 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findPath(testObj, {_id: 2}); - + assert.deepEqual(result.path, ["items.1"]); }); }); - + describe("Negative tests", () => { it("Will return the correct path for a root string", () => { const result = findPath("Bookshop1", "Bookshop2"); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for non-matching a nested equal object", () => { const result = findPath({"profile": {"_id": "Bookshop1"}}, {"profile": {"_id": "Bookshop2"}}); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for an array nested string", () => { const result = findPath([{"_id": "Bookshop1"}], {"_id": "Bookshop2"}); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for a single-level nested string", () => { const result = findPath({"profile": {"_id": "Bookshop1"}}, {"_id": "Bookshop2"}); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for a complex nested string", () => { const testObj = { "items": [{ @@ -651,12 +678,12 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findPath(testObj, "Bookshop3"); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for a complex nested object", () => { const testObj = { "items": [{ @@ -669,46 +696,46 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findPath(testObj, {_id: 3}); - + assert.strictEqual(result.match, false); }); }); }); - + describe("findOnePath()", () => { describe("Positive tests", () => { it("Will return the correct path for a root string", () => { const result = findOnePath("Bookshop1", "Bookshop1"); - + assert.strictEqual(result.path, ""); }); - + it("Will return the correct path for an object nested string", () => { const result = findOnePath([{"_id": "Bookshop1"}], "Bookshop1"); - + assert.strictEqual(result.path, "0._id"); }); - + it("Will return the correct path for a nested equal object", () => { const result = findOnePath({"profile": {"_id": "Bookshop1"}}, {"profile": {"_id": "Bookshop1"}}); - + assert.strictEqual(result.path, ""); }); - + it("Will return the correct path for an array nested string", () => { const result = findOnePath([{"_id": "Bookshop1"}], {"_id": "Bookshop1"}); - + assert.strictEqual(result.path, "0"); }); - + it("Will return the correct path for a single-level nested string", () => { const result = findOnePath({"profile": {"_id": "Bookshop1"}}, {"_id": "Bookshop1"}); - + assert.strictEqual(result.path, "profile"); }); - + it("Will return the correct path for a complex nested string", () => { const testObj = { "items": [{ @@ -721,12 +748,12 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findOnePath(testObj, "Bookshop1"); - + assert.strictEqual(result.path, "items.0.stockedBy.0"); }); - + it("Will return the correct path for a complex nested object", () => { const testObj = { "items": [{ @@ -739,38 +766,38 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findOnePath(testObj, {_id: 2}); - + assert.strictEqual(result.path, "items.1"); }); }); - + describe("Negative tests", () => { it("Will return the correct path for a root string", () => { const result = findOnePath("Bookshop1", "Bookshop2"); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for non-matching a nested equal object", () => { const result = findOnePath({"profile": {"_id": "Bookshop1"}}, {"profile": {"_id": "Bookshop2"}}); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for an array nested string", () => { const result = findOnePath([{"_id": "Bookshop1"}], {"_id": "Bookshop2"}); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for a single-level nested string", () => { const result = findOnePath({"profile": {"_id": "Bookshop1"}}, {"_id": "Bookshop2"}); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for a complex nested string", () => { const testObj = { "items": [{ @@ -783,12 +810,12 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findOnePath(testObj, "Bookshop3"); - + assert.strictEqual(result.match, false); }); - + it("Will return the correct path for a complex nested object", () => { const testObj = { "items": [{ @@ -801,45 +828,45 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = findOnePath(testObj, {_id: 3}); - + assert.strictEqual(result.match, false); }); }); }); - + describe("isNotEqual()", () => { it("Will return the correct result for a root string", () => { const result = isNotEqual("Bookshop1", "Bookshop1", ""); - + assert.strictEqual(result, false); }); - + it("Will return the correct result for an object nested string", () => { const result = isNotEqual([{"_id": "Bookshop1"}], "Bookshop1", "0._id"); - + assert.strictEqual(result, true); }); - + it("Will return the correct result for a nested equal object", () => { const result = isNotEqual({"profile": {"_id": "Bookshop1"}}, {"profile": {"_id": "Bookshop1"}}, "profile._id"); - + assert.strictEqual(result, false); }); - + it("Will return the correct result for an array nested string", () => { const result = isNotEqual([{"_id": "Bookshop1"}], [{"_id": "Bookshop1"}], "0._id"); - + assert.strictEqual(result, false); }); - + it("Will return the correct result for a single-level nested string", () => { const result = isNotEqual({"profile": {"_id": "Bookshop1"}}, {"_id": "Bookshop1"}, "profile._id"); - + assert.strictEqual(result, true); }); - + it("Will return the correct result for a complex nested string", () => { const testObj1 = { "items": [{ @@ -849,7 +876,7 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop4"] }] }; - + const testObj2 = { "items": [{ "_id": 2, @@ -858,12 +885,12 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop2"] }] }; - + const result = isNotEqual(testObj1, testObj2, ["items.0.author", "items.0.stockedBy.0"]); - + assert.strictEqual(result, false); }); - + it("Will return the correct result when checking deep equality with positive outcome", () => { const testObj1 = { "items": [{ @@ -879,7 +906,7 @@ describe("Path", () => { }] }] }; - + const testObj2 = { "items": [{ "_id": 2, @@ -894,7 +921,7 @@ describe("Path", () => { }] }] }; - + const testObj3 = { "items": [{ "_id": 2, @@ -909,15 +936,15 @@ describe("Path", () => { }] }] }; - + const result1 = isNotEqual(testObj1, testObj2, ["items"], true); const result2 = isNotEqual(testObj1, testObj3, ["items"], true); - + assert.strictEqual(result1, false); assert.strictEqual(result2, true); }); }); - + describe("leafNodes()", () => { it("Will return an array of paths for all leafs in an array structure", () => { const structure = [{ @@ -931,9 +958,9 @@ describe("Path", () => { "name": "An array", "type": 42 }]; - + const result = leafNodes(structure); - + assert.strictEqual(result instanceof Array, true, "The result is an array"); assert.strictEqual(result[0], "0.arr.0.id", "The result value is correct"); assert.strictEqual(result[1], "0.arr.1.id", "The result value is correct"); @@ -941,7 +968,7 @@ describe("Path", () => { assert.strictEqual(result[3], "0.name", "The result value is correct"); assert.strictEqual(result[4], "0.type", "The result value is correct"); }); - + it("Will return an array of paths for all leafs in an object structure", () => { const structure = { "rootArray": [{ @@ -956,9 +983,9 @@ describe("Path", () => { "type": 42 }] }; - + const result = leafNodes(structure); - + assert.strictEqual(result instanceof Array, true, "The result is an array"); assert.strictEqual(result[0], "rootArray.0.arr.0.id", "The result value is correct"); assert.strictEqual(result[1], "rootArray.0.arr.1.id", "The result value is correct"); @@ -967,7 +994,7 @@ describe("Path", () => { assert.strictEqual(result[4], "rootArray.0.type", "The result value is correct"); }); }); - + describe("diff()", () => { it("Will return an array of paths for all leafs in an array structure that differ from the other structure", () => { const obj1 = [{ @@ -981,7 +1008,7 @@ describe("Path", () => { "name": "An array", "type": 42 }]; - + const obj2 = [{ "arr": [{ "id": 1 @@ -993,7 +1020,7 @@ describe("Path", () => { "name": "An array", "type": 42 }]; - + const obj3 = [{ "arr": [{ "id": 1 @@ -1005,10 +1032,10 @@ describe("Path", () => { "name": "An array", "type": 43 }]; - + const result1 = diff(obj1, obj2); const result2 = diff(obj1, obj3); - + assert.strictEqual(result1 instanceof Array, true, "The result is an array"); assert.strictEqual(result1.length, 0, "The result value is correct"); assert.strictEqual(result2 instanceof Array, true, "The result is an array"); @@ -1016,7 +1043,7 @@ describe("Path", () => { assert.strictEqual(result2[0], "0.arr.1.id", "The result value is correct"); assert.strictEqual(result2[1], "0.type", "The result value is correct"); }); - + it("Will return an array of paths for all leafs in an object structure that differ from the other structure", () => { const obj1 = { "rootArray": [{ @@ -1031,7 +1058,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj2 = { "rootArray": [{ "arr": [{ @@ -1045,7 +1072,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj3 = { "rootArray": [{ "arr": [{ @@ -1059,10 +1086,10 @@ describe("Path", () => { "type": 43 }] }; - + const result1 = diff(obj1, obj2); const result2 = diff(obj1, obj3); - + assert.strictEqual(result1 instanceof Array, true, "The result is an array"); assert.strictEqual(result1.length, 0, "The result value is correct"); assert.strictEqual(result2 instanceof Array, true, "The result is an array"); @@ -1070,7 +1097,7 @@ describe("Path", () => { assert.strictEqual(result2[0], "rootArray.0.arr.1.id", "The result value is correct"); assert.strictEqual(result2[1], "rootArray.0.type", "The result value is correct"); }); - + it("Will return an array of paths for all leafs in an object structure under a specific path that differ from the other structure", () => { const obj1 = { "rootArray": [{ @@ -1085,7 +1112,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj2 = { "rootArray": [{ "arr": [{ @@ -1099,7 +1126,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj3 = { "rootArray": [{ "arr": [{ @@ -1113,10 +1140,10 @@ describe("Path", () => { "type": 43 }] }; - + const result1 = diff(obj1, obj2, "rootArray.0.arr"); const result2 = diff(obj1, obj3, "rootArray.0.arr"); - + assert.strictEqual(result1 instanceof Array, true, "The result is an array"); assert.strictEqual(result1.length, 0, "The result value is correct"); assert.strictEqual(result2 instanceof Array, true, "The result is an array"); @@ -1124,138 +1151,138 @@ describe("Path", () => { assert.strictEqual(result2[0], "rootArray.0.arr.1.id", "The result value is correct"); }); }); - + describe("pushVal()", () => { it("Will push a value to an array at the given path", () => { const obj = { "foo": [] }; - + assert.strictEqual(obj.foo.length, 0, "The array is empty"); - + pushVal(obj, "foo", "New val"); - + assert.strictEqual(obj.foo.length, 1, "The array is no longer empty"); assert.strictEqual(obj.foo[0], "New val", "The value is correct"); }); - + it("Will push a value to an array that doesn't yet exist at the given path", () => { const obj = {}; - + assert.strictEqual(obj.foo, undefined, "The key has no array yet"); - + pushVal(obj, "foo", "New val"); - + assert.strictEqual(obj.foo instanceof Array, true, "The array was created"); assert.strictEqual(obj.foo.length, 1, "The array is no longer empty"); assert.strictEqual(obj.foo[0], "New val", "The value is correct"); }); - + it("Will push a value to an array at the given path with immutability", () => { const obj = { "foo": [] }; - + const oldFoo = obj.foo; const newObj = pushVal(obj, "foo", "New val", {immutable: true}); - + assert.strictEqual(obj !== newObj, true, "The old obj and new obj are not the same reference"); assert.strictEqual(oldFoo !== newObj.foo, true, "The old array and new array are not the same reference"); }); }); - + describe("up()", () => { it("Returns the new path correctly", () => { const path = "foo.bar.thing"; const result = up(path); - + assert.strictEqual(result, "foo.bar", "The path is correct"); }); - + it("Returns the new path correctly when levels are specified", () => { const path = "foo.bar.thing"; const result = up(path, 2); - + assert.strictEqual(result, "foo", "The path is correct"); }); - + it("Returns the new path correctly when too many levels are specified", () => { const path = "foo.bar.thing"; const result = up(path, 5); - + assert.strictEqual(result, "", "The path is correct"); }); }); - + describe("down()", () => { it("Returns the new path correctly", () => { const path = "foo.bar.thing"; const result = down(path); - + assert.strictEqual(result, "bar.thing", "The path is correct"); }); - + it("Returns the new path correctly when levels are specified", () => { const path = "foo.bar.thing"; const result = down(path, 2); - + assert.strictEqual(result, "thing", "The path is correct"); }); - + it("Returns the new path correctly when too many levels are specified", () => { const path = "foo.bar.thing"; const result = down(path, 5); - + assert.strictEqual(result, "", "The path is correct"); }); }); - + describe("pop()", () => { it("Returns the new path correctly", () => { const path = "foo.bar.thing"; const result = pop(path); - + assert.strictEqual(result, "thing", "The path is correct"); }); - + it("Returns the new path correctly when levels are specified", () => { const path = "foo.bar.thing"; const result = pop(path, 2); - + assert.strictEqual(result, "bar", "The path is correct"); }); - + it("Returns the new path correctly when too many levels are specified", () => { const path = "foo.bar.thing"; const result = pop(path, 5); - + assert.strictEqual(result, "", "The path is correct"); }); }); - + describe("shift()", () => { it("Returns the new path correctly", () => { const path = "foo.bar.thing"; const result = shift(path); - + assert.strictEqual(result, "foo", "The path is correct"); }); - + it("Returns the new path correctly when levels are specified", () => { const path = "foo.bar.thing"; const result = shift(path, 2); - + assert.strictEqual(result, "bar", "The path is correct"); }); - + it("Returns the new path correctly when too many levels are specified", () => { const path = "foo.bar.thing"; const result = shift(path, 5); - + assert.strictEqual(result, "", "The path is correct"); }); }); - + describe("update()", () => { it("Applies the correct values to multiple paths", () => { const obj = {}; @@ -1263,13 +1290,13 @@ describe("Path", () => { "foo": "fooVal", "bar.one.two": "Three" }); - + assert.strictEqual(resultObj.foo, "fooVal", "Value is correct for single noted path"); assert.strictEqual(resultObj.bar.one.two, "Three", "Value is correct for multi noted path"); assert.strictEqual(obj, resultObj, "The changes were made by reference"); }); }); - + describe("updateImmutable()", () => { it("Applies the correct values to multiple paths", () => { const obj = {}; @@ -1277,10 +1304,10 @@ describe("Path", () => { "foo": "fooVal", "bar.one.two": "Three" }); - + assert.strictEqual(resultObj.foo, "fooVal", "Value is correct for single noted path"); assert.strictEqual(resultObj.bar.one.two, "Three", "Value is correct for multi noted path"); assert.notStrictEqual(obj, resultObj, "The changes were made by value"); }); }); -}); \ No newline at end of file +});