[FIX] web: fix crash on editable list view when moving over a RO field

This is a forward-port of commit https://github.com/odoo/odoo/commit/970387f91fea0cb47404612f7d519ffba2c9ad3b

In editable list view, moving to next cell using TAB key crash when the
following field is becoming read-only (i.e non-focusable) and an onchange()
event is triggered.

Consider an editable tree view like this:

<form>
   <field name="o2m" onchange="1">
       <tree>
          <field name="description"/>
          <field name="date" attrs="{'readonly': [('description', '!=', False)]}"/>
          <field name="type"/>
       </tree>
   </field>
</form>

1. Edit 'description'
2. Issue a TAB keypress before 'date' becomes read-only
It will crash while calling getSelectionRange() on an empty element.

opw-2523630

closes odoo/odoo#71163

X-original-commit: 2a880a8020b55b692505846e9626359fe019dbdb
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Signed-off-by: Anh Thao PHAM <kitan191@users.noreply.github.com>
This commit is contained in:
Anh Thao Pham (pta)
2021-05-21 15:09:42 +00:00
parent 5fe51e562b
commit cb500aba27
2 changed files with 74 additions and 1 deletions
@@ -265,7 +265,7 @@ ListRenderer.include({
currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex];
if (currentWidget) {
focusedElement = currentWidget.getFocusableElement().get(0);
if (currentWidget.formatType !== 'boolean') {
if (currentWidget.formatType !== 'boolean' && focusedElement) {
selectionRange = dom.getSelectionRange(focusedElement);
}
}
@@ -11771,6 +11771,79 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('edition, then navigation with tab (with a readonly re-evaluated field and onchange)', async function (assert) {
// This test makes sure that if we have a cell in a row that will become
// read-only after editing another cell, in case the keyboard navigation
// move over it before it becomes read-only and there are unsaved changes
// (which will trigger an onchange), the focus of the next activable
// field will not crash
assert.expect(4);
this.data.bar.onchanges = {
o2m: function () {},
};
this.data.bar.fields.o2m = {string: "O2M field", type: "one2many", relation: "foo"};
this.data.bar.records[0].o2m = [1, 4];
var form = await createView({
View: FormView,
model: 'bar',
res_id: 1,
data: this.data,
arch: '<form>' +
'<group>' +
'<field name="display_name"/>' +
'<field name="o2m">' +
'<tree editable="bottom">' +
'<field name="foo"/>' +
'<field name="date" attrs="{\'readonly\': [(\'foo\', \'!=\', \'yop\')]}"/>' +
'<field name="int_field"/>' +
'</tree>' +
'</field>' +
'</group>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'onchange') {
assert.step(args.method + ':' + args.model);
}
return this._super.apply(this, arguments);
},
fieldDebounce: 1,
viewOptions: {
mode: 'edit',
},
});
var jq_evspecial_focus_trigger = $.event.special.focus.trigger;
// As KeyboardEvent will be triggered by JS and not from the
// User-Agent itself, the focus event will not trigger default
// action (event not being trusted), we need to manually trigger
// 'change' event on the currently focused element
$.event.special.focus.trigger = function () {
if (this !== document.activeElement && this.focus) {
var activeElement = document.activeElement;
this.focus();
$(activeElement).trigger('change');
}
};
// editable list, click on first td and press TAB
await testUtils.dom.click(form.$('.o_data_cell:contains(yop)'));
assert.strictEqual(document.activeElement, form.$('tr.o_selected_row input[name="foo"]')[0],
"focus should be on an input with name = foo");
testUtils.fields.editInput(form.$('tr.o_selected_row input[name="foo"]'), 'new value');
var tabEvent = $.Event("keydown", { which: $.ui.keyCode.TAB });
await testUtils.dom.triggerEvents(form.$('tr.o_selected_row input[name="foo"]'), [tabEvent]);
assert.strictEqual(document.activeElement, form.$('tr.o_selected_row input[name="int_field"]')[0],
"focus should be on an input with name = int_field");
// Restore origin jQuery special trigger for 'focus'
$.event.special.focus.trigger = jq_evspecial_focus_trigger;
assert.verifySteps(["onchange:bar"], "onchange method should have been called");
form.destroy();
});
});
});