mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 11:52:04 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,81 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
start,
|
||||
startServer,
|
||||
openDiscuss,
|
||||
mockGetMedia,
|
||||
onlineTest,
|
||||
defineMailModels,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { onRpc } from "@web/../tests/web_test_helpers";
|
||||
import { PeerToPeer, UPDATE_EVENT } from "@mail/discuss/call/common/peer_to_peer";
|
||||
|
||||
defineMailModels();
|
||||
|
||||
function connectionReady(p2p) {
|
||||
return new Promise((resolve) => {
|
||||
p2p.addEventListener("update", ({ detail }) => {
|
||||
if (
|
||||
detail.name === UPDATE_EVENT.CONNECTION_CHANGE &&
|
||||
detail.payload.state === "connected"
|
||||
) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function mockPeerToPeerCallEnvironment({ channelId, remoteSessionId }) {
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
const localUserP2P = env.services["discuss.p2p"];
|
||||
const remoteUserP2P = new PeerToPeer({
|
||||
notificationRoute: "/mail/rtc/session/notify_call_members",
|
||||
});
|
||||
remoteUserP2P.connect(remoteSessionId, channelId);
|
||||
|
||||
onRpc("/mail/rtc/session/notify_call_members", async (req) => {
|
||||
const {
|
||||
params: { peer_notifications },
|
||||
} = await req.json();
|
||||
for (const [sender, , message] of peer_notifications) {
|
||||
/**
|
||||
* This is a simplification, if more than 2 users we should check notification.target to know which user
|
||||
* should get the notification.
|
||||
*/
|
||||
if (sender === rtc.selfSession.id) {
|
||||
await remoteUserP2P.handleNotification(sender, message);
|
||||
} else {
|
||||
await localUserP2P.handleNotification(sender, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
const localUserConnected = connectionReady(localUserP2P);
|
||||
const remoteUserConnected = connectionReady(remoteUserP2P);
|
||||
return { localUserConnected, remoteUserConnected };
|
||||
}
|
||||
|
||||
onlineTest("Can join a call in p2p", async (assert) => {
|
||||
mockGetMedia();
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const remoteSessionId = pyEnv["discuss.channel.rtc.session"].create({
|
||||
channel_member_id: pyEnv["discuss.channel.member"].create({
|
||||
channel_id: channelId,
|
||||
partner_id: pyEnv["res.partner"].create({ name: "Remote" }),
|
||||
}),
|
||||
channel_id: channelId,
|
||||
});
|
||||
const { localUserConnected, remoteUserConnected } = await mockPeerToPeerCallEnvironment({
|
||||
channelId,
|
||||
remoteSessionId,
|
||||
});
|
||||
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Join Call']");
|
||||
await contains(".o-discuss-Call");
|
||||
await contains(".o-discuss-CallParticipantCard[title='Remote']");
|
||||
await Promise.all([localUserConnected, remoteUserConnected]);
|
||||
await contains("span[data-connection-state='connected']");
|
||||
});
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
mockGetMedia,
|
||||
mockPermissionsPrompt,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Starting a video call asks for permissions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
mockGetMedia();
|
||||
mockPermissionsPrompt();
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Start Video Call']");
|
||||
await contains(".modal[role='dialog']", { count: 1 });
|
||||
rtc.cameraPermission = "granted";
|
||||
await click(".modal-footer button", { text: "Use Camera" });
|
||||
await contains(".o-discuss-CallActionList button[title='Stop camera']");
|
||||
});
|
||||
|
||||
test("Turning on the microphone asks for permissions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
mockGetMedia();
|
||||
mockPermissionsPrompt();
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Start Call']");
|
||||
await contains(".o-discuss-CallActionList button[title='Turn camera on']");
|
||||
await click(".o-discuss-CallActionList button[title='Unmute']");
|
||||
await contains(".modal[role='dialog']", { count: 1 });
|
||||
rtc.microphonePermission = "granted";
|
||||
await click(".modal-footer button", { text: "Use Microphone" });
|
||||
await contains(".o-discuss-CallActionList button[title='Mute']");
|
||||
await contains(".o-discuss-CallActionList button[title='Turn camera on']");
|
||||
});
|
||||
|
||||
test("Turning on the camera asks for permissions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
mockGetMedia();
|
||||
mockPermissionsPrompt();
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Start Call']");
|
||||
await click(".o-discuss-CallActionList button[title='Turn camera on']");
|
||||
await contains(".modal[role='dialog']", { count: 1 });
|
||||
rtc.cameraPermission = "granted";
|
||||
await click(".modal-footer button", { text: "Use Camera" });
|
||||
await contains(".o-discuss-CallActionList button[title='Stop camera']");
|
||||
});
|
||||
|
||||
test("Turn on both microphone and camera from permission dialog", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
mockGetMedia();
|
||||
mockPermissionsPrompt();
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Start Call']");
|
||||
await contains(".o-discuss-CallActionList button[title='Turn camera on']");
|
||||
await click(".o-discuss-CallActionList button[title='Turn camera on']");
|
||||
await contains(".modal[role='dialog']", { count: 1 });
|
||||
rtc.microphonePermission = "granted";
|
||||
rtc.cameraPermission = "granted";
|
||||
await click(".modal-footer button", { text: "Use microphone and camera" });
|
||||
await contains(".o-discuss-CallActionList button[title='Stop camera']");
|
||||
await contains(".o-discuss-CallActionList button[title='Mute']");
|
||||
});
|
||||
|
||||
test("Combined mic+camera button only shown when both permissions not granted", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
mockGetMedia();
|
||||
mockPermissionsPrompt();
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Start Call']");
|
||||
await click(".o-discuss-CallActionList button[title='Turn camera on']");
|
||||
await contains(".modal-footer button", { count: 2 });
|
||||
await contains(".modal-footer button", { text: "Use microphone and camera" });
|
||||
await contains(".modal-footer button", { text: "Use Camera" });
|
||||
rtc.cameraPermission = "granted";
|
||||
await click(".modal-footer button", { text: "Use Camera" });
|
||||
await click(".o-discuss-CallActionList button[title='Unmute']");
|
||||
await contains(".modal-footer button");
|
||||
await contains(".modal-footer button", { text: "Use Microphone" });
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Call has Picture-in-picture feature", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click("[title='Start Call']");
|
||||
await contains(".o-discuss-Call");
|
||||
await contains(".o-discuss-Call-layoutActions button[title='Picture in Picture']");
|
||||
});
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
editInput,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
SIZES,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test, expect } from "@odoo/hoot";
|
||||
import { advanceTime } from "@odoo/hoot-mock";
|
||||
import { asyncStep, patchWithCleanup, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Renders the call settings", async () => {
|
||||
patchWithCleanup(browser.navigator.mediaDevices, {
|
||||
enumerateDevices: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
deviceId: "mockAudioDeviceId",
|
||||
kind: "audioinput",
|
||||
label: "mockAudioDeviceLabel",
|
||||
},
|
||||
{
|
||||
deviceId: "mockVideoDeviceId",
|
||||
kind: "videoinput",
|
||||
label: "mockVideoDeviceLabel",
|
||||
},
|
||||
]),
|
||||
});
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const env = await start();
|
||||
const rtc = env.services["discuss.rtc"];
|
||||
await openDiscuss(channelId);
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await contains(".o-discuss-CallSettings");
|
||||
await contains("label[aria-label='Camera']");
|
||||
await contains("label[aria-label='Microphone']");
|
||||
await contains("label[aria-label='Audio Output']");
|
||||
await contains("option", { textContent: "Permission Needed", count: 3 });
|
||||
rtc.microphonePermission = "granted";
|
||||
await contains("option[value=mockAudioDeviceId]");
|
||||
rtc.cameraPermission = "granted";
|
||||
await contains("option[value=mockVideoDeviceId]");
|
||||
await contains("button", { text: "Voice Detection" });
|
||||
await contains("button", { text: "Push to Talk" });
|
||||
await contains("span", { text: "Voice detection sensitivity" });
|
||||
await contains("button", { text: "Test" });
|
||||
await contains("label", { text: "Show video participants only" });
|
||||
await contains("label", { text: "Blur video background" });
|
||||
});
|
||||
|
||||
test("activate push to talk", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await click("button", { text: "Push to Talk" });
|
||||
await contains("i[aria-label='Register new key']");
|
||||
await contains("label", { text: "Delay after releasing push-to-talk" });
|
||||
await contains("label", { text: "Voice detection sensitivity", count: 0 });
|
||||
});
|
||||
|
||||
test("activate blur", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await click("input[title='Blur video background']");
|
||||
await contains("label", { text: "Blur video background" });
|
||||
await contains("label", { text: "Edge blur intensity" });
|
||||
});
|
||||
|
||||
test("local storage for call settings", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
|
||||
localStorage.setItem("mail_user_setting_background_blur_amount", "3");
|
||||
localStorage.setItem("mail_user_setting_edge_blur_amount", "5");
|
||||
localStorage.setItem("mail_user_setting_show_only_video", "true");
|
||||
localStorage.setItem("mail_user_setting_use_blur", "true");
|
||||
patchWithCleanup(localStorage, {
|
||||
setItem(key, value) {
|
||||
if (key.startsWith("mail_user_setting")) {
|
||||
asyncStep(`${key}: ${value}`);
|
||||
}
|
||||
return super.setItem(key, value);
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
// testing load from local storage
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await contains("input[title='Show video participants only']:checked");
|
||||
await contains("input[title='Blur video background']:checked");
|
||||
await contains("label[title='Background blur intensity']", { text: "15%" });
|
||||
await contains("label[title='Edge blur intensity']", { text: "25%" });
|
||||
|
||||
// testing save to local storage
|
||||
await click("input[title='Show video participants only']");
|
||||
await waitForSteps(["mail_user_setting_show_only_video: false"]);
|
||||
await click("input[title='Blur video background']");
|
||||
expect(localStorage.getItem("mail_user_setting_use_blur")).toBe(null);
|
||||
await editInput(document.body, ".o-Discuss-CallSettings-thresholdInput", 0.3);
|
||||
await advanceTime(2000); // threshold setting debounce timer
|
||||
await waitForSteps(["mail_user_setting_voice_threshold: 0.3"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
import { describe, expect } from "@odoo/hoot";
|
||||
import { advanceTime } from "@odoo/hoot-mock";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { asyncStep, onRpc, mountWebClient, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
import { defineMailModels, mockGetMedia, onlineTest } from "@mail/../tests/mail_test_helpers";
|
||||
import { PeerToPeer, STREAM_TYPE, UPDATE_EVENT } from "@mail/discuss/call/common/peer_to_peer";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
class Network {
|
||||
_peerToPeerInstances = new Map();
|
||||
_notificationRoute;
|
||||
constructor(route) {
|
||||
this._notificationRoute = route || "/any/mock/notification";
|
||||
onRpc(this._notificationRoute, async (req) => {
|
||||
const {
|
||||
params: { peer_notifications },
|
||||
} = await req.json();
|
||||
for (const notification of peer_notifications) {
|
||||
const [sender_session_id, target_session_ids, content] = notification;
|
||||
for (const id of target_session_ids) {
|
||||
const p2p = this._peerToPeerInstances.get(id);
|
||||
p2p.handleNotification(sender_session_id, content);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @param id
|
||||
* @return {{id, p2p: PeerToPeer}}
|
||||
*/
|
||||
register(id) {
|
||||
const p2p = new PeerToPeer({ notificationRoute: this._notificationRoute });
|
||||
this._peerToPeerInstances.set(id, p2p);
|
||||
return { id, p2p };
|
||||
}
|
||||
close() {
|
||||
for (const p2p of this._peerToPeerInstances.values()) {
|
||||
p2p.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onlineTest("basic peer to peer connection", async () => {
|
||||
await mountWebClient();
|
||||
const channelId = 1;
|
||||
const network = new Network();
|
||||
const user1 = network.register(1);
|
||||
const user2 = network.register(2);
|
||||
user2.p2p.addEventListener("update", ({ detail: { name, payload } }) => {
|
||||
if (name === UPDATE_EVENT.CONNECTION_CHANGE && payload.state === "connected") {
|
||||
asyncStep(payload.state);
|
||||
}
|
||||
});
|
||||
|
||||
user2.p2p.connect(user2.id, channelId);
|
||||
user1.p2p.connect(user1.id, channelId);
|
||||
await user1.p2p.addPeer(user2.id);
|
||||
await waitForSteps(["connected"]);
|
||||
network.close();
|
||||
});
|
||||
|
||||
onlineTest("mesh peer to peer connections", async () => {
|
||||
await mountWebClient();
|
||||
const channelId = 2;
|
||||
const network = new Network();
|
||||
const userCount = 10;
|
||||
const users = Array.from({ length: userCount }, (_, i) => network.register(i));
|
||||
const promises = [];
|
||||
for (const user of users) {
|
||||
user.p2p.connect(user.id, channelId);
|
||||
for (let i = 0; i < user.id; i++) {
|
||||
promises.push(user.p2p.addPeer(i));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
let connectionsCount = 0;
|
||||
for (const user of users) {
|
||||
connectionsCount += user.p2p.peers.size;
|
||||
}
|
||||
expect(connectionsCount).toBe(userCount * (userCount - 1));
|
||||
connectionsCount = 0;
|
||||
network.close();
|
||||
for (const user of users) {
|
||||
connectionsCount += user.p2p.peers.size;
|
||||
}
|
||||
expect(connectionsCount).toBe(0);
|
||||
});
|
||||
|
||||
onlineTest("connection recovery", async () => {
|
||||
await mountWebClient();
|
||||
const channelId = 1;
|
||||
const network = new Network();
|
||||
const user1 = network.register(1);
|
||||
const user2 = network.register(2);
|
||||
user2.remoteStates = new Map();
|
||||
user2.p2p.addEventListener("update", ({ detail: { name, payload } }) => {
|
||||
if (name === UPDATE_EVENT.CONNECTION_CHANGE && payload.state === "connected") {
|
||||
asyncStep(payload.state);
|
||||
}
|
||||
});
|
||||
|
||||
user1.p2p.connect(user1.id, channelId);
|
||||
user1.p2p.addPeer(user2.id);
|
||||
// only connecting user2 after user1 has called addPeer so that user2 ignores notifications
|
||||
// from user1, which simulates a connection drop that should be recovered.
|
||||
user2.p2p.connect(user2.id, channelId);
|
||||
const openPromise = new Promise((resolve) => {
|
||||
user1.p2p.peers.get(2).dataChannel.onopen = resolve;
|
||||
});
|
||||
advanceTime(5_000); // recovery timeout
|
||||
await openPromise;
|
||||
await waitForSteps(["connected"]);
|
||||
network.close();
|
||||
});
|
||||
|
||||
onlineTest("can broadcast a stream and control download", async () => {
|
||||
mockGetMedia();
|
||||
await mountWebClient();
|
||||
const channelId = 3;
|
||||
const network = new Network();
|
||||
const user1 = network.register(1);
|
||||
const user2 = network.register(2);
|
||||
user2.remoteMedia = new Map();
|
||||
const trackPromise = new Promise((resolve) => {
|
||||
user2.p2p.addEventListener("update", ({ detail: { name, payload } }) => {
|
||||
if (name === UPDATE_EVENT.TRACK) {
|
||||
user2.remoteMedia.set(payload.sessionId, {
|
||||
[payload.type]: {
|
||||
track: payload.track,
|
||||
active: payload.active,
|
||||
},
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
user2.p2p.connect(user2.id, channelId);
|
||||
user1.p2p.connect(user1.id, channelId);
|
||||
await user1.p2p.addPeer(user2.id);
|
||||
const videoStream = await browser.navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
});
|
||||
const videoTrack = videoStream.getVideoTracks()[0];
|
||||
await user1.p2p.updateUpload(STREAM_TYPE.CAMERA, videoTrack);
|
||||
await trackPromise;
|
||||
const user2RemoteMedia = user2.remoteMedia.get(user1.id);
|
||||
const user2CameraTransceiver = user2.p2p.peers.get(user1.id).getTransceiver(STREAM_TYPE.CAMERA);
|
||||
expect(user2CameraTransceiver.direction).toBe("recvonly");
|
||||
expect(user2RemoteMedia[STREAM_TYPE.CAMERA].track.kind).toBe("video");
|
||||
expect(user2RemoteMedia[STREAM_TYPE.CAMERA].active).toBe(true);
|
||||
user2.p2p.updateDownload(user1.id, { camera: false });
|
||||
expect(user2CameraTransceiver.direction).toBe("inactive");
|
||||
network.close();
|
||||
});
|
||||
|
||||
onlineTest("can broadcast arbitrary messages (dataChannel)", async () => {
|
||||
await mountWebClient();
|
||||
const channelId = 4;
|
||||
const network = new Network();
|
||||
const user1 = network.register(1);
|
||||
const user2 = network.register(2);
|
||||
user2.p2p.connect(user2.id, channelId);
|
||||
user1.p2p.connect(user1.id, channelId);
|
||||
await user1.p2p.addPeer(user2.id);
|
||||
user1.inbox = [];
|
||||
const pongPromise = new Promise((resolve) => {
|
||||
user1.p2p.addEventListener("update", ({ detail: { name, payload } }) => {
|
||||
if (name === UPDATE_EVENT.BROADCAST) {
|
||||
user1.inbox.push(payload);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
user2.inbox = [];
|
||||
user2.p2p.addEventListener("update", ({ detail: { name, payload } }) => {
|
||||
if (name === UPDATE_EVENT.BROADCAST && payload.message === "ping") {
|
||||
user2.inbox.push(payload);
|
||||
user2.p2p.broadcast("pong");
|
||||
}
|
||||
});
|
||||
user1.p2p.broadcast("ping");
|
||||
await pongPromise;
|
||||
expect(user2.inbox[0].senderId).toBe(user1.id);
|
||||
expect(user2.inbox[0].message).toBe("ping");
|
||||
expect(user1.inbox[0].senderId).toBe(user2.id);
|
||||
expect(user1.inbox[0].message).toBe("pong");
|
||||
network.close();
|
||||
});
|
||||
|
||||
onlineTest("can reject arbitrary offers", async () => {
|
||||
await mountWebClient();
|
||||
const channelId = 1;
|
||||
const network = new Network();
|
||||
const user1 = network.register(1);
|
||||
const user2 = network.register(2);
|
||||
user2.p2p.connect(user2.id, channelId);
|
||||
user1.p2p.connect(user1.id, channelId);
|
||||
user2.p2p._emitLog = (id, message) => {
|
||||
if (message === "offer rejected") {
|
||||
asyncStep("offer rejected");
|
||||
}
|
||||
};
|
||||
user2.p2p.acceptOffer = (id, sequence) => id !== user1.id || sequence > 20;
|
||||
user1.p2p.addPeer(user2.id, { sequence: 19 });
|
||||
await waitForSteps(["offer rejected"]);
|
||||
network.close();
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
mockGetMedia,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
SIZES,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { pttExtensionServiceInternal } from "@mail/discuss/call/common/ptt_extension_service";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { patchWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("display banner when ptt extension is not enabled", async () => {
|
||||
mockGetMedia();
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
patchWithCleanup(pttExtensionServiceInternal, {
|
||||
onAnswerIsEnabled(pttService) {
|
||||
pttService.isEnabled = false;
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await click("button", { text: "Push to Talk" });
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click("button[title='New Meeting']");
|
||||
await click("button[title='Close panel']"); // invitation panel automatically open
|
||||
await contains(".o-discuss-PttAdBanner");
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await click("button", { text: "Voice Detection" });
|
||||
await click("[title*='Close Chat Window']");
|
||||
await contains(".o-discuss-PttAdBanner", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
mockGetMedia,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
SIZES,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { pttExtensionServiceInternal } from "@mail/discuss/call/common/ptt_extension_service";
|
||||
import { PTT_RELEASE_DURATION } from "@mail/discuss/call/common/rtc_service";
|
||||
import { advanceTime, freezeTime, keyDown, mockTouch, mockUserAgent, test } from "@odoo/hoot";
|
||||
import { patchWithCleanup, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
||||
defineMailModels();
|
||||
|
||||
test.tags("desktop");
|
||||
test("no auto-call on joining chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await contains(".o_command_name", { count: 5 });
|
||||
await insertText("input[placeholder='Search a conversation']", "mario");
|
||||
await contains(".o_command_name", { count: 3 });
|
||||
await click(".o_command_name", { text: "Mario" });
|
||||
await contains(".o-mail-DiscussSidebar-item", { text: "Mario" });
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
await contains(".o-discuss-Call", { count: 0 });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("no auto-call on joining group chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ name: "Mario" },
|
||||
{ name: "Luigi" },
|
||||
]);
|
||||
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await click("a", { text: "Create Chat" });
|
||||
await click("li", { text: "Mario" });
|
||||
await click("li", { text: "Luigi" });
|
||||
await click("button", { text: "Create Group Chat" });
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('Mario, and Luigi')");
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
await contains(".o-discuss-Call", { count: 0 });
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("show Push-to-Talk button on mobile", async () => {
|
||||
mockGetMedia();
|
||||
mockTouch(true);
|
||||
mockUserAgent("android");
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
patchWithCleanup(pttExtensionServiceInternal, {
|
||||
onAnswerIsEnabled(pttService) {
|
||||
pttService.isEnabled = false;
|
||||
},
|
||||
});
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-ChatWindow-moreActions", { text: "General" });
|
||||
await click(".o-dropdown-item:text('Start Call')");
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await click("button", { text: "Push to Talk" });
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Call Settings" });
|
||||
await contains("button", { text: "Push to talk" });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Can push-to-talk", async () => {
|
||||
mockGetMedia();
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["res.users.settings"].create({
|
||||
use_push_to_talk: true,
|
||||
user_id: serverState.userId,
|
||||
push_to_talk_key: "...f",
|
||||
});
|
||||
patchWithCleanup(pttExtensionServiceInternal, {
|
||||
onAnswerIsEnabled(pttService) {
|
||||
pttService.isEnabled = false;
|
||||
},
|
||||
});
|
||||
freezeTime();
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(1000);
|
||||
await click("[title='Start Call']");
|
||||
await advanceTime(1000);
|
||||
await contains(".o-discuss-Call");
|
||||
await click(".o-discuss-Call");
|
||||
await advanceTime(1000);
|
||||
await keyDown("f");
|
||||
await advanceTime(PTT_RELEASE_DURATION);
|
||||
await contains(".o-discuss-CallParticipantCard .o-isTalking");
|
||||
// switching tab while PTT key still pressed then released on other tab should eventually release PTT
|
||||
browser.dispatchEvent(new Event("blur"));
|
||||
await advanceTime(PTT_RELEASE_DURATION + 1000);
|
||||
await contains(".o-discuss-CallParticipantCard:not(:has(.o-isTalking))");
|
||||
await click(".o-discuss-Call");
|
||||
await advanceTime(1000);
|
||||
await keyDown("f");
|
||||
await advanceTime(PTT_RELEASE_DURATION);
|
||||
await contains(".o-discuss-CallParticipantCard .o-isTalking");
|
||||
});
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Empty attachment panel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = await pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Attachments']");
|
||||
await contains(".o-mail-ActionPanel", {
|
||||
text: "This channel doesn't have any attachments.",
|
||||
});
|
||||
});
|
||||
|
||||
test("Attachment panel sort by date", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
res_id: channelId,
|
||||
res_model: "discuss.channel",
|
||||
name: "file1.pdf",
|
||||
create_date: "2023-08-20 10:00:00",
|
||||
},
|
||||
{
|
||||
res_id: channelId,
|
||||
res_model: "discuss.channel",
|
||||
name: "file2.pdf",
|
||||
create_date: "2023-09-21 10:00:00",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Attachments']");
|
||||
await contains(".o-mail-AttachmentList", {
|
||||
text: "file2.pdf",
|
||||
after: [".o-mail-DateSection", { text: "September, 2023" }],
|
||||
before: [".o-mail-DateSection", { text: "August, 2023" }],
|
||||
});
|
||||
await contains(".o-mail-AttachmentList", {
|
||||
text: "file1.pdf",
|
||||
after: [".o-mail-DateSection", { text: "August, 2023" }],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import { waitForChannels } from "@bus/../tests/bus_test_helpers";
|
||||
import { onWebsocketEvent } from "@bus/../tests/mock_websocket";
|
||||
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { describe, edit, expect, mockDate, press, test } from "@odoo/hoot";
|
||||
|
||||
import { Command } from "@web/../tests/web_test_helpers";
|
||||
|
||||
defineMailModels();
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("bus subscription updated when joining/leaving thread as non member", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const johnUser = pyEnv["res.users"].create({ name: "John" });
|
||||
const johnPartner = pyEnv["res.partner"].create({ name: "John", user_ids: [johnUser] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [Command.create({ partner_id: johnPartner })],
|
||||
name: "General",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await waitForChannels([`discuss.channel_${channelId}`]);
|
||||
await click("[title='Channel Actions']");
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await click("button", { text: "Leave Conversation" });
|
||||
await waitForChannels([`discuss.channel_${channelId}`], { operation: "delete" });
|
||||
});
|
||||
|
||||
test("bus subscription updated when opening/closing chat window as a non member", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [],
|
||||
name: "Sales",
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow", { text: "Sales" });
|
||||
await waitForChannels([`discuss.channel_${channelId}`]);
|
||||
await click("[title*='Close Chat Window']", {
|
||||
parent: [".o-mail-ChatWindow", { text: "Sales" }],
|
||||
});
|
||||
await contains(".o-mail-ChatWindow", { count: 0, text: "Sales" });
|
||||
await waitForChannels([`discuss.channel_${channelId}`], { operation: "delete" });
|
||||
await press(["control", "k"]);
|
||||
await click(".o_command_palette_search input");
|
||||
await edit("@");
|
||||
await click(".o-mail-DiscussCommand", { text: "Sales" });
|
||||
await waitForChannels([`discuss.channel_${channelId}`]);
|
||||
});
|
||||
|
||||
test("bus subscription updated when joining locally pinned thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [],
|
||||
name: "General",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await waitForChannels([`discuss.channel_${channelId}`]);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", {
|
||||
text: "Mitchell Admin",
|
||||
});
|
||||
await click(".o-discuss-ChannelInvitation [title='Invite']:enabled");
|
||||
await waitForChannels([`discuss.channel_${channelId}`], { operation: "delete" });
|
||||
});
|
||||
|
||||
test("bus subscription is refreshed when channel is joined", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create([{ name: "General" }, { name: "Sales" }]);
|
||||
onWebsocketEvent("subscribe", () => expect.step("subscribe"));
|
||||
const later = luxon.DateTime.now().plus({ seconds: 2 });
|
||||
mockDate(
|
||||
`${later.year}-${later.month}-${later.day} ${later.hour}:${later.minute}:${later.second}`
|
||||
);
|
||||
await start();
|
||||
await expect.waitForSteps(["subscribe"]);
|
||||
await openDiscuss();
|
||||
await expect.waitForSteps([]);
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await insertText("input[placeholder='Search a conversation']", "new channel");
|
||||
await expect.waitForSteps(["subscribe"]);
|
||||
});
|
||||
|
||||
test("bus subscription is refreshed when channel is left", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({ name: "General" });
|
||||
onWebsocketEvent("subscribe", () => expect.step("subscribe"));
|
||||
const later = luxon.DateTime.now().plus({ seconds: 2 });
|
||||
mockDate(
|
||||
`${later.year}-${later.month}-${later.day} ${later.hour}:${later.minute}:${later.second}`
|
||||
);
|
||||
await start();
|
||||
await expect.waitForSteps(["subscribe"]);
|
||||
await openDiscuss();
|
||||
await expect.waitForSteps([]);
|
||||
await click("[title='Channel Actions']");
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await expect.waitForSteps(["subscribe"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
mockPermissionsPrompt,
|
||||
openDiscuss,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { animationFrame } from "@odoo/hoot-dom";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { Command, getService, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("should display the channel invitation form after clicking on the invite button of a chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
email: "testpartner@odoo.com",
|
||||
name: "TestPartner",
|
||||
});
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Invite People']");
|
||||
await contains(".o-discuss-ChannelInvitation");
|
||||
});
|
||||
|
||||
test("can invite users in channel from chat window", async () => {
|
||||
mockDate("2025-01-01 12:00:00", +1);
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
email: "testpartner@odoo.com",
|
||||
name: "TestPartner",
|
||||
});
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_type: "channel",
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start();
|
||||
// dropdown requires an extra delay before click (because handler is registered in useEffect)
|
||||
await contains("[title='Open Actions Menu']");
|
||||
await click("[title='Open Actions Menu']");
|
||||
await click(".o-dropdown-item", { text: "Invite People" });
|
||||
await contains(".o-discuss-ChannelInvitation");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "TestPartner" });
|
||||
await click(".o-discuss-ChannelInvitation [title='Invite']:enabled");
|
||||
await contains(".o-discuss-ChannelInvitation", { count: 0 });
|
||||
await contains(".o-mail-Thread .o-mail-NotificationMessage", {
|
||||
text: "Mitchell Admin invited TestPartner to the channel1:00 PM",
|
||||
});
|
||||
});
|
||||
|
||||
test("should be able to search for a new user to invite from an existing chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({
|
||||
email: "testpartner@odoo.com",
|
||||
name: "TestPartner",
|
||||
});
|
||||
const partnerId_2 = pyEnv["res.partner"].create({
|
||||
email: "testpartner2@odoo.com",
|
||||
name: "TestPartner2",
|
||||
});
|
||||
pyEnv["res.users"].create({ partner_id: partnerId_1 });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId_2 });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Invite People']");
|
||||
await insertText(".o-discuss-ChannelInvitation-search", "TestPartner2");
|
||||
await contains(".o-discuss-ChannelInvitation-selectable", { text: "TestPartner2" });
|
||||
});
|
||||
|
||||
test("Invitation form should display channel group restriction", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
email: "testpartner@odoo.com",
|
||||
name: "TestPartner",
|
||||
});
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const groupId = pyEnv["res.groups"].create({
|
||||
name: "testGroup",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_type: "channel",
|
||||
group_public_id: groupId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Invite People']");
|
||||
await contains(".o-discuss-ChannelInvitation div", {
|
||||
text: 'Access restricted to group "testGroup"',
|
||||
after: ["button .fa.fa-copy"],
|
||||
});
|
||||
});
|
||||
|
||||
test("should be able to create a new group chat from an existing chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({
|
||||
email: "testpartner@odoo.com",
|
||||
name: "TestPartner",
|
||||
});
|
||||
const partnerId_2 = pyEnv["res.partner"].create({
|
||||
email: "testpartner2@odoo.com",
|
||||
name: "TestPartner2",
|
||||
});
|
||||
pyEnv["res.users"].create({ partner_id: partnerId_1 });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId_2 });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-DiscussContent-header button[title='Invite People']");
|
||||
await contains(".o-discuss-ChannelInvitation");
|
||||
await insertText(".o-discuss-ChannelInvitation-search", "TestPartner2");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "TestPartner2" });
|
||||
await click("button[title='Create Group Chat']:enabled");
|
||||
await contains(".o-discuss-ChannelInvitation", { count: 0 });
|
||||
await contains(".o-mail-DiscussSidebarChannel", {
|
||||
text: "Mitchell Admin, TestPartner, and TestPartner2",
|
||||
});
|
||||
});
|
||||
|
||||
test("unnamed group chat should display correct name just after being invited", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
email: "jane@example.com",
|
||||
name: "Jane",
|
||||
});
|
||||
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const [, channelId] = pyEnv["discuss.channel"].create([
|
||||
{ name: "General" },
|
||||
{
|
||||
channel_member_ids: [Command.create({ partner_id: partnerId })],
|
||||
channel_type: "group",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "General" });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0, text: "Jane and Mitchell Admin" });
|
||||
const currentPartnerId = serverState.partnerId;
|
||||
await withUser(userId, async () => {
|
||||
await getService("orm").call("discuss.channel", "add_members", [[channelId]], {
|
||||
partner_ids: [currentPartnerId],
|
||||
});
|
||||
});
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "Jane and Mitchell Admin" });
|
||||
await contains(".o_notification", {
|
||||
text: "You have been invited to #Jane and Mitchell Admin",
|
||||
});
|
||||
});
|
||||
|
||||
test("invite user to self chat opens DM chat with user", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "TestGuest" });
|
||||
const partnerId_1 = pyEnv["res.partner"].create({
|
||||
email: "testpartner@odoo.com",
|
||||
name: "TestPartner",
|
||||
});
|
||||
pyEnv["res.users"].create({ partner_id: partnerId_1 });
|
||||
const [selfChatId] = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [Command.create({ partner_id: serverState.partnerId })],
|
||||
channel_type: "chat",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
],
|
||||
channel_type: "group",
|
||||
},
|
||||
{
|
||||
// group chat with guest as correspondent for coverage of no crash
|
||||
channel_member_ids: [
|
||||
Command.create({ guest_id: guestId }),
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
],
|
||||
channel_type: "group",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(selfChatId);
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "Mitchell Admin" }); // self-chat
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "TestPartner and Mitchell Admin" });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "TestGuest and Mitchell Admin" });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "TestPartner" });
|
||||
await click(".o-mail-DiscussContent-header button[title='Invite People']");
|
||||
await insertText(".o-discuss-ChannelInvitation-search", "TestPartner");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "TestPartner" });
|
||||
await click("button:contains('Go to Conversation'):enabled");
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "TestPartner" });
|
||||
});
|
||||
|
||||
test("Invite sidebar action has the correct title for group chats", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click("button[title='Chat Actions']");
|
||||
await click(".o-dropdown-item", { text: "Invite People" });
|
||||
await contains(".modal-title", { text: "Mitchell Admin and Demo" });
|
||||
});
|
||||
|
||||
test("Active dialog retains focus over invite input", async () => {
|
||||
await startServer();
|
||||
mockPermissionsPrompt();
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("button[title='New Meeting']");
|
||||
await animationFrame();
|
||||
await contains(".o-discuss-ChannelInvitation");
|
||||
await contains("button:focus", { text: "Use Camera" });
|
||||
});
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, getService, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("there should be a button to show member list in the thread view topbar initially", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains("[title='Members']");
|
||||
});
|
||||
|
||||
test("should show member list when clicking on member list button in thread view topbar", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // open by default
|
||||
await click("[title='Members']");
|
||||
await contains(".o-discuss-ChannelMemberList", { count: 0 });
|
||||
await click("[title='Members']");
|
||||
await contains(".o-discuss-ChannelMemberList");
|
||||
});
|
||||
|
||||
test("should have correct members in member list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMember", { count: 2 });
|
||||
await contains(".o-discuss-ChannelMember", { text: serverState.partnerName });
|
||||
await contains(".o-discuss-ChannelMember", { text: "Demo" });
|
||||
});
|
||||
|
||||
test("members should be correctly categorised into online/offline", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [onlinePartnerId, idlePartnerId] = pyEnv["res.partner"].create([
|
||||
{ name: "Online Partner", im_status: "online" },
|
||||
{ name: "Idle Partner", im_status: "away" },
|
||||
]);
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { im_status: "im_partner" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChanel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: onlinePartnerId }),
|
||||
Command.create({ partner_id: idlePartnerId }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList h6", { text: "Online - 2" });
|
||||
await contains(".o-discuss-ChannelMemberList h6", { text: "Offline - 1" });
|
||||
});
|
||||
|
||||
test("chat with member should be opened after clicking on channel member", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-discuss-ChannelMember.cursor-pointer", { text: "Demo" });
|
||||
await contains(".o_avatar_card .o_card_user_infos", { text: "Demo" });
|
||||
await click(".o_avatar_card button", { text: "Send message" });
|
||||
await contains(".o-mail-AutoresizeInput[title='Demo']");
|
||||
});
|
||||
|
||||
test("should show a button to load more members if they are not all loaded", async () => {
|
||||
// Test assumes at most 100 members are loaded at once.
|
||||
const pyEnv = await startServer();
|
||||
const channel_member_ids = [];
|
||||
for (let i = 0; i < 101; i++) {
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "name" + i });
|
||||
channel_member_ids.push(Command.create({ partner_id: partnerId }));
|
||||
}
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
pyEnv["discuss.channel"].write([channelId], { channel_member_ids });
|
||||
await contains(
|
||||
".o-mail-ActionPanel:has(.o-mail-ActionPanel-header:contains('Members')) button",
|
||||
{ text: "Load more" }
|
||||
);
|
||||
});
|
||||
|
||||
test("Load more button should load more members", async () => {
|
||||
// Test assumes at most 100 members are loaded at once.
|
||||
const pyEnv = await startServer();
|
||||
const channel_member_ids = [];
|
||||
for (let i = 0; i < 101; i++) {
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "name" + i });
|
||||
channel_member_ids.push(Command.create({ partner_id: partnerId }));
|
||||
}
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "TestChannel",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
pyEnv["discuss.channel"].write([channelId], { channel_member_ids });
|
||||
await click(
|
||||
".o-mail-ActionPanel:has(.o-mail-ActionPanel-header:contains('Members')) [title='Load more']"
|
||||
);
|
||||
await contains(".o-discuss-ChannelMember", { count: 102 });
|
||||
});
|
||||
|
||||
test("Channel member count update after user joined", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const userId = pyEnv["res.users"].create({ name: "Harry" });
|
||||
pyEnv["res.partner"].create({ name: "Harry", user_ids: [userId] });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await contains(".o-discuss-ChannelMemberList h6", { text: "Offline - 1" });
|
||||
await click("[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Harry" });
|
||||
await click(".o-discuss-ChannelInvitation [title='Invite']:enabled");
|
||||
await contains(".o-discuss-ChannelInvitation", { count: 0 });
|
||||
await click("[title='Members']");
|
||||
await contains(".o-discuss-ChannelMemberList h6", { text: "Offline - 2" });
|
||||
});
|
||||
|
||||
test("Channel member count update after user left", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Dobby" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Dobby", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMember", { count: 2 });
|
||||
await withUser(userId, () =>
|
||||
getService("orm").call("discuss.channel", "action_unfollow", [channelId])
|
||||
);
|
||||
await contains(".o-discuss-ChannelMember", { count: 1 });
|
||||
});
|
||||
|
||||
test("Members are partitioned by online/offline", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [userId_1, userId_2] = pyEnv["res.users"].create([{ name: "Dobby" }, { name: "John" }]);
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{
|
||||
name: "Dobby",
|
||||
user_ids: [userId_1],
|
||||
im_status: "offline",
|
||||
},
|
||||
{
|
||||
name: "John",
|
||||
user_ids: [userId_2],
|
||||
im_status: "online",
|
||||
},
|
||||
]);
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
],
|
||||
});
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { im_status: "online" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMember", { count: 3 });
|
||||
await contains("h6", { text: "Online - 2" });
|
||||
await contains("h6", { text: "Offline - 1" });
|
||||
await contains(".o-discuss-ChannelMember", {
|
||||
text: "John",
|
||||
after: ["h6", { text: "Online - 2" }],
|
||||
before: ["h6", { text: "Offline - 1" }],
|
||||
});
|
||||
await contains(".o-discuss-ChannelMember", {
|
||||
text: "Mitchell Admin",
|
||||
after: ["h6", { text: "Online - 2" }],
|
||||
before: ["h6", { text: "Offline - 1" }],
|
||||
});
|
||||
await contains(".o-discuss-ChannelMember", {
|
||||
text: "Dobby",
|
||||
after: ["h6", { text: "Offline - 1" }],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
|
||||
defineMailModels();
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("Group name is based on channel members when name is not set", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].create(
|
||||
["Alice", "Bob", "Eve", "John", "Sam"].map((name) => ({
|
||||
name,
|
||||
partner_id: pyEnv["res.partner"].create({ name }),
|
||||
}))
|
||||
);
|
||||
const channelId = pyEnv["discuss.channel"].create({ channel_type: "group" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Mitchell Admin']");
|
||||
await click("button[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Alice" });
|
||||
await click("button", { text: "Invite to Group Chat" });
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Mitchell Admin and Alice']");
|
||||
await click("button[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Bob" });
|
||||
await click("button", { text: "Invite to Group Chat" });
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Mitchell Admin, Alice, and Bob']");
|
||||
await click("button[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Eve" });
|
||||
await click("button", { text: "Invite to Group Chat" });
|
||||
await contains(
|
||||
".o-mail-DiscussContent-threadName[title='Mitchell Admin, Alice, Bob, and 1 other']"
|
||||
);
|
||||
await click("button[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "John" });
|
||||
await click("button", { text: "Invite to Group Chat" });
|
||||
await contains(
|
||||
".o-mail-DiscussContent-threadName[title='Mitchell Admin, Alice, Bob, and 2 others']"
|
||||
);
|
||||
await click(".o-mail-DiscussContent-threadName");
|
||||
await insertText(".o-mail-DiscussContent-threadName.o-focused", "Custom name", {
|
||||
replace: true,
|
||||
});
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Custom name']");
|
||||
await press("Enter");
|
||||
// Ensure that after setting the name, members are not taken into account for the group name.
|
||||
await click("button[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Sam" });
|
||||
await click("button", { text: "Invite to Group Chat" });
|
||||
await contains(".o_mail_notification", { text: "invited Sam to the channel" });
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Custom name']");
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { getOrigin } from "@web/core/utils/urls";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("clicking message link does not swap open chat window", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [rdId, supportId] = pyEnv["discuss.channel"].create([
|
||||
{ name: "R&D" },
|
||||
{ name: "Support" },
|
||||
]);
|
||||
const messageRdId = pyEnv["mail.message"].create({
|
||||
body: "Hello R&D",
|
||||
model: "discuss.channel",
|
||||
res_id: rdId,
|
||||
});
|
||||
const urlRd = `${getOrigin()}/mail/message/${messageRdId}`;
|
||||
const messageSupportId = pyEnv["mail.message"].create({
|
||||
body: `Hello from there <a class="o_message_redirect" href="${urlRd}" data-oe-model="mail.message" data-oe-id="${messageRdId}">${urlRd}</a>`,
|
||||
model: "discuss.channel",
|
||||
res_id: supportId,
|
||||
});
|
||||
const urlSupport = `${getOrigin()}/mail/message/${messageSupportId}`;
|
||||
pyEnv["mail.message"].create({
|
||||
body: `Hello back <a class="o_message_redirect" href="${urlSupport}" data-oe-model="mail.message" data-oe-id="${messageSupportId}">${urlSupport}</a>`,
|
||||
model: "discuss.channel",
|
||||
res_id: rdId,
|
||||
});
|
||||
setupChatHub({ opened: [rdId, supportId] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow:eq(0) .o-mail-ChatWindow-header:contains(R&D)");
|
||||
await contains(".o-mail-ChatWindow:eq(1) .o-mail-ChatWindow-header:contains(Support)");
|
||||
await click("a.o_message_redirect:contains(R&D)");
|
||||
await contains(".o-mail-Message.o-highlighted:contains(Hello R&D)");
|
||||
await contains(".o-mail-ChatWindow:eq(0) .o-mail-ChatWindow-header:contains(R&D)");
|
||||
await contains(".o-mail-ChatWindow:eq(1) .o-mail-ChatWindow-header:contains(Support)");
|
||||
await click("a.o_message_redirect:contains(Support)");
|
||||
await contains(".o-mail-Message.o-highlighted:contains(Hello from there)");
|
||||
await contains(".o-mail-ChatWindow:eq(0) .o-mail-ChatWindow-header:contains(R&D)");
|
||||
await contains(".o-mail-ChatWindow:eq(1) .o-mail-ChatWindow-header:contains(Support)");
|
||||
});
|
||||
|
|
@ -0,0 +1,661 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("rendering when just one has received the message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Other User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const [memberId_1] = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId_1],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write([memberId_1], {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: false,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageSeenIndicator");
|
||||
await contains(".o-mail-MessageSeenIndicator[title='Sent']");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 1 });
|
||||
await contains(".o-mail-MessageSeenIndicator.o-hasEveryoneSeen", { count: 0 });
|
||||
});
|
||||
|
||||
test("rendering when everyone have received the message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Other User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: false,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageSeenIndicator");
|
||||
await contains(".o-mail-MessageSeenIndicator[title='Sent']");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 1 });
|
||||
await contains(".o-mail-MessageSeenIndicator.o-hasEveryoneSeen", { count: 0 });
|
||||
});
|
||||
|
||||
test("rendering when just one has seen the message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Other User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: false,
|
||||
});
|
||||
const [memberId_1] = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId_1],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write([memberId_1], {
|
||||
seen_message_id: messageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageSeenIndicator");
|
||||
await contains(".o-mail-MessageSeenIndicator[title='Seen by Demo User']");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 });
|
||||
await contains(".o-mail-MessageSeenIndicator.o-hasEveryoneSeen", { count: 0 });
|
||||
});
|
||||
|
||||
test("rendering when just one has seen & received the message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Other User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const mesageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const [memberId_1] = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId_1],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write([memberId_1], {
|
||||
seen_message_id: mesageId,
|
||||
fetched_message_id: mesageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageSeenIndicator");
|
||||
await contains(".o-mail-MessageSeenIndicator[title='Seen by Demo User']");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 });
|
||||
await contains(".o-mail-MessageSeenIndicator.o-hasEveryoneSeen", { count: 0 });
|
||||
});
|
||||
|
||||
test("rendering when just everyone has seen the message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Other User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: messageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageSeenIndicator");
|
||||
await contains(".o-mail-MessageSeenIndicator[title='Seen by everyone']");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 });
|
||||
await contains(".o-mail-MessageSeenIndicator.o-hasEveryoneSeen", { count: 1 });
|
||||
});
|
||||
|
||||
test("'channel_fetch' notification received is correctly handled", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "test" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 0 });
|
||||
|
||||
const channel = pyEnv["discuss.channel"].search_read([["id", "=", channelId]])[0];
|
||||
// Simulate received channel fetched notification
|
||||
pyEnv["bus.bus"]._sendone(channel, "discuss.channel.member/fetched", {
|
||||
id: pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId],
|
||||
])[0],
|
||||
channel_id: channelId,
|
||||
last_message_id: 100,
|
||||
partner_id: partnerId,
|
||||
});
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 1 });
|
||||
});
|
||||
|
||||
test("mark channel as seen from the bus", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "test" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 0 });
|
||||
const channel = pyEnv["discuss.channel"].search_read([["id", "=", channelId]])[0];
|
||||
// Simulate received channel seen notification
|
||||
const DiscussChannelMember = pyEnv["discuss.channel.member"];
|
||||
pyEnv["bus.bus"]._sendone(
|
||||
channel,
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store(
|
||||
DiscussChannelMember.browse(
|
||||
DiscussChannelMember.search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId],
|
||||
])
|
||||
),
|
||||
{ seen_message_id: messageId }
|
||||
).get_result()
|
||||
);
|
||||
await contains(".o-mail-MessageSeenIndicator[title='Seen by test']");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 });
|
||||
});
|
||||
|
||||
test("should display message indicator when message is fetched/seen", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Recipient" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 0 });
|
||||
const channel = pyEnv["discuss.channel"].search_read([["id", "=", channelId]])[0];
|
||||
// Simulate received channel fetched notification
|
||||
pyEnv["bus.bus"]._sendone(channel, "discuss.channel.member/fetched", {
|
||||
id: pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId],
|
||||
])[0],
|
||||
channel_id: channelId,
|
||||
last_message_id: messageId,
|
||||
partner_id: partnerId,
|
||||
});
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 1 });
|
||||
// Simulate received channel seen notification
|
||||
const DiscussChannelMember = pyEnv["discuss.channel.member"];
|
||||
pyEnv["bus.bus"]._sendone(
|
||||
channel,
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store(
|
||||
DiscussChannelMember.browse(
|
||||
DiscussChannelMember.search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId],
|
||||
])
|
||||
),
|
||||
{ seen_message_id: messageId }
|
||||
).get_result()
|
||||
);
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 });
|
||||
});
|
||||
|
||||
test("do not show message seen indicator on the last message seen by everyone when the current user is not author of the message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, { seen_message_id: messageId });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-MessageSeenIndicator", { count: 0 });
|
||||
});
|
||||
|
||||
test("do not show message seen indicator on all the messages of the current user that are older than the last message seen by everyone", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
const [, messageId_2] = pyEnv["mail.message"].create([
|
||||
{
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Message before last seen</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
},
|
||||
{
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Last seen by everyone</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
},
|
||||
]);
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, { seen_message_id: messageId_2 });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", {
|
||||
text: "Message before last seen",
|
||||
contains: [".o-mail-MessageSeenIndicator", { contains: [".fa-check", { count: 0 }] }],
|
||||
});
|
||||
});
|
||||
|
||||
test("only show messaging seen indicator if authored by me, after last seen by all message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
res_id: channelId,
|
||||
model: "discuss.channel",
|
||||
});
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: messageId - 1,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 1 });
|
||||
});
|
||||
|
||||
test("all seen indicator in chat displayed only once (chat created by correspondent)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const demoPid = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const demoUid = pyEnv["res.users"].create({ partner_id: demoPid });
|
||||
const selfPid = serverState.partnerId;
|
||||
const channelId = await withUser(demoUid, () =>
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: demoPid }),
|
||||
Command.create({ partner_id: selfPid }),
|
||||
],
|
||||
})
|
||||
);
|
||||
const [, messageId] = pyEnv["mail.message"].create([
|
||||
{
|
||||
author_id: selfPid,
|
||||
body: "<p>Test1</p>",
|
||||
res_id: channelId,
|
||||
model: "discuss.channel",
|
||||
},
|
||||
{
|
||||
author_id: selfPid,
|
||||
body: "<p>Test2</p>",
|
||||
res_id: channelId,
|
||||
model: "discuss.channel",
|
||||
},
|
||||
]);
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: messageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 2 });
|
||||
await contains(".o-mail-MessageSeenIndicator.o-hasEveryoneSeen .fa-check", { count: 2 });
|
||||
});
|
||||
|
||||
test("no seen indicator in 'channel' channels (with is_typing)", async () => {
|
||||
// is_typing info contains fetched / seen message so this could mistakenly show seen indicators
|
||||
const pyEnv = await startServer();
|
||||
const demoId = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const demoUserId = pyEnv["res.users"].create({ partner_id: demoId });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test-channel",
|
||||
channel_type: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: demoId }),
|
||||
],
|
||||
});
|
||||
const chatId = pyEnv["discuss.channel"].create({
|
||||
name: "test-chat",
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: demoId }),
|
||||
],
|
||||
});
|
||||
const [channelMsgId, chatMsgId] = pyEnv["mail.message"].create([
|
||||
{
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>channel-msg</p>",
|
||||
res_id: channelId,
|
||||
model: "discuss.channel",
|
||||
},
|
||||
{
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>chat-msg</p>",
|
||||
res_id: chatId,
|
||||
model: "discuss.channel",
|
||||
},
|
||||
]);
|
||||
const channelMemberIds = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
]);
|
||||
const chatMemberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", chatId]]);
|
||||
pyEnv["discuss.channel.member"].write(channelMemberIds, {
|
||||
fetched_message_id: channelMsgId,
|
||||
seen_message_id: 0,
|
||||
});
|
||||
pyEnv["discuss.channel.member"].write(chatMemberIds, {
|
||||
fetched_message_id: chatMsgId,
|
||||
seen_message_id: 0,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { text: "channel-msg" });
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 0 }); // none in channel
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "Demo User" });
|
||||
await contains(".o-mail-Message", { text: "chat-msg" });
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 1 }); // received in chat
|
||||
// simulate channel read by Demo User in both threads
|
||||
await withUser(demoUserId, () =>
|
||||
rpc("/discuss/channel/mark_as_read", {
|
||||
channel_id: channelId,
|
||||
last_message_id: channelMsgId,
|
||||
})
|
||||
);
|
||||
await withUser(demoUserId, () =>
|
||||
rpc("/discuss/channel/mark_as_read", {
|
||||
channel_id: chatId,
|
||||
last_message_id: chatMsgId,
|
||||
})
|
||||
);
|
||||
// simulate typing by Demo User in both threads
|
||||
await withUser(demoUserId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await withUser(demoUserId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: chatId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 }); // seen in chat
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "test-channel" });
|
||||
await contains(".o-mail-Message", { text: "channel-msg" });
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 0 }); // none in channel
|
||||
});
|
||||
|
||||
test("Show everyone seen title on message seen indicator", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Other User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, last_seen_dt: "2024-06-01 12:00" }),
|
||||
Command.create({ partner_id: partnerId_1, last_seen_dt: "2024-06-01 12:00" }),
|
||||
Command.create({ partner_id: partnerId_2, last_seen_dt: "2024-06-01 13:00" }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const mesageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const [memberId_1, memberId_2] = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "in", [partnerId_1, partnerId_2]],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write([memberId_1], {
|
||||
seen_message_id: mesageId,
|
||||
fetched_message_id: mesageId,
|
||||
});
|
||||
pyEnv["discuss.channel.member"].write([memberId_2], {
|
||||
seen_message_id: mesageId,
|
||||
fetched_message_id: mesageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains("[title='Seen by everyone']");
|
||||
});
|
||||
|
||||
test("Title show some member seen info (partial seen), click show dialog with full info", async () => {
|
||||
// last member flagged as not seen so that it doesn't show "Seen by everyone" but list names instead
|
||||
const pyEnv = await startServer();
|
||||
const partners = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
partners.push({ name: `User ${i}` });
|
||||
}
|
||||
const partnerIds = pyEnv["res.partner"].create(partners);
|
||||
const channelMemberIds = [];
|
||||
for (const partner_id of partnerIds) {
|
||||
channelMemberIds.push(
|
||||
Command.create({
|
||||
partner_id,
|
||||
last_seen_dt: partner_id === partnerIds.at(-1) ? false : "2024-06-01 12:00",
|
||||
})
|
||||
);
|
||||
}
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_seen_dt: "2024-06-01 12:00",
|
||||
}),
|
||||
...channelMemberIds,
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const mesageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
const members = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "in", partnerIds.filter((p) => p !== partnerIds.at(-1))],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write(members, {
|
||||
seen_message_id: mesageId,
|
||||
fetched_message_id: mesageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains("[title='Seen by User 0, User 1, User 2 and 8 others']");
|
||||
await click(".o-mail-MessageSeenIndicator");
|
||||
await contains("li", { count: 11 });
|
||||
for (let i = 0; i < 11; i++) {
|
||||
await contains("li", { text: `User ${i}` }); // Not checking datetime because HOOT mocking of tz do not work
|
||||
}
|
||||
});
|
||||
|
||||
test("Show seen indicator on message with only attachment", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Demo User" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "test",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
|
||||
const attachmentId = pyEnv["ir.attachment"].create({
|
||||
name: "test.txt",
|
||||
mimetype: "text/plain",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
attachment_ids: [attachmentId],
|
||||
});
|
||||
const memberIds = pyEnv["discuss.channel.member"].search([["channel_id", "=", channelId]]);
|
||||
pyEnv["discuss.channel.member"].write(memberIds, {
|
||||
fetched_message_id: messageId,
|
||||
seen_message_id: false,
|
||||
});
|
||||
const [memberId_1] = pyEnv["discuss.channel.member"].search([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", partnerId_1],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write([memberId_1], {
|
||||
seen_message_id: messageId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageSeenIndicator");
|
||||
await contains(".o-mail-MessageSeenIndicator .fa-check", { count: 2 });
|
||||
});
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import { insertText as htmlInsertText } from "@html_editor/../tests/_helpers/user_actions";
|
||||
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
focus,
|
||||
insertText,
|
||||
onRpcBefore,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { Composer } from "@mail/core/common/composer";
|
||||
import { beforeEach, describe, test } from "@odoo/hoot";
|
||||
import {
|
||||
asyncStep,
|
||||
getService,
|
||||
patchWithCleanup,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
beforeEach(() => {
|
||||
// Simulate real user interactions
|
||||
patchWithCleanup(Composer.prototype, {
|
||||
isEventTrusted() {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('do not send typing notification on typing "/" command', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "channel" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", () => {
|
||||
if (!testEnded) {
|
||||
asyncStep("notify_typing");
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "/");
|
||||
await contains(".o-mail-Composer button[title='Send']:enabled");
|
||||
await waitForSteps([]); // No rpc done
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test('do not send typing notification on typing after selecting suggestion from "/" command', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "channel" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", () => {
|
||||
if (!testEnded) {
|
||||
asyncStep("notify_typing");
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "/");
|
||||
await click(":nth-child(1 of .o-mail-Composer-suggestion)");
|
||||
await contains(".o-mail-Composer-suggestion strong", { count: 0 });
|
||||
await insertText(".o-mail-Composer-input", " is user?");
|
||||
await waitForSteps([]); // No rpc done"
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test("send is_typing on adding emoji", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "channel" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", () => {
|
||||
if (!testEnded) {
|
||||
asyncStep("notify_typing");
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click("button[title='Add Emojis']");
|
||||
await insertText("input[placeholder='Search emoji']", "Santa Claus");
|
||||
await click(".o-Emoji", { text: "🎅" });
|
||||
await waitForSteps(["notify_typing"]);
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test("add an emoji after a command", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-input", { value: "" });
|
||||
await insertText(".o-mail-Composer-input", "/");
|
||||
await click(":nth-child(1 of .o-mail-Composer-suggestion)");
|
||||
await contains(".o-mail-Composer-input", { value: "/who " });
|
||||
await click("button[title='Add Emojis']");
|
||||
await click(".o-Emoji", { text: "😊" });
|
||||
await contains(".o-mail-Composer-input", { value: "/who 😊" });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("html composer: send a message in a channel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-input", { value: "" });
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await focus(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "Hello");
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable", { text: "Hello" });
|
||||
await click(".o-mail-Composer button[title='Send']:enabled");
|
||||
await click(".o-mail-Message[data-persistent]:contains(Hello)");
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable", { text: "" });
|
||||
});
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, getService, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Add member to channel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const userId = pyEnv["res.users"].create({ name: "Harry" });
|
||||
pyEnv["res.partner"].create({ name: "Harry", user_ids: [userId] });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await contains(".o-discuss-ChannelMember", { text: "Mitchell Admin" });
|
||||
await click("[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Harry" });
|
||||
await click(".o-discuss-ChannelInvitation [title='Invite']:enabled");
|
||||
await contains(".o-discuss-ChannelInvitation", { count: 0 });
|
||||
await click("[title='Members']");
|
||||
await contains(".o-discuss-ChannelMember", { text: "Harry" });
|
||||
});
|
||||
|
||||
test("Remove member from channel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Harry" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
name: "Harry",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMember", { text: "Harry" });
|
||||
withUser(userId, () =>
|
||||
getService("orm").call("discuss.channel", "action_unfollow", [channelId])
|
||||
);
|
||||
await contains(".o-discuss-ChannelMember", { count: 0, text: "Harry" });
|
||||
});
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { onWebsocketEvent } from "@bus/../tests/mock_websocket";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { tick } from "@odoo/hoot-dom";
|
||||
import { makeMockEnv } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Member list and Pinned Messages Panel menu are exclusive", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // member list open by default
|
||||
await click("[title='Pinned Messages']");
|
||||
await contains(".o-discuss-PinnedMessagesPanel");
|
||||
await contains(".o-discuss-ChannelMemberList", { count: 0 });
|
||||
});
|
||||
|
||||
test("subscribe to presence channels according to store data", async () => {
|
||||
const env = await makeMockEnv();
|
||||
const store = env.services["mail.store"];
|
||||
onWebsocketEvent("subscribe", (data) => expect.step(`subscribe - [${data.channels}]`));
|
||||
expect(env.services.bus_service.isActive).toBe(false);
|
||||
// Should not subscribe to presences as bus service is not started.
|
||||
store["res.partner"].insert({ id: 1, name: "Partner 1" });
|
||||
store["res.partner"].insert({ id: 2, name: "Partner 2" });
|
||||
await tick();
|
||||
expect.waitForSteps([]);
|
||||
// Starting the bus should subscribe to known presence channels.
|
||||
env.services.bus_service.start();
|
||||
await expect.waitForSteps([
|
||||
"subscribe - [odoo-presence-res.partner_1,odoo-presence-res.partner_2]",
|
||||
]);
|
||||
// Discovering new presence channels should refresh the subscription.
|
||||
store["mail.guest"].insert({ id: 1 });
|
||||
await expect.waitForSteps([
|
||||
"subscribe - [odoo-presence-mail.guest_1,odoo-presence-res.partner_1,odoo-presence-res.partner_2]",
|
||||
]);
|
||||
// Updating "im_status_access_token" should refresh the subscription.
|
||||
store["mail.guest"].insert({ id: 1, im_status_access_token: "token" });
|
||||
await expect.waitForSteps([
|
||||
"subscribe - [odoo-presence-mail.guest_1-token,odoo-presence-res.partner_1,odoo-presence-res.partner_2]",
|
||||
]);
|
||||
});
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
observeRenders,
|
||||
openDiscuss,
|
||||
prepareObserveRenders,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { Composer } from "@mail/core/common/composer";
|
||||
import { Message } from "@mail/core/common/message";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { onMounted, onPatched } from "@odoo/owl";
|
||||
import { patchWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { range } from "@web/core/utils/numbers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("posting new message should only render relevant part", async () => {
|
||||
// For example, it should not render old messages again
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
const messageIds = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
messageIds.push(
|
||||
pyEnv["mail.message"].create({
|
||||
body: `not_empty_${i}`,
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
})
|
||||
);
|
||||
}
|
||||
messageIds.pop(); // remove last as it might need re-render (it was the newest message before)
|
||||
let posting = false;
|
||||
prepareObserveRenders();
|
||||
patchWithCleanup(Message.prototype, {
|
||||
setup() {
|
||||
const cb = () => {
|
||||
if (posting) {
|
||||
if (messageIds.includes(this.message.id)) {
|
||||
throw new Error(
|
||||
"Should not re-render old messages again on posting a new message"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
onMounted(cb);
|
||||
onPatched(cb);
|
||||
return super.setup();
|
||||
},
|
||||
});
|
||||
await start();
|
||||
const stopObserve1 = observeRenders();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 10 });
|
||||
await insertText(".o-mail-Composer-input", "Test");
|
||||
const result1 = stopObserve1();
|
||||
// LessThan because renders could be batched
|
||||
expect(result1.get(Message)).toBeLessThan(11); // 10: all messages initially
|
||||
const stopObserve2 = observeRenders();
|
||||
posting = true;
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message", { count: 11 });
|
||||
posting = false;
|
||||
const result2 = stopObserve2();
|
||||
expect(result2.get(Composer)).toBeLessThan(3); // 2: temp disabling + clear content
|
||||
expect(result2.get(Message)).toBeLessThan(4); // 3: new temp msg + new genuine msg + prev msg
|
||||
});
|
||||
|
||||
test("replying to message should only render relevant part", async () => {
|
||||
// For example, it should not render all messages when selecting message to reply
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
const messageIds = range(0, 10).map((i) =>
|
||||
pyEnv["mail.message"].create({ body: `${i}`, model: "discuss.channel", res_id: channelId })
|
||||
);
|
||||
messageIds.pop(); // remove last as this is the one to be replied to
|
||||
let replying = false;
|
||||
prepareObserveRenders();
|
||||
patchWithCleanup(Message.prototype, {
|
||||
setup() {
|
||||
const cb = () => {
|
||||
if (replying) {
|
||||
if (messageIds.includes(this.message.id)) {
|
||||
throw new Error(
|
||||
"Should not re-render other messages on replying to a message"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
onMounted(cb);
|
||||
onPatched(cb);
|
||||
return super.setup();
|
||||
},
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 10 });
|
||||
const stopObserve = observeRenders();
|
||||
replying = true;
|
||||
await click(".o-mail-Message:last [title='Reply']");
|
||||
await contains(".o-mail-Composer:has(:text('Replying to Mitchell Admin'))");
|
||||
replying = false;
|
||||
const result = stopObserve();
|
||||
expect(result.get(Composer)).toBeLessThan(2);
|
||||
expect(result.get(Message)).toBeLessThan(2);
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { waitUntilSubscribe } from "@bus/../tests/bus_test_helpers";
|
||||
import {
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { Command, mockService, patchWithCleanup, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("open channel in discuss from push notification", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss("mail.box_inbox");
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Inbox']");
|
||||
navigator.serviceWorker.dispatchEvent(
|
||||
new MessageEvent("message", {
|
||||
data: { action: "OPEN_CHANNEL", data: { id: channelId } },
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-DiscussContent-threadName[title='General']");
|
||||
});
|
||||
|
||||
test("notify message to user as non member", async () => {
|
||||
patchWithCleanup(window, {
|
||||
Notification: class Notification {
|
||||
static get permission() {
|
||||
return "granted";
|
||||
}
|
||||
constructor() {
|
||||
expect.step("push notification");
|
||||
}
|
||||
addEventListener() {}
|
||||
},
|
||||
});
|
||||
mockService("multi_tab", { isOnMainTab: () => true });
|
||||
const pyEnv = await startServer();
|
||||
const johnUser = pyEnv["res.users"].create({ name: "John" });
|
||||
const johnPartner = pyEnv["res.partner"].create({ name: "John", user_ids: [johnUser] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [Command.create({ partner_id: johnPartner })],
|
||||
});
|
||||
await start();
|
||||
await Promise.all([openDiscuss(channelId), waitUntilSubscribe(`discuss.channel_${channelId}`)]);
|
||||
await withUser(johnUser, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: { body: "Hello!", message_type: "comment" },
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-Message", { text: "Hello!" });
|
||||
expect.verifySteps(["push notification"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
onRpcAfter,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Deferred, mockDate, animationFrame } from "@odoo/hoot-mock";
|
||||
import { Command, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("navigate to sub channel", async () => {
|
||||
mockDate("2025-01-01 12:00:00", +1);
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
// Should access sub-thread after its creation.
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "General" });
|
||||
await click("button[title='Threads']");
|
||||
await click("button[aria-label='Create Thread']");
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "New Thread" });
|
||||
// Should access sub-thread when clicking on the menu.
|
||||
await click(".o-mail-DiscussSidebarChannel", { name: "General" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "General" });
|
||||
await click("button[title='Threads']");
|
||||
await click(".o-mail-SubChannelPreview", { text: "New Thread" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "New Thread" });
|
||||
// Should access sub-thread when clicking on the notification.
|
||||
await click(".o-mail-DiscussSidebarChannel", { name: "General" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "New Thread" });
|
||||
await contains(".o-mail-NotificationMessage", {
|
||||
text: `${serverState.partnerName} started a thread: New Thread.1:00 PM`,
|
||||
});
|
||||
await click(".o-mail-NotificationMessage a", { text: "New Thread" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "New Thread" });
|
||||
});
|
||||
|
||||
test("can manually unpin a sub-thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
// Open thread so this is pinned
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "General" });
|
||||
await click("button[title='Threads']");
|
||||
await click("button[aria-label='Create Thread']");
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "New Thread" });
|
||||
await click("[title='Thread Actions']");
|
||||
await click(".o-dropdown-item:contains('Unpin Conversation')");
|
||||
await contains(".o-mail-DiscussSidebar-item", { text: "New Thread", count: 0 });
|
||||
});
|
||||
|
||||
test("create sub thread from existing message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
body: "<p>Selling a training session and selling the products after the training session is more efficient.</p>",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Message-actions [title='Expand']");
|
||||
await click(".o-dropdown-item:contains('Create Thread')");
|
||||
await contains(".o-mail-DiscussContent-threadName", {
|
||||
value: "Selling a training session and",
|
||||
});
|
||||
await contains(".o-mail-Message", {
|
||||
text: "Selling a training session and selling the products after the training session is more efficient.",
|
||||
});
|
||||
await click(".o-mail-DiscussSidebarChannel", { name: "General" });
|
||||
await click(".o-mail-Message-actions [title='Expand']");
|
||||
await contains(".o-dropdown-item:contains('Create Thread')", { count: 0 });
|
||||
await contains(".o-mail-SubChannelPreview:contains('Selling a training session and')");
|
||||
await click(".o-mail-SubChannelPreview:contains('Selling a training session and')");
|
||||
await contains(".o-mail-DiscussContent-threadName", {
|
||||
value: "Selling a training session and",
|
||||
});
|
||||
await contains(".o-mail-SubChannelPreview:contains('Selling a training session and')", {
|
||||
count: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should allow creating a thread from an existing thread", async () => {
|
||||
mockDate("2025-01-01 12:00:00", +1);
|
||||
const pyEnv = await startServer();
|
||||
const parent_channel_id = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const sub_channel_id = pyEnv["discuss.channel"].create({
|
||||
name: "sub channel",
|
||||
parent_channel_id: parent_channel_id,
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
model: "discuss.channel",
|
||||
res_id: sub_channel_id,
|
||||
body: "<p>hello alex</p>",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(sub_channel_id);
|
||||
await click(".o-mail-Message-actions [title='Expand']");
|
||||
await click(".o-dropdown-item:contains('Create Thread')");
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "hello alex" });
|
||||
await click(".o-mail-DiscussSidebarChannel", { name: "General" });
|
||||
await contains(".o-mail-NotificationMessage", {
|
||||
text: `${serverState.partnerName} started a thread: hello alex.1:00 PM`,
|
||||
});
|
||||
});
|
||||
|
||||
test("create sub thread from existing message (slow network)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
body: "<p>Selling a training session and selling the products after the training session is more efficient.</p>",
|
||||
});
|
||||
const createSubChannelDef = new Deferred();
|
||||
onRpcAfter("/discuss/channel/sub_channel/create", async () => await createSubChannelDef);
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Message-actions [title='Expand']");
|
||||
await click(".o-dropdown-item:contains('Create Thread')");
|
||||
await animationFrame();
|
||||
createSubChannelDef.resolve();
|
||||
await contains(".o-mail-DiscussContent-threadName", {
|
||||
value: "Selling a training session and",
|
||||
});
|
||||
await contains(".o-mail-Message", {
|
||||
text: "Selling a training session and selling the products after the training session is more efficient.",
|
||||
});
|
||||
});
|
||||
|
||||
test("create sub thread from sub-thread list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("button[title='Threads']");
|
||||
await contains(".o-mail-SubChannelList", { text: "This channel has no thread yet." });
|
||||
await click("button[aria-label='Create Thread']");
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "New Thread" });
|
||||
await click(".o-mail-DiscussSidebarChannel", { name: "General" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "General" });
|
||||
await click(".o-mail-DiscussContent-header button[title='Threads']");
|
||||
await insertText(
|
||||
".o-mail-ActionPanel:has(.o-mail-SubChannelList) .o_searchview_input",
|
||||
"MyEpicThread"
|
||||
);
|
||||
await click("button[aria-label='Search button']");
|
||||
await contains(".o-mail-SubChannelList", { text: 'No thread named "MyEpicThread"' });
|
||||
await click("button[aria-label='Create Thread']");
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "MyEpicThread" });
|
||||
});
|
||||
|
||||
test("'Thread' menu available in threads", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
});
|
||||
const subChannelID = pyEnv["discuss.channel"].create({
|
||||
name: "ThreadOne",
|
||||
parent_channel_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(subChannelID);
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "ThreadOne" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "ThreadOne" });
|
||||
await click("button[title='Threads']");
|
||||
await insertText(".o-mail-ActionPanel input[placeholder='Search by name']", "ThreadTwo");
|
||||
await click(".o-mail-ActionPanel button", { text: "Create" });
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "ThreadTwo" });
|
||||
});
|
||||
|
||||
test("sub thread is available for channel and group, not for chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
});
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "Group",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("button[title='Threads']");
|
||||
await insertText(
|
||||
".o-mail-ActionPanel input[placeholder='Search by name']",
|
||||
"Sub thread for channel"
|
||||
);
|
||||
await click(".o-mail-ActionPanel button", { text: "Create" });
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "Sub thread for channel" });
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Group" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "Group" });
|
||||
await click("button[title='Threads']");
|
||||
await insertText(
|
||||
".o-mail-ActionPanel input[placeholder='Search by name']",
|
||||
"Sub thread for group"
|
||||
);
|
||||
await click(".o-mail-ActionPanel button", { text: "Create" });
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "Sub thread for group" });
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Demo" });
|
||||
await contains("button[title='Threads']", { count: 0 });
|
||||
});
|
||||
|
||||
test("mention suggestions in thread match channel restrictions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const groupId = pyEnv["res.groups"].create({ name: "testGroup" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
group_public_id: groupId,
|
||||
});
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "Thread",
|
||||
parent_channel_id: channelId,
|
||||
});
|
||||
pyEnv["res.users"].write(serverState.userId, { group_ids: [Command.link(groupId)] });
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ email: "p1@odoo.com", name: "p1" },
|
||||
{ email: "p2@odoo.com", name: "p2" },
|
||||
]);
|
||||
pyEnv["res.users"].create([
|
||||
{ partner_id: partnerId_1, group_ids: [Command.link(groupId)] },
|
||||
{ partner_id: partnerId_2 },
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-DiscussSidebar-item.o-active:contains('General')");
|
||||
await insertText(".o-mail-Composer-input", "@");
|
||||
await contains(".o-mail-Composer-suggestion", { count: 2 });
|
||||
await contains(".o-mail-Composer-suggestion", { text: "Mitchell Admin" });
|
||||
await contains(".o-mail-Composer-suggestion", { text: "p1" });
|
||||
await click(".o-mail-DiscussSidebar-item:contains('Thread')");
|
||||
await contains(".o-mail-DiscussSidebar-item.o-active:contains('Thread')");
|
||||
await insertText(".o-mail-Composer-input", "@");
|
||||
await contains(".o-mail-Composer-suggestion", { count: 2 });
|
||||
await contains(".o-mail-Composer-suggestion", { text: "Mitchell Admin" });
|
||||
await contains(".o-mail-Composer-suggestion", { text: "p1" });
|
||||
});
|
||||
|
||||
test("sub-thread is visually muted when mute is active", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "General" });
|
||||
await click("button[title='Threads']");
|
||||
await click("button[aria-label='Create Thread']");
|
||||
await contains(".opacity-50.o-mail-DiscussSidebar-item:contains('New Thread')", { count: 0 });
|
||||
await click(".o-mail-DiscussSidebar-item:contains('New Thread')");
|
||||
await click("button[title='Notification Settings']");
|
||||
await click("button:contains('Mute Conversation')");
|
||||
await click("button:contains('Until I turn it back on')");
|
||||
await contains(".opacity-50.o-mail-DiscussSidebar-item:contains('New Thread')");
|
||||
});
|
||||
|
||||
test("muted channel hides sub-thread unless channel is selected or thread has unread messages", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const partnerId2 = pyEnv["res.partner"].create({ email: "p1@odoo.com", name: "p1" });
|
||||
const userId2 = pyEnv["res.users"].create({ name: "User 2", partner_id: partnerId2 });
|
||||
const partnerId = serverState.partnerId;
|
||||
const subChannelId = pyEnv["discuss.channel"].create({
|
||||
name: "New Thread",
|
||||
parent_channel_id: channelId,
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: partnerId }),
|
||||
Command.create({ partner_id: partnerId2 }),
|
||||
],
|
||||
});
|
||||
pyEnv["discuss.channel"].create({ name: "Other" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-DiscussSidebar-item:contains('General')");
|
||||
await click("button[title='Notification Settings']");
|
||||
await click("button:contains('Mute Conversation')");
|
||||
await click("button:contains('Until I turn it back on')");
|
||||
await click(".o-mail-DiscussSidebar-item:contains('Other')");
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('New Thread')", { count: 0 });
|
||||
await click(".o-mail-DiscussSidebar-item:contains('General')");
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('New Thread')");
|
||||
await click(".o-mail-DiscussSidebar-item:contains('Other')");
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('New Thread')", { count: 0 });
|
||||
withUser(userId2, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: { body: "Some message", message_type: "comment" },
|
||||
thread_id: subChannelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('New Thread')");
|
||||
});
|
||||
|
||||
test("show notification when clicking on deleted thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "Test Channel" });
|
||||
const activeThreadId = pyEnv["discuss.channel"].create({
|
||||
name: "Message 1",
|
||||
parent_channel_id: channelId,
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: `<div class="o_mail_notification"> started a thread:<a href="#" class="o_channel_redirect" data-oe-id="${activeThreadId}" data-oe-model="discuss.channel">Message 1</a></div>`,
|
||||
message_type: "notification",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
pyEnv["discuss.channel"].unlink(activeThreadId);
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-NotificationMessage a", { text: "Message 1" });
|
||||
await contains(".o_notification:has(.o_notification_bar.bg-danger)", {
|
||||
text: "This thread is no longer available.",
|
||||
});
|
||||
});
|
||||
|
||||
test("Can delete channel thread as author of thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const subChannelID = pyEnv["discuss.channel"].create({
|
||||
name: "test thread",
|
||||
parent_channel_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(subChannelID);
|
||||
await contains(".o-mail-DiscussContent-threadName[title='test thread']");
|
||||
await click(".o-mail-DiscussSidebar-item:contains('test thread') [title='Thread Actions']");
|
||||
await click(".o-dropdown-item:contains('Delete Thread')");
|
||||
await click(".modal button:contains('Delete Thread')");
|
||||
await contains(".o-mail-DiscussContent-threadName[title='General']");
|
||||
await contains(
|
||||
`.o-mail-NotificationMessage :text(Mitchell Admin deleted the thread "test thread")`
|
||||
);
|
||||
});
|
||||
|
||||
test("can mention all group chat members inside its sub-thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Lilibeth" });
|
||||
const groupChannelId = pyEnv["discuss.channel"].create({
|
||||
name: "Our channel",
|
||||
channel_type: "group",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
const groupSubChannelId = pyEnv["discuss.channel"].create({
|
||||
name: "New Thread",
|
||||
parent_channel_id: groupChannelId,
|
||||
channel_member_ids: [Command.create({ partner_id: serverState.partnerId })],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(groupSubChannelId);
|
||||
await insertText(".o-mail-Composer-input", "@");
|
||||
await contains(".o-mail-Composer-suggestion", { count: 2 });
|
||||
});
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
import { insertText as htmlInsertText } from "@html_editor/../tests/_helpers/user_actions";
|
||||
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { beforeEach, describe, test } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { Command, getService, patchWithCleanup, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { Composer } from "@mail/core/common/composer";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
beforeEach(() => {
|
||||
// Simulate real user interactions
|
||||
patchWithCleanup(Composer.prototype, {
|
||||
isEventTrusted() {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('[text composer] display command suggestions on typing "/"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await insertText(".o-mail-Composer-input", "/");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open");
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("display command suggestions on typing '/'", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await focus(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "/");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open");
|
||||
});
|
||||
|
||||
test("[text composer] use a command for a specific channel type", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ channel_type: "chat" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await contains(".o-mail-Composer-input", { value: "" });
|
||||
await insertText(".o-mail-Composer-input", "/");
|
||||
await click(".o-mail-Composer-suggestion strong", { text: "who" });
|
||||
await contains(".o-mail-Composer-input", { value: "/who " });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("use a command for a specific channel type", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ channel_type: "chat" });
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await focus(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "/");
|
||||
await click(".o-mail-Composer-suggestion strong", { text: "who" });
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable", { text: "/who" });
|
||||
});
|
||||
|
||||
test("[text composer] command suggestion should only open if command is the first character", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await contains(".o-mail-Composer-input", { value: "" });
|
||||
await insertText(".o-mail-Composer-input", "bluhbluh ");
|
||||
await contains(".o-mail-Composer-input", { value: "bluhbluh " });
|
||||
await insertText(".o-mail-Composer-input", "/");
|
||||
// weak test, no guarantee that we waited long enough for the potential list to open
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("command suggestion should only open if command is the first character", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await focus(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "bluhbluh");
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable", { text: "bluhbluh" });
|
||||
await htmlInsertText(editor, "/");
|
||||
// weak test, no guarantee that we waited long enough for the potential list to open
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
});
|
||||
|
||||
test("Sort partner suggestions by recent chats", async () => {
|
||||
mockDate("2023-01-03 12:00:00"); // so that it's after last interest (mock server is in 2019 by default!)
|
||||
const pyEnv = await startServer();
|
||||
const [partner_1, partner_2, partner_3] = pyEnv["res.partner"].create([
|
||||
{ name: "User 1" },
|
||||
{ name: "User 2" },
|
||||
{ name: "User 3" },
|
||||
]);
|
||||
pyEnv["res.users"].create([
|
||||
{ partner_id: partner_1 },
|
||||
{ partner_id: partner_2 },
|
||||
{ partner_id: partner_3 },
|
||||
]);
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partner_1 }),
|
||||
Command.create({ partner_id: partner_2 }),
|
||||
Command.create({ partner_id: partner_3 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
last_interest_dt: "2023-01-01 00:00:00",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ partner_id: partner_1 }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
last_interest_dt: "2023-01-01 00:00:10",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ partner_id: partner_2 }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
last_interest_dt: "2023-01-01 00:00:20",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ partner_id: partner_3 }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "User 2" });
|
||||
await insertText(".o-mail-Composer-input", "This is a test");
|
||||
await press("Enter");
|
||||
await contains(".o-mail-Message-content", { text: "This is a test" });
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "General" });
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-chat + .o-mail-DiscussSidebarChannel-container:text(User 2)"
|
||||
);
|
||||
await insertText(".o-mail-Composer-input[placeholder='Message #General…']", "@");
|
||||
await insertText(".o-mail-Composer-input", "User");
|
||||
await contains(".o-mail-Composer-suggestion strong", { count: 3 });
|
||||
await contains(":nth-child(1 of .o-mail-Composer-suggestion) strong", { text: "User 2" });
|
||||
await contains(":nth-child(2 of .o-mail-Composer-suggestion) strong", { text: "User 3" });
|
||||
await contains(":nth-child(3 of .o-mail-Composer-suggestion) strong", { text: "User 1" });
|
||||
});
|
||||
|
||||
test("mention suggestion are shown after deleting a character", async () => {
|
||||
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: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "@John D");
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "John Doe" });
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await contains(".o-mail-Composer-suggestion strong", { count: 0, text: "John D" });
|
||||
// Simulate pressing backspace
|
||||
const textarea = document.querySelector(".o-mail-Composer-input");
|
||||
textarea.value = textarea.value.slice(0, -1);
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "John Doe" });
|
||||
});
|
||||
|
||||
test("[text composer] command suggestion are shown after deleting a character", async () => {
|
||||
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: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "/he");
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "help" });
|
||||
await insertText(".o-mail-Composer-input", "e");
|
||||
await contains(".o-mail-Composer-suggestion strong", { count: 0, text: "help" });
|
||||
// Simulate pressing backspace
|
||||
const textarea = document.querySelector(".o-mail-Composer-input");
|
||||
textarea.value = textarea.value.slice(0, -1);
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "help" });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("command suggestion are shown after deleting a character", async () => {
|
||||
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: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-suggestionList");
|
||||
await contains(".o-mail-Composer-suggestionList .o-open", { count: 0 });
|
||||
await focus(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "/he");
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "help" });
|
||||
await htmlInsertText(editor, "e");
|
||||
await contains(".o-mail-Composer-suggestion strong", { count: 0, text: "help" });
|
||||
await press("Backspace");
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "help" });
|
||||
});
|
||||
|
||||
test("mention suggestion displays OdooBot before archived partners", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jane", active: false });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "Our channel",
|
||||
channel_type: "group",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
Command.create({ partner_id: serverState.odoobotId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "@");
|
||||
await contains(".o-mail-Composer-suggestion", { count: 3 });
|
||||
await contains(".o-mail-Composer-suggestion", {
|
||||
text: "Mitchell Admin",
|
||||
before: [
|
||||
".o-mail-Composer-suggestion",
|
||||
{
|
||||
text: "OdooBot",
|
||||
before: [".o-mail-Composer-suggestion", { text: "Jane" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("can open DM from @username in command palette", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const marioUid = pyEnv["res.users"].create({ name: "Mario" });
|
||||
pyEnv["res.partner"].create({ name: "Mario", user_ids: [marioUid] });
|
||||
await start();
|
||||
triggerHotkey("control+k");
|
||||
await insertText(".o_command_palette_search input", "@");
|
||||
await insertText("input[placeholder='Search a conversation']", "Mario");
|
||||
await click(".o_command.focused:has(.oi-user)", { text: "Mario" });
|
||||
await contains(".o-mail-ChatWindow", { text: "Mario" });
|
||||
});
|
||||
|
||||
test("can open channel from @channel_name in command palette", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "general",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-02 10:00:00", // same last interest to sort by id
|
||||
}),
|
||||
],
|
||||
});
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "project",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-02 10:00:00", // same last interest to sort by id
|
||||
}),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
triggerHotkey("control+k");
|
||||
await insertText(".o_command_palette_search input", "@");
|
||||
await contains(".o_command", { count: 6 });
|
||||
await contains(".o_command:eq(0):has(.fa-hashtag)", { text: "project" });
|
||||
await contains(".o_command:eq(1):has(.fa-hashtag)", { text: "general" });
|
||||
await contains(".o_command:has(.oi-user)", { text: "OdooBot" });
|
||||
await contains(".o_command:has(.oi-user)", { text: "Mitchell Admin" }); // self-conversation
|
||||
await contains(".o_command", { text: "Create Channel" });
|
||||
await contains(".o_command", { text: "Create Chat" });
|
||||
await click(".o_command.focused:has(.fa-hashtag)", { text: "project" });
|
||||
await contains(".o-mail-ChatWindow", { text: "project" });
|
||||
});
|
||||
|
||||
test("Conversation mentions in the command palette with @", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "group",
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
body: "@Mitchell Admin",
|
||||
needaction: true,
|
||||
});
|
||||
pyEnv["mail.notification"].create({
|
||||
mail_message_id: messageId,
|
||||
notification_type: "inbox",
|
||||
res_partner_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
triggerHotkey("control+k");
|
||||
await insertText(".o_command_palette_search input", "@", { replace: true });
|
||||
await contains(".o_command_palette .o_command_category", {
|
||||
contains: [
|
||||
["span.fw-bold", { text: "Mentions" }],
|
||||
[".o_command.focused .o_command_name", { text: "Mitchell Admin and Mario" }],
|
||||
],
|
||||
});
|
||||
// can also make self conversation
|
||||
await contains(".o_command_palette .o_command_category", {
|
||||
contains: [[".o_command_name", { text: "Mitchell Admin" }]],
|
||||
});
|
||||
await click(".o_command.focused");
|
||||
await contains(".o-mail-ChatWindow", { text: "Mitchell Admin and Mario" });
|
||||
});
|
||||
|
||||
test("Max 3 most recent conversations in command palette of Discuss", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({ name: "channel_1" });
|
||||
pyEnv["discuss.channel"].create({ name: "channel_2" });
|
||||
pyEnv["discuss.channel"].create({ name: "channel_3" });
|
||||
pyEnv["discuss.channel"].create({ name: "channel_4" });
|
||||
await start();
|
||||
triggerHotkey("control+k");
|
||||
await insertText(".o_command_palette_search input", "@", { replace: true });
|
||||
await contains(".o_command_palette .o_command_category", {
|
||||
contains: [
|
||||
["span.fw-bold", { text: "Recent" }],
|
||||
[".o_command", { count: 3 }],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("only partners with dedicated users will be displayed in command palette", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const demoUid = pyEnv["res.users"].create({ name: "Demo" });
|
||||
pyEnv["res.partner"].create({ name: "Demo", user_ids: [demoUid] });
|
||||
pyEnv["res.partner"].create({ name: "Portal" });
|
||||
await start();
|
||||
triggerHotkey("control+k");
|
||||
await insertText(".o_command_palette_search input", "@");
|
||||
await contains(".o_command_name", { count: 5 });
|
||||
await contains(".o_command_name", { text: "Demo" });
|
||||
await contains(".o_command_name", { text: "OdooBot" });
|
||||
await contains(".o_command_name", { text: "Mitchell Admin" }); // self-conversation
|
||||
await contains(".o_command_name", { text: "Create Channel" });
|
||||
await contains(".o_command_name", { text: "Create Chat" });
|
||||
await contains(".o_command_name", { text: "Portal", count: 0 });
|
||||
});
|
||||
|
||||
test("hide conversations in recent if they have mentions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: serverState.odoobotId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
body: "@OdooBot",
|
||||
});
|
||||
await start();
|
||||
triggerHotkey("control+k");
|
||||
await insertText(".o_command_palette_search input", "@", { replace: true });
|
||||
await contains(".o_command_category span.fw-bold", { text: "Mentions" });
|
||||
await contains(".o_command_palette .o_command_category .o_command_name", {
|
||||
text: "OdooBot",
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test("Ctrl-K opens @ command palette in discuss app", async () => {
|
||||
await start();
|
||||
await openDiscuss();
|
||||
triggerHotkey("control+k");
|
||||
await contains(".o_command_palette_search", { text: "@" });
|
||||
});
|
||||
|
||||
test("Can create group chat from ctrl-k without any user selected", async () => {
|
||||
await start();
|
||||
await openDiscuss();
|
||||
triggerHotkey("control+k");
|
||||
await click(".o_command_name:contains(Create Chat)");
|
||||
await click(".modal-footer > .btn:contains(Create Group Chat)");
|
||||
await contains(".o-mail-DiscussSidebarChannel-itemName", { text: "Mitchell Admin" });
|
||||
});
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { asyncStep, mockService, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Channel subscription is renewed when channel is manually added", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General", channel_member_ids: [] });
|
||||
await start();
|
||||
mockService("bus_service", {
|
||||
forceUpdateChannels() {
|
||||
asyncStep("update-channels");
|
||||
},
|
||||
});
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("[title='Invite People']");
|
||||
await click(".o-discuss-ChannelInvitation-selectable", { text: "Mitchell Admin" });
|
||||
await click("[title='Invite']:enabled");
|
||||
await waitForSteps(["update-channels"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
listenStoreFetch,
|
||||
onRpcBefore,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
SIZES,
|
||||
start,
|
||||
startServer,
|
||||
STORE_FETCH_ROUTES,
|
||||
triggerHotkey,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import {
|
||||
asyncStep,
|
||||
Command,
|
||||
onRpc,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { pick } from "@web/core/utils/objects";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("can create a new channel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
onRpcBefore((route, args) => {
|
||||
if (
|
||||
(route.startsWith("/mail") || route.startsWith("/discuss")) &&
|
||||
!STORE_FETCH_ROUTES.includes(route)
|
||||
) {
|
||||
asyncStep(`${route} - ${JSON.stringify(args)}`);
|
||||
}
|
||||
});
|
||||
listenStoreFetch(undefined, { logParams: ["/discuss/create_channel"] });
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await waitStoreFetch([
|
||||
"failures",
|
||||
"systray_get_activities",
|
||||
"init_messaging",
|
||||
"channels_as_member",
|
||||
]);
|
||||
await contains(".o-mail-Discuss");
|
||||
await contains(".o-mail-DiscussSidebar-item", { text: "abc", count: 0 });
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await insertText("input[placeholder='Search a conversation']", "abc");
|
||||
await waitForSteps([`/discuss/search - {"term":""}`, `/discuss/search - {"term":"abc"}`]);
|
||||
await click("a", { text: "Create Channel" });
|
||||
await contains(".o-mail-DiscussSidebar-item", { text: "abc" });
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
const [channelId] = pyEnv["discuss.channel"].search([["name", "=", "abc"]]);
|
||||
const [selfMember] = pyEnv["discuss.channel.member"].search_read([
|
||||
["channel_id", "=", channelId],
|
||||
["partner_id", "=", serverState.partnerId],
|
||||
]);
|
||||
await waitStoreFetch([["/discuss/create_channel", { name: "abc" }]], {
|
||||
stepsAfter: [
|
||||
`/discuss/channel/messages - ${JSON.stringify({
|
||||
channel_id: channelId,
|
||||
fetch_params: { limit: 60, around: selfMember.new_message_separator },
|
||||
})}`,
|
||||
`/discuss/channel/members - ${JSON.stringify({
|
||||
channel_id: channelId,
|
||||
known_member_ids: [selfMember.id],
|
||||
})}`,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("can make a DM chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
onRpcBefore((route, args) => {
|
||||
if (
|
||||
(route.startsWith("/mail") || route.startsWith("/discuss")) &&
|
||||
!STORE_FETCH_ROUTES.includes(route)
|
||||
) {
|
||||
asyncStep(`${route} - ${JSON.stringify(args)}`);
|
||||
}
|
||||
});
|
||||
onRpc((params) => {
|
||||
if (params.model === "discuss.channel" && ["search_read"].includes(params.method)) {
|
||||
asyncStep(
|
||||
`${params.route} - ${JSON.stringify(
|
||||
pick(params, "args", "kwargs", "method", "model")
|
||||
)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
listenStoreFetch(undefined, {
|
||||
logParams: ["/discuss/get_or_create_chat"],
|
||||
});
|
||||
await start();
|
||||
await waitStoreFetch(["failures", "systray_get_activities", "init_messaging"]);
|
||||
await openDiscuss();
|
||||
await waitStoreFetch(["channels_as_member"]);
|
||||
await contains(".o-mail-Discuss");
|
||||
await contains(".o-mail-DiscussSidebar-item", { text: "Mario", count: 0 });
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await contains(".o_command_name", { count: 5 });
|
||||
await insertText("input[placeholder='Search a conversation']", "mario");
|
||||
await contains(".o_command_name", { count: 3 });
|
||||
await click(".o_command_name", { text: "Mario" });
|
||||
await contains(".o-mail-DiscussSidebar-item", { text: "Mario" });
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
const [channelId] = pyEnv["discuss.channel"].search([["name", "=", "Mario, Mitchell Admin"]]);
|
||||
await waitStoreFetch([["/discuss/get_or_create_chat", { partners_to: [partnerId] }]], {
|
||||
stepsAfter: [
|
||||
`/discuss/channel/messages - ${JSON.stringify({
|
||||
channel_id: channelId,
|
||||
fetch_params: { limit: 60, around: 0 },
|
||||
})}`,
|
||||
],
|
||||
stepsBefore: [`/discuss/search - {"term":""}`, `/discuss/search - {"term":"mario"}`],
|
||||
});
|
||||
});
|
||||
|
||||
test("can create a group chat conversation", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ name: "Mario" },
|
||||
{ name: "Luigi" },
|
||||
]);
|
||||
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await click("a", { text: "Create Chat" });
|
||||
await click("li", { text: "Mario" });
|
||||
await click("li", { text: "Luigi" });
|
||||
await click(".btn", { text: "Create Group Chat" });
|
||||
await contains(".o-mail-DiscussSidebarChannel");
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
});
|
||||
|
||||
test("mobile chat search should allow to create group chat", async () => {
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains("button.active", { text: "Notifications" });
|
||||
await click("button", { text: "Chats" });
|
||||
await contains(".o-mail-DiscussSearch-inputContainer");
|
||||
});
|
||||
|
||||
test("Chat is pinned on other tabs when joined", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jerry Golay" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const env1 = await start({ asTab: true });
|
||||
const env2 = await start({ asTab: true });
|
||||
await openDiscuss(undefined, { target: env1 });
|
||||
await openDiscuss(undefined, { target: env2 });
|
||||
await click(`${env1.selector} input[placeholder='Search conversations']`);
|
||||
await contains(`${env1.selector} .o_command_name`, { count: 5 });
|
||||
await insertText(`${env1.selector} input[placeholder='Search a conversation']`, "Jer");
|
||||
await contains(`${env1.selector} .o_command_name`, { count: 3 });
|
||||
await click(`${env1.selector} .o_command_name`, { text: "Jerry Golay" });
|
||||
await contains(`${env1.selector} .o-mail-DiscussSidebar-item`, { text: "Jerry Golay" });
|
||||
await contains(`${env2.selector} .o-mail-DiscussSidebar-item`, { text: "Jerry Golay" });
|
||||
});
|
||||
|
||||
test("Auto-open OdooBot chat when opening discuss for the first time", async () => {
|
||||
// Odoobot chat has onboarding for using Discuss app.
|
||||
// We assume pinned odoobot chat without any message seen means user just started using Discuss app.
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: serverState.odoobotId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "OdooBot" });
|
||||
});
|
||||
|
||||
test("no conversation selected when opening non-existing channel in discuss", async () => {
|
||||
await startServer();
|
||||
await start();
|
||||
await openDiscuss(200); // non-existing id
|
||||
await contains("h4", { text: "No conversation selected." });
|
||||
await contains(".o-mail-DiscussSidebarCategory-channel .oi-chevron-down");
|
||||
await click(".o-mail-DiscussSidebar .btn", { text: "Channels" }); // check no crash
|
||||
await contains(".o-mail-DiscussSidebarCategory-channel .oi-chevron-right");
|
||||
});
|
||||
|
||||
test("can access portal partner profile from avatar popover", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const joelPartnerId = pyEnv["res.partner"].create({
|
||||
name: "Joel",
|
||||
user_ids: [Command.create({ name: "Joel", share: true })],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: joelPartnerId }),
|
||||
],
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: joelPartnerId,
|
||||
body: "Hello!",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Message-avatar", {
|
||||
parent: [".o-mail-Message", { text: "Joel" }],
|
||||
});
|
||||
await contains(".o_avatar_card", { text: "Joel" });
|
||||
await click("button", { text: "View Profile" });
|
||||
await contains(".o_form_view");
|
||||
await contains(".o_field_widget[name='name'] .o_input", { value: "Joel" });
|
||||
});
|
||||
|
||||
test("Preserve letter case and accents when creating channel from sidebar", async () => {
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await insertText("input[placeholder='Search a conversation']", "Crème brûlée Fan Club");
|
||||
await click("a", { text: "Create Channel" });
|
||||
await contains(".o-mail-DiscussContent-threadName", { value: "Crème brûlée Fan Club" });
|
||||
});
|
||||
|
||||
test("Create channel must have a name", async () => {
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await click("a", { text: "Create Channel" });
|
||||
await click("input[placeholder='Channel name']");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".invalid-feedback", { text: "Channel must have a name." });
|
||||
});
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
import {
|
||||
SIZES,
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, disableAnimations, expect, mockPermission, mockTouch, test } from "@odoo/hoot";
|
||||
import {
|
||||
Command,
|
||||
contains as webContains,
|
||||
getService,
|
||||
serverState,
|
||||
swipeLeft,
|
||||
swipeRight,
|
||||
withUser,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("can make DM chat in mobile", async () => {
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Gandalf" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains("button.active", { text: "Notifications" });
|
||||
await click("button", { text: "Chats" });
|
||||
await click(".o-mail-DiscussSearch-inputContainer");
|
||||
await contains(".o_command_name", { count: 5 });
|
||||
await insertText("input[placeholder='Search a conversation']", "Gandalf");
|
||||
await contains(".o_command_name", { count: 3 });
|
||||
await click(".o_command_name", { text: "Gandalf" });
|
||||
await contains(".o-mail-ChatWindow", { text: "Gandalf" });
|
||||
});
|
||||
|
||||
test("can search channel in mobile", async () => {
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({ name: "Gryffindors" });
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains("button.active", { text: "Notifications" });
|
||||
await click("button", { text: "Channels" });
|
||||
await click(".o-mail-DiscussSearch-inputContainer");
|
||||
await contains(".o_command_name", { count: 5 });
|
||||
await insertText("input[placeholder='Search a conversation']", "Gryff");
|
||||
await contains(".o_command_name", { count: 3 });
|
||||
await click(".o_command_name", { text: "Gryffindors" });
|
||||
await contains(".o-mail-ChatWindow div[title='Gryffindors']");
|
||||
});
|
||||
|
||||
test("can make new channel in mobile", async () => {
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains("button.active", { text: "Notifications" });
|
||||
await click("button", { text: "Channels" });
|
||||
await click(".o-mail-DiscussSearch-inputContainer");
|
||||
await insertText("input[placeholder='Search a conversation']", "slytherins");
|
||||
await click("a", { text: "Create Channel" });
|
||||
await contains(".o-mail-ChatWindow", { text: "slytherins" });
|
||||
});
|
||||
|
||||
test("new message opens the @ command palette", async () => {
|
||||
await start();
|
||||
await click(".o_menu_systray .dropdown-toggle i[aria-label='Messages']");
|
||||
await click(".o-mail-MessagingMenu button", { text: "New Message" });
|
||||
await contains(".o_command_palette_search .o_namespace", { text: "@" });
|
||||
await contains(".o_command_palette input[placeholder='Search a conversation']");
|
||||
});
|
||||
|
||||
test("channel preview show deleted messages", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "<p>before last</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "<p></p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { text: "before last" });
|
||||
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
|
||||
await contains(".o-mail-NotificationItem-text", {
|
||||
text: "Demo: This message has been removed",
|
||||
});
|
||||
});
|
||||
|
||||
test("deleted message should not show parent message reference and mentions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
body: "<p>Parent Message</p>",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "<p>reply message</p>",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
parent_id: messageId,
|
||||
partner_ids: [serverState.partnerId],
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-MessageInReply", { text: "Parent Message" });
|
||||
await webContains(
|
||||
".o-mail-Message:has(.o-mail-Message-bubble.o-orange):contains('reply message')"
|
||||
).hover();
|
||||
await webContains(
|
||||
".o-mail-Message:has(.o-mail-Message-bubble.o-orange):contains('reply message') [title='Expand']"
|
||||
).click();
|
||||
await click(".o-mail-Message-moreMenu .o-dropdown-item:contains(Delete)");
|
||||
await click(".o_dialog button:contains(Delete)");
|
||||
await contains(".o-mail-Message:not(:has(.o-mail-Message-bubble.o-orange))", {
|
||||
text: "This message has been removed",
|
||||
});
|
||||
await contains(".o-mail-MessageInReply", { count: 0 });
|
||||
});
|
||||
|
||||
test("channel preview ignores transient message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "<p>test</p>",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "/who");
|
||||
await click(".o-mail-Composer button[title='Send']:enabled");
|
||||
await contains(".o_mail_notification", { text: "You are alone in this channel." });
|
||||
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
|
||||
await contains(".o-mail-NotificationItem-text", { text: "Demo: test" });
|
||||
});
|
||||
|
||||
test("channel preview ignores messages from the past", async () => {
|
||||
disableAnimations();
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
body: "first message",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
for (let i = 0; i < 100; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
body: `message ${i}`,
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
}
|
||||
const newestMessageId = pyEnv["mail.message"].create({
|
||||
body: "last message",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
parent_id: messageId,
|
||||
res_id: channelId,
|
||||
});
|
||||
const [selfMember] = pyEnv["discuss.channel.member"].search_read([
|
||||
["partner_id", "=", serverState.partnerId],
|
||||
["channel_id", "=", channelId],
|
||||
]);
|
||||
pyEnv["discuss.channel.member"].write([selfMember.id], {
|
||||
new_message_separator: newestMessageId + 1,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
await contains(".o-mail-Message-content", { text: "last message" });
|
||||
await contains(".o-mail-Thread", { scroll: "bottom" });
|
||||
await click(".o-mail-MessageInReply-content", { text: "first message" });
|
||||
await contains(".o-mail-Message", { count: 31 });
|
||||
await contains(".o-mail-Message-content", { text: "first message" });
|
||||
await contains(".o-mail-Message-content", { text: "last message", count: 0 });
|
||||
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
|
||||
await contains(".o-mail-NotificationItem-text", { text: "You: last message" });
|
||||
withUser(serverState.userId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: { body: "it's a good idea", message_type: "comment" },
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-NotificationItem-text", { text: "You: it's a good idea" });
|
||||
});
|
||||
|
||||
test("counter is taking into account non-fetched channels", async () => {
|
||||
mockPermission("notifications", "denied");
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ message_unread_counter: 1, partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "first message",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await contains(".o-mail-MessagingMenu-counter", { text: "1" });
|
||||
expect(
|
||||
Boolean(getService("mail.store").Thread.get({ model: "discuss.channel", id: channelId }))
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("counter is updated on receiving message on non-fetched channels", async () => {
|
||||
mockPermission("notifications", "denied");
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
|
||||
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "first message",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await contains(".o_menu_systray .dropdown-toggle i[aria-label='Messages']");
|
||||
await contains(".o-mail-MessagingMenu-counter", { count: 0 });
|
||||
expect(
|
||||
Boolean(getService("mail.store").Thread.get({ model: "discuss.channel", id: channelId }))
|
||||
).toBe(false);
|
||||
withUser(userId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: { body: "good to know", message_type: "comment" },
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-MessagingMenu-counter", { text: "1" });
|
||||
});
|
||||
|
||||
test("can use notification item swipe actions", async () => {
|
||||
mockTouch(true);
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", email: "demo@odoo.com" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "chat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "A message",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains("button.active", { text: "Notifications" });
|
||||
await click("button", { text: "Chats" });
|
||||
await contains(".o-mail-NotificationItem .o-mail-NotificationItem-badge:contains(1)");
|
||||
await swipeRight(".o_actionswiper"); // marks as read
|
||||
await contains(".o-mail-NotificationItem-badge", { count: 0 });
|
||||
await swipeLeft(".o_actionswiper"); // unpins
|
||||
await contains(".o-mail-NotificationItem", { count: 0 });
|
||||
});
|
||||
|
||||
test("counter does not double count channel needaction messages", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
body: "Hey @Mitchell Admin",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
pyEnv["mail.notification"].create({
|
||||
mail_message_id: messageId,
|
||||
notification_status: "sent",
|
||||
notification_type: "inbox",
|
||||
res_partner_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']"); // fetch channels
|
||||
await contains(".o-mail-NotificationItem", { text: "General" }); // ensure channels fetched
|
||||
await contains(".o-mail-MessagingMenu-counter:text('1')");
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { waitForChannels } from "@bus/../tests/bus_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
import { asyncStep, Command, serverState, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("unknown channel can be displayed and interacted with", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [Command.create({ partner_id: partnerId })],
|
||||
channel_type: "channel",
|
||||
name: "Not So Secret",
|
||||
});
|
||||
const env = await start();
|
||||
env.services.bus_service.subscribe("discuss.channel/new_message", () =>
|
||||
asyncStep("discuss.channel/new_message")
|
||||
);
|
||||
await openDiscuss("mail.box_inbox");
|
||||
await contains("button.o-active", { text: "Inbox" });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
|
||||
await openDiscuss(channelId);
|
||||
await waitForChannels([`discuss.channel_${channelId}`]);
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Not So Secret" });
|
||||
await insertText(".o-mail-Composer-input", "Hello", { replace: true });
|
||||
await press("Enter");
|
||||
await contains(".o-mail-Message", { text: "Hello" });
|
||||
await waitForSteps(["discuss.channel/new_message"]);
|
||||
await click("button", { text: "Inbox" });
|
||||
await contains(".o-mail-DiscussSidebarChannel:not(.o-active)", { text: "Not So Secret" });
|
||||
await click("[title='Channel Actions']");
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await click("button", { text: "Leave Conversation" });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
import { AWAY_DELAY } from "@mail/core/common/im_status_service";
|
||||
import { defineMailModels, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { beforeEach, describe, expect, test } from "@odoo/hoot";
|
||||
import { advanceTime, freezeTime } from "@odoo/hoot-dom";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import {
|
||||
asyncStep,
|
||||
makeMockEnv,
|
||||
mockService,
|
||||
patchWithCleanup,
|
||||
restoreRegistry,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
defineMailModels();
|
||||
beforeEach(freezeTime);
|
||||
describe.current.tags("headless");
|
||||
|
||||
test("update presence if IM status changes to offline while this device is online", async () => {
|
||||
mockService("bus_service", { send: (type) => asyncStep(type) });
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { im_status: "online" });
|
||||
await start();
|
||||
await waitForSteps(["update_presence"]);
|
||||
pyEnv["bus.bus"]._sendone(serverState.partnerId, "bus.bus/im_status_updated", {
|
||||
presence_status: "offline",
|
||||
im_status: "offline",
|
||||
partner_id: serverState.partnerId,
|
||||
});
|
||||
await waitForSteps(["update_presence"]);
|
||||
});
|
||||
|
||||
test("update presence if IM status changes to away while this device is online", async () => {
|
||||
mockService("bus_service", { send: (type) => asyncStep(type) });
|
||||
localStorage.setItem("presence.lastPresence", Date.now());
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { im_status: "online" });
|
||||
await start();
|
||||
await waitForSteps(["update_presence"]);
|
||||
pyEnv["bus.bus"]._sendone(serverState.partnerId, "bus.bus/im_status_updated", {
|
||||
presence_status: "away",
|
||||
im_status: "away",
|
||||
partner_id: serverState.partnerId,
|
||||
});
|
||||
await waitForSteps(["update_presence"]);
|
||||
});
|
||||
|
||||
test("do not update presence if IM status changes to away while this device is away", async () => {
|
||||
mockService("bus_service", { send: (type) => asyncStep(type) });
|
||||
localStorage.setItem("presence.lastPresence", Date.now() - AWAY_DELAY);
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { im_status: "away" });
|
||||
await start();
|
||||
await waitForSteps(["update_presence"]);
|
||||
pyEnv["bus.bus"]._sendone(serverState.partnerId, "bus.bus/im_status_updated", {
|
||||
presence_status: "away",
|
||||
im_status: "away",
|
||||
partner_id: serverState.partnerId,
|
||||
});
|
||||
await waitForSteps([]);
|
||||
});
|
||||
|
||||
test("do not update presence if other user's IM status changes to away", async () => {
|
||||
mockService("bus_service", { send: (type) => asyncStep(type) });
|
||||
localStorage.setItem("presence.lastPresence", Date.now());
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { im_status: "online" });
|
||||
await start();
|
||||
await waitForSteps(["update_presence"]);
|
||||
pyEnv["bus.bus"]._sendone(serverState.partnerId, "bus.bus/im_status_updated", {
|
||||
presence_status: "away",
|
||||
im_status: "away",
|
||||
partner_id: serverState.publicPartnerId,
|
||||
});
|
||||
await waitForSteps([]);
|
||||
});
|
||||
|
||||
test("update presence when user comes back from away", async () => {
|
||||
mockService("bus_service", {
|
||||
send: (type, payload) => {
|
||||
if (type === "update_presence") {
|
||||
asyncStep(payload.inactivity_period);
|
||||
}
|
||||
},
|
||||
});
|
||||
localStorage.setItem("presence.lastPresence", Date.now() - AWAY_DELAY);
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { im_status: "away" });
|
||||
await start();
|
||||
await waitForSteps([AWAY_DELAY]);
|
||||
localStorage.setItem("presence.lastPresence", Date.now());
|
||||
await waitForSteps([0]);
|
||||
});
|
||||
|
||||
test("update presence when user status changes to away", async () => {
|
||||
mockService("bus_service", {
|
||||
send: (type, payload) => {
|
||||
if (type === "update_presence") {
|
||||
asyncStep(payload.inactivity_period);
|
||||
}
|
||||
},
|
||||
});
|
||||
localStorage.setItem("presence.lastPresence", Date.now());
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { im_status: "online" });
|
||||
await start();
|
||||
await waitForSteps([0]);
|
||||
await advanceTime(AWAY_DELAY);
|
||||
await waitForSteps([AWAY_DELAY]);
|
||||
});
|
||||
|
||||
test("new tab update presence when user comes back from away", async () => {
|
||||
// Tabs notify presence with a debounced update, and the status service skips
|
||||
// duplicates. This test ensures a new tab that never sent presence still issues
|
||||
// its first update (important when old tabs close and new ones replace them).
|
||||
localStorage.setItem("presence.lastPresence", Date.now() - AWAY_DELAY);
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { im_status: "offline" });
|
||||
const tabEnv_1 = await makeMockEnv();
|
||||
patchWithCleanup(tabEnv_1.services.bus_service, {
|
||||
send: (type) => {
|
||||
if (type === "update_presence") {
|
||||
expect.step("update_presence");
|
||||
}
|
||||
},
|
||||
});
|
||||
tabEnv_1.services.bus_service.start();
|
||||
await expect.waitForSteps(["update_presence"]);
|
||||
restoreRegistry(registry);
|
||||
const tabEnv_2 = await makeMockEnv(null, { makeNew: true });
|
||||
patchWithCleanup(tabEnv_2.services.bus_service, {
|
||||
send: (type) => {
|
||||
if (type === "update_presence") {
|
||||
expect.step("update_presence");
|
||||
}
|
||||
},
|
||||
});
|
||||
tabEnv_2.services.bus_service.start();
|
||||
await expect.waitForSteps([]);
|
||||
localStorage.setItem("presence.lastPresence", Date.now()); // Simulate user presence.
|
||||
await expect.waitForSteps(["update_presence", "update_presence"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openDiscuss,
|
||||
scroll,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test, expect } from "@odoo/hoot";
|
||||
import { disableAnimations } from "@odoo/hoot-mock";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
async function assertPinnedPanelUnpinCount(expectedCount) {
|
||||
await contains(".dropdown-item", { text: "Unpin", count: expectedCount });
|
||||
await click(".o-mail-DiscussContent-header button[title='Pinned Messages']");
|
||||
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message", {
|
||||
text: "Test pinned message",
|
||||
});
|
||||
expect(".o-discuss-PinnedMessagesPanel button[title='Unpin']").toHaveCount(expectedCount);
|
||||
}
|
||||
|
||||
test("Pin message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Pinned Messages']");
|
||||
await contains(".o-discuss-PinnedMessagesPanel p", {
|
||||
text: "This channel doesn't have any pinned messages.",
|
||||
});
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item", { text: "Pin" });
|
||||
await click(".modal-footer button", { text: "Yeah, pin it!" });
|
||||
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message", { text: "Hello world!" });
|
||||
});
|
||||
|
||||
test("Unpin message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
pinned_at: "2023-03-30 11:27:11",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Pinned Messages']");
|
||||
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await click(".dropdown-item", { text: "Unpin" });
|
||||
await click(".modal-footer button", { text: "Yes, remove it please" });
|
||||
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message", { count: 0 });
|
||||
});
|
||||
|
||||
test("Open pinned panel from notification", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(":nth-child(1 of .o-mail-Message) [title='Expand']");
|
||||
await click(".dropdown-item", { text: "Pin" });
|
||||
await click(".modal-footer button", { text: "Yeah, pin it!" });
|
||||
await contains(".o-discuss-PinnedMessagesPanel", { count: 0 });
|
||||
await click(".o_mail_notification a", { text: "See all pinned messages" });
|
||||
await contains(".o-discuss-PinnedMessagesPanel");
|
||||
});
|
||||
|
||||
test("Jump to message", async () => {
|
||||
disableAnimations();
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
pinned_at: "2023-04-03 08:15:04",
|
||||
});
|
||||
for (let i = 0; i < 20; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Non Empty Body ".repeat(25),
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
}
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click(".o-mail-DiscussContent-header button[title='Pinned Messages']");
|
||||
await click(".o-discuss-PinnedMessagesPanel a[role='button']", { text: "Jump" });
|
||||
await contains(".o-mail-Thread .o-mail-Message-body", { text: "Hello world!", visible: true });
|
||||
});
|
||||
|
||||
test("Jump to message from notification", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
for (let i = 0; i < 20; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Non Empty Body ".repeat(25),
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
}
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 21 });
|
||||
await click(":nth-child(1 of .o-mail-Message) [title='Expand']");
|
||||
await click(".dropdown-item", { text: "Pin" });
|
||||
await click(".modal-footer button", { text: "Yeah, pin it!" });
|
||||
await contains(".o_mail_notification");
|
||||
await scroll(".o-mail-Thread", "bottom");
|
||||
await contains(".o-mail-Thread", { scroll: "bottom" });
|
||||
await click(".o_mail_notification a", { text: "message" });
|
||||
await contains(".o-mail-Thread", { count: 0, scroll: "bottom" });
|
||||
});
|
||||
|
||||
test("can add reactions from pinned panel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
pinned_at: "2025-10-09 11:15:04",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Message-actions [title='Add a Reaction']");
|
||||
await click(".o-mail-QuickReactionMenu button", { text: "👍" });
|
||||
await contains(".o-mail-MessageReaction", { text: "👍1" });
|
||||
await click(".o-mail-DiscussContent-header button[title='Pinned Messages']");
|
||||
await click(".o-discuss-PinnedMessagesPanel .o-mail-Message [title='Add a Reaction']");
|
||||
await click(".o-mail-QuickReactionMenu button", { text: "👍" });
|
||||
await contains(".o-mail-MessageReaction", { count: 0 });
|
||||
});
|
||||
|
||||
test("Guest user cannot see unpin button", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Test pinned message",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
pinned_at: "2023-03-30 11:27:11",
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { text: "Test pinned message" });
|
||||
expect(".o-mail-Message [title='Expand']").toHaveCount(0);
|
||||
await assertPinnedPanelUnpinCount(0);
|
||||
});
|
||||
|
||||
test("Internal user can see unpin button", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Test pinned message",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
pinned_at: "2023-03-30 11:27:11",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-Message [title='Expand']");
|
||||
await assertPinnedPanelUnpinCount(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
onRpcBefore,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
scroll,
|
||||
SIZES,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, mockTouch, mockUserAgent, test } from "@odoo/hoot";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
import { tick } from "@odoo/hoot-mock";
|
||||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { HIGHLIGHT_CLASS } from "@mail/core/common/message_search_hook";
|
||||
|
||||
defineMailModels();
|
||||
|
||||
test.tags("desktop");
|
||||
test("Should have a search button", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains("[title='Search Messages']");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Should open the search panel when search button is clicked", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("[title='Search Messages']");
|
||||
await contains(".o-mail-SearchMessagesPanel");
|
||||
await contains(".o_searchview");
|
||||
await contains(".o_searchview_input");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Should open the search panel with hotkey 'f'", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await press("alt+f");
|
||||
await contains(".o-mail-SearchMessagesPanel");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await click("button[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search should be hightlighted", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(`.o-mail-SearchMessagesPanel .o-mail-Message .${HIGHLIGHT_CLASS}`);
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a starred message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
starred_partner_ids: [serverState.partnerId],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss("mail.box_starred");
|
||||
await contains(".o-mail-Message");
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a message in inbox", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
needaction: true,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss("mail.box_inbox");
|
||||
await contains(".o-mail-Message");
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a message in history (desktop)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
needaction: false,
|
||||
});
|
||||
pyEnv["mail.notification"].create({
|
||||
is_read: true,
|
||||
mail_message_id: messageId,
|
||||
notification_status: "sent",
|
||||
notification_type: "inbox",
|
||||
res_partner_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss("mail.box_history");
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-SearchMessagesPanel .o-mail-MessageCard-jump");
|
||||
await contains(".o-mail-Thread .o-mail-Message.o-highlighted");
|
||||
});
|
||||
|
||||
test.tags("mobile");
|
||||
test("Search a message in history (mobile)", async () => {
|
||||
mockTouch(true);
|
||||
mockUserAgent("android");
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
const messageId = pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
needaction: false,
|
||||
});
|
||||
pyEnv["mail.notification"].create({
|
||||
is_read: true,
|
||||
mail_message_id: messageId,
|
||||
notification_status: "sent",
|
||||
notification_type: "inbox",
|
||||
res_partner_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss("mail.box_history");
|
||||
await contains(".o-mail-Thread");
|
||||
await click("[title='Search Messages']");
|
||||
await contains(".o-mail-SearchMessagesPanel");
|
||||
await contains(".o-mail-Thread", { count: 0 });
|
||||
await insertText(".o_searchview_input", "message");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
await click(".o-mail-MessageCard-jump");
|
||||
await contains(".o-mail-Thread");
|
||||
await contains(".o-mail-SearchMessagesPanel", { count: 0 });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Should close the search panel when search button is clicked again", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("[title='Search Messages']");
|
||||
await click("[title='Close Search']");
|
||||
await contains(".o-mail-SearchMessagesPanel");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a message in 60 messages should return 30 message first", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
for (let i = 0; i < 60; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
}
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message", { count: 30 });
|
||||
// give enough time to useVisible to potentially load more (unexpected) messages
|
||||
await tick();
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message", { count: 30 });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Scrolling to the bottom should load more searched message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
for (let i = 0; i < 90; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
}
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message", { count: 30 });
|
||||
await scroll(".o-mail-SearchMessagesPanel .o-mail-ActionPanel", "bottom");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message", { count: 60 });
|
||||
// give enough time to useVisible to potentially load more (unexpected) messages
|
||||
await tick();
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message", { count: 60 });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Editing the searched term should not edit the current searched term", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
for (let i = 0; i < 60; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
}
|
||||
onRpcBefore("/discuss/channel/messages", (args) => {
|
||||
if (args.search_term) {
|
||||
const { search_term } = args;
|
||||
expect(search_term).toBe("message");
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMemberList"); // wait for auto-open of this panel
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await insertText(".o_searchview_input", "test");
|
||||
await scroll(".o-mail-SearchMessagesPanel .o-mail-ActionPanel", "bottom");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a message containing round brackets", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a (message)",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await click("button[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "(message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Search a message containing single quotes", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "I can't do it");
|
||||
await click(".o-sendMessageActive:enabled");
|
||||
await contains(".o-mail-Message");
|
||||
await click("button[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "can't");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessagesPanel .o-mail-Message");
|
||||
});
|
||||
|
|
@ -0,0 +1,906 @@
|
|||
import { insertText as htmlInsertText } from "@html_editor/../tests/_helpers/user_actions";
|
||||
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
onRpcBefore,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { advanceTime, mockDate } from "@odoo/hoot-mock";
|
||||
import {
|
||||
asyncStep,
|
||||
Command,
|
||||
getService,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
withUser,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { Store } from "@mail/core/common/store_service";
|
||||
import { LONG_TYPING, SHORT_TYPING } from "@mail/discuss/typing/common/composer_patch";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test('[text composer] receive other member typing status "is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
// simulate receive typing notification from demo
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test('receive other member typing status "is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
});
|
||||
|
||||
test('[text composer] receive other member typing status "is typing" then "no longer is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
// simulate receive typing notification from demo "is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
// simulate receive typing notification from demo "is no longer typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test('receive other member typing status "is typing" then "no longer is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
});
|
||||
|
||||
test('[text composer] assume other member typing status becomes "no longer is typing" after long without any updated typing status', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
// simulate receive typing notification from demo "is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
await advanceTime(Store.OTHER_LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test('assume other member typing status becomes "no longer is typing" after long without any updated typing status', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
await advanceTime(Store.OTHER_LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
});
|
||||
|
||||
test('"is typing" timeout should work even when 2 notify_typing happen at the exact same time', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
mockDate("2024-01-01 12:00:00");
|
||||
await withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
await advanceTime(Store.OTHER_LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing..." });
|
||||
});
|
||||
|
||||
test('[text composer] other member typing status "is typing" refreshes of assuming no longer typing', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
// simulate receive typing notification from demo "is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
// simulate receive typing notification from demo "is typing" again after long time.
|
||||
await advanceTime(LONG_TYPING);
|
||||
await withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await advanceTime(LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
await advanceTime(Store.OTHER_LONG_TYPING - LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test('other member typing status "is typing" refreshes of assuming no longer typing', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
await advanceTime(LONG_TYPING);
|
||||
await withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await advanceTime(LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { text: "Demo is typing..." });
|
||||
await advanceTime(Store.OTHER_LONG_TYPING - LONG_TYPING);
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
});
|
||||
|
||||
test('[text composer] receive several other members typing status "is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [userId_1, userId_2, userId_3] = pyEnv["res.users"].create([
|
||||
{ name: "Other 10" },
|
||||
{ name: "Other 11" },
|
||||
{ name: "Other 12" },
|
||||
]);
|
||||
const [partnerId_1, partnerId_2, partnerId_3] = pyEnv["res.partner"].create([
|
||||
{ name: "Other 10", user_ids: [userId_1] },
|
||||
{ name: "Other 11", user_ids: [userId_2] },
|
||||
{ name: "Other 12", user_ids: [userId_3] },
|
||||
]);
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
Command.create({ partner_id: partnerId_3 }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
// simulate receive typing notification from other 10 (is typing)
|
||||
withUser(userId_1, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 10 is typing..." });
|
||||
// simulate receive typing notification from other 11 (is typing)
|
||||
withUser(userId_2, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 10 and Other 11 are typing..." });
|
||||
// simulate receive typing notification from other 12 (is typing)
|
||||
withUser(userId_3, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 10, Other 11 and more are typing..." });
|
||||
// simulate receive typing notification from other 10 (no longer is typing)
|
||||
withUser(userId_1, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 11 and Other 12 are typing..." });
|
||||
// simulate receive typing notification from other 10 (is typing again)
|
||||
withUser(userId_1, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 11, Other 12 and more are typing..." });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test('receive several other members typing status "is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [userId_1, userId_2, userId_3] = pyEnv["res.users"].create([
|
||||
{ name: "Other 10" },
|
||||
{ name: "Other 11" },
|
||||
{ name: "Other 12" },
|
||||
]);
|
||||
const [partnerId_1, partnerId_2, partnerId_3] = pyEnv["res.partner"].create([
|
||||
{ name: "Other 10", user_ids: [userId_1] },
|
||||
{ name: "Other 11", user_ids: [userId_2] },
|
||||
{ name: "Other 12", user_ids: [userId_3] },
|
||||
]);
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId_1 }),
|
||||
Command.create({ partner_id: partnerId_2 }),
|
||||
Command.create({ partner_id: partnerId_3 }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
withUser(userId_1, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 10 is typing..." });
|
||||
withUser(userId_2, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 10 and Other 11 are typing..." });
|
||||
withUser(userId_3, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 10, Other 11 and more are typing..." });
|
||||
withUser(userId_1, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 11 and Other 12 are typing..." });
|
||||
withUser(userId_1, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Other 11, Other 12 and more are typing..." });
|
||||
});
|
||||
|
||||
test("[text composer] current partner notify is typing to other thread members", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) => {
|
||||
if (!testEnded) {
|
||||
asyncStep(`notify_typing:${args.is_typing}`);
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("current partner notify is typing to other thread members", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) => {
|
||||
if (!testEnded) {
|
||||
asyncStep(`notify_typing:${args.is_typing}`);
|
||||
}
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test("[text composer] current partner notify is typing again to other members for long continuous typing", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) => {
|
||||
if (!testEnded) {
|
||||
asyncStep(`notify_typing:${args.is_typing}`);
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
// simulate current partner typing a character for a long time.
|
||||
const elapseTickTime = SHORT_TYPING / 2;
|
||||
for (let i = 0; i <= LONG_TYPING / elapseTickTime; i++) {
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await advanceTime(elapseTickTime);
|
||||
}
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("current partner notify is typing again to other members for long continuous typing", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) => {
|
||||
if (!testEnded) {
|
||||
asyncStep(`notify_typing:${args.is_typing}`);
|
||||
}
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable");
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
const elapseTickTime = SHORT_TYPING / 2;
|
||||
for (let i = 0; i <= LONG_TYPING / elapseTickTime; i++) {
|
||||
await htmlInsertText(editor, "a");
|
||||
await advanceTime(elapseTickTime);
|
||||
}
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test("[text composer] current partner notify no longer is typing to thread members after 5 seconds inactivity", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) =>
|
||||
asyncStep(`notify_typing:${args.is_typing}`)
|
||||
);
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await advanceTime(Store.FETCH_DATA_DEBOUNCE_DELAY);
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
await advanceTime(SHORT_TYPING);
|
||||
await waitForSteps(["notify_typing:false"]);
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("current partner notify no longer is typing to thread members after 5 seconds inactivity", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) =>
|
||||
asyncStep(`notify_typing:${args.is_typing}`)
|
||||
);
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
await advanceTime(SHORT_TYPING);
|
||||
await waitForSteps(["notify_typing:false"]);
|
||||
});
|
||||
|
||||
test("[text composer] current partner is typing should not translate on textual typing status", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) => {
|
||||
if (!testEnded) {
|
||||
asyncStep(`notify_typing:${args.is_typing}`);
|
||||
}
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("current partner is typing should not translate on textual typing status", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
|
||||
let testEnded = false;
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) => {
|
||||
if (!testEnded) {
|
||||
asyncStep(`notify_typing:${args.is_typing}`);
|
||||
}
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
await contains(".o-discuss-Typing");
|
||||
await contains(".o-discuss-Typing", { count: 0, text: "Demo is typing...)" });
|
||||
testEnded = true;
|
||||
});
|
||||
|
||||
test("[text composer] chat: correspondent is typing", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
im_status: "online",
|
||||
name: "Demo",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarChannel .o-mail-DiscussSidebarChannel-threadIcon");
|
||||
await contains(".fa-circle.text-success");
|
||||
// simulate receive typing notification from demo "is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing-icon[title='Demo is typing...']");
|
||||
// simulate receive typing notification from demo "no longer is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains(".fa-circle.text-success");
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("chat: correspondent is typing", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
im_status: "online",
|
||||
name: "Demo",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarChannel .o-mail-DiscussSidebarChannel-threadIcon");
|
||||
await contains(".fa-circle.text-success");
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing-icon[title='Demo is typing...']");
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains(".fa-circle.text-success");
|
||||
});
|
||||
|
||||
test("[text composer] chat: correspondent is typing in chat window", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
im_status: "online",
|
||||
name: "Demo",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem");
|
||||
await contains("[title='Demo is typing...']", { count: 0 });
|
||||
// simulate receive typing notification from demo "is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains("[title='Demo is typing...']", { count: 2 }); // icon in header & text above composer
|
||||
// simulate receive typing notification from demo "no longer is typing"
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains("[title='Demo is typing...']", { count: 0 });
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("chat: correspondent is typing in chat window", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
im_status: "online",
|
||||
name: "Demo",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem");
|
||||
await contains("[title='Demo is typing...']", { count: 0 });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains("[title='Demo is typing...']", { count: 2 });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: false,
|
||||
})
|
||||
);
|
||||
await contains("[title='Demo is typing...']", { count: 0 });
|
||||
});
|
||||
|
||||
test("[text composer] show typing in member list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Other 10" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Other 10", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMember", { count: 2 });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-ChannelMemberList [title='Other 10 is typing...']");
|
||||
withUser(serverState.userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(
|
||||
`.o-discuss-ChannelMemberList [title='${serverState.partnerName} is typing...']`
|
||||
);
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("show typing in member list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Other 10" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Other 10", user_ids: [userId] });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "channel",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-ChannelMember", { count: 2 });
|
||||
withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-ChannelMemberList [title='Other 10 is typing...']");
|
||||
withUser(serverState.userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(
|
||||
`.o-discuss-ChannelMemberList [title='${serverState.partnerName} is typing...']`
|
||||
);
|
||||
});
|
||||
|
||||
test("[text composer] switching to another channel triggers notify_typing to stop", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
im_status: "online",
|
||||
name: "Demo",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const chatId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
pyEnv["discuss.channel"].create({ name: "general" });
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) =>
|
||||
asyncStep(`notify_typing:${args.is_typing}`)
|
||||
);
|
||||
await start();
|
||||
await openDiscuss(chatId);
|
||||
await insertText(".o-mail-Composer-input", "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "general" });
|
||||
await advanceTime(SHORT_TYPING / 2);
|
||||
await waitForSteps(["notify_typing:false"]);
|
||||
});
|
||||
|
||||
test.tags("html composer");
|
||||
test("switching to another channel triggers notify_typing to stop", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "Demo" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
im_status: "online",
|
||||
name: "Demo",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const chatId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
pyEnv["discuss.channel"].create({ name: "general" });
|
||||
onRpcBefore("/discuss/channel/notify_typing", (args) =>
|
||||
asyncStep(`notify_typing:${args.is_typing}`)
|
||||
);
|
||||
await start();
|
||||
const composerService = getService("mail.composer");
|
||||
composerService.setHtmlComposer();
|
||||
await openDiscuss(chatId);
|
||||
await contains(".o-mail-Composer-html.odoo-editor-editable");
|
||||
const editor = {
|
||||
document,
|
||||
editable: document.querySelector(".o-mail-Composer-html.odoo-editor-editable"),
|
||||
};
|
||||
await htmlInsertText(editor, "a");
|
||||
await waitForSteps(["notify_typing:true"]);
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "general" });
|
||||
await advanceTime(SHORT_TYPING / 2);
|
||||
await waitForSteps(["notify_typing:false"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
mockGetMedia,
|
||||
openDiscuss,
|
||||
patchVoiceMessageAudio,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, globals, test } from "@odoo/hoot";
|
||||
import { Deferred, mockDate } from "@odoo/hoot-mock";
|
||||
import { Command, patchWithCleanup, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { loadLamejs } from "@mail/discuss/voice_message/common/voice_message_service";
|
||||
import { VoicePlayer } from "@mail/discuss/voice_message/common/voice_player";
|
||||
import { patchable } from "@mail/discuss/voice_message/common/voice_recorder";
|
||||
import { Mp3Encoder } from "@mail/discuss/voice_message/common/mp3_encoder";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("make voice message in chat", async () => {
|
||||
const file = new File([new Uint8Array(25000)], "test.mp3", { type: "audio/mp3" });
|
||||
const voicePlayerDrawing = new Deferred();
|
||||
patchWithCleanup(Mp3Encoder.prototype, {
|
||||
encode() {},
|
||||
finish() {
|
||||
return Array(500).map(() => new Int8Array());
|
||||
},
|
||||
});
|
||||
patchWithCleanup(patchable, { makeFile: () => file });
|
||||
patchWithCleanup(VoicePlayer.prototype, {
|
||||
async drawWave(...args) {
|
||||
voicePlayerDrawing.resolve();
|
||||
return super.drawWave(...args);
|
||||
},
|
||||
async fetchFile() {
|
||||
return super.fetchFile("/mail/static/src/audio/call-invitation.mp3");
|
||||
},
|
||||
_fetch(url) {
|
||||
if (url.includes("call-invitation.mp3")) {
|
||||
const realFetch = globals.fetch;
|
||||
return realFetch(...arguments);
|
||||
}
|
||||
return super._fetch(...arguments);
|
||||
},
|
||||
});
|
||||
mockGetMedia();
|
||||
const resources = patchVoiceMessageAudio();
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ partner_id: partnerId }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await loadLamejs(); // simulated AudioProcess.process() requires lamejs fully loaded
|
||||
await click(".o-mail-Composer button[title='More Actions']");
|
||||
await contains(".dropdown-item:contains('Voice Message')");
|
||||
mockDate("2023-07-31 13:00:00");
|
||||
await click(".dropdown-item:contains('Voice Message')");
|
||||
await contains(".o-mail-VoiceRecorder", { text: "00 : 00" });
|
||||
/**
|
||||
* Simulate 10 sec elapsed.
|
||||
* `patchDate` does not freeze the time, it merely changes the value of "now" at the time it was
|
||||
* called. The code of click following the first `patchDate` doesn't actually happen at the time
|
||||
* that was specified, but few miliseconds later (8 ms on my machine).
|
||||
* The process following the next `patchDate` is intended to be between 10s and 11s later than
|
||||
* the click, because the test wants to assert a 10 sec counter, and the two dates are
|
||||
* substracted and then rounded down in the code (it means absolute values are irrelevant here).
|
||||
* The problem with aiming too close to a 10s difference is that if the click is longer than
|
||||
* the following process, it will round down to 9s.
|
||||
* The problem with aiming too close to a 11s difference is that if the click is shorter than
|
||||
* the following process, it will round down to 11s.
|
||||
* The best bet is therefore to use 10s + 500ms difference.
|
||||
*/
|
||||
mockDate("2023-07-31 13:00:10.500");
|
||||
// simulate some microphone data
|
||||
resources.audioProcessor.process([[new Float32Array(128)]]);
|
||||
await contains(".o-mail-VoiceRecorder", { text: "00 : 10" });
|
||||
await click(".o-mail-Composer button[title='Stop Recording']");
|
||||
await contains(".o-mail-VoicePlayer");
|
||||
// wait for audio stream decode + drawing of waves
|
||||
await voicePlayerDrawing;
|
||||
await contains(".o-mail-VoicePlayer button[title='Play']");
|
||||
await contains(".o-mail-VoicePlayer canvas", { count: 2 }); // 1 for global waveforms, 1 for played waveforms
|
||||
await contains(".o-mail-VoicePlayer", { text: "00 : 03" }); // duration of call-invitation_.mp3
|
||||
await click(".o-mail-Composer button[title='More Actions']");
|
||||
await contains(".dropdown-item:contains('Attach Files')"); // check menu loaded
|
||||
await contains(".dropdown-item:contains('Voice Message')", { count: 0 }); // only 1 voice message at a time
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue