Bug 1499617 - Folder tab (3-pane) WebExtensions API, mochitests; r=mkmelin
authorGeoff Lankow <geoff@darktrojan.net>
Sun, 30 Dec 2018 20:32:50 +1300
changeset 33226 4331140b9077
parent 33225 71c8bc1307b8
child 33227 04e98cdf2061
push id2368
push userclokep@gmail.com
push dateMon, 28 Jan 2019 21:12:50 +0000
treeherdercomm-beta@56d23c07d815 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1499617
Bug 1499617 - Folder tab (3-pane) WebExtensions API, mochitests; r=mkmelin
mail/base/content/msgMail3PaneWindow.js
mail/components/extensions/test/browser/.eslintrc.js
mail/components/extensions/test/browser/browser.ini
mail/components/extensions/test/browser/browser_ext_mailTabs.js
mail/components/extensions/test/browser/browser_ext_quickFilter.js
mail/components/extensions/test/browser/head.js
mailnews/moz.build
mailnews/test/resources/messageGenerator.js
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -314,16 +314,17 @@ function UpdateMailPaneConfig(aMsgWindow
       cloneToolbarset = hdrToolbox.toolbarset.cloneNode(true);
     }
 
     // See Bug 381992. The ctor for the browser element will fire again when we
     // re-insert the messagePaneBoxWrapper back into the document.  But the dtor
     // doesn't fire when the element is removed from the document.  Manually
     // call destroy here to avoid a nasty leak.
     document.getElementById("messagepane").destroy();
+    document.getElementById("FindToolbar").destroy();
     let footerBox = desiredParent.lastChild;
     if (footerBox && footerBox.id == "msg-footer-notification-box") {
       desiredParent.insertBefore(messagePaneSplitter, footerBox);
       desiredParent.insertBefore(messagePaneBoxWrapper, footerBox);
     } else {
       desiredParent.appendChild(messagePaneSplitter);
       desiredParent.appendChild(messagePaneBoxWrapper);
     }
--- a/mail/components/extensions/test/browser/.eslintrc.js
+++ b/mail/components/extensions/test/browser/.eslintrc.js
@@ -4,10 +4,11 @@ module.exports = {
   "extends": "plugin:mozilla/browser-test",
 
   "env": {
     "webextensions": true,
   },
 
   "rules": {
     "func-names": "off",
+    "mozilla/import-headjs-globals": "error",
   },
 };
--- a/mail/components/extensions/test/browser/browser.ini
+++ b/mail/components/extensions/test/browser/browser.ini
@@ -1,8 +1,10 @@
 [DEFAULT]
 head = head.js
 subsuite = thunderbird
 tags = webextensions
 
 [browser_ext_addressBooksUI.js]
 [browser_ext_browserAction.js]
 [browser_ext_composeAction.js]
+[browser_ext_mailTabs.js]
+[browser_ext_quickFilter.js]
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/test/browser/browser_ext_mailTabs.js
@@ -0,0 +1,382 @@
+/* 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 account, rootFolder, subFolders;
+
+add_task(async function setup() {
+  account = createAccount();
+  rootFolder = account.incomingServer.rootFolder;
+  subFolders = [...rootFolder.subFolders];
+  createMessages(subFolders[0], 10);
+
+  window.gFolderTreeView.selectFolder(rootFolder);
+  await new Promise(executeSoon);
+});
+
+add_task(async function test_update() {
+  async function background() {
+    function awaitMessage(messageToSend, ...sendArgs) {
+      return new Promise(resolve => {
+        browser.test.onMessage.addListener(function listener(...args) {
+          browser.test.onMessage.removeListener(listener);
+          resolve(args);
+        });
+        if (messageToSend) {
+          browser.test.sendMessage(messageToSend, ...sendArgs);
+        }
+      });
+    }
+
+    function assertDeepEqual(expected, actual) {
+      if (Array.isArray(expected)) {
+        browser.test.assertTrue(Array.isArray(actual));
+        browser.test.assertEq(expected.length, actual.length);
+        for (let i = 0; i < expected.length; i++) {
+          assertDeepEqual(expected[i], actual[i]);
+        }
+        return;
+      }
+
+      let expectedKeys = Object.keys(expected);
+      let actualKeys = Object.keys(actual);
+      // Ignore any extra keys on the actual object.
+      browser.test.assertTrue(expectedKeys.length <= actualKeys.length);
+
+      for (let key of expectedKeys) {
+        browser.test.assertTrue(actualKeys.includes(key), `Key ${key} exists`);
+        if (expected[key] === null) {
+          browser.test.assertTrue(actual[key] === null);
+          continue;
+        }
+        if (["array", "object"].includes(typeof expected[key])) {
+          assertDeepEqual(expected[key], actual[key]);
+          continue;
+        }
+        browser.test.assertEq(expected[key], actual[key]);
+      }
+    }
+
+    async function checkCurrent(expected) {
+      let current = await browser.mailTabs.getCurrent();
+      assertDeepEqual(expected, current);
+    }
+
+    let [accountId] = await awaitMessage();
+    let { folders } = await browser.accounts.get(accountId);
+    let state = {
+      sortType: null,
+      sortOrder: null,
+      layout: "standard",
+      folderPaneVisible: null,
+      messagePaneVisible: null,
+      displayedFolder: {
+        accountId,
+        name: "Local Folders",
+        path: "/",
+      },
+    };
+    await checkCurrent(state);
+    await awaitMessage("checkRealLayout", state);
+
+    browser.mailTabs.update({ displayedFolder: folders[0] });
+    state.sortType = "byDate";
+    state.sortOrder = "ascending";
+    state.folderPaneVisible = true;
+    state.messagePaneVisible = true;
+    state.displayedFolder = folders[0];
+    await checkCurrent(state);
+    await awaitMessage("checkRealLayout", state);
+    await awaitMessage("checkRealSort", state);
+
+    state.sortOrder = "descending";
+    for (let value of ["byDate", "bySubject", "byAuthor"]) {
+      await browser.mailTabs.update({ sortType: value, sortOrder: "descending" });
+      state.sortType = value;
+      await awaitMessage("checkRealSort", state);
+    }
+    state.sortOrder = "ascending";
+    for (let value of ["byAuthor", "bySubject", "byDate"]) {
+      await browser.mailTabs.update({ sortType: value, sortOrder: "ascending" });
+      state.sortType = value;
+      await awaitMessage("checkRealSort", state);
+    }
+
+    for (let key of ["folderPaneVisible", "messagePaneVisible"]) {
+      for (let value of [false, true]) {
+        await browser.mailTabs.update({ [key]: value });
+        state[key] = value;
+        await checkCurrent(state);
+        await awaitMessage("checkRealLayout", state);
+      }
+    }
+    for (let value of ["wide", "vertical", "standard"]) {
+      await browser.mailTabs.update({ layout: value });
+      state.layout = value;
+      await checkCurrent(state);
+      await awaitMessage("checkRealLayout", state);
+    }
+
+    let selectedMessages = await browser.mailTabs.getSelectedMessages();
+    browser.test.assertEq(0, selectedMessages.length);
+
+    browser.test.notifyPass("mailTabs");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: { permissions: ["accountsRead", "mailTabs", "messagesRead"] },
+  });
+
+  extension.onMessage("checkRealLayout", (expected) => {
+    let intValue = ["standard", "wide", "vertical"].indexOf(expected.layout);
+    is(Services.prefs.getIntPref("mail.pane_config.dynamic"), intValue);
+    if (typeof expected.messagePaneVisible == "boolean") {
+      is(document.getElementById("messagepaneboxwrapper").collapsed, !expected.messagePaneVisible);
+    }
+    if (typeof expected.folderPaneVisible == "boolean") {
+      is(document.getElementById("folderPaneBox").collapsed, !expected.folderPaneVisible);
+    }
+    extension.sendMessage();
+  });
+
+  extension.onMessage("checkRealSort", (expected) => {
+    for (let [columnId, sortType] of window.gFolderDisplay.COLUMNS_MAP) {
+      if (sortType == expected.sortType) {
+        let column = document.getElementById(columnId);
+        is(column.getAttribute("sortDirection"), expected.sortOrder);
+        extension.sendMessage();
+        return;
+      }
+    }
+    throw new Error("This test should never get here.");
+  });
+
+  await extension.startup();
+  extension.sendMessage(account.key);
+  await extension.awaitFinish("mailTabs");
+  await extension.unload();
+
+  window.gFolderTreeView.selectFolder(rootFolder);
+});
+
+add_task(async function test_events() {
+  async function background() {
+    function awaitMessage() {
+      return new Promise(resolve => {
+        browser.test.onMessage.addListener(function listener(...args) {
+          browser.test.onMessage.removeListener(listener);
+          resolve(args);
+        });
+      });
+    }
+
+    let [accountId] = await awaitMessage();
+
+    let current = await browser.mailTabs.getCurrent();
+    browser.test.assertEq(accountId, current.displayedFolder.accountId);
+    browser.test.assertEq("/", current.displayedFolder.path);
+
+    async function selectFolder(newFolderPath) {
+      return new Promise(resolve => {
+        browser.mailTabs.onDisplayedFolderChanged.addListener(function listener(tabId, folder) {
+          browser.mailTabs.onDisplayedFolderChanged.removeListener(listener);
+          browser.test.assertEq(current.id, tabId);
+          browser.test.assertEq(accountId, folder.accountId);
+          browser.test.assertEq(newFolderPath, folder.path);
+          resolve();
+        });
+        browser.test.sendMessage("selectFolder", newFolderPath);
+      });
+    }
+    await selectFolder("/Trash");
+    await selectFolder("/Unsent Messages");
+    await selectFolder("/");
+
+    async function selectFolderByUpdate(newFolderPath) {
+      return new Promise(resolve => {
+        browser.mailTabs.onDisplayedFolderChanged.addListener(function listener(tabId, folder) {
+          browser.mailTabs.onDisplayedFolderChanged.removeListener(listener);
+          browser.test.assertEq(current.id, tabId);
+          browser.test.assertEq(accountId, folder.accountId);
+          browser.test.assertEq(newFolderPath, folder.path);
+          resolve();
+        });
+        browser.mailTabs.update({ displayedFolder: { accountId, path: newFolderPath } });
+      });
+    }
+    await selectFolderByUpdate("/Trash");
+    await selectFolderByUpdate("/Unsent Messages");
+    await selectFolderByUpdate("/");
+    await selectFolderByUpdate("/Trash");
+
+    async function selectMessage(...newMessages) {
+      return new Promise(resolve => {
+        browser.mailTabs.onSelectedMessagesChanged.addListener(function listener(tabId, messages) {
+          browser.mailTabs.onSelectedMessagesChanged.removeListener(listener);
+          browser.test.assertEq(newMessages.length, messages.length);
+          browser.mailTabs.getSelectedMessages().then(selectedMessages => {
+            browser.test.assertEq(newMessages.length, selectedMessages.length);
+            resolve();
+          });
+        });
+        browser.test.sendMessage("selectMessage", newMessages);
+      });
+    }
+    await selectMessage(3);
+    await selectMessage(7);
+    await selectMessage(4, 6);
+    await selectMessage();
+
+    await new Promise(setTimeout);
+    browser.test.notifyPass("mailTabs");
+  }
+
+  let folderMap = new Map([
+    ["/", rootFolder],
+    ["/Trash", subFolders[0]],
+    ["/Unsent Messages", subFolders[1]],
+  ]);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: { permissions: ["accountsRead", "mailTabs", "messagesRead"] },
+  });
+
+  extension.onMessage("selectFolder", (newFolderPath) => {
+    window.gFolderTreeView.selectFolder(folderMap.get(newFolderPath));
+  });
+
+  extension.onMessage("selectMessage", (newMessages) => {
+    let allMessages = [...window.gFolderDisplay.displayedFolder.messages];
+    window.gFolderDisplay.selectMessages(newMessages.map(i => allMessages[i]));
+  });
+
+  await extension.startup();
+  extension.sendMessage(account.key);
+  await extension.awaitFinish("mailTabs");
+  await extension.unload();
+
+  window.gFolderTreeView.selectFolder(rootFolder);
+});
+
+add_task(async function test_background_tab() {
+  async function background() {
+    function awaitMessage(messageToSend, ...sendArgs) {
+      return new Promise(resolve => {
+        browser.test.onMessage.addListener(function listener(...args) {
+          browser.test.onMessage.removeListener(listener);
+          resolve(args);
+        });
+        if (messageToSend) {
+          browser.test.sendMessage(messageToSend, ...sendArgs);
+        }
+      });
+    }
+
+    let [accountId] = await awaitMessage();
+    let { folders } = await browser.accounts.get(accountId);
+    let allTabs = await browser.tabs.query({});
+    let queryTabs = await browser.tabs.query({ isMail3Pane: true });
+    let allMailTabs = await browser.mailTabs.getAll();
+
+    browser.test.assertEq(4, allTabs.length);
+    browser.test.assertEq(2, queryTabs.length);
+    browser.test.assertEq(2, allMailTabs.length);
+
+    browser.test.assertEq(accountId, allMailTabs[0].displayedFolder.accountId);
+    browser.test.assertEq("/", allMailTabs[0].displayedFolder.path);
+
+    browser.test.assertEq(accountId, allMailTabs[1].displayedFolder.accountId);
+    browser.test.assertEq("/Trash", allMailTabs[1].displayedFolder.path);
+    browser.test.assertTrue(allMailTabs[1].active);
+
+    // Check the initial state.
+    await awaitMessage("checkRealLayout", {
+      messagePaneVisible: true,
+      folderPaneVisible: true,
+      displayedFolder: "/Trash",
+    });
+
+    await browser.mailTabs.update(allMailTabs[0].id, {
+      folderPaneVisible: false,
+      messagePaneVisible: false,
+      displayedFolder: folders[1],
+    });
+
+    // Should be in the same state, since we're updating a background tab.
+    await awaitMessage("checkRealLayout", {
+      messagePaneVisible: true,
+      folderPaneVisible: true,
+      displayedFolder: "/Trash",
+    });
+
+    allMailTabs = await browser.mailTabs.getAll();
+    browser.test.assertEq(2, allMailTabs.length);
+
+    browser.test.assertEq(accountId, allMailTabs[0].displayedFolder.accountId);
+    browser.test.assertEq("/Unsent Messages", allMailTabs[0].displayedFolder.path);
+
+    browser.test.assertEq(accountId, allMailTabs[1].displayedFolder.accountId);
+    browser.test.assertEq("/Trash", allMailTabs[1].displayedFolder.path);
+    browser.test.assertTrue(allMailTabs[1].active);
+
+    // Switch to the other mail tab.
+    await browser.tabs.update(allMailTabs[0].id, { active: true });
+
+    // Should have changed to the updated state.
+    await awaitMessage("checkRealLayout", {
+      messagePaneVisible: false,
+      folderPaneVisible: false,
+      displayedFolder: "/Unsent%20Messages",
+    });
+
+    await browser.mailTabs.update(allMailTabs[0].id, {
+      folderPaneVisible: true,
+      messagePaneVisible: true,
+    });
+    await awaitMessage("checkRealLayout", {
+      messagePaneVisible: true,
+      folderPaneVisible: true,
+      displayedFolder: "/Unsent%20Messages",
+    });
+
+    // Switch back to the first mail tab.
+    await browser.tabs.update(allMailTabs[1].id, { active: true });
+
+    // Should be in the same state it was in.
+    await awaitMessage("checkRealLayout", {
+      messagePaneVisible: true,
+      folderPaneVisible: true,
+      displayedFolder: "/Trash",
+    });
+
+    browser.test.notifyPass("mailTabs");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: { permissions: ["accountsRead", "mailTabs", "tabs"] },
+  });
+
+  extension.onMessage("checkRealLayout", async (expected) => {
+    is(document.getElementById("messagepaneboxwrapper").collapsed, !expected.messagePaneVisible);
+    is(document.getElementById("folderPaneBox").collapsed, !expected.folderPaneVisible);
+    is(window.gFolderTreeView.getSelectedFolders()[0].URI,
+       account.incomingServer.serverURI + expected.displayedFolder);
+    extension.sendMessage();
+  });
+
+  let tabmail = document.getElementById("tabmail");
+  window.openContentTab("about:config");
+  window.openContentTab("about:mozilla");
+  tabmail.openTab("folder", { folder: subFolders[0] });
+
+  await extension.startup();
+  extension.sendMessage(account.key);
+  await extension.awaitFinish("mailTabs");
+  await extension.unload();
+
+  tabmail.closeOtherTabs(tabmail.tabModes.folder.tabs[0]);
+  window.gFolderTreeView.selectFolder(rootFolder);
+});
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/test/browser/browser_ext_quickFilter.js
@@ -0,0 +1,105 @@
+/* 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 account, rootFolder, subFolders;
+
+add_task(async () => {
+  account = createAccount();
+  rootFolder = account.incomingServer.rootFolder;
+  subFolders = [...rootFolder.subFolders];
+  createMessages(subFolders[0], 10);
+
+  window.gFolderTreeView.selectFolder(rootFolder);
+  await new Promise(executeSoon);
+});
+
+add_task(async () => {
+  async function background() {
+    function awaitMessage(messageToSend, ...sendArgs) {
+      return new Promise(resolve => {
+        browser.test.onMessage.addListener(function listener(...args) {
+          browser.test.onMessage.removeListener(listener);
+          resolve(args);
+        });
+        if (messageToSend) {
+          browser.test.sendMessage(messageToSend, ...sendArgs);
+        }
+      });
+    }
+
+    browser.mailTabs.setQuickFilter({ unread: true });
+    await awaitMessage("checkVisible", 1, 3, 5, 7, 9);
+
+    browser.mailTabs.setQuickFilter({ starred: true });
+    await awaitMessage("checkVisible", 1, 6);
+
+    browser.mailTabs.setQuickFilter({ starred: true, unread: true });
+    await awaitMessage("checkVisible", 1);
+
+    browser.mailTabs.setQuickFilter({ tags: true });
+    await awaitMessage("checkVisible", 0, 1, 3, 5, 6, 7, 8, 9);
+
+    browser.mailTabs.setQuickFilter({ tags: { mode: "any", tags: { "$label1": true } } });
+    await awaitMessage("checkVisible", 0, 3, 6, 9);
+
+    browser.mailTabs.setQuickFilter({ tags: { mode: "any", tags: { "$label2": true } } });
+    await awaitMessage("checkVisible", 1, 3, 5, 7, 9);
+
+    browser.mailTabs.setQuickFilter({ tags: { mode: "any", tags: { "$label1": true, "$label2": true } } });
+    await awaitMessage("checkVisible", 0, 1, 3, 5, 6, 7, 9);
+
+    browser.mailTabs.setQuickFilter({ tags: { mode: "all", tags: { "$label1": true, "$label2": true } } });
+    await awaitMessage("checkVisible", 3, 9);
+
+    browser.mailTabs.setQuickFilter({ tags: { mode: "all", tags: { "$label1": true, "$label2": false } } });
+    await awaitMessage("checkVisible", 0, 6);
+
+    browser.test.notifyPass("quickFilter");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: { permissions: ["mailTabs"] },
+  });
+
+  extension.onMessage("checkVisible", async (...expected) => {
+    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+    await new Promise(r => setTimeout(r, 500));
+
+    let actual = [];
+    let dbView = window.gFolderDisplay.view.dbView;
+    for (let i = 0; i < dbView.numMsgsInView; i++) {
+      actual.push(messages.indexOf(dbView.getMsgHdrAt(i)));
+    }
+
+    is(JSON.stringify(actual), JSON.stringify(expected));
+    extension.sendMessage();
+  });
+
+  window.gFolderTreeView.selectFolder(subFolders[0]);
+  let messages = [...window.gFolderDisplay.displayedFolder.messages];
+  messages[0].markRead(true);
+  messages[2].markRead(true);
+  messages[4].markRead(true);
+  messages[6].markRead(true);
+  messages[8].markRead(true);
+  messages[1].markFlagged(true);
+  messages[6].markFlagged(true);
+  messages[0].setProperty("keywords", "$label1");
+  messages[1].setProperty("keywords", "$label2");
+  messages[3].setProperty("keywords", "$label1 $label2");
+  messages[5].setProperty("keywords", "$label2");
+  messages[6].setProperty("keywords", "$label1");
+  messages[7].setProperty("keywords", "$label2 $label3");
+  messages[8].setProperty("keywords", "$label3");
+  messages[9].setProperty("keywords", "$label1 $label2 $label3");
+
+  // await new Promise(r => setTimeout(r, 10000));
+
+  await extension.startup();
+  await extension.awaitFinish("quickFilter");
+  await extension.unload();
+
+  window.gFolderTreeView.selectFolder(rootFolder);
+});
--- a/mail/components/extensions/test/browser/head.js
+++ b/mail/components/extensions/test/browser/head.js
@@ -26,13 +26,23 @@ function cleanUpAccount(account) {
 function addIdentity(account) {
   let identity = MailServices.accounts.createIdentity();
   identity.email = "mochitest@localhost";
   account.addIdentity(identity);
   account.defaultIdentity = identity;
   info(`Created identity ${identity.toString()}`);
 }
 
+function createMessages(folder, count) {
+  const {
+    MessageGenerator,
+  } = ChromeUtils.import("resource://testing-common/mailnews/messageGenerator.js", null);
+  let messages = new MessageGenerator().makeMessages({ count });
+  let messageStrings = messages.map(message => message.toMboxString());
+  folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+  folder.addMessageBatch(messageStrings.length, messageStrings);
+}
+
 async function promiseAnimationFrame(win = window) {
   await new Promise(win.requestAnimationFrame);
   // dispatchToMainThread throws if used as the first argument of Promise.
   return new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
 }
--- a/mailnews/moz.build
+++ b/mailnews/moz.build
@@ -71,16 +71,17 @@ TESTING_JS_MODULES.mailnews += [
     'test/fakeserver/imapd.js',
     'test/fakeserver/maild.js',
     'test/fakeserver/nntpd.js',
     'test/fakeserver/pop3d.js',
     'test/fakeserver/smtpd.js',
     'test/resources/IMAPpump.js',
     'test/resources/localAccountUtils.js',
     'test/resources/mailTestUtils.js',
+    'test/resources/messageGenerator.js',
     'test/resources/MockFactory.js',
     'test/resources/NetworkTestUtils.jsm',
     'test/resources/PromiseTestUtils.jsm',
 ]
 
 if 'comm' in CONFIG['MOZ_BUILD_APP']:
   test_harness_base = TEST_HARNESS_FILES.xpcshell.comm
 else:
--- a/mailnews/test/resources/messageGenerator.js
+++ b/mailnews/test/resources/messageGenerator.js
@@ -1,12 +1,14 @@
 /* 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/. */
 
+this.EXPORTED_SYMBOLS = ['MessageGenerator'];
+
 /**
  * A list of first names for use by MessageGenerator to create deterministic,
  *  reversible names.  To keep things easily reversible, if you add names, make
  *  sure they have no spaces in them!
  */
 var FIRST_NAMES = [
   "Andy", "Bob", "Chris", "David", "Emily", "Felix",
   "Gillian", "Helen", "Idina", "Johnny", "Kate", "Lilia",