diff --git a/addons/web/static/src/core/autocomplete/autocomplete.js b/addons/web/static/src/core/autocomplete/autocomplete.js
index d4693d32757..52437d8ff3a 100644
--- a/addons/web/static/src/core/autocomplete/autocomplete.js
+++ b/addons/web/static/src/core/autocomplete/autocomplete.js
@@ -12,6 +12,7 @@ export class AutoComplete extends Component {
this.nextSourceId = 0;
this.nextOptionId = 0;
this.sources = [];
+ this.inEdition = false;
this.state = useState({
navigationRev: 0,
@@ -45,7 +46,6 @@ export class AutoComplete extends Component {
}
}, this.constructor.timeout);
-
useExternalListener(window, "scroll", this.onWindowScroll, true);
this.hotkey = useService("hotkey");
@@ -54,8 +54,10 @@ export class AutoComplete extends Component {
owl.onWillUpdateProps((nextProps) => {
if (this.props.value !== nextProps.value || this.forceValFromProp) {
this.forceValFromProp = false;
- this.state.value = nextProps.value;
- this.inputRef.el.value = nextProps.value;
+ if (!this.inEdition) {
+ this.state.value = nextProps.value;
+ this.inputRef.el.value = nextProps.value;
+ }
this.close();
}
});
@@ -156,6 +158,7 @@ export class AutoComplete extends Component {
}
selectOption(indices, params = {}) {
const option = this.sources[indices[0]].options[indices[1]];
+ this.inEdition = false;
if (option.unselectable) {
this.inputRef.el.value = "";
this.close();
@@ -234,6 +237,7 @@ export class AutoComplete extends Component {
this.props.onBlur({
inputValue: value,
});
+ this.inEdition = false;
this.close();
}
}
@@ -250,6 +254,7 @@ export class AutoComplete extends Component {
});
}
async onInput() {
+ this.inEdition = true;
this.pendingPromise = this.pendingPromise || new Deferred();
this.loadingPromise = this.pendingPromise;
this.debouncedProcessInput();
diff --git a/addons/web/static/tests/core/autocomplete_tests.js b/addons/web/static/tests/core/autocomplete_tests.js
index 75ee1e0f326..6c3e3a26fb5 100644
--- a/addons/web/static/tests/core/autocomplete_tests.js
+++ b/addons/web/static/tests/core/autocomplete_tests.js
@@ -397,4 +397,84 @@ QUnit.module("Components", (hooks) => {
assert.strictEqual(target.querySelector(".o-autocomplete input"), document.activeElement);
});
+
+ QUnit.test("autocomplete in edition keep edited value before select option", async (assert) => {
+ class Parent extends Component {
+ setup() {
+ this.state = useState({ value: "Hello" });
+ }
+
+ onClick() {
+ this.state.value = "My Click";
+ }
+
+ onSelect() {
+ this.state.value = "My Selection";
+ }
+ }
+ Parent.components = { AutoComplete };
+ Parent.template = xml`
+
+
+ `;
+
+ await mount(Parent, target, { env });
+ const input = target.querySelector(".o-autocomplete input");
+ input.value = "Yolo";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "Yolo");
+
+ await click(target, ".myButton");
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "Yolo");
+
+ // Leave inEdition mode when selecting an option
+ await click(target.querySelector(".o-autocomplete input"));
+ await click(target.querySelectorAll(".o-autocomplete--dropdown-item")[0]);
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "My Selection");
+
+ await click(target, ".myButton");
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "My Click");
+ });
+
+ QUnit.test("autocomplete in edition keep edited value before blur", async (assert) => {
+ let count = 0;
+ class Parent extends Component {
+ setup() {
+ this.state = useState({ value: "Hello" });
+ }
+
+ onClick() {
+ this.state.value = `My Click ${count++}`;
+ }
+ }
+ Parent.components = { AutoComplete };
+ Parent.template = xml`
+
+
+ `;
+
+ await mount(Parent, target, { env });
+ let input = target.querySelector(".o-autocomplete input");
+ input.value = "";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "");
+
+ await click(target, ".myButton");
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "");
+
+ // Leave inEdition mode when blur the input
+ input = target.querySelector(".o-autocomplete input");
+ await triggerEvent(input, null, "blur");
+ await nextTick();
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "");
+
+ await click(target, ".myButton");
+ assert.strictEqual(target.querySelector(".o-autocomplete input").value, "My Click 1");
+ });
});
diff --git a/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js b/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js
index 755da186fc8..dac99c73692 100644
--- a/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js
@@ -10,6 +10,7 @@ import {
selectDropdownItem,
triggerEvent,
clickDiscard,
+ clickOpenedDropdownItem,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { browser } from "@web/core/browser/browser";
@@ -360,8 +361,7 @@ QUnit.module("Fields", (hooks) => {
const input = target.querySelector(".o_field_widget[name=user_id] input");
input.value = "yy";
await triggerEvent(input, null, "input");
- await click(target, ".o_field_widget[name=user_id] input");
- await selectDropdownItem(target, "user_id", "Create and edit...");
+ await clickOpenedDropdownItem(target, "user_id", "Create and edit...");
await clickDiscard(target.querySelector(".modal"));
assert.strictEqual(target.querySelector(".o_field_widget[name=user_id] input").value, "");
diff --git a/addons/web/static/tests/views/fields/many2one_field_tests.js b/addons/web/static/tests/views/fields/many2one_field_tests.js
index 534c89afab7..088ce052bf2 100644
--- a/addons/web/static/tests/views/fields/many2one_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2one_field_tests.js
@@ -692,8 +692,7 @@ QUnit.module("Fields", (hooks) => {
const input = target.querySelector(".o_field_widget[name=trululu] input");
input.value = "yy";
await triggerEvent(input, null, "input");
- await click(target, ".o_field_widget[name=trululu] input");
- await selectDropdownItem(target, "trululu", "Create and edit...");
+ await clickOpenedDropdownItem(target, "trululu", "Create and edit...");
await clickSave(target.querySelector(".modal"));
assert.verifySteps([`web_save: [[],{"name":"yy"}]`]);
@@ -742,8 +741,7 @@ QUnit.module("Fields", (hooks) => {
await click(target.querySelectorAll(".o_data_cell")[0]);
const input = target.querySelector(".o_field_widget[name=user_id] input");
await editInput(input, null, "yy");
- await click(target, ".o_field_widget[name=user_id] input");
- await selectDropdownItem(target, "user_id", 'Create "yy"');
+ await clickOpenedDropdownItem(target, "user_id", 'Create "yy"');
assert.verifySteps(["name_create"]);
}
@@ -834,6 +832,46 @@ QUnit.module("Fields", (hooks) => {
}
);
+ QUnit.test(
+ "edit many2one before onchange is finished should not reset the value",
+ async function (assert) {
+ serverData.models.partner.onchanges.display_name = function (obj) {
+ obj.user_id = 19;
+ };
+
+ const def = makeDeferred();
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ resId: 1,
+ serverData,
+ arch: `
+
`,
+ async mockRPC(route, { method }) {
+ if (method === "onchange") {
+ assert.step("onchange");
+ await def;
+ }
+ },
+ });
+
+ await editInput(target, "[name='display_name'] input", "new name");
+ const input = target.querySelector("[name='user_id'] input");
+ input.value = "Plop";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(target.querySelector("[name='user_id'] input").value, "Plop");
+
+ def.resolve();
+ await nextTick();
+ assert.strictEqual(target.querySelector("[name='user_id'] input").value, "Plop");
+
+ assert.verifySteps(["onchange"]);
+ }
+ );
+
QUnit.test("many2one doesn't trigger field_change when being emptied", async function (assert) {
await makeView({
type: "list",