text: define([
  	'dojo/_base/declare',
  	'dojo/dom-construct',
  	'dojo/on',
  	'dojo/query',
  	'dgrid/OnDemandList',
  	'dgrid/OnDemandGrid',
  	'dgrid/Keyboard',
  	'dgrid/ColumnSet',
  	'dgrid/test/data/createSyncStore',
  	'dgrid/test/data/genericData',
  	'../addCss!'
  ], function (declare, domConstruct, on, query, OnDemandList, OnDemandGrid, Keyboard,
  	ColumnSet, createSyncStore, genericData) {
  
  	var tdd = intern.getPlugin('interface.tdd');
  	var assert = intern.getPlugin('chai').assert;
  	var handles = [];
  	var testStore = createSyncStore({ data: genericData });
  	var item = testStore.getSync(1);
  	var grid;
  	var columnSet = [
  		[
  			[
  				{ label: 'Column 1', field: 'col1' },
  				{ label: 'Column 2', field: 'col2', sortable: false }
  			],
  			[
  				{ label: 'Column 3', field: 'col3', colSpan: 2 }
  			]
  		],
  		[
  			[
  				{ label: 'Column 1', field: 'col1', rowSpan: 2 },
  				{ label: 'Column 4', field: 'col4' }
  			],
  			[
  				{ label: 'Column 5', field: 'col5' }
  			]
  		]
  	];
  
  	function getColumns() {
  		return {
  			col1: 'Column 1',
  			col3: 'Column 3',
  			col5: 'Column 5'
  		};
  	}
  
  	function afterEach() {
  		grid.domNode.style.display = '';
  
  		for (var i = handles.length; i--;) {
  			handles[i].remove && handles[i].remove();
  		}
  		handles = [];
  
  		// Restore item that was removed for focus retention test
  		testStore.put(item);
  	}
  
  	function after() {
  		// Destroy list or grid
  		if (grid) {
  			grid.destroy();
  			grid = undefined;
  		}
  	}
  
  	// Common test functions for grid w/ cellNavigation: false and list
  
  	function testRowFocus() {
  		var rowId;
  		// listen for a dgrid-cellfocusin event
  		handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  			assert.ok(e.row, 'dgrid-cellfocusin event got a non-null row value');
  			rowId = e.row.id;
  		}));
  		// trigger a focus with no argument, which should focus the first row
  		grid.focus();
  		assert.strictEqual(document.activeElement, query('.dgrid-row', grid.contentNode)[0],
  			'focus() targeted the first row');
  		assert.strictEqual(rowId, 0,
  			'dgrid-cellfocusin event triggered on first row on focus() call');
  	}
  
  	function testRowFocusArgs() {
  		var rowId, target;
  		// listen for a dgrid-cellfocusin event
  		handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  			assert.ok(e.row, 'dgrid-cellfocusin event got a non-null row value');
  			rowId = e.row.id;
  		}));
  		// trigger a body focus with the second row as the target
  		target = query('.dgrid-row', grid.contentNode)[1];
  		grid.focus(target);
  		// make sure we got the right row
  		assert.strictEqual(document.activeElement, target,
  			'focus(...) targeted the expected row');
  		assert.strictEqual(rowId, 1,
  			'dgrid-cellfocusin event triggered on expected row');
  	}
  
  	function testRowBlur() {
  		var blurredRow,
  			targets = query('.dgrid-row', grid.contentNode);
  
  		// call one focus event, followed by a subsequent focus event,
  		// thus triggering a dgrid-cellfocusout event
  		grid.focus(targets[0]);
  
  		// listen for a dgrid-cellfocusout event
  		handles.push(on(document.body, 'dgrid-cellfocusout', function (e) {
  			blurredRow = e.row;
  			assert.ok(blurredRow, 'dgrid-cellfocusout event got a non-null row value');
  		}));
  
  		grid.focus(targets[1]);
  		// make sure our handler was called
  		assert.strictEqual(blurredRow && blurredRow.id, 0,
  			'dgrid-cellfocusout event triggered on expected row');
  	}
  
  	function testRowUpdate() {
  		var element, elementId;
  		// Focus a row based on a store ID, then issue an update and make sure
  		// the same id is still focused
  		grid.focus(1);
  
  		element = document.activeElement;
  		assert.ok(element && element.className && element.className.indexOf('dgrid-row') > -1,
  			'focus(id) call focused a row');
  
  		elementId = element.id;
  		grid.collection.put(item);
  		assert.notStrictEqual(element, document.activeElement,
  			'A different DOM element is focused after updating the item');
  		assert.strictEqual(elementId, document.activeElement.id,
  			'The item\'s new row is focused after updating the item');
  	}
  
  	function testRowRemove() {
  		var dfd = this.async(1000),
  			element,
  			nextElement;
  
  		// Focus a row based on a store ID, then remove the item and
  		// make sure the corresponding cell is eventually focused
  		grid.focus(1);
  
  		element = document.activeElement;
  		assert.ok(element && element.className && element.className.indexOf('dgrid-row') > -1,
  			'focus(id) call focused a row');
  
  		nextElement = element.nextSibling;
  		grid.collection.remove(1);
  
  		// The logic responsible for moving to the next row runs on next turn,
  		// since it operates as a fallback that is run only if a replacement
  		// is not immediately inserted.  Therefore we need to execute our
  		// assertions on the next turn as well.
  		setTimeout(dfd.callback(function () {
  			assert.strictEqual(nextElement, document.activeElement,
  				'The next row is focused after removing the item');
  		}), 0);
  
  		return dfd;
  	}
  
  	function registerRowTests(name) {
  		tdd.afterEach(afterEach);
  		tdd.after(after);
  
  		tdd.test(name + '.focus + no args', testRowFocus);
  		tdd.test(name + '.focus + args', testRowFocusArgs);
  		tdd.test('dgrid-cellfocusout event', testRowBlur);
  		tdd.test(name + '.focus + item update', testRowUpdate);
  		tdd.test(name + '.focus + item removal', testRowRemove);
  	}
  
  	tdd.suite('Keyboard (Grid + cellNavigation:true)', function () {
  		tdd.before(function () {
  			grid = new (declare([OnDemandGrid, Keyboard]))({
  				columns: getColumns(),
  				sort: 'id',
  				collection: testStore
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		tdd.afterEach(afterEach);
  		tdd.after(after);
  
  		tdd.test('grid.focus + no args', function () {
  			var colId;
  			// listen for a dgrid-cellfocusin event
  			handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  				assert.ok(e.cell, 'dgrid-cellfocusin event got a non-null cell value');
  				colId = e.cell.column.id;
  			}));
  			// trigger a focus with no argument, which should focus the first cell
  			grid.focus();
  			assert.strictEqual(document.activeElement, query('.dgrid-cell', grid.contentNode)[0],
  				'focus() targeted the first cell');
  			assert.strictEqual(colId, 'col1',
  				'dgrid-cellfocusin event triggered on first cell on focus() call');
  		});
  
  		tdd.test('grid.focusHeader + no args', function () {
  			var colId;
  			// listen for a dgrid-cellfocusin event (header triggers same event)
  			handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  				assert.ok(e.cell, 'dgrid-cellfocusin event got a non-null cell value');
  				assert.ok(!e.row, 'dgrid-cellfocusin event for header got a falsy row value');
  				colId = e.cell.column.id;
  			}));
  			// trigger a header focus with no argument, which should focus the first cell
  			grid.focusHeader();
  			assert.strictEqual(document.activeElement, query('.dgrid-cell', grid.headerNode)[0],
  				'focus() targeted the first header cell');
  			assert.strictEqual(colId, 'col1',
  				'dgrid-cellfocusin event triggered on first cell on focusHeader() call');
  		});
  
  		tdd.test('grid.focus + args', function () {
  			var focusedCell, target;
  			// listen for a dgrid-cellfocusin event
  			handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  				assert.ok(e.cell, 'dgrid-cellfocusin event got a non-null cell value');
  				focusedCell = e.cell;
  			}));
  			// trigger a body focus with the second cell as the target
  			target = query('.dgrid-cell', grid.contentNode)[1];
  			grid.focus(target);
  			assert.strictEqual(document.activeElement, target,
  				'focus(...) targeted the expected cell');
  			assert.ok(focusedCell, 'dgrid-cellfocusin event fired');
  			assert.strictEqual(focusedCell.row.id, 0,
  				'dgrid-cellfocusin event triggered on expected row');
  			assert.strictEqual(focusedCell.column.id, 'col3',
  				'dgrid-cellfocusin event triggered on second cell on focus(...) call');
  		});
  
  		tdd.test('grid.focusHeader + args', function () {
  			var colId, target;
  			// listen for a dgrid-cellfocusin event (header triggers same event)
  			handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  				assert.ok(e.cell, 'dgrid-cellfocusin event got a non-null cell value');
  				assert.ok(!e.row, 'dgrid-cellfocusin event for header got a falsy row value');
  				colId = e.cell.column.id;
  			}));
  			// trigger a focus on the first header cell
  			target = query('.dgrid-cell', grid.headerNode)[1];
  			grid.focus(target);
  			assert.strictEqual(document.activeElement, target,
  				'focusHeader(...) targeted the expected cell');
  			assert.strictEqual(colId, 'col3', 'dgrid-cellfocusin event triggered on expected header cell');
  		});
  
  		tdd.test('dgrid-cellfocusout event', function () {
  			var blurredCell,
  				blurredElementRowId,
  				targets = query('.dgrid-cell', grid.contentNode);
  
  			// call one focus event, followed by a subsequent focus event,
  			// thus triggering a dgrid-cellfocusout event
  			grid.focus(targets[0]);
  
  			// listen for a dgrid-cellfocusout event
  			handles.push(on(document.body, 'dgrid-cellfocusout', function (e) {
  				blurredElementRowId = grid.row(e.target).id;
  				blurredCell = e.cell;
  				assert.ok(blurredCell, 'dgrid-cellfocusout event got a non-null cell value');
  			}));
  
  			// Focus first cell in next row and make sure handler was called
  			grid.focus(targets[3]);
  			assert.ok(blurredCell, 'dgrid-cellfocusout event fired');
  			assert.strictEqual(blurredElementRowId, 0,
  				'dgrid-cellfocusout event fired from expected element');
  			assert.strictEqual(blurredCell.row.id, 0,
  				'dgrid-cellfocusout event.cell contains expected row');
  			assert.strictEqual(blurredCell.column.id, 'col1',
  				'dgrid-cellfocusout event.cell contains expected column');
  		});
  
  		tdd.test('grid.focus - no args, empty store', function () {
  			grid.set('collection', createSyncStore({ data: [] }));
  			assert.doesNotThrow(function () {
  				grid.focus();
  			}, null, 'grid.focus() on empty grid should not throw error');
  			assert.strictEqual(document.activeElement, grid.contentNode,
  				'grid.contentNode should be focused after grid.focus() on empty grid');
  		});
  	});
  
  	tdd.suite('Keyboard (Grid + cellNavigation:true + ColumnSet)', function () {
  		tdd.before(function () {
  			grid = new (declare([OnDemandGrid, ColumnSet, Keyboard]))({
  				columnSets: columnSet,
  				sort: 'id',
  				collection: testStore
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		tdd.afterEach(afterEach);
  		tdd.after(after);
  
  		tdd.test('grid.focusHeader + ColumnSet', function () {
  			var colSetId;
  
  			handles.push(on(document.body, 'dgrid-cellfocusin', function (e) {
  				assert.isTrue('cell' in e, 'dgrid-cellfocusin event has a cell property');
  				assert.isFalse('row' in e, 'dgrid-cellfocusin event does not have a row property');
  				colSetId = e.cell.column.id;
  			}));
  
  			grid.focus(); // first focus the content body
  			grid.focusHeader();
  			assert.strictEqual(document.activeElement, query('.dgrid-cell', grid.headerNode)[0],
  				'focusHeader() targeted the first header cell');
  			assert.strictEqual(colSetId, '0-0-0',
  				'dgrid-cellfocusin event triggered on first cell on focusHeader() call');
  		});
  	});
  
  	tdd.suite('Keyboard focus preservation', function () {
  		tdd.before(function () {
  			grid = new (declare([OnDemandGrid, Keyboard]))({
  				columns: getColumns(),
  				sort: 'id',
  				collection: testStore
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		tdd.afterEach(afterEach);
  		tdd.after(after);
  
  		tdd.test('grid.focus + item update', function () {
  			var element;
  			// Focus a row based on a store ID + column ID,
  			// then issue an update and make sure the same id is still focused
  			grid.focus(grid.cell(1, 'col1'));
  
  			element = document.activeElement;
  			assert.ok(element && element.className && element.className.indexOf('dgrid-cell') > -1,
  				'focus(id) call focused a cell');
  
  			grid.collection.put(item);
  			assert.notStrictEqual(element, document.activeElement,
  				'A different DOM element is focused after updating the item');
  			assert.strictEqual(grid.cell(1, 'col1').element, document.activeElement,
  				'The item\'s new cell is focused after updating the item');
  		});
  
  		tdd.test('grid.focus + item update on hidden grid', function () {
  			var element;
  			// Focus a row based on a store ID + column ID,
  			// then issue an update and make sure the same id is still focused
  			grid.focus(grid.cell(1, 'col1'));
  
  			element = document.activeElement;
  			assert.ok(element && element.className && element.className.indexOf('dgrid-cell') > -1,
  				'focus(id) call focused a cell');
  
  			// Hide the grid
  			grid.domNode.style.display = 'none';
  
  			// Modify the item in the store
  			grid.collection.put(item);
  			assert.notStrictEqual(element, document.activeElement,
  				'A different or no DOM element is focused after updating the item');
  		});
  
  		tdd.test('grid.focus + item removal', function () {
  			var dfd = this.async(1000),
  				element,
  				nextElement;
  
  			// Focus a cell based on a store ID, then remove the item and
  			// make sure the corresponding cell is eventually focused
  			grid.focus(grid.cell(1, 'col1'));
  
  			element = document.activeElement;
  			assert.ok(element && element.className && element.className.indexOf('dgrid-cell') > -1,
  				'focus(id) call focused a cell');
  
  			nextElement = grid.cell(2, 'col1').element;
  			grid.collection.remove(1);
  
  			// The logic responsible for moving to the next row runs on next turn,
  			// since it operates as a fallback that is run only if a replacement
  			// is not immediately inserted.  Therefore we need to execute our
  			// assertions on the next turn as well.
  			setTimeout(dfd.callback(function () {
  				assert.strictEqual(nextElement, document.activeElement,
  					'The next row is focused after removing the item');
  			}), 0);
  
  			return dfd;
  		});
  	});
  
  	tdd.suite('Keyboard focused node preservation after blur', function () {
  		var button;
  
  		tdd.before(function () {
  			grid = new (declare([OnDemandGrid, Keyboard]))({
  				columns: getColumns(),
  				sort: 'id',
  				collection: testStore
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  
  			// Add a button as a target to move focus out of grid
  			button = domConstruct.create('button', null, document.body);
  		});
  
  		tdd.afterEach(afterEach);
  		tdd.after(function () {
  			after();
  			domConstruct.destroy(button);
  		});
  
  		tdd.test('grid.focus + item update', function () {
  			var element;
  			grid.focus(grid.cell(1, 'col1'));
  
  			element = document.activeElement;
  			assert.ok(element && element.className && element.className.indexOf('dgrid-cell') > -1,
  				'focus(id) call focused a cell');
  
  			// Focus the button we added to move focus out of the grid
  			button.focus();
  
  			grid.collection.put(item);
  			grid.focus();
  			assert.notStrictEqual(element, document.activeElement,
  				'A different DOM element is focused after updating the item');
  			assert.strictEqual(grid.cell(1, 'col1').element, document.activeElement,
  				'The item\'s new cell is focused after updating the item');
  		});
  
  		tdd.test('grid.focus + item removal', function () {
  			var dfd = this.async(1000),
  				element,
  				nextElement;
  
  			grid.focus(grid.cell(1, 'col1'));
  
  			element = document.activeElement;
  			assert.ok(element && element.className && element.className.indexOf('dgrid-cell') > -1,
  				'focus(id) call focused a cell');
  
  			button.focus();
  
  			nextElement = grid.cell(2, 'col1').element;
  			grid.collection.remove(1);
  
  			setTimeout(dfd.callback(function () {
  				assert.doesNotThrow(function () {
  					grid.focus();
  				}, null, 'focus() after blur and item removal should not throw error');
  				assert.strictEqual(nextElement, document.activeElement,
  					'The next row is focused after calling focus()');
  			}), 0);
  
  			return dfd;
  		});
  
  		tdd.test('grid.focus called in same turn after item removal', function () {
  			var dfd = this.async(1000),
  				element,
  				nextElement;
  
  			grid.focus(grid.cell(1, 'col1'));
  
  			element = document.activeElement;
  			assert.ok(element && element.className && element.className.indexOf('dgrid-cell') > -1,
  				'focus(id) call focused a cell');
  
  			button.focus();
  
  			nextElement = grid.cell(2, 'col1').element;
  			grid.collection.remove(1);
  			grid.focus();
  
  			setTimeout(dfd.callback(function () {
  				assert.strictEqual(nextElement, document.activeElement,
  					'The next row is focused after removing the item and calling focus on the same turn');
  			}), 0);
  
  			return dfd;
  		});
  	});
  
  	tdd.suite('Keyboard (Grid + cellNavigation:false)', function () {
  		tdd.before(function () {
  			grid = new (declare([OnDemandGrid, Keyboard]))({
  				cellNavigation: false,
  				columns: getColumns(),
  				sort: 'id',
  				collection: testStore
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		registerRowTests('grid');
  	});
  
  	tdd.suite('Keyboard (List)', function () {
  		tdd.before(function () {
  			grid = new (declare([OnDemandList, Keyboard]))({
  				sort: 'id',
  				collection: testStore,
  				renderRow: function (item) {
  					var div = document.createElement('div');
  					div.appendChild(document.createTextNode(item.col5));
  					return div;
  				}
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		registerRowTests('list');
  	});
  
  	tdd.suite('Keyboard + initially-empty store', function () {
  		var store = createSyncStore({ data: [] });
  		tdd.beforeEach(function () {
  			grid = new (declare([ OnDemandGrid, Keyboard ]))({
  				collection: store,
  				columns: getColumns()
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		tdd.afterEach(afterEach);
  		tdd.after(after);
  
  		tdd.test('Proper tabIndex initialization after item is added', function () {
  			assert.strictEqual(grid.contentNode.tabIndex, 0,
  				'contentNode should be focusable when grid is empty');
  
  			var item = store.addSync({});
  
  			assert.strictEqual(grid.contentNode.tabIndex, -1,
  				'contentNode should not be focusable once an item exists');
  			assert.strictEqual(query('.dgrid-cell', grid.contentNode)[0].tabIndex, 0,
  				'First cell should be focusable once an item exists');
  
  			store.removeSync(item.id);
  
  			assert.strictEqual(grid.contentNode.tabIndex, 0,
  				'contentNode should be focusable when grid is empty again');
  		});
  	});
  
  	tdd.suite('Keyboard _ensureScroll', function () {
  		function isRowVisible(row) {
  			return row.element.offsetTop < grid.bodyNode.scrollTop + grid.bodyNode.offsetHeight;
  		}
  
  		tdd.beforeEach(function () {
  			var store = createSyncStore({ data: genericData });
  			grid = new (declare([ OnDemandGrid, Keyboard ]))({
  				collection: store,
  				columns: getColumns()
  			});
  			document.body.appendChild(grid.domNode);
  			grid.startup();
  		});
  
  		tdd.afterEach(after);
  
  		tdd.test('scroll to row', function () {
  			var row40 = grid.row(40);
  			assert.isTrue(!isRowVisible(row40), 'Row 40 is visible.');
  			grid._ensureScroll(row40);
  			assert.isTrue(isRowVisible(row40), 'Row 40 is not visible.');
  		});
  
  		tdd.test('scroll to cell', function () {
  			var cell40col3 = grid.cell(40, 'col3');
  			assert.isTrue(!isRowVisible(cell40col3.row), 'Row 40 is visible.');
  			grid._ensureScroll(cell40col3);
  			// This is how the code works when there are no column sets and only one subrow.
  			assert.isTrue(!isRowVisible(cell40col3.row), 'Row 40 is visible.');
  		});
  	});
  });
  