From dd94406047d11484c1edc85658e35470945a947e Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Sun, 20 Jan 2019 16:01:51 +0300 Subject: [PATCH 1/7] add some tests for checkIsExistsData --- src/tests/checkIsExists.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tests/checkIsExists.test.js b/src/tests/checkIsExists.test.js index 7b8def2..3f7b218 100644 --- a/src/tests/checkIsExists.test.js +++ b/src/tests/checkIsExists.test.js @@ -2,6 +2,10 @@ import { checkIsExists } from '../index'; const chekIsExistsData = [ + [null, 'a', false], + [[10], '0', true], + [[{ a: 10 }], '0.a', true], + [[{ a: 10 }], '1.a', false], [{ a: 10 }, 'a', true], [{ a: 10 }, 'a.aa', false], [{ a: 10 }, 'a2', false], @@ -24,6 +28,8 @@ const chekIsExistsData = [ [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[2]', true], [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[3]', false], [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[]', false], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[+5454]', false], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[].bbb', false], ]; const chekIsExistsData2 = [ From ed0c2ba046d4973c478e39a087901e4542b783cb Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Sun, 20 Jan 2019 16:02:03 +0300 Subject: [PATCH 2/7] add getValue function --- src/index.js | 27 +++++++++++------ src/tests/getValue.test.js | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 src/tests/getValue.test.js diff --git a/src/index.js b/src/index.js index e3ddc82..db72e92 100644 --- a/src/index.js +++ b/src/index.js @@ -74,19 +74,27 @@ function checkIsLocked(pObj) { /* ############################################################################# */ // It has been exported for tests, but you could use it if needed export function checkIsExists(pObject, pPath) { - let node = pObject; + return getValue(pObject, pPath) !== undefined; +} +/* ############################################################################# */ +export function getValue(pObject, pPath) { + if (!(pObject instanceof Object)) return undefined; + const pieces = typeof(pPath) === 'string' - ? pPath.replace(/(\[|\])+/g, '').split('.') - : pPath; + ? pPath.replace(/(\[|\])+/g, '').split('.') + : (Array.isArray(pPath) ? pPath : []); + if (pieces.length === 0) return pObject; + const lastIndex = pieces.length - 1; - for (let i = 0; i < lastIndex && node instanceof Object; i++) { + let node = pObject; + for (let i = 0; i < lastIndex; i++) { node = checkIsLocked(node[pieces[i]]) ? node[pieces[i]].__value__ : node[pieces[i]]; - if (!node || !(node instanceof Object) || checkIsRemoved(node)) return false; + if (!node || !(node instanceof Object) || checkIsRemoved(node)) return undefined; } - return node[pieces[lastIndex]] !== undefined; + return node[pieces[lastIndex]]; } /* ############################################################################# */ function extToTree(pExt, pSource) { @@ -113,8 +121,9 @@ function extToTree(pExt, pSource) { const path = separatePath(PAIR[0]); if (PAIR.length < 2 || PAIR[1] === undefined) { - const isExists = checkIsExists(pSource, path) || checkIsExists(FULL_RESULT, path); - if (!isExists) return FULL_RESULT; + if (!(checkIsExists(pSource, path) || checkIsExists(FULL_RESULT, path))) { + return FULL_RESULT; + } } const pieces = path.split('.'); @@ -237,4 +246,4 @@ function toFunction(pObj) { result = mutate(result, pExt); return result; }; -} \ No newline at end of file +} diff --git a/src/tests/getValue.test.js b/src/tests/getValue.test.js new file mode 100644 index 0000000..d7caa52 --- /dev/null +++ b/src/tests/getValue.test.js @@ -0,0 +1,62 @@ +import { getValue } from '../index'; + + +const data = [ + [null, 'a', undefined], + [[10], '0', 10], + [[{ a: 10 }], '0.a', 10], + [[{ a: 10 }], '1.a', undefined], + [{ a: 10 }, 'a', 10], + [{ a: 10 }, 'a.aa', undefined], + [{ a: 10 }, 'a2', undefined], + [{ }, 'a', undefined], + [{ }, 'a.aa', undefined], + [{ a: { aa: { aaa: 10 } } }, 'a', { aa: { aaa: 10 } }], + [{ a: { aa: { aaa: 10 } } }, 'a.aa', { aaa: 10 }], + [{ a: { aa: { aaa: 10 } } }, 'a.aa.aaa', 10], + [{ a: { aa: { aaa: 10 } } }, '[a]', { aa: { aaa: 10 } }], + [{ a: { aa: { aaa: 10 } } }, 'a.[aa]', { aaa: 10 }], + [{ a: { aa: { aaa: 10 } } }, '[a].[aa].[aaa]', 10], + [{ a: { aa: { aaa: 10 } } }, 'a.aa.aaa2', undefined], + [{ a: { aa: { aaa: 10 } } }, 'a.aa2', undefined], + [{ a: { aa: { aaa: 10 } } }, 'a2', undefined], + [{ a: { aa: { aaa: 10 } } }, 'a.aa.aaa.aaaa', undefined], + [{ a: { aa: { aaa: 10 } } }, 'a.aa2.aaa', undefined], + [{ a: { aa: { aaa: 10 } } }, 'a2.aa.aaa', undefined], + + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[0]', 1], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[2]', 3], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[3]', undefined], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[]', undefined], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[+5454]', undefined], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.[].bbb', undefined], +]; + +const data2 = [ + [{ a: { aa: { aaa: 10 } } }, 'a'.split('.'), { aa: { aaa: 10 } }], + [{ a: { aa: { aaa: 10 } } }, 'a.aa'.split('.'), { aaa: 10 }], + [{ a: { aa: { aaa: 10 } } }, 'a.aa.aaa'.split('.'), 10], + [{ a: { aa: { aaa: 10 } } }, 'a.aa.aaa2'.split('.'), undefined], + [{ a: { aa: { aaa: 10 } } }, 'a.aa2'.split('.'), undefined], + [{ a: { aa: { aaa: 10 } } }, 'a2'.split('.'), undefined], + [{ a: { aa: { aaa: 10 } } }, 'a.aa.aaa.aaaa'.split('.'), undefined], + [{ a: { aa: { aaa: 10 } } }, 'a.aa2.aaa'.split('.'), undefined], + [{ a: { aa: { aaa: 10 } } }, 'a2.aa.aaa'.split('.'), undefined], + + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.0'.split('.'), 1], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.2'.split('.'), 3], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.3'.split('.'), undefined], + [{ a: { aa: { aaa: [1,2,3] } } }, 'a.aa.aaa.'.split('.'), undefined], +]; + +describe('getValue', () => { + test.each(data)('getValue(%j, %s) === %j', (obj, path, expected) => { + const result = getValue(obj, path); + expect(result).toEqual(expected); + }); + + test.each(data2)('getValue(%j, %j) === %j', (obj, path, expected) => { + const result = getValue(obj, path); + expect(result).toEqual(expected); + }); +}); \ No newline at end of file From 7b70620b99eb729fa8babb5239bc2af9cf77b08a Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Sun, 20 Jan 2019 16:52:45 +0300 Subject: [PATCH 3/7] mutate shouldn't return new object, when changes are empty --- src/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.js b/src/index.js index db72e92..cdd7da9 100644 --- a/src/index.js +++ b/src/index.js @@ -124,6 +124,9 @@ function extToTree(pExt, pSource) { if (!(checkIsExists(pSource, path) || checkIsExists(FULL_RESULT, path))) { return FULL_RESULT; } + } else { + const currentValue = getValue(pSource, path); + if (currentValue === PAIR[1]) return FULL_RESULT; } const pieces = path.split('.'); @@ -235,6 +238,9 @@ export default function mutate(pObj, pExt) { } const tree = extToTree(pExt, pObj); + if (Object.getOwnPropertyNames(tree).length === 0) { + return pObj; + } return updateSection(pObj, tree); } /* ############################################################################# */ From 8155306712356b51629f11d0f490e4e75557d7bf Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Sun, 20 Jan 2019 16:53:11 +0300 Subject: [PATCH 4/7] add special tests to test new object creation --- src/tests/mutate.test.js | 7 -- src/tests/object_immutable.test.js | 113 +++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 src/tests/object_immutable.test.js diff --git a/src/tests/mutate.test.js b/src/tests/mutate.test.js index 7076367..c8bee11 100644 --- a/src/tests/mutate.test.js +++ b/src/tests/mutate.test.js @@ -31,13 +31,6 @@ describe('mutate', () => { expect(ex.message).toBe('Changes should be Object or Array'); } }); - - test('should return new object', () => { - const obj = { a: 1 }; - const changes = []; - const result = mutate(obj, changes); - expect(result).not.toBe(obj); - }); test('should update deeply', () => { const obj = { a: { aa: { aaa: 10 }, aa2: { aa2a: 5 } }, b: { bb: { bbb: 1 } }, c: { cc: { ccc: 1 } } }; diff --git a/src/tests/object_immutable.test.js b/src/tests/object_immutable.test.js new file mode 100644 index 0000000..694ce30 --- /dev/null +++ b/src/tests/object_immutable.test.js @@ -0,0 +1,113 @@ +import mutate from '../index'; + + +describe('mutate', () => { + describe( 'should return the same object', () => { + test('when changes are empty array', () => { + const obj = { a: 1 }; + const changes = []; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + + test('when changes are empty object', () => { + const obj = { a: 1 }; + const changes = {}; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + + test('when new walue is equal of current value', () => { + const obj = { a: 1 }; + const changes = { a: 1 }; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + + test('when changes cantains instruction to remove undefined elements', () => { + const obj = { a: 1 }; + const changes = { b: undefined }; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + + test('when new walue is equal of current value', () => { + const obj = { a: 1 }; + const changes = { a: 1, b: undefined }; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + + test('when new walue is equal of current value in deep path', () => { + const obj = { a: { aa: [1,2,3] } }; + const changes = { 'a.aa.[2]': 3 }; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + + test('when new walue is equal of current value in deep path', () => { + const obj = { a: { aa: { aaa: 35 } } }; + const changes = { 'a.aa.aaa': 35 }; + const result = mutate(obj, changes); + expect(result).toBe(obj); + }); + }); + + describe( 'should return new object', () => { + test('when changes contains new value as array', () => { + const obj = { a: 1 }; + const changes = [['a', 15]]; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ a: 15 }); + }); + + test('when changes contains new value as object', () => { + const obj = { a: 1 }; + const changes = { a: 30 }; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ a: 30 }); + }); + + test('when changes contains new value', () => { + const obj = { a: 1 }; + const changes = { a: 1, b: 7 }; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ a: 1, b: 7 }); + }); + + test('when changes contains instruction to remove defined prop', () => { + const obj = { a: 1 }; + const changes = { a: undefined }; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ }); + }); + + test('when changes contains instruction to remove defined prop', () => { + const obj = { a: { aa: { aaa: 35 } } }; + const changes = { 'a.aa.aaa': undefined }; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ a: { aa: { } } }); + }); + + test('when new walue is not equal of current value in deep path', () => { + const obj = { a: { aa: [1,2,3] } }; + const changes = { 'a.aa.[2]': 7 }; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ a: { aa: [1,2,7] } }); + }); + + test('when new walue is not equal of current value in deep path', () => { + const obj = { a: { aa: { aaa: 35 } } }; + const changes = { 'a.aa.aaa': 99 }; + const result = mutate(obj, changes); + expect(result).not.toBe(obj); + expect(result).toEqual({ a: { aa: { aaa: 99 } } }); + }); + }); +}); From 5bc4729020824aac6da306e1ed75a0bf793abf7d Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Mon, 21 Jan 2019 00:05:34 +0300 Subject: [PATCH 5/7] add some tests --- src/tests/mutate_function.test.js | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/tests/mutate_function.test.js diff --git a/src/tests/mutate_function.test.js b/src/tests/mutate_function.test.js new file mode 100644 index 0000000..405aea5 --- /dev/null +++ b/src/tests/mutate_function.test.js @@ -0,0 +1,38 @@ +import mutate from '../index'; + + +describe('mutate patch-function', () => { + test('should return function', () => { + const obj = [1, 2, 3]; + const patch = mutate(obj); + + expect(typeof patch).toEqual('function'); + expect(patch.length).toEqual(1); + }); + + test('should return new patched array each time', () => { + const obj = [1, 2, 3]; + const patch = mutate(obj); + const result1 = patch({ '[]': 10 }); + const result2 = patch({ '[]': 20 }); + const result3 = patch(); + + expect(obj).toEqual([1, 2, 3]); + expect(result1).toEqual([1, 2, 3, 10]); + expect(result2).toEqual([1, 2, 3, 10, 20]); + expect(result3).toBe(result2); + }); + + test('should return new patched object each time', () => { + const obj = { a: 1, b: 2, c: 3 }; + const patch = mutate(obj); + const result1 = patch({ d: 4 }); + const result2 = patch({ e: 5 }); + const result3 = patch(); + + expect(obj).toEqual({ a: 1, b: 2, c: 3 }); + expect(result1).toEqual({ a: 1, b: 2, c: 3, d: 4 }); + expect(result2).toEqual({ a: 1, b: 2, c: 3, d: 4, e: 5 }); + expect(result3).toBe(result2); + }); +}); \ No newline at end of file From 99f7edb015337ef5696525e6af4be15764b91697 Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Mon, 21 Jan 2019 00:05:46 +0300 Subject: [PATCH 6/7] readme --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index da587dd..4aac727 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ It is simple function which get object and list of changes and returns new patched object. +**Since the version 2.0.0 `deep-mutation` returns new object only when anything was changed** + ## Installation ``` @@ -25,9 +27,13 @@ const result = { } } }; -// equal with mutate +``` +equal with `deep-mutation` +```javascript +import mutate from 'deep-mutation'; const resultMutate = mutate(obj, { 'c.c3.c32': 25 }); ``` + ### Simple example ```javascript import mutate from 'deep-mutation'; @@ -57,7 +63,7 @@ const result = mutate(myObject, changes); ``` #### Result will be -``` +```javascript { a: 111, b: { @@ -114,6 +120,46 @@ If key for array item begins from `+` (\`[+${randomNumber}]\`), then value will 'a.[]': 100 ... ``` +It is needed when you want add some items to array and use changes as Object. +```javascript +import muatate from 'deep-mutation'; +... +return mutate({ a: [] }, { + // It is error, because JS object can't have some values with the same keys! + // 'a.[]': 1, + // 'a.[]': 2, + // 'a.[]': 3, + // 'a.[]': 4, + + //It is true way + 'a.[+1123]': 1, + 'a.[+232]': 2, + 'a.[+43534]': 3, + 'a.[+64]': 4, +}); + +// result will be = { a: [1,2,3,4] } +``` + +## Deep-mutation can return patch-function + +If `deep-mutation` function will be called with only one argument (only object, without changes), then result will be **function**, which take one argument as changes, save and returns patched object + + +```javascript +import mutate from 'deep-mutation'; + +const patch = mutate({ a: 1, b: 2}); + +const result1 = patch({ c: 3 }); +// result1 === { a: 1, b: 2, c: 3} + +const result2 = patch({ d: 4 }); +// result2 === { a: 1, b: 2, c: 3, d: 4} + +const result3 = patch(); +// result3 === result2 === { a: 1, b: 2, c: 3, d: 4} +``` # Immutable comparison **Examples** @@ -133,7 +179,7 @@ https://github.com/axules/deep-mutation/tree/master/ImmutableComparison ![deep-mutation vs immutable performance](https://raw.githubusercontent.com/axules/deep-mutation/master/ImmutableComparison/SyntaxComparison.png) -# Tests cases +# Tests cases / code example ```javascript mutate({ a: 10 }, [['a', 5]]); // { a: 5 } @@ -280,7 +326,7 @@ mutate( */ ``` -### It returns new object always +### It returns the same object (**actual since version 2.0.0**) ```javascript const obj = { a: 10 }; const result = mutate(obj, []); From fb68e116089e5dbf3fc2ff08658311e9942eb4b8 Mon Sep 17 00:00:00 2001 From: Denis Shelest Date: Mon, 21 Jan 2019 00:05:57 +0300 Subject: [PATCH 7/7] v2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bdfde28..bd54dac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deep-mutation", - "version": "1.1.0", + "version": "2.0.0", "author": { "name": "Shelest Denis", "email": "axules@gmail.com"