[IMP] web: list: directional keys navigation in readonly
With this rev., it is now possible to navigate with arrows in list views (except when editing, as in this case hitting the arrows moves the cursor in the input/textarea), and to expand/collapse groups with keyboard navigation. Part of task 1915702
This commit is contained in:
committed by
Aaron Bohy
parent
ca19b17fd3
commit
8ea5a40417
@@ -706,9 +706,16 @@ var ListController = BasicController.extend({
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onToggleGroup: function (ev) {
|
||||
var self = this;
|
||||
this.model
|
||||
.toggleGroup(ev.data.group.id)
|
||||
.then(this.update.bind(this, {}, {keepSelection: true, reload: false}));
|
||||
.then(function () {
|
||||
self.update({}, {keepSelection: true, reload: false}).then(function () {
|
||||
if (ev.data.onSuccess) {
|
||||
ev.data.onSuccess();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -645,7 +645,7 @@ ListRenderer.include({
|
||||
// row of the group)
|
||||
if (options.forceCreate || record.isDirty()) {
|
||||
// if we modified the current record, add a row to create a new record
|
||||
groupId = $nextRow.data('groupID');
|
||||
groupId = $nextRow.data('group-id');
|
||||
} else {
|
||||
// if we didn't change anything to the current line (e.g. we pressed TAB on
|
||||
// each cell without modifying/entering any data), we discard that line (if
|
||||
@@ -742,13 +742,14 @@ ListRenderer.include({
|
||||
var $groupBody = result[0];
|
||||
var $a = $('<a href="#" role="button">')
|
||||
.text(_t("Add a line"))
|
||||
.data('groupID', group.id);
|
||||
.attr('data-group-id', group.id);
|
||||
var $td = $('<td>')
|
||||
.attr('colspan', this._getNumberOfCols())
|
||||
.addClass('o_group_field_row_add')
|
||||
.attr('tabindex', -1)
|
||||
.append($a);
|
||||
var $tr = $('<tr>', {class: 'o_add_record_row'})
|
||||
.data('groupID', group.id)
|
||||
.attr('data-group-id', group.id)
|
||||
.append($td);
|
||||
$groupBody.append($tr.prepend($('<td>').html(' ')));
|
||||
}
|
||||
@@ -1037,7 +1038,8 @@ ListRenderer.include({
|
||||
ev.stopPropagation();
|
||||
|
||||
var self = this;
|
||||
var groupId = $(ev.target).data('groupID');
|
||||
var groupId = $(ev.target).data('group-id');
|
||||
this.currentGroupId = groupId;
|
||||
this.unselectRow().then(function () {
|
||||
self.trigger_up('add_record', {
|
||||
groupId: groupId,
|
||||
@@ -1097,6 +1099,35 @@ ListRenderer.include({
|
||||
_onFooterClick: function () {
|
||||
this.unselectRow();
|
||||
},
|
||||
/**
|
||||
* Manages the keyboard events on the list. If the list is not editable, when the user navigates to
|
||||
* a cell using the keyboard, if he presses enter, enter the model represented by the line
|
||||
*
|
||||
* @private
|
||||
* @param {KeyboardEvent} ev
|
||||
* @override
|
||||
*/
|
||||
_onKeyDown: function (ev) {
|
||||
var $target = $(ev.currentTarget);
|
||||
var $tr = $target.closest('tr');
|
||||
|
||||
if (this.editable && ev.keyCode === $.ui.keyCode.ENTER && $tr.hasClass('o_selected_row')) {
|
||||
// enter on a textarea for example, let it bubble
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editable && ev.keyCode === $.ui.keyCode.ENTER && !$tr.hasClass('o_selected_row') && !$tr.hasClass('o_group_header')) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if ($target.closest('td').hasClass('o_group_field_row_add')) {
|
||||
this._onAddRecordToGroup(ev);
|
||||
} else {
|
||||
this._onCellClick(ev);
|
||||
}
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {KeyDownEvent} e
|
||||
@@ -1180,10 +1211,20 @@ ListRenderer.include({
|
||||
// prevent from closing the potential dialog containing this list
|
||||
// also auto-focus the 1st control, if any.
|
||||
ev.data.originalEvent.stopPropagation();
|
||||
var rowIndex = this.currentRow;
|
||||
var cellIndex = this.currentFieldIndex + 1;
|
||||
this.trigger_up('discard_changes', {
|
||||
recordID: ev.target.dataPointID,
|
||||
onSuccess: function () {
|
||||
self.$('.o_field_x2many_list_row_add a:first').focus(); // FIXME
|
||||
var recordId = self._getRecordID(rowIndex);
|
||||
if (recordId) {
|
||||
var correspondingRow = self._getRow(recordId);
|
||||
correspondingRow.children().eq(cellIndex).focus();
|
||||
} else if (self.currentGroupId) {
|
||||
self.$('a[data-group-id=' + self.currentGroupId + ']').focus();
|
||||
} else {
|
||||
self.$('.o_field_x2many_list_row_add a:first').focus(); // FIXME
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -39,8 +39,8 @@ var ListRenderer = BasicRenderer.extend({
|
||||
'click .o_group_header': '_onToggleGroup',
|
||||
'click thead .o_list_record_selector input': '_onToggleSelection',
|
||||
'keypress thead tr td': '_onKeyPress',
|
||||
'keydown tr': '_onKeyDown',
|
||||
'keydown thead tr': '_onKeyDown',
|
||||
'keydown td': '_onKeyDown',
|
||||
'keydown th': '_onKeyDown',
|
||||
},
|
||||
/**
|
||||
* @constructor
|
||||
@@ -78,7 +78,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
* @public
|
||||
*/
|
||||
giveFocus: function () {
|
||||
this.$('tbody .o_list_record_selector input:first()').focus();
|
||||
this.$('th:eq(0) input, th:eq(1)').first().focus();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -168,6 +168,56 @@ var ListRenderer = BasicRenderer.extend({
|
||||
};
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $cell
|
||||
* @param {string} direction
|
||||
* @param {integer} colIndex
|
||||
* @returns {jQuery|null}
|
||||
*/
|
||||
_findConnectedCell: function ($cell, direction, colIndex) {
|
||||
var $connectedRow = $cell.closest('tr')[direction]('tr');
|
||||
|
||||
if (!$connectedRow.length) {
|
||||
// Is there another group ? Look at our parent's sibling
|
||||
// We can have th in tbody so we can't simply look for thead
|
||||
// if cell is a th and tbody instead
|
||||
var tbody = $cell.closest('tbody, thead');
|
||||
var $connectedGroup = tbody[direction]('tbody, thead');
|
||||
if ($connectedGroup.length) {
|
||||
// Found another group
|
||||
var $connectedRows = $connectedGroup.find('tr');
|
||||
var rowIndex;
|
||||
if (direction === 'prev') {
|
||||
rowIndex = $connectedRows.length - 1;
|
||||
} else {
|
||||
rowIndex = 0;
|
||||
}
|
||||
$connectedRow = $connectedRows.eq(rowIndex);
|
||||
} else {
|
||||
// End of the table
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var $connectedCell;
|
||||
if ($connectedRow.hasClass('o_group_header')) {
|
||||
$connectedCell = $connectedRow.children();
|
||||
this.currentColIndex = colIndex;
|
||||
} else if ($connectedRow.has('td.o_group_field_row_add').length) {
|
||||
$connectedCell = $connectedRow.find('.o_group_field_row_add');
|
||||
this.currentColIndex = colIndex;
|
||||
} else {
|
||||
var connectedRowChildren = $connectedRow.children();
|
||||
if (colIndex === -1) {
|
||||
colIndex = connectedRowChildren.length - 1;
|
||||
}
|
||||
$connectedCell = connectedRowChildren.eq(colIndex);
|
||||
}
|
||||
|
||||
return $connectedCell;
|
||||
},
|
||||
/**
|
||||
* return the number of visible columns. Note that this number depends on
|
||||
* the state of the renderer. For example, in editable mode, it could be
|
||||
@@ -283,7 +333,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
tdClassName += (' o_' + node.attrs.widget + '_cell');
|
||||
}
|
||||
}
|
||||
var $td = $('<td>', { class: tdClassName });
|
||||
var $td = $('<td>', { class: tdClassName, tabindex: -1 });
|
||||
|
||||
// We register modifiers on the <td> element so that it gets the correct
|
||||
// modifiers classes (for styling)
|
||||
@@ -492,6 +542,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
}
|
||||
var $th = $('<th>')
|
||||
.addClass('o_group_name')
|
||||
.attr('tabindex', -1)
|
||||
.text(name + ' (' + group.count + ')')
|
||||
var $arrow = $('<span>')
|
||||
.css('padding-left', (groupLevel * 20) + 'px')
|
||||
@@ -649,6 +700,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
}
|
||||
$th.text(description)
|
||||
.attr('data-name', name)
|
||||
.attr('tabindex', -1)
|
||||
.toggleClass('o-sort-down', isNodeSorted ? !order[0].asc : false)
|
||||
.toggleClass('o-sort-up', isNodeSorted ? order[0].asc : false)
|
||||
.addClass(field.sortable && 'o_column_sortable');
|
||||
@@ -848,23 +900,59 @@ var ListRenderer = BasicRenderer.extend({
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
_onKeyDown: function (ev) {
|
||||
if (!this.editable) {
|
||||
switch (ev.which) {
|
||||
case $.ui.keyCode.DOWN:
|
||||
$(ev.currentTarget).next().find('input').focus();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case $.ui.keyCode.UP:
|
||||
$(ev.currentTarget).prev().find('input').focus();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case $.ui.keyCode.ENTER:
|
||||
ev.preventDefault();
|
||||
var id = $(ev.currentTarget).data('id');
|
||||
var $cell = $(ev.currentTarget);
|
||||
var $tr;
|
||||
var $futureCell;
|
||||
var colIndex;
|
||||
switch (ev.keyCode) {
|
||||
case $.ui.keyCode.LEFT:
|
||||
ev.preventDefault();
|
||||
$tr = $cell.closest('tr');
|
||||
if ($tr.hasClass('o_group_header') && $tr.hasClass('o_group_open')) {
|
||||
this._onToggleGroup(ev);
|
||||
} else {
|
||||
$futureCell = $cell.prev();
|
||||
}
|
||||
break;
|
||||
case $.ui.keyCode.RIGHT:
|
||||
ev.preventDefault();
|
||||
$tr = $cell.closest('tr');
|
||||
if ($tr.hasClass('o_group_header') && !$tr.hasClass('o_group_open')) {
|
||||
this._onToggleGroup(ev);
|
||||
} else {
|
||||
$futureCell = $cell.next();
|
||||
}
|
||||
break;
|
||||
case $.ui.keyCode.UP:
|
||||
ev.preventDefault();
|
||||
colIndex = this.currentColIndex || $cell.index();
|
||||
$futureCell = this._findConnectedCell($cell, 'prev', colIndex);
|
||||
break;
|
||||
case $.ui.keyCode.DOWN:
|
||||
ev.preventDefault();
|
||||
colIndex = this.currentColIndex || $cell.index();
|
||||
$futureCell = this._findConnectedCell($cell, 'next', colIndex);
|
||||
break;
|
||||
case $.ui.keyCode.ENTER:
|
||||
ev.preventDefault();
|
||||
$tr = $cell.closest('tr');
|
||||
if ($tr.hasClass('o_group_header')) {
|
||||
this._onToggleGroup(ev);
|
||||
} else {
|
||||
var id = $tr.data('id');
|
||||
if (id) {
|
||||
this.trigger_up('open_record', { id: id, target: ev.target });
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($futureCell) {
|
||||
// If the cell contains activable elements, focus them instead
|
||||
var $activables = $futureCell.find(':focusable');
|
||||
if ($activables.length) {
|
||||
$activables[0].focus();
|
||||
} else {
|
||||
$futureCell.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -903,12 +991,27 @@ var ListRenderer = BasicRenderer.extend({
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
* @param {DOMEvent} ev
|
||||
*/
|
||||
_onToggleGroup: function (ev) {
|
||||
var group = $(ev.currentTarget).data('group');
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var group = $(ev.currentTarget).closest('tr').data('group');
|
||||
if (group.count) {
|
||||
this.trigger_up('toggle_group', { group: group });
|
||||
this.trigger_up('toggle_group', {
|
||||
group: group,
|
||||
onSuccess: function() {
|
||||
// Refocus the header after re-render unless the user
|
||||
// already focused something else by now
|
||||
if (document.activeElement.tagName === 'BODY') {
|
||||
var groupHeaders = $('tr.o_group_header:data("group")');
|
||||
var header = groupHeaders.filter(function () {
|
||||
return $(this).data('group').id === group.id;
|
||||
});
|
||||
header.find('.o_group_name').focus();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -136,11 +136,15 @@
|
||||
border-top: none; // Override bootstrap for grouped list views
|
||||
}
|
||||
|
||||
tbody {
|
||||
&, &.table-striped {
|
||||
tr:focus-within {
|
||||
background-color: $o-form-lightsecondary;
|
||||
background-color: lighten($o-form-lightsecondary, 10%);
|
||||
}
|
||||
}
|
||||
thead th:focus-within, tbody td:focus-within {
|
||||
background-color: $o-form-lightsecondary;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_data_cell.o_text_overflow,
|
||||
.o_data_cell.o_invisible_modifier {
|
||||
|
||||
@@ -6,5 +6,9 @@
|
||||
|
||||
tbody > tr.o_group_header {
|
||||
background-image: linear-gradient(to bottom, #fcfcfc, #dedede);
|
||||
|
||||
&:focus-within {
|
||||
background-image: linear-gradient(to bottom, #fcfcfc, $o-form-lightsecondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2975,6 +2975,68 @@ QUnit.module('Views', {
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pressing TAB in editable list with several fields [REQUIRE FOCUS]', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom">' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="int_field"/>' +
|
||||
'</tree>',
|
||||
});
|
||||
|
||||
await testUtils.dom.click(list.$('.o_data_cell:first'));
|
||||
assert.hasClass(list.$('.o_data_row:first'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:first .o_data_cell:first input')[0]);
|
||||
|
||||
// Press 'Tab' -> should go to next cell (still in first row)
|
||||
await testUtils.fields.triggerKeydown(list.$('.o_selected_row input'), 'tab');
|
||||
assert.hasClass(list.$('.o_data_row:first'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:first .o_data_cell:last input')[0]);
|
||||
|
||||
// Press 'Tab' -> should go to next line (first cell)
|
||||
await testUtils.fields.triggerKeydown(list.$('.o_selected_row input'), 'tab');
|
||||
assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell:first input')[0]);
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pressing SHIFT-TAB in editable list with several fields [REQUIRE FOCUS]', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom">' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="int_field"/>' +
|
||||
'</tree>',
|
||||
});
|
||||
|
||||
await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(1)'));
|
||||
assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell:last input')[0]);
|
||||
|
||||
// Press 'shift-Tab' -> should go to previous line (last cell)
|
||||
list.$('tr.o_selected_row input').trigger($.Event('keydown', {which: $.ui.keyCode.TAB, shiftKey: true}));
|
||||
await testUtils.nextTick();
|
||||
assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell:first input')[0]);
|
||||
|
||||
// Press 'shift-Tab' -> should go to previous cell
|
||||
list.$('tr.o_selected_row input').trigger($.Event('keydown', {which: $.ui.keyCode.TAB, shiftKey: true}));
|
||||
await testUtils.nextTick();
|
||||
assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
|
||||
assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell:last input')[0]);
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('navigation with tab and readonly field (no modification)', async function (assert) {
|
||||
// This test makes sure that if we have 2 cells in a row, the first in
|
||||
// edit mode, and the second one readonly, then if we press TAB when the
|
||||
@@ -5244,6 +5306,276 @@ QUnit.module('Views', {
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('cell-level keyboard navigation in non-editable list', async function (assert) {
|
||||
assert.expect(16);
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree><field name="foo" required="1"/></tree>',
|
||||
intercepts: {
|
||||
switch_view: function (event) {
|
||||
assert.strictEqual(event.data.res_id, 3,
|
||||
"'switch_view' event has been triggered");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.ok(document.activeElement.classList.contains('o_searchview_input'), 'default focus should be in search view');
|
||||
// switch focus to the create button in tests while it works on live
|
||||
$('.o_list_button_add').focus();
|
||||
assert.ok(document.activeElement.classList.contains('o_list_button_add'),
|
||||
'focus should now be in create button');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'focus should now be on the record selector');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'focus should now be in first row input');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.tagName, 'TD',
|
||||
'focus should now be in field TD');
|
||||
assert.strictEqual(document.activeElement.textContent, 'yop',
|
||||
'focus should now be in first row field');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.textContent, 'yop',
|
||||
'should not cycle at end of line');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'focus should now be in second row field');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'gnap',
|
||||
'focus should now be in third row field');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'focus should now be in last row field');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'focus should still be in last row field (arrows do not cycle)');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'focus should still be in last row field (arrows still do not cycle)');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'focus should now be in last row input');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'should not cycle at start of line');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.textContent, 'gnap',
|
||||
'focus should now be in third row field');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('cell-level keyboard navigation in editable grouped list', async function (assert) {
|
||||
assert.expect(56);
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="bottom"><field name="foo" required="1"/></tree>',
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
await testUtils.dom.click(list.$('.o_group_header:first')); // open first group
|
||||
await testUtils.dom.click(list.$('td:contains(blip)')); // select row of first group
|
||||
assert.hasClass(list.$('tr.o_data_row:eq(1)'), 'o_selected_row',
|
||||
'second row should be opened');
|
||||
|
||||
var $secondRowInput = list.$('tr.o_data_row:eq(1) td:eq(1) input');
|
||||
assert.strictEqual($secondRowInput.val(), 'blip',
|
||||
'second record should be in edit mode');
|
||||
|
||||
await testUtils.fields.editAndTrigger($secondRowInput, 'blipbloup', 'input');
|
||||
assert.strictEqual($secondRowInput.val(), 'blipbloup',
|
||||
'second record should be changed but not saved yet');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'escape');
|
||||
|
||||
assert.hasClass($('body'), 'modal-open',
|
||||
'record has been modified, are you sure modal should be opened');
|
||||
await testUtils.dom.click($('body .modal button span:contains(Ok)'));
|
||||
|
||||
assert.doesNotHaveClass(list.$('tr.o_data_row:eq(1)'), 'o_selected_row',
|
||||
'second row should be closed');
|
||||
assert.strictEqual(document.activeElement.tagName, 'TD',
|
||||
'focus is in field td');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'second field of second record should be focused');
|
||||
assert.strictEqual(list.$('tr.o_data_row:eq(1) td:eq(1)').text(), 'blip',
|
||||
'change should not have been saved');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'record selector should be focused');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.tagName, 'TD',
|
||||
'focus is in first record td');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
var $firstRowInput = list.$('tr.o_data_row:eq(0) td:eq(1) input');
|
||||
assert.hasClass(list.$('tr.o_data_row:eq(0)'), 'o_selected_row',
|
||||
'first row should be selected');
|
||||
assert.strictEqual($firstRowInput.val(), 'yop',
|
||||
'first record should be in edit mode');
|
||||
|
||||
await testUtils.fields.editAndTrigger($firstRowInput, 'Zipadeedoodah', 'input');
|
||||
assert.strictEqual($firstRowInput.val(), 'Zipadeedoodah',
|
||||
'first record should be changed but not saved yet');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
assert.strictEqual(list.$('tr.o_data_row:eq(0) td:eq(1)').text(), 'Zipadeedoodah',
|
||||
'first record should be saved');
|
||||
assert.doesNotHaveClass(list.$('tr.o_data_row:eq(0)'), 'o_selected_row',
|
||||
'first row should be closed');
|
||||
assert.hasClass(list.$('tr.o_data_row:eq(1)'), 'o_selected_row',
|
||||
'second row should be opened');
|
||||
assert.strictEqual(list.$('tr.o_data_row:eq(1) td:eq(1) input').val(), 'blip',
|
||||
'second record should be in edit mode');
|
||||
|
||||
assert.strictEqual(document.activeElement.value, 'blip',
|
||||
'second record input should be focused');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.value, 'blip',
|
||||
'second record input should still be focused (arrows movements are disabled in edit)');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(document.activeElement.value, 'blip',
|
||||
'second record input should still be focused (arrows movements are still disabled in edit)');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'escape');
|
||||
assert.doesNotHaveClass(list.$('tr.o_data_row:eq(1)'), 'o_selected_row',
|
||||
'second row should be closed');
|
||||
assert.strictEqual(document.activeElement.tagName, 'TD',
|
||||
'focus is in field td');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'second field of second record should be focused');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
|
||||
assert.strictEqual(document.activeElement.tagName, 'A',
|
||||
'should focus the "Add a line" button');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
|
||||
assert.strictEqual(document.activeElement.textContent, 'false (1)',
|
||||
'focus should be on second group header');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 3,
|
||||
'should have 3 rows displayed');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 4,
|
||||
'should have 4 rows displayed');
|
||||
assert.strictEqual(document.activeElement.textContent, 'false (1)',
|
||||
'focus should still be on second group header');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'second field of last record should be focused');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'A',
|
||||
'should focus the "Add a line" button');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'A',
|
||||
'arrow navigation should not cycle (focus still on last row)');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
await testUtils.fields.editAndTrigger($('tr.o_data_row:eq(4) td:eq(1) input'),
|
||||
'cheateur arrete de cheater', 'input');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 6,
|
||||
'should have 6 rows displayed (new record + new edit line)');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'escape');
|
||||
assert.strictEqual(document.activeElement.tagName, 'A',
|
||||
'should focus the "Add a line" button');
|
||||
|
||||
// come back to the top
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
|
||||
assert.strictEqual(document.activeElement.tagName, 'TH',
|
||||
'focus is in table header');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(document.activeElement.tagName, 'INPUT',
|
||||
'focus is in header input');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(document.activeElement.tagName, 'TD',
|
||||
'focus is in field td');
|
||||
assert.strictEqual(document.activeElement.textContent, 'Zipadeedoodah',
|
||||
'second field of first record should be focused');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
assert.strictEqual(document.activeElement.textContent, 'true (3)',
|
||||
'focus should be on first group header');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'enter');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 2,
|
||||
'should have 2 rows displayed (first group should be closed)');
|
||||
assert.strictEqual(document.activeElement.textContent, 'true (3)',
|
||||
'focus should still be on first group header');
|
||||
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 2,
|
||||
'should have 2 rows displayed');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 5,
|
||||
'should have 5 rows displayed');
|
||||
assert.strictEqual(document.activeElement.textContent, 'true (3)',
|
||||
'focus is still in header');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'right');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 5,
|
||||
'should have 5 rows displayed');
|
||||
assert.strictEqual(document.activeElement.textContent, 'true (3)',
|
||||
'focus is still in header');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 2,
|
||||
'should have 2 rows displayed');
|
||||
assert.strictEqual(document.activeElement.textContent, 'true (3)',
|
||||
'focus is still in header');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'left');
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 2,
|
||||
'should have 2 rows displayed');
|
||||
assert.strictEqual(document.activeElement.textContent, 'true (3)',
|
||||
'focus is still in header');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'false (2)',
|
||||
'focus should now be on second group header');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'TD',
|
||||
'record td should be focused');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'second field of first record of second group should be focused');
|
||||
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.textContent, 'cheateur arrete de cheater',
|
||||
'second field of last record of second group should be focused');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'down');
|
||||
assert.strictEqual(document.activeElement.tagName, 'A',
|
||||
'should focus the "Add a line" button');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
assert.strictEqual(document.activeElement.textContent, 'cheateur arrete de cheater',
|
||||
'second field of last record of second group should be focused (special case: the first td of the "Add a line" line was skipped');
|
||||
await testUtils.fields.triggerKeydown($(document.activeElement), 'up');
|
||||
assert.strictEqual(document.activeElement.textContent, 'blip',
|
||||
'second field of first record of second group should be focused');
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('add a new row in grouped editable="top" list', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user