From ca86f39688bdcdde6bfc10e4dd210ccb97848c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Fri, 6 Dec 2019 11:33:00 +0100 Subject: [PATCH 1/4] Allow path with empty string. Fix #3 --- src/Path.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Path.js b/src/Path.js index 36d3d33..6317d5c 100644 --- a/src/Path.js +++ b/src/Path.js @@ -331,7 +331,7 @@ const set = (obj, path, val, options = {}) => { } // No path string - if (!internalPath) { + if (internalPath == null) { return; } @@ -399,7 +399,7 @@ const unSet = (obj, path, options = {}, tracking = {}) => { } // No path string - if (!internalPath) { + if (internalPath == null) { return; } From bb7a570a57ef03ec866d175043c0017ebbb7515c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Fri, 6 Dec 2019 11:47:01 +0100 Subject: [PATCH 2/4] Empty lines with whitespaces --- src/Path.js | 332 +++++++++++++++---------------- tests/path.test.js | 484 ++++++++++++++++++++++----------------------- 2 files changed, 408 insertions(+), 408 deletions(-) diff --git a/src/Path.js b/src/Path.js index 6317d5c..e06e628 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,7 +168,7 @@ const numberToWildcard = (key) => { // The key is a number, convert to a wildcard return "$"; } - + return key; }; @@ -181,11 +181,11 @@ const clean = (str) => { if (!str) { return str; } - + if (str.substr(0, 1) === ".") { str = str.substr(1, str.length - 1); } - + return str; }; @@ -201,13 +201,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 +244,53 @@ 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 +300,7 @@ const get = (obj, path, defaultVal = undefined, options = {}) => { break; } } - + return objPart !== undefined ? objPart : defaultVal; }; @@ -317,48 +317,48 @@ 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 == 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 +367,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 +385,66 @@ 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 == 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 +463,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 +488,7 @@ const decouple = (obj, options = {}) => { if (!options.immutable) { return obj; } - + return _newInstance(obj); }; @@ -505,30 +505,30 @@ 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 +545,30 @@ 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 +583,55 @@ 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("."); }; @@ -651,23 +651,23 @@ const values = (obj, path, options = {}) => { 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 +689,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 +740,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 +783,7 @@ const join = (...args) => { if (item !== undefined && String(item)) { arr.push(item); } - + return arr; }, []).join("."); }; @@ -801,7 +801,7 @@ const joinEscaped = (...args) => { const escapedArgs = args.map((item) => { return escape(item); }); - + return join(...escapedArgs); }; @@ -814,10 +814,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 +829,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 +852,7 @@ const leafNodes = (obj, parentPath = "", objCache = []) => { } } } - + return paths; }; @@ -865,17 +865,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 +883,7 @@ const hasMatchingPathsInObject = function (testKeys, testObj) { } } } - + return result; }; @@ -897,24 +897,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 +925,7 @@ const countMatchingPathsInObject = (testKeys, testObj) => { } } } - + return { matchedKeys, matchedKeyCount, @@ -947,7 +947,7 @@ const type = (item) => { if (Array.isArray(item)) { return 'array'; } - + return typeof item; }; @@ -961,28 +961,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 +998,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 +1031,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 +1039,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 +1141,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 +1193,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 +1204,7 @@ const isEqual = (obj1, obj2, path, deep = false, strict = false) => { }) === -1; } } - + return (strict && val1 === val2) || (!strict && val1 == val2); }; diff --git a/tests/path.test.js b/tests/path.test.js index 1a69681..05ab46c 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,87 @@ 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"); }); }); - + 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 +313,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 +346,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 +366,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 +375,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 +405,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 +424,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 +445,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 +480,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 +589,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 +607,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 +651,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 +669,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 +721,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 +739,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 +783,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 +801,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 +849,7 @@ describe("Path", () => { "stockedBy": ["Bookshop1", "Bookshop4"] }] }; - + const testObj2 = { "items": [{ "_id": 2, @@ -858,12 +858,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 +879,7 @@ describe("Path", () => { }] }] }; - + const testObj2 = { "items": [{ "_id": 2, @@ -894,7 +894,7 @@ describe("Path", () => { }] }] }; - + const testObj3 = { "items": [{ "_id": 2, @@ -909,15 +909,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 +931,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 +941,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 +956,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 +967,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 +981,7 @@ describe("Path", () => { "name": "An array", "type": 42 }]; - + const obj2 = [{ "arr": [{ "id": 1 @@ -993,7 +993,7 @@ describe("Path", () => { "name": "An array", "type": 42 }]; - + const obj3 = [{ "arr": [{ "id": 1 @@ -1005,10 +1005,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 +1016,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 +1031,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj2 = { "rootArray": [{ "arr": [{ @@ -1045,7 +1045,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj3 = { "rootArray": [{ "arr": [{ @@ -1059,10 +1059,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 +1070,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 +1085,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj2 = { "rootArray": [{ "arr": [{ @@ -1099,7 +1099,7 @@ describe("Path", () => { "type": 42 }] }; - + const obj3 = { "rootArray": [{ "arr": [{ @@ -1113,10 +1113,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 +1124,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 +1263,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 +1277,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 +}); From 0fa62068d82f62ca28dcea8b8c4044113c7b206d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Fri, 6 Dec 2019 11:49:02 +0100 Subject: [PATCH 3/4] Just drop clean --- src/Path.js | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/src/Path.js b/src/Path.js index e06e628..2a27a27 100644 --- a/src/Path.js +++ b/src/Path.js @@ -172,23 +172,6 @@ const numberToWildcard = (key) => { 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; -}; - /** * Splits a path by period character, taking into account * escaped period characters. @@ -267,8 +250,6 @@ const get = (obj, path, defaultVal = undefined, options = {}) => { if (!internalPath) { return obj; } - internalPath = clean(internalPath); - // Path is not a string, throw error if (typeof internalPath !== "string") { @@ -334,8 +315,6 @@ const set = (obj, path, val, options = {}) => { if (internalPath == null) { return; } - internalPath = clean(internalPath); - // Path is not a string, throw error if (typeof internalPath !== "string") { @@ -403,8 +382,6 @@ const unSet = (obj, path, options = {}, tracking = {}) => { return; } - internalPath = clean(internalPath); - // Path is not a string, throw error if (typeof internalPath !== "string") { throw new Error("Path argument must be a string"); @@ -506,9 +483,6 @@ const pushVal = (obj, path, val, options = {}) => { return obj; } - // Clean the path - path = clean(path); - const pathParts = split(path); const part = pathParts.shift(); @@ -546,9 +520,6 @@ const pullVal = (obj, path, val, options = {}) => { return obj; } - // Clean the path - path = clean(path); - const pathParts = split(path); const part = pathParts.shift(); @@ -598,8 +569,6 @@ const furthest = (obj, path, options = {}) => { 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"); @@ -647,7 +616,7 @@ 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 = {}; @@ -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 +}; From 066fc300061cdf777f68479b9fedc7a0ee294900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Fri, 6 Dec 2019 11:49:13 +0100 Subject: [PATCH 4/4] Test paths with empty-string keys --- tests/path.test.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/path.test.js b/tests/path.test.js index 05ab46c..eb3baf0 100644 --- a/tests/path.test.js +++ b/tests/path.test.js @@ -270,6 +270,33 @@ describe("Path", () => { 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()", () => {