From 9118bcb0c6c3000ecd3e4f60b980aeabfbc54054 Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Tue, 9 Jan 2024 16:16:37 +0100 Subject: [PATCH] [FIX] web: Input Field always display the correct value Before this commit, a Field using the useInputField hook can ignore values from an onchange in some cases. Problem: ======= In order to know if it should apply the value of an onchange on the field's input, the useInputField hook uses a useEffect which will check if the field is not dirty (being edited) and if it is not invalid. If it true, it will apply the value returned by getValue which often corresponds to record.data[fieldName]. By executing getValue, it will therefore start to observe changes in record.data[fieldName] until the next render. If getValue is not executed then changes applied by onchange on record.data[fieldName] are ignored because we don't observe record.data[fieldName]. Solution: ======= Always execute getValue in the useEffect to always observe changes on record.data[fieldName] and apply them correctly on the input if needed. (It s a hack but we don't have a better solution for now) How to reproduce: ================ - Go to a Form view with a field B with onchange and a field A (char field) - Edit field B (an onchange is in progress) - Insert a value in the input of field A - The onchange is resolved with a value for field A - The value in the input for field A does not change (because the field is in edition) - Click outside the input for field A (trigger change event) - Edit field B (an onchange is executed directly) Before this commit: The value returned by the second onchange is not visually applied to field A. The old value is still present in the input. After this commit: The value returned by the second onchange is correctly applied to field A. Its input contains the value of the onchange. closes odoo/odoo#148711 Signed-off-by: Aaron Bohy (aab) --- .../src/views/fields/input_field_hook.js | 6 +- .../tests/views/fields/char_field_tests.js | 71 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/views/fields/input_field_hook.js b/addons/web/static/src/views/fields/input_field_hook.js index 8f39485ac17..e77a75cf77a 100644 --- a/addons/web/static/src/views/fields/input_field_hook.js +++ b/addons/web/static/src/views/fields/input_field_hook.js @@ -110,12 +110,16 @@ export function useInputField(params) { * If it is not such a case, we update the field with the new value. */ useEffect(() => { + // We need to call getValue before the condition to always observe + // the corresponding value in the record. Otherwise, in some cases, + // if the value in the record change the useEffect isn't triggered. + const value = params.getValue(); if ( inputRef.el && !isDirty && !component.props.record.isFieldInvalid(component.props.name) ) { - inputRef.el.value = params.getValue(); + inputRef.el.value = value; lastSetValue = inputRef.el.value; } }); diff --git a/addons/web/static/tests/views/fields/char_field_tests.js b/addons/web/static/tests/views/fields/char_field_tests.js index cd0346cccc6..ca74fc69d19 100644 --- a/addons/web/static/tests/views/fields/char_field_tests.js +++ b/addons/web/static/tests/views/fields/char_field_tests.js @@ -721,6 +721,77 @@ QUnit.module("Fields", (hooks) => { } ); + QUnit.test( + "input field: change value before pending onchange returns (2)", + async function (assert) { + serverData.models.partner.onchanges = { + int_field(obj) { + if (obj.int_field === 7) { + obj.foo = "blabla"; + } else { + obj.foo = "tralala"; + } + }, + }; + + const def = makeDeferred(); + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData, + arch: ` +
+ + + + +
`, + async mockRPC(route, { method }) { + if (method === "onchange") { + await def; + } + }, + }); + assert.strictEqual( + target.querySelector(".o_field_widget[name='foo'] input").value, + "yop", + "should contain the correct value" + ); + + // trigger a deferred onchange + await editInput(target, ".o_field_widget[name='int_field'] input", "7"); + + // insert a value in input foo + target.querySelector(".o_field_widget[name=foo] input").value = "test"; + await triggerEvent(target, ".o_field_widget[name=foo] input", "input"); + + // complete the onchange + def.resolve(); + await nextTick(); + assert.strictEqual( + target.querySelector(".o_field_widget[name='foo'] input").value, + "test", + "The onchange value should not be applied because the input is in edition" + ); + + // apply the value of the input foo + await triggerEvent(target, ".o_field_widget[name=foo] input", "change"); + assert.strictEqual( + target.querySelector(".o_field_widget[name='foo'] input").value, + "test" + ); + + // trigger another onchange (not deferred) + await editInput(target, ".o_field_widget[name='int_field'] input", "10"); + assert.strictEqual( + target.querySelector(".o_field_widget[name='foo'] input").value, + "tralala", + "the onchange value should be applied because the input is not in edition" + ); + } + ); + QUnit.test( "input field: change value before pending onchange returns (with fieldDebounce)", async function (assert) {