[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:
@@ -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)");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user