summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/test
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/matrix/test')
-rw-r--r--comm/chat/protocols/matrix/test/head.js291
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixAccount.js399
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixCommands.js177
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixMessage.js441
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixMessageContent.js652
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixPowerLevels.js204
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixRoom.js928
-rw-r--r--comm/chat/protocols/matrix/test/test_matrixTextForEvent.js834
-rw-r--r--comm/chat/protocols/matrix/test/test_roomTypeChange.js54
-rw-r--r--comm/chat/protocols/matrix/test/xpcshell.ini12
10 files changed, 3992 insertions, 0 deletions
diff --git a/comm/chat/protocols/matrix/test/head.js b/comm/chat/protocols/matrix/test/head.js
new file mode 100644
index 0000000000..ecb485ec6d
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/head.js
@@ -0,0 +1,291 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+);
+const { MatrixProtocol } = ChromeUtils.importESModule(
+ "resource:///modules/matrix.sys.mjs"
+);
+const { MatrixRoom, MatrixAccount, MatrixMessage } = ChromeUtils.importESModule(
+ "resource:///modules/matrixAccount.sys.mjs"
+);
+var { MatrixSDK } = ChromeUtils.importESModule(
+ "resource:///modules/matrix-sdk.sys.mjs"
+);
+function loadMatrix() {
+ IMServices.conversations.initConversations();
+}
+
+/**
+ * Get a MatrixRoom instance with a mocked client.
+ *
+ * @param {boolean} isMUC
+ * @param {string} [name="#test:example.com"]
+ * @param {(any, string) => any?|object} [clientHandler]
+ * @returns {MatrixRoom}
+ */
+function getRoom(
+ isMUC,
+ name = "#test:example.com",
+ clientHandler = () => undefined,
+ account
+) {
+ if (!account) {
+ account = getAccount(clientHandler);
+ }
+ const room = getClientRoom(name, clientHandler, account._client);
+ const conversation = new MatrixRoom(account, isMUC, name);
+ conversation.initRoom(room);
+ return conversation;
+}
+
+/**
+ *
+ * @param {string} roomId
+ * @param {(any, string) => any?|object} clientHandler
+ * @param {MatrixClient} client
+ * @returns {Room}
+ */
+function getClientRoom(roomId, clientHandler, client) {
+ const room = new Proxy(
+ {
+ roomId,
+ name: roomId,
+ tags: {},
+ getJoinedMembers() {
+ return [];
+ },
+ getAvatarUrl() {
+ return "";
+ },
+ getLiveTimeline() {
+ return {
+ getState() {
+ return {
+ getStateEvents() {
+ return [];
+ },
+ };
+ },
+ };
+ },
+ isSpaceRoom() {
+ return false;
+ },
+ getLastActiveTimestamp() {
+ return Date.now();
+ },
+ getMyMembership() {
+ return "join";
+ },
+ getAccountData(key) {
+ return null;
+ },
+ getUnfilteredTimelineSet() {
+ return {
+ getLiveTimeline() {
+ return {
+ getEvents() {
+ return [];
+ },
+ getBaseIndex() {
+ return 0;
+ },
+ getNeighbouringTimeline() {
+ return null;
+ },
+ getPaginationToken() {
+ return "";
+ },
+ };
+ },
+ };
+ },
+ guessDMUserId() {
+ return "@other:example.com";
+ },
+ loadMembersIfNeeded() {
+ return Promise.resolve();
+ },
+ getEncryptionTargetMembers() {
+ return Promise.resolve([]);
+ },
+ },
+ makeProxyHandler(clientHandler)
+ );
+ client._rooms.set(roomId, room);
+ return room;
+}
+
+/**
+ *
+ * @param {(any, string) => any?|object} clientHandler
+ * @returns {MatrixAccount}
+ */
+function getAccount(clientHandler) {
+ const account = new MatrixAccount(Object.create(MatrixProtocol.prototype), {
+ logDebugMessage(message) {
+ account._errors.push(message.message);
+ },
+ });
+ account._errors = [];
+ account._client = new Proxy(
+ {
+ _rooms: new Map(),
+ credentials: {
+ userId: "@user:example.com",
+ },
+ getHomeserverUrl() {
+ return "https://example.com";
+ },
+ getRoom(roomId) {
+ return this._rooms.get(roomId);
+ },
+ async joinRoom(roomId) {
+ if (!this._rooms.has(roomId)) {
+ getClientRoom(roomId, clientHandler, this);
+ }
+ return this._rooms.get(roomId);
+ },
+ setAccountData(field, data) {},
+ async createRoom(spec) {
+ const roomId =
+ "!" + spec.name + ":example.com" || "!newroom:example.com";
+ if (!this._rooms.has(roomId)) {
+ getClientRoom(roomId, clientHandler, this);
+ }
+ return {
+ room_id: roomId,
+ };
+ },
+ getRooms() {
+ return Array.from(this._rooms.values());
+ },
+ getVisibleRooms() {
+ return Array.from(this._rooms.values());
+ },
+ isCryptoEnabled() {
+ return false;
+ },
+ getPushActionsForEvent() {
+ return {};
+ },
+ leave(roomId) {
+ this._rooms.delete(roomId);
+ },
+ downloadKeys() {
+ return Promise.resolve({});
+ },
+ getUser(userId) {
+ return {
+ displayName: userId,
+ userId,
+ };
+ },
+ getStoredDevicesForUser() {
+ return [];
+ },
+ isRoomEncrypted(roomId) {
+ return false;
+ },
+ },
+ makeProxyHandler(clientHandler)
+ );
+ return account;
+}
+
+/**
+ * @param {(any, string) => any?|object} [clientHandler]
+ * @returns {object}
+ */
+function makeProxyHandler(clientHandler) {
+ return {
+ get(target, key, receiver) {
+ if (typeof clientHandler === "function") {
+ const value = clientHandler(target, key);
+ if (value) {
+ return value;
+ }
+ } else if (clientHandler.hasOwnProperty(key)) {
+ return clientHandler[key];
+ }
+ return target[key];
+ },
+ };
+}
+
+/**
+ * Build a MatrixEvent like object from a plain object.
+ *
+ * @param {{ type: MatrixSDK.EventType, content: object, sender: string, id: number, redacted: boolean, time: Date }} eventSpec - Data the event holds.
+ * @returns {MatrixEvent}
+ */
+function makeEvent(eventSpec = {}) {
+ const time = eventSpec.time || new Date();
+ return {
+ isRedacted() {
+ return eventSpec.redacted || false;
+ },
+ getType() {
+ return eventSpec.type;
+ },
+ getContent() {
+ return eventSpec.content || {};
+ },
+ getPrevContent() {
+ return eventSpec.prevContent || {};
+ },
+ getWireContent() {
+ return eventSpec.content;
+ },
+ getSender() {
+ return eventSpec.sender;
+ },
+ getDate() {
+ return time;
+ },
+ sender: {
+ name: "foo bar",
+ getAvatarUrl() {
+ return "https://example.com/avatar";
+ },
+ },
+ getId() {
+ return eventSpec.id || 0;
+ },
+ isEncrypted() {
+ return (
+ eventSpec.type == MatrixSDK.EventType.RoomMessageEncrypted ||
+ eventSpec.isEncrypted
+ );
+ },
+ shouldAttemptDecryption() {
+ return Boolean(eventSpec.shouldDecrypt);
+ },
+ isBeingDecrypted() {
+ return Boolean(eventSpec.decrypting);
+ },
+ isDecryptionFailure() {
+ return eventSpec.content?.msgtype == "m.bad.encrypted";
+ },
+ isRedaction() {
+ return eventSpec.type == MatrixSDK.EventType.RoomRedaction;
+ },
+ getRedactionEvent() {
+ return eventSpec.redaction;
+ },
+ target: eventSpec.target,
+ replyEventId:
+ eventSpec.content?.["m.relates_to"]?.["m.in_reply_to"]?.event_id,
+ threadRootId: eventSpec.threadRootId || null,
+ getRoomId() {
+ return eventSpec.roomId || "!test:example.com";
+ },
+ status: eventSpec.status || null,
+ _listeners: {},
+ once(event, listener) {
+ this._listeners[event] = listener;
+ },
+ };
+}
diff --git a/comm/chat/protocols/matrix/test/test_matrixAccount.js b/comm/chat/protocols/matrix/test/test_matrixAccount.js
new file mode 100644
index 0000000000..e49d150a21
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixAccount.js
@@ -0,0 +1,399 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+loadMatrix();
+
+add_task(function test_getConversationById() {
+ const mockAccount = {
+ roomList: new Map(),
+ _pendingRoomAliases: new Map(),
+ };
+ mockAccount.roomList.set("foo", "bar");
+ mockAccount._pendingRoomAliases.set("lorem", "ipsum");
+
+ equal(MatrixAccount.prototype.getConversationById.call(mockAccount), null);
+ equal(
+ MatrixAccount.prototype.getConversationById.call(mockAccount, "foo"),
+ "bar"
+ );
+ equal(
+ MatrixAccount.prototype.getConversationById.call(mockAccount, "lorem"),
+ "ipsum"
+ );
+});
+
+add_task(function test_getConversationByIdOrAlias() {
+ const mockAccount = {
+ getConversationById(id) {
+ if (id === "foo") {
+ return "bar";
+ }
+ if (id === "_lorem") {
+ return "ipsum";
+ }
+ return null;
+ },
+ _client: {
+ getRoom(id) {
+ if (id === "lorem") {
+ return {
+ roomId: "_" + id,
+ };
+ }
+ return null;
+ },
+ },
+ };
+
+ equal(
+ MatrixAccount.prototype.getConversationByIdOrAlias.call(mockAccount),
+ null
+ );
+ equal(
+ MatrixAccount.prototype.getConversationByIdOrAlias.call(mockAccount, "foo"),
+ "bar"
+ );
+ equal(
+ MatrixAccount.prototype.getConversationByIdOrAlias.call(
+ mockAccount,
+ "lorem"
+ ),
+ "ipsum"
+ );
+ equal(
+ MatrixAccount.prototype.getConversationByIdOrAlias.call(mockAccount, "baz"),
+ null
+ );
+});
+
+add_task(async function test_getGroupConversation() {
+ registerCleanupFunction(() => {
+ const conversations = IMServices.conversations.getConversations();
+ for (const conversation of conversations) {
+ try {
+ conversation.forget();
+ } catch {}
+ }
+ });
+
+ let allowedGetRoomIds = new Set(["baz"]);
+ const mockAccount = getAccount({
+ getRoom(roomId) {
+ if (this._rooms.has(roomId)) {
+ return this._rooms.get(roomId);
+ }
+ if (allowedGetRoomIds.has(roomId)) {
+ return getClientRoom("baz", {}, mockAccount._client);
+ }
+ return null;
+ },
+ async joinRoom(roomId) {
+ if (roomId === "lorem") {
+ allowedGetRoomIds.add(roomId);
+ return getClientRoom(roomId, {}, mockAccount._client);
+ } else if (roomId.endsWith(":example.com")) {
+ const error = new Error("not found");
+ error.errcode = "M_NOT_FOUND";
+ throw error;
+ }
+ throw new Error("Could not join");
+ },
+ getDomain() {
+ return "example.com";
+ },
+ getHomeserverUrl() {
+ return "https://example.com";
+ },
+ leave(roomId) {
+ this._rooms.delete(roomId);
+ mockAccount.left = true;
+ },
+ });
+ const fooRoom = getRoom(true, "bar", {}, mockAccount);
+ mockAccount.roomList.set("foo", fooRoom);
+
+ equal(mockAccount.getGroupConversation(""), null, "No room with empty ID");
+ equal(
+ mockAccount.getGroupConversation("foo").name,
+ "bar",
+ "Room with expected name"
+ );
+ fooRoom.close();
+
+ const existingRoom = mockAccount.getGroupConversation("baz");
+ await existingRoom.waitForRoom();
+ strictEqual(existingRoom, mockAccount.roomList.get("baz"));
+ ok(!existingRoom.joining, "Not joining existing room");
+ existingRoom.close();
+
+ const joinedRoom = mockAccount.getGroupConversation("lorem");
+ ok(joinedRoom.joining, "joining room");
+ allowedGetRoomIds.add("lorem");
+ await joinedRoom.waitForRoom();
+ strictEqual(joinedRoom, mockAccount.roomList.get("lorem"));
+ ok(!joinedRoom.joining, "Joined room");
+ joinedRoom.close();
+
+ const createdRoom = mockAccount.getGroupConversation("#ipsum:example.com");
+ ok(createdRoom.joining, "Joining new room");
+ await createdRoom.waitForRoom();
+ ok(!createdRoom.joining, "Joined new room");
+ strictEqual(createdRoom, mockAccount.roomList.get("!ipsum:example.com"));
+ // Wait for catchup to complete.
+ await TestUtils.waitForTick();
+ createdRoom.close();
+
+ const roomAlreadyBeingCreated =
+ mockAccount.getGroupConversation("#lorem:example.com");
+ ok(
+ roomAlreadyBeingCreated.joining,
+ "Joining room that is about to get replaced"
+ );
+ const pendingRoom = getRoom(true, "hi", {}, mockAccount);
+ mockAccount._pendingRoomAliases.set("#lorem:example.com", pendingRoom);
+ await roomAlreadyBeingCreated.waitForRoom();
+ ok(!roomAlreadyBeingCreated.joining, "Not joining replaced room");
+ ok(roomAlreadyBeingCreated._replacedBy, "Room got replaced");
+ pendingRoom.forget();
+
+ const missingLocalRoom =
+ mockAccount.getGroupConversation("!ipsum:example.com");
+ await TestUtils.waitForTick();
+ ok(!missingLocalRoom.joining, "Not joining missing room");
+ ok(mockAccount.left, "Left missing room");
+
+ mockAccount.left = false;
+ const unjoinableRemoteRoom =
+ mockAccount.getGroupConversation("#test:matrix.org");
+ await TestUtils.waitForTick();
+ ok(!unjoinableRemoteRoom.joining, "Not joining unjoinable room");
+ ok(mockAccount.left, "Left unjoinable room");
+});
+
+add_task(async function test_joinChat() {
+ const roomId = "!foo:example.com";
+ const conversation = {
+ waitForRoom() {
+ return Promise.resolve();
+ },
+ checkForUpdate() {
+ this.checked = true;
+ },
+ };
+ const mockAccount = {
+ getGroupConversation(id) {
+ this.groupConv = id;
+ return conversation;
+ },
+ };
+ const components = {
+ getValue(key) {
+ if (key === "roomIdOrAlias") {
+ return roomId;
+ }
+ ok(false, "Unknown chat room field");
+ return null;
+ },
+ };
+
+ const conv = MatrixAccount.prototype.joinChat.call(mockAccount, components);
+ equal(mockAccount.groupConv, roomId);
+ strictEqual(conv, conversation);
+ await Promise.resolve();
+ ok(conversation.checked);
+});
+
+add_task(async function test_getDMRoomIdsForUserId() {
+ const account = getAccount({
+ getRoom(roomId) {
+ if (roomId === "!invalid:example.com") {
+ return null;
+ }
+ return getClientRoom(
+ roomId,
+ {
+ isSpaceRoom() {
+ return roomId === "!space:example.com";
+ },
+ getMyMembership() {
+ return roomId === "!left:example.com" ? "leave" : "join";
+ },
+ getMember(userId) {
+ return {
+ membership: "invite",
+ };
+ },
+ },
+ account._client
+ );
+ },
+ });
+ account._userToRoom = {
+ "@test:example.com": [
+ "!asdf:example.com",
+ "!space:example.com",
+ "!left:example.com",
+ "!invalid:example.com",
+ ],
+ };
+ const invalid = account.getDMRoomIdsForUserId("@nouser:example.com");
+ ok(Array.isArray(invalid));
+ equal(invalid.length, 0);
+
+ const rooms = account.getDMRoomIdsForUserId("@test:example.com");
+ ok(Array.isArray(rooms));
+ equal(rooms.length, 1);
+ equal(rooms[0], "!asdf:example.com");
+});
+
+add_task(async function test_invitedToDMIn_deny() {
+ const dmRoomId = "!test:example.com";
+ let leftRoom = false;
+ const account = getAccount({
+ leave(roomId) {
+ equal(roomId, dmRoomId);
+ leftRoom = true;
+ },
+ });
+ const room = getClientRoom(
+ dmRoomId,
+ {
+ getDMInviter() {
+ return "@other:example.com";
+ },
+ },
+ account._client
+ );
+ const requestObserver = TestUtils.topicObserved(
+ "buddy-authorization-request"
+ );
+ account.invitedToDM(room);
+ const [request] = await requestObserver;
+ request.QueryInterface(Ci.prplIBuddyRequest);
+ equal(request.userName, "@other:example.com");
+ request.deny();
+ ok(leftRoom);
+});
+
+add_task(async function test_nameIsMXID() {
+ const account = getAccount();
+ account.imAccount.name = "@test:example.com";
+ ok(account.nameIsMXID);
+ account.imAccount.name = "@test:example.com:8443";
+ ok(account.nameIsMXID);
+ account.imAccount.name = "test:example.com";
+ ok(!account.nameIsMXID);
+ account.imAccount.name = "test";
+ ok(!account.nameIsMXID);
+});
+
+add_task(async function test_invitedToChat_deny() {
+ const chatRoomId = "!test:xample.com";
+ let leftRoom = false;
+ const account = getAccount({
+ leave(roomId) {
+ equal(roomId, chatRoomId);
+ leftRoom = true;
+ return Promise.resolve();
+ },
+ });
+ const room = getClientRoom(
+ chatRoomId,
+ {
+ getCanonicalAlias() {
+ return "#foo:example.com";
+ },
+ },
+ account._client
+ );
+ const requestObserver = TestUtils.topicObserved("conv-authorization-request");
+ account.invitedToChat(room);
+ const [request] = await requestObserver;
+ request.QueryInterface(Ci.prplIChatRequest);
+ equal(request.conversationName, "#foo:example.com");
+ ok(request.canDeny);
+ request.deny();
+ ok(leftRoom);
+});
+
+add_task(async function test_invitedToChat_cannotDenyServerNotice() {
+ const chatRoomId = "!test:xample.com";
+ const account = getAccount({});
+ const room = getClientRoom(
+ chatRoomId,
+ {
+ getCanonicalAlias() {
+ return "#foo:example.com";
+ },
+ tags: {
+ "m.server_notice": true,
+ },
+ },
+ account._client
+ );
+ console.log(room.tags);
+ const requestObserver = TestUtils.topicObserved("conv-authorization-request");
+ account.invitedToChat(room);
+ const [request] = await requestObserver;
+ request.QueryInterface(Ci.prplIChatRequest);
+ equal(request.conversationName, "#foo:example.com");
+ ok(!request.canDeny);
+});
+
+add_task(async function test_deleteAccount() {
+ let clientLoggedIn = true;
+ let storesCleared;
+ let storesPromise = new Promise(resolve => {
+ storesCleared = resolve;
+ });
+ let stopped = false;
+ let removedListeners;
+ const account = getAccount({
+ isLoggedIn() {
+ return true;
+ },
+ logout() {
+ clientLoggedIn = false;
+ return Promise.resolve();
+ },
+ clearStores() {
+ storesCleared();
+ },
+ stopClient() {
+ stopped = true;
+ },
+ removeAllListeners(type) {
+ removedListeners = type;
+ },
+ });
+ const conv = account.getGroupConversation("example");
+ await conv.waitForRoom();
+ const timeout = setTimeout(() => ok(false), 1000); // eslint-disable-line mozilla/no-arbitrary-setTimeout
+ account._verificationRequestTimeouts.add(timeout);
+ let verificationRequestCancelled = false;
+ account._pendingOutgoingVerificationRequests.set("foo", {
+ cancel() {
+ verificationRequestCancelled = true;
+ return Promise.reject(new Error("test"));
+ },
+ });
+ account.remove();
+ account.unInit();
+ await storesPromise;
+ ok(!clientLoggedIn, "logged out");
+ ok(
+ !IMServices.conversations.getConversations().includes(conv),
+ "room closed"
+ );
+ ok(verificationRequestCancelled, "verification request cancelled");
+ ok(stopped);
+ equal(removedListeners, MatrixSDK.ClientEvent.Sync);
+ equal(account._verificationRequestTimeouts.size, 0);
+});
diff --git a/comm/chat/protocols/matrix/test/test_matrixCommands.js b/comm/chat/protocols/matrix/test/test_matrixCommands.js
new file mode 100644
index 0000000000..e65ff195e7
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixCommands.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { commands } = ChromeUtils.importESModule(
+ "resource:///modules/matrixCommands.sys.mjs"
+);
+
+add_task(function testUnhandledEmptyCommands() {
+ const noopCommands = [
+ "ban",
+ "unban",
+ "invite",
+ "kick",
+ "nick",
+ "op",
+ "deop",
+ "topic",
+ "roomname",
+ "addalias",
+ "removealias",
+ "upgraderoom",
+ "me",
+ "msg",
+ "join",
+ ];
+ for (const command of commands) {
+ if (noopCommands.includes(command.name)) {
+ ok(
+ !command.run(""),
+ "Command " + command.name + " reports it handled no arguments"
+ );
+ ok(
+ !command.run(" "),
+ "Command " +
+ command.name +
+ " reports it handled purely whitespace arguments"
+ );
+ }
+ }
+});
+
+add_task(function testHelpString() {
+ for (const command of commands) {
+ const helpString = command.helpString;
+ equal(
+ typeof helpString,
+ "string",
+ "Usage help for " + command.name + " is not a string"
+ );
+ ok(
+ helpString.includes(command.name),
+ command.name + " is not mentioned in its help string"
+ );
+ }
+});
+
+add_task(function testTopic() {
+ const conversation = {
+ wrappedJSObject: {
+ set topic(value) {
+ conversation._topic = value;
+ },
+ },
+ };
+ const topic = "foo bar";
+ const command = _getRunCommand("topic");
+ const result = command(topic, conversation);
+ ok(result, "Setting topic was not handled");
+ equal(conversation._topic, topic, "Topic not correctly set");
+});
+
+add_task(async function testMsgSuccess() {
+ const targetUser = "@test:example.com";
+ const directMessage = "lorem ipsum";
+ let onMessage;
+ const sendMsgPromise = new Promise(resolve => {
+ onMessage = resolve;
+ });
+ const dm = {
+ waitForRoom() {
+ return Promise.resolve(this);
+ },
+ sendMsg(message) {
+ onMessage(message);
+ },
+ };
+ const conversation = {
+ wrappedJSObject: {
+ _account: {
+ getDirectConversation(userId) {
+ if (userId === targetUser) {
+ return dm;
+ }
+ return null;
+ },
+ },
+ },
+ };
+ const command = _getRunCommand("msg");
+ const result = command(targetUser + " " + directMessage, conversation);
+ ok(result, "Sending direct message was not handled");
+ const message = await sendMsgPromise;
+ equal(message, directMessage, "Message was not sent in DM room");
+});
+
+add_task(function testMsgMissingMessage() {
+ const targetUser = "@test:example.com";
+ const conversation = {};
+ const command = _getRunCommand("msg");
+ const result = command(targetUser, conversation);
+ ok(!result, "Sending direct message was handled");
+});
+
+add_task(function testMsgNoRoom() {
+ const targetUser = "@test:example.com";
+ const directMessage = "lorem ipsum";
+ const conversation = {
+ wrappedJSObject: {
+ _account: {
+ getDirectConversation(userId) {
+ conversation.userId = userId;
+ return null;
+ },
+ ERROR(errorMsg) {
+ conversation.errorMsg = errorMsg;
+ },
+ },
+ },
+ };
+ const command = _getRunCommand("msg");
+ const result = command(targetUser + " " + directMessage, conversation);
+ ok(result, "Sending direct message was not handled");
+ equal(
+ conversation.userId,
+ targetUser,
+ "Did not try to get the conversation for the target user"
+ );
+ ok(conversation.errorMsg, "Did not report an error");
+});
+
+add_task(function testJoinSuccess() {
+ const roomName = "#test:example.com";
+ const conversation = {
+ wrappedJSObject: {
+ _account: {
+ getGroupConversation(roomId) {
+ conversation.roomId = roomId;
+ },
+ },
+ },
+ };
+ const command = _getRunCommand("join");
+ const result = command(roomName, conversation);
+ ok(result, "Did not handle join command");
+ equal(conversation.roomId, roomName, "Did not try to join expected room");
+});
+
+add_task(function testJoinNotRoomId() {
+ const roomName = "!asdf:example.com";
+ const conversation = {};
+ const command = _getRunCommand("join");
+ const result = command(roomName, conversation);
+ ok(!result, "Handled join command for unsupported room Id");
+});
+
+// Fetch the run() of a named command.
+function _getRunCommand(aCommandName) {
+ for (let command of commands) {
+ if (command.name == aCommandName) {
+ return command.run;
+ }
+ }
+
+ // Fail if no command was found.
+ ok(false, "Could not find the '" + aCommandName + "' command.");
+ return null;
+}
diff --git a/comm/chat/protocols/matrix/test/test_matrixMessage.js b/comm/chat/protocols/matrix/test/test_matrixMessage.js
new file mode 100644
index 0000000000..4d0d2120ba
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixMessage.js
@@ -0,0 +1,441 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ReceiptType } = ChromeUtils.importESModule(
+ "resource:///modules/matrix-sdk.sys.mjs"
+);
+
+const kSendReadPref = "purple.conversations.im.send_read";
+
+loadMatrix();
+
+add_task(function test_whenDisplayed() {
+ const mockConv = {
+ _account: {
+ _client: {
+ sendReadReceipt(event, receiptType) {
+ mockConv.readEvent = event;
+ mockConv.receiptType = receiptType;
+ return Promise.resolve();
+ },
+ },
+ },
+ };
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ {
+ event: "baz",
+ },
+ mockConv
+ );
+
+ message.whenDisplayed();
+
+ equal(mockConv.readEvent, "baz");
+ equal(
+ mockConv.receiptType,
+ message.hideReadReceipts ? ReceiptType.ReadPrivate : ReceiptType.Read
+ );
+
+ mockConv.readEvent = false;
+
+ message.whenDisplayed();
+ ok(!mockConv.readEvent);
+});
+
+add_task(async function test_whenDisplayedError() {
+ let resolveError;
+ const errorPromise = new Promise(resolve => {
+ resolveError = resolve;
+ });
+ const readReceiptRejection = "foo bar";
+ const mockConv = {
+ ERROR(error) {
+ resolveError(error);
+ },
+ _account: {
+ _client: {
+ sendReadReceipt() {
+ return Promise.reject(readReceiptRejection);
+ },
+ },
+ },
+ };
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ {
+ event: "baz",
+ },
+ mockConv
+ );
+
+ message.whenDisplayed();
+ const error = await errorPromise;
+ equal(error, readReceiptRejection);
+});
+
+add_task(function test_whenRead() {
+ const mockConv = {
+ _roomId: "lorem",
+ _account: {
+ _client: {
+ setRoomReadMarkers(roomId, eventId) {
+ mockConv.readRoomId = roomId;
+ mockConv.readEventId = eventId;
+ return Promise.resolve();
+ },
+ },
+ },
+ };
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ {
+ event: {
+ getId() {
+ return "baz";
+ },
+ },
+ },
+ mockConv
+ );
+
+ message.whenRead();
+
+ equal(mockConv.readEventId, "baz");
+ equal(mockConv.readRoomId, "lorem");
+
+ mockConv.readEventId = false;
+
+ message.whenRead();
+ ok(!mockConv.readEventId);
+});
+
+add_task(async function test_whenReadError() {
+ let resolveError;
+ const errorPromise = new Promise(resolve => {
+ resolveError = resolve;
+ });
+ const readReceiptRejection = "foo bar";
+ const mockConv = {
+ ERROR(error) {
+ resolveError(error);
+ },
+ _account: {
+ _client: {
+ setRoomReadMarkers() {
+ return Promise.reject(readReceiptRejection);
+ },
+ },
+ },
+ };
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ {
+ event: {
+ getId() {
+ return "baz";
+ },
+ },
+ },
+ mockConv
+ );
+
+ message.whenRead();
+ const error = await errorPromise;
+ equal(error, readReceiptRejection);
+});
+
+add_task(async function test_whenDisplayedNoEvent() {
+ const message = new MatrixMessage("foo", "bar", {
+ system: true,
+ });
+
+ message.whenDisplayed();
+
+ ok(!message._displayed);
+});
+
+add_task(async function test_whenReadNoEvent() {
+ const message = new MatrixMessage("foo", "bar", {
+ system: true,
+ });
+
+ message.whenRead();
+
+ ok(!message._read);
+});
+
+add_task(async function test_hideReadReceipts() {
+ const message = new MatrixMessage("foo", "bar", {});
+ const initialSendRead = Services.prefs.getBoolPref(kSendReadPref);
+ strictEqual(message.hideReadReceipts, !initialSendRead);
+ Services.prefs.setBoolPref(kSendReadPref, !initialSendRead);
+ const message2 = new MatrixMessage("lorem", "ipsum", {});
+ strictEqual(message2.hideReadReceipts, initialSendRead);
+ strictEqual(message.hideReadReceipts, !initialSendRead);
+ Services.prefs.setBoolPref(kSendReadPref, initialSendRead);
+});
+
+add_task(async function test_getActions() {
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ });
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ { event },
+ {
+ roomState: {
+ maySendRedactionForEvent() {
+ return false;
+ },
+ },
+ }
+ );
+ const actions = message.getActions();
+ ok(Array.isArray(actions));
+ equal(actions.length, 0);
+});
+
+add_task(async function test_getActions_decryptionFailure() {
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: "m.bad.encrypted",
+ },
+ });
+ let eventKeysWereRequestedFor;
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ { event },
+ {
+ _account: {
+ _client: {
+ cancelAndResendEventRoomKeyRequest(matrixEvent) {
+ eventKeysWereRequestedFor = matrixEvent;
+ return Promise.resolve();
+ },
+ },
+ },
+ roomState: {
+ maySendRedactionForEvent() {
+ return false;
+ },
+ },
+ }
+ );
+ const actions = message.getActions();
+ ok(Array.isArray(actions));
+ equal(actions.length, 1);
+ const [action] = actions;
+ ok(action.label);
+ action.run();
+ strictEqual(eventKeysWereRequestedFor, event);
+});
+
+add_task(async function test_getActions_redact() {
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "foo bar",
+ },
+ roomId: "!actions:example.com",
+ threadRootId: "$thread:example.com",
+ id: "$ev:example.com",
+ });
+ let eventRedacted = false;
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ { event },
+ {
+ _account: {
+ userId: 0,
+ _client: {
+ redactEvent(roomId, threadRootId, eventId) {
+ equal(roomId, "!actions:example.com");
+ equal(threadRootId, "$thread:example.com");
+ equal(eventId, "$ev:example.com");
+ eventRedacted = true;
+ return Promise.resolve();
+ },
+ },
+ },
+ roomState: {
+ maySendRedactionForEvent(ev, userId) {
+ equal(ev, event);
+ equal(userId, 0);
+ return true;
+ },
+ },
+ }
+ );
+ const actions = message.getActions();
+ ok(Array.isArray(actions));
+ equal(actions.length, 1);
+ const [action] = actions;
+ ok(action.label);
+ action.run();
+ ok(eventRedacted);
+});
+
+add_task(async function test_getActions_noEvent() {
+ const message = new MatrixMessage("system", "test", {
+ system: true,
+ });
+ const actions = message.getActions();
+ ok(Array.isArray(actions));
+ deepEqual(actions, []);
+});
+
+add_task(async function test_getActions_report() {
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum",
+ },
+ roomId: "!actions:example.com",
+ id: "$ev:example.com",
+ });
+ let eventReported = false;
+ const message = new MatrixMessage(
+ "user",
+ "lorem ipsum",
+ { event, incoming: true },
+ {
+ _account: {
+ _client: {
+ reportEvent(roomId, eventId, score, reason) {
+ equal(roomId, "!actions:example.com");
+ equal(eventId, "$ev:example.com");
+ equal(score, -100);
+ equal(reason, "");
+ eventReported = true;
+ return Promise.resolve();
+ },
+ },
+ },
+ roomState: {
+ maySendRedactionForEvent(ev, userId) {
+ return false;
+ },
+ },
+ }
+ );
+ const actions = message.getActions();
+ ok(Array.isArray(actions));
+ const [action] = actions;
+ ok(action.label);
+ action.run();
+ ok(eventReported);
+});
+
+add_task(async function test_getActions_notSent() {
+ let resendCalled = false;
+ let cancelCalled = false;
+ const event = makeEvent({
+ status: MatrixSDK.EventStatus.NOT_SENT,
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "foo bar",
+ },
+ });
+ const message = new MatrixMessage(
+ "!test:example.com",
+ "Error sending message",
+ {
+ event,
+ error: true,
+ },
+ {
+ _account: {
+ _client: {
+ resendEvent(ev, room) {
+ equal(ev, event);
+ ok(room);
+ resendCalled = true;
+ },
+ cancelPendingEvent(ev) {
+ equal(ev, event);
+ cancelCalled = true;
+ },
+ },
+ },
+ roomState: {
+ maySendRedactionForEvent(ev, userId) {
+ return false;
+ },
+ },
+ room: {},
+ }
+ );
+ const actions = message.getActions();
+ ok(Array.isArray(actions));
+ equal(actions.length, 2);
+ const [retryAction, cancelAction] = actions;
+ ok(retryAction.label);
+ ok(cancelAction.label);
+ retryAction.run();
+ ok(resendCalled);
+ ok(!cancelCalled);
+ cancelAction.run();
+ ok(cancelCalled);
+});
+
+add_task(function test_whenDisplayedUnsent() {
+ const mockConv = {
+ _account: {
+ _client: {
+ sendReadReceipt(event, options) {
+ ok(false, "Should not send read receipt for unsent event");
+ },
+ },
+ },
+ };
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ {
+ event: makeEvent({
+ status: MatrixSDK.EventStatus.NOT_SENT,
+ }),
+ },
+ mockConv
+ );
+
+ message.whenDisplayed();
+ ok(!message._displayed);
+});
+
+add_task(function test_whenReadUnsent() {
+ const mockConv = {
+ _account: {
+ _client: {
+ setRoomReadMarkers(event, options) {
+ ok(false, "Should not send read marker for unsent event");
+ },
+ },
+ },
+ };
+ const message = new MatrixMessage(
+ "foo",
+ "bar",
+ {
+ event: makeEvent({
+ status: MatrixSDK.EventStatus.NOT_SENT,
+ }),
+ },
+ mockConv
+ );
+
+ message.whenRead();
+ ok(!message._read);
+});
diff --git a/comm/chat/protocols/matrix/test/test_matrixMessageContent.js b/comm/chat/protocols/matrix/test/test_matrixMessageContent.js
new file mode 100644
index 0000000000..b09d3807ca
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixMessageContent.js
@@ -0,0 +1,652 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { MatrixMessageContent } = ChromeUtils.importESModule(
+ "resource:///modules/matrixMessageContent.sys.mjs"
+);
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+var { getMatrixTextForEvent } = ChromeUtils.importESModule(
+ "resource:///modules/matrixTextForEvent.sys.mjs"
+);
+var { l10nHelper } = ChromeUtils.importESModule(
+ "resource:///modules/imXPCOMUtils.sys.mjs"
+);
+var _ = l10nHelper("chrome://chat/locale/matrix.properties");
+
+// Required to make it so the DOMParser can handle images and such.
+XPCShellContentUtils.init(this);
+
+const PLAIN_FIXTURES = [
+ {
+ description: "Normal text message plain quote",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `> lorem ipsum
+> dolor sit amet
+
+dolor sit amet`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum!",
+ },
+ sender: "@foo:example.com",
+ },
+ result: `@foo:example.com:
+> lorem ipsum!
+
+dolor sit amet`,
+ },
+ {
+ description: "Normal text message plain quote with missing quote message",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `> lorem ipsum
+
+dolor sit amet`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ result: `> lorem ipsum
+
+dolor sit amet`,
+ },
+ {
+ description: "Emote message plain quote",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `> lorem ipsum
+
+dolor sit amet`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Emote,
+ body: "lorem ipsum",
+ },
+ sender: "@foo:example.com",
+ },
+ result: `> * @foo:example.com lorem ipsum *
+
+dolor sit amet`,
+ },
+ {
+ description: "Reply is emote",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Emote,
+ body: `> lorem ipsum
+
+dolor sit amet`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum",
+ },
+ sender: "@foo:example.com",
+ },
+ result: "\ndolor sit amet",
+ },
+ {
+ description: "Attachment",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.File,
+ body: "example.png",
+ url: "mxc://example.com/asdf",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "https://example.com/_matrix/media/r0/download/example.com/asdf",
+ },
+ {
+ description: "Sticker",
+ event: {
+ type: MatrixSDK.EventType.Sticker,
+ content: {
+ body: "example.png",
+ url: "mxc://example.com/asdf",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "https://example.com/_matrix/media/r0/download/example.com/asdf",
+ },
+ {
+ description: "Normal body with HTML-y contents",
+ event: {
+ type: MatrixSDK.EventType.Text,
+ content: {
+ body: "<foo>",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "&lt;foo&gt;",
+ },
+ {
+ description: "Non-mxc attachment",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "hello.jpg",
+ msgtype: MatrixSDK.MsgType.Image,
+ url: "https://example.com/hello.jpg",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "hello.jpg",
+ },
+ {
+ description: "Key verification request",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.KeyVerificationRequest,
+ },
+ sender: "@bar:example.com",
+ },
+ isGetTextForEvent: true,
+ },
+ {
+ description: "Decryption failure",
+ event: {
+ type: MatrixSDK.EventType.RoomMessageEncrypted,
+ content: {
+ msgtype: "m.bad.encrypted",
+ },
+ },
+ isGetTextForEvent: true,
+ },
+ {
+ description: "Being decrypted",
+ event: {
+ type: MatrixSDK.EventType.RoomMessageEncrypted,
+ decrypting: true,
+ },
+ result: _("message.decrypting"),
+ },
+ {
+ description: "Unsent event",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "foo",
+ msgtype: MatrixSDK.MsgType.Text,
+ },
+ sender: "@bar:example.com",
+ status: MatrixSDK.EventStatus.NOT_SENT,
+ },
+ result: "",
+ },
+ {
+ description: "Redacted event",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {},
+ sender: "@bar:example.com",
+ redacted: true,
+ },
+ result: _("message.redacted"),
+ },
+ {
+ description: "Tombstone",
+ event: {
+ type: MatrixSDK.EventType.RoomTombstone,
+ content: {
+ body: "tombstone",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "tombstone",
+ },
+ {
+ description: "Encryption start",
+ event: {
+ type: MatrixSDK.EventType.RoomEncryption,
+ content: {},
+ sender: "@bar:example.com",
+ },
+ isGetTextForEvent: true,
+ },
+ {
+ description: "Reaction",
+ event: {
+ type: MatrixSDK.EventType.Reaction,
+ content: {
+ ["m.relates_to"]: {
+ rel_type: MatrixSDK.RelationType.Annotation,
+ event_id: "!event:example.com",
+ key: "🐦",
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum!",
+ },
+ sender: "@foo:example.com",
+ },
+ result: _("message.reaction", "@bar:example.com", "@foo:example.com", "🐦"),
+ },
+];
+
+const HTML_FIXTURES = [
+ {
+ description: "Normal text message plain quote",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `> lorem ipsum
+> dolor sit amet
+
+dolor sit amet`,
+ format: "org.matrix.custom.html",
+ formatted_body: `<mx-reply>
+ <a href="https://matrix.to/#/@foo:example.com">Foo</a> wrote:<br>
+ <blockquote>lorem ipsum</blockquote>
+</mx-reply>
+<p>dolor sit amet</p>`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum!",
+ },
+ sender: "@foo:example.com",
+ },
+ result: `<span class="ib-person">@foo:example.com</span>:<blockquote>lorem ipsum!</blockquote>\n<p>dolor sit amet</p>`,
+ },
+ {
+ description: "Normal text message with missing quote message",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `> lorem ipsum
+> dolor sit amet
+
+dolor sit amet`,
+ format: "org.matrix.custom.html",
+ formatted_body: `<mx-reply>
+ <a href="https://matrix.to/#/@foo:example.com">Foo</a> wrote:<br>
+ <blockquote>lorem ipsum</blockquote>
+</mx-reply>
+<p>dolor sit amet</p>`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ result: `
+ <span class="ib-person">@foo:example.com</span> wrote:<br>
+ <blockquote>lorem ipsum</blockquote>
+
+<p>dolor sit amet</p>`,
+ },
+ {
+ description: "Quoted emote message",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `> lorem ipsum
+
+dolor sit amet`,
+ format: "org.matrix.custom.html",
+ formatted_body: `<mx-reply>
+ <a href="https://matrix.to/#/@foo:example.com">Foo</a> wrote:<br>
+ <blockquote>lorem ipsum</blockquote>
+</mx-reply>
+<p>dolor sit amet</p>`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Emote,
+ body: "lorem ipsum",
+ format: "org.matrix.custom.html",
+ formatted_body: "<p>lorem ipsum</p>",
+ },
+ sender: "@foo:example.com",
+ },
+ result: `<blockquote>* @foo:example.com <p>lorem ipsum</p> *</blockquote>
+<p>dolor sit amet</p>`,
+ },
+ {
+ description: "Reply is emote",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Emote,
+ body: `> lorem ipsum
+
+dolor sit amet`,
+ format: "org.matrix.custom.html",
+ formatted_body: `<mx-reply>
+ <a href="https://matrix.to/#/@foo:example.com">Foo</a> wrote:<br>
+ <blockquote>lorem ipsum</blockquote>
+</mx-reply>
+<p>dolor sit amet</p>`,
+ ["m.relates_to"]: {
+ "m.in_reply_to": {
+ event_id: "!event:example.com",
+ },
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum",
+ },
+ sender: "@foo:example.com",
+ },
+ result: "\n<p>dolor sit amet</p>",
+ },
+ {
+ description: "Attachment",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.File,
+ body: "example.png",
+ url: "mxc://example.com/asdf",
+ },
+ sender: "@bar:example.com",
+ },
+ result:
+ '<a href="https://example.com/_matrix/media/r0/download/example.com/asdf">example.png</a>',
+ },
+ {
+ description: "Sticker",
+ event: {
+ type: MatrixSDK.EventType.Sticker,
+ content: {
+ body: "example.png",
+ url: "mxc://example.com/asdf",
+ },
+ sender: "@bar:example.com",
+ },
+ result:
+ '<a href="https://example.com/_matrix/media/r0/download/example.com/asdf">example.png</a>',
+ },
+ {
+ description: "Normal formatted body",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "foo bar",
+ msgtype: MatrixSDK.MsgType.Text,
+ format: "org.matrix.custom.html",
+ formatted_body: "<p>foo bar</p>",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "<p>foo bar</p>",
+ },
+ {
+ description: "Inline image",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: ":emote:",
+ msgtype: MatrixSDK.MsgType.Text,
+ format: "org.matrix.custom.html",
+ formatted_body: '<img alt=":emote:" src="mxc://example.com/emote.png">',
+ },
+ sender: "@bar:example.com",
+ },
+ result:
+ '<a href="https://example.com/_matrix/media/r0/download/example.com/emote.png">:emote:</a>',
+ },
+ {
+ description: "Non-mxc attachment",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "foo.png",
+ msgtype: MatrixSDK.MsgType.Image,
+ url: "https://example.com/image.png",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "foo.png",
+ },
+ {
+ description: "Fallback to normal body",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "hello world <!>",
+ msgtype: MatrixSDK.MsgType.Notice,
+ },
+ sender: "@bar:example.com",
+ },
+ result: "hello world &lt;!&gt;",
+ },
+ {
+ description: "Colored text",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "rainbow",
+ msgtype: MatrixSDK.MsgType.Text,
+ format: "org.matrix.custom.html",
+ formatted_body:
+ '<font data-mx-color="ff0000">ra</font><span data-mx-color="00ff00">inb</span><i data-mx-color="0000ff">ow</i>',
+ },
+ sender: "@bar:example.com",
+ },
+ result:
+ '<font style="color: rgb(255, 0, 0);">ra</font><span style="color: rgb(0, 255, 0);">inb</span><i data-mx-color="0000ff">ow</i>',
+ },
+ {
+ description: "Unsent event",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ body: "foo",
+ msgtype: MatrixSDK.MsgType.Text,
+ },
+ sender: "@bar:example.com",
+ status: MatrixSDK.EventStatus.NOT_SENT,
+ },
+ result: "",
+ },
+ {
+ description: "Redacted event",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {},
+ sender: "@bar:example.com",
+ redacted: true,
+ },
+ result: _("message.redacted"),
+ },
+ {
+ description: "Tombstone",
+ event: {
+ type: MatrixSDK.EventType.RoomTombstone,
+ content: {
+ body: "tombstone",
+ },
+ sender: "@bar:example.com",
+ },
+ result: "tombstone",
+ },
+ {
+ description: "Encryption start",
+ event: {
+ type: MatrixSDK.EventType.RoomEncryption,
+ content: {},
+ sender: "@bar:example.com",
+ },
+ isGetTextForEvent: true,
+ },
+ {
+ description: "Reaction",
+ event: {
+ type: MatrixSDK.EventType.Reaction,
+ content: {
+ ["m.relates_to"]: {
+ rel_type: MatrixSDK.RelationType.Annotation,
+ event_id: "!event:example.com",
+ key: "🐦",
+ },
+ },
+ sender: "@bar:example.com",
+ },
+ getEventResult: {
+ id: "!event:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "lorem ipsum!",
+ },
+ sender: "@foo:example.com",
+ },
+ result: _(
+ "message.reaction",
+ '<span class="ib-person">@bar:example.com</span>',
+ '<span class="ib-person">@foo:example.com</span>',
+ "🐦"
+ ),
+ },
+ {
+ description: "URL encoded mention",
+ event: {
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: `@foo:example.com dolor sit amet`,
+ format: "org.matrix.custom.html",
+ formatted_body: `<a href="https://matrix.to/#/%40foo%3Aexample.com">Foo</a> dolor sit amet`,
+ },
+ sender: "@bar:example.com",
+ },
+ result: '<span class="ib-person">@foo:example.com</span> dolor sit amet',
+ },
+];
+
+add_task(function test_plainBody() {
+ for (const fixture of PLAIN_FIXTURES) {
+ const event = makeEvent(fixture.event);
+ const result = MatrixMessageContent.getIncomingPlain(
+ event,
+ "https://example.com",
+ eventId => {
+ if (fixture.getEventResult) {
+ equal(
+ eventId,
+ fixture.getEventResult.id,
+ `${fixture.description}: getEvent event ID`
+ );
+ return makeEvent(fixture.getEventResult);
+ }
+ return undefined;
+ }
+ );
+ if (fixture.isGetTextForEvent) {
+ equal(result, getMatrixTextForEvent(event));
+ } else {
+ equal(result, fixture.result, fixture.description);
+ }
+ }
+});
+
+add_task(function test_htmlBody() {
+ for (const fixture of HTML_FIXTURES) {
+ const event = makeEvent(fixture.event);
+ const result = MatrixMessageContent.getIncomingHTML(
+ event,
+ "https://example.com",
+ eventId => {
+ if (fixture.getEventResult) {
+ equal(
+ eventId,
+ fixture.getEventResult.id,
+ `${fixture.description}: getEvent event ID`
+ );
+ return makeEvent(fixture.getEventResult);
+ }
+ return undefined;
+ }
+ );
+ if (fixture.isGetTextForEvent) {
+ equal(result, getMatrixTextForEvent(event));
+ } else {
+ equal(result, fixture.result, fixture.description);
+ }
+ }
+});
diff --git a/comm/chat/protocols/matrix/test/test_matrixPowerLevels.js b/comm/chat/protocols/matrix/test/test_matrixPowerLevels.js
new file mode 100644
index 0000000000..40237664a3
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixPowerLevels.js
@@ -0,0 +1,204 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { MatrixPowerLevels } = ChromeUtils.importESModule(
+ "resource:///modules/matrixPowerLevels.sys.mjs"
+);
+var { l10nHelper } = ChromeUtils.importESModule(
+ "resource:///modules/imXPCOMUtils.sys.mjs"
+);
+var _ = l10nHelper("chrome://chat/locale/matrix.properties");
+
+const TO_TEXT_FIXTURES = [
+ {
+ level: MatrixPowerLevels.user,
+ defaultLevel: MatrixPowerLevels.user,
+ result: _(
+ "powerLevel.detailed",
+ _("powerLevel.default"),
+ MatrixPowerLevels.user
+ ),
+ name: "Default power level for default 0",
+ },
+ {
+ level: MatrixPowerLevels.user,
+ defaultLevel: 10,
+ result: _(
+ "powerLevel.detailed",
+ _("powerLevel.restricted"),
+ MatrixPowerLevels.user
+ ),
+ name: "Restricted power level",
+ },
+ {
+ level: 10,
+ defaultLevel: 10,
+ result: _("powerLevel.detailed", _("powerLevel.default"), 10),
+ name: "Default power level for default 10",
+ },
+ {
+ level: MatrixPowerLevels.moderator,
+ defaultLevel: MatrixPowerLevels.user,
+ result: _(
+ "powerLevel.detailed",
+ _("powerLevel.moderator"),
+ MatrixPowerLevels.moderator
+ ),
+ name: "Moderator",
+ },
+ {
+ level: MatrixPowerLevels.admin,
+ defaultLevel: MatrixPowerLevels.user,
+ result: _(
+ "powerLevel.detailed",
+ _("powerLevel.admin"),
+ MatrixPowerLevels.admin
+ ),
+ name: "Admin",
+ },
+ {
+ level: 25,
+ defaultLevel: MatrixPowerLevels.user,
+ result: _("powerLevel.detailed", _("powerLevel.custom"), 25),
+ name: "Custom power level 25",
+ },
+];
+const GET_EVENT_LEVEL_FIXTURES = [
+ {
+ powerLevels: undefined,
+ expected: 0,
+ },
+ {
+ powerLevels: {},
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: 10,
+ },
+ expected: 10,
+ },
+ {
+ powerLevels: {
+ events_default: Infinity,
+ },
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: "foo",
+ },
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: 0,
+ events: {},
+ },
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: 0,
+ events: {
+ [MatrixSDK.EventType.RoomMessage]: 0,
+ },
+ },
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: 0,
+ events: {
+ [MatrixSDK.EventType.RoomMessage]: Infinity,
+ },
+ },
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: 0,
+ events: {
+ [MatrixSDK.EventType.RoomMessage]: "foo",
+ },
+ },
+ expected: 0,
+ },
+ {
+ powerLevels: {
+ events_default: 0,
+ events: {
+ [MatrixSDK.EventType.RoomMessage]: 10,
+ },
+ },
+ expected: 10,
+ },
+];
+
+add_task(async function testToText() {
+ for (const fixture of TO_TEXT_FIXTURES) {
+ const result = MatrixPowerLevels.toText(
+ fixture.level,
+ fixture.defaultLevel
+ );
+ equal(result, fixture.result);
+ }
+});
+
+add_task(async function testGetUserDefaultLevel() {
+ equal(MatrixPowerLevels.getUserDefaultLevel(), 0);
+ equal(MatrixPowerLevels.getUserDefaultLevel({}), 0);
+ equal(
+ MatrixPowerLevels.getUserDefaultLevel({
+ users_default: 10,
+ }),
+ 10
+ );
+ equal(
+ MatrixPowerLevels.getUserDefaultLevel({
+ users_default: Infinity,
+ }),
+ 0
+ );
+ equal(
+ MatrixPowerLevels.getUserDefaultLevel({
+ users_default: "foo",
+ }),
+ 0
+ );
+});
+
+add_task(async function testGetEventDefaultLevel() {
+ equal(MatrixPowerLevels.getEventDefaultLevel(), 0);
+ equal(MatrixPowerLevels.getEventDefaultLevel({}), 0);
+ equal(
+ MatrixPowerLevels.getEventDefaultLevel({
+ events_default: 10,
+ }),
+ 10
+ );
+ equal(
+ MatrixPowerLevels.getEventDefaultLevel({
+ events_default: Infinity,
+ }),
+ 0
+ );
+ equal(
+ MatrixPowerLevels.getEventDefaultLevel({
+ events_default: "foo",
+ }),
+ 0
+ );
+});
+
+add_task(async function testGetEventLevel() {
+ for (const eventLevelTest of GET_EVENT_LEVEL_FIXTURES) {
+ equal(
+ MatrixPowerLevels.getEventLevel(
+ eventLevelTest.powerLevels,
+ MatrixSDK.EventType.RoomMessage
+ ),
+ eventLevelTest.expected
+ );
+ }
+});
diff --git a/comm/chat/protocols/matrix/test/test_matrixRoom.js b/comm/chat/protocols/matrix/test/test_matrixRoom.js
new file mode 100644
index 0000000000..3e4c72a19a
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixRoom.js
@@ -0,0 +1,928 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+loadMatrix();
+
+add_task(async function test_initRoom() {
+ const roomStub = getRoom(true);
+ equal(typeof roomStub._resolveInitializer, "function");
+ ok(roomStub._initialized);
+ await roomStub._initialized;
+ roomStub.forget();
+});
+
+add_task(async function test_initRoom_withSpace() {
+ const roomStub = getRoom(true, "#test:example.com", (target, key) => {
+ if (key === "isSpaceRoom") {
+ return () => true;
+ }
+ return null;
+ });
+ ok(roomStub._initialized);
+ ok(roomStub.left);
+ await roomStub._initialized;
+ roomStub.forget();
+});
+
+add_task(function test_replaceRoom() {
+ const roomStub = {
+ __proto__: MatrixRoom.prototype,
+ _resolveInitializer() {
+ this.initialized = true;
+ },
+ _mostRecentEventId: "foo",
+ _joiningLocks: new Set(),
+ };
+ const newRoom = {};
+ MatrixRoom.prototype.replaceRoom.call(roomStub, newRoom);
+ strictEqual(roomStub._replacedBy, newRoom);
+ ok(roomStub.initialized);
+ equal(newRoom._mostRecentEventId, roomStub._mostRecentEventId);
+});
+
+add_task(async function test_waitForRoom() {
+ const roomStub = {
+ _initialized: Promise.resolve(),
+ };
+ const awaitedRoom = await MatrixRoom.prototype.waitForRoom.call(roomStub);
+ strictEqual(awaitedRoom, roomStub);
+});
+
+add_task(async function test_waitForRoomReplaced() {
+ const roomStub = getRoom(true);
+ const newRoom = {
+ waitForRoom() {
+ return Promise.resolve("success");
+ },
+ };
+ MatrixRoom.prototype.replaceRoom.call(roomStub, newRoom);
+ const awaitedRoom = await MatrixRoom.prototype.waitForRoom.call(roomStub);
+ equal(awaitedRoom, "success");
+ roomStub.forget();
+});
+
+add_task(function test_addEventRedacted() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ redacted: true,
+ redaction: {
+ event_id: 2,
+ type: MatrixSDK.EventType.RoomRedaction,
+ },
+ type: MatrixSDK.EventType.RoomMessage,
+ });
+ let updatedMessage;
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com/";
+ },
+ },
+ },
+ updateMessage(sender, message, opts) {
+ updatedMessage = {
+ sender,
+ message,
+ opts,
+ };
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub._mostRecentEventId, 2);
+ equal(typeof updatedMessage, "object");
+ ok(!updatedMessage.opts.system);
+ ok(updatedMessage.opts.deleted);
+ equal(typeof updatedMessage.message, "string");
+ equal(updatedMessage.sender, "@user:example.com");
+});
+
+add_task(function test_addEventMessageIncoming() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ content: {
+ body: "foo",
+ msgtype: MatrixSDK.MsgType.Text,
+ },
+ type: MatrixSDK.EventType.RoomMessage,
+ });
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com/";
+ },
+ },
+ },
+ _eventsWaitingForDecryption: new Set(),
+ writeMessage(who, message, options) {
+ this.who = who;
+ this.message = message;
+ this.options = options;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub.who, "@user:example.com");
+ equal(roomStub.message, "foo");
+ ok(!roomStub.options.system);
+ ok(!roomStub.options.delayed);
+ equal(roomStub._mostRecentEventId, 0);
+});
+
+add_task(function test_addEventMessageOutgoing() {
+ const event = makeEvent({
+ sender: "@test:example.com",
+ content: {
+ body: "foo",
+ msgtype: MatrixSDK.MsgType.Text,
+ },
+ type: MatrixSDK.EventType.RoomMessage,
+ });
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com";
+ },
+ },
+ },
+ _eventsWaitingForDecryption: new Set(),
+ writeMessage(who, message, options) {
+ this.who = who;
+ this.message = message;
+ this.options = options;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub.who, "@test:example.com");
+ equal(roomStub.message, "foo");
+ ok(!roomStub.options.system);
+ ok(!roomStub.options.delayed);
+ equal(roomStub._mostRecentEventId, 0);
+});
+
+add_task(function test_addEventMessageEmote() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ content: {
+ body: "foo",
+ msgtype: MatrixSDK.MsgType.Emote,
+ },
+ type: MatrixSDK.EventType.RoomMessage,
+ });
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com";
+ },
+ },
+ },
+ _eventsWaitingForDecryption: new Set(),
+ writeMessage(who, message, options) {
+ this.who = who;
+ this.message = message;
+ this.options = options;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub.who, "@user:example.com");
+ equal(roomStub.message, "foo");
+ ok(roomStub.options.action);
+ ok(!roomStub.options.system);
+ ok(!roomStub.options.delayed);
+ equal(roomStub._mostRecentEventId, 0);
+});
+
+add_task(function test_addEventMessageDelayed() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ content: {
+ body: "foo",
+ msgtype: MatrixSDK.MsgType.Text,
+ },
+ type: MatrixSDK.EventType.RoomMessage,
+ });
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com";
+ },
+ },
+ },
+ _eventsWaitingForDecryption: new Set(),
+ writeMessage(who, message, options) {
+ this.who = who;
+ this.message = message;
+ this.options = options;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event, true);
+ equal(roomStub.who, "@user:example.com");
+ equal(roomStub.message, "foo");
+ ok(!roomStub.options.system);
+ ok(roomStub.options.delayed);
+ equal(roomStub._mostRecentEventId, 0);
+});
+
+add_task(function test_addEventTopic() {
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomTopic,
+ id: 1,
+ content: {
+ topic: "foo bar",
+ },
+ sender: "@user:example.com",
+ });
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com/";
+ },
+ },
+ },
+ _eventsWaitingForDecryption: new Set(),
+ setTopic(topic, who) {
+ this.who = who;
+ this.topic = topic;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub.who, "@user:example.com");
+ equal(roomStub.topic, "foo bar");
+ equal(roomStub._mostRecentEventId, 1);
+});
+
+add_task(async function test_addEventTombstone() {
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomTombstone,
+ id: 1,
+ content: {
+ body: "updated room",
+ replacement_room: "!new_room:example.com",
+ },
+ sender: "@test:example.com",
+ });
+ const conversation = getRoom(true);
+ const newText = waitForNotification(conversation, "new-text");
+ conversation.addEvent(event);
+ const { subject: message } = await newText;
+ const newConversation = await conversation.waitForRoom();
+ equal(newConversation.normalizedName, event.getContent().replacement_room);
+ equal(message.who, event.getSender());
+ equal(message.message, event.getContent().body);
+ ok(message.system);
+ ok(message.incoming);
+ ok(!conversation._account);
+ newConversation.forget();
+});
+
+add_task(function test_forgetWith_close() {
+ const roomList = new Map();
+ const roomStub = {
+ closeDm() {
+ this.closeCalled = true;
+ },
+ _roomId: "foo",
+ _account: {
+ roomList,
+ },
+ // stubs for jsProtoHelper implementations
+ addObserver() {},
+ unInit() {},
+ _releaseJoiningLock(lock) {
+ this.releasedLock = lock;
+ },
+ };
+ roomList.set(roomStub._roomId, roomStub);
+ IMServices.conversations.addConversation(roomStub);
+
+ MatrixRoom.prototype.forget.call(roomStub);
+ ok(!roomList.has(roomStub._roomId));
+ ok(roomStub.closeCalled);
+ equal(roomStub.releasedLock, "roomInit", "Released roomInit lock");
+});
+
+add_task(function test_forgetWithout_close() {
+ const roomList = new Map();
+ const roomStub = {
+ isChat: true,
+ _roomId: "foo",
+ _account: {
+ roomList,
+ },
+ // stubs for jsProtoHelper implementations
+ addObserver() {},
+ unInit() {},
+ _releaseJoiningLock(lock) {
+ this.releasedLock = lock;
+ },
+ };
+ roomList.set(roomStub._roomId, roomStub);
+ IMServices.conversations.addConversation(roomStub);
+
+ MatrixRoom.prototype.forget.call(roomStub);
+ ok(!roomList.has(roomStub._roomId));
+ equal(roomStub.releasedLock, "roomInit", "Released roomInit lock");
+});
+
+add_task(function test_close() {
+ const roomStub = {
+ forget() {
+ this.forgetCalled = true;
+ },
+ cleanUpOutgoingVerificationRequests() {
+ this.cleanUpCalled = true;
+ },
+ _roomId: "foo",
+ _account: {
+ _client: {
+ leave(roomId) {
+ roomStub.leftRoom = roomId;
+ },
+ },
+ },
+ };
+
+ MatrixRoom.prototype.close.call(roomStub);
+ equal(roomStub.leftRoom, roomStub._roomId);
+ ok(roomStub.forgetCalled);
+ ok(roomStub.cleanUpCalled);
+});
+
+add_task(function test_setTypingState() {
+ const roomStub = getRoom(true, "foo", {
+ sendTyping(roomId, isTyping) {
+ roomStub.typingRoomId = roomId;
+ roomStub.typing = isTyping;
+ return Promise.resolve();
+ },
+ });
+
+ roomStub._setTypingState(true);
+ equal(roomStub.typingRoomId, roomStub._roomId);
+ ok(roomStub.typing);
+
+ roomStub._setTypingState(false);
+ equal(roomStub.typingRoomId, roomStub._roomId);
+ ok(!roomStub.typing);
+
+ roomStub._setTypingState(true);
+ equal(roomStub.typingRoomId, roomStub._roomId);
+ ok(roomStub.typing);
+
+ roomStub._cleanUpTimers();
+ roomStub.forget();
+});
+
+add_task(function test_setTypingStateDebounce() {
+ const roomStub = getRoom(true, "foo", {
+ sendTyping(roomId, isTyping) {
+ roomStub.typingRoomId = roomId;
+ roomStub.typing = isTyping;
+ return Promise.resolve();
+ },
+ });
+
+ roomStub._setTypingState(true);
+ equal(roomStub.typingRoomId, roomStub._roomId);
+ ok(roomStub.typing);
+ ok(roomStub._typingDebounce);
+
+ roomStub.typing = false;
+
+ roomStub._setTypingState(true);
+ equal(roomStub.typingRoomId, roomStub._roomId);
+ ok(!roomStub.typing);
+ ok(roomStub._typingDebounce);
+
+ clearTimeout(roomStub._typingDebounce);
+ roomStub._typingDebounce = null;
+
+ roomStub._setTypingState(true);
+ equal(roomStub.typingRoomId, roomStub._roomId);
+ ok(roomStub.typing);
+
+ roomStub._cleanUpTimers();
+ roomStub.forget();
+});
+
+add_task(function test_cancelTypingTimer() {
+ const roomStub = {
+ _typingTimer: setTimeout(() => {}, 10000), // eslint-disable-line mozilla/no-arbitrary-setTimeout
+ };
+ MatrixRoom.prototype._cancelTypingTimer.call(roomStub);
+ ok(!roomStub._typingTimer);
+});
+
+add_task(function test_cleanUpTimers() {
+ const roomStub = getRoom(true);
+ roomStub._typingTimer = setTimeout(() => {}, 10000); // eslint-disable-line mozilla/no-arbitrary-setTimeout
+ roomStub._typingDebounce = setTimeout(() => {}, 1000); // eslint-disable-line mozilla/no-arbitrary-setTimeout
+ roomStub._cleanUpTimers();
+ ok(!roomStub._typingTimer);
+ ok(!roomStub._typingDebounce);
+ roomStub.forget();
+});
+
+add_task(function test_finishedComposing() {
+ let typingState = true;
+ const roomStub = {
+ __proto__: MatrixRoom.prototype,
+ shouldSendTypingNotifications: false,
+ _roomId: "foo",
+ _account: {
+ _client: {
+ sendTyping(roomId, state) {
+ typingState = state;
+ return Promise.resolve();
+ },
+ },
+ },
+ };
+
+ MatrixRoom.prototype.finishedComposing.call(roomStub);
+ ok(typingState);
+
+ roomStub.shouldSendTypingNotifications = true;
+ MatrixRoom.prototype.finishedComposing.call(roomStub);
+ ok(!typingState);
+});
+
+add_task(function test_sendTyping() {
+ let typingState = false;
+ const roomStub = getRoom(true, "foo", {
+ sendTyping(roomId, state) {
+ typingState = state;
+ return Promise.resolve();
+ },
+ });
+ Services.prefs.setBoolPref("purple.conversations.im.send_typing", false);
+
+ let result = roomStub.sendTyping("lorem ipsum");
+ ok(!roomStub._typingTimer);
+ equal(result, Ci.prplIConversation.NO_TYPING_LIMIT);
+ ok(!typingState);
+
+ Services.prefs.setBoolPref("purple.conversations.im.send_typing", true);
+ result = roomStub.sendTyping("lorem ipsum");
+ ok(roomStub._typingTimer);
+ equal(result, Ci.prplIConversation.NO_TYPING_LIMIT);
+ ok(typingState);
+
+ result = roomStub.sendTyping("");
+ ok(!roomStub._typingTimer);
+ equal(result, Ci.prplIConversation.NO_TYPING_LIMIT);
+ ok(!typingState);
+
+ roomStub._cleanUpTimers();
+ roomStub.forget();
+});
+
+add_task(function test_setInitialized() {
+ const roomStub = {
+ _resolveInitializer() {
+ this.calledResolve = true;
+ },
+ _releaseJoiningLock(lock) {
+ this.releasedLock = lock;
+ },
+ };
+ MatrixRoom.prototype._setInitialized.call(roomStub);
+ ok(roomStub.calledResolve);
+ equal(roomStub.releasedLock, "roomInit", "Released roomInit lock");
+});
+
+add_task(function test_addEventSticker() {
+ const date = new Date();
+ const event = makeEvent({
+ time: date,
+ sender: "@user:example.com",
+ type: MatrixSDK.EventType.Sticker,
+ content: {
+ body: "foo",
+ url: "mxc://example.com/sticker.png",
+ },
+ });
+ const roomStub = {
+ _account: {
+ userId: "@test:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com";
+ },
+ },
+ },
+ _eventsWaitingForDecryption: new Set(),
+ writeMessage(who, message, options) {
+ this.who = who;
+ this.message = message;
+ this.options = options;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub.who, "@user:example.com");
+ equal(
+ roomStub.message,
+ "https://example.com/_matrix/media/r0/download/example.com/sticker.png"
+ );
+ ok(!roomStub.options.system);
+ ok(!roomStub.options.delayed);
+ equal(roomStub._mostRecentEventId, 0);
+});
+
+add_task(function test_sendMsg() {
+ let isTyping = true;
+ let message;
+ const roomStub = getRoom(true, "#test:example.com", {
+ sendTyping(roomId, typing) {
+ equal(roomId, roomStub._roomId);
+ isTyping = typing;
+ return Promise.resolve();
+ },
+ sendTextMessage(roomId, threadId, msg) {
+ equal(roomId, roomStub._roomId);
+ equal(threadId, null);
+ message = msg;
+ return Promise.resolve();
+ },
+ });
+ roomStub.dispatchMessage("foo bar");
+ ok(!isTyping);
+ equal(message, "foo bar");
+ roomStub._cleanUpTimers();
+ roomStub.forget();
+});
+
+add_task(function test_sendMsg_emote() {
+ let isTyping = true;
+ let message;
+ const roomStub = getRoom(true, "#test:example.com", {
+ sendTyping(roomId, typing) {
+ equal(roomId, roomStub._roomId);
+ isTyping = typing;
+ return Promise.resolve();
+ },
+ sendEmoteMessage(roomId, threadId, msg) {
+ equal(roomId, roomStub._roomId);
+ equal(threadId, null);
+ message = msg;
+ return Promise.resolve();
+ },
+ });
+ roomStub.dispatchMessage("foo bar", true);
+ ok(!isTyping);
+ equal(message, "foo bar");
+ roomStub._cleanUpTimers();
+ roomStub.forget();
+});
+
+add_task(function test_createMessage() {
+ const time = Date.now();
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ time,
+ sender: "@foo:example.com",
+ });
+ const roomStub = getRoom(true, "#test:example.com", {
+ getPushActionsForEvent(eventToProcess) {
+ equal(eventToProcess, event);
+ return {
+ tweaks: {
+ highlight: true,
+ },
+ };
+ },
+ });
+ const message = roomStub.createMessage("@foo:example.com", "bar", {
+ event,
+ });
+ equal(message.message, "bar");
+ equal(message.who, "@foo:example.com");
+ equal(message.conversation, roomStub);
+ ok(!message.outgoing);
+ ok(message.incoming);
+ equal(message.alias, "foo bar");
+ ok(!message.isEncrypted);
+ ok(message.containsNick);
+ equal(message.time, Math.floor(time / 1000));
+ equal(message.iconURL, "https://example.com/avatar");
+ equal(message.remoteId, 0);
+ roomStub.forget();
+});
+
+add_task(async function test_addEventWaitingForDecryption() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ type: MatrixSDK.EventType.RoomMessageEncrypted,
+ shouldDecrypt: true,
+ });
+ const roomStub = getRoom(true, "#test:example.com");
+ const writePromise = waitForNotification(roomStub, "new-text");
+ roomStub.addEvent(event);
+ const { subject: result } = await writePromise;
+ ok(!result.error, "Waiting for decryption message is not an error");
+ ok(!result.system, "Waiting for decryption message is not system");
+ roomStub.forget();
+});
+
+add_task(async function test_addEventReplaceDecryptedEvent() {
+ //TODO need to emit event on event?
+ let spec = {
+ sender: "@user:example.com",
+ type: MatrixSDK.EventType.RoomMessage,
+ isEncrypted: true,
+ shouldDecrypt: true,
+ content: {
+ msgtype: MatrixSDK.MsgType.Text,
+ body: "foo",
+ },
+ };
+ const event = makeEvent(spec);
+ const roomStub = getRoom(true, "#test:example.com");
+ const writePromise = waitForNotification(roomStub, "new-text");
+ roomStub.addEvent(event);
+ const { subject: initialEvent } = await writePromise;
+ ok(!initialEvent.error, "Pending event is not an error");
+ ok(!initialEvent.system, "Pending event is not a system message");
+ equal(
+ initialEvent.who,
+ "@user:example.com",
+ "Pending message has correct sender"
+ );
+ const updatePromise = waitForNotification(roomStub, "update-text");
+ spec.shouldDecrypt = false;
+ event._listeners[MatrixSDK.MatrixEventEvent.Decrypted](event);
+ const { subject: result } = await updatePromise;
+ equal(result.who, "@user:example.com", "Correct message sender");
+ equal(result.message, "foo", "Message contents displayed");
+ roomStub.forget();
+});
+
+add_task(async function test_addEventDecryptionError() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ type: MatrixSDK.EventType.RoomMessageEncrypted,
+ content: {
+ msgtype: "m.bad.encrypted",
+ },
+ });
+ const roomStub = getRoom(true, "#test:example.com");
+ const writePromise = waitForNotification(roomStub, "new-text");
+ roomStub.addEvent(event);
+ const { subject: result } = await writePromise;
+ ok(result.error, "Message is an error");
+ ok(!result.system, "Not displayed as system event");
+ roomStub.forget();
+});
+
+add_task(async function test_addEventPendingDecryption() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ type: MatrixSDK.EventType.RoomMessageEncrypted,
+ decrypting: true,
+ });
+ const roomStub = getRoom(true, "#test:example.com");
+ const writePromise = waitForNotification(roomStub, "new-text");
+ roomStub.addEvent(event);
+ const { subject: result } = await writePromise;
+ ok(!result.error, "Not marked as error");
+ ok(!result.system, "Not displayed as system event");
+ roomStub.forget();
+});
+
+add_task(async function test_addEventRedaction() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ id: 1443,
+ type: MatrixSDK.EventType.RoomRedaction,
+ });
+ const roomStub = {
+ writeMessage() {
+ ok(false, "called writeMessage");
+ },
+ updateMessage() {
+ ok(false, "called updateMessage");
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ equal(roomStub._mostRecentEventId, undefined);
+});
+
+add_task(function test_encryptionStateUnavailable() {
+ const room = getRoom(true, "#test:example.com");
+ equal(
+ room.encryptionState,
+ Ci.prplIConversation.ENCRYPTION_NOT_SUPPORTED,
+ "Encryption state is encryption not supported with crypto disabled"
+ );
+ room.forget();
+});
+
+add_task(function test_encryptionStateCanEncrypt() {
+ const room = getRoom(true, "#test:example.com", {
+ isCryptoEnabled() {
+ return true;
+ },
+ });
+ let maySendStateEvent = false;
+ room.room.currentState = {
+ mayClientSendStateEvent(eventType, client) {
+ equal(
+ eventType,
+ MatrixSDK.EventType.RoomEncryption,
+ "mayClientSendStateEvent called for room encryption"
+ );
+ equal(
+ client,
+ room._account._client,
+ "mayClientSendStateEvent got the expected client"
+ );
+ return maySendStateEvent;
+ },
+ };
+ equal(
+ room.encryptionState,
+ Ci.prplIConversation.ENCRYPTION_NOT_SUPPORTED,
+ "Encryption state is encryption not supported when state event can't be sent"
+ );
+ maySendStateEvent = true;
+ equal(
+ room.encryptionState,
+ Ci.prplIConversation.ENCRYPTION_AVAILABLE,
+ "Encryption state is available"
+ );
+ room.forget();
+});
+
+add_task(async function test_encryptionStateOn() {
+ const room = getRoom(true, "#test:example.com", {
+ isCryptoEnabled() {
+ return true;
+ },
+ isRoomEncrypted(roomId) {
+ return true;
+ },
+ });
+ room.room.currentState = {
+ mayClientSendStateEvent(eventType, client) {
+ equal(
+ eventType,
+ MatrixSDK.EventType.RoomEncryption,
+ "mayClientSendStateEvent called for room encryption"
+ );
+ equal(
+ client,
+ room._account._client,
+ "mayClientSendStateEvent got the expected client"
+ );
+ return false;
+ },
+ };
+ equal(
+ room.encryptionState,
+ Ci.prplIConversation.ENCRYPTION_ENABLED,
+ "Encryption state is enabled"
+ );
+ room._hasUnverifiedDevices = false;
+ equal(
+ room.encryptionState,
+ Ci.prplIConversation.ENCRYPTION_TRUSTED,
+ "Encryption state is trusted"
+ );
+ await Promise.resolve();
+ room.forget();
+});
+
+add_task(async function test_addEventReaction() {
+ const event = makeEvent({
+ sender: "@user:example.com",
+ type: MatrixSDK.EventType.Reaction,
+ content: {
+ ["m.relates_to"]: {
+ rel_type: MatrixSDK.RelationType.Annotation,
+ event_id: "!event:example.com",
+ key: "🐦",
+ },
+ },
+ });
+ let wroteMessage = false;
+ const roomStub = {
+ _account: {
+ userId: "@user:example.com",
+ _client: {
+ getHomeserverUrl() {
+ return "https://example.com/";
+ },
+ },
+ },
+ room: {
+ findEventById(id) {
+ equal(id, "!event:example.com", "Reading expected annotated event");
+ return {
+ getSender() {
+ return "@foo:example.com";
+ },
+ };
+ },
+ },
+ writeMessage(who, message, options) {
+ equal(who, "@user:example.com", "Correct sender for reaction");
+ ok(message.includes("🐦"), "Message contains reaction content");
+ ok(options.system, "reaction is a system message");
+ wroteMessage = true;
+ },
+ };
+ MatrixRoom.prototype.addEvent.call(roomStub, event);
+ ok(wroteMessage, "Wrote reaction to conversation");
+});
+
+add_task(async function test_removeParticipant() {
+ let roomMembers = [
+ {
+ userId: "@foo:example.com",
+ },
+ {
+ userId: "@bar:example.com",
+ },
+ ];
+ const room = getRoom(true, "#test:example.com", {
+ getJoinedMembers() {
+ return roomMembers;
+ },
+ });
+ for (const member of roomMembers) {
+ room.addParticipant(member);
+ }
+ equal(room._participants.size, 2, "Room has two participants");
+
+ const participantRemoved = waitForNotification(room, "chat-buddy-remove");
+ room.removeParticipant(roomMembers.splice(1, 1)[0].userId);
+ const { subject } = await participantRemoved;
+ const participantsArray = Array.from(
+ subject.QueryInterface(Ci.nsISimpleEnumerator)
+ );
+ equal(participantsArray.length, 1, "One participant is being removed");
+ equal(
+ participantsArray[0].QueryInterface(Ci.nsISupportsString).data,
+ "@bar:example.com",
+ "The participant is being removed by its user ID"
+ );
+ equal(room._participants.size, 1, "One participant is left");
+ room.forget();
+});
+
+add_task(function test_highlightForNotifications() {
+ const time = Date.now();
+ const event = makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ time,
+ sender: "@foo:example.com",
+ });
+ const roomStub = getRoom(true, "#test:example.com", {
+ getPushActionsForEvent(eventToProcess) {
+ equal(eventToProcess, event);
+ return {
+ notify: true,
+ };
+ },
+ });
+ const message = roomStub.createMessage("@foo:example.com", "bar", {
+ event,
+ });
+ equal(message.message, "bar");
+ equal(message.who, "@foo:example.com");
+ equal(message.conversation, roomStub);
+ ok(!message.outgoing);
+ ok(message.incoming);
+ equal(message.alias, "foo bar");
+ ok(message.containsNick);
+ roomStub.forget();
+});
+
+function waitForNotification(target, expectedTopic) {
+ let promise = new Promise(resolve => {
+ let observer = {
+ observe(subject, topic, data) {
+ if (topic === expectedTopic) {
+ resolve({ subject, data });
+ target.removeObserver(observer);
+ }
+ },
+ };
+ target.addObserver(observer);
+ });
+ return promise;
+}
diff --git a/comm/chat/protocols/matrix/test/test_matrixTextForEvent.js b/comm/chat/protocols/matrix/test/test_matrixTextForEvent.js
new file mode 100644
index 0000000000..feeab4edee
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_matrixTextForEvent.js
@@ -0,0 +1,834 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { getMatrixTextForEvent } = ChromeUtils.importESModule(
+ "resource:///modules/matrixTextForEvent.sys.mjs"
+);
+var { l10nHelper } = ChromeUtils.importESModule(
+ "resource:///modules/imXPCOMUtils.sys.mjs"
+);
+var _ = l10nHelper("chrome://chat/locale/matrix.properties");
+
+function run_test() {
+ add_test(testGetTextForMatrixEvent);
+ run_next_test();
+}
+
+const SENDER = "@test:example.com";
+const FIXTURES = [
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "ban",
+ },
+ target: {
+ userId: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.banned", SENDER, "@foo:example.com"),
+ name: "Banned without reason",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "ban",
+ reason: "test",
+ },
+ target: {
+ userId: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.bannedWithReason", SENDER, "@foo:example.com", "test"),
+ name: "Banned with reason",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "invite",
+ third_party_invite: {
+ display_name: "bar",
+ },
+ },
+ target: {
+ userId: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.acceptedInviteFor", "@foo:example.com", "bar"),
+ name: "Invite accepted by other user with display name",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "invite",
+ third_party_invite: {},
+ },
+ target: {
+ userId: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.acceptedInvite", "@foo:example.com"),
+ name: "Invite accepted by other user",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "invite",
+ },
+ target: {
+ userId: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.invited", SENDER, "@foo:example.com"),
+ name: "User invited",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "join",
+ displayname: "ipsum",
+ },
+ prevContent: {
+ membership: "join",
+ displayname: "lorem",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.displayName.changed", SENDER, "lorem", "ipsum"),
+ name: "User changed their display name",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "join",
+ displayname: "ipsum",
+ },
+ prevContent: {
+ membership: "join",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.displayName.set", SENDER, "ipsum"),
+ name: "User set their display name",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "join",
+ },
+ prevContent: {
+ membership: "join",
+ displayname: "lorem",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.displayName.remove", SENDER, "lorem"),
+ name: "User removed their display name",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "join",
+ },
+ prevContent: {
+ membership: "join",
+ },
+ sender: SENDER,
+ }),
+ result: null,
+ name: "Users join event was edited without relevant changes",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "join",
+ },
+ target: {
+ userId: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.joined", "@foo:example.com"),
+ name: "Users joined",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ },
+ prevContent: {
+ membership: "invite",
+ },
+ target: {
+ userId: "@test:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.rejectedInvite", "@test:example.com"),
+ name: "Invite rejected",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ },
+ prevContent: {
+ membership: "join",
+ },
+ target: {
+ userId: "@test:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.left", "@test:example.com"),
+ name: "Left room",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ },
+ prevContent: {
+ membership: "ban",
+ },
+ target: {
+ userId: "@target:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.unbanned", SENDER, "@target:example.com"),
+ name: "Unbanned",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ },
+ prevContent: {
+ membership: "join",
+ },
+ target: {
+ userId: "@target:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.kicked", SENDER, "@target:example.com"),
+ name: "Kicked without reason",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ reason: "lorem ipsum",
+ },
+ prevContent: {
+ membership: "join",
+ },
+ target: {
+ userId: "@target:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.kickedWithReason",
+ SENDER,
+ "@target:example.com",
+ "lorem ipsum"
+ ),
+ name: "Kicked with reason",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ reason: "lorem ipsum",
+ },
+ prevContent: {
+ membership: "invite",
+ },
+ target: {
+ userId: "@target:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.withdrewInviteWithReason",
+ SENDER,
+ "@target:example.com",
+ "lorem ipsum"
+ ),
+ name: "Invite withdrawn with reason",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ },
+ prevContent: {
+ membership: "invite",
+ },
+ target: {
+ userId: "@target:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.withdrewInvite", SENDER, "@target:example.com"),
+ name: "Invite withdrawn without reason",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMember,
+ content: {
+ membership: "leave",
+ },
+ prevContent: {
+ membership: "leave",
+ },
+ target: {
+ userId: "@target:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: null,
+ name: "No message for leave to leave",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ sender: SENDER,
+ }),
+ result: null,
+ name: "No previous power levels",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ },
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ },
+ },
+ sender: SENDER,
+ }),
+ result: null,
+ name: "No user power level changes",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 50,
+ },
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ },
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.powerLevel.changed",
+ SENDER,
+ _(
+ "message.powerLevel.fromTo",
+ "@foo:example.com",
+ _("powerLevel.default") + " (0)",
+ _("powerLevel.moderator") + " (50)"
+ )
+ ),
+ name: "Gave a user power levels",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 50,
+ },
+ users_default: 10,
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ },
+ users_default: 10,
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.powerLevel.changed",
+ SENDER,
+ _(
+ "message.powerLevel.fromTo",
+ "@foo:example.com",
+ _("powerLevel.default") + " (10)",
+ _("powerLevel.moderator") + " (50)"
+ )
+ ),
+ name: "Gave a user power levels with default level",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 10,
+ },
+ users_default: 10,
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 0,
+ },
+ users_default: 10,
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.powerLevel.changed",
+ SENDER,
+ _(
+ "message.powerLevel.fromTo",
+ "@foo:example.com",
+ _("powerLevel.restricted") + " (0)",
+ _("powerLevel.default") + " (10)"
+ )
+ ),
+ name: "Promote a restricted user to default",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 100,
+ },
+ users_default: 10,
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 50,
+ },
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.powerLevel.changed",
+ SENDER,
+ _(
+ "message.powerLevel.fromTo",
+ "@foo:example.com",
+ _("powerLevel.moderator") + " (50)",
+ _("powerLevel.admin") + " (100)"
+ )
+ ),
+ name: "Prompted user from moderator to admin",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 0,
+ },
+ users_default: 0,
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 100,
+ },
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.powerLevel.changed",
+ SENDER,
+ _(
+ "message.powerLevel.fromTo",
+ "@foo:example.com",
+ _("powerLevel.admin") + " (100)",
+ _("powerLevel.default") + " (0)"
+ )
+ ),
+ name: "Demote user from admin to default",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomPowerLevels,
+ content: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 50,
+ "@bar:example.com": 0,
+ },
+ users_default: 0,
+ },
+ prevContent: {
+ users: {
+ "@test:example.com": 100,
+ "@foo:example.com": 0,
+ "@bar:example.com": 50,
+ },
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.powerLevel.changed",
+ SENDER,
+ _(
+ "message.powerLevel.fromTo",
+ "@foo:example.com",
+ _("powerLevel.default") + " (0)",
+ _("powerLevel.moderator") + " (50)"
+ ) +
+ ", " +
+ _(
+ "message.powerLevel.fromTo",
+ "@bar:example.com",
+ _("powerLevel.moderator") + " (50)",
+ _("powerLevel.default") + " (0)"
+ )
+ ),
+ name: "Changed multiple users's power level",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomName,
+ content: {
+ name: "test",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.roomName.changed", SENDER, "test"),
+ name: "Set room name",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomName,
+ sender: SENDER,
+ }),
+ result: _("message.roomName.remove", SENDER),
+ name: "Remove room name",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomGuestAccess,
+ content: {
+ guest_access: MatrixSDK.GuestAccess.Forbidden,
+ },
+ sender: SENDER,
+ }),
+ result: _("message.guest.prevented", SENDER),
+ name: "Guest access forbidden",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomGuestAccess,
+ content: {
+ guest_access: MatrixSDK.GuestAccess.CanJoin,
+ },
+ sender: SENDER,
+ }),
+ result: _("message.guest.allowed", SENDER),
+ name: "Guest access allowed",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomHistoryVisibility,
+ content: {
+ history_visibility: MatrixSDK.HistoryVisibility.WorldReadable,
+ },
+ sender: SENDER,
+ }),
+ result: _("message.history.anyone", SENDER),
+ name: "History access granted to anyone",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomHistoryVisibility,
+ content: {
+ history_visibility: MatrixSDK.HistoryVisibility.Shared,
+ },
+ sender: SENDER,
+ }),
+ result: _("message.history.shared", SENDER),
+ name: "History access granted to members, including before they joined",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomHistoryVisibility,
+ content: {
+ history_visibility: MatrixSDK.HistoryVisibility.Invited,
+ },
+ sender: SENDER,
+ }),
+ result: _("message.history.invited", SENDER),
+ name: "History access granted to members, including invited",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomHistoryVisibility,
+ content: {
+ history_visibility: MatrixSDK.HistoryVisibility.Joined,
+ },
+ sender: SENDER,
+ }),
+ result: _("message.history.joined", SENDER),
+ name: "History access granted to members from the point they join",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.alias.main", SENDER, undefined, "#test:example.com"),
+ name: "Room alias added",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ },
+ prevContent: {
+ alias: "#old:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.alias.main",
+ SENDER,
+ "#old:example.com",
+ "#test:example.com"
+ ),
+ name: "Room alias changed",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ alt_aliases: ["#foo:example.com"],
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.alias.added", SENDER, "#foo:example.com"),
+ name: "Room alt alias added",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ alt_aliases: ["#foo:example.com"],
+ },
+ sender: SENDER,
+ }),
+ result: _("message.alias.removed", SENDER, "#foo:example.com"),
+ name: "Room alt alias removed",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ alt_aliases: ["#bar:example.com"],
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ alt_aliases: ["#foo:example.com", "#bar:example.com"],
+ },
+ sender: SENDER,
+ }),
+ result: _("message.alias.removed", SENDER, "#foo:example.com"),
+ name: "Room alt alias removed with multiple alts",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ alt_aliases: ["#foo:example.com", "#bar:example.com"],
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ alt_aliases: ["#bar:example.com"],
+ },
+ sender: SENDER,
+ }),
+ result: _("message.alias.added", SENDER, "#foo:example.com"),
+ name: "Room alt alias added with multiple alts",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ alt_aliases: [
+ "#foo:example.com",
+ "#bar:example.com",
+ "#baz:example.com",
+ ],
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ alt_aliases: ["#bar:example.com"],
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.alias.added",
+ SENDER,
+ "#foo:example.com, #baz:example.com"
+ ),
+ name: "Multiple room alt aliases added with multiple alts",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ alt_aliases: ["#foo:example.com", "#bar:example.com"],
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ alt_aliases: ["#bar:example.com", "#baz:example.com"],
+ },
+ sender: SENDER,
+ }),
+ result: _(
+ "message.alias.removedAndAdded",
+ SENDER,
+ "#baz:example.com",
+ "#foo:example.com"
+ ),
+ name: "Room alias added and removed",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomCanonicalAlias,
+ content: {
+ alias: "#test:example.com",
+ alt_aliases: [],
+ },
+ prevContent: {
+ alias: "#test:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: null,
+ name: "No discernible changes to the room aliases",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMessage,
+ content: {
+ msgtype: MatrixSDK.MsgType.KeyVerificationRequest,
+ to: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.verification.request2", SENDER, "@foo:example.com"),
+ name: "Inline key verification request",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.KeyVerificationRequest,
+ content: {
+ to: "@foo:example.com",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.verification.request2", SENDER, "@foo:example.com"),
+ name: "Key verification request",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.KeyVerificationCancel,
+ content: {
+ reason: "Lorem ipsum",
+ },
+ sender: SENDER,
+ }),
+ result: _("message.verification.cancel2", SENDER, "Lorem ipsum"),
+ name: "Key verification cancelled",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.KeyVerificationDone,
+ sender: SENDER,
+ }),
+ result: _("message.verification.done"),
+ name: "Key verification done",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomMessageEncrypted,
+ content: {
+ msgtype: "m.bad.encrypted",
+ },
+ }),
+ result: _("message.decryptionError"),
+ name: "Decryption error",
+ },
+ {
+ event: makeEvent({
+ type: MatrixSDK.EventType.RoomEncryption,
+ }),
+ result: _("message.encryptionStart"),
+ name: "Encryption start",
+ },
+];
+
+function testGetTextForMatrixEvent() {
+ for (const fixture of FIXTURES) {
+ const result = getMatrixTextForEvent(fixture.event);
+ equal(result, fixture.result, fixture.name);
+ }
+ run_next_test();
+}
diff --git a/comm/chat/protocols/matrix/test/test_roomTypeChange.js b/comm/chat/protocols/matrix/test/test_roomTypeChange.js
new file mode 100644
index 0000000000..df2bc39200
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/test_roomTypeChange.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+loadMatrix();
+
+add_task(async function test_toDMConversation() {
+ const acc = getAccount({});
+ const roomId = "#test:example.com";
+ acc.isDirectRoom = rId => roomId === rId;
+ const conversation = new MatrixRoom(acc, true, roomId);
+ conversation.initRoom(
+ getClientRoom(
+ roomId,
+ {
+ guessDMUserId() {
+ return "@user:example.com";
+ },
+ // Avoid running searchForVerificationRequests
+ getMyMembership() {
+ return "leave";
+ },
+ },
+ acc._client
+ )
+ );
+ await conversation.checkForUpdate();
+ ok(!conversation.isChat);
+ conversation.forget();
+});
+
+add_task(async function test_toGroupConversation() {
+ const acc = getAccount({});
+ const roomId = "#test:example.com";
+ acc.isDirectRoom = rId => roomId !== rId;
+ const conversation = new MatrixRoom(acc, false, roomId);
+ conversation.initRoom(
+ getClientRoom(
+ roomId,
+ {
+ guessDMUserId() {
+ return "@user:example.com";
+ },
+ // Avoid running searchForVerificationRequests
+ getMyMembership() {
+ return "leave";
+ },
+ },
+ acc._client
+ )
+ );
+ await conversation.checkForUpdate();
+ ok(conversation.isChat);
+ conversation.forget();
+});
diff --git a/comm/chat/protocols/matrix/test/xpcshell.ini b/comm/chat/protocols/matrix/test/xpcshell.ini
new file mode 100644
index 0000000000..92bcc7168e
--- /dev/null
+++ b/comm/chat/protocols/matrix/test/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+head = head.js
+tail =
+
+[test_matrixAccount.js]
+[test_matrixCommands.js]
+[test_matrixTextForEvent.js]
+[test_matrixMessage.js]
+[test_matrixMessageContent.js]
+[test_matrixPowerLevels.js]
+[test_matrixRoom.js]
+[test_roomTypeChange.js]