summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/addrbook/test/browser/browser_directory_tree.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/addrbook/test/browser/browser_directory_tree.js')
-rw-r--r--comm/mail/components/addrbook/test/browser/browser_directory_tree.js982
1 files changed, 982 insertions, 0 deletions
diff --git a/comm/mail/components/addrbook/test/browser/browser_directory_tree.js b/comm/mail/components/addrbook/test/browser/browser_directory_tree.js
new file mode 100644
index 0000000000..ee4b31ab7c
--- /dev/null
+++ b/comm/mail/components/addrbook/test/browser/browser_directory_tree.js
@@ -0,0 +1,982 @@
+/* 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/. */
+
+function rightClickOnIndex(index) {
+ let abWindow = getAddressBookWindow();
+ let booksList = abWindow.booksList;
+ let menu = abWindow.document.getElementById("bookContext");
+
+ let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ booksList
+ .getRowAtIndex(index)
+ .querySelector(".bookRow-name, .listRow-name"),
+ { type: "contextmenu" },
+ abWindow
+ );
+ return shownPromise;
+}
+
+/**
+ * Tests that additions and removals are accurately displayed.
+ */
+add_task(async function test_additions_and_removals() {
+ function checkBooksOrder(...expected) {
+ function checkRow(index, { level, open, isList, text, uid }) {
+ info(`Row ${index}`);
+ let row = rows[index];
+
+ let containingList = row.closest("ul");
+ if (level == 1) {
+ Assert.equal(containingList.getAttribute("is"), "ab-tree-listbox");
+ } else if (level == 2) {
+ Assert.equal(containingList.parentNode.localName, "li");
+ containingList = containingList.parentNode.closest("ul");
+ Assert.equal(containingList.getAttribute("is"), "ab-tree-listbox");
+ }
+
+ let childList = row.querySelector("ul");
+ // NOTE: We're not explicitly handling open === false because no test
+ // needed it.
+ if (open) {
+ // Ancestor shouldn't have the collapsed class and the UL child list
+ // should be expanded and visible.
+ Assert.ok(!row.classList.contains("collapsed"));
+ Assert.greater(childList.clientHeight, 0);
+ } else if (childList) {
+ if (row.classList.contains("collapsed")) {
+ // If we have a UL child list and the ancestor element has a collapsed
+ // class, the child list shouldn't be visible.
+ Assert.equal(childList.clientHeight, 0);
+ } else if (childList.childNodes.length) {
+ // If the ancestor doesn't have the collapsed class, and the UL child
+ // list has at least one child node, the child list should be visible.
+ Assert.greater(childList.clientHeight, 0);
+ }
+ }
+
+ Assert.equal(row.classList.contains("listRow"), isList);
+ Assert.equal(row.querySelector("span").textContent, text);
+ Assert.equal(row.getAttribute("aria-label"), text);
+ Assert.equal(row.dataset.uid, uid);
+ }
+
+ let rows = abWindow.booksList.rows;
+ Assert.equal(rows.length, expected.length + 1);
+ for (let i = 0; i < expected.length; i++) {
+ let dir = expected[i].directory;
+ checkRow(i + 1, {
+ ...expected[i],
+ isList: dir.isMailList,
+ text: dir.dirName,
+ uid: dir.UID,
+ });
+ }
+ }
+
+ let abWindow = await openAddressBookWindow();
+
+ // Check the initial order.
+
+ checkDirectoryDisplayed(null);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: historyBook }
+ );
+
+ // Add one book, *not* using the UI, and check that we don't move to it.
+
+ let newBook1 = createAddressBook("New Book 1");
+ checkDirectoryDisplayed(null);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Add another book, using the UI, and check that we move to the new book.
+
+ let newBook2 = await createAddressBookWithUI("New Book 2");
+ checkDirectoryDisplayed(newBook2);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1 },
+ { level: 1, directory: newBook2 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Add some lists, *not* using the UI, and check that we don't move to them.
+
+ let list1 = newBook1.addMailList(createMailingList("New Book 1 - List 1"));
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(newBook2);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list1 },
+ { level: 1, directory: newBook2 },
+ { level: 1, directory: historyBook }
+ );
+
+ let list3 = newBook1.addMailList(createMailingList("New Book 1 - List 3"));
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(newBook2);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2 },
+ { level: 1, directory: historyBook }
+ );
+
+ let list0 = newBook1.addMailList(createMailingList("New Book 1 - List 0"));
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(newBook2);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2 },
+ { level: 1, directory: historyBook }
+ );
+
+ let list2 = newBook1.addMailList(createMailingList("New Book 1 - List 2"));
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(newBook2);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Close the window and open it again. The tree should be as it was before.
+
+ await closeAddressBookWindow();
+ abWindow = await openAddressBookWindow();
+
+ checkDirectoryDisplayed(null);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2 },
+ { level: 1, directory: historyBook }
+ );
+
+ openDirectory(newBook2);
+
+ let list4 = newBook2.addMailList(createMailingList("New Book 2 - List 4"));
+ checkDirectoryDisplayed(newBook2);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2, open: true },
+ { level: 2, directory: list4 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Add a new list, using the UI, and check that we move to it.
+
+ let list5 = await createMailingListWithUI(newBook2, "New Book 2 - List 5");
+ checkDirectoryDisplayed(list5);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2, open: true },
+ { level: 2, directory: list4 },
+ { level: 2, directory: list5 },
+ { level: 1, directory: historyBook }
+ );
+
+ let list6 = await createMailingListWithUI(newBook2, "New Book 2 - List 6");
+ checkDirectoryDisplayed(list6);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 2, directory: list3 },
+ { level: 1, directory: newBook2, open: true },
+ { level: 2, directory: list4 },
+ { level: 2, directory: list5 },
+ { level: 2, directory: list6 },
+ { level: 1, directory: historyBook }
+ );
+ // Delete a list that isn't displayed, and check that we don't move.
+
+ newBook1.deleteDirectory(list3);
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(list6);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 1, directory: newBook2, open: true },
+ { level: 2, directory: list4 },
+ { level: 2, directory: list5 },
+ { level: 2, directory: list6 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Select list5
+ let list5Row = abWindow.booksList.getRowForUID(list5.UID);
+ EventUtils.synthesizeMouseAtCenter(
+ list5Row.querySelector("span"),
+ {},
+ abWindow
+ );
+ checkDirectoryDisplayed(list5);
+
+ // Delete the displayed list, and check that we move to the next list under
+ // the same book.
+
+ newBook2.deleteDirectory(list5);
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(list6);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 1, directory: newBook2, open: true },
+ { level: 2, directory: list4 },
+ { level: 2, directory: list6 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Delete the last list, and check we move to the previous list under the same
+ // book.
+ newBook2.deleteDirectory(list6);
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(list4);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 1, directory: newBook2, open: true },
+ { level: 2, directory: list4 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Delete the displayed book, and check that we move to the next book.
+
+ await promiseDirectoryRemoved(newBook2.URI);
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(historyBook);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: newBook1, open: true },
+ { level: 2, directory: list0 },
+ { level: 2, directory: list1 },
+ { level: 2, directory: list2 },
+ { level: 1, directory: historyBook }
+ );
+
+ // Select a list in the first book, then delete the book. Check that we
+ // move to the next book.
+
+ openDirectory(list1);
+ await promiseDirectoryRemoved(newBook1.URI);
+ await new Promise(r => abWindow.setTimeout(r));
+ checkDirectoryDisplayed(historyBook);
+ checkBooksOrder(
+ { level: 1, directory: personalBook },
+ { level: 1, directory: historyBook }
+ );
+
+ await closeAddressBookWindow();
+});
+
+/**
+ * Tests that renaming or deleting books or lists is reflected in the UI.
+ */
+add_task(async function test_rename_and_delete() {
+ let abWindow = await openAddressBookWindow();
+
+ let abDocument = abWindow.document;
+ let booksList = abWindow.booksList;
+ let searchInput = abWindow.searchInput;
+ Assert.equal(booksList.rowCount, 3);
+
+ // Create a book.
+
+ EventUtils.synthesizeMouseAtCenter(booksList, {}, abWindow);
+ let newBook = await createAddressBookWithUI("New Book");
+ Assert.equal(booksList.rowCount, 4);
+ Assert.equal(booksList.getIndexForUID(newBook.UID), 2);
+ Assert.equal(booksList.selectedIndex, 2);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ let bookRow = booksList.getRowAtIndex(2);
+ Assert.equal(bookRow.querySelector(".bookRow-name").textContent, "New Book");
+ Assert.equal(bookRow.getAttribute("aria-label"), "New Book");
+
+ await TestUtils.waitForCondition(
+ () => searchInput.placeholder == "Search New Book",
+ "search placeholder updated"
+ );
+
+ // Rename the book.
+
+ let menu = abDocument.getElementById("bookContext");
+ let propertiesMenuItem = abDocument.getElementById("bookContextProperties");
+
+ await rightClickOnIndex(2);
+
+ Assert.ok(BrowserTestUtils.is_visible(propertiesMenuItem));
+ Assert.ok(!propertiesMenuItem.disabled);
+ Assert.deepEqual(document.l10n.getAttributes(propertiesMenuItem), {
+ id: "about-addressbook-books-context-properties",
+ args: null,
+ });
+
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abAddressBookNameDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ let dialogDocument = dialogWindow.document;
+
+ let nameInput = dialogDocument.getElementById("name");
+ Assert.equal(nameInput.value, "New Book");
+ nameInput.value = "Old Book";
+
+ dialogDocument.querySelector("dialog").getButton("accept").click();
+ });
+ menu.activateItem(propertiesMenuItem);
+ await dialogPromise;
+
+ Assert.equal(booksList.rowCount, 4);
+ Assert.equal(booksList.getIndexForUID(newBook.UID), 2);
+ Assert.equal(booksList.selectedIndex, 2);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ bookRow = booksList.getRowAtIndex(2);
+ Assert.equal(bookRow.querySelector(".bookRow-name").textContent, "Old Book");
+ Assert.equal(bookRow.getAttribute("aria-label"), "Old Book");
+
+ await TestUtils.waitForCondition(
+ () => searchInput.placeholder == "Search Old Book",
+ "search placeholder updated"
+ );
+
+ // Create a list.
+
+ let newList = await createMailingListWithUI(newBook, "New List");
+ Assert.equal(booksList.rowCount, 5);
+ Assert.equal(booksList.getIndexForUID(newList.UID), 3);
+ Assert.equal(booksList.selectedIndex, 3);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ let listRow = booksList.getRowAtIndex(3);
+ Assert.equal(
+ listRow.compareDocumentPosition(bookRow),
+ Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING
+ );
+ Assert.equal(listRow.querySelector(".listRow-name").textContent, "New List");
+ Assert.equal(listRow.getAttribute("aria-label"), "New List");
+
+ await TestUtils.waitForCondition(
+ () => searchInput.placeholder == "Search New List",
+ "search placeholder updated"
+ );
+
+ // Rename the list.
+
+ await rightClickOnIndex(3);
+
+ Assert.ok(BrowserTestUtils.is_visible(propertiesMenuItem));
+ Assert.deepEqual(document.l10n.getAttributes(propertiesMenuItem), {
+ id: "about-addressbook-books-context-edit-list",
+ args: null,
+ });
+
+ dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abEditListDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ let dialogDocument = dialogWindow.document;
+
+ let nameInput = dialogDocument.getElementById("ListName");
+ Assert.equal(nameInput.value, "New List");
+ nameInput.value = "Old List";
+
+ dialogDocument.querySelector("dialog").getButton("accept").click();
+ });
+ menu.activateItem(propertiesMenuItem);
+ await dialogPromise;
+
+ Assert.equal(booksList.rowCount, 5);
+ Assert.equal(booksList.getIndexForUID(newList.UID), 3);
+ Assert.equal(booksList.selectedIndex, 3);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ listRow = booksList.getRowAtIndex(3);
+ Assert.equal(listRow.querySelector(".listRow-name").textContent, "Old List");
+ Assert.equal(listRow.getAttribute("aria-label"), "Old List");
+
+ await TestUtils.waitForCondition(
+ () => searchInput.placeholder == "Search Old List",
+ "search placeholder updated"
+ );
+
+ // Delete the list.
+
+ let promptPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ let selectPromise = BrowserTestUtils.waitForEvent(booksList, "select");
+ EventUtils.synthesizeKey("KEY_Delete", {}, abWindow);
+ await promptPromise;
+ await selectPromise;
+ Assert.equal(newBook.childNodes.length, 0, "list was actually deleted");
+ await new Promise(r => abWindow.setTimeout(r));
+
+ Assert.equal(booksList.rowCount, 4);
+ Assert.equal(booksList.getIndexForUID(newBook.UID), 2);
+ Assert.equal(booksList.getIndexForUID(newList.UID), -1);
+ // Moves to parent when last list is deleted.
+ Assert.equal(booksList.selectedIndex, 2);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ bookRow = booksList.getRowAtIndex(2);
+ Assert.ok(!bookRow.classList.contains("children"));
+ Assert.ok(!bookRow.querySelector("ul, li"));
+
+ // Delete the book.
+
+ promptPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ selectPromise = BrowserTestUtils.waitForEvent(booksList, "select");
+ EventUtils.synthesizeKey("KEY_Delete", {}, abWindow);
+ await promptPromise;
+ await selectPromise;
+ Assert.equal(
+ MailServices.ab.directories.length,
+ 2,
+ "book was actually deleted"
+ );
+
+ Assert.equal(booksList.rowCount, 3);
+ Assert.equal(booksList.getIndexForUID(newBook.UID), -1);
+ Assert.equal(booksList.selectedIndex, 2);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ // Attempt to delete the All Address Books entry.
+ // Synthesizing the delete key here does not throw immediately.
+
+ booksList.selectedIndex = 0;
+ await Assert.rejects(
+ booksList.deleteSelected(),
+ /Cannot delete the All Address Books item/,
+ "Attempting to delete All Address Books should fail."
+ );
+
+ // Attempt to delete Personal Address Book.
+ // Synthesizing the delete key here does not throw immediately.
+
+ booksList.selectedIndex = 1;
+ await Assert.rejects(
+ booksList.deleteSelected(),
+ /Refusing to delete a built-in address book/,
+ "Attempting to delete Personal Address Book should fail."
+ );
+
+ // Attempt to delete Collected Addresses.
+ // Synthesizing the delete key here does not throw immediately.
+
+ booksList.selectedIndex = 2;
+ await Assert.rejects(
+ booksList.deleteSelected(),
+ /Refusing to delete a built-in address book/,
+ "Attempting to delete Collected Addresses should fail."
+ );
+
+ await closeAddressBookWindow();
+});
+
+/**
+ * Tests the context menu of the list.
+ */
+add_task(async function test_context_menu() {
+ let book = createAddressBook("Ordinary Book");
+ book.addMailList(createMailingList("Ordinary List"));
+ createAddressBook("CardDAV Book", Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+
+ let abWindow = await openAddressBookWindow();
+ let abDocument = abWindow.document;
+ let booksList = abWindow.booksList;
+
+ let menu = abWindow.document.getElementById("bookContext");
+ let propertiesMenuItem = abDocument.getElementById("bookContextProperties");
+ let synchronizeMenuItem = abDocument.getElementById("bookContextSynchronize");
+ let printMenuItem = abDocument.getElementById("bookContextPrint");
+ let deleteMenuItem = abDocument.getElementById("bookContextDelete");
+ let removeMenuItem = abDocument.getElementById("bookContextRemove");
+ let startupDefaultItem = abDocument.getElementById(
+ "bookContextStartupDefault"
+ );
+
+ Assert.equal(booksList.rowCount, 6);
+
+ // Test that the menu does not show for All Address Books.
+
+ await rightClickOnIndex(0);
+ Assert.equal(booksList.selectedIndex, 0);
+ Assert.equal(abDocument.activeElement, booksList);
+
+ let visibleItems = [...menu.children].filter(BrowserTestUtils.is_visible);
+ Assert.equal(visibleItems.length, 1);
+ Assert.equal(
+ visibleItems[0],
+ startupDefaultItem,
+ "only the startup default item should be visible"
+ );
+ let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.hidePopup();
+
+ // Test directories that can't be deleted.
+
+ for (let index of [1, booksList.rowCount - 1]) {
+ await rightClickOnIndex(index);
+ Assert.equal(booksList.selectedIndex, index);
+ Assert.ok(BrowserTestUtils.is_visible(propertiesMenuItem));
+ Assert.ok(!propertiesMenuItem.disabled);
+ Assert.ok(!BrowserTestUtils.is_visible(synchronizeMenuItem));
+ Assert.ok(BrowserTestUtils.is_visible(printMenuItem));
+ Assert.ok(!printMenuItem.disabled);
+ Assert.ok(BrowserTestUtils.is_visible(deleteMenuItem));
+ Assert.ok(deleteMenuItem.disabled);
+ Assert.ok(!BrowserTestUtils.is_visible(removeMenuItem));
+ hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.hidePopup();
+ await hiddenPromise;
+ Assert.equal(abDocument.activeElement, booksList);
+ }
+
+ // Test and delete CardDAV directory at index 4.
+
+ await rightClickOnIndex(4);
+ Assert.equal(booksList.selectedIndex, 4);
+ Assert.ok(BrowserTestUtils.is_visible(propertiesMenuItem));
+ Assert.ok(!propertiesMenuItem.disabled);
+ Assert.ok(BrowserTestUtils.is_visible(synchronizeMenuItem));
+ Assert.ok(!synchronizeMenuItem.disabled);
+ Assert.ok(BrowserTestUtils.is_visible(printMenuItem));
+ Assert.ok(!printMenuItem.disabled);
+ Assert.ok(!BrowserTestUtils.is_visible(deleteMenuItem));
+ Assert.ok(BrowserTestUtils.is_visible(removeMenuItem));
+ Assert.ok(!removeMenuItem.disabled);
+ let promptPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ let selectPromise = BrowserTestUtils.waitForEvent(booksList, "select");
+ hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.activateItem(removeMenuItem);
+ await promptPromise;
+ await selectPromise;
+ await hiddenPromise;
+ Assert.equal(abDocument.activeElement, booksList);
+
+ Assert.equal(booksList.rowCount, 5);
+ Assert.equal(booksList.selectedIndex, 4);
+ Assert.equal(menu.state, "closed");
+
+ // Test and delete list at index 3, then directory at index 2.
+
+ for (let index of [3, 2]) {
+ await new Promise(r => abWindow.setTimeout(r, 250));
+ await rightClickOnIndex(index);
+ Assert.equal(booksList.selectedIndex, index);
+ Assert.ok(BrowserTestUtils.is_visible(propertiesMenuItem));
+ Assert.ok(!propertiesMenuItem.disabled);
+ Assert.ok(!BrowserTestUtils.is_visible(synchronizeMenuItem));
+ Assert.ok(BrowserTestUtils.is_visible(printMenuItem));
+ Assert.ok(!printMenuItem.disabled);
+ Assert.ok(BrowserTestUtils.is_visible(deleteMenuItem));
+ Assert.ok(!deleteMenuItem.disabled);
+ Assert.ok(!BrowserTestUtils.is_visible(removeMenuItem));
+ promptPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ selectPromise = BrowserTestUtils.waitForEvent(booksList, "select");
+ hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.activateItem(deleteMenuItem);
+ await promptPromise;
+ await selectPromise;
+ await hiddenPromise;
+ Assert.equal(abDocument.activeElement, booksList);
+
+ if (index == 3) {
+ Assert.equal(booksList.rowCount, 4);
+ // Moves to parent when last list is deleted.
+ Assert.equal(booksList.selectedIndex, 2);
+ } else {
+ Assert.equal(booksList.rowCount, 3);
+ Assert.equal(booksList.selectedIndex, 2);
+ }
+ Assert.equal(menu.state, "closed");
+ }
+
+ // Test that the menu does not show beyond the last book.
+
+ EventUtils.synthesizeMouseAtCenter(
+ booksList,
+ 100,
+ booksList.clientHeight - 10,
+ { type: "contextmenu" },
+ abWindow
+ );
+ Assert.equal(booksList.selectedIndex, 2);
+ await new Promise(r => abWindow.setTimeout(r, 500));
+ Assert.equal(menu.state, "closed", "menu stayed closed as expected");
+ Assert.equal(abDocument.activeElement, booksList);
+
+ await closeAddressBookWindow();
+});
+
+/**
+ * Tests the menu button on each item.
+ */
+add_task(async function test_context_menu_button() {
+ let book = createAddressBook("Ordinary Book");
+ book.addMailList(createMailingList("Ordinary List"));
+
+ let abWindow = await openAddressBookWindow();
+ let booksList = abWindow.booksList;
+ let menu = abWindow.document.getElementById("bookContext");
+
+ for (let row of booksList.rows) {
+ info(row.querySelector(".bookRow-name, .listRow-name").textContent);
+ let button = row.querySelector(".bookRow-menu, .listRow-menu");
+ Assert.ok(BrowserTestUtils.is_hidden(button), "menu button is hidden");
+
+ EventUtils.synthesizeMouse(row, 100, 5, { type: "mousemove" }, abWindow);
+ Assert.ok(BrowserTestUtils.is_visible(button), "menu button is visible");
+
+ let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(button, {}, abWindow);
+ await shownPromise;
+
+ let buttonRect = button.getBoundingClientRect();
+ let menuRect = menu.getBoundingClientRect();
+ Assert.less(
+ Math.abs(menuRect.top - buttonRect.bottom),
+ 13,
+ "menu appeared near the button vertically"
+ );
+ Assert.less(
+ Math.abs(menuRect.left - buttonRect.left),
+ 20,
+ "menu appeared near the button horizontally"
+ );
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.hidePopup();
+ await hiddenPromise;
+ }
+
+ await closeAddressBookWindow();
+
+ await promiseDirectoryRemoved(book.URI);
+});
+
+/**
+ * Tests that the collapsed state of books survives a reload of the page.
+ */
+add_task(async function test_collapse_expand() {
+ Services.xulStore.removeDocument("about:addressbook");
+
+ personalBook.addMailList(createMailingList("Personal List 1"));
+ personalBook.addMailList(createMailingList("Personal List 2"));
+
+ historyBook.addMailList(createMailingList("History List 1"));
+
+ let book1 = createAddressBook("Book 1");
+ book1.addMailList(createMailingList("Book 1 List 1"));
+ book1.addMailList(createMailingList("Book 1 List 2"));
+
+ let book2 = createAddressBook("Book 2");
+ book2.addMailList(createMailingList("Book 2 List 1"));
+ book2.addMailList(createMailingList("Book 2 List 2"));
+ book2.addMailList(createMailingList("Book 2 List 3"));
+
+ function getRowForBook(book) {
+ return abDocument.getElementById(`book-${book.UID}`);
+ }
+
+ function checkCollapsedState(book, expectedCollapsed) {
+ Assert.equal(
+ getRowForBook(book).classList.contains("collapsed"),
+ expectedCollapsed,
+ `${book.dirName} is ${expectedCollapsed ? "collapsed" : "expanded"}`
+ );
+ }
+
+ function toggleCollapsedState(book) {
+ let twisty = getRowForBook(book).querySelector(".twisty");
+ Assert.ok(
+ BrowserTestUtils.is_visible(twisty),
+ `twisty for ${book.dirName} is visible`
+ );
+ EventUtils.synthesizeMouseAtCenter(twisty, {}, abWindow);
+ }
+
+ let abWindow = await openAddressBookWindow();
+ let abDocument = abWindow.document;
+
+ checkCollapsedState(personalBook, false);
+ checkCollapsedState(book1, false);
+ checkCollapsedState(book2, false);
+ checkCollapsedState(historyBook, false);
+
+ toggleCollapsedState(personalBook);
+ toggleCollapsedState(book1);
+
+ info("Closing and re-opening");
+ await closeAddressBookWindow();
+ abWindow = await openAddressBookWindow();
+ abDocument = abWindow.document;
+
+ checkCollapsedState(personalBook, true);
+ checkCollapsedState(book1, true);
+ checkCollapsedState(book2, false);
+ checkCollapsedState(historyBook, false);
+
+ toggleCollapsedState(book1);
+ toggleCollapsedState(book2);
+ toggleCollapsedState(historyBook);
+
+ info("Closing and re-opening");
+ await closeAddressBookWindow();
+ abWindow = await openAddressBookWindow();
+ abDocument = abWindow.document;
+
+ checkCollapsedState(personalBook, true);
+ checkCollapsedState(book1, false);
+ checkCollapsedState(book2, true);
+ checkCollapsedState(historyBook, true);
+
+ toggleCollapsedState(personalBook);
+
+ info("Closing and re-opening");
+ await closeAddressBookWindow();
+ await promiseDirectoryRemoved(book2.URI);
+ abWindow = await openAddressBookWindow();
+ abDocument = abWindow.document;
+
+ checkCollapsedState(personalBook, false);
+ checkCollapsedState(book1, false);
+ checkCollapsedState(historyBook, true);
+
+ await closeAddressBookWindow();
+
+ personalBook.childNodes.forEach(list => personalBook.deleteDirectory(list));
+ historyBook.childNodes.forEach(list => historyBook.deleteDirectory(list));
+ await promiseDirectoryRemoved(book1.URI);
+ Services.xulStore.removeDocument("about:addressbook");
+});
+
+/**
+ * Tests that the chosen default directory (or lack thereof) is opened when
+ * the page opens.
+ */
+add_task(async function test_startup_directory() {
+ const URI_PREF = "mail.addr_book.view.startupURI";
+ const DEFAULT_PREF = "mail.addr_book.view.startupURIisDefault";
+
+ Services.prefs.clearUserPref(URI_PREF);
+ Services.prefs.clearUserPref(DEFAULT_PREF);
+
+ async function checkMenuItem(index, expectChecked, toggle = false) {
+ await rightClickOnIndex(index);
+
+ let menu = abWindow.document.getElementById("bookContext");
+ let item = abWindow.document.getElementById("bookContextStartupDefault");
+ Assert.equal(
+ item.hasAttribute("checked"),
+ expectChecked,
+ `directory at index ${index} is the default?`
+ );
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ if (toggle) {
+ menu.activateItem(item);
+ } else {
+ menu.hidePopup();
+ }
+ await hiddenPromise;
+ }
+
+ // With the defaults, All Address Books should open.
+ // No changes should be made to the prefs.
+
+ let abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed();
+ await checkMenuItem(0, true);
+ await checkMenuItem(1, false);
+ await checkMenuItem(2, false);
+ openDirectory(personalBook);
+ await closeAddressBookWindow();
+ Assert.ok(!Services.prefs.prefHasUserValue(URI_PREF));
+
+ // Now we'll set the default to "last-used".
+ // The last-used book should be saved.
+
+ abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed();
+ await checkMenuItem(0, true);
+ await checkMenuItem(1, false);
+ await checkMenuItem(2, false);
+ Services.prefs.setBoolPref(DEFAULT_PREF, false);
+ openDirectory(personalBook);
+ await closeAddressBookWindow();
+ Assert.equal(Services.prefs.getStringPref(URI_PREF), personalBook.URI);
+
+ // The last-used book should open.
+
+ abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed(personalBook);
+ await checkMenuItem(0, false);
+ await checkMenuItem(1, false);
+ await checkMenuItem(2, false);
+ openDirectory(historyBook);
+ await closeAddressBookWindow();
+ Assert.equal(Services.prefs.getStringPref(URI_PREF), historyBook.URI);
+
+ // The last-used book should open.
+ // We'll set a default directory again.
+
+ abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed(historyBook);
+ await checkMenuItem(0, false);
+ await checkMenuItem(1, false);
+ await checkMenuItem(2, false, true);
+ openDirectory(personalBook);
+ await closeAddressBookWindow();
+ Assert.ok(Services.prefs.getBoolPref(DEFAULT_PREF));
+ Assert.equal(Services.prefs.getStringPref(URI_PREF), historyBook.URI);
+
+ // Check that the saved default opens. Change the default.
+
+ abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed(historyBook);
+ await checkMenuItem(0, false);
+ await checkMenuItem(2, true);
+ await checkMenuItem(1, false, true);
+ await closeAddressBookWindow();
+ Assert.ok(Services.prefs.getBoolPref(DEFAULT_PREF));
+ Assert.equal(Services.prefs.getStringPref(URI_PREF), personalBook.URI);
+
+ // Check that the saved default opens. Change the default to All Address Books.
+
+ abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed(personalBook);
+ await checkMenuItem(1, true);
+ await checkMenuItem(2, false);
+ await checkMenuItem(0, false, true);
+ await closeAddressBookWindow();
+ Assert.ok(Services.prefs.getBoolPref(DEFAULT_PREF));
+ Assert.ok(!Services.prefs.prefHasUserValue(URI_PREF));
+
+ // Check that the saved default opens. Clear the default.
+
+ abWindow = await openAddressBookWindow();
+ checkDirectoryDisplayed();
+ await checkMenuItem(1, false);
+ await checkMenuItem(2, false);
+ await checkMenuItem(0, true, true);
+ await closeAddressBookWindow();
+ Assert.ok(!Services.prefs.getBoolPref(DEFAULT_PREF));
+ Assert.ok(!Services.prefs.prefHasUserValue(URI_PREF));
+});
+
+add_task(async function test_total_address_book_count() {
+ let book1 = createAddressBook("First Book");
+ let book2 = createAddressBook("Second Book");
+ book1.addMailList(createMailingList("Ordinary List"));
+
+ book1.addCard(createContact("contact1", "book 1"));
+ book1.addCard(createContact("contact2", "book 1"));
+ book1.addCard(createContact("contact3", "book 1"));
+
+ book2.addCard(createContact("contact1", "book 2"));
+
+ let abWindow = await openAddressBookWindow();
+ let abDocument = abWindow.document;
+ let booksList = abWindow.booksList;
+ let cardCount = abDocument.getElementById("cardCount");
+
+ await openAllAddressBooks();
+ Assert.deepEqual(abDocument.l10n.getAttributes(cardCount), {
+ id: "about-addressbook-card-count-all",
+ args: {
+ count: 5,
+ },
+ });
+
+ for (let [index, [name, count]] of [
+ ["Personal Address Book", 0],
+ ["First Book", 4],
+ ["Ordinary List", 0],
+ ["Second Book", 1],
+ ].entries()) {
+ booksList.getRowAtIndex(index + 1).click();
+ Assert.deepEqual(abDocument.l10n.getAttributes(cardCount), {
+ id: "about-addressbook-card-count",
+ args: { name, count },
+ });
+ }
+
+ // Create a contact and check that the count updates.
+ // Select second book.
+ booksList.getRowAtIndex(4).click();
+ let createdPromise = TestUtils.topicObserved("addrbook-contact-created");
+ book2.addCard(createContact("contact2", "book 2"));
+ await createdPromise;
+ Assert.deepEqual(
+ abDocument.l10n.getAttributes(cardCount),
+ {
+ id: "about-addressbook-card-count",
+ args: { name: "Second Book", count: 2 },
+ },
+ "Address Book count is updated on contact creation."
+ );
+
+ // Delete a contact an check that the count updates.
+ let promptPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ let deletedPromise = TestUtils.topicObserved("addrbook-contact-deleted");
+ let cards = abWindow.cardsPane.cardsList;
+ EventUtils.synthesizeMouseAtCenter(cards.getRowAtIndex(0), {}, abWindow);
+ EventUtils.synthesizeKey("VK_DELETE", {}, abWindow);
+ await promptPromise;
+ await deletedPromise;
+ Assert.deepEqual(
+ abDocument.l10n.getAttributes(cardCount),
+ {
+ id: "about-addressbook-card-count",
+ args: { name: "Second Book", count: 1 },
+ },
+ "Address Book count is updated on contact deletion."
+ );
+
+ await closeAddressBookWindow();
+ await promiseDirectoryRemoved(book1.URI);
+ await promiseDirectoryRemoved(book2.URI);
+});