editable-grid
Version:
Bootstrap grid with CRUD functionality.
738 lines (648 loc) • 25.1 kB
JavaScript
require('./loader');
var _ = require('underscore'),
$ = require('jquery'),
expect = require('chai').expect,
gridUtils = require('gridUtils'),
sinon = require('sinon'),
Ears = require('elephant-ears');
describe('Grid Utils', function () {
beforeEach(function () {
this.sandbox = sinon.sandbox.create();
});
afterEach(function () {
this.sandbox.restore();
});
it('Should return whether a column is sorted', function () {
var options = {
id: 'id',
sortConfig: [
{
id: 'col-a',
asc: true
},
{
id: 'col-b',
asc: false
}
],
columns: [
{
id: 'col-a'
},
{
id: 'col-b'
},
{
id: 'col-c'
}
]
};
var utils = gridUtils(options);
expect(utils._isColumnSorted('col-a')).to.equal('asc');
expect(utils._isColumnSorted('col-b')).to.equal('desc');
expect(utils._isColumnSorted('col-c')).to.be.null;
});
it('Should set sort type according to column type', function () {
var utils = gridUtils.call({}, {});
expect(utils._getSortType('text')).to.equal('string');
expect(utils._getSortType('date')).to.equal('date');
expect(utils._getSortType('select')).to.equal('string');
expect(utils._getSortType('cost')).to.equal('float');
expect(utils._getSortType('percent')).to.equal('float');
expect(utils._getSortType('boolean')).to.equal('boolean');
expect(utils._getSortType('integer')).to.equal('integer');
});
it('Should determine what to do when a column is clicked', function () {
var options = {
id: 'id',
sortConfig: [
{
id: 'col-a',
asc: true
},
{
id: 'col-b',
asc: false
}
],
columns: [
{
id: 'col-a',
sortable: true,
type: 'string',
sortType: 'foo'
},
{
id: 'col-b',
sortable: true,
type: 'cost'
},
{
id: 'col-c',
sortable: false,
type: 'select'
},
{
id: 'col-d',
sortable: false,
type: 'date'
},
{
id: 'col-e',
sortable: false,
type: 'percent'
}
],
data: []
};
var grid = {
render: function () {
},
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var spy = this.sandbox.spy(utils, '_sort'),
spyGetSortType = this.sandbox.spy(utils, '_getSortType'),
beforeSortCallback = this.sandbox.spy(),
afterSortCallback = this.sandbox.spy();
grid.ears.on('editable-before-sort', beforeSortCallback);
grid.ears.on('editable-after-sort', afterSortCallback);
expect(spy.callCount).to.equal(0);
utils._columnClicked('col-a');
expect(spy.callCount).to.equal(1);
expect(spy.args[0][0]).to.equal('col-a');
expect(spy.args[0][1]).to.equal('desc');
expect(options.sortConfig).to.have.length(2);
expect(options.sortConfig[0].asc).to.be.false;
expect(spyGetSortType.args[0][0]).to.equal('foo');
utils._columnClicked('col-b');
expect(spy.callCount).to.equal(2);
expect(spy.args[1][0]).to.equal('col-b');
expect(spy.args[1][1]).to.be.null;
expect(options.sortConfig).to.have.length(1);
utils._columnClicked('col-b');
expect(spy.callCount).to.equal(3);
expect(spy.args[2][0]).to.equal('col-b');
expect(spy.args[2][1]).to.equal('asc');
expect(options.sortConfig).to.have.length(1);
expect(options.sortConfig[0].asc).to.be.true;
// sort function should not be called as column is not allowed to be sorted
utils._columnClicked('col-c');
expect(spy.callCount).to.equal(3);
expect(beforeSortCallback.callCount).to.equal(3);
expect(afterSortCallback.callCount).to.equal(3);
});
it('Should parse the values and add a new row', function () {
var data = [];
var options = {
id: 'id',
el: $('<div><div class="editable-footer-table"><table><tr class="new-row">' +
'<td data-col-id="string"><input value="hello"/></td>' +
'<td data-col-id="cost"><input value="500.36"/></td>' +
'<td data-col-id="percent"><input value="45.5"/></td>' +
'<td data-col-id="date"><input value="2014-01-01"/></td>' +
'<td data-col-id="select"><select><option value="a"></option>' +
'<option value="b"></option></td>' +
'</tr></table></div></div>'),
columns: [
{
id: 'string',
type: 'string',
parser: function (id, value) {
return value;
}
},
{
id: 'cost',
type: 'cost',
parser: function (id, value) {
return parseFloat(value);
}
},
{
id: 'percent',
type: 'percent',
parser: function (id, value) {
return parseFloat(value) / 100;
}
},
{
id: 'date',
type: 'date',
parser: function (id, value) {
return value;
}
},
{
id: 'select',
type: 'select',
list: ['a', 'b', 'c'],
parser: function (id, value) {
return value;
}
}
],
data: data
};
var grid = {
data: data,
dataOrder: _.clone(data),
render: function () {
},
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var spy = this.sandbox.spy(grid, 'render'),
callback = this.sandbox.spy();
grid.ears.on('editable-new-row', callback);
expect(spy.callCount).to.equal(0);
expect(options.data).to.have.length(0);
expect(grid.dataOrder).to.have.length(0);
expect(callback.callCount).to.equal(0);
utils._newRowClicked();
expect(spy.callCount).to.equal(1);
expect(options.data).to.have.length(1);
expect(grid.dataOrder).to.have.length(1);
expect(options.data[0].id).to.equal('-1');
expect(options.data[0].string).to.equal('hello');
expect(options.data[0].cost).to.equal(500.36);
expect(options.data[0].percent).to.equal(0.455);
expect(options.data[0].date).to.equal('2014-01-01');
expect(options.data[0].select).to.equal('a');
expect(callback.callCount).to.equal(1);
expect(callback.args[0][0].id).to.equal('-1');
});
it('Should update the data on an input change event', function () {
var options = {
el: $('<div><div class="editable-body-table"><table><tr data-row-id="row-id">' +
'<td data-col-id="col-a"><input/></td>' +
'<td data-col-id="nested.foo"><input/></td></tr></table></div></div>'),
id: 'id',
columns: [
{
id: 'col-a',
parser: function (id, value) {
return 'a' + value;
},
formatter: function (id, value) {
return value;
}
},
{
id: 'nested.foo',
parser: function (id, value) {
return value;
},
formatter: function (id, value) {
return value;
}
}
],
data: [
{
id: 'row-id',
'col-a': 'c'
}
]
};
var grid = {
ears: new Ears(),
render: function () {
}
};
var utils = gridUtils.call(grid, options);
var callback = this.sandbox.spy();
grid.ears.on('editable-value-updated', callback);
expect(callback.callCount).to.equal(0);
expect(options.data[0]['col-a']).to.equal('c');
utils._valueChanged('row-id', 'col-a', 'b');
expect(options.data[0]['col-a']).to.equal('ab');
expect(callback.callCount).to.equal(1);
expect(callback.args[0][0].colId).to.equal('col-a');
expect(callback.args[0][0].rowId).to.equal('row-id');
expect(callback.args[0][0].value).to.equal('ab');
var input = options.el.find('input').eq(0);
expect(input.val()).to.equal('ab');
expect(options.data[0].nested).to.be.undefined;
utils._valueChanged('row-id', 'nested.foo', 'bar');
expect(options.data[0].nested.foo).to.equal('bar');
expect(callback.callCount).to.equal(2);
expect(callback.args[1][0].colId).to.equal('nested.foo');
expect(callback.args[1][0].rowId).to.equal('row-id');
expect(callback.args[1][0].value).to.equal('bar');
input = options.el.find('input').eq(1);
expect(input.val()).to.equal('bar');
});
it('Should parse the values when a new value changes', function () {
var data = [];
var options = {
id: 'id',
el: $('<div><div class="editable-footer-table"><table><tr class="new-row">' +
'<td data-col-id="string"><input value="hello"/></td>' +
'<td data-col-id="cost"><input value="500.36"/></td>' +
'<td data-col-id="percent"><input value="45.5"/></td>' +
'<td data-col-id="date"><input value="2014-01-01"/></td>' +
'<td data-col-id="checkbox"><input type="checkbox" checked="checked"/></td>' +
'<td data-col-id="select"><select><option value="a"></option>' +
'<option value="b"></option></td>' +
'</tr><table></div></div>'),
columns: [
{
id: 'string',
type: 'string',
parser: function (id, value) {
return value;
}
},
{
id: 'cost',
type: 'cost',
parser: function (id, value) {
return parseFloat(value);
}
},
{
id: 'percent',
type: 'percent',
parser: function (id, value) {
return parseFloat(value) / 100;
}
},
{
id: 'date',
type: 'date',
parser: function (id, value) {
return value;
}
},
{
id: 'select',
type: 'select',
list: ['a', 'b', 'c'],
parser: function (id, value) {
return value;
}
},
{
id: 'checkbox',
type: 'checkbox'
}
],
data: data
};
var grid = {
data: data,
dataOrder: _.clone(data),
render: function () {
},
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var callback = this.sandbox.spy();
grid.ears.on('editable-new-row-value-changed', callback);
expect(callback.callCount).to.equal(0);
utils._newRowChanged('colId');
expect(callback.callCount).to.equal(1);
expect(callback.args[0][0].string).to.equal('hello');
expect(callback.args[0][0].cost).to.equal(500.36);
expect(callback.args[0][0].percent).to.equal(0.455);
expect(callback.args[0][0].date).to.equal('2014-01-01');
expect(callback.args[0][0].select).to.equal('a');
expect(callback.args[0][0].checkbox).to.be.true;
expect(callback.args[0][1]).to.equal('colId');
});
it('Should fire an event when a row is clicked', function () {
var options = {
id: 'id',
columns: [
{
id: 'col-a',
parser: function (id, value) {
return 'a' + value;
}
},
{
id: 'nested.foo',
parser: function (id, value) {
return value;
}
}
],
data: [
{
id: 'row-id',
'col-a': 'c'
}
]
};
var grid = {
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var callback = this.sandbox.spy();
grid.ears.on('editable-row-clicked', callback);
expect(callback.callCount).to.equal(0);
expect(options.data[0]['col-a']).to.equal('c');
utils._rowClicked('row-id', 'col-a');
expect(callback.callCount).to.equal(1);
expect(callback.args[0][0].obj.id).to.equal('row-id');
expect(callback.args[0][0].rowId).to.equal('row-id');
expect(callback.args[0][0].colId).to.equal('col-a');
});
describe('Validation', function () {
it('Should validate input for a required field', function () {
var options = {
id: 'id',
columns: [
{
id: 'cost-col',
validate: function (id, value) {
var cost = parseFloat(value);
if (_.isNaN(cost)) {
return 'This is an error message.';
}
return '';
}
}
]
};
var grid = {
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var cell = $('<td><input value="foo"/></td>'), // an invalid value
input = cell.find('input');
expect(cell.is('.has-error')).to.be.false;
utils._validate('row-1', 'cost-col', input);
expect(cell.is('.has-error')).to.be.true;
expect(cell.find('.validation-error.small.help-block').text())
.to.equal('Required. This is an error message.');
input.val('133'); // valid value
utils._validate('row-1', 'cost-col', input);
expect(cell.is('.has-error')).to.be.false;
expect(cell.find('.validation-error.small.help-block')).to.have.length(0);
// checkbox
expect(utils._validate('row-id', 'col-id', $('<input type="checkbox"/>'))).to.be.true;
});
it('Should validate input for a NON required field', function () {
var options = {
id: 'id',
columns: [
{
id: 'cost-col',
nullable: true,
validate: function (id, value) {
var cost = parseFloat(value);
if (_.isNaN(cost)) {
return 'This is an error message.';
}
return '';
}
}
]
};
var grid = {
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var cell = $('<td><input value="foo"/></td>'), // an invalid value
input = cell.find('input');
expect(cell.is('.has-error')).to.be.false;
utils._validate('row-1', 'cost-col', input);
expect(cell.is('.has-error')).to.be.true;
expect(cell.find('.validation-error.small.help-block').text())
.to.equal('This is an error message.');
input.val('133'); // valid value
utils._validate('row-1', 'cost-col', input);
expect(cell.is('.has-error')).to.be.false;
expect(cell.find('.validation-error.small.help-block')).to.have.length(0);
});
it('Should validate inputs when add button is fired', function () {
var options = {
id: 'id',
el: $('<div><div class="editable-footer-table"><table><tr data-row-id="new-row">' +
'<td data-col-id="cost"><input value="blah"/></td>' +
'<td data-col-id="percent"><input value="blah"/></td>' +
'</tr></table></div></div>'),
columns: [
{
id: 'cost',
validate: function (id, value) {
var cost = parseFloat(value);
if (_.isNaN(cost)) {
return 'Error with cost.';
}
return '';
}
},
{
id: 'percent',
validate: function (id, value) {
var percent = parseFloat(value);
if (_.isNaN(percent)) {
return 'Error with present.';
}
return '';
}
}
]
};
var grid = {
ears: new Ears()
};
var utils = gridUtils.call(grid, options);
var row = options.el.find('tr'),
costCell = options.el.find('td[data-col-id="cost"]'),
percentCell = options.el.find('td[data-col-id="percent"]');
expect(costCell.is('.has-error')).to.be.false;
expect(percentCell.is('.has-error')).to.be.false;
expect(utils._validateRow(row)).to.be.false;
expect(costCell.is('.has-error')).to.be.true;
expect(percentCell.is('.has-error')).to.be.true;
costCell.find('input').val('133'); // valid value
percentCell.find('input').val('20'); // valid value
expect(utils._validateRow(row)).to.be.true;
expect(costCell.is('.has-error')).to.be.false;
expect(percentCell.is('.has-error')).to.be.false;
});
});
it('Should remove the row and delete the data item', function () {
var data = [
{
id: '1'
},
{
id: '2'
},
{
id: '3'
}
];
var el = $('<div><table><tr data-row-id="1"></tr><tr data-row-id="2"></tr>' +
'<tr data-row-id="3"></tr></table></div>');
var options = {
id: 'id',
el: el,
data: data
};
var ears = new Ears();
var grid = {
ears: ears,
bodyTable: el
};
var utils = gridUtils.call(grid, options);
ears.on('editable-can-delete', function () {
return $.Deferred().resolve();
});
expect(data).to.have.length(3);
expect(el.find('tr')).to.have.length(3);
expect(el.find('tr[data-row-id="2"]')).to.have.length(1);
utils._deleteRow('2');
expect(data).to.have.length(2);
expect(data[0].id).to.equal('1');
expect(data[1].id).to.equal('3');
expect(el.find('tr')).to.have.length(2);
expect(el.find('tr[data-row-id="2"]')).to.have.length(0);
});
it('Should expand and collapse a tree node', function () {
var data = [
{
id: '1'
},
{
id: '2'
}
];
var el = $('<div><table>' +
'<tr data-row-id="1">' +
' <td><div class="tree-node tree-node-collapse"></div></td>' +
'</tr>' +
'<tr data-row-id="2">' +
' <td><div class="tree-node tree-node-collapse"></div></td>' +
'</tr></table>');
var childData = {
2: [
{
id: '10'
},
{
id: '11'
}
],
10: [
{
id: '20'
},
{
id: '21'
},
{
id: '22'
}
],
21: [
{
id: '30'
}
]
};
var options = {
id: 'id',
el: el,
data: data,
childData: function (id) {
return $.Deferred().resolve(childData[id]);
}
};
var ears = new Ears();
var grid = {
ears: ears,
bodyTable: el,
render: function () {
}
};
var renderSpy = this.sandbox.spy(grid, 'render');
var utils = gridUtils.call(grid, options);
var beforeTreeExpandCallback = this.sandbox.spy(),
afterTreeExpandCallback = this.sandbox.spy(),
beforeTreeCollapseCallback = this.sandbox.spy(),
afterTreeCollapseCallback = this.sandbox.spy();
grid.ears.on('editable-before-tree-node-expand', beforeTreeExpandCallback);
grid.ears.on('editable-after-tree-node-expand', afterTreeExpandCallback);
grid.ears.on('editable-before-tree-node-collapse', beforeTreeCollapseCallback);
grid.ears.on('editable-after-tree-node-collapse', afterTreeCollapseCallback);
utils._treeNodeExpand('2');
expect(grid._treeState['2']).to.equal('expand');
var obj = _.findWhere(options.data, {id: '2'});
expect(obj.children).to.have.length(2);
expect(obj.children[0].id).to.equal('10');
expect(renderSpy.callCount).to.equal(1);
expect(beforeTreeExpandCallback.callCount).to.equal(1);
expect(afterTreeExpandCallback.callCount).to.equal(1);
utils._treeNodeExpand('10');
expect(grid._treeState['10']).to.equal('expand');
var parent = _.findWhere(options.data, {id: '2'});
var child = _.findWhere(parent.children, {id: '10'});
expect(child.children).to.have.length(3);
expect(child.children[0].id).to.equal('20');
expect(renderSpy.callCount).to.equal(2);
expect(beforeTreeExpandCallback.callCount).to.equal(2);
expect(afterTreeExpandCallback.callCount).to.equal(2);
utils._treeNodeExpand('21');
expect(grid._treeState['21']).to.equal('expand');
parent = _.findWhere(options.data, {id: '2'});
parent = _.findWhere(parent.children, {id: '10'});
child = _.findWhere(parent.children, {id: '21'});
expect(child.children).to.have.length(1);
expect(child.children[0].id).to.equal('30');
expect(renderSpy.callCount).to.equal(3);
expect(beforeTreeExpandCallback.callCount).to.equal(3);
expect(afterTreeExpandCallback.callCount).to.equal(3);
expect(grid._treeState['10']).to.equal('expand');
utils._treeNodeCollapse('10');
expect(grid._treeState['10']).to.equal('collapse');
expect(renderSpy.callCount).to.equal(4);
expect(beforeTreeCollapseCallback.callCount).to.equal(1);
expect(afterTreeCollapseCallback.callCount).to.equal(1);
});
});