Skip to content

Commit

Permalink
Add chain get/set for ObjectTreeNode (#85)
Browse files Browse the repository at this point in the history
* Add chain get/set for ObjectTreeNode

* rm unused import
  • Loading branch information
snewcomer authored Oct 11, 2020
1 parent 70e918a commit 9f8f515
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 21 deletions.
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import objectWithout from './utils/object-without';
import take from './utils/take';
import mergeDeep from './utils/merge-deep';
import setDeep from './utils/set-deep';
import getDeep from './utils/get-deep';
import getDeep, { getSubObject } from './utils/get-deep';

import {
Changes,
Expand Down Expand Up @@ -947,8 +947,9 @@ export class BufferedChangeset implements IChangeset {

const baseContent = this.safeGet(content, baseKey);
const subContent = this.getDeep(baseContent, remaining.join('.'));
const subChanges = getSubObject(changes, key);
// give back and object that can further retrieve changes and/or content
const tree = new ObjectTreeNode(result, subContent, this.safeGet);
const tree = new ObjectTreeNode(subChanges, subContent, this.getDeep);
return tree.proxy;
} else if (typeof result !== 'undefined') {
return result;
Expand Down Expand Up @@ -976,7 +977,7 @@ export class BufferedChangeset implements IChangeset {
}

// may still access a value on the changes or content objects
const tree = new ObjectTreeNode(subChanges, subContent, this.safeGet);
const tree = new ObjectTreeNode(subChanges, subContent, this.getDeep);
return tree.proxy;
}

Expand Down
33 changes: 32 additions & 1 deletion src/utils/get-deep.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Change from '../-private/change';

/**
* Handles both single key or nested string keys ('person.name')
*
Expand All @@ -12,7 +14,7 @@ export default function getDeep<T extends Record<string, any>>(
if (path.indexOf('.') === -1) {
return obj[path as string];
}
const parts = typeof path === 'string' ? path.split('.') : path;
const parts: string[] = typeof path === 'string' ? path.split('.') : path;

for (let i = 0; i < parts.length; i++) {
if (obj === undefined || obj === null) {
Expand All @@ -25,3 +27,32 @@ export default function getDeep<T extends Record<string, any>>(

return obj;
}

/**
* Returns subObject while skipping `Change` instances
*
* @method getSubObject
*/
export function getSubObject<T extends Record<string, any>>(root: T, path: string | string[]): any {
let obj: T = root;

if (path.indexOf('.') === -1) {
return obj[path as string];
}

const parts: string[] = typeof path === 'string' ? path.split('.') : path;

for (let i = 0; i < parts.length; i++) {
if (obj === undefined || obj === null) {
return undefined;
}

if (obj[parts[i]] instanceof Change) {
obj = obj[parts[i]].value;
} else {
obj = obj[parts[i]];
}
}

return obj;
}
9 changes: 9 additions & 0 deletions src/utils/object-tree-node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ProxyHandler, Content } from '../types';
import isObject from './is-object';
import setDeep from './set-deep';
import Change from '../-private/change';
import normalizeObject from './normalize-object';

Expand Down Expand Up @@ -98,6 +99,14 @@ class ObjectTreeNode implements ProxyHandler {
this.children = Object.create(null);
}

get(key: string) {
return this.safeGet(this.changes, key);
}

set(key: string, value: unknown) {
return setDeep(this.changes, key, value);
}

unwrap(): Record<string, any> {
let changes = this.changes;

Expand Down
41 changes: 25 additions & 16 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,22 +584,6 @@ describe('Unit | Utility | changeset', () => {
expect(result).toBeUndefined();
});

it('nested objects will return correct values', () => {
dummyModel['org'] = {
asia: { sg: '_initial' }, // for the sake of disambiguating nulls
usa: {
ca: null,
ny: null,
ma: { name: null }
}
};

const dummyChangeset = Changeset(dummyModel, lookupValidator(dummyValidations));
expect(dummyChangeset.get('org.asia.sg')).toBe('_initial');
dummyChangeset.set('org.asia.sg', 'sg');
expect(dummyChangeset.get('org.asia.sg')).toBe('sg');
});

it('#get nested objects can contain arrays', () => {
expect.assertions(7);
dummyModel.name = 'Bob';
Expand Down Expand Up @@ -1194,6 +1178,31 @@ describe('Unit | Utility | changeset', () => {
expect(result.sync).toEqual(false);
});

it('#set nested objects at various level of tree will return correct values', () => {
dummyModel['org'] = {
asia: { sg: '_initial' }, // for the sake of disambiguating nulls
usa: {
ca: null,
ny: null,
ma: { name: null }
}
};

const dummyChangeset = Changeset(dummyModel, lookupValidator(dummyValidations));
expect(dummyChangeset.get('org.asia.sg')).toBe('_initial');

dummyChangeset.set('org.asia.sg', 'sg');
expect(dummyChangeset.get('org.asia.sg')).toBe('sg');

dummyChangeset.get('org.asia').set('sg', 'SG');
expect(dummyChangeset.get('org.asia.sg')).toBe('SG');

dummyChangeset.get('org').set('asia.sg', 'sg');
expect(dummyChangeset.get('org.asia.sg')).toBe('sg');

expect(dummyChangeset.get('org').get('asia.sg')).toBe('sg');
});

it('it accepts async validations', async () => {
const dummyChangeset = Changeset(dummyModel, lookupValidator(dummyValidations));
/* const dummyChangeset = Changeset(dummyModel, dummyValidator); */
Expand Down
56 changes: 55 additions & 1 deletion test/utils/get-deep.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getDeep from '../../src/utils/get-deep';
import getDeep, { getSubObject } from '../../src/utils/get-deep';
import Change from '../../src/-private/change';

describe('Unit | Utility | get deep', () => {
it('it returns value', () => {
Expand Down Expand Up @@ -28,4 +29,57 @@ describe('Unit | Utility | get deep', () => {

expect(value).toEqual({ other: 'Ivan' });
});

it('it returns Change', () => {
const objA = { name: new Change({ other: 'Ivan' }), foo: { other: 'bar' } };

let value = getDeep(objA, 'name');
expect(value).toEqual(new Change({ other: 'Ivan' }));

value = getDeep(objA, 'name.other');
expect(value).toEqual(undefined);
});
});

describe('Unit | Utility | get sub object', () => {
it('it returns value', () => {
const objA = { other: 'Ivan' };
const value = getSubObject(objA, 'foo');

expect(value).toBeUndefined();
});

it('it returns value from nested', () => {
const objA = { name: { other: 'Ivan' } };
const value = getSubObject(objA, 'name');

expect(value).toEqual({ other: 'Ivan' });
});

it('it returns value from deep nested', () => {
const objA = { name: { other: 'Ivan' } };
const value = getSubObject(objA, 'name.other');

expect(value).toBe('Ivan');
});

it('it returns multiple values from nested', () => {
const objA = { name: { other: 'Ivan' }, foo: { other: 'bar' } };
const value = getSubObject(objA, 'name');

expect(value).toEqual({ other: 'Ivan' });
});

it('it returns object inside Change', () => {
const objA = { name: new Change({ other: 'Ivan' }), foo: { other: 'bar' } };

let value = getSubObject(objA, 'name');
expect(value).toEqual(objA.name);

value = getSubObject(objA, 'foo');
expect(value).toEqual(objA.foo);

value = getSubObject(objA, 'name.other');
expect(value).toEqual('Ivan');
});
});

0 comments on commit 9f8f515

Please sign in to comment.