UNPKG

@onehat/data

Version:

JS data modeling package with adapters for many storage mediums.

1,008 lines (847 loc) 24.3 kB
import Joi from 'Joi'; import * as yup from 'yup'; import Entity from '../../src/Entity/Entity.js'; import Schema from '../../src/Schema/index.js'; import PropertyTypes from '../../src/Property/index.js'; describe('Entity', function() { beforeEach(function() { this.schema = new Schema({ name: 'baz', model: { idProperty: 'foo', displayProperty: 'bar', properties: [ { name: 'foo', type: 'int' }, { name: 'bar' }, { name: 'baz', mapping: 'baz.test.val', type: 'bool', defaultValue: null, }, ], validator: null, }, entity: { methods: { testMethod: function() { this.azx = 'test me'; }, }, statics: { azx: null, }, }, }); this.data = { foo: 1, bar: 'one', baz: { test: { val: true, }, }, }; this.entity = new Entity(this.schema, this.data); this.entity.initialize(); }); afterEach(function() { if (!this.entity.isDestroyed) { this.entity.destroy(); } if (!this.schema.isDestroyed) { this.schema.destroy(); } }); describe('constructor', function() { it('creates an entity', function() { expect(this.entity instanceof Entity).to.be.true; expect(this.entity.getSchema()).to.be.eq(this.schema); expect(this.entity.id).to.be.eq(1); }); it('createTempId, isTempId', function() { const schema = new Schema({ name: 'baz', model: { idProperty: 'foo', displayProperty: 'bar', properties: [ { name: 'foo', type: 'int' }, { name: 'bar' }, ], }, }), data = { bar: 'one', }, entity = new Entity(schema, data); entity.initialize(); expect(entity.id).to.be.null; expect(entity.isTempId).to.be.false; entity.createTempId(); expect(entity.id).to.be.not.null; expect(entity.isTempId).to.be.true; entity.markSaved(); expect(entity.isTempId).to.be.false; entity.isTempId = true; expect(entity.isTempId).to.be.true; }); it('clone', function() { const entity = this.entity; entity.bar = 'two'; const clone = entity.clone(); expect(entity === clone).to.be.false; expect(_.isEqual(entity.id, clone.id)).to.be.true; expect(_.isEqual(entity.displayValue, clone.displayValue)).to.be.true; expect(_.isEqual(entity.isDirty, clone.isDirty)).to.be.true; expect(_.isEqual(entity.isPersisted, clone.isPersisted)).to.be.true; expect(_.isEqual(entity.isDeleted, clone.isDeleted)).to.be.true; expect(_.isEqual(entity._originalData, clone._originalData)).to.be.true; expect(_.isEqual(entity.getOriginalData(), clone.getOriginalData())).to.be.true; expect(_.isEqual(entity.getRawValues(), clone.getRawValues())).to.be.true; expect(_.isEqual(entity.getParsedValues(), clone.getParsedValues())).to.be.true; expect(_.isEqual(entity.getDisplayValues(), clone.getDisplayValues())).to.be.true; expect(_.isEqual(entity.getSubmitValues(), clone.getSubmitValues())).to.be.true; }); it('_createProperties', function() { const entity = this.entity; expect(_.size(entity.properties)).to.be.eq(3); expect(entity.properties.foo.name).to.be.eq('foo'); expect(entity.properties.bar.name).to.be.eq('bar'); expect(entity.properties.baz.name).to.be.eq('baz'); }); it('_createMethods', function() { this.entity.testMethod(); expect(this.entity.azx).to.be.eq('test me'); }); it('loadOriginalData', function() { const data = { foo: 2, bar: 'two', }; this.entity.loadOriginalData(data); expect(this.entity.isDirty).to.be.false; expect(this.entity.isPhantom).to.be.false; expect(this.entity.foo).to.be.eq(2); expect(this.entity.bar).to.be.eq('two'); }); it('reset', function() { const originalData = this.entity.getParsedValues(); expect(this.entity.bar).to.be.eq('one'); this.entity.setValue('bar', 'Test'); expect(this.entity.bar).to.be.eq('Test'); this.entity.reset(); expect(this.entity.bar).to.be.eq('one'); expect(this.entity.isDirty).to.be.false; expect(_.isEqual(this.entity.getParsedValues(), originalData)).to.be.true; this.entity.delete(); expect(this.entity.isDeleted).to.be.true; this.entity.reset(); expect(this.entity.isDeleted).to.be.false; }); it('getMappedValue', function() { const getMappedValue = Entity.getMappedValue, root = { a: { b: { c: true } } }, mapping = 'a.b.c'; let result = getMappedValue(mapping, root); expect(result).to.be.true; result = getMappedValue('a.b', root); expect(_.isEqual(result, { c: true })).to.be.true; }); }); describe('getters', function() { it('proxy getter for getPropertySubmitValue works', function() { expect(this.entity.foo).to.be.eq(1); expect(this.entity.bar).to.be.eq('one'); }); it('getSchema', function() { const schema = this.entity.getSchema(); expect(schema).to.be.eq(this.schema); }); it('getProperty', function() { const property = this.entity.getProperty('foo'); expect(property.name).to.be.eq('foo'); }); it('getPropertySubmitValue', function() { const result = this.entity.getPropertySubmitValue('foo'); expect(result).to.be.eq(1); }); it('getPropertyDisplayValue', function() { const result = this.entity.getPropertyDisplayValue('foo'); expect(result).to.be.eq(1); }); it('getSubmitValues & submitValues', function() { const result = this.entity.getSubmitValues(), expected = { foo: 1, bar: 'one', baz: true, }; expect(_.isEqual(result, expected)).to.be.true; expect(_.isEqual(this.entity.submitValues, expected)).to.be.true; }); it('getSubmitValuesMapped & submitValuesMapped', function() { const result = this.entity.getSubmitValuesMapped(), expected = { foo: 1, bar: 'one', 'baz.test.val': true, }; expect(_.isEqual(result, expected)).to.be.true; expect(_.isEqual(this.entity.submitValuesMapped, expected)).to.be.true; }); it('getDisplayValues & displayValues', function() { const result = this.entity.getDisplayValues(), expected = { foo: 1, bar: 'one', baz: 'Yes', }; expect(_.isEqual(result, expected)).to.be.true; expect(_.isEqual(this.entity.displayValues, expected)).to.be.true; }); it('getRawValues & rawValues', function() { const result = this.entity.getRawValues(), expected = { foo: 1, bar: 'one', baz: true, }; expect(_.isEqual(result, expected)).to.be.true; expect(_.isEqual(this.entity.rawValues, expected)).to.be.true; }); it('getParsedRawValues & parsedRawValues', function() { const result = this.entity.getParsedRawValues(), expected = { foo: 1, bar: 'one', baz: true, }; expect(_.isEqual(result, expected)).to.be.true; expect(_.isEqual(this.entity.parsedRawValues, expected)).to.be.true; }); it('getParsedValues & parsedValues', function() { const result = this.entity.getParsedValues(), expected = { foo: 1, bar: 'one', baz: true, }; expect(_.isEqual(result, expected)).to.be.true; expect(_.isEqual(this.entity.parsedValues, expected)).to.be.true; }); it('_getReconstructedOriginalData', function() { let originalData = this.entity._originalData, reconstructed = this.entity._getReconstructedOriginalData(); expect(_.isEqual(originalData, reconstructed)).to.be.true; // Change some values, save it, then check again this.entity.foo = 2; this.entity.baz = false; this.entity.markSaved(); reconstructed = this.entity._getReconstructedOriginalData(); const expected = { foo: 2, bar: 'one', baz: { test: { val: false, }, }, }; expect(_.isEqual(expected, reconstructed)).to.be.true; }); it('getReverseMappedRawValue', function() { const definition1 = { name: 'foo', mapping: 'a.b.c', type: 'int', }, Property1 = PropertyTypes[definition1.type]; const property1 = new Property1(definition1, 'fakeEntity'); property1.setValue('47'); const reverseMapped1 = Entity.getReverseMappedRawValue(property1), expected1 = { a: { b: { c: '47' }, }, }; expect(_.isEqual(reverseMapped1, expected1)).to.be.true; // Now, with no mapping const definition2 = { name: 'foo', type: 'int', }, Property2 = PropertyTypes[definition2.type]; const property2 = new Property2(definition2, 'fakeEntity'); property2.setValue('47'); const reverseMapped2 = Entity.getReverseMappedRawValue(property2), expected2 = { foo: '47' }; expect(_.isEqual(reverseMapped2, expected2)).to.be.true; }); it('getReverseMappedRawValues (check deep matches)', function() { const schema = new Schema({ name: 'baz', model: { idProperty: 'foo', displayProperty: 'bar', properties: [ { name: 'foo', type: 'int' }, { name: 'bar' }, { name: 'baz', mapping: 'baz.test.val', type: 'bool', defaultValue: null, }, { name: 'baz2', mapping: 'baz.test2', type: 'bool', defaultValue: null, }, ], }, }), data = { foo: 1, bar: 'one', baz: { test: { val: true, }, test2: false, }, }, entity = new Entity(schema, data); entity.initialize(); const result = entity.getReverseMappedRawValues(); expect(_.isEqual(result, data)).to.be.true; }); it('build a new entity from an existing one using getDataForNewEntity', function() { const entity = new Entity(this.schema, this.entity.getDataForNewEntity()); entity.initialize(); expect(_.isEqual(entity.submitValues, this.entity.submitValues)).to.be.true; }); it('getChanged', function() { this.entity.foo = 2; const result = this.entity.getChanged(), expected = ['foo']; expect(_.isEqual(result, expected)).to.be.true; }); it('getChangedValues', function() { this.entity.foo = 2; const result = this.entity.getChangedValues(), expected = { foo: { original: 1, current: 2, }, }; expect(_.isEqual(result, expected)).to.be.true; }); it('data', function() { const result = this.entity.data, expected = { foo: 1, bar: 'one', baz: true, }; expect(_.isEqual(result, expected)).to.be.true; }); it('getPropertiesBy', function() { const properties = this.entity.getPropertiesBy((property) => { return property.name === 'foo'; }); expect(properties[0].name).to.be.eq('foo'); }); it('getIdProperty', function() { const property = this.entity.getIdProperty(); expect(property.name).to.be.eq('foo'); }); it('getId', function() { const result = this.entity.getId(); expect(result).to.be.eq(1); }); it('id', function() { const result = this.entity.id; expect(result).to.be.eq(1); }); it('id - after destroyed', function() { const entity = new Entity(this.schema, this.data); entity.initialize(); const id = entity.id; entity.destroy(); expect(_.isEqual(entity.id, id)).to.be.true; }); it('getDisplayProperty', function() { const property = this.entity.getDisplayProperty(); expect(property.name).to.be.eq('bar'); }); it('getDisplayValue', function() { const result = this.entity.getDisplayValue(); expect(result).to.be.eq('one'); }); it('displayValue', function() { const result = this.entity.displayValue; expect(result).to.be.eq('one'); }); it('isPhantom', function() { expect(this.entity.isPhantom).to.be.false; const entity = new Entity(this.schema, {}); entity.initialize(); expect(entity.isPhantom).to.be.true; entity.createTempId(); expect(entity.isPhantom).to.be.true; entity.markSaved(); expect(entity.isPhantom).to.be.false; }); it('isDirty', function() { expect(this.entity.isDirty).to.be.false; this.entity.setValue('bar', 'Test'); expect(this.entity.isDirty).to.be.true; this.entity.reset(); expect(this.entity.isDirty).to.be.false; this.entity.setValues({ foo: 50, bar: 'Test13', }); expect(this.entity.isDirty).to.be.true; }); it('getOriginalData', function() { const result = this.entity.getOriginalData(); expect(_.isEqual(result, this.data)).to.be.true; }); it('hash', function() { expect(this.entity.hash).to.be.eq('40aa53cc3f0cffef350ce784537eebeacf270b45'); // change a property & check again this.entity.foo = 3; expect(this.entity.hash).to.be.eq('6b8d327b573da8133ec2ff39acc9e15efd152afb'); }); }); describe('setters', function() { it('proxy setter for setValue works', function() { let original = this.entity.foo; this.entity.foo = 2; expect(this.entity.foo).to.not.eq(original); original = this.entity.bar; this.entity.bar = 'Test'; expect(this.entity.bar).to.not.eq(original); }); it('setValue', function() { expect(this.entity.bar).to.be.eq('one'); expect(this.entity.isDirty).to.be.false; this.entity.setValue('bar', 'Test'); expect(this.entity.bar).to.be.eq('Test'); expect(this.entity.isDirty).to.be.true; }); it('setValues', function() { expect(this.entity.foo).to.be.eq(1); expect(this.entity.bar).to.be.eq('one'); expect(this.entity.baz).to.be.eq(true); expect(this.entity.isDirty).to.be.false; this.entity.setValues({ foo: 2, bar: 'two', baz: false, }); expect(this.entity.foo).to.be.eq(2); expect(this.entity.bar).to.be.eq('two'); expect(this.entity.baz).to.be.eq(false); expect(this.entity.isDirty).to.be.true; }); it('setRawValues', function() { expect(this.entity.foo).to.be.eq(1); expect(this.entity.bar).to.be.eq('one'); expect(this.entity.baz).to.be.eq(true); expect(this.entity.isDirty).to.be.false; this.entity.setRawValues({ foo: 2, bar: 'two', baz: { test: { val: false, }, }, }); expect(this.entity.foo).to.be.eq(2); expect(this.entity.bar).to.be.eq('two'); expect(this.entity.baz).to.be.eq(false); expect(this.entity.isDirty).to.be.true; }); it('getMappedData', function() { const mapped = this.entity.getMappedData({ foo: 2, bar: 'two', baz: { test: { val: false, }, }, }); expect(mapped.foo).to.be.eq(2); expect(mapped.bar).to.be.eq('two'); expect(mapped.baz).to.be.eq(false); }); it('setId', function() { expect(this.entity.foo).to.be.eq(1); expect(this.entity.isTempId).to.be.false; this.entity.setId(2); expect(this.entity.foo).to.be.eq(2); expect(this.entity.isTempId).to.be.false; }); it('_recalculateDependentProperties', function() { const schema = new Schema({ name: 'baz', model: { idProperty: 'foo', displayProperty: 'bar', properties: [ { name: 'foo', type: 'int' }, { name: 'bar' }, { name: 'virtual', depends: 'bar', parse: function() { const parsedValues = this.getEntity().getParsedValues(); return parsedValues.bar + ' for you'; } }, ], }, }), data = { foo: 1, bar: 'one', }, entity = new Entity(schema, data); entity.initialize(); expect(entity.virtual).to.be.eq('one for you'); entity.bar = 'two'; expect(entity.virtual).to.be.eq('two for you'); }); it('markSaved', function() { const entity = new Entity(this.schema); entity.initialize(); const id = entity.id; expect(entity.isPersisted).to.be.false; expect(_.isEqual(entity.getOriginalData(), {})).to.be.true; entity.bar = 'Test'; entity.markSaved(); expect(entity.isTempId).to.be.false; expect(entity.isStaged).to.be.false; expect(entity.isPersisted).to.be.true; const expected = { foo: id, bar: 'Test', baz: { test: { val: null, }, }, }; expect(_.isEqual(entity.getOriginalData(), expected)).to.be.true; }); it('markDeleted', function() { this.entity.markDeleted(); expect(this.entity.isDeleted).to.be.true; this.entity.markDeleted(false); expect(this.entity.isDeleted).to.be.false; }); it('delete', function() { this.entity.delete(); expect(this.entity.isDeleted).to.be.true; }); it('undelete', function() { this.entity.delete(); this.entity.undelete(); expect(this.entity.isDeleted).to.be.false; }); it('markStaged, stage', function() { expect(this.entity.isStaged).to.be.false; this.entity.stage(); expect(this.entity.isStaged).to.be.true; this.entity.markStaged(false); expect(this.entity.isStaged).to.be.false; }); it('setValue changed lastModified', function() { let earlyLastModified, lateLastModified; earlyLastModified = this.entity.lastModified; cy.wait(100); this.entity.setValue('foo', '125'); lateLastModified = this.entity.lastModified; expect(earlyLastModified < lateLastModified).to.be.true; cy.wait(100); earlyLastModified = this.entity.lastModified; cy.wait(100); this.entity.foo = '126'; lateLastModified = this.entity.lastModified; expect(earlyLastModified < lateLastModified).to.be.true; }); it('freeze', function() { this.entity.freeze(); expect(this.entity.isFrozen).to.be.true; this.entity.destroy(); expect(this.entity.isDestroyed).to.be.false; let isError = false; try { this.entity.foo = 2; } catch(e) { isError = true; } expect(isError).to.be.true; }); }); describe('events', function() { it('change', function() { let didFire = false, matches = false; this.entity.on('change', (entity) => { didFire = true; matches = (this.entity === entity); }); this.entity.setValue('bar', 'Test'); expect(didFire).to.be.true; expect(matches).to.be.true; }); it('reset', function() { let didFire = false; this.entity.on('reset', () => { didFire = true; }); this.entity.setValue('bar', 'Test'); this.entity.reset(); expect(didFire).to.be.true; }); it('save', function() { let didFire = false; this.entity.on('save', () => { didFire = true; }); this.entity.setValue('bar', 'Test'); this.entity.save(); expect(didFire).to.be.true; }); it('reload', function() { let didFire = false; this.entity.on('reload', () => { didFire = true; }); this.entity.reload(); expect(didFire).to.be.true; }); it('delete', function() { let didFire = false; this.entity.on('delete', () => { didFire = true; }); this.entity.delete(); expect(didFire).to.be.true; }); it('undelete', function() { this.entity.delete(); expect(this.entity.isDeleted).to.be.true; let didFire = false; this.entity.on('undelete', () => { didFire = true; }); this.entity.undelete(); expect(didFire).to.be.true; expect(this.entity.isDeleted).to.be.false; }); it('destroy', function() { let didFire = false; this.entity.on('destroy', () => { didFire = true; }); this.entity.destroy(); expect(didFire).to.be.true; }); it('property change', function() { let didFire = false; this.entity.on('change', () => { didFire = true; }); const property = this.entity.getDisplayProperty(); property.setValue('Test'); expect(didFire).to.be.true; }); }); describe('validators', function() { it('Yup integration', function() { (async () => { // Recreate everything from beforeEach() const schema = new Schema({ name: 'baz', model: { idProperty: 'foo', displayProperty: 'bar', properties: [ { name: 'foo', type: 'int' }, { name: 'bar' }, { name: 'baz', mapping: 'baz.test.val', type: 'bool', defaultValue: null, }, ], validator: yup.object({ foo: yup.number() .integer(), bar: yup.string() .required(), baz: yup.mixed(), }), }, entity: { methods: { testMethod: function() { this.azx = 'test me'; }, }, statics: { azx: null, }, }, }), data = { foo: 1, bar: 'one', baz: { test: { val: true, }, }, }, entity = new Entity(schema, data); entity.initialize(); // END recreate let didFire = false; entity.on('changeValidity', (entity) => { didFire = true; }); // Initial condition expect(entity.isValid).to.be.null; await entity.validate(); expect(entity.isValid).to.be.true; expect(didFire).to.be.true; // Set a property to be invalid entity.bar = null; await entity.validate(); expect(entity.isValid).to.be.false; expect(entity.validationError).to.match(/bar must be a `string`/); // Restore validity entity.bar = 'test'; await entity.validate(); expect(entity.isValid).to.be.true; expect(!entity.validationError).to.be.true; })(); }); it('Joi integration', function() { (async () => { // Recreate everything from beforeEach() const schema = new Schema({ name: 'baz', model: { idProperty: 'foo', displayProperty: 'bar', properties: [ { name: 'foo', type: 'int' }, { name: 'bar' }, { name: 'baz', mapping: 'baz.test.val', type: 'bool', defaultValue: null, }, ], validator: Joi.object({ foo: Joi.number() .integer(), bar: Joi.string() .required(), baz: Joi.any(), }), }, entity: { methods: { testMethod: function() { this.azx = 'test me'; }, }, statics: { azx: null, }, }, }), data = { foo: 1, bar: 'one', baz: { test: { val: true, }, }, }, entity = new Entity(schema, data); entity.initialize(); // END recreate let didFire = false; entity.on('changeValidity', (entity) => { didFire = true; }); // Initial condition expect(entity.isValid).to.be.null; await entity.validate(); expect(entity.isValid).to.be.true; expect(didFire).to.be.true; // Set a property to be invalid entity.bar = null; await entity.validate(); expect(entity.isValid).to.be.false; expect(entity.validationError).to.match(/"bar" must be a string/); // Restore validity entity.bar = 'test'; await entity.validate(); expect(entity.isValid).to.be.true; expect(!entity.validationError).to.be.true; })(); }); }); describe('tree', function() { // Needed for all tree tests... const schema = new Schema({ name: 'nodes', model: { idProperty: 'id', displayProperty: 'display', parentIdProperty: 'parent_id', depthProperty: 'depth', hasChildrenProperty: 'hasChildren', isTree: true, isClosureTable: true, properties: [ { name: 'id', type: 'int' }, { name: 'display' }, { name: 'parent_id', type: 'int' }, { name: 'depth', type: 'int' }, { name: 'hasChildren', type: 'bool' }, ], }, }), data = { id: 2, display: 'Child 1', parent_id: 1, depth: 1, hasChildren: true, }, creatEntity = async () => { const entity = new Entity(schema, data); entity.initialize(); return entity; }, destoryEntity = (entity) => { entity.destroy(); }; it('magic properties', function() { (async () => { const entity = await creatEntity(); expect(entity.parentId).to.be.eq(1); expect(entity.depth).to.be.eq(1); expect(entity.hasChildren).to.be.true; destoryEntity(entity); })(); }); it('parents/children', function() { (async () => { const entity = await creatEntity(); const parent = this.entity; entity.parent = parent; expect(entity.parent).to.be.eq(parent); const children = [this.entity]; entity.children = children; expect(entity.children).to.be.eq(children); destoryEntity(entity); })(); }); it('ensureTree', function() { (async () => { const entity = await creatEntity(); expect(entity.ensureTree()).to.be.true; entity.isTree = false; expect(entity.ensureTree()).to.be.false; destoryEntity(entity); })(); }); }); });