Skip to content

Commit

Permalink
Merge pull request #1 from axules/dev_2.0.0
Browse files Browse the repository at this point in the history
Dev 2.0.0
  • Loading branch information
axules authored Jan 20, 2019
2 parents f087369 + fb68e11 commit 839b868
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 21 deletions.
54 changes: 50 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand All @@ -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';
Expand Down Expand Up @@ -57,7 +63,7 @@ const result = mutate(myObject, changes);
```

#### Result will be
```
```javascript
{
a: 111,
b: {
Expand Down Expand Up @@ -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**
Expand All @@ -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 }
Expand Down Expand Up @@ -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, []);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "deep-mutation",
"version": "1.1.0",
"version": "2.0.0",
"author": {
"name": "Shelest Denis",
"email": "axules@gmail.com"
Expand Down
33 changes: 24 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -113,8 +121,12 @@ 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;
}
} else {
const currentValue = getValue(pSource, path);
if (currentValue === PAIR[1]) return FULL_RESULT;
}

const pieces = path.split('.');
Expand Down Expand Up @@ -226,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);
}
/* ############################################################################# */
Expand All @@ -237,4 +252,4 @@ function toFunction(pObj) {
result = mutate(result, pExt);
return result;
};
}
}
6 changes: 6 additions & 0 deletions src/tests/checkIsExists.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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 = [
Expand Down
62 changes: 62 additions & 0 deletions src/tests/getValue.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
7 changes: 0 additions & 7 deletions src/tests/mutate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } } };
Expand Down
38 changes: 38 additions & 0 deletions src/tests/mutate_function.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading

0 comments on commit 839b868

Please sign in to comment.