Skip to content

Commit

Permalink
Unit Tests for Common Functions: Data Manipulation (elastic#442)
Browse files Browse the repository at this point in the history
* unit tests for altercolumn

* unit tests for as

* Added tests for as

* fixed typo

* Refactored alterColumn to not mutate context. Updated unit tests for altercolumn

* Updated unit tests for as

* Updated altercolumn to return original context when name and type are not provided as args

* Added unit tests for columns. Refactored columns function to return empty array of rows if columns is empty instead of an array of empty objects

* Changed describe from 'as' to 'columns'

* Unit tests for getCell. Added throw for invalid column in getCell

* Unit tests for head

* Added functionWrapper that fills in default args

* fixed merge conflict

* Added default name for as function. Added unit test for as

* Updated tests for getCell and head

* Cleaned up unit tests for alterColumn

* Updated unit tests for as

* Cleaned up tests for columns

* Cleaned up tests for getCell

* Cleaned up tests for head

* Added unit tests for tail

* Unit tests for rowCount

* Added throw for missing column name and default for value in staticColumn. Added unit tests for staticColumn

* Removed .only from unit tests for rowCount

* Fixed typo

* Removed jsonquery function. Moving to canvas-extras

* Added alias for args._ in sort and removed aliases key from function definition

* Added logic to sort on first column by default if column isn't provided as an arg. Added unit tests for sort

* Added test for staticColumn

* Added throw for missing column name. Refactored mapColumn to not mutate context. Added unit tests for mapColumn

* Merged functionWrapper from common-tests

* Removed compare unit tests from this branch. Unit tests for compare are in PR elastic#415

* WIP: unit tests for ply

* WIP: unit tests for ply

* Fixed tests for mapColumn

* Added unit tests for ply. Add checks for missing args in ply

* Fixed unit tests for ply

* Changed functionWrapper to function_wrapper and fixed import paths. Removed .js extension in imports.

* Added check for column arg in alterColumn. Cleaned up tests for alterColumn

* Cleaned up tests for alterColumn and as

* Added tests to alterColumn

* Cleaned up columns tests

* Cleaned up getCell tests

* Cleaned up tests for head and tail

* Cleaned up mapColumn tests

* Cleaned up tests for mapColumn, ply, and sort

* Cleaned up tests for staticColumn

* Cleaned up tests

* Fixed jquery version

* Changed alterColumn, mapColumn, and staticColumn to overwrite existing columns if provided name of existing columns

* fixed tests for ply

* Fixed spacing in unit tests

* Fixed typo

* Condensed tests. Used '.and' where applicable.

* Changed getCell to default to first column if args._ is missing
  • Loading branch information
cqliu1 authored Apr 18, 2018
1 parent 70d94d9 commit a35ff01
Show file tree
Hide file tree
Showing 25 changed files with 1,012 additions and 103 deletions.
10 changes: 10 additions & 0 deletions __tests__/helpers/function_wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { mapValues } from 'lodash';

export const functionWrapper = fnSpec => {
const spec = fnSpec();
const defaultArgs = mapValues(spec.args, argSpec => {
return argSpec.default;
});

return (context, args, handlers) => spec.fn(context, { ...defaultArgs, ...args }, handlers);
};
210 changes: 210 additions & 0 deletions common/functions/__tests__/alterColumn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import expect from 'expect.js';
import { alterColumn } from '../alterColumn';
import { functionWrapper } from '../../../__tests__/helpers/function_wrapper';
import { emptyTable, testTable } from './fixtures/test_tables';

describe('alterColumn', () => {
const fn = functionWrapper(alterColumn);
const nameColumnIndex = testTable.columns.findIndex(({ name }) => name === 'name');
const timeColumnIndex = testTable.columns.findIndex(({ name }) => name === 'time');
const priceColumnIndex = testTable.columns.findIndex(({ name }) => name === 'price');
const inStockColumnIndex = testTable.columns.findIndex(({ name }) => name === 'in_stock');

it('returns a datatable', () => {
const alteredTable = fn(testTable, { column: 'price', type: 'string', name: 'priceString' });

expect(alteredTable.type).to.be('datatable');
});

describe('args', () => {
it('returns original context if no args are provided', () => {
expect(fn(testTable)).to.eql(testTable);
});

describe('column', () => {
// ISO 8601 string -> date
it('specifies which column to alter', () => {
const dateToString = fn(testTable, { column: 'time', type: 'string', name: 'timeISO' });
const originalColumn = testTable.columns[timeColumnIndex];
const newColumn = dateToString.columns[timeColumnIndex];
const arbitraryRowIndex = 6;

expect(newColumn.name).to.not.be(originalColumn.name);
expect(newColumn.type).to.not.be(originalColumn.type);
expect(dateToString.rows[arbitraryRowIndex].timeISO).to.be.a('string');
expect(new Date(dateToString.rows[arbitraryRowIndex].timeISO)).to.eql(
new Date(testTable.rows[arbitraryRowIndex].time)
);
});

it('returns original context if column is not specified', () => {
expect(fn(testTable, { type: 'date', name: 'timeISO' })).to.eql(testTable);
});

it('throws if column does not exists', () => {
expect(() => fn(emptyTable, { column: 'foo', type: 'number' })).to.throwException(e => {
expect(e.message).to.be("Column not found: 'foo'");
});
});
});

describe('type', () => {
it('converts the column to the specified type', () => {
const dateToString = fn(testTable, { column: 'time', type: 'string', name: 'timeISO' });

expect(dateToString.columns[timeColumnIndex].type).to.be('string');
expect(dateToString.rows[timeColumnIndex].timeISO).to.be.a('string');
expect(new Date(dateToString.rows[timeColumnIndex].timeISO)).to.eql(
new Date(testTable.rows[timeColumnIndex].time)
);
});

it('does not change column if type is not specified', () => {
const unconvertedColumn = fn(testTable, { column: 'price', name: 'foo' });
const originalType = testTable.columns[priceColumnIndex].type;
const arbitraryRowIndex = 2;

expect(unconvertedColumn.columns[priceColumnIndex].type).to.be(originalType);
expect(unconvertedColumn.rows[arbitraryRowIndex].foo).to.be.a(
originalType,
testTable.rows[arbitraryRowIndex].price
);
});

it('throws when converting to an invalid type', () => {
expect(() => fn(testTable, { column: 'name', type: 'foo' })).to.throwException(e => {
expect(e.message).to.be('Cannot convert to foo');
});
});
});

describe('name', () => {
it('changes column name to specified name', () => {
const dateToString = fn(testTable, { column: 'time', type: 'date', name: 'timeISO' });
const arbitraryRowIndex = 8;

expect(dateToString.columns[timeColumnIndex].name).to.be('timeISO');
expect(dateToString.rows[arbitraryRowIndex]).to.have.property('timeISO');
});

it('overwrites existing column if provided an existing column name', () => {
const overwriteName = fn(testTable, { column: 'time', type: 'string', name: 'name' });
const originalColumn = testTable.columns[timeColumnIndex];
const newColumn = overwriteName.columns[nameColumnIndex];
const arbitraryRowIndex = 5;

expect(newColumn.name).to.not.be(originalColumn.name);
expect(newColumn.type).to.not.be(originalColumn.type);
expect(overwriteName.rows[arbitraryRowIndex].name).to.be.a('string');
expect(new Date(overwriteName.rows[arbitraryRowIndex].name)).to.eql(
new Date(testTable.rows[arbitraryRowIndex].time)
);
});

it('retains original column name if name is not provided', () => {
const unchangedName = fn(testTable, { column: 'price', type: 'string' });

expect(unchangedName.columns[priceColumnIndex].name).to.be(
testTable.columns[priceColumnIndex].name
);
});
});
});

describe('valid type conversions', () => {
it('converts number <-> string', () => {
const arbitraryRowIndex = 4;
const numberToString = fn(testTable, { column: 'price', type: 'string' });

expect(numberToString.columns[priceColumnIndex])
.to.have.property('name', 'price')
.and.to.have.property('type', 'string');
expect(numberToString.rows[arbitraryRowIndex].price)
.to.be.a('string')
.and.to.eql(testTable.rows[arbitraryRowIndex].price);

const stringToNumber = fn(numberToString, { column: 'price', type: 'number' });

expect(stringToNumber.columns[priceColumnIndex])
.to.have.property('name', 'price')
.and.to.have.property('type', 'number');
expect(stringToNumber.rows[arbitraryRowIndex].price)
.to.be.a('number')
.and.to.eql(numberToString.rows[arbitraryRowIndex].price);
});

it('converts date <-> string', () => {
const arbitraryRowIndex = 4;
const dateToString = fn(testTable, { column: 'time', type: 'string' });

expect(dateToString.columns[timeColumnIndex])
.to.have.property('name', 'time')
.and.to.have.property('type', 'string');
expect(dateToString.rows[arbitraryRowIndex].time).to.be.a('string');
expect(new Date(dateToString.rows[arbitraryRowIndex].time)).to.eql(
new Date(testTable.rows[arbitraryRowIndex].time)
);

const stringToDate = fn(dateToString, { column: 'time', type: 'date' });

expect(stringToDate.columns[timeColumnIndex])
.to.have.property('name', 'time')
.and.to.have.property('type', 'date');
expect(new Date(stringToDate.rows[timeColumnIndex].time))
.to.be.a(Date)
.and.to.eql(new Date(dateToString.rows[timeColumnIndex].time));
});

it('converts date <-> number', () => {
const dateToNumber = fn(testTable, { column: 'time', type: 'number' });
const arbitraryRowIndex = 1;

expect(dateToNumber.columns[timeColumnIndex])
.to.have.property('name', 'time')
.and.to.have.property('type', 'number');
expect(dateToNumber.rows[arbitraryRowIndex].time)
.to.be.a('number')
.and.to.eql(testTable.rows[arbitraryRowIndex].time);

const numberToDate = fn(dateToNumber, { column: 'time', type: 'date' });

expect(numberToDate.columns[timeColumnIndex])
.to.have.property('name', 'time')
.and.to.have.property('type', 'date');
expect(new Date(numberToDate.rows[arbitraryRowIndex].time))
.to.be.a(Date)
.and.to.eql(testTable.rows[arbitraryRowIndex].time);
});

it('converts bool <-> number', () => {
const booleanToNumber = fn(testTable, { column: 'in_stock', type: 'number' });
const arbitraryRowIndex = 7;

expect(booleanToNumber.columns[inStockColumnIndex])
.to.have.property('name', 'in_stock')
.and.to.have.property('type', 'number');
expect(booleanToNumber.rows[arbitraryRowIndex].in_stock)
.to.be.a('number')
.and.to.eql(booleanToNumber.rows[arbitraryRowIndex].in_stock);

const numberToBoolean = fn(booleanToNumber, { column: 'in_stock', type: 'boolean' });

expect(numberToBoolean.columns[inStockColumnIndex])
.to.have.property('name', 'in_stock')
.and.to.have.property('type', 'boolean');
expect(numberToBoolean.rows[arbitraryRowIndex].in_stock)
.to.be.a('boolean')
.and.to.eql(numberToBoolean.rows[arbitraryRowIndex].in_stock);
});

it('converts any type -> null', () => {
const stringToNull = fn(testTable, { column: 'name', type: 'null' });
const arbitraryRowIndex = 0;

expect(stringToNull.columns[nameColumnIndex])
.to.have.property('name', 'name')
.and.to.have.property('type', 'null');
expect(stringToNull.rows[arbitraryRowIndex].name).to.be(null);
});
});
});
39 changes: 39 additions & 0 deletions common/functions/__tests__/as.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import expect from 'expect.js';
import { asFn } from '../as';
import { functionWrapper } from '../../../__tests__/helpers/function_wrapper';

describe('as', () => {
const fn = functionWrapper(asFn);

it('returns a datatable with a single column and single row', () => {
expect(fn('foo', { _: 'bar' })).to.eql({
type: 'datatable',
columns: [{ name: 'bar', type: 'string' }],
rows: [{ bar: 'foo' }],
});

expect(fn(2, { _: 'num' })).to.eql({
type: 'datatable',
columns: [{ name: 'num', type: 'number' }],
rows: [{ num: 2 }],
});

expect(fn(true, { _: 'bool' })).to.eql({
type: 'datatable',
columns: [{ name: 'bool', type: 'boolean' }],
rows: [{ bool: true }],
});
});

describe('args', () => {
describe('_', () => {
it('sets the column name of the resulting datatable', () => {
expect(fn(null, { _: 'foo' }).columns[0].name).to.eql('foo');
});

it("returns a datatable with the column name 'value'", () => {
expect(fn(null).columns[0].name).to.eql('value');
});
});
});
});
114 changes: 114 additions & 0 deletions common/functions/__tests__/columns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import expect from 'expect.js';
import { columns } from '../columns';
import { functionWrapper } from '../../../__tests__/helpers/function_wrapper';
import { emptyTable, testTable } from './fixtures/test_tables';

describe('columns', () => {
const fn = functionWrapper(columns);

it('returns a datatable', () => {
expect(fn(testTable, { include: 'name' }).type).to.be('datatable');
});

describe('args', () => {
it('returns a datatable with included columns and without excluded columns', () => {
const arbitraryRowIndex = 7;
const result = fn(testTable, {
include: 'name, price, quantity, foo, bar',
exclude: 'price, quantity, fizz, buzz',
});

expect(result.columns[0]).to.have.property('name', 'name');
expect(result.rows[arbitraryRowIndex])
.to.have.property('name', testTable.rows[arbitraryRowIndex].name)
.and.to.not.have.property('price')
.and.to.not.have.property('quantity')
.and.to.not.have.property('foo')
.and.to.not.have.property('bar')
.and.to.not.have.property('fizz')
.and.to.not.have.property('buzz');
});

it('returns original context if args are not provided', () => {
expect(fn(testTable)).to.eql(testTable);
});

it('returns an empty datatable if include and exclude both reference the same column(s)', () => {
expect(fn(testTable, { include: 'price', exclude: 'price' })).to.eql(emptyTable);

expect(
fn(testTable, {
include: 'price, quantity, in_stock',
exclude: 'price, quantity, in_stock',
})
).to.eql(emptyTable);
});

describe('include', () => {
it('returns a datatable with included columns only', () => {
const arbitraryRowIndex = 3;
const result = fn(testTable, {
include: 'name, time, in_stock',
});

expect(result.columns).to.have.length(3);
expect(Object.keys(result.rows[0])).to.have.length(3);

expect(result.columns[0]).to.have.property('name', 'name');
expect(result.columns[1]).to.have.property('name', 'time');
expect(result.columns[2]).to.have.property('name', 'in_stock');
expect(result.rows[arbitraryRowIndex])
.to.have.property('name', testTable.rows[arbitraryRowIndex].name)
.and.to.have.property('time', testTable.rows[arbitraryRowIndex].time)
.and.to.have.property('in_stock', testTable.rows[arbitraryRowIndex].in_stock);
});

it('ignores invalid columns', () => {
const arbitraryRowIndex = 6;
const result = fn(testTable, {
include: 'name, foo, bar',
});

expect(result.columns[0]).to.have.property('name', 'name');
expect(result.rows[arbitraryRowIndex])
.to.have.property('name', testTable.rows[arbitraryRowIndex].name)
.and.to.not.have.property('foo')
.and.to.not.have.property('bar');
});

it('returns an empty datable if include only has invalid columns', () => {
expect(fn(testTable, { include: 'foo, bar' })).to.eql(emptyTable);
});
});

describe('exclude', () => {
it('returns a datatable without excluded columns', () => {
const arbitraryRowIndex = 5;
const result = fn(testTable, { exclude: 'price, quantity, foo, bar' });

expect(result.columns.length).to.equal(testTable.columns.length - 2);
expect(Object.keys(result.rows[0])).to.have.length(testTable.columns.length - 2);
expect(result.rows[arbitraryRowIndex])
.to.not.have.property('price')
.and.to.not.have.property('quantity')
.and.to.not.have.property('foo')
.and.to.not.have.property('bar');
});

it('ignores invalid columns', () => {
const arbitraryRowIndex = 1;
const result = fn(testTable, { exclude: 'time, foo, bar' });

expect(result.columns.length).to.equal(testTable.columns.length - 1);
expect(result.rows[arbitraryRowIndex])
.to.not.have.property('time')
.and.to.not.have.property('foo')
.and.to.not.have.property('bar');
});

it('returns original context if exclude only references invalid column name(s)', () => {
expect(fn(testTable, { exclude: 'foo, bar, fizz, buzz' })).to.eql(testTable);
});
});
});
});
Loading

0 comments on commit a35ff01

Please sign in to comment.