From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mail/components/im/test/TestProtocol.sys.mjs | 308 ++++++++++++++++++ comm/mail/components/im/test/browser/browser.ini | 26 ++ .../im/test/browser/browser_browserRequest.js | 112 +++++++ .../im/test/browser/browser_chatNotifications.js | 101 ++++++ .../im/test/browser/browser_chatTelemetry.js | 52 +++ .../im/test/browser/browser_contextMenu.js | 243 ++++++++++++++ .../components/im/test/browser/browser_logs.js | 97 ++++++ .../im/test/browser/browser_messagesMail.js | 235 ++++++++++++++ .../im/test/browser/browser_readMessage.js | 49 +++ .../im/test/browser/browser_removeMessage.js | 54 ++++ .../test/browser/browser_requestNotifications.js | 350 +++++++++++++++++++++ .../im/test/browser/browser_spacesToolbarChat.js | 255 +++++++++++++++ .../components/im/test/browser/browser_tooltips.js | 194 ++++++++++++ .../im/test/browser/browser_updateMessage.js | 62 ++++ comm/mail/components/im/test/browser/head.js | 132 ++++++++ comm/mail/components/im/test/components.conf | 14 + 16 files changed, 2284 insertions(+) create mode 100644 comm/mail/components/im/test/TestProtocol.sys.mjs create mode 100644 comm/mail/components/im/test/browser/browser.ini create mode 100644 comm/mail/components/im/test/browser/browser_browserRequest.js create mode 100644 comm/mail/components/im/test/browser/browser_chatNotifications.js create mode 100644 comm/mail/components/im/test/browser/browser_chatTelemetry.js create mode 100644 comm/mail/components/im/test/browser/browser_contextMenu.js create mode 100644 comm/mail/components/im/test/browser/browser_logs.js create mode 100644 comm/mail/components/im/test/browser/browser_messagesMail.js create mode 100644 comm/mail/components/im/test/browser/browser_readMessage.js create mode 100644 comm/mail/components/im/test/browser/browser_removeMessage.js create mode 100644 comm/mail/components/im/test/browser/browser_requestNotifications.js create mode 100644 comm/mail/components/im/test/browser/browser_spacesToolbarChat.js create mode 100644 comm/mail/components/im/test/browser/browser_tooltips.js create mode 100644 comm/mail/components/im/test/browser/browser_updateMessage.js create mode 100644 comm/mail/components/im/test/browser/head.js create mode 100644 comm/mail/components/im/test/components.conf (limited to 'comm/mail/components/im/test') diff --git a/comm/mail/components/im/test/TestProtocol.sys.mjs b/comm/mail/components/im/test/TestProtocol.sys.mjs new file mode 100644 index 0000000000..7fddbf176b --- /dev/null +++ b/comm/mail/components/im/test/TestProtocol.sys.mjs @@ -0,0 +1,308 @@ +/* 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/. */ + +import { + GenericAccountPrototype, + GenericConvChatPrototype, + GenericConvIMPrototype, + GenericConversationPrototype, + GenericProtocolPrototype, + GenericConvChatBuddyPrototype, + GenericMessagePrototype, + TooltipInfo, +} from "resource:///modules/jsProtoHelper.sys.mjs"; + +import { nsSimpleEnumerator } from "resource:///modules/imXPCOMUtils.sys.mjs"; + +function Message(who, text, properties, conversation) { + this._init(who, text, properties, conversation); + this.displayed = new Promise(resolve => { + this._onDisplayed = resolve; + }); + this.read = new Promise(resolve => { + this._onRead = resolve; + }); + this.actionRan = new Promise(resolve => { + this._onAction = resolve; + }); +} + +Message.prototype = { + __proto__: GenericMessagePrototype, + + whenDisplayed() { + this._onDisplayed(); + }, + + whenRead() { + this._onRead(); + }, + + getActions() { + return [ + { + QueryInterface: ChromeUtils.generateQI(["prplIMessageAction"]), + label: "Test", + run: () => { + this._onAction(); + }, + }, + ]; + }, +}; + +/** + * + * @param {string} who - Nick of the participant. + * @param {string} [alias] - Display name of the participant. + */ +function Participant(who, alias) { + this._name = who; + if (alias) { + this.alias = alias; + } +} +Participant.prototype = { + __proto__: GenericConvChatBuddyPrototype, +}; + +const SharedConversationPrototype = { + _disconnected: false, + /** + * Disconnect the conversation. + */ + _setDisconnected() { + this._disconnected = true; + }, + /** + * Close the conversation, including in the UI. + */ + close() { + this._disconnected = true; + this._account._conversations.delete(this); + GenericConversationPrototype.close.call(this); + }, + /** + * Send an outgoing message. + * + * @param {string} aMsg - Message to send. + * @returns + */ + dispatchMessage(aMsg, aAction = false, aNotice = false) { + if (this._disconnected) { + return; + } + this.writeMessage("You", aMsg, { outgoing: true, notification: aNotice }); + }, + + /** + * + * @param {Array} messages - Array of messages to add to the + * conversation. Expects an object with a |who|, |content| and |options| + * properties, corresponding to the three params of |writeMessage|. + */ + addMessages(messages) { + for (const message of messages) { + this.writeMessage(message.who, message.content, message.options); + } + }, + + /** + * Add a notice to the conversation. + */ + addNotice() { + this.writeMessage("system", "test notice", { system: true }); + }, + + createMessage(who, text, options) { + const message = new Message(who, text, options, this); + return message; + }, +}; + +/** + * + * @param {prplIAccount} account + * @param {string} name - Name of the conversation. + */ +function MUC(account, name) { + this._init(account, name, "You"); +} +MUC.prototype = { + __proto__: GenericConvChatPrototype, + + /** + * + * @param {string} who - Nick of the user to add. + * @param {string} alias - Display name of the participant. + * @returns + */ + addParticipant(who, alias) { + if (this._participants.has(who)) { + return; + } + const participant = new Participant(who, alias); + this._participants.set(who, participant); + }, + ...SharedConversationPrototype, +}; + +/** + * + * @param {prplIAccount} account + * @param {string} name - Name of the conversation. + */ +function DM(account, name) { + this._init(account, name); +} +DM.prototype = { + __proto__: GenericConvIMPrototype, + ...SharedConversationPrototype, +}; + +function Account(aProtoInstance, aImAccount) { + this._init(aProtoInstance, aImAccount); + this._conversations = new Set(); +} +Account.prototype = { + __proto__: GenericAccountPrototype, + + /** + * @type {Set} + */ + _conversations: null, + + /** + * + * @param {string} name - Name of the conversation. + * @returns {MUC} + */ + makeMUC(name) { + const conversation = new MUC(this, name); + this._conversations.add(conversation); + return conversation; + }, + + /** + * + * @param {string} name - Name of the conversation. + * @returns {DM} + */ + makeDM(name) { + const conversation = new DM(this, name); + this._conversations.add(conversation); + return conversation; + }, + + connect() { + this.reportConnecting(); + // do something here + this.reportConnected(); + }, + disconnect() { + this.reportDisconnecting(Ci.prplIAccount.NO_ERROR, ""); + this.reportDisconnected(); + }, + + requestBuddyInfo(who) { + const participant = Array.from(this._conversations) + .find(conv => conv.isChat && conv._participants.has(who)) + ?._participants.get(who); + if (participant) { + const tooltipInfo = [new TooltipInfo("Display Name", participant.alias)]; + Services.obs.notifyObservers( + new nsSimpleEnumerator(tooltipInfo), + "user-info-received", + who + ); + } + }, + + get canJoinChat() { + return true; + }, + chatRoomFields: { + channel: { label: "_Channel Field", required: true }, + channelDefault: { label: "_Field with default", default: "Default Value" }, + password: { + label: "_Password Field", + default: "", + isPassword: true, + required: false, + }, + sampleIntField: { + label: "_Int Field", + default: 4, + min: 0, + max: 10, + required: true, + }, + }, + + // Nothing to do. + unInit() { + for (const conversation of this._conversations) { + conversation.close(); + } + }, + remove() {}, +}; + +export function TestProtocol() {} +TestProtocol.prototype = { + __proto__: GenericProtocolPrototype, + get id() { + return "prpl-mochitest"; + }, + get normalizedName() { + return "mochitest"; + }, + get name() { + return "Mochitest"; + }, + options: { + text: { label: "Text option", default: "foo" }, + bool: { label: "Boolean option", default: true }, + int: { label: "Integer option", default: 42 }, + list: { + label: "Select option", + default: "option2", + listValues: { + option1: "First option", + option2: "Default option", + option3: "Other option", + }, + }, + }, + usernameSplits: [ + { + label: "Server", + separator: "@", + defaultValue: "default.server", + reverse: true, + }, + ], + getAccount(aImAccount) { + return new Account(this, aImAccount); + }, + classID: Components.ID("{a4617631-b8b8-4053-8afa-5c4c43498280}"), +}; + +export function registerTestProtocol() { + Services.catMan.addCategoryEntry( + "im-protocol-plugin", + TestProtocol.prototype.id, + "@mozilla.org/chat/mochitest;1", + false, + true + ); +} + +export function unregisterTestProtocol() { + Services.catMan.deleteCategoryEntry( + "im-protocol-plugin", + TestProtocol.prototype.id, + true + ); +} 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: "lorem ipsum", + 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} + */ +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(); +}); diff --git a/comm/mail/components/im/test/components.conf b/comm/mail/components/im/test/components.conf new file mode 100644 index 0000000000..3f8c09fc09 --- /dev/null +++ b/comm/mail/components/im/test/components.conf @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{a4617631-b8b8-4053-8afa-5c4c43498280}', + 'contract_ids': ['@mozilla.org/chat/mochitest;1'], + 'esModule': 'resource://testing-common/TestProtocol.sys.mjs', + 'constructor': 'TestProtocol', + }, +] -- cgit v1.2.3