datamodel
Version:
Relational algebra compliant in-memory tabular data store
1,189 lines (1,089 loc) • 132 kB
JavaScript
/* 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