canvas-datagrid
Version:
Canvas based data grid web component. Capable of displaying millions of contiguous hierarchical rows and columns without paging or loading, on a single canvas element.
1,096 lines (1,094 loc) • 136 kB
JavaScript
/*jslint browser: true*/
/*globals Event: false, describe: false, afterEach: false, beforeEach: false, after: false, it: false, canvasDatagrid: false, async: false, requestAnimationFrame: false*/
(function () {
'use strict';
var kcs = {
up: 38,
down: 40,
left: 37,
right: 39,
enter: 13,
tab: 9,
space: 32,
pgup: 33,
pgdown: 34,
a: 65,
esc: 27
},
blocks = '██████████████████',
c = {
b: 'rgb(0, 0, 255)',
y: 'rgb(255, 255, 0)',
r: 'rgb(255, 0, 0)',
fu: 'rgb(255, 0, 255)',
white: 'rgb(255, 255, 255)',
black: 'rgb(0, 0, 0)'
},
markerColors = [
'#a50026',
'#d73027',
'#f46d43',
'#fdae61',
'#fee090',
'#e0f3f8',
'#abd9e9',
'#74add1',
'#4575b4',
'#313695'
],
smallData = function () {
return [
{col1: 'foo', col2: 0, col3: 'a'},
{col1: 'bar', col2: 1, col3: 'b'},
{col1: 'baz', col2: 2, col3: 'c'}
];
};
function getC(v) {
return Object.keys(c).filter(function (k) {
return c[k] === v;
})[0] || v;
}
function itoa(n) {
var ordA = 'a'.charCodeAt(0),
ordZ = 'z'.charCodeAt(0),
len = ordZ - ordA + 1,
s = '';
while (n >= 0) {
s = String.fromCharCode(n % len + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
}
function makeData(r, c, dFn) {
var y, x, d = [];
for (y = 0; y < r; y += 1) {
d[y] = {};
for (x = 0; x < c; x += 1) {
d[y][itoa(x)] = dFn ? dFn(y, x) : '';
}
}
return d;
}
function cleanup(done) {
//HACK: this allows for DOM events to cool off?
setTimeout(done, 2);
var m = document.getElementById('mocha');
m.scrollTop = m.scrollHeight;
if (this.currentTest && this.currentTest.grid) {
this.currentTest.grid.disposeContextMenu();
}
}
function marker(grid, x, y) {
grid.markerCount = grid.markerCount || 0;
grid.markerCount += 1;
grid.addEventListener('afterdraw', function () {
grid.ctx.fillStyle = markerColors[(grid.markerCount + (markerColors.length / 2)) % markerColors.length];
grid.ctx.fillRect(0, y, grid.canvas.width, 1);
grid.ctx.fillRect(x, 0, 1, grid.canvas.height);
grid.ctx.fillStyle = markerColors[(grid.markerCount) % markerColors.length];
grid.ctx.fillRect(x - 1, y - 1, 3, 3);
});
}
function assertPxColorFn(grid, x, y, expected) {
var d, match, e;
x = x * window.devicePixelRatio;
y = y * window.devicePixelRatio;
return function (callback) {
function f() {
d = grid.ctx.getImageData(x, y, 1, 1).data;
d = 'rgb(' + [d['0'], d['1'], d['2']].join(', ') + ')';
match = d === expected;
if (expected !== undefined) {
e = new Error('Expected color ' + getC(expected) + ' but got color ' + getC(d));
if (expected && !match) { console.error(e); }
if (callback) {
marker(grid, x, y);
return callback(expected && !match ? e : undefined);
}
}
requestAnimationFrame(grid.draw);
return d;
}
if (!callback) {
return f();
}
f();
};
}
function assertPxColor(grid, x, y, expected, callback) {
return assertPxColorFn(grid, x, y, expected)(callback);
}
function de(el, event, args) {
var e = new Event(event);
Object.keys(args).forEach(function (key) {
e[key] = args[key];
});
el.dispatchEvent(e);
}
function keydown(el, keyCode, args) {
args = args || {};
args.keyCode = keyCode;
de(el, 'keydown', args);
}
function bb(el) {
return el.getBoundingClientRect();
}
function mouseup(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'mouseup', {clientX: x + p.left, clientY: y + p.top });
}
function mousemove(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'mousemove', {clientX: x + p.left, clientY: y + p.top });
}
function mousedown(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'mousedown', {clientX: x + p.left, clientY: y + p.top });
}
function contextmenu(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'contextmenu', {clientX: x + p.left, clientY: y + p.top });
}
function touchstart(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'touchstart', {touches: [{clientX: x + p.left, clientY: y + p.top }], changedTouches: [{x: 0, y: 0}]});
}
function touchend(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'touchend', {touches: [{clientX: x + p.left, clientY: y + p.top }], changedTouches: [{x: 0, y: 0}]});
}
function touchcancel(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'touchcancel', {touches: [{clientX: x + p.left, clientY: y + p.top }], changedTouches: [{x: 0, y: 0}]});
}
function touchmove(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'touchmove', {touches: [{clientX: x + p.left, clientY: y + p.top }], changedTouches: [{x: 0, y: 0}]});
}
function click(el, x, y, bbEl, ev) {
var p = bb(bbEl || el);
ev = ev || {};
ev.clientX = x + p.left;
ev.clientY = y + p.top;
de(el, 'click', ev);
}
function dblclick(el, x, y, bbEl) {
var p = bb(bbEl || el);
de(el, 'dblclick', {clientX: x + p.left, clientY: y + p.top });
}
function g(args) {
var grid,
i = document.getElementById('grid'),
a = document.createElement('div'),
t = document.createElement('div'),
d = document.createElement('div');
a.className = 'test-container';
d.className = 'grid-container';
t.className = 'grid-test-title';
t.innerHTML = args.test.title;
function poll() {
setTimeout(function () {
if (args.test.state === 'failed') {
t.classList.add('grid-test-failed');
grid.draw();
} else if (args.test.state === 'passed') {
t.classList.add('grid-test-passed');
grid.draw();
} else {
poll();
}
}, 10);
}
poll();
delete args.testTitle;
a.appendChild(t);
a.appendChild(d);
// i.appendChild(a);
i.insertBefore(a, i.firstChild);
args = args || {};
args.parentNode = d;
if (args.component) {
grid = document.createElement('canvas-datagrid');
d.appendChild(grid);
Object.keys(args).forEach(function (arg) {
if (arg === 'parentNode') { return; }
grid[arg] = args[arg];
});
} else {
grid = canvasDatagrid(args);
}
args.test.grid = grid;
return grid;
}
function assertIf(cond, msg) {
var x;
for (x = 2; x < arguments.length; x += 1) {
msg = msg.replace(/%s|%n/, arguments[x]);
}
if (cond) { return new Error(msg); }
}
describe('canvas-datagrid', function () {
after(function (done) {
// git rid of lingering artifacts from the run
mouseup(document.body, 1, 1);
mouseup(document.body, 1, 1);
click(document.body, 1, 1);
done();
});
beforeEach(cleanup);
afterEach(cleanup);
describe('Integration Tests', function () {
describe('Instantiation', function () {
it('Should be callable without arguments.', function (done) {
canvasDatagrid();
done();
});
it('Should create an instance of datagrid', function (done) {
var grid = g({test: this.test});
assertIf(!grid, 'Expected a grid instance, instead got something false');
grid.style.gridBackgroundColor = c.y;
assertPxColor(grid, 80, 32, c.y, done);
});
it('Should create, then completely annihilate the grid.', function (done) {
var grid = g({test: this.test});
grid.dispose();
done(assertIf(!grid.parentNode,
'Expected to see the grid gone, it is not.'));
});
it('Should create a grid and set data, data should be visible.', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.style.activeCellBackgroundColor = c.white;
assertIf(grid.data.length !== 3,
'Expected to see data in the interface.');
assertPxColor(grid, 80, 32, c.white, done);
});
});
if (window.customElements) {
describe('Web component', function () {
it('Should create a web component, set a style', function (done) {
var grid = g({
test: this.test,
data: smallData(),
component: true
});
grid.style.activeCellBackgroundColor = c.white;
assertIf(grid.data.length !== 3,
'Expected to see data in the interface.');
assertPxColor(grid, 80, 32, c.white, done);
});
it('Should create a web component and set a hyphenated style', function (done) {
var grid = g({
test: this.test,
data: smallData(),
component: true
});
grid.style['active-cell-background-color'] = c.white;
assertIf(grid.data.length !== 3,
'Expected to see data in the interface.');
assertPxColor(grid, 80, 32, c.white, done);
});
it('Should create a web component and set a hyphenated style with a custom prefix', function (done) {
var grid = g({
test: this.test,
data: smallData(),
component: true
});
grid.style['-cdg-active-cell-background-color'] = c.white;
assertIf(grid.data.length !== 3,
'Expected to see data in the interface.');
assertPxColor(grid, 80, 32, c.white, done);
});
it('Should create a web component and set a schema', function (done) {
var grid = g({
test: this.test,
data: [{a: blocks}],
component: true
});
grid.style.gridBackgroundColor = c.white;
grid.schema = [{name: 'a', width: 30}];
assertIf(grid.data.length !== 1,
'Expected to see data in the interface.');
assertPxColor(grid, 80, 32, c.white, done);
});
});
}
describe('Drawing', function () {
it('Should draw row selections.', function (done) {
var grid = g({
test: this.test,
data: smallData(),
selectionMode: 'row',
style: {
activeCellSelectedBackgroundColor: c.b,
cellSelectedBackgroundColor: c.b
}
});
mousemove(grid.canvas, 45, 37);
mousedown(grid.canvas, 45, 37);
mouseup(grid.canvas, 45, 37);
setTimeout(function () {
assertPxColor(grid, 80, 37, c.b, done);
}, 1);
});
it('Should draw a debug message.', function (done) {
var grid = g({
test: this.test,
data: smallData(),
debug: true
});
grid.style.activeCellBackgroundColor = c.b;
mousemove(grid.canvas, 100, 113);
assertPxColor(grid, 120, 10, 'rgb(90, 90, 90)', done);
});
// TODO: after phantomjs has been replaced, re-enable this test.
// phantom throws a nonsense error due to the way the data url is constructed in the html function
// it('Should draw HTML.', function (done) {
// var grid = g({
// test: this.test,
// data: [{a: '<span style="background: ' + c.b + ';color: ' + c.b + '">blah</span>' }],
// schema: [{name: 'a', type: 'html'}]
// });
// setTimeout(function () {
// assertPxColor(grid, 50, 32, c.b, done);
// }, 100);
// });
});
describe('Styles', function () {
it('Should set the active cell color to black.', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.style.activeCellBackgroundColor = c.black;
assertPxColor(grid, 100, 32, c.black, done);
});
});
describe('Data interface', function () {
it('Pass array of objects.', function (done) {
var grid = g({
test: this.test
});
grid.data = [
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': 5, 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
];
done(assertIf(grid.data[2].c !== 9,
'Expected grid to be able to import and export this format'));
});
it('Pass array that contain other arrays of objects.', function (done) {
var grid = g({
test: this.test
});
grid.data = [
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': [
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': 5, 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
], 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
];
//TODO: this test cannot work until cell grids are fixed https://github.com/TonyGermaneri/canvas-datagrid/issues/35
// so this test success is false
done();
});
it('Pass array that contains an array of objects with mixed object/primitives as values.', function (done) {
var grid = g({
test: this.test
});
grid.data = [
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': {'a': 0, 'b': 1, 'c': 2}, 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
];
//TODO: this test cannot work until cell grids are fixed https://github.com/TonyGermaneri/canvas-datagrid/issues/35
// so this test success is false
done();
});
it('Pass jagged data', function (done) {
var grid = g({
test: this.test
});
grid.data = [['a', 'b', 'c'], ['1', '2'], ['q']];
done(assertIf(grid.data[0][0] !== 'a',
'Expected grid to be able to import and export this format'));
});
it('Pass string to data', function (done) {
var grid = g({
test: this.test
});
grid.data = "blah";
done(assertIf(grid.data[0][0] !== 'blah',
'Expected grid to be able to import and export this format'));
});
it('Pass number to data', function (done) {
var grid = g({
test: this.test
});
grid.data = 4235234234;
done(assertIf(grid.data[0][0] !== 4235234234,
'Expected grid to be able to import and export this format'));
});
it('Pass boolean to data', function (done) {
var grid = g({
test: this.test
});
grid.data = false;
done(assertIf(grid.data[0][0] !== false,
'Expected grid to be able to import and export this format'));
});
it('Pass a function to data', function (done) {
var grid = g({
test: this.test
});
grid.data = function () {
return [
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': 5, 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
];
};
done(assertIf(grid.data[0].a !== 0,
'Expected grid to be able to import and export this format'));
});
it('Pass an async function to data', function (done) {
var grid = g({
test: this.test
});
grid.data = function (callback) {
return callback([
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': 5, 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
]);
};
done(assertIf(grid.data[0].a !== 0,
'Expected grid to be able to import and export this format'));
});
it('Pass an async function to data', function (done) {
var grid = g({
test: this.test
});
grid.data = function (callback) {
return callback([
{'a': 0, 'b': 1, 'c': 2},
{'a': 4, 'b': 5, 'c': 6},
{'a': 7, 'b': 8, 'c': 9}
]);
};
done(assertIf(grid.data[0].a !== 0,
'Expected grid to be able to import and export this format'));
});
});
describe('Public interface', function () {
it('Focus on the grid', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.focus();
done(assertIf(!grid.hasFocus, 'Expected the grid to have focus'));
});
it('Blur the grid', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.blur();
done(assertIf(grid.hasFocus, 'Expected the grid to not have focus'));
});
it('Insert column', function (done) {
var grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
grid.insertColumn({
name: 'f',
defaultValue: 'g'
}, 1);
done(assertIf(grid.schema[1].name !== 'f' || grid.data[0].f !== 'g',
'Expected to see a specific column here, it is not here.'));
});
it('Use a function as a default value', function (done) {
var grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
grid.insertColumn({
name: 'f',
defaultValue: function () { return 'g'; }
}, 1);
done(assertIf(grid.schema[1].name !== 'f' || grid.data[0].f !== 'g',
'Expected to see a specific column here, it is not here.'));
});
it('Autosize a column', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
grid.addEventListener('rendercell', function (e) {
if (e.cell.columnIndex === 1) {
e.ctx.fillStyle = c.b;
}
});
grid.autosize('d');
assertPxColor(grid, 200, 32, c.b, done);
});
it('Autosize all columns', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}],
style: {
gridBackgroundColor: c.b
}
});
grid.autosize();
assertPxColor(grid, 220, 32, c.b, done);
});
it('Add a style to the style setter', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
grid.style.gridBackgroundColor = c.b;
assertPxColor(grid, 200, 70, c.b, done);
});
it('Add an attribute to the attribute setter', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
grid.addEventListener('attributechanged', function (e) {
done(assertIf(grid.attributes.name !== 'blah', 'expected name to be blah'));
});
grid.attributes.name = 'blah';
});
it('Get visible schema', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
done(assertIf(grid.visibleSchema[0].name !== 'd', 'Expected schema to be returned'));
});
it('Get visible rows', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
done(assertIf(grid.visibleRows.length === 1, 'Expected 1 row to be returned'));
});
it('Get visible cells', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
done(assertIf(grid.visibleCells.length === 2, 'Expected 2 cells to be returned'));
});
it('Get current cell', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
mousemove(grid.canvas, 45, 37);
mousedown(grid.canvas, 45, 37);
mouseup(grid.canvas, 45, 37);
done(assertIf(grid.currentCell.rowIndex !== 0, 'Expected current cell to be rowIndex 0'));
});
it('Get offsetLeft of the parent node', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
done(assertIf(grid.offsetLeft === 0, 'Expected offsetLeft to be > 0'));
});
it('Get offsetTop of the parent node', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
done(assertIf(grid.offsetTop === 0, 'Expected offsetLeft to be > 0'));
});
it('Get the offsetParent node', function (done) {
var grid = g({
test: this.test,
data: [{d: '123456', e: '123456'}]
});
done(assertIf(grid.offsetParent === undefined, 'Expected a DOM node'));
});
it('Should throw an error if insertColumn is passed a bad index', function (done) {
var e, grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
try {
grid.insertColumn({
name: 'f',
defaultValue: 'g'
}, 5000);
} catch (er) {
e = er;
} finally {
done(assertIf(e === undefined,
'Expected insertColumn to throw an error.'));
}
});
it('Delete column', function (done) {
var grid = g({
test: this.test,
data: [{d: '', e: ''}]
}),
n = Object.keys(smallData()[0])[0];
grid.deleteColumn(0);
done(assertIf(Object.keys(grid.data[0])[0] === n || grid.schema[0].name === n,
'Expected to see column 0 deleted, but it appears to still be there.'));
});
it('Add column', function (done) {
var l, grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
grid.addColumn({
name: 'f',
defaultValue: 'g'
});
l = grid.schema.length - 1;
done(assertIf(grid.schema[l].name !== 'f' || grid.data[0].f !== 'g',
'Expected to see a specific column here, it is not here.'));
});
it('Add row', function (done) {
var l, grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e', defaultValue: 10}]
});
grid.addRow({d: '1'});
l = grid.data.length - 1;
done(assertIf(grid.data[l].d !== '1' || grid.data[l].e !== 10,
'Expected to see a specific row here, it is not here.'));
});
it('Insert row', function (done) {
var grid = g({
test: this.test,
data: [{d: '1', e: '2'}, {d: '3', e: '4'}],
schema: [{name: 'd'}, {name: 'e', defaultValue: 10}]
});
grid.insertRow({d: '6'}, 1);
done(assertIf(grid.data[2].d !== '3' || grid.data[1].e !== 10,
'Expected to see a specific row here, it is not here.'));
});
it('Should throw an error if insertRow is passed a bad index', function (done) {
var e, grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
try {
grid.insertRow({d: '6'}, 5000);
} catch (er) {
e = er;
} finally {
done(assertIf(e === undefined,
'Expected insertRow to throw an error.'));
}
});
it('Delete row', function (done) {
var grid = g({
test: this.test,
data: [{d: '1'}, {d: '2'}]
});
grid.deleteRow(1);
done(assertIf(grid.data.length !== 1 || grid.data[0].d !== '1',
'Expected to see only 1 row, expected row 1 to contain a specific value.'));
});
it('Set row height', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('rendercell', function (e) {
if (e.cell.rowIndex === 0) {
e.ctx.fillStyle = c.y;
}
});
grid.setRowHeight(0, 60);
assertPxColor(grid, 40, 80, c.y, done);
});
it('Set column width', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('rendercell', function (e) {
if (e.cell.columnIndex === 0) {
e.ctx.fillStyle = c.y;
}
});
grid.setColumnWidth(0, 10);
setTimeout(function () {
assertPxColor(grid, 35, 78, c.y, done);
}, 1);
});
it('Reset row height', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('rendercell', function (e) {
if (e.cell.rowIndex !== 0) {
e.ctx.fillStyle = c.y;
}
});
grid.setRowHeight(0, 60);
grid.resetRowHeights();
assertPxColor(grid, 90, 80, c.y, done);
});
it('Reset column width', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('rendercell', function (e) {
if (e.cell.columnIndex === 1) {
e.ctx.fillStyle = c.y;
}
});
grid.setColumnWidth(0, 10);
grid.resetColumnWidths();
assertPxColor(grid, 340, 80, c.y, done);
});
it('Inserting an impossible column index should throw an error', function (done) {
var err, grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
try {
grid.insertColumn({
name: 'f',
defaultValue: 'g'
}, 999);
} catch (e) {
err = e;
}
done(assertIf(!err, 'Expected to see an error.'));
});
it('Inserting an impossible row index should throw an error', function (done) {
var err, grid = g({
test: this.test,
data: [{d: '', e: ''}],
schema: [{name: 'd'}, {name: 'e'}]
});
try {
grid.insertrow({
d: 'f',
e: 'g'
}, 999);
} catch (e) {
err = e;
}
done(assertIf(!err, 'Expected to see an error.'));
});
it('Goto a specific row', function (done) {
var grid = g({
test: this.test,
data: makeData(30, 30)
});
grid.gotoRow(20);
done(assertIf(grid.scrollTop < 1,
'Expected scrollTop to be a little further along.'));
});
it('Fit column size to values', function (done) {
var doneCalled, grid = g({
test: this.test,
data: makeData(30, 30, function (y, x) { return x + ':' + y; })
});
grid.fitColumnToValues('a');
grid.addEventListener('rendercell', function (e) {
if (e.cell.rowIndex === 0 && e.cell.columnIndex === 0) {
if (doneCalled) { return; }
doneCalled = true;
done(assertIf(e.cell.width > 100,
'Expected column to be a little narrower.'));
}
});
grid.draw();
});
it('isCellVisible should return true when cell is visible, false when it is not', function (done) {
var grid = g({
test: this.test,
data: makeData(30, 30, function (y, x) { return x + ':' + y; })
});
done(assertIf(!grid.isCellVisible({x: 0, y: 0})
|| grid.isCellVisible({x: 0, y: 20}),
'Expected column to be a little narrower.'));
});
});
describe('Context menu', function () {
it('Should produce a context menu', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
done(assertIf(!document.body.contains(e.items[0].title), 'Expected context menu to exist in the body and be visible.'));
}, 1);
});
contextmenu(grid.canvas, 60, 37);
});
it('Clicking Order by asc should order the selected column asc', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
e.items[4].contextItemContainer.dispatchEvent(new Event('click'));
done(assertIf(grid.data[0].col1 !== 'bar',
'Expected the content to be reordered asc.'));
}, 1);
});
contextmenu(grid.canvas, 100, 37);
});
it('Should produce a context menu very wide requiring the context menu move to be fully visible', function (done) {
var d = [], x, grid = g({
test: this.test,
data: smallData()
});
for (x = 0; x < 100; x += 1) {
d.push({
title: 'veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverywide'
});
}
grid.addEventListener('contextmenu', function (e) {
e.items.push({
title: 'Child menu',
items: function () {
return d;
}
});
setTimeout(function () {
e.items[6].contextItemContainer.dispatchEvent(new Event('mouseover'));
setTimeout(function () {
done(assertIf(!e.items[6].contextMenu.container, 'Expected child context menu.'));
}, 1);
}, 1);
});
contextmenu(grid.canvas, 60, 37);
});
it('Should create a child context menu using a function that returns items', function (done) {
var d = [], x, grid = g({
test: this.test,
data: smallData()
});
for (x = 0; x < 100; x += 1) {
d.push({
title: x
});
}
grid.addEventListener('contextmenu', function (e) {
e.items.push({
title: 'Child menu',
items: function () {
return d;
}
});
setTimeout(function () {
e.items[6].contextItemContainer.dispatchEvent(new Event('mouseover'));
setTimeout(function () {
done(assertIf(!e.items[6].contextMenu.container, 'Expected child context menu.'));
}, 1);
}, 1);
});
contextmenu(grid.canvas, 60, 37);
});
it('Should create a child context menu using a function that uses a callback argument', function (done) {
var d = [], x, grid = g({
test: this.test,
data: smallData()
});
for (x = 0; x < 100; x += 1) {
d.push({
title: x
});
}
grid.addEventListener('contextmenu', function (e) {
e.items.push({
title: 'Child menu',
items: function (callback) {
return callback(d);
}
});
setTimeout(function () {
e.items[6].contextItemContainer.dispatchEvent(new Event('mouseover'));
setTimeout(function () {
done(assertIf(!e.items[6].contextMenu.container, 'Expected child context menu.'));
}, 1);
}, 1);
});
contextmenu(grid.canvas, 60, 37);
});
it('Create a child context menu and scroll up and down using mouseover events, then exit menu', function (done) {
var d = [], x, grid = g({
test: this.test,
data: smallData()
});
for (x = 0; x < 100; x += 1) {
d.push({
title: x
});
}
grid.addEventListener('contextmenu', function (e) {
e.items.push({
title: 'child menu',
items: d
});
setTimeout(function () {
e.items[6].contextItemContainer.dispatchEvent(new Event('mouseover'));
e.items[6].contextMenu.downArrow.dispatchEvent(new Event('mouseover'));
setTimeout(function () {
var err = assertIf(e.items[6].contextMenu.container.scrollTop === 0);
if (err) { return done(err); }
e.items[6].contextMenu.downArrow.dispatchEvent(new Event('mouseout'));
e.items[6].contextMenu.upArrow.dispatchEvent(new Event('mouseover'));
setTimeout(function () {
e.items[6].contextMenu.upArrow.dispatchEvent(new Event('mouseout'));
err = assertIf(e.items[6].contextMenu.container.scrollTop !== 0);
if (err) { return done(err); }
setTimeout(function () {
e.items[6].contextItemContainer.dispatchEvent(new Event('mouseout'));
done(assertIf(e.items[6].contextMenu !== undefined,
'expected child context menu to be gone.'));
}, 100);
}, 1500);
}, 1000);
}, 1);
});
contextmenu(grid.canvas, 60, 37);
}).timeout(5000);
it('Autocomplete should appear when a value is entered into the filter input', function (done) {
var grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
//HACK: get to filter input element in context menu
var i = e.items[0].title.children[1];
i.value = 'f';
i.dispatchEvent(new Event('keyup'));
done(assertIf(document.body.lastChild.childNodes.length === 1
&& document.body.lastChild.firstChild.innerHTML !== 'foo',
'Expected the autocomplete to be the most recent item added to body and expected it to only contain "foo"'));
}, 1);
});
contextmenu(grid.canvas, 100, 37);
});
it('Autocomplete keys should key down and filter', function (done) {
var err,
grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
//HACK: get to filter input element in context menu
var i = e.items[0].title.children[1];
i.value = 'b';
i.dispatchEvent(new Event('keyup'));
['down', 'enter'].forEach(function (kk) {
var ev = new Event('keydown');
ev.keyCode = kcs[kk];
i.dispatchEvent(ev);
if (kk === 'enter') {
err = assertIf(grid.data[0].col1 !== 'baz', 'Expected key combination to filter for baz');
}
});
done(err);
}, 1);
});
contextmenu(grid.canvas, 100, 37);
});
it('Autocomplete keys should key down, key up and filter', function (done) {
var err,
grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
//HACK: get to filter input element in context menu
var i = e.items[0].title.children[1];
i.value = 'b';
i.dispatchEvent(new Event('keyup'));
['down', 'up', 'enter'].forEach(function (kk) {
var ev = new Event('keydown');
ev.keyCode = kcs[kk];
i.dispatchEvent(ev);
if (kk === 'enter') {
err = assertIf(grid.data[0].col1 !== 'bar', 'Expected key combination to filter for bar');
}
});
done(err);
}, 1);
});
contextmenu(grid.canvas, 100, 37);
});
it('Autocomplete keys should key tab', function (done) {
var err,
grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
//HACK: get to filter input element in context menu
var i = e.items[0].title.children[1];
i.value = 'f';
i.dispatchEvent(new Event('keyup'));
['tab'].forEach(function (kk) {
var ev = new Event('keydown');
ev.keyCode = kcs[kk];
i.dispatchEvent(ev);
if (kk === 'tab') {
err = assertIf(grid.data[0].col1 !== 'foo', 'Expected key combination to filter for bar');
}
});
done(err);
}, 1);
});
contextmenu(grid.canvas, 100, 37);
});
it('Autocomplete keys should key esc', function (done) {
var err,
grid = g({
test: this.test,
data: smallData()
});
grid.addEventListener('contextmenu', function (e) {
setTimeout(function () {
//HACK: get to filter input element in context menu
var i = e.items[0].title.children[1];
i.value = 'f';
i.dispatchEvent(new Event('keyup'));
['esc'].forEach(function (kk) {
var ev = new Event('keydown');
ev.keyCode = kcs[kk];
i.dispatchEvent(ev);
if (kk === 'esc') {
err = assertIf(grid.data[0].col1 !== 'foo', 'Expected key combination to filter for bar');
}
});
done(err);
}, 1);
});
contextmenu(grid.canvas, 100, 37);
});