Skip to content

Commit

Permalink
added lodash implimentation of object map
Browse files Browse the repository at this point in the history
  • Loading branch information
joepuzzo committed Dec 10, 2018
1 parent 35d38cf commit d95c692
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Expand Up @@ -2,7 +2,8 @@
"env": {
"browser": true,
"commonjs": true,
"es6": true
"es6": true,
"mocha": true
},
"extends": "eslint:recommended",
"parserOptions": {
Expand Down
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -100,7 +100,8 @@
"webpack-node-externals": "^1.7.2"
},
"dependencies": {
"@babel/runtime-corejs2": "^7.0.0-rc.1"
"@babel/runtime-corejs2": "^7.0.0-rc.1",
"lodash": "^4.17.11"
},
"peerDependencies": {
"react": "^16.0.0"
Expand Down
10 changes: 5 additions & 5 deletions src/Controller/FormController.js
@@ -1,4 +1,4 @@
import ObjectMap from '../ObjectMap';
import ObjectMap from '../ObjectMap/LodashMap';
const EventEmitter = require('events').EventEmitter;

class FormController extends EventEmitter {
Expand All @@ -7,11 +7,11 @@ class FormController extends EventEmitter {
this.setMaxListeners(0);
this.hooks = hooks;
this.config = config;
this.values = new ObjectMap(config.initialValues);
this.touched = new ObjectMap();
this.errors = new ObjectMap();
this.values = new ObjectMap(config.initialValues, {name: 'values'});
this.touched = new ObjectMap({}, {name: 'touched'});
this.errors = new ObjectMap({}, {name: 'errors'});
this.submits = 0;
this.asyncErrors = new ObjectMap();
this.asyncErrors = new ObjectMap({}, {name: 'asyncErrors'});
this.api = {
setValue: this.setValue,
getValue: this.getValue,
Expand Down
58 changes: 58 additions & 0 deletions src/ObjectMap/LodashMap.js
@@ -0,0 +1,58 @@
import ldset from 'lodash/setWith';
import ldunset from 'lodash/unset';
import ldtoPath from 'lodash/toPath';
import ldget from 'lodash/get';
import ldvalues from 'lodash/values';

class ObjectMap {
constructor(object = {}, opts ={}) {
this.name = opts.name;
this.object = JSON.parse(JSON.stringify(object));
}

empty() {
return ldvalues(this.object).length === 0;
}

rebuild(object = {}) {
this.object = JSON.parse(JSON.stringify(object));
}

get(path) {
return ldget(this.object, path);
}

set(path, value) {
//console.log(`${this.name} obj: ${JSON.stringify(this.object)}`);
//console.log(`${this.name} setting ${path} to ${value}`);
if( value == null ){
this.delete(path);
} else {
ldset(this.object, path, value);
}
//console.log(`${this.name} obj: ${JSON.stringify(this.object)}`);
}

delete(path) {
ldunset(this.object, path);
let pathArray = ldtoPath(path);
pathArray = pathArray.slice(0, pathArray.length - 1);
cleanup(this.object, pathArray);
}
}

function cleanup( obj, path ) {
// Base case no path left
if( path.length === 0 ){
return;
}
// Delete object if its empty
const object = ldget(obj, path);
if( Array.isArray(object) ? object.every(e=> e == null) : JSON.stringify( object ) === '{}' ){
ldunset(obj, path);
}
// Recur
cleanup( obj, path.slice(0, path.length - 1) );
}

export default ObjectMap;
237 changes: 237 additions & 0 deletions tests/LodashMap.spec.js
@@ -0,0 +1,237 @@
import ObjectMap from '../src/ObjectMap/LodashMap';
import { expect } from 'chai';

describe('LodashMap', () => {
describe('constructor', () => {
it('create a map and an object', () => {
const expectedObj = { foo: 'bar' };
const objectMap = new ObjectMap({ foo: 'bar' });
const actualObj = objectMap.object;
expect(actualObj).to.deep.equal(expectedObj);
});

it('create a nested map and an object', () => {
const expectedObj = { foo: { bar: { baz: 3 } } };
const objectMap = new ObjectMap({ foo: { bar: { baz: 3 } } });
const actualObj = objectMap.object;
expect(actualObj).to.deep.equal(expectedObj);
});
});

describe('get', () => {
it('should get value from very nested object', () => {
const expected = 2;
const object = {
foo: {
bar: {
baz: [
{
taz: {
bar: [
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
[
null,
null,
null,
{
bar: {
'0': {
'5': 2
}
}
}
]
]
}
},
{}
]
}
}
};
const objectMap = new ObjectMap(object);
const actual = objectMap.get(
'foo[\'bar\'].baz[0].taz.bar[10][3].bar[\'0\'].5'
);
expect(actual).to.equal(expected);
});

it('should return undefined when get is called with field that does not exist', () => {
const map = undefined;
const expected = undefined;
const objectMap = new ObjectMap();
const actual = objectMap.get('foo.bar');
expect(actual).to.equal(expected);
});
});

describe('set', () => {
it('should set a value', () => {
const expected = 3;
const objectMap = new ObjectMap();
objectMap.set('foo', 3);
const actual = objectMap.get('foo');
expect(actual).to.equal(expected);
});

describe('object', () => {
it('should set a nested value and initialize objects along the way', () => {
const expected = { foo: { bar: { baz: 3 } } };
const objectMap = new ObjectMap();
objectMap.set('foo.bar.baz', 3);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should set a nested value and initialize arrays along the way', () => {
const expected = { foo: [, [, 3]] };
const objectMap = new ObjectMap();
objectMap.set('foo[1][1]', 3);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should set a nested value and initialize objects along the way with array object syntax', () => {
const expected = { foo: { bar: { baz: 3 } } };
const objectMap = new ObjectMap();
objectMap.set('foo[\'bar\'].baz', 3);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should set a nested value and initialize arrays and objects along the way', () => {
const expected = { foo: [, { bar: [, 3] }] };
const objectMap = new ObjectMap();
objectMap.set('foo[1].bar[1]', 3);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should remove objects when they are empty after setting null', () => {
const expected = {};
const objectMap = new ObjectMap({ foo: { bar: { baz: 3 } } });
objectMap.set('foo.bar.baz', null);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should NOT remove objects when they are NOT empty after setting null', () => {
const expected = { foo: { bar: { boo: 4 } } };
const objectMap = new ObjectMap({ foo: { bar: { baz: 3, boo: 4 } } });
objectMap.set('foo.bar.baz', null);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should remove array when they are empty after setting null', () => {
const expected = {};
const objectMap = new ObjectMap({ foo: [, [, 3]] });
objectMap.set('foo[1][1]', null);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should NOT remove array when they are NOT empty after setting null', () => {
const expected = { foo: [null, [null, , 4]] };
const objectMap = new ObjectMap({ foo: [, [, 3, 4]] });
objectMap.set('foo[1][1]', null);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should remove arrays and objects when they are empty after setting null', () => {
const expected = {};
const objectMap = new ObjectMap({ foo: [, { bar: [, 3] }] });
objectMap.set('foo[1].bar[1]', null);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});

it('should NOT remove arrays and objects when they are NOT empty after setting null', () => {
const expected = { foo: [null, { bar: [null, , 4] }] };
const objectMap = new ObjectMap({ foo: [, { bar: [, 3, 4] }] });
objectMap.set('foo[1].bar[1]', null);
const actual = objectMap.object;
expect(actual).to.deep.equal(expected);
});
});

describe('delete', () => {
it('should delete value', () => {
const objectMap = new ObjectMap({ foo: { bar: { baz: 3 } } });
objectMap.delete('foo.bar.baz');
expect(objectMap.object).to.deep.equal({});
});

it('should delete value from array', () => {
const objectMap = new ObjectMap({ foo: { bar: { baz: [1, 2, 3] } } });
objectMap.delete('foo.bar.baz[1]');
expect(objectMap.object).to.deep.equal({
foo: { bar: { baz: [1,,3] } }
});
});

it('should remove array and objects when all values are deleted', () => {
const objectMap = new ObjectMap({ foo: { bar: { baz: [1, 2, 3] } } });
objectMap.delete('foo.bar.baz[0]');
objectMap.delete('foo.bar.baz[1]');
objectMap.delete('foo.bar.baz[2]');
expect(objectMap.object).to.deep.equal({});
});
});

it('should set value in very nested obejct', () => {
const expected = 3;
const object = {
foo: {
bar: {
baz: [
{
taz: {
bar: [
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
[
null,
null,
null,
{
bar: {
'0': {
'5': 2
}
}
}
]
]
}
},
{}
]
}
}
};
const objectMap = new ObjectMap(object);
objectMap.set('foo.bar.baz[0].taz.bar[10][3].bar.0.5', 3);
const actual = objectMap.get('foo.bar.baz[0].taz.bar[10][3].bar.0.5');
expect(actual).to.equal(expected);
});
});
});
2 changes: 1 addition & 1 deletion tests/components/Form.spec.js
Expand Up @@ -227,7 +227,7 @@ describe('Form', () => {
const setApi = param => {
api = param;
};
const validate = greeting =>
const validate = greeting =>
greeting === 'hello!' ? 'ooo thats no good' : null;
const wrapper = mount(
<Form onSubmit={spy} getApi={setApi}>
Expand Down

0 comments on commit d95c692

Please sign in to comment.