From 0ae059848fa68414a68cd02ff36a1c2058493773 Mon Sep 17 00:00:00 2001 From: zel-odoo Date: Wed, 29 Nov 2023 17:58:14 +0100 Subject: [PATCH] [FIX] mail: traceback when switch nav list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a nav list is open, if there is a new props provided, a error could be thrown. Reproduce: 1. write "@aUser #ge" in the composer 2. click on the @aUser => traceback The problem is because when NavigableList is open, the `props.optionTemplate` will change before `state.options`, leading to a mismatch between the template and the record. This commit solves the issue by removing syncing props.options and state.options altogether. This felt needed to enrich options with id, but this is unnecessary because the forged id was simply the index of option in the list. closes odoo/odoo#144260 X-original-commit: b41f25bc7b41799bd34e27e8e1013d7a85320bcc Signed-off-by: Alexandre Kühn (aku) --- .../static/src/core/common/navigable_list.js | 40 +++++++------------ .../static/src/core/common/navigable_list.xml | 8 ++-- .../static/tests/composer/composer_tests.js | 29 ++++++++++++++ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/addons/mail/static/src/core/common/navigable_list.js b/addons/mail/static/src/core/common/navigable_list.js index 3d2a9ad0080..099827ce930 100644 --- a/addons/mail/static/src/core/common/navigable_list.js +++ b/addons/mail/static/src/core/common/navigable_list.js @@ -28,9 +28,8 @@ export class NavigableList extends Component { setup() { this.rootRef = useRef("root"); this.state = useState({ - activeOption: null, + activeIndex: null, open: false, - options: [], }); this.hotkey = useService("hotkey"); this.hotkeysToRemove = []; @@ -58,33 +57,22 @@ export class NavigableList extends Component { } get show() { - return Boolean(this.state.open && (this.props.isLoading || this.state.options.length)); + return Boolean(this.state.open && (this.props.isLoading || this.props.options.length)); } open() { - this.load(); this.state.open = true; + this.state.activeIndex = null; this.navigate("first"); } close() { this.state.open = false; - this.state.activeOption = null; + this.state.activeIndex = null; } - load() { - this.state.options = []; - this.state.options = this.props.options.map((option, index) => ({ - ...option, - id: index, - })); - } - - isActiveOption(option) { - return this.state.activeOption?.id === option.id; - } - - selectOption(ev, option, params = {}) { + selectOption(ev, index, params = {}) { + const option = this.props.options[index]; if (option.unselectable) { this.close(); return; @@ -96,14 +84,14 @@ export class NavigableList extends Component { } navigate(direction) { - const activeOptionId = this.state.activeOption ? this.state.activeOption.id : -1; + const activeOptionId = this.state.activeIndex !== null ? this.state.activeIndex : 0; let targetId = undefined; switch (direction) { case "first": targetId = 0; break; case "last": - targetId = this.state.options.length - 1; + targetId = this.props.options.length - 1; break; case "previous": targetId = activeOptionId - 1; @@ -114,7 +102,7 @@ export class NavigableList extends Component { break; case "next": targetId = activeOptionId + 1; - if (targetId > this.state.options.length - 1) { + if (targetId > this.props.options.length - 1) { this.navigate("first"); return; } @@ -122,7 +110,7 @@ export class NavigableList extends Component { default: return; } - this.state.activeOption = this.state.options.find((o) => o.id === targetId); + this.state.activeIndex = targetId; } onKeydown(ev) { @@ -132,11 +120,11 @@ export class NavigableList extends Component { const hotkey = getActiveHotkey(ev); switch (hotkey) { case "enter": - if (!this.show || !this.state.activeOption) { + if (!this.show || this.state.activeIndex === null) { return; } markEventHandled(ev, "NavigableList.select"); - this.selectOption(ev, this.state.activeOption); + this.selectOption(ev, this.state.activeIndex); break; case "escape": markEventHandled(ev, "NavigableList.close"); @@ -157,7 +145,7 @@ export class NavigableList extends Component { ev.preventDefault(); } - onOptionMouseEnter(option) { - this.state.activeOption = option; + onOptionMouseEnter(index) { + this.state.activeIndex = index; } } diff --git a/addons/mail/static/src/core/common/navigable_list.xml b/addons/mail/static/src/core/common/navigable_list.xml index 45fe0f5895c..38170334a71 100644 --- a/addons/mail/static/src/core/common/navigable_list.xml +++ b/addons/mail/static/src/core/common/navigable_list.xml @@ -12,13 +12,13 @@
- + diff --git a/addons/mail/static/tests/composer/composer_tests.js b/addons/mail/static/tests/composer/composer_tests.js index 8a0fda96b53..91bb7e3fca6 100644 --- a/addons/mail/static/tests/composer/composer_tests.js +++ b/addons/mail/static/tests/composer/composer_tests.js @@ -388,6 +388,35 @@ QUnit.test("pending mentions are kept when toggling composer", async () => { await contains(".o-mail-Message-body a.o_mail_redirect", { text: "@Mitchell Admin" }); }); +QUnit.test("composer suggestion should match with input selection", async () => { + const pyEnv = await startServer(); + const partnerId = pyEnv["res.partner"].create({ + email: "testpartner@odoo.com", + name: "Luigi", + }); + const channelId = pyEnv["discuss.channel"].create({ + name: "Mario Party", + channel_member_ids: [ + Command.create({ partner_id: pyEnv.currentPartnerId }), + Command.create({ partner_id: partnerId }), + ], + }); + const { openDiscuss } = await start(); + openDiscuss(channelId); + await contains(".o-mail-Composer-input", { value: "" }); + await insertText(".o-mail-Composer-input", "#"); + await contains(".o-mail-Composer-suggestion", { text: "#Mario Party" }); + await click(".o-mail-Composer-suggestion"); + await contains(".o-mail-Composer-input", { value: "#Mario Party " }); + await insertText(".o-mail-Composer-input", "@"); + await contains(".o-mail-Composer-suggestion", { text: "Luigi" }); + $(".o-mail-Composer-input")[0].setSelectionRange(3, 3); + await contains(".o-mail-Composer-suggestion", { text: "#Mario Party" }); + const textarea = $(".o-mail-Composer-input")[0]; + textarea.setSelectionRange(textarea.value.length, textarea.value.length); + await contains(".o-mail-Composer-suggestion", { text: "Luigi" }); +}); + QUnit.test('do not post message on channel with "SHIFT-Enter" keyboard shortcut', async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "general" });