UNPKG

datamodel

Version:

Relational algebra compliant in-memory tabular data store

1,189 lines (1,089 loc) 132 kB
/* global beforeEach, describe, it, context */ /* eslint-disable no-unused-expressions, no-new */ import { expect } from 'chai'; import { FilteringMode, DataFormat } from './enums'; import { DM_DERIVATIVES } from './constants'; import DataModel from './index'; import pkg from '../package.json'; import InvalidAwareTypes from './invalid-aware-types'; import { getDerivationArguments } from './helper'; function avg(...nums) { return nums.reduce((acc, next) => acc + next, 0) / nums.length; } describe('DataModel', () => { describe('#Constructor', () => { it('should validate schema before use', () => { const data = [ { age: 30, job: 'unemployed', marital: null }, { age: 'Age', job: 'services', marital: 'married' }, { age: 22, job: undefined, marital: 'single' } ]; let schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'un-supported-type' }, ]; const mockedFn = () => { new DataModel(data, schema); }; expect(mockedFn).to.throw(); schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension', subtype: 'invalid-subtype' }, ]; expect(mockedFn).to.throw(); schema = [ { name: 'age', type: 'measure', subtype: 'invalid-subtype' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; expect(mockedFn).to.throw(); }); }); describe('#version', () => { it('should be same to the version value specified in package.json file', () => { expect(DataModel.version).to.equal(pkg.version); }); }); describe('#configureInvalidAwareTypes', () => { it('should update invalid values mapping with new configuration', () => { const data = [ { age: 30, job: 'unemployed', marital: null }, { age: 'Age', job: 'services', marital: 'married' }, { age: 22, job: undefined, marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; DataModel.configureInvalidAwareTypes({ undefined: DataModel.InvalidAwareTypes.NA }); const dataModel = new DataModel(data, schema); const dmData = dataModel.getData().data; expect(dmData[0][2] instanceof DataModel.InvalidAwareTypes).to.be.true; expect(dmData[0][2]).to.eql(DataModel.InvalidAwareTypes.NULL); expect(dmData[2][1] instanceof DataModel.InvalidAwareTypes).to.be.true; expect(dmData[2][1]).to.eql(DataModel.InvalidAwareTypes.NA); }); }); describe('#getFieldsConfig', () => { it('should return all field meta info', () => { const schema = [ { name: 'name', type: 'dimension' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ]; const data = [ { name: 'Rousan', birthday: '1995-07-05', roll: 12 }, { name: 'Sumant', birthday: '1996-08-04', roll: 89 }, { name: 'Akash', birthday: '1994-01-03', roll: 33 } ]; const dataModel = new DataModel(data, schema); const expected = { name: { index: 0, def: { name: 'name', type: 'dimension', subtype: 'categorical' }, }, birthday: { index: 1, def: { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } } }; expect(dataModel.getFieldsConfig()).to.be.deep.equal(expected); }); }); describe('#clone', () => { it('should make a new copy of the current DataModel instance', () => { const data = [ { age: 30, job: 'unemployed', marital: 'married' }, { age: 33, job: 'services', marital: 'married' }, { age: 35, job: 'management', marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; const dataModel = new DataModel(data, schema); let cloneRelation; cloneRelation = dataModel.clone(); expect(cloneRelation instanceof DataModel).to.be.true; // Check clone datamodel have all the required attribute expect(cloneRelation._colIdentifier).to.equal(dataModel._colIdentifier); expect(cloneRelation._rowDiffset).to.equal(dataModel._rowDiffset); }); it('should set parent-child relationship when saveChild is true', () => { const data = [ { age: 30, job: 'unemployed', marital: 'married' }, { age: 33, job: 'services', marital: 'married' }, { age: 35, job: 'management', marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; const dataModel = new DataModel(data, schema); const cloneDm = dataModel.clone(true); expect(cloneDm.getParent()).to.be.equal(dataModel); expect(dataModel.getChildren()[0]).to.be.equal(cloneDm); }); it('should remove parent-child relationship when saveChild is false', () => { const data = [ { age: 30, job: 'unemployed', marital: 'married' }, { age: 33, job: 'services', marital: 'married' }, { age: 35, job: 'management', marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; const dataModel = new DataModel(data, schema); const cloneDm = dataModel.clone(false); expect(cloneDm.getParent()).to.be.null; expect(dataModel.getChildren().length).to.be.equal(0); }); }); context('Test for empty DataModel', () => { let data = []; let schema = []; let edm = new DataModel(data, schema); it('should return empty data array', () => { expect(edm.getData().data).to.deep.equal([]); }); it('should return have empty fields array', () => { expect(edm.getFieldspace().fields.length).to.equal(0); }); it('should have zero columns', () => { expect(edm._colIdentifier).to.equal(''); }); it('should have empty rowDiffset', () => { expect(edm._rowDiffset).to.equal(''); }); }); context('Test for resolving schema', () => { it('should take field alternative name in schema', () => { const data = [ { age: 30, job: 'unemployed', marital_status: 'married' }, { age: 33, job: 'services', marital_status: 'married' }, { age: 35, job: 'management', marital_status: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital_status', type: 'dimension', as: 'marital' }, ]; const dm = new DataModel(data, schema); expect(dm.getFieldspace().fieldsObj().marital_status).to.be.undefined; expect(!!dm.getFieldspace().fieldsObj().marital).to.be.true; }); }); context('Test for a failing data format type', () => { let mockedDm = () => new DataModel([], [], { dataFormat: 'erroneous-data-type' }); it('should throw no coverter function found error', () => { expect(mockedDm).to.throw('No converter function found for erroneous-data-type format'); }); }); context('should cache namespace values once it is computed', () => { const data = [ { age: 30, job: 'unemployed', marital: 'married' }, { age: 33, job: 'services', marital: 'married' }, { age: 35, job: 'management', marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; const dataModel = new DataModel(data, schema); const fieldspace = dataModel.getFieldspace(); const fieldsObj = fieldspace.fieldsObj(); expect(fieldsObj).to.eql(fieldspace._cachedFieldsObj); const measureFields = fieldspace.getMeasure(); expect(measureFields).to.eql(fieldspace._cachedMeasure); const dimensionFields = fieldspace.getDimension(); expect(dimensionFields).to.eql(fieldspace._cachedDimension); }); describe('#getData', () => { it('should return the data in the specified format', () => { const schema = [ { name: 'name', type: 'dimension' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ]; const data = [ { name: 'Rousan', birthday: '1995-07-05', roll: 12 }, { name: 'Sumant', birthday: '1996-08-04', roll: 89 }, { name: 'Akash', birthday: '1994-01-03', roll: 33 } ]; const dataModel = new DataModel(data, schema); let generatedData = dataModel.getData({ order: 'row' }); let expected = { data: [ ['Rousan', new Date(1995, 7 - 1, 5).getTime()], ['Sumant', new Date(1996, 8 - 1, 4).getTime()], ['Akash', new Date(1994, 1 - 1, 3).getTime()] ], schema: [ { name: 'name', type: 'dimension', subtype: 'categorical' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ], uids: [0, 1, 2] }; expect(generatedData).to.deep.equal(expected); generatedData = dataModel.getData({ order: 'column' }); expected = { data: [ [ 'Rousan', 'Sumant', 'Akash' ], [ new Date(1995, 7 - 1, 5).getTime(), new Date(1996, 8 - 1, 4).getTime(), new Date(1994, 1 - 1, 3).getTime() ] ], schema: [ { name: 'name', type: 'dimension', subtype: 'categorical' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ], uids: [0, 1, 2] }; expect(generatedData).to.deep.equal(expected); generatedData = dataModel.getData({ order: 'row', formatter: { name: val => val.toUpperCase(), birthday: (val) => { const dm = new Date(val); return `${dm.getFullYear()}-${dm.getMonth() + 1}-${dm.getDay()}`; } } }); expected = { schema: [ { name: 'name', type: 'dimension', subtype: 'categorical' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ], data: [ ['ROUSAN', '1995-7-3'], ['SUMANT', '1996-8-0'], ['AKASH', '1994-1-1'] ], uids: [0, 1, 2] }; expect(generatedData).to.deep.equal(expected); generatedData = dataModel.getData({ order: 'column', formatter: { name: val => val.toUpperCase(), birthday: (val) => { const dm = new Date(val); return `${dm.getFullYear()}-${dm.getMonth() + 1}-${dm.getDay()}`; } } }); expected = { schema: [ { name: 'name', type: 'dimension', subtype: 'categorical' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ], data: [ ['ROUSAN', 'SUMANT', 'AKASH'], ['1995-7-3', '1996-8-0', '1994-1-1'] ], uids: [0, 1, 2] }; expect(generatedData).to.deep.equal(expected); }); it('should return sorted data according to the specified config', () => { const data = [ { performance: 'low', horsepower: 100, weight: 2 }, { performance: 'high', horsepower: 400, weight: 1 }, { performance: 'medium', horsepower: 20, weight: 1.5 }, { performance: 'decent', horsepower: 30, weight: 0.5 } ]; const schema = [ { name: 'performance', type: 'dimension', subtype: 'categorical' }, { name: 'horsepower', type: 'measure', subtype: 'continuous' }, { name: 'weight', type: 'measure', subtype: 'continuous' } ]; const dm = new DataModel(data, schema); const expected = { schema: [ { name: 'performance', type: 'dimension', subtype: 'categorical' }, { name: 'horsepower', type: 'measure', subtype: 'continuous' }, { name: 'weight', type: 'measure', subtype: 'continuous' } ], data: [ ['medium', 20, 1.5], ['decent', 30, 0.5], ['low', 100, 2], ['high', 400, 1] ], uids: [2, 3, 0, 1] }; expect(dm.getData({ sort: [['horsepower', 'asc']] })).to.deep.equal(expected); }); it('should add a column named uid if withUid is true', () => { const schema = [ { name: 'name', type: 'dimension' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' } ]; const data = [ { name: 'Rousan', birthday: '1995-07-05' }, { name: 'Sumant', birthday: '1996-08-04' }, { name: 'Akash', birthday: '1994-01-03' } ]; const dataModel = new DataModel(data, schema); const expected = { data: [ ['Rousan', new Date(1995, 7 - 1, 5).getTime(), 0], ['Sumant', new Date(1996, 8 - 1, 4).getTime(), 1], ['Akash', new Date(1994, 1 - 1, 3).getTime(), 2] ], schema: [ { name: 'name', type: 'dimension', subtype: 'categorical' }, { name: 'birthday', type: 'dimension', subtype: 'temporal', format: '%Y-%m-%d' }, { name: 'uid', type: 'identifier' } ], uids: [0, 1, 2] }; expect(dataModel.getData({ withUid: true })).to.deep.equal(expected); }); it('should return all field data when getAllFields is true', () => { const data = [ { age: 30, job: 'unemployed', marital: 'married' }, { age: 33, job: 'services', marital: 'married' }, { age: 35, job: 'management', marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' }, ]; const dataModel = new DataModel(data, schema); const dm = dataModel.project(['age', 'job']); const expected = { schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], data: [ [ 30, 'unemployed', 'married' ], [ 33, 'services', 'married' ], [ 35, 'management', 'single' ] ], uids: [ 0, 1, 2 ] }; expect(dm.getData({ getAllFields: true })).to.deep.equal(expected); }); }); describe('#project', () => { const data = [ { age: 30, education: 'tertiary', job: 'management', marital: 'married' }, { age: 59, education: 'secondary', job: 'blue-collar', marital: 'married' }, { age: 35, education: 'tertiary', job: 'management', marital: 'single' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'education', type: 'dimension' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; it('should make projection with the input fields', () => { const dataModel = new DataModel(data, schema); const projectedDataModel = dataModel.project(['age', 'job']); const expected = { data: [ [30, 'management'], [59, 'blue-collar'], [35, 'management'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, ], uids: [0, 1, 2] }; expect(dataModel === projectedDataModel).to.be.false; expect(projectedDataModel.getData()).to.deep.equal(expected); }); it('should make inverted projections', () => { const dataModel = new DataModel(data, schema); const invProjectedDataModel = dataModel.project(['age', 'job'], { mode: FilteringMode.INVERSE }); const expected = { data: [ ['tertiary', 'married'], ['secondary', 'married'], ['tertiary', 'single'] ], schema: [ { name: 'education', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2] }; expect(expected).to.deep.equal(invProjectedDataModel.getData()); }); it('should make normal and inverse projection both when mode is ALL', () => { const datamodel = new DataModel(data, schema); const dataModels = datamodel.project(['age', 'job'], { mode: FilteringMode.ALL }); const projectedModel = { data: [ [30, 'management'], [59, 'blue-collar'], [35, 'management'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, ], uids: [0, 1, 2] }; const rejectionModel = { data: [ ['tertiary', 'married'], ['secondary', 'married'], ['tertiary', 'single'] ], schema: [ { name: 'education', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2] }; expect(projectedModel).to.deep.equal(dataModels[0].getData()); expect(rejectionModel).to.deep.equal(dataModels[1].getData()); }); it('should maintain the order of column names given in project params', () => { const datamodel = new DataModel(data, schema); const dataModels = datamodel.project(['job', 'age', 'marital', 'education']); const expColumnOrder = 'job,age,marital,education'; expect(dataModels._colIdentifier).to.equal(expColumnOrder); }); it('should maintain the order of column names in fieldConfig and schema', () => { const datamodel = new DataModel(data, schema); const dataModels = datamodel.project(['job', 'age', 'marital', 'education']); const shecma = dataModels.getData().schema.map((scheme, i) => ({ name: scheme.name, index: i })); const fieldMap = dataModels.getFieldsConfig(); shecma.forEach((sch) => { expect(sch.index).to.equal(fieldMap[sch.name].index); }); }); it('should make projection by matching fields with the input regexp', () => { const dataModel = new DataModel(data, schema); const projectedDataModel = dataModel.project([/o/g, /age/g]); const expected = { schema: [ { name: 'education', type: 'dimension', subtype: 'categorical' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'age', type: 'measure', subtype: 'continuous' } ], data: [ ['tertiary', 'management', 30], ['secondary', 'blue-collar', 59], ['tertiary', 'management', 35] ], uids: [0, 1, 2] }; expect(dataModel === projectedDataModel).to.be.false; expect(projectedDataModel.getData()).to.deep.equal(expected); }); it('should store derivation criteria info', () => { const dataModel = new DataModel(data, schema); const dm = dataModel.select(fields => fields.age.value < 40); const projectedDataModel = dm.project(['age', 'job']); expect(projectedDataModel.getDerivations()[0].op).to.be.equal(DM_DERIVATIVES.PROJECT); expect(projectedDataModel.getAncestorDerivations()[0].op).to.be.equal(DM_DERIVATIVES.SELECT); }); it('should control parent-child relationships on saveChild config', () => { let rootDm = new DataModel(data, schema); let dm = rootDm.project(['age', 'job'], { saveChild: true }); expect(dm.getParent()).to.be.equal(rootDm); expect(rootDm.getChildren()[0]).to.be.equal(dm); rootDm = new DataModel(data, schema); dm = rootDm.project(['age', 'job'], { saveChild: false }); expect(dm.getParent()).to.be.null; expect(rootDm.getChildren().length).to.be.equal(0); }); }); describe('#select', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; it('should not fail with null or invalid data', () => { const dataaa = [ { age: 30, job: 'management', marital: null }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: null, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: null, marital: 'married' }, ]; const schemaa = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const expData = { data: [ [30, 'management', DataModel.InvalidAwareTypes.NULL], [28, DataModel.InvalidAwareTypes.NULL, 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 4] }; const expData2 = { data: [ [59, 'blue-collar', 'married'], [57, 'self-employed', 'married'], [28, DataModel.InvalidAwareTypes.NULL, 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [1, 3, 4] }; const dataModel = new DataModel(dataaa, schemaa); const selectedDm = dataModel.select(fields => fields.age.value < 40); const selectDm2 = dataModel.select(fields => fields.marital.value === 'married'); expect(selectDm2.getData()).to.deep.equal(expData2); expect(selectedDm.getData()).to.deep.equal(expData); }); it('should perform normal selection', () => { const dataModel = new DataModel(data, schema); const selectedDm = dataModel.select(fields => fields.age.value < 40); const expData = { data: [ [30, 'management', 'married'], [35, 'management', 'single'], [28, 'blue-collar', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 2, 4] }; // check project is not applied on the same DataModel expect(dataModel === selectedDm).to.be.false; expect(selectedDm._rowDiffset).to.equal('0,2,4'); // Check The return data expect(selectedDm.getData()).to.deep.equal(expData); }); it('should perform selection with the specified modes', () => { const dataModel = new DataModel(data, schema); const selected = dataModel.select(fields => fields.marital.value === 'married').getData(); const rejected = dataModel.select(fields => fields.marital.value === 'married', { mode: FilteringMode.INVERSE }).getData(); const selectionAll = dataModel.select(fields => fields.marital.value === 'married', { mode: FilteringMode.ALL }); expect(selected).to.deep.equal({ data: [ [30, 'management', 'married'], [59, 'blue-collar', 'married'], [57, 'self-employed', 'married'], [28, 'blue-collar', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 3, 4] }); expect(rejected).to.deep.equal({ data: [ [35, 'management', 'single'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [2] }); expect(selectionAll[0].getData()).to.deep.equal(selected); expect(selectionAll[1].getData()).to.deep.equal(rejected); }); it('should perform selection functionality in extreme condition', () => { const dataModel = new DataModel(data, schema); const selectedDm = dataModel.project(['age', 'job']).select(fields => fields.job.value === 'management'); // Check if repetition select works const selectedDm2 = selectedDm.select(fields => fields.marital.value === 'single'); let expData = { data: [ [30, 'management'], [35, 'management'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, ], uids: [0, 2] }; // check project is not applied on the same DataModel expect(dataModel === selectedDm).to.be.false; expect(selectedDm._rowDiffset).to.equal('0,2'); // Check The return data expect(selectedDm.getData()).to.deep.equal(expData); expData = { data: [ [35, 'management'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, ], uids: [2] }; expect(selectedDm2._rowDiffset).to.equal('2'); // Check The return data expect(selectedDm2.getData()).to.deep.equal(expData); }); it('should perform selection and field domain should return only selected data', () => { const dataModel = new DataModel(data, schema); const selectedDm = dataModel.select(fields => fields.age.value < 40); const expData = { data: [ [30, 'management', 'married'], [35, 'management', 'single'], [28, 'blue-collar', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 2, 4] }; // check project is not applied on the same DataModel expect(dataModel === selectedDm).to.be.false; expect(selectedDm._rowDiffset).to.equal('0,2,4'); // Check The return data expect(selectedDm.getData()).to.deep.equal(expData); expect(selectedDm.getFieldspace().fields[0].domain()).to.deep.equal([28, 35]); }); it('should provide appropriate arguments to the predicate function', () => { const dataModel = new DataModel(data, schema); let selectedDm = dataModel.select((fields, i, cloneProvider, store) => { if (!store.clonedDm) { store.clonedDm = cloneProvider(); } if (!store.avgAge) { store.avgAge = store.clonedDm.groupBy([''], { age: 'avg' }).getData().data[0][0]; } return fields.age.value > store.avgAge; }); let expData = { schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], data: [ [59, 'blue-collar', 'married'], [57, 'self-employed', 'married'] ], uids: [1, 3] }; expect(selectedDm.getData()).to.eql(expData); selectedDm = dataModel.select((fields, i) => i < 2); expData = { schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], data: [ [30, 'management', 'married'], [59, 'blue-collar', 'married'] ], uids: [0, 1] }; expect(selectedDm.getData()).to.eql(expData); }); it('should store provide proper selected datamodel with undefined mode', () => { const dataModel = new DataModel(data, schema); const dm = dataModel.project(['age', 'job', 'marital']); const selectedDm = dm.select(fields => fields.age.value < 40, { mode: undefined }); const expData = { data: [ [30, 'management', 'married'], [35, 'management', 'single'], [28, 'blue-collar', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 2, 4] }; // check project is not applied on the same DataModel expect(dataModel === selectedDm).to.be.false; expect(selectedDm._rowDiffset).to.equal('0,2,4'); // Check The return data expect(selectedDm.getData()).to.deep.equal(expData); }); it('should store derivation criteria info', () => { const dataModel = new DataModel(data, schema); const dm = dataModel.project(['age', 'job']); const selectedDm = dm.select(fields => fields.age.value < 40); expect(selectedDm.getDerivations()[0].op).to.be.equal(DM_DERIVATIVES.SELECT); expect(selectedDm.getAncestorDerivations()[0].op).to.be.equal(DM_DERIVATIVES.PROJECT); }); it('should control parent-child relationships on saveChild config', () => { let rootDm = new DataModel(data, schema); let dm = rootDm.select(fields => fields.age.value < 40, { saveChild: true }); expect(dm.getParent()).to.be.equal(rootDm); expect(rootDm.getChildren()[0]).to.be.equal(dm); rootDm = new DataModel(data, schema); dm = rootDm.select(fields => fields.age.value > 40, { saveChild: false }); expect(dm.getParent()).to.be.null; expect(rootDm.getChildren().length).to.be.equal(0); }); }); describe('#sort', () => { it('should perform sorting properly', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' } ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['age', 'desc'] ]); const expData = { data: [ [59, 'blue-collar', 'married'], [57, 'self-employed', 'married'], [35, 'management', 'single'], [30, 'management', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2, 3] }; expect(sortedDm).not.to.equal(dataModel); expect(sortedDm.getData()).to.deep.equal(expData); }); it('should perform multi sort properly', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, { age: 30, job: 'blue-collar', marital: 'single' }, ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['age', 'desc'], ['job'], ]); const expData = { data: [ [59, 'blue-collar', 'married'], [57, 'self-employed', 'married'], [35, 'management', 'single'], [30, 'blue-collar', 'single'], [30, 'management', 'married'], [28, 'blue-collar', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2, 3, 4, 5] }; expect(sortedDm.getData()).to.deep.equal(expData); }); it('should retain the order when null is provided for a field', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, { age: 30, job: 'blue-collar', marital: 'single' }, ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['job', null], ]); const expData = { data: [ [30, 'management', 'married'], [59, 'blue-collar', 'married'], [35, 'management', 'single'], [57, 'self-employed', 'married'], [28, 'blue-collar', 'married'], [30, 'blue-collar', 'single'], ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2, 3, 4, 5] }; expect(sortedDm.getData()).to.deep.equal(expData); }); it('should perform multi sort without sorting first field', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, { age: 30, job: 'blue-collar', marital: 'single' }, ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['job', null], ['age', 'desc'], ]); const expData = { data: [ [35, 'management', 'single'], [59, 'blue-collar', 'married'], [30, 'management', 'married'], [57, 'self-employed', 'married'], [30, 'blue-collar', 'single'], [28, 'blue-collar', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2, 3, 4, 5] }; expect(sortedDm.getData()).to.deep.equal(expData); }); it('should perform multi sort without sorting second field', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, { age: 30, job: 'blue-collar', marital: 'single' }, ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['job', 'asc'], ['marital', null], ['age', 'asc'], ]); const expData = { data: [ [28, 'blue-collar', 'married'], [59, 'blue-collar', 'married'], [30, 'blue-collar', 'single'], [30, 'management', 'married'], [35, 'management', 'single'], [57, 'self-employed', 'married'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2, 3, 4, 5] }; expect(sortedDm.getData()).to.deep.equal(expData); }); it('should perform multi sort while having multiple null sorted fields', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, { age: 30, job: 'blue-collar', marital: 'single' }, ]; const schema = [ { name: 'age', type: 'measure' }, { name: 'job', type: 'dimension' }, { name: 'marital', type: 'dimension' } ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['job', null], ['marital', null], ['age', 'desc'], ]); const expData = { data: [ [30, 'management', 'married'], [59, 'blue-collar', 'married'], [35, 'management', 'single'], [57, 'self-employed', 'married'], [28, 'blue-collar', 'married'], [30, 'blue-collar', 'single'] ], schema: [ { name: 'age', type: 'measure', subtype: 'continuous' }, { name: 'job', type: 'dimension', subtype: 'categorical' }, { name: 'marital', type: 'dimension', subtype: 'categorical' } ], uids: [0, 1, 2, 3, 4, 5] }; expect(sortedDm.getData()).to.deep.equal(expData); }); it('should perform sort with string data', () => { const data = [ { Name: 'Shubham', Age: '22', Gender: 'Male', Location: 'Kolkata' }, { Name: 'Teen', Age: '14', Gender: 'Female', Location: 'Kolkata' }, { Name: 'Manoj', Age: '52', Gender: 'Male', Location: 'Kolkata' }, { Name: 'Usha', Age: '49', Gender: 'Female', Location: 'Kolkata' }, { Name: 'Akash', Age: '28', Gender: 'Male', Location: 'Kolkata' }, { Name: 'Shyam', Age: '74', Gender: 'Male', Location: 'Kolkata' }, { Name: 'Baby', Age: '3', Gender: 'Male', Location: 'Kolkata' }, ]; const schema = [ { name: 'Name', type: 'dimension' }, { name: 'Age', type: 'measure' }, { name: 'Gender', type: 'dimension' }, { name: 'Location', type: 'dimension' }, ]; const dataModel = new DataModel(data, schema); const sortedDm = dataModel.sort([ ['Gender', 'desc'], ]); const expData = { schema: [ { name: 'Name', type: 'dimension', subtype: 'categorical' }, { name: 'Age', type: 'measure', subtype: 'continuous' }, { name: 'Gender', type: 'dimension', subtype: 'categorical' }, { name: 'Location', type: 'dimension', subtype: 'categorical' }, ], data: [ ['Shubham', 22, 'Male', 'Kolkata'], ['Manoj', 52, 'Male', 'Kolkata'], ['Akash', 28, 'Male', 'Kolkata'], ['Shyam', 74, 'Male', 'Kolkata'], ['Baby', 3, 'Male', 'Kolkata'], ['Teen', 14, 'Female', 'Kolkata'], ['Usha', 49, 'Female', 'Kolkata'], ], uids: [0, 1, 2, 3, 4, 5, 6] }; expect(sortedDm.getData()).to.deep.equal(expData); }); it('should perform sort with sorting function', () => { const data = [ { age: 30, job: 'management', marital: 'married' }, { age: 59, job: 'blue-collar', marital: 'married' }, { age: 35, job: 'management', marital: 'single' }, { age: 57, job: 'self-employed', marital: 'married' }, { age: 28, job: 'blue-collar', marital: 'married' }, { age: 30