summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/im/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/im/test/browser')
-rw-r--r--comm/mail/components/im/test/browser/browser.ini26
-rw-r--r--comm/mail/components/im/test/browser/browser_browserRequest.js112
-rw-r--r--comm/mail/components/im/test/browser/browser_chatNotifications.js101
-rw-r--r--comm/mail/components/im/test/browser/browser_chatTelemetry.js52
-rw-r--r--comm/mail/components/im/test/browser/browser_contextMenu.js243
-rw-r--r--comm/mail/components/im/test/browser/browser_logs.js97
-rw-r--r--comm/mail/components/im/test/browser/browser_messagesMail.js235
-rw-r--r--comm/mail/components/im/test/browser/browser_readMessage.js49
-rw-r--r--comm/mail/components/im/test/browser/browser_removeMessage.js54
-rw-r--r--comm/mail/components/im/test/browser/browser_requestNotifications.js350
-rw-r--r--comm/mail/components/im/test/browser/browser_spacesToolbarChat.js255
-rw-r--r--comm/mail/components/im/test/browser/browser_tooltips.js194
-rw-r--r--comm/mail/components/im/test/browser/browser_updateMessage.js62
-rw-r--r--comm/mail/components/im/test/browser/head.js132
14 files changed, 1962 insertions, 0 deletions
diff --git a/comm/mail/components/im/test/browser/browser.ini b/comm/mail/components/im/test/browser/browser.ini
new file mode 100644
index 0000000000..5592953682
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser.ini
@@ -0,0 +1,26 @@
+[default]
+prefs =
+ ldap_2.servers.osx.description=
+ ldap_2.servers.osx.dirType=-1
+ ldap_2.servers.osx.uri=
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+ chat.otr.enable=false
+subsuite = thunderbird
+head = head.js
+
+[browser_browserRequest.js]
+[browser_chatNotifications.js]
+[browser_chatTelemetry.js]
+[browser_contextMenu.js]
+[browser_logs.js]
+[browser_messagesMail.js]
+[browser_readMessage.js]
+[browser_removeMessage.js]
+[browser_requestNotifications.js]
+[browser_spacesToolbarChat.js]
+[browser_tooltips.js]
+[browser_updateMessage.js]
diff --git a/comm/mail/components/im/test/browser/browser_browserRequest.js b/comm/mail/components/im/test/browser/browser_browserRequest.js
new file mode 100644
index 0000000000..7ffdb1c725
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_browserRequest.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { InteractiveBrowser, CancelledError } = ChromeUtils.importESModule(
+ "resource:///modules/InteractiveBrowser.sys.mjs"
+);
+const kBaseWindowUri = "chrome://messenger/content/browserRequest.xhtml";
+
+add_task(async function testBrowserRequestObserverNotification() {
+ const windowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(
+ undefined,
+ win => win.document.documentURI === kBaseWindowUri
+ );
+ let notifyLoaded;
+ const loadedPromise = new Promise(resolve => {
+ notifyLoaded = resolve;
+ });
+ const cancelledPromise = new Promise(resolve => {
+ Services.obs.notifyObservers(
+ {
+ promptText: "",
+ iconURI: "",
+ url: "about:blank",
+ cancelled() {
+ resolve();
+ },
+ loaded(window, webProgress) {
+ ok(webProgress);
+ notifyLoaded(window);
+ },
+ },
+ "browser-request"
+ );
+ });
+
+ const requestWindow = await windowPromise;
+ const loadedWindow = await loadedPromise;
+ ok(loadedWindow);
+ is(loadedWindow.document.documentURI, kBaseWindowUri);
+
+ const closeEvent = new Event("close");
+ requestWindow.dispatchEvent(closeEvent);
+ await BrowserTestUtils.closeWindow(requestWindow);
+
+ await cancelledPromise;
+});
+
+add_task(async function testWaitForRedirect() {
+ const initialUrl = "about:blank";
+ const promptText = "just testing";
+ const completionUrl = InteractiveBrowser.COMPLETION_URL + "/done?info=foo";
+ const windowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(
+ undefined,
+ win => win.document.documentURI === kBaseWindowUri
+ );
+ const request = InteractiveBrowser.waitForRedirect(initialUrl, promptText);
+ const requestWindow = await windowPromise;
+ is(requestWindow.document.title, promptText, "set window title");
+
+ const closedWindow = BrowserTestUtils.domWindowClosed(requestWindow);
+ const browser = requestWindow.document.getElementById("requestFrame");
+ await BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, completionUrl);
+ const result = await request;
+ is(result, completionUrl, "finished with correct URL");
+
+ await closedWindow;
+});
+
+add_task(async function testCancelWaitForRedirect() {
+ const initialUrl = "about:blank";
+ const promptText = "just testing";
+ const windowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(
+ undefined,
+ win => win.document.documentURI === kBaseWindowUri
+ );
+ const request = InteractiveBrowser.waitForRedirect(initialUrl, promptText);
+ const requestWindow = await windowPromise;
+ is(requestWindow.document.title, promptText, "set window title");
+
+ await new Promise(resolve => setTimeout(resolve));
+
+ const closeEvent = new Event("close");
+ requestWindow.dispatchEvent(closeEvent);
+ await BrowserTestUtils.closeWindow(requestWindow);
+
+ try {
+ await request;
+ ok(false, "request should be rejected");
+ } catch (error) {
+ ok(error instanceof CancelledError, "request was rejected");
+ }
+});
+
+add_task(async function testAlreadyComplete() {
+ const completionUrl = InteractiveBrowser.COMPLETION_URL + "/done?info=foo";
+ const promptText = "just testing";
+ const windowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(
+ undefined,
+ win => win.document.documentURI === kBaseWindowUri
+ );
+ const request = InteractiveBrowser.waitForRedirect(completionUrl, promptText);
+ const requestWindow = await windowPromise;
+ is(requestWindow.document.title, promptText, "set window title");
+
+ const closedWindow = BrowserTestUtils.domWindowClosed(requestWindow);
+ const result = await request;
+ is(result, completionUrl, "finished with correct URL");
+
+ await closedWindow;
+});
diff --git a/comm/mail/components/im/test/browser/browser_chatNotifications.js b/comm/mail/components/im/test/browser/browser_chatNotifications.js
new file mode 100644
index 0000000000..f902a9132b
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_chatNotifications.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../content/chat-messenger.js */
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+const { ChatIcons } = ChromeUtils.importESModule(
+ "resource:///modules/chatIcons.sys.mjs"
+);
+
+let originalAlertsServiceCID;
+let alertShown;
+const reset = () => {
+ alertShown = false;
+};
+
+add_setup(async () => {
+ reset();
+ class MockAlertsService {
+ QueryInterface = ChromeUtils.generateQI(["nsIAlertsService"]);
+ showAlert(alertInfo, listener) {
+ alertShown = true;
+ }
+ }
+ originalAlertsServiceCID = MockRegistrar.register(
+ "@mozilla.org/alerts-service;1",
+ new MockAlertsService()
+ );
+});
+
+registerCleanupFunction(() => {
+ MockRegistrar.unregister(originalAlertsServiceCID);
+});
+
+add_task(async function testNotificationsDisabled() {
+ Services.prefs.setBoolPref("mail.chat.show_desktop_notifications", false);
+
+ Services.obs.notifyObservers(
+ {
+ who: "notifier",
+ alias: "Notifier",
+ time: Date.now() / 1000 - 10,
+ displayMessage: "<strong>lorem ipsum</strong>",
+ action: false,
+ conversation: {
+ isChat: true,
+ },
+ },
+ "new-directed-incoming-message"
+ );
+
+ await TestUtils.waitForTick();
+ ok(!alertShown, "No alert shown when they are disabled");
+
+ Services.prefs.setBoolPref("mail.chat.show_desktop_notifications", true);
+ reset();
+
+ let soundPlayed = TestUtils.topicObserved("play-chat-notification-sound");
+ Services.obs.notifyObservers(
+ {
+ who: "notifier",
+ alias: "Notifier",
+ time: Date.now() / 1000 - 5,
+ displayMessage: "",
+ action: false,
+ conversation: {
+ isChat: true,
+ },
+ },
+ "new-directed-incoming-message"
+ );
+ await soundPlayed;
+ ok(!alertShown, "No alert shown with main window focused");
+
+ reset();
+
+ await openChatTab();
+
+ Services.obs.notifyObservers(
+ {
+ who: "notifier",
+ alias: "Notifier",
+ time: Date.now() / 1000,
+ displayMessage: "",
+ action: false,
+ conversation: {
+ isChat: true,
+ },
+ },
+ "new-directed-incoming-message"
+ );
+ await TestUtils.waitForTick();
+ ok(!alertShown, "No alert shown, no sound with chat tab focused");
+
+ await closeChatTab();
+});
diff --git a/comm/mail/components/im/test/browser/browser_chatTelemetry.js b/comm/mail/components/im/test/browser/browser_chatTelemetry.js
new file mode 100644
index 0000000000..4dbad87708
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_chatTelemetry.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+add_task(async function testMessageThemeTelemetry() {
+ Services.telemetry.clearScalars();
+
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent");
+ ok(
+ !scalars["tb.chat.active_message_theme"],
+ "Active chat theme not reported without open conversation."
+ );
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("collapse");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ const conversationLoaded = waitForConversationLoad(chatConv.convBrowser);
+ ok(chatConv, "found conversation");
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ await BrowserTestUtils.browserLoaded(chatConv.convBrowser);
+
+ await conversationLoaded;
+ scalars = TelemetryTestUtils.getProcessScalars("parent");
+ // NOTE: tb.chat.active_message_theme expires at v 117.
+ is(
+ scalars["tb.chat.active_message_theme"],
+ "mail:default",
+ "Active chat message theme and variant reported after opening conversation."
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_contextMenu.js b/comm/mail/components/im/test/browser/browser_contextMenu.js
new file mode 100644
index 0000000000..44afcb2a3b
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_contextMenu.js
@@ -0,0 +1,243 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testContextMenu() {
+ const account = IMServices.accounts.createAccount(
+ "context",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("context");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ const conversationLoaded = waitForConversationLoad();
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ await BrowserTestUtils.browserLoaded(chatConv.convBrowser);
+
+ await conversationLoaded;
+
+ const contextMenu = document.getElementById("chatConversationContextMenu");
+ ok(BrowserTestUtils.is_hidden(contextMenu));
+
+ const popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouse(
+ "body",
+ 0,
+ 0,
+ { type: "contextmenu" },
+ chatConv.convBrowser,
+ true
+ );
+ await popupShown;
+
+ const popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ // Assume normal context menu semantics work and just close it directly.
+ contextMenu.hidePopup();
+ await popupHidden;
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testMessageContextMenuOnLink() {
+ const account = IMServices.accounts.createAccount(
+ "context",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("linker");
+
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ await BrowserTestUtils.browserLoaded(chatConv.convBrowser);
+
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+
+ conversation.addMessages([
+ {
+ who: "linker",
+ content: "hi https://example.com/",
+ options: {
+ incoming: true,
+ },
+ },
+ {
+ who: "linker",
+ content: "hi mailto:test@example.com",
+ options: {
+ incoming: true,
+ },
+ },
+ ]);
+ // Wait for at least one event.
+ do {
+ await BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ } while (chatConv.convBrowser.getPendingMessagesCount() > 0);
+
+ const contextMenu = document.getElementById("chatConversationContextMenu");
+ ok(BrowserTestUtils.is_hidden(contextMenu));
+
+ const popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouse(
+ ".message:nth-child(1) a",
+ 0,
+ 0,
+ { type: "contextmenu", centered: true },
+ chatConv.convBrowser,
+ true
+ );
+ await popupShown;
+
+ ok(
+ BrowserTestUtils.is_visible(contextMenu.querySelector("#context-openlink")),
+ "open link"
+ );
+ ok(
+ BrowserTestUtils.is_visible(contextMenu.querySelector("#context-copylink")),
+ "copy link"
+ );
+
+ const popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ // Assume normal context menu semantics work and just close it directly.
+ contextMenu.hidePopup();
+ await popupHidden;
+
+ const popupShownAgain = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ BrowserTestUtils.synthesizeMouse(
+ ".message:nth-child(2) a",
+ 0,
+ 0,
+ { type: "contextmenu", centered: true },
+ chatConv.convBrowser,
+ true
+ );
+ await popupShownAgain;
+
+ ok(
+ BrowserTestUtils.is_visible(
+ contextMenu.querySelector("#context-copyemail")
+ ),
+ "copy mail"
+ );
+
+ const popupHiddenAgain = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ // Assume normal context menu semantics work and just close it directly.
+ contextMenu.hidePopup();
+ await popupHiddenAgain;
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testMessageAction() {
+ const account = IMServices.accounts.createAccount(
+ "context",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("context");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ await BrowserTestUtils.browserLoaded(chatConv.convBrowser);
+
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+
+ const messagePromise = waitForNotification(conversation, "new-text");
+ const displayedPromise = BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ conversation.writeMessage("context", "hello world", {
+ incoming: true,
+ });
+ const { subject: message } = await messagePromise;
+ await displayedPromise;
+
+ const contextMenu = document.getElementById("chatConversationContextMenu");
+ ok(BrowserTestUtils.is_hidden(contextMenu));
+
+ const popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouse(
+ ".message:nth-child(1)",
+ 0,
+ 0,
+ { type: "contextmenu", centered: true },
+ chatConv.convBrowser,
+ true
+ );
+ await popupShown;
+
+ const separator = contextMenu.querySelector("#context-sep-messageactions");
+ if (!BrowserTestUtils.is_visible(separator)) {
+ await BrowserTestUtils.waitForMutationCondition(
+ separator,
+ {
+ subtree: false,
+ childList: false,
+ attributes: true,
+ attributeFilter: ["hidden"],
+ },
+ () => BrowserTestUtils.is_visible(separator)
+ );
+ }
+ const item = contextMenu.querySelector(
+ "#context-sep-messageactions + menuitem"
+ );
+ ok(item, "Item for message action injected");
+ is(item.getAttribute("label"), "Test");
+
+ const popupHiddenAgain = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ item.click();
+ // Assume normal context menu semantics work and just close it.
+ contextMenu.hidePopup();
+ await Promise.all([message.actionRan, popupHiddenAgain]);
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_logs.js b/comm/mail/components/im/test/browser/browser_logs.js
new file mode 100644
index 0000000000..2f95a2accd
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_logs.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+
+add_task(async function testTopicRestored() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation =
+ account.prplAccount.wrappedJSObject.makeMUC("logs topic");
+ let convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ let chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ const browserDisplayed = BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+
+ conversation.addParticipant("topic");
+ conversation.addMessages([
+ {
+ who: "topic",
+ content: "hi",
+ options: {
+ incoming: true,
+ },
+ },
+ ]);
+ await browserDisplayed;
+
+ // Close and re-open conversation to get logs
+ conversation.close();
+ const newConversation =
+ account.prplAccount.wrappedJSObject.makeMUC("logs topic");
+ convNode = getConversationItem(newConversation);
+ ok(convNode);
+
+ let conversationLoaded = waitForConversationLoad();
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ chatConv = getChatConversationElement(newConversation);
+ ok(chatConv, "found conversation");
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+
+ const topicChanged = waitForNotification(
+ newConversation,
+ "chat-update-topic"
+ );
+ newConversation.setTopic("foo bar", "topic");
+ await topicChanged;
+ const logTree = document.getElementById("logTree");
+ const chatTopInfo = document.querySelector("chat-conversation-info");
+
+ is(chatTopInfo.topic.value, "foo bar");
+
+ // Wait for log list to be populated, sadly there is no event and it is delayed by promises.
+ await TestUtils.waitForCondition(() => logTree.view.rowCount > 0);
+
+ await conversationLoaded;
+ const logBrowser = document.getElementById("conv-log-browser");
+ conversationLoaded = waitForConversationLoad(logBrowser);
+ mailTestUtils.treeClick(EventUtils, window, logTree, 0, 0, {
+ clickCount: 1,
+ });
+ await conversationLoaded;
+
+ ok(BrowserTestUtils.is_visible(logBrowser));
+ is(chatTopInfo.topic.value, "", "Topic is cleared when viewing logs");
+
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("goToConversation"),
+ {}
+ );
+
+ ok(BrowserTestUtils.is_hidden(logBrowser));
+ is(chatTopInfo.topic.value, "foo bar");
+
+ newConversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_messagesMail.js b/comm/mail/components/im/test/browser/browser_messagesMail.js
new file mode 100644
index 0000000000..6bc73c723c
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_messagesMail.js
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testCollapse() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("collapse");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ const messageParent = await getChatMessageParent(chatConv);
+
+ await addNotice(conversation, chatConv);
+
+ is(
+ messageParent.querySelector(".event-row:nth-child(1) .body").textContent,
+ "test notice",
+ "notice added to conv"
+ );
+
+ await addNotice(conversation, chatConv);
+ await addNotice(conversation, chatConv);
+ await addNotice(conversation, chatConv);
+ await Promise.all([
+ await addNotice(conversation, chatConv),
+ BrowserTestUtils.waitForMutationCondition(
+ messageParent,
+ {
+ subtree: true,
+ childList: true,
+ attributes: true,
+ attributeFilter: ["class"],
+ },
+ () => messageParent.querySelector(".hide-children")
+ ),
+ ]);
+
+ const hiddenGroup = messageParent.querySelector(".hide-children");
+ const toggle = hiddenGroup.querySelector(".eventToggle");
+ ok(toggle);
+ ok(hiddenGroup.querySelectorAll(".event-row").length >= 5);
+
+ toggle.click();
+ await BrowserTestUtils.waitForMutationCondition(
+ hiddenGroup,
+ {
+ attributes: true,
+ attributeFilter: ["class"],
+ },
+ () => !hiddenGroup.classList.contains("hide-children")
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testGrouping() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(
+ BrowserTestUtils.is_visible(document.getElementById("chatPanel")),
+ "Chat tab is visible"
+ );
+
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("grouping");
+ const convNode = getConversationItem(conversation);
+ ok(convNode, "Conversation is in contacts list");
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "Found conversation element");
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ const messageParent = await getChatMessageParent(chatConv);
+
+ conversation.addMessages([
+ {
+ who: "grouping",
+ content: "system message",
+ options: {
+ system: true,
+ incoming: true,
+ },
+ },
+ {
+ who: "grouping",
+ content: "normal message",
+ options: {
+ incoming: true,
+ },
+ },
+ {
+ who: "grouping",
+ content: "another system message",
+ options: {
+ system: true,
+ incoming: true,
+ },
+ },
+ ]);
+ // Wait for at least one event.
+ do {
+ await BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ } while (chatConv.convBrowser.getPendingMessagesCount() > 0);
+
+ for (let child of messageParent.children) {
+ isnot(child.id, "insert", "Message element is not the insert point");
+ }
+ is(
+ messageParent.childElementCount,
+ 3,
+ "All three messages are their own top level element"
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testSystemMessageReplacement() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(
+ BrowserTestUtils.is_visible(document.getElementById("chatPanel")),
+ "Chat tab is visible"
+ );
+
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("replacing");
+ const convNode = getConversationItem(conversation);
+ ok(convNode, "Conversation is in contacts list");
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "Found conversation element");
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ const messageParent = await getChatMessageParent(chatConv);
+
+ conversation.addMessages([
+ {
+ who: "replacing",
+ content: "system message",
+ options: {
+ system: true,
+ incoming: true,
+ remoteId: "foo",
+ },
+ },
+ {
+ who: "replacing",
+ content: "another system message",
+ options: {
+ system: true,
+ incoming: true,
+ remoteId: "bar",
+ },
+ },
+ ]);
+ // Wait for at least one event.
+ do {
+ await BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ } while (chatConv.convBrowser.getPendingMessagesCount() > 0);
+
+ const updateTextPromise = waitForNotification(conversation, "update-text");
+ conversation.updateMessage("replacing", "better system message", {
+ system: true,
+ incoming: true,
+ remoteId: "foo",
+ });
+ await updateTextPromise;
+ await TestUtils.waitForTick();
+
+ is(messageParent.childElementCount, 1, "Only one message group in browser");
+ is(
+ messageParent.firstElementChild.childElementCount,
+ 3,
+ "Has two messages plus insert inside group"
+ );
+ const firstMessage = messageParent.firstElementChild.firstElementChild;
+ ok(
+ firstMessage.classList.contains("event-row"),
+ "Replacement message is an event-row"
+ );
+ is(firstMessage.dataset.remoteId, "foo");
+ is(
+ firstMessage.querySelector(".body").textContent,
+ "better system message",
+ "Message content was updated"
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+function addNotice(conversation, uiConversation) {
+ conversation.addNotice();
+ return BrowserTestUtils.waitForEvent(
+ uiConversation.convBrowser,
+ "MessagesDisplayed"
+ );
+}
diff --git a/comm/mail/components/im/test/browser/browser_readMessage.js b/comm/mail/components/im/test/browser/browser_readMessage.js
new file mode 100644
index 0000000000..e290cb36fb
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_readMessage.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testDisplayed() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeMUC("collapse");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ ok(!convNode.hasAttribute("unread"), "No unread messages");
+
+ const messagePromise = waitForNotification(conversation, "new-text");
+ conversation.writeMessage("mochitest", "hello world", {
+ incoming: true,
+ });
+ const { subject: message } = await messagePromise;
+
+ ok(convNode.hasAttribute("unread"), "Unread message waiting");
+ is(convNode.getAttribute("unreadCount"), "(1)");
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ const browserDisplayed = BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+
+ await browserDisplayed;
+ await message.displayed;
+
+ ok(!convNode.hasAttribute("unread"), "Message read");
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_removeMessage.js b/comm/mail/components/im/test/browser/browser_removeMessage.js
new file mode 100644
index 0000000000..0d95bb77b5
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_removeMessage.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testRemove() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeMUC("collapse");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ conversation.writeMessage("mochitest", "hello world", {
+ incoming: true,
+ remoteId: "foo",
+ });
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ const browserDisplayed = BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ const messageParent = await getChatMessageParent(chatConv);
+ await browserDisplayed;
+
+ is(
+ messageParent.querySelector(".message.incoming:nth-child(1) .ib-msg-txt")
+ .textContent,
+ "hello world",
+ "message added to conv"
+ );
+
+ const updateTextPromise = waitForNotification(conversation, "remove-text");
+ conversation.removeMessage("foo");
+ await updateTextPromise;
+ await TestUtils.waitForTick();
+
+ ok(!messageParent.querySelector(".message"));
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_requestNotifications.js b/comm/mail/components/im/test/browser/browser_requestNotifications.js
new file mode 100644
index 0000000000..62128add8b
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_requestNotifications.js
@@ -0,0 +1,350 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testGrantingBuddyRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const notificationTopic = TestUtils.topicObserved(
+ "buddy-authorization-request"
+ );
+ const requestPromise = new Promise((resolve, reject) => {
+ prplAccount.addBuddyRequest("test-user", resolve, reject);
+ });
+ const [request] = await notificationTopic;
+ is(request.userName, "test-user");
+ is(request.account.id, account.id);
+ await TestUtils.waitForTick();
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value = "buddy-auth-request-" + request.account.id + request.userName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ ok(
+ BrowserTestUtils.is_hidden(notification.closeButton),
+ "Can't dismiss without interacting"
+ );
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+
+ EventUtils.synthesizeMouseAtCenter(
+ notification.buttonContainer.firstElementChild,
+ {}
+ );
+ await requestPromise;
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testCancellingBuddyRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const notificationTopic = TestUtils.topicObserved(
+ "buddy-authorization-request"
+ );
+ prplAccount.addBuddyRequest(
+ "test-user",
+ () => {
+ ok(false, "request was granted");
+ },
+ () => {
+ ok(false, "request was denied");
+ }
+ );
+ const [request] = await notificationTopic;
+ is(request.userName, "test-user");
+ is(request.account.id, account.id);
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value = "buddy-auth-request-" + request.account.id + request.userName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+
+ const cancelTopic = TestUtils.topicObserved(
+ "buddy-authorization-request-canceled"
+ );
+ prplAccount.cancelBuddyRequest("test-user");
+ const [canceledRequest] = await cancelTopic;
+ is(canceledRequest.userName, request.userName);
+ is(canceledRequest.account.id, request.account.id);
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testDenyingBuddyRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const notificationTopic = TestUtils.topicObserved(
+ "buddy-authorization-request"
+ );
+ const requestPromise = new Promise((resolve, reject) => {
+ prplAccount.addBuddyRequest("test-user", reject, resolve);
+ });
+ const [request] = await notificationTopic;
+ is(request.userName, "test-user");
+ is(request.account.id, account.id);
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value = "buddy-auth-request-" + request.account.id + request.userName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+
+ EventUtils.synthesizeMouseAtCenter(
+ notification.buttonContainer.lastElementChild,
+ {}
+ );
+ await requestPromise;
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testGrantingChatRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const requestTopic = TestUtils.topicObserved("conv-authorization-request");
+ const requestPromise = new Promise((resolve, reject) => {
+ prplAccount.addChatRequest("test-chat", resolve, reject);
+ });
+ const [request] = await requestTopic;
+ is(request.conversationName, "test-chat");
+ is(request.account.id, account.id);
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value =
+ "conv-auth-request-" + request.account.id + request.conversationName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ ok(
+ BrowserTestUtils.is_hidden(notification.closeButton),
+ "Can't dismiss without interacting"
+ );
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+
+ EventUtils.synthesizeMouseAtCenter(
+ notification.buttonContainer.firstElementChild,
+ {}
+ );
+ await requestPromise;
+ const result = await request.completePromise;
+ ok(result);
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testCancellingChatRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(
+ BrowserTestUtils.is_visible(document.getElementById("chatPanel")),
+ "chat tab visible"
+ );
+
+ const requestTopic = TestUtils.topicObserved("conv-authorization-request");
+ prplAccount.addChatRequest(
+ "test-chat",
+ () => {
+ ok(false, "chat request was granted");
+ },
+ () => {
+ ok(false, "chat request was denied");
+ }
+ );
+ const [request] = await requestTopic;
+ is(request.conversationName, "test-chat", "conversation name matches");
+ is(request.account.id, account.id, "account id matches");
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value =
+ "conv-auth-request-" + request.account.id + request.conversationName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+
+ prplAccount.cancelChatRequest("test-chat");
+ await Assert.rejects(
+ request.completePromise,
+ /Cancelled/,
+ "completePromise is rejected to indicate cancellation"
+ );
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testDenyingChatRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const requestTopic = TestUtils.topicObserved("conv-authorization-request");
+ const requestPromise = new Promise((resolve, reject) => {
+ prplAccount.addChatRequest("test-chat", reject, resolve);
+ });
+ const [request] = await requestTopic;
+ is(request.conversationName, "test-chat");
+ is(request.account.id, account.id);
+ ok(request.canDeny);
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value =
+ "conv-auth-request-" + request.account.id + request.conversationName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+
+ EventUtils.synthesizeMouseAtCenter(
+ notification.buttonContainer.lastElementChild,
+ {}
+ );
+ await requestPromise;
+ const result = await request.completePromise;
+ ok(!result);
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testUndenyableChatRequest() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ const prplAccount = account.prplAccount.wrappedJSObject;
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const requestTopic = TestUtils.topicObserved("conv-authorization-request");
+ const requestPromise = new Promise(resolve => {
+ prplAccount.addChatRequest("test-chat", resolve);
+ });
+ const [request] = await requestTopic;
+ is(request.conversationName, "test-chat");
+ is(request.account.id, account.id);
+ ok(!request.canDeny);
+
+ const notificationBox = window.chatHandler.msgNotificationBar;
+ const value =
+ "conv-auth-request-" + request.account.id + request.conversationName;
+ const notification = notificationBox.getNotificationWithValue(value);
+ ok(notification, "notification shown");
+ const closePromise = new Promise(resolve => {
+ notification.eventCallback = event => {
+ resolve();
+ };
+ });
+ is(notification.buttonContainer.children.length, 1);
+
+ EventUtils.synthesizeMouseAtCenter(
+ notification.buttonContainer.firstElementChild,
+ {}
+ );
+ await requestPromise;
+ const result = await request.completePromise;
+ ok(result);
+
+ await closePromise;
+ ok(!notificationBox.getNotificationWithValue(value), "notification closed");
+
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_spacesToolbarChat.js b/comm/mail/components/im/test/browser/browser_spacesToolbarChat.js
new file mode 100644
index 0000000000..d95b5e48c0
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_spacesToolbarChat.js
@@ -0,0 +1,255 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function test_spacesToolbarChatBadgeMUC() {
+ window.gSpacesToolbar.toggleToolbar(false);
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ if (window.chatHandler._chatButtonUpdatePending) {
+ await TestUtils.waitForTick();
+ }
+
+ const chatButton = document.getElementById("chatButton");
+
+ ok(
+ !chatButton.classList.contains("has-badge"),
+ "Initially no unread chat messages"
+ );
+
+ // Send a new message in a MUC that is not currently open.
+ const conversation =
+ account.prplAccount.wrappedJSObject.makeMUC("noSpaceBadge");
+ const messagePromise = waitForNotification(conversation, "new-text");
+ conversation.writeMessage("spaceBadge", "just a normal message", {
+ incoming: true,
+ });
+ await messagePromise;
+ // Make sure nothing else was waiting to happen.
+ await TestUtils.waitForTick();
+
+ ok(
+ !chatButton.classList.contains("has-badge"),
+ "Untargeted MUC message doesn't change badge"
+ );
+
+ // Send a new targeted message in the conversation.
+ const unreadContainer = chatButton.querySelector(".spaces-badge-container");
+ const unreadContainerText = unreadContainer.textContent;
+ const unreadCountChanged = TestUtils.topicObserved("unread-im-count-changed");
+ conversation.writeMessage("spaceBadge", "new direct message", {
+ incoming: true,
+ containsNick: true,
+ });
+ await unreadCountChanged;
+ ok(chatButton.classList.contains("has-badge"), "Unread badge is shown");
+
+ // Fluent doesn't immediately apply the translation, wait for it.
+ await TestUtils.waitForCondition(
+ () => unreadContainer.textContent !== unreadContainerText
+ );
+
+ is(unreadContainer.textContent, "1", "Unread count is in badge");
+ ok(unreadContainer.title);
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function test_spacesToolbarChatBadgeDM() {
+ window.gSpacesToolbar.toggleToolbar(false);
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ if (window.chatHandler._chatButtonUpdatePending) {
+ await TestUtils.waitForTick();
+ }
+
+ const chatButton = document.getElementById("chatButton");
+
+ ok(
+ !chatButton.classList.contains("has-badge"),
+ "Initially no unread chat messages"
+ );
+
+ const unreadContainer = chatButton.querySelector(".spaces-badge-container");
+ if (unreadContainer.textContent !== "0") {
+ await BrowserTestUtils.waitForMutationCondition(
+ unreadContainer,
+ {
+ subtree: true,
+ childList: true,
+ characterData: true,
+ },
+ () => unreadContainer.textContent === "0"
+ );
+ }
+
+ // Send a new message in a DM conversation that is not currently open.
+ const unreadContainerText = unreadContainer.textContent;
+ let unreadCountChanged = TestUtils.topicObserved("unread-im-count-changed");
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("spaceBadge");
+ conversation.writeMessage("spaceBadge", "new direct message", {
+ incoming: true,
+ });
+ await unreadCountChanged;
+ ok(chatButton.classList.contains("has-badge"), "Unread badge is shown");
+
+ // Fluent doesn't immediately apply the translation, wait for it.
+ await TestUtils.waitForCondition(
+ () => unreadContainer.textContent !== unreadContainerText
+ );
+
+ is(unreadContainer.textContent, "1", "Unread count is in badge");
+ ok(unreadContainer.title);
+
+ // Display the DM conversation.
+ unreadCountChanged = TestUtils.topicObserved("unread-im-count-changed");
+ await openChatTab();
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv);
+ ok(BrowserTestUtils.is_visible(chatConv));
+ await unreadCountChanged;
+
+ ok(
+ !chatButton.classList.contains("has-badge"),
+ "Unread badge is hidden again"
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function test_spacesToolbarPinnedChatBadgeMUC() {
+ window.gSpacesToolbar.toggleToolbar(true);
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ if (window.chatHandler._chatButtonUpdatePending) {
+ await TestUtils.waitForTick();
+ }
+
+ const spacesPopupButtonChat = document.getElementById(
+ "spacesPopupButtonChat"
+ );
+
+ ok(
+ !spacesPopupButtonChat.classList.contains("has-badge"),
+ "Initially no unread chat messages"
+ );
+
+ // Send a new message in a MUC that is not currently open.
+ const conversation =
+ account.prplAccount.wrappedJSObject.makeMUC("noSpaceBadge");
+ const messagePromise = waitForNotification(conversation, "new-text");
+ conversation.writeMessage("spaceBadge", "just a normal message", {
+ incoming: true,
+ });
+ await messagePromise;
+ // Make sure nothing else was waiting to happen.
+ await TestUtils.waitForTick();
+
+ ok(
+ !spacesPopupButtonChat.classList.contains("has-badge"),
+ "Untargeted MUC message doesn't change badge"
+ );
+
+ // Send a new targeted message in the conversation.
+ const unreadCountChanged = TestUtils.topicObserved("unread-im-count-changed");
+ conversation.writeMessage("spaceBadge", "new direct message", {
+ incoming: true,
+ containsNick: true,
+ });
+ await unreadCountChanged;
+ ok(
+ spacesPopupButtonChat.classList.contains("has-badge"),
+ "Unread badge is shown"
+ );
+ ok(
+ document
+ .getElementById("spacesPinnedButton")
+ .classList.contains("has-badge"),
+ "Unread state is propagated to pinned menu button"
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function test_spacesToolbarPinnedChatBadgeDM() {
+ window.gSpacesToolbar.toggleToolbar(true);
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ if (window.chatHandler._chatButtonUpdatePending) {
+ await TestUtils.waitForTick();
+ }
+
+ const spacesPopupButtonChat = document.getElementById(
+ "spacesPopupButtonChat"
+ );
+ const spacesPinnedButton = document.getElementById("spacesPinnedButton");
+
+ ok(
+ !spacesPopupButtonChat.classList.contains("has-badge"),
+ "Initially no unread chat messages"
+ );
+ ok(!spacesPinnedButton.classList.contains("has-badge"));
+
+ // Send a new message in a DM conversation that is not currently open.
+ let unreadCountChanged = TestUtils.topicObserved("unread-im-count-changed");
+ const conversation = account.prplAccount.wrappedJSObject.makeDM("spaceBadge");
+ conversation.writeMessage("spaceBadge", "new direct message", {
+ incoming: true,
+ });
+ await unreadCountChanged;
+ ok(
+ spacesPopupButtonChat.classList.contains("has-badge"),
+ "Unread badge is shown"
+ );
+ ok(spacesPinnedButton.classList.contains("has-badge"));
+
+ // Display the DM conversation.
+ unreadCountChanged = TestUtils.topicObserved("unread-im-count-changed");
+ await openChatTab();
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv);
+ ok(BrowserTestUtils.is_visible(chatConv));
+ await unreadCountChanged;
+
+ ok(
+ !spacesPopupButtonChat.classList.contains("has-badge"),
+ "Unread badge is hidden again"
+ );
+ ok(!spacesPinnedButton.classList.contains("has-badge"));
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/browser_tooltips.js b/comm/mail/components/im/test/browser/browser_tooltips.js
new file mode 100644
index 0000000000..db8a7fd86b
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_tooltips.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testMUCMessageSenderTooltip() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ const conversation = account.prplAccount.wrappedJSObject.makeMUC("tooltips");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv);
+ ok(BrowserTestUtils.is_visible(chatConv));
+ const messageParent = await getChatMessageParent(chatConv);
+
+ conversation.addParticipant("foo", "1");
+ conversation.addParticipant("bar", "2");
+ conversation.addParticipant("loremipsum", "3");
+ conversation.addMessages([
+ // Message without alias
+ {
+ who: "foo",
+ content: "hi",
+ options: {
+ incoming: true,
+ },
+ },
+ // Message with alias
+ {
+ who: "bar",
+ content: "o/",
+ options: {
+ incoming: true,
+ _alias: "Bar",
+ },
+ },
+ // Alias is not directly related to nick
+ {
+ who: "loremipsum",
+ content: "what's up?",
+ options: {
+ incoming: true,
+ _alias: "Dolor sit amet",
+ },
+ },
+ ]);
+ // Wait for at least one event.
+ do {
+ await BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ } while (chatConv.convBrowser.getPendingMessagesCount() > 0);
+
+ const tooltip = document.getElementById("imTooltip");
+ const tooltipTests = [
+ {
+ messageIndex: 1,
+ who: "foo",
+ alias: "1",
+ displayed: "foo",
+ },
+ {
+ messageIndex: 2,
+ who: "bar",
+ alias: "2",
+ displayed: "Bar",
+ },
+ {
+ messageIndex: 3,
+ who: "loremipsum",
+ alias: "3",
+ displayed: "Dolor sit amet",
+ },
+ ];
+ window.windowUtils.disableNonTestMouseEvents(true);
+ try {
+ for (const testInfo of tooltipTests) {
+ const usernameSelector = `.message:nth-child(${testInfo.messageIndex}) .ib-sender`;
+ const username = messageParent.querySelector(usernameSelector);
+ is(username.textContent, testInfo.displayed);
+
+ let buddyInfo = TestUtils.topicObserved(
+ "user-info-received",
+ (subject, data) => data === testInfo.who
+ );
+ await showTooltip(usernameSelector, tooltip, chatConv.convBrowser);
+
+ is(tooltip.getAttribute("displayname"), testInfo.who);
+ await buddyInfo;
+ is(tooltip.table.querySelector("td").textContent, testInfo.alias);
+ await hideTooltip(tooltip, chatConv.convBrowser);
+ }
+ } finally {
+ window.windowUtils.disableNonTestMouseEvents(false);
+ }
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+add_task(async function testTimestampTooltip() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ const conversation = account.prplAccount.wrappedJSObject.makeMUC("tooltips");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv);
+ ok(BrowserTestUtils.is_visible(chatConv));
+
+ const messageTime = Math.floor(Date.now() / 1000);
+
+ conversation.addParticipant("foo", "1");
+ conversation.addMessages([
+ {
+ who: "foo",
+ content: "hi",
+ options: {
+ incoming: true,
+ },
+ time: messageTime,
+ },
+ ]);
+ // Wait for at least one event.
+ do {
+ await BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ } while (chatConv.convBrowser.getPendingMessagesCount() > 0);
+
+ const tooltip = document.getElementById("imTooltip");
+ window.windowUtils.disableNonTestMouseEvents(true);
+ try {
+ const messageSelector = ".message:nth-child(1)";
+ const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
+ timeStyle: "medium",
+ });
+ const expectedText = dateTimeFormatter.format(new Date(messageTime * 1000));
+
+ await showTooltip(messageSelector, tooltip, chatConv.convBrowser);
+
+ const htmlTooltip = tooltip.querySelector(".htmlTooltip");
+ ok(BrowserTestUtils.is_visible(htmlTooltip));
+ is(htmlTooltip.textContent, expectedText);
+ await hideTooltip(tooltip, chatConv.convBrowser);
+ } finally {
+ window.windowUtils.disableNonTestMouseEvents(false);
+ }
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
+
+async function showTooltip(elementSelector, tooltip, browser) {
+ const popupShown = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ elementSelector,
+ { type: "mousemove" },
+ browser
+ );
+ return popupShown;
+}
+
+async function hideTooltip(tooltip, browser) {
+ const popupHidden = BrowserTestUtils.waitForEvent(tooltip, "popuphidden");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ ".message .body",
+ { type: "mousemove" },
+ browser
+ );
+ return popupHidden;
+}
diff --git a/comm/mail/components/im/test/browser/browser_updateMessage.js b/comm/mail/components/im/test/browser/browser_updateMessage.js
new file mode 100644
index 0000000000..1aa74a9c64
--- /dev/null
+++ b/comm/mail/components/im/test/browser/browser_updateMessage.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function testUpdate() {
+ const account = IMServices.accounts.createAccount(
+ "testuser",
+ "prpl-mochitest"
+ );
+ account.password = "this is a test";
+ account.connect();
+
+ await openChatTab();
+ ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
+
+ const conversation = account.prplAccount.wrappedJSObject.makeMUC("collapse");
+ const convNode = getConversationItem(conversation);
+ ok(convNode);
+
+ conversation.writeMessage("mochitest", "hello world", {
+ incoming: true,
+ remoteId: "foo",
+ });
+
+ await EventUtils.synthesizeMouseAtCenter(convNode, {});
+
+ const chatConv = getChatConversationElement(conversation);
+ ok(chatConv, "found conversation");
+ const browserDisplayed = BrowserTestUtils.waitForEvent(
+ chatConv.convBrowser,
+ "MessagesDisplayed"
+ );
+ ok(BrowserTestUtils.is_visible(chatConv), "conversation visible");
+ const messageParent = await getChatMessageParent(chatConv);
+ await browserDisplayed;
+
+ is(
+ messageParent.querySelector(".message.incoming:nth-child(1) .ib-msg-txt")
+ .textContent,
+ "hello world",
+ "message added to conv"
+ );
+
+ const updateTextPromise = waitForNotification(conversation, "update-text");
+ conversation.updateMessage("mochitest", "bye world", {
+ incoming: true,
+ remoteId: "foo",
+ });
+ await updateTextPromise;
+ await TestUtils.waitForTick();
+
+ is(
+ messageParent.querySelector(".message.incoming:nth-child(1) .ib-msg-txt")
+ .textContent,
+ "bye world",
+ "message text updated"
+ );
+
+ conversation.close();
+ account.disconnect();
+ IMServices.accounts.deleteAccount(account.id);
+});
diff --git a/comm/mail/components/im/test/browser/head.js b/comm/mail/components/im/test/browser/head.js
new file mode 100644
index 0000000000..b80d274149
--- /dev/null
+++ b/comm/mail/components/im/test/browser/head.js
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { registerTestProtocol, unregisterTestProtocol } =
+ ChromeUtils.importESModule("resource://testing-common/TestProtocol.sys.mjs");
+var { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+);
+
+async function openChatTab() {
+ let tabmail = document.getElementById("tabmail");
+ let chatMode = tabmail.tabModes.chat;
+
+ if (chatMode.tabs.length == 1) {
+ tabmail.selectedTab = chatMode.tabs[0];
+ } else {
+ window.showChatTab();
+ }
+
+ is(chatMode.tabs.length, 1, "chat tab is open");
+ is(tabmail.selectedTab, chatMode.tabs[0], "chat tab is selected");
+
+ await new Promise(resolve => setTimeout(resolve));
+}
+
+async function closeChatTab() {
+ let tabmail = document.getElementById("tabmail");
+ let chatMode = tabmail.tabModes.chat;
+
+ if (chatMode.tabs.length == 1) {
+ tabmail.closeTab(chatMode.tabs[0]);
+ }
+
+ is(chatMode.tabs.length, 0, "chat tab is not open");
+
+ await new Promise(resolve => setTimeout(resolve));
+}
+
+/**
+ * @param {prplIConversation} conversation
+ * @returns {HTMLElement} The corresponding chat-imconv-richlistitem element.
+ */
+function getConversationItem(conversation) {
+ const convList = document.getElementById("contactlistbox");
+ const convNode = Array.from(convList.children).find(
+ element =>
+ element.getAttribute("is") === "chat-imconv-richlistitem" &&
+ element.getAttribute("displayname") === conversation.name
+ );
+ return convNode;
+}
+
+/**
+ * @param {prplIConversation} conversation
+ * @returns {HTMLElement} The corresponding chat-conversation element.
+ */
+function getChatConversationElement(conversation) {
+ const chatConv = Array.from(
+ document.querySelectorAll("chat-conversation")
+ ).find(element => element._conv.target.wrappedJSObject === conversation);
+ return chatConv;
+}
+
+/**
+ * @param {HTMLElement} chatConv - chat-conversation element.
+ * @returns {HTMLElement} The parent element to all chat messages.
+ */
+async function getChatMessageParent(chatConv) {
+ await BrowserTestUtils.browserLoaded(chatConv.convBrowser);
+ const messageParent = chatConv.convBrowser.contentChatNode;
+ return messageParent;
+}
+
+/**
+ * @param {HTMLElement} [browser] - The conversation-browser element.
+ * @returns {Promise<void>}
+ */
+function waitForConversationLoad(browser) {
+ return TestUtils.topicObserved(
+ "conversation-loaded",
+ subject => !browser || subject === browser
+ );
+}
+
+function waitForNotification(target, expectedTopic) {
+ let observer;
+ let promise = new Promise(resolve => {
+ observer = {
+ observe(subject, topic, data) {
+ if (topic === expectedTopic) {
+ resolve({ subject, data });
+ target.removeObserver(observer);
+ }
+ },
+ };
+ });
+ target.addObserver(observer);
+ return promise;
+}
+
+registerTestProtocol();
+
+registerCleanupFunction(async () => {
+ // Make sure the chat state is clean
+ await closeChatTab();
+
+ const conversations = IMServices.conversations.getConversations();
+ is(conversations.length, 0, "All conversations were closed by their test");
+ for (const conversation of conversations) {
+ try {
+ conversation.close();
+ } catch (error) {
+ ok(false, error.message);
+ }
+ }
+
+ const accounts = IMServices.accounts.getAccounts();
+ is(accounts.length, 0, "All accounts were removed by their test");
+ for (const account of accounts) {
+ try {
+ if (account.connected || account.connecting) {
+ account.disconnect();
+ }
+ IMServices.accounts.deleteAccount(account.id);
+ } catch (error) {
+ ok(false, "Error deleting account " + account.id + ": " + error.message);
+ }
+ }
+
+ unregisterTestProtocol();
+});