[REV] mail: debounce suggestion (and channel selector) searches

This reverts odoo#126869.

Motivation for debounce was to reduce the amount of RPCs, to
not put too much load on server.

However, this makes showing of mention slower than it should,
and the load on server is already adjusted based on duration
of ongoing `fetchSuggestion` RPC: if server can reply with mentions
immediately, then no need to debounce. If the RPC is slow, then
next `fetchSuggestion` waits for current RPC to finish before
making a new fetch.

This PR also fixes this small crash when using mentions:

```
TypeError: Cannot read properties of undefined (reading 'toLowerCase')
    at cleanTerm (format.js:244:1) (/mail/static/src/utils/format.js:244)
```

Steps to reproduce:
- type `@` then `backspace` then `@` then `backspace`, etc.

This happened because suggestion hook internal state had `term` being undefined when there is no term, but `undefined` is invalid for `cleanTerm`. `""` is a better default for no search term, and since this is a string this works with `cleanTerm`.

closes odoo/odoo#127399

Related: odoo/enterprise#43678
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2023-07-05 18:46:42 +02:00
parent a1deba1186
commit e228acb081
17 changed files with 69 additions and 241 deletions
@@ -9,7 +9,6 @@ import {
insertText,
start,
} from "@mail/../tests/helpers/test_utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { editInput, nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
@@ -112,13 +111,10 @@ QUnit.test('display canned response suggestions on typing ":"', async (assert) =
source: "hello",
substitution: "Hello! How are you?",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
await insertText(".o-mail-Composer-input", ":");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
});
@@ -137,14 +133,11 @@ QUnit.test("use a canned response", async (assert) => {
source: "hello",
substitution: "Hello! How are you?",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", ":");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
assert.strictEqual(
@@ -169,16 +162,13 @@ QUnit.test("use a canned response some text", async (assert) => {
source: "hello",
substitution: "Hello! How are you?",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "bluhbluh ");
assert.strictEqual($(".o-mail-Composer-input").val(), "bluhbluh ");
await insertText(".o-mail-Composer-input", ":");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
assert.strictEqual(
@@ -203,14 +193,11 @@ QUnit.test("add an emoji after a canned response", async (assert) => {
source: "hello",
substitution: "Hello! How are you?",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", ":");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
assert.strictEqual(
@@ -65,6 +65,9 @@ export class NavigableList extends Component {
}
open() {
if (this.state.isLoading) {
return;
}
this.load().then(() => {
this.state.open = true;
this.navigate("first");
@@ -77,7 +80,6 @@ export class NavigableList extends Component {
}
async load() {
this.state.isLoading = true;
this.state.options = [];
const makeOption = (opt) => {
return Object.assign(Object.create(opt), {
@@ -85,12 +87,12 @@ export class NavigableList extends Component {
});
};
if (this.props.options instanceof Promise) {
this.state.isLoading = true;
return this.props.options.then((opts) => {
opts.forEach((opt) => this.state.options.push(makeOption(opt)));
this.state.isLoading = false;
});
}
this.state.isLoading = false;
if (this.props.options instanceof Array) {
if (this.props.options.length === 0) {
return;
@@ -1,10 +1,8 @@
/* @odoo-module */
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { useComponent, useEffect, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { useDebounced } from "@web/core/utils/timing";
export function useSuggestion() {
const comp = useComponent();
@@ -22,7 +20,7 @@ export function useSuggestion() {
Object.assign(self.search, {
delimiter: undefined,
position: undefined,
term: undefined,
term: "",
});
self.state.items = undefined;
},
@@ -121,15 +119,14 @@ export function useSuggestion() {
search: {
delimiter: undefined,
position: undefined,
term: undefined,
term: "",
},
state: useState({
count: 0,
items: undefined,
loading: false,
}),
update() {
if (!self.search.delimiter || self.state.loading) {
if (!self.search.delimiter) {
return;
}
const suggestions = suggestionService.searchSuggestions(
@@ -153,10 +150,6 @@ export function useSuggestion() {
self.state.items = { type, mainSuggestions, extraSuggestions };
},
};
const debouncedFetchSuggestions = useDebounced(
(...args) => suggestionService.fetchSuggestions(...args),
DEBOUNCE_FETCH_SUGGESTION_TIME
);
useEffect(
() => {
self.update();
@@ -164,14 +157,12 @@ export function useSuggestion() {
if (self.search.position === undefined || !self.search.delimiter) {
return; // ignore obsolete call
}
self.state.loading = true;
await debouncedFetchSuggestions(self.search, {
await suggestionService.fetchSuggestions(self.search, {
thread: self.thread,
onFetched() {
if (owl.status(comp) === "destroyed") {
return;
}
self.state.loading = false;
self.update();
},
});
@@ -4,8 +4,6 @@ import { cleanTerm } from "@mail/utils/common/format";
import { registry } from "@web/core/registry";
export const DEBOUNCE_FETCH_SUGGESTION_TIME = 500;
export class SuggestionService {
constructor(env, services) {
this.orm = services.orm;
@@ -2,7 +2,6 @@
import { useStore } from "@mail/core/common/messaging_hook";
import { NavigableList } from "@mail/core/common/navigable_list";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { cleanTerm } from "@mail/utils/common/format";
import { createLocalId } from "@mail/utils/common/misc";
import { isEventHandled, markEventHandled } from "@web/core/utils/misc";
@@ -13,7 +12,6 @@ import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
import { _t } from "@web/core/l10n/translation";
import { TagsList } from "@web/core/tags_list/tags_list";
import { useService } from "@web/core/utils/hooks";
import { useDebounced } from "@web/core/utils/timing";
export class ChannelSelector extends Component {
static components = { TagsList, NavigableList };
@@ -38,10 +36,6 @@ export class ChannelSelector extends Component {
onMounted(() => this.inputRef.el.focus());
}
this.markEventHandled = markEventHandled;
this.debouncedFetchSuggestions = useDebounced(
this.fetchSuggestions.bind(this),
DEBOUNCE_FETCH_SUGGESTION_TIME
);
}
async fetchSuggestions() {
@@ -205,7 +199,7 @@ export class ChannelSelector extends Component {
this.props.category.id === "channels"
? "discuss.ChannelSelector.channel"
: "discuss.ChannelSelector.chat",
options: this.debouncedFetchSuggestions(),
options: this.fetchSuggestions(),
};
}
}
@@ -28,7 +28,6 @@ import {
import { file } from "web.test_utils";
const { inputFiles } = file;
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
QUnit.module("composer", {
async beforeEach() {
@@ -360,16 +359,13 @@ QUnit.test("add an emoji after a partner mention", async (assert) => {
Command.create({ partner_id: partnerId }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "T");
await insertText(".o-mail-Composer-input", "e");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
assert.strictEqual($(".o-mail-Composer-input").val().replace(/\s/, " "), "@TestPartner ");
@@ -384,7 +380,7 @@ QUnit.test("mention a channel after some text", async (assert) => {
name: "General",
channel_type: "channel",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
@@ -395,9 +391,6 @@ QUnit.test("mention a channel after some text", async (assert) => {
"text content of composer should have content"
);
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
assert.strictEqual(
@@ -413,14 +406,11 @@ QUnit.test("add an emoji after a channel mention", async (assert) => {
name: "General",
channel_type: "channel",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
assert.strictEqual(
@@ -437,13 +427,10 @@ QUnit.test("add an emoji after a channel mention", async (assert) => {
QUnit.test("pending mentions are kept when toggling composer", async (assert) => {
const pyEnv = await startServer();
const { advanceTime, openFormView } = await start({ hasTimeControl: true });
const { openFormView } = await start();
await openFormView("res.partner", pyEnv.currentPartnerId);
await click("button:contains(Send message)");
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:contains(Mitchell Admin)");
await click("button:contains(Send message)");
await click("button:contains(Send message)");
@@ -624,8 +611,7 @@ QUnit.test("Select composer suggestion via Enter does not send the message", asy
Command.create({ partner_id: partnerId }),
],
});
const { advanceTime, openDiscuss } = await start({
hasTimeControl: true,
const { openDiscuss } = await start({
async mockRPC(route, args) {
if (route === "/mail/message/post") {
assert.step("message_post");
@@ -635,9 +621,6 @@ QUnit.test("Select composer suggestion via Enter does not send the message", asy
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Shrek");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await afterNextRender(() => triggerHotkey("Enter"));
assert.equal($(".o-mail-Composer-input").val().trim(), "@Shrek");
assert.verifySteps([]);
@@ -1,9 +1,8 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { editInput, nextTick, triggerEvent } from "@web/../tests/helpers/utils";
import { editInput, triggerEvent } from "@web/../tests/helpers/utils";
QUnit.module("call");
@@ -11,7 +10,7 @@ QUnit.test("no default rtc after joining a chat conversation", async (assert) =>
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussCategoryItem");
@@ -19,8 +18,6 @@ QUnit.test("no default rtc after joining a chat conversation", async (assert) =>
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
@@ -37,21 +34,17 @@ QUnit.test("no default rtc after joining a group conversation", async (assert) =
{ name: "Luigi" },
]);
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussCategoryItem");
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "luigi")
);
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
@@ -4,8 +4,7 @@ import { Composer } from "@mail/core/common/composer";
import { Command } from "@mail/../tests/helpers/command";
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("suggestion", {
async beforeEach() {
@@ -111,7 +110,7 @@ QUnit.test("Sort partner suggestions by recent chats", async (assert) => {
channel_type: "chat",
},
]);
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
await click(".o-mail-DiscussCategoryItem:contains('User 2')");
await insertText(".o-mail-Composer-input", "This is a test");
@@ -120,9 +119,6 @@ QUnit.test("Sort partner suggestions by recent chats", async (assert) => {
await click(".o-mail-DiscussCategoryItem:contains('General')");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "User");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsN($, ".o-mail-Composer-suggestion", 3);
assert.strictEqual($(".o-mail-Composer-suggestion").eq(0).text(), "User 2");
assert.strictEqual($(".o-mail-Composer-suggestion").eq(1).text(), "User 1");
@@ -15,8 +15,7 @@ import {
startServer,
} from "@mail/../tests/helpers/test_utils";
import { makeDeferred, nextTick } from "@web/../tests/helpers/utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { makeDeferred } from "@web/../tests/helpers/utils";
QUnit.module("chat window: new message");
@@ -104,8 +103,7 @@ QUnit.test(
1920,
"should have enough space to open 3 chat windows simultaneously"
);
const { advanceTime } = await start({
hasTimeControl: true,
await start({
mockRPC(route, args) {
if (args.method === "im_search") {
imSearchDef.resolve();
@@ -139,8 +137,6 @@ QUnit.test(
// search for a user in "new message" autocomplete
await afterNextRender(async () => {
await insertText(".o-discuss-ChannelSelector input", "131");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await imSearchDef;
});
assert.containsOnce($, ".o-discuss-ChannelSelector-suggestion a");
@@ -175,12 +171,10 @@ QUnit.test(
channel_type: "chat",
name: "Partner 131",
});
const { advanceTime } = await start({ hasTimeControl: true });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
await insertText(".o-discuss-ChannelSelector", "131");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion a");
assert.containsNone($, ".o-mail-ChatWindow-header:contains(New message)");
assert.containsOnce($, ".o-mail-ChatWindow");
@@ -192,8 +186,7 @@ QUnit.test("new message autocomplete should automatically select first result",
const partnerId = pyEnv["res.partner"].create({ name: "Partner 131" });
pyEnv["res.users"].create({ partner_id: partnerId });
const imSearchDef = makeDeferred();
const { advanceTime } = await start({
hasTimeControl: true,
await start({
mockRPC(route, args) {
if (args.method === "im_search") {
imSearchDef.resolve();
@@ -206,8 +199,6 @@ QUnit.test("new message autocomplete should automatically select first result",
// search for a user in "new message" autocomplete
await afterNextRender(async () => {
await insertText(".o-discuss-ChannelSelector", "131");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await imSearchDef;
});
assert.hasClass($(".o-discuss-ChannelSelector-suggestion a"), "o-mail-NavigableList-active");
@@ -7,16 +7,15 @@ import {
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { triggerHotkey, patchWithCleanup, nextTick } from "@web/../tests/helpers/utils";
import { triggerHotkey, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("crosstab");
QUnit.test("Channel subscription is renewed when channel is manually added", async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "General", channel_member_ids: [] });
const { advanceTime, env, openDiscuss } = await start({ hasTimeControl: true });
const { env, openDiscuss } = await start();
patchWithCleanup(env.services["bus_service"], {
forceUpdateChannels() {
assert.step("update-channels");
@@ -25,8 +24,6 @@ QUnit.test("Channel subscription is renewed when channel is manually added", asy
await openDiscuss();
await click("[title='Add or join a channel']");
await insertText(".o-discuss-ChannelSelector", "General");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await afterNextRender(() => triggerHotkey("Enter"));
assert.verifySteps(["update-channels"]);
});
@@ -7,16 +7,14 @@ import {
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { editInput, nextTick, triggerEvent, triggerHotkey } from "@web/../tests/helpers/utils";
import { editInput, triggerEvent, triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("discuss");
QUnit.test("can create a new channel [REQUIRE FOCUS]", async (assert) => {
await startServer();
const { advanceTime, openDiscuss } = await start({
hasTimeControl: true,
const { openDiscuss } = await start({
mockRPC(route, params) {
if (
route.startsWith("/mail") ||
@@ -37,8 +35,6 @@ QUnit.test("can create a new channel [REQUIRE FOCUS]", async (assert) => {
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "abc")
);
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-mail-DiscussCategoryItem");
assert.containsNone($, ".o-mail-Discuss-content .o-mail-Message");
@@ -58,15 +54,14 @@ QUnit.test(
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussCategoryItem");
await click("i[title='Start a conversation']");
await afterNextRender(async () => {
editInput(document.body, ".o-discuss-ChannelSelector input", "mario");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
});
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-discuss-ChannelSelector span[title='Mario']");
assert.containsNone($, ".o-mail-DiscussCategoryItem");
@@ -76,10 +71,9 @@ QUnit.test(
});
assert.containsNone($, ".o-discuss-ChannelSelector span[title='Mario']");
await afterNextRender(async () => {
editInput(document.body, ".o-discuss-ChannelSelector input", "mario");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
});
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
@@ -92,8 +86,7 @@ QUnit.test("can join a chat conversation", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { advanceTime, openDiscuss } = await start({
hasTimeControl: true,
const { openDiscuss } = await start({
mockRPC(route, params) {
if (
route.startsWith("/mail") ||
@@ -114,8 +107,6 @@ QUnit.test("can join a chat conversation", async (assert) => {
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
@@ -138,17 +129,13 @@ QUnit.test("can create a group chat conversation", async (assert) => {
{ name: "Luigi" },
]);
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussCategoryItem");
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await insertText(".o-discuss-ChannelSelector input", "Mario");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await insertText(".o-discuss-ChannelSelector input", "Luigi");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
@@ -158,12 +145,10 @@ QUnit.test("can create a group chat conversation", async (assert) => {
});
QUnit.test("chat search should display no result when no matches found", async (assert) => {
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await insertText(".o-discuss-ChannelSelector", "Rainbow Panda");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
assert.containsOnce($, ".o-discuss-ChannelSelector-suggestion:contains(No results found)");
});
@@ -173,13 +158,11 @@ QUnit.test(
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Panda" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussCategoryItem");
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await insertText(".o-discuss-ChannelSelector", "Panda");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
assert.containsOnce($, ".o-discuss-ChannelSelector-suggestion");
await click(".o-mail-DiscussSidebar");
assert.containsNone($, ".o-discuss-ChannelSelector-suggestion");
@@ -204,14 +187,12 @@ QUnit.test("Chat is added to discuss on other tab that the one that joined", asy
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Jerry Golay" });
pyEnv["res.users"].create({ partner_id: partnerId });
const tab1 = await start({ asTab: true, hasTimeControl: true });
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss();
await tab2.openDiscuss();
await tab1.click(".o-mail-DiscussCategory-chat .o-mail-DiscussCategory-add");
await tab1.insertText(".o-discuss-ChannelSelector input", "Jer");
await tab1.advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await tab1.click(".o-discuss-ChannelSelector-suggestion");
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce(tab1.target, ".o-mail-DiscussCategoryItem:contains(Jerry Golay)");
@@ -9,8 +9,7 @@ import {
} from "@mail/../tests/helpers/test_utils";
import { patchUiSize } from "@mail/../tests/helpers/patch_ui_size";
import { nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("messaging menu");
@@ -19,13 +18,11 @@ QUnit.test('"Start a conversation" item selection opens chat', async (assert) =>
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Gandalf" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
await click("button:contains(Chat)");
await click("button:contains(Start a conversation)");
await insertText("input[placeholder='Start a conversation']", "Gandalf");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce($, ".o-mail-ChatWindow-name[title='Gandalf']");
@@ -35,26 +32,22 @@ QUnit.test('"New channel" item selection opens channel (existing)', async (asser
patchUiSize({ height: 360, width: 640 });
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "Gryffindors" });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
await click("button:contains(Channel)");
await click("button:contains(New Channel)");
await insertText("input[placeholder='Add or join a channel']", "Gryff");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-mail-ChatWindow-name[title='Gryffindors']");
});
QUnit.test('"New channel" item selection opens channel (new)', async (assert) => {
patchUiSize({ height: 360, width: 640 });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
await click("button:contains(Channel)");
await click("button:contains(New Channel)");
await insertText("input[placeholder='Add or join a channel']", "slytherins");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-mail-ChatWindow-name[title='slytherins']");
});
@@ -8,7 +8,6 @@ import {
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import { makeDeferred } from "@web/../tests/helpers/utils";
@@ -23,8 +22,7 @@ QUnit.test("sidebar find shows channels matching search term", async (assert) =>
name: "test",
});
const def = makeDeferred();
const { advanceTime, openDiscuss } = await start({
hasTimeControl: true,
const { openDiscuss } = await start({
async mockRPC(route, args) {
if (args.method === "search_read") {
def.resolve();
@@ -34,7 +32,6 @@ QUnit.test("sidebar find shows channels matching search term", async (assert) =>
await openDiscuss();
await click(".o-mail-DiscussCategory-add");
await insertText(".o-discuss-ChannelSelector input", "test");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await def;
await nextAnimationFrame(); // ensures search_read rpc is rendered.
// When searching for a single existing channel, the results list will have at least 2 lines:
@@ -55,8 +52,7 @@ QUnit.test(
name: "test",
});
const def = makeDeferred();
const { advanceTime, openDiscuss } = await start({
hasTimeControl: true,
const { openDiscuss } = await start({
async mockRPC(route, args) {
if (args.method === "search_read") {
def.resolve();
@@ -66,7 +62,6 @@ QUnit.test(
await openDiscuss();
await click(".o-mail-DiscussCategory-add");
await insertText(".o-discuss-ChannelSelector input", "test");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await def;
await nextAnimationFrame(); // ensures search_read rpc is rendered.
// When searching for a single existing channel, the results list will have at least 2 lines:
@@ -7,14 +7,8 @@ import {
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
import {
patchWithCleanup,
triggerHotkey,
mockTimeout,
nextTick,
} from "@web/../tests/helpers/utils";
import { patchWithCleanup, triggerHotkey, mockTimeout } from "@web/../tests/helpers/utils";
QUnit.module("discuss inbox");
@@ -63,7 +57,7 @@ QUnit.test("reply: discard on pressing escape", async (assert) => {
notification_type: "inbox",
res_partner_id: pyEnv.currentPartnerId,
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
@@ -79,9 +73,6 @@ QUnit.test("reply: discard on pressing escape", async (assert) => {
// Escape on suggestion prompt does not stop replying
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
@@ -16,14 +16,12 @@ import { url } from "@web/core/utils/urls";
import {
editInput,
makeDeferred,
nextTick,
patchWithCleanup,
triggerEvent,
triggerHotkey,
} from "@web/../tests/helpers/utils";
const { DateTime } = luxon;
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
QUnit.module("message");
@@ -321,14 +319,11 @@ QUnit.test("can add new mentions when editing message", async (assert) => {
res_id: channelId,
message_type: "comment",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await click(".o-mail-Message [title='Expand']");
await click(".o-mail-Message [title='Edit']");
await insertText(".o-mail-Composer-input", " @");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:contains(TestPartner)");
await click(".o-mail-Message a:contains('save')");
assert.strictEqual($(".o-mail-Message-body")[0].innerText, "Hello @TestPartner");
@@ -1241,15 +1236,12 @@ QUnit.test("Chat with partner should be opened after clicking on their mention",
email: "testpartner@odoo.com",
});
pyEnv["res.users"].create({ partner_id: partnerId });
const { advanceTime, openFormView } = await start({ hasTimeControl: true });
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
await click("button:contains(Send message)");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "T");
await insertText(".o-mail-Composer-input", "e");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:contains(Test Partner)");
await click(".o-mail-Composer-send");
await click(".o_mail_redirect");
@@ -1292,13 +1284,10 @@ QUnit.test("Channel should be opened after clicking on its mention", async (asse
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["discuss.channel"].create({ name: "my-channel" });
const { advanceTime, openFormView } = await start({ hasTimeControl: true });
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
await click("button:contains(Send message)");
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:contains(my-channel)");
await click(".o-mail-Composer-send");
await click(".o_channel_redirect");
@@ -10,7 +10,6 @@ import {
patchWithCleanup,
triggerHotkey,
} from "@web/../tests/helpers/utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
QUnit.module("suggestion", {
async beforeEach() {
@@ -42,14 +41,11 @@ QUnit.test('display partner mention suggestions on typing "@"', async (assert) =
Command.create({ partner_id: partnerId_2 }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsN($, ".o-mail-Composer-suggestion", 3);
});
@@ -74,7 +70,7 @@ QUnit.test(
Command.create({ partner_id: partnerId_2 }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "first message");
@@ -82,23 +78,17 @@ QUnit.test(
await nextTick();
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsN($, ".o-mail-Composer-suggestion", 3);
}
);
QUnit.test('display partner mention suggestions on typing "@" in chatter', async (assert) => {
const pyEnv = await startServer();
const { advanceTime, openFormView } = await start({ hasTimeControl: true });
const { openFormView } = await start();
await openFormView("res.partner", pyEnv.currentPartnerId);
await click("button:contains(Send message)");
assert.containsNone($, ".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(Mitchell Admin)");
});
@@ -115,12 +105,9 @@ QUnit.test("show other channel member in @ mention", async (assert) => {
Command.create({ partner_id: partnerId }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(TestPartner)");
});
@@ -137,12 +124,9 @@ QUnit.test("select @ mention insert mention text in composer", async (assert) =>
Command.create({ partner_id: partnerId }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:contains(TestPartner)");
assert.strictEqual($(".o-mail-Composer-input").val().trim(), "@TestPartner");
});
@@ -153,13 +137,10 @@ QUnit.test('display channel mention suggestions on typing "#"', async (assert) =
name: "General",
channel_type: "channel",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
});
@@ -169,14 +150,11 @@ QUnit.test("mention a channel", async (assert) => {
name: "General",
channel_type: "channel",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
assert.strictEqual(
@@ -21,7 +21,6 @@ import {
patchWithCleanup,
triggerEvents,
} from "@web/../tests/helpers/utils";
import { DEBOUNCE_FETCH_SUGGESTION_TIME } from "@mail/core/common/suggestion_service";
QUnit.module("thread");
@@ -209,12 +208,9 @@ QUnit.test("thread is still scrolling after scrolling up then to bottom", async
QUnit.test("mention a channel with space in the name", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good boy" });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
@@ -224,12 +220,9 @@ QUnit.test("mention a channel with space in the name", async (assert) => {
QUnit.test('mention a channel with "&" in the name', async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General & good" });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
@@ -550,13 +543,10 @@ QUnit.test("Mention a partner with special character (e.g. apostrophe ')", async
Command.create({ partner_id: partnerId }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Pyn");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce(
@@ -585,19 +575,13 @@ QUnit.test("mention 2 different partners that have the same name", async (assert
Command.create({ partner_id: partnerId_2 }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Te");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:eq(0)");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Te");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:eq(1)");
await click(".o-mail-Composer-send");
assert.containsOnce($, ".o-mail-Message-body");
@@ -614,13 +598,10 @@ QUnit.test("mention 2 different partners that have the same name", async (assert
QUnit.test("mention a channel on a second line when the first line contains #", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good" });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#blabla\n");
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
@@ -632,12 +613,9 @@ QUnit.test(
async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good" });
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
const text = $(".o-mail-Composer-input").val();
$(".o-mail-Composer-input").val(text.slice(0, -1));
@@ -661,19 +639,13 @@ QUnit.test("mention 2 different channels that have the same name", async (assert
name: "my channel",
},
]);
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId_1);
await insertText(".o-mail-Composer-input", "#");
await insertText(".o-mail-Composer-input", "m");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:eq(0)");
await insertText(".o-mail-Composer-input", "#");
await insertText(".o-mail-Composer-input", "m");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion:eq(1)");
await click(".o-mail-Composer-send");
assert.containsOnce($, ".o-mail-Message-body");
@@ -702,14 +674,11 @@ QUnit.test(
Command.create({ partner_id: partnerId }),
],
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "email@odoo.com\n");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Te");
await advanceTime(DEBOUNCE_FETCH_SUGGESTION_TIME);
await nextTick();
await nextTick();
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce(