define([
|
'intern!tdd',
|
'intern/chai!assert',
|
'dojo/_base/declare',
|
'dojo/_base/lang',
|
'dojo/Deferred',
|
'dojo/on',
|
'dojo/promise/all',
|
'dojo/query',
|
'dojo/when',
|
'dijit/registry',
|
'dijit/form/TextBox',
|
'dgrid/Grid',
|
'dgrid/OnDemandGrid',
|
'dgrid/Editor',
|
'dgrid/extensions/Pagination',
|
'dgrid/test/data/createSyncStore',
|
'dgrid/test/data/orderedData',
|
'../addCss!'
|
], function (test, assert, declare, lang, Deferred, on, all, query, when, registry, TextBox,
|
Grid, OnDemandGrid, Editor, Pagination, createSyncStore, orderedData) {
|
|
var testOrderedData = orderedData.items,
|
EditorGrid = declare([ Grid, Editor ]),
|
grid;
|
|
function addDelay(result) {
|
var delay = new Deferred();
|
when(result).then(function () {
|
var args = arguments;
|
setTimeout(function () {
|
delay.resolve.apply(delay, args);
|
}, 300);
|
});
|
return delay;
|
}
|
|
test.suite('Editor mixin', function () {
|
|
test.afterEach(function () {
|
if (grid) {
|
grid.destroy();
|
grid = undefined;
|
}
|
});
|
|
test.test('canEdit - always-on (instance-per-row) editor', function () {
|
var results = {};
|
var data = [
|
{id: 1, data1: 'Data 1.a', data2: 'Data 2.a'},
|
{id: 2, data1: 'Data 1.b', data2: 'Data 2.b'},
|
{id: 3, data1: 'Data 1.c', data2: 'Data 2.c'}
|
];
|
grid = new EditorGrid({
|
columns: [
|
{
|
field: 'data1',
|
label: 'Data 1'
|
},
|
{
|
editor: 'text',
|
field: 'data2',
|
label: 'Data 2',
|
canEdit: function (object, value) {
|
results[object.id] = value;
|
}
|
}
|
]
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(data);
|
|
assert.strictEqual(results[1], 'Data 2.a',
|
'canEdit should have been called (item 1)');
|
assert.strictEqual(results[2], 'Data 2.b',
|
'canEdit should have been called (item 2)');
|
assert.strictEqual(results[3], 'Data 2.c',
|
'canEdit should have been called (item 3)');
|
});
|
|
test.test('canEdit - editOn (shared) editor', function () {
|
var results = {};
|
var data = [
|
{id: 1, data1: 'Data 1.a', data2: 'Data 2.a'},
|
{id: 2, data1: 'Data 1.b', data2: 'Data 2.b'},
|
{id: 3, data1: 'Data 1.c', data2: 'Data 2.c'}
|
];
|
grid = new EditorGrid({
|
columns: [
|
{
|
field: 'data1',
|
label: 'Data 1',
|
id: 'data1'
|
},
|
{
|
editor: TextBox,
|
editOn: 'click',
|
field: 'data2',
|
label: 'Data 2',
|
id: 'data2',
|
canEdit: function (object, value) {
|
results[object.id] = value;
|
}
|
}
|
]
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(data);
|
|
assert.isUndefined(results[1],
|
'canEdit should not have been called yet for editOn editor (item 1)');
|
assert.isUndefined(results[2],
|
'canEdit should not have been called yet for editOn editor (item 2)');
|
assert.isUndefined(results[3],
|
'canEdit should not have been called yet for editOn editor (item 3)');
|
|
// Note: The "Data 2" column's canEdit method always returns false so none of the following
|
// grid.edit calls will return a promise and no editor will receive focus.
|
grid.edit(grid.cell(1, 'data2'));
|
assert.isUndefined(results[1],
|
'canEdit should not have been called yet for editOn editor (item 1)');
|
assert.strictEqual(results[2], 'Data 2.b',
|
'canEdit should have been called for editOn editor (item 2)');
|
assert.isUndefined(results[3],
|
'canEdit should not have been called yet for editOn editor (item 3)');
|
|
grid.edit(grid.cell(0, 'data2'));
|
assert.strictEqual(results[1], 'Data 2.a',
|
'canEdit should have been called for editOn editor (item 1)');
|
assert.strictEqual(results[2], 'Data 2.b',
|
'canEdit should have been called for editOn editor (item 2)');
|
assert.isUndefined(results[3],
|
'canEdit should not have been called yet for editOn editor (item 3)');
|
|
grid.edit(grid.cell(2, 'data2'));
|
assert.strictEqual(results[1], 'Data 2.a',
|
'canEdit should have been called for editOn editor (item 1)');
|
assert.strictEqual(results[2], 'Data 2.b',
|
'canEdit should have been called for editOn editor (item 2)');
|
assert.strictEqual(results[3], 'Data 2.c',
|
'canEdit should have been called for editOn editor (item 3)');
|
});
|
|
test.test('canEdit always-on editor - suppress on false', function () {
|
var rowCount,
|
cell,
|
matchedNodes,
|
dfd = this.async();
|
|
function canEdit(data) {
|
return data.order % 2;
|
}
|
|
grid = new EditorGrid({
|
columns: {
|
order: 'step',
|
name: {
|
editor: 'text',
|
label: 'Name',
|
canEdit: canEdit
|
},
|
description: {
|
editor: 'text',
|
label: 'Description',
|
editOn: 'click',
|
canEdit: canEdit
|
}
|
}
|
});
|
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(testOrderedData);
|
rowCount = testOrderedData.length;
|
|
function testRow(rowIndex) {
|
cell = grid.cell(rowIndex, 'name');
|
addDelay(grid.edit(cell)).then(dfd.rejectOnError(function () {
|
matchedNodes = query('input', cell.element);
|
if (canEdit(cell.row.data)) {
|
assert.strictEqual(1, matchedNodes.length,
|
'Cell with canEdit=>true should have an editor element');
|
}
|
else {
|
assert.strictEqual(0, matchedNodes.length,
|
'Cell with canEdit=>false should not have an editor element');
|
}
|
rowIndex++;
|
if (rowIndex < rowCount) {
|
testRow(rowIndex);
|
}
|
else {
|
dfd.resolve();
|
}
|
}));
|
}
|
testRow(0);
|
|
return dfd;
|
});
|
|
test.test('canEdit edit-on click editor - suppress on false', function () {
|
var rowCount,
|
cell,
|
matchedNodes,
|
dfd = this.async();
|
|
function canEdit(data) {
|
return data.order % 2;
|
}
|
|
grid = new EditorGrid({
|
columns: {
|
order: 'step',
|
name: {
|
editor: 'text',
|
label: 'Name',
|
canEdit: canEdit
|
},
|
description: {
|
editor: 'text',
|
label: 'Description',
|
editOn: 'click',
|
canEdit: canEdit
|
}
|
}
|
});
|
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(testOrderedData);
|
rowCount = testOrderedData.length;
|
|
function testRow(rowIndex) {
|
cell = grid.cell(rowIndex, 'description');
|
// Waiting for the promise to resolve is not enough time for IE9 to render the input.
|
addDelay(grid.edit(cell)).then(dfd.rejectOnError(function () {
|
matchedNodes = query('input', cell.element);
|
if (canEdit(cell.row.data)) {
|
assert.strictEqual(1, matchedNodes.length,
|
'Cell with canEdit=>true should have an editor element: rowIndex = ' + rowIndex);
|
}
|
else {
|
assert.strictEqual(0, matchedNodes.length,
|
'Cell with canEdit=>false should not have an editor element: rowIndex = ' + rowIndex);
|
}
|
rowIndex++;
|
if (rowIndex < rowCount) {
|
testRow(rowIndex);
|
}
|
else {
|
dfd.resolve();
|
}
|
}));
|
}
|
testRow(0);
|
|
return dfd;
|
});
|
|
test.test('destroy editor widgets - native', function () {
|
var matchedNodes;
|
|
matchedNodes = query('input');
|
assert.strictEqual(0, matchedNodes.length,
|
'Before grid is created there should be 0 input elements on the page');
|
|
grid = new EditorGrid({
|
columns: {
|
order: 'step',
|
name: {
|
label: 'Name',
|
editor: 'text'
|
},
|
description: {
|
label: 'Description',
|
editor: 'text',
|
editOn: 'click'
|
}
|
}
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(testOrderedData);
|
|
matchedNodes = query('input');
|
assert.strictEqual(testOrderedData.length, matchedNodes.length,
|
'There should be ' + testOrderedData.length + ' input elements for the grid\'s editors');
|
|
grid.destroy();
|
|
matchedNodes = query('input');
|
assert.strictEqual(0, matchedNodes.length,
|
'After grid is destroyed there should be 0 input elements on the page');
|
});
|
|
test.test('destroy editor widgets - Dijit', function () {
|
assert.strictEqual(0, registry.length,
|
'Before grid is created there should be 0 widgets on the page');
|
|
grid = new EditorGrid({
|
columns: {
|
order: 'step',
|
name: {
|
label: 'Name',
|
editor: TextBox
|
},
|
description: {
|
label: 'Description',
|
editor: TextBox,
|
editOn: 'click'
|
}
|
}
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(testOrderedData);
|
|
// Expected is data length + 1 due to widget for editOn editor
|
assert.strictEqual(testOrderedData.length + 1, registry.length,
|
'There should be ' + (testOrderedData.length + 1) + ' widgets for the grid\'s editors');
|
|
grid.destroy();
|
|
assert.strictEqual(0, registry.length,
|
'After grid is destroyed there should be 0 widgets on the page');
|
});
|
|
test.test('editor widget startup called at appropriate time', function () {
|
var assertionMessage;
|
var AssertionTextBox = declare(TextBox, {
|
startup: function () {
|
if (this._started) {
|
return;
|
}
|
assert.isTrue(this.domNode.offsetHeight > 0,
|
assertionMessage + ': startup should not be called before widgets are in flow');
|
this.inherited(arguments);
|
}
|
});
|
|
grid = new (declare([ OnDemandGrid, Editor ]))({
|
columns: {
|
order: 'step',
|
name: {
|
label: 'Name',
|
editor: AssertionTextBox
|
},
|
description: {
|
label: 'Description',
|
editor: AssertionTextBox,
|
editOn: 'click'
|
}
|
},
|
collection: createSyncStore({
|
data: testOrderedData,
|
idProperty: 'order'
|
})
|
});
|
document.body.appendChild(grid.domNode);
|
|
assertionMessage = 'always-on';
|
grid.startup();
|
|
// Assertions will automatically run for always-on editor;
|
// test activating an editOn editor and also test updating a row
|
assertionMessage = 'editOn + edit()';
|
grid.edit(grid.cell(1, 'description'));
|
assertionMessage = 'editOn + Trackable';
|
grid.collection.put(grid.collection.getSync(2));
|
});
|
|
test.test('editor focus with always-on editor', function () {
|
var rowCount,
|
cell,
|
cellEditor,
|
dfd = this.async();
|
|
grid = new EditorGrid({
|
columns: {
|
order: 'step',
|
name: {
|
label: 'Name',
|
editor: 'text'
|
},
|
description: {
|
label: 'Description',
|
editor: 'text',
|
editOn: 'click'
|
}
|
}
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(testOrderedData);
|
rowCount = testOrderedData.length;
|
|
function testRow(rowIndex) {
|
// Test calling 'grid.edit()' in an always-on cell
|
cell = grid.cell(rowIndex, 'name');
|
addDelay(grid.edit(cell)).then(dfd.rejectOnError(function (node) {
|
cellEditor = query('input', cell.element)[0];
|
assert.strictEqual(cellEditor, node,
|
'edit method\'s promise should return the active editor');
|
assert.strictEqual(cellEditor, document.activeElement,
|
'Editing a cell should make the cell\'s editor active');
|
rowIndex++;
|
if (rowIndex < rowCount) {
|
testRow(rowIndex);
|
}
|
else {
|
dfd.resolve();
|
}
|
}));
|
}
|
testRow(0);
|
|
return dfd;
|
});
|
|
test.test('editor focus and show event with edit-on click editor', function () {
|
var rowCount,
|
cell,
|
cellEditor,
|
dfd = this.async();
|
|
grid = new EditorGrid({
|
columns: {
|
order: 'step',
|
name: {
|
label: 'Name',
|
editor: 'text'
|
},
|
description: {
|
label: 'Description',
|
editor: 'text',
|
editOn: 'click'
|
}
|
}
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
grid.renderArray(testOrderedData);
|
rowCount = testOrderedData.length;
|
|
function testRow(rowIndex) {
|
var dfdEvent = new Deferred();
|
// Test calling 'grid.edit()' in an always-on cell
|
cell = grid.cell(rowIndex, 'description');
|
// Respond to the "dgrid-editor-show" event to ensure the
|
// correct cell has an editor. This event actually fires
|
// synchronously, so we don't need to use this.async.
|
on.once(grid, 'dgrid-editor-show', dfd.rejectOnError(function (event) {
|
assert.strictEqual(cell.element, event.cell.element,
|
'The activated cell should be being edited'
|
);
|
dfdEvent.resolve();
|
}));
|
// Don't move on to the next row until the editor has received focus and the show event has fired.
|
all([addDelay(grid.edit(cell)), dfdEvent]).then(dfd.rejectOnError(function () {
|
cellEditor = query('input', cell.element)[0];
|
assert.strictEqual(cellEditor, document.activeElement,
|
'Editing a cell should make the cell\'s editor active');
|
rowIndex++;
|
if (rowIndex < rowCount) {
|
testRow(rowIndex);
|
}
|
else {
|
dfd.resolve();
|
}
|
}));
|
}
|
testRow(0);
|
|
return dfd;
|
});
|
|
test.test('Maintain shared widgets on refresh in IE', function() {
|
grid = new (declare([ OnDemandGrid, Editor ]))({
|
columns: {
|
name: {
|
editor: TextBox,
|
editOn: 'click'
|
}
|
},
|
collection: createSyncStore({
|
data: testOrderedData,
|
idProperty: 'order'
|
})
|
});
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
|
var cell = grid.cell(1, 'name');
|
var cellContent;
|
return addDelay(grid.edit(cell)).then(function () {
|
cellContent = cell.element.innerHTML;
|
return addDelay(grid.refresh());
|
}).then(function () {
|
cell = grid.cell(1, 'name');
|
return addDelay(grid.edit(cell));
|
}).then(function () {
|
assert.strictEqual(cell.element.innerHTML, cellContent, 'Widget should be identical to before refresh');
|
});
|
});
|
|
test.suite('Listener cleanup', function () {
|
function testCellListeners(rowCount, editorColumnCount) {
|
var rowEntryCount = 0;
|
for (var rowKey in grid._editorCellListeners) {
|
rowEntryCount++;
|
var listenerCount = 0;
|
var rowCellListeners = grid._editorCellListeners[rowKey];
|
|
for (var cellKey in rowCellListeners) {
|
assert.ok(rowCellListeners[cellKey].remove, 'Object in hash should be a listener handle');
|
listenerCount++;
|
}
|
|
assert.strictEqual(listenerCount, editorColumnCount, 'Should have two listeners per row');
|
}
|
assert.strictEqual(rowEntryCount, rowCount, 'Should only have one entry for each row');
|
}
|
|
function buildPaginatedGrid (GridConstructor, rowsPerPage) {
|
var paginatedGrid = new GridConstructor({
|
rowsPerPage: rowsPerPage || 10,
|
columns: {
|
name: {
|
editor: 'text',
|
editOn: 'click'
|
},
|
description: {
|
editor: 'text',
|
editOn: 'click'
|
}
|
},
|
collection: createSyncStore({
|
data: testOrderedData,
|
idProperty: 'order'
|
})
|
});
|
|
document.body.appendChild(paginatedGrid.domNode);
|
paginatedGrid.startup();
|
return paginatedGrid;
|
}
|
|
test.test('clean up after refresh cell', function () {
|
// Test with Pagination mixed in first
|
grid = buildPaginatedGrid(declare([ Grid, Pagination, Editor ]));
|
for (var i = 1; i < 10;i ++) {
|
grid.refreshCell(grid.cell(i, 'name'));
|
grid.refreshCell(grid.cell(i, 'description'));
|
}
|
testCellListeners(9, 2);
|
grid.destroy();
|
|
// Test with Editor mixed in first
|
grid = buildPaginatedGrid(declare([ Grid, Editor, Pagination ]));
|
for (i = 1; i < 10;i ++) {
|
grid.refreshCell(grid.cell(i, 'name'));
|
grid.refreshCell(grid.cell(i, 'description'));
|
}
|
testCellListeners(9, 2);
|
});
|
|
test.test('clean up after row is rerendered', function () {
|
grid = buildPaginatedGrid(declare([Grid, Pagination, Editor]));
|
for (var i = 0; i < 9; i++) {
|
grid.collection.put(
|
lang.mixin(lang.clone(orderedData.items[i]), {description: 'modified'})
|
);
|
}
|
testCellListeners(9, 2);
|
});
|
|
test.test('clean up when switching pages', function () {
|
grid = buildPaginatedGrid(declare([Grid, Pagination, Editor]), 5);
|
testCellListeners(5, 2);
|
grid.gotoPage(2);
|
testCellListeners(4, 2);
|
});
|
|
test.test('clean up when scrolling', function () {
|
var data = [];
|
for (var i = 0; i < 1000; i++) {
|
data[i] = {
|
id: i,
|
name: 'Name ' + i
|
};
|
}
|
grid = new (declare([OnDemandGrid, Editor]))({
|
columns: {
|
name: {
|
editor: 'text',
|
editOn: 'click'
|
}
|
},
|
minRowsPerPage: 250,
|
collection: createSyncStore({
|
data: data
|
})
|
});
|
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
|
testCellListeners(grid.contentNode.querySelectorAll('.dgrid-row').length, 1);
|
grid.bodyNode.scrollTop = '10000';
|
return when(grid._processScroll(), function () {
|
testCellListeners(grid.contentNode.querySelectorAll('.dgrid-row').length, 1);
|
});
|
});
|
});
|
|
test.suite('Editor#refreshCell', function () {
|
var store;
|
|
function createGrid(columnOptions) {
|
store = createSyncStore({
|
data: testOrderedData,
|
idProperty: 'order'
|
});
|
|
grid = new (declare([ OnDemandGrid, Editor ]))({
|
columns: {
|
name: lang.mixin({
|
label: 'Name'
|
}, columnOptions)
|
},
|
collection: store,
|
shouldTrackCollection: false // Disable normal row-based observation hooks to test refreshCell
|
});
|
|
document.body.appendChild(grid.domNode);
|
grid.startup();
|
}
|
|
function createTest(columnOptions) {
|
return function () {
|
createGrid(columnOptions);
|
var cell = grid.cell(1, 'name');
|
var originalElement;
|
var originalValue;
|
|
// Test refreshing an active cell before its value is committed (should revert value, keep focus)
|
|
return addDelay(grid.edit(cell)).then(function () {
|
originalElement = document.activeElement;
|
originalValue = originalElement.value;
|
originalElement.value = 'new name';
|
|
return grid.refreshCell(cell);
|
}).then(function () {
|
assert.strictEqual(document.activeElement, originalElement,
|
'The same editor should still be focused after refreshCell is called on the active cell');
|
assert.strictEqual(originalElement.value, originalValue,
|
'The editor should revert to its stored value upon refresh');
|
|
// Test refreshing a cell with changed data that is not the active cell
|
|
store.putSync(lang.mixin(store.getSync(2), { name: 'new name 2'}));
|
|
return grid.refreshCell(grid.cell(2, 'name'));
|
}).then(function () {
|
var cellElement = grid.cell(2, 'name').element;
|
var name = columnOptions.editOn ? cellElement.textContent :
|
cellElement.widget ? cellElement.widget.get('value') : cellElement.input.value;
|
|
assert.strictEqual(document.activeElement, originalElement,
|
'The same editor should still be focused after refreshCell is called on another cell');
|
assert.strictEqual(name, 'new name 2',
|
'Cell should contain expected new value after refresh');
|
});
|
};
|
}
|
|
test.afterEach(function () {
|
if (grid) {
|
grid.destroy();
|
grid = null;
|
}
|
});
|
|
test.test('HTML input, always-on', createTest({ editor: 'text' }));
|
test.test('HTML input, editOn', createTest({ editor: 'text', editOn: 'click' }));
|
test.test('Dijit TextBox, always-on', createTest({ editor: TextBox }));
|
test.test('Dijit TextBox, editOn', createTest({ editor: TextBox, editOn: 'click' }));
|
});
|
});
|
});
|