[FIX] mail, *: fix suggestions after search/fetch returned no result

*: im_livechat.

Before this commit, suggestions were not showing anymore after
search/fetch returned no results. This commit fixes the issue
by resetting the search state when nothing is found.

task-3459933/3455060

closes odoo/odoo#132264

X-original-commit: 13b3f36a0d71bb55e2ca56fd207c7dfaba23aa86
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2023-08-18 21:45:11 +02:00
committed by Alexandre Kühn
parent 9e012914af
commit 01fa22e0c9
5 changed files with 108 additions and 20 deletions
@@ -0,0 +1,31 @@
/* @odoo-module */
import { Command } from "@mail/../tests/helpers/command";
import { insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("suggestion");
QUnit.test("Suggestions are shown after delimiter was used in text (:)", async (assert) => {
const pyEnv = await startServer();
pyEnv["mail.shortcode"].create({
source: "hello",
substitution: "Hello dear customer, how may I help you?",
});
const channelId = pyEnv["discuss.channel"].create({
anonymous_name: "Visitor",
channel_type: "livechat",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: pyEnv.publicPartnerId }),
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", ")");
assert.containsNone($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", " ");
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(hello)");
});
@@ -8,6 +8,8 @@ import { useService } from "@web/core/utils/hooks";
export function useSuggestion() {
const comp = useComponent();
const sequential = useSequential();
let pendingFetchCount = 0;
/** @type {import("@mail/core/common/suggestion_service").SuggestionService} */
const suggestionService = useService("mail.suggestion");
const self = {
clearRawMentions() {
@@ -38,10 +40,21 @@ export function useSuggestion() {
if (self.search.position !== undefined && self.search.position < selectionStart) {
candidatePositions.push(self.search.position);
}
// consider the char before the current cursor position if the
// consider the chars before the current cursor position if the
// current delimiter is no longer valid (or if there is none)
if (selectionStart > 0) {
candidatePositions.push(selectionStart - 1);
let numberOfSpaces = 0;
for (let index = selectionStart - 1; index >= 0; --index) {
if (/\s/.test(content[index])) {
numberOfSpaces++;
if (numberOfSpaces === 2) {
// The consideration stops after the second space since
// a majority of partners have a two-word name. This
// removes the need to check for mentions following a
// delimiter used earlier in the content.
break;
}
}
candidatePositions.push(index);
}
const supportedDelimiters = suggestionService.getSupportedDelimiters(self.thread);
for (const candidatePosition of candidatePositions) {
@@ -109,7 +122,7 @@ export function useSuggestion() {
count: 0,
items: undefined,
}),
update() {
update({ clearSearchOnEmpty = true } = {}) {
if (!self.search.delimiter) {
return;
}
@@ -120,7 +133,11 @@ export function useSuggestion() {
);
const { type, mainSuggestions, extraSuggestions = [] } = suggestions;
if (!mainSuggestions.length && !extraSuggestions.length) {
self.state.items = undefined;
if (clearSearchOnEmpty) {
self.clearSearch();
} else {
self.state.items = undefined;
}
return;
}
// arbitrary limit to avoid displaying too many elements at once
@@ -136,21 +153,26 @@ export function useSuggestion() {
};
useEffect(
() => {
self.update();
self.update({ clearSearchOnEmpty: false });
sequential(async () => {
if (self.search.position === undefined || !self.search.delimiter) {
return; // ignore obsolete call
}
await suggestionService.fetchSuggestions(self.search, {
thread: self.thread,
onFetched() {
if (owl.status(comp) === "destroyed") {
return;
}
self.update();
},
});
self.update();
pendingFetchCount++;
try {
await suggestionService.fetchSuggestions(self.search, {
thread: self.thread,
onFetched() {
if (owl.status(comp) === "destroyed") {
return;
}
self.update({ clearSearchOnEmpty: pendingFetchCount === 0 });
},
});
} finally {
pendingFetchCount--;
}
self.update({ clearSearchOnEmpty: pendingFetchCount === 0 });
});
},
() => {
@@ -481,6 +481,7 @@ QUnit.test("leave command on channel", async (assert) => {
assert.hasClass($(".o-mail-DiscussSidebarChannel:contains(general)"), "o-active");
await insertText(".o-mail-Composer-input", "/leave");
await afterNextRender(() => triggerHotkey("Enter"));
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsNone($, ".o-mail-DiscussSidebarChannel:contains(general)");
assert.containsOnce($, ".o-mail-Discuss:contains(No conversation selected.)");
assert.verifySteps(["You unsubscribed from general."]);
@@ -528,6 +529,7 @@ QUnit.test("leave command on chat", async (assert) => {
assert.hasClass($(".o-mail-DiscussSidebarChannel:contains(Chuck Norris)"), "o-active");
await insertText(".o-mail-Composer-input", "/leave");
await afterNextRender(() => triggerHotkey("Enter"));
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsNone($, ".o-mail-DiscussSidebarChannel:contains(Chuck Norris)");
assert.containsOnce($, ".o-mail-Discuss:contains(No conversation selected.)");
assert.verifySteps(["You unpinned your conversation with Chuck Norris"]);
@@ -133,19 +133,24 @@ QUnit.test("Sort partner suggestions by recent chats", async (assert) => {
QUnit.test("suggestion are shown after deleting a character", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
const channelId = pyEnv["discuss.channel"].create({
name: "General",
channel_type: "channel",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "/");
await insertText(".o-mail-Composer-input", "hee");
assert.containsNone($, ".o-mail-Composer-suggestion:contains(help)");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "John Da");
assert.containsNone($, ".o-mail-Composer-suggestion:contains(John Doe)");
// Simulate pressing backspace
await afterNextRender(() => {
const textarea = document.querySelector(".o-mail-Composer-input");
textarea.value = textarea.value.slice(0, -1);
});
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(help)");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(John Doe)");
});
@@ -187,3 +187,31 @@ QUnit.test("Channel suggestions do not crash after rpc returns", async (assert)
await deferred;
assert.verifySteps(["get_mention_suggestions"]);
});
QUnit.test("Suggestions are shown after delimiter was used in text (@)", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "NonExistingUser");
assert.containsNone($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", " ");
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(Mitchell Admin)");
});
QUnit.test("Suggestions are shown after delimiter was used in text (#)", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "NonExistingChannel");
assert.containsNone($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", " ");
await insertText(".o-mail-Composer-input", "#");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(General)");
});