diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/components/addrbook/test/browser/browser_directory_tree.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | comm/mail/components/addrbook/test/browser/browser_directory_tree.js | 982 |
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); +}); |