Bug 526541 - Please add a "confirm on delete" option for address book entries...; r=bienvenu ui-r=bwinton
authorMike Conley <mconley@mozilla.com>
Tue, 24 May 2011 22:07:12 +0100
changeset 7816 e79d3d86eab02ff857b142562ae063615019679c
parent 7815 2171a0890992610ce1ed20e0c2a3ff122b9d3afc
child 7817 c0dae37dd87289927ae613d5607d83b1b90fed3d
push id6005
push userbugzilla@standard8.plus.com
push dateTue, 24 May 2011 21:15:49 +0000
treeherdercomm-central@f314c7f8580f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbienvenu, bwinton
bugs526541
Bug 526541 - Please add a "confirm on delete" option for address book entries...; r=bienvenu ui-r=bwinton
mail/components/addrbook/content/abCommon.js
mail/locales/en-US/chrome/messenger/addressbook/addressBook.properties
mail/test/mozmill/addrbook/test-address-book.js
mail/test/mozmill/shared-modules/test-address-book-helpers.js
--- a/mail/components/addrbook/content/abCommon.js
+++ b/mail/components/addrbook/content/abCommon.js
@@ -297,30 +297,33 @@ function InitCommonJS()
 function AbDelete()
 {
   var types = GetSelectedCardTypes();
   if (types == kNothingSelected)
     return;
 
   var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
   // If at least one mailing list is selected then prompt users for deletion.
-  if (types != kCardsOnly)
-  {
-    var confirmDeleteMessage;
-    if (types == kListsAndCards)
-      confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteListsAndContacts");
-    else if (types == kMultipleListsOnly)
-      confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteMailingLists");
+
+  var confirmDeleteMessage;
+  if (types == kListsAndCards)
+    confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteListsAndContacts");
+  else if (types == kMultipleListsOnly)
+    confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteMailingLists");
+  else if (types == kSingleListOnly)
+    confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteMailingList");
+  else if (types == kCardsOnly && gAbView && gAbView.selection) {
+    if (gAbView.selection.count < 2)
+      confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteContact");
     else
-      confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteMailingList");
-    if (!promptService.confirm(window, null, confirmDeleteMessage))
-      return;
+      confirmDeleteMessage = gAddressBookBundle.getString("confirmDeleteContacts");
   }
 
-  gAbView.deleteSelectedCards();
+  if (confirmDeleteMessage && promptService.confirm(window, null, confirmDeleteMessage))
+    gAbView.deleteSelectedCards();
 }
 
 function AbNewCard()
 {
   goNewCardDialog(GetSelectedDirectory());
 }
 
 function AbEditCard(card)
--- a/mail/locales/en-US/chrome/messenger/addressbook/addressBook.properties
+++ b/mail/locales/en-US/chrome/messenger/addressbook/addressBook.properties
@@ -63,16 +63,18 @@ incorrectEmailAddressFormatTitle=Incorre
 
 viewListTitle=Mailing List: %S
 mailListNameExistsTitle=Mailing List Already Exists
 mailListNameExistsMessage=A Mailing List with that name already exists. Please choose a different name.
 
 # used in the addressbook
 confirmDeleteMailingListTitle=Delete Mailing List
 confirmDeleteAddressbookTitle=Delete Address Book
+confirmDeleteContact=Are you sure you want to delete the selected contact?
+confirmDeleteContacts=Are you sure you want to delete the selected contacts?
 confirmDeleteAddressbook=Are you sure you want to delete the selected address book?
 confirmDeleteCollectionAddressbook=If this address book is deleted, %S will no longer collect addresses. Are you sure you want to delete the selected address book?
 confirmDeleteMailingList=Are you sure you want to delete the selected mailing list?
 confirmDeleteListsAndContacts=Are you sure you want to delete the selected contacts and mailing lists?
 confirmDeleteMailingLists=Are you sure you want to delete the selected mailing lists?
 
 propertyPrimaryEmail=Email
 propertyListName=List Name
--- a/mail/test/mozmill/addrbook/test-address-book.js
+++ b/mail/test/mozmill/addrbook/test-address-book.js
@@ -39,21 +39,60 @@
  * Tests for the address book.
  */
 
 var MODULE_NAME = 'test-address-book';
 
 var RELATIVE_ROOT = '../shared-modules';
 var MODULE_REQUIRES = ['address-book-helpers', 'folder-display-helpers'];
 
+const kPromptServiceUUID = "{6cc9c9fe-bc0b-432b-a410-253ef8bcc699}";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/Services.jsm");
+
 let abController = null;
-
 var addrBook1, addrBook2, addrBook3, addrBook4;
 var mListA, mListB, mListC, mListD, mListE;
 
+var gMockPromptService = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]),
+  _will_return: null,
+  _did_confirm: false,
+  _confirm_msg: null,
+
+  confirm: function(aParent, aDialogTitle, aText) {
+    this._did_confirm = true;
+    this._confirm_msg = aText;
+    return this._will_return;
+  },
+
+  _return: function(aReturn) {
+    this._will_return = aReturn;
+  },
+
+  _reset: function() {
+    this._will_return = null;
+    this._did_confirm = false;
+    this._confirm_msg = null;
+  },
+};
+
+var gMockPromptServiceFactory = {
+  createInstance: function(aOuter, aIID) {
+    if (aOuter != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+
+    if (!aIID.equals(Ci.nsIPromptService))
+      throw Cr.NS_ERROR_NO_INTERFACE;
+
+    return gMockPromptService;
+  }
+};
+
 function setupModule(module)
 {
   let fdh = collector.getModule('folder-display-helpers');
   fdh.installInto(module);
 
   let abh = collector.getModule('address-book-helpers');
   abh.installInto(module);
 
@@ -149,8 +188,135 @@ function test_persist_collapsed_and_expa
   abController.window.close();
   abController = open_address_book_window();
 
   assert_true(!is_address_book_collapsed(addrBook2));
   assert_true(is_address_book_collapsed(addrBook1));
   assert_true(is_address_book_collapsed(addrBook3));
 }
 
+/* Test that if we try to delete a contact, that we are given
+ * a confirm prompt.
+ */
+function test_deleting_contact_causes_confirm_prompt()
+{
+  // Register the Mock Prompt Service
+
+  Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+            .registerFactory(Components.ID(kPromptServiceUUID),
+                             "Mock Prompt Service",
+                             "@mozilla.org/embedcomp/prompt-service;1",
+                             gMockPromptServiceFactory);
+
+  // Create a contact that we'll try to delete
+  let contact1 = create_contact("test@nobody.com", "Sammy Jenkis", true);
+  let toDelete = [contact1];
+
+  let bundle = Services.strings
+                       .createBundle("chrome://messenger/locale/addressbook/addressBook.properties")
+  let confirmSingle = bundle.GetStringFromName("confirmDeleteContact");
+  // Add some contacts to the address book
+  load_contacts_into_address_book(addrBook1, toDelete);
+  select_address_book(addrBook1);
+
+  let totalEntries = abController.window.gAbView.rowCount;
+
+  // Set the mock prompt to return false, so that the
+  // contact should not be deleted.
+  gMockPromptService._return(false);
+
+  // Now attempt to delete the contact
+  select_contact(toDelete);
+  abController.keypress(null, "VK_DELETE", {});
+
+  // Was a confirm displayed?
+  assert_true(gMockPromptService._did_confirm);
+  // Was the right message displayed?
+  assert_equals(gMockPromptService._confirm_msg, confirmSingle);
+  // The contact should not have been deleted.
+  assert_equals(abController.window.gAbView.rowCount, totalEntries);
+
+  gMockPromptService._reset();
+
+  // Now we'll return true on confirm so that
+  // the contact is deleted.
+  gMockPromptService._return(true);
+  select_contact(toDelete);
+  abController.keypress(null, "VK_DELETE", {});
+
+  // Was a confirm displayed?
+  assert_true(gMockPromptService._did_confirm);
+  // Was the right message displayed?
+  assert_equals(gMockPromptService._confirm_msg, confirmSingle);
+  // The contact should have been deleted.
+  assert_equals(abController.window.gAbView.rowCount,
+                totalEntries - toDelete.length);
+
+  Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+            .unregisterFactory(Components.ID(kPromptServiceUUID),
+                               gMockPromptServiceFactory);
+}
+
+/* Test that if we try to delete multiple contacts, that we are give
+ * a confirm prompt.
+ */
+function test_deleting_contacts_causes_confirm_prompt()
+{
+  // Register the Mock Prompt Service
+
+  Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+            .registerFactory(Components.ID(kPromptServiceUUID),
+                             "Mock Prompt Service",
+                             "@mozilla.org/embedcomp/prompt-service;1",
+                             gMockPromptServiceFactory);
+
+  // Create some contacts that we'll try to delete.
+  let contact2 = create_contact("test2@nobody.com", "Leonard Shelby", true);
+  let contact3 = create_contact("test3@nobody.com", "John Edward Gammell", true);
+  let contact4 = create_contact("test4@nobody.com", "Natalie", true);
+
+  let toDelete = [contact2, contact3, contact4];
+
+  let bundle = Services.strings
+                       .createBundle("chrome://messenger/locale/addressbook/addressBook.properties")
+  let confirmMultiple = bundle.GetStringFromName("confirmDeleteContacts");
+
+  // Add some contacts to the address book
+  load_contacts_into_address_book(addrBook1, toDelete);
+  select_address_book(addrBook1);
+
+  let totalEntries = abController.window.gAbView.rowCount;
+
+  // Set the mock prompt to return false, so that the
+  // contact should not be deleted.
+  gMockPromptService._return(false);
+
+  // Now attempt to delete the contact
+  select_contacts(toDelete);
+  abController.keypress(null, "VK_DELETE", {});
+
+  // Was a confirm displayed?
+  assert_true(gMockPromptService._did_confirm);
+  // Was the right message displayed?
+  assert_equals(gMockPromptService._confirm_msg, confirmMultiple);
+  // The contact should not have been deleted.
+  assert_equals(abController.window.gAbView.rowCount, totalEntries);
+
+  gMockPromptService._reset();
+
+  // Now we'll return true on confirm so that
+  // the contact is deleted.
+  gMockPromptService._return(true);
+  select_contacts(toDelete);
+  abController.keypress(null, "VK_DELETE", {});
+
+  // Was a confirm displayed?
+  assert_true(gMockPromptService._did_confirm);
+  // Was the right message displayed?
+  assert_equals(gMockPromptService._confirm_msg, confirmMultiple);
+  // The contact should have been deleted.
+  assert_equals(abController.window.gAbView.rowCount,
+                totalEntries - toDelete.length);
+
+  Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+            .unregisterFactory(Components.ID(kPromptServiceUUID),
+                               gMockPromptServiceFactory);
+}
--- a/mail/test/mozmill/shared-modules/test-address-book-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-address-book-helpers.js
@@ -84,16 +84,21 @@ function installInto(module) {
   // the same code as set_address_books_expanded/collapsed, so I just
   // alias them here.
   module.set_address_book_collapsed = set_address_books_collapsed;
   module.set_address_book_expanded = set_address_books_expanded;
   module.is_address_book_collapsed = is_address_book_collapsed;
   module.is_address_book_collapsible = is_address_book_collapsible;
   module.get_name_of_address_book_element_at = get_name_of_address_book_element_at;
   module.select_address_book = select_address_book;
+  module.get_contact_ab_view_index = get_contact_ab_view_index;
+  // select_contact is aliased for select_contacts, since they
+  // share the same code.
+  module.select_contact = select_contacts;
+  module.select_contacts = select_contacts;
 }
 
 /**
  * Make sure that there is a card for this email address
  * @param emailAddress the address that should have a card
  * @param displayName the display name the card should have
  * @param preferDisplayName |true| if the card display name should override the
  *                          header display name
@@ -231,27 +236,34 @@ function get_mailing_list_from_address_b
       return list;
   }
   throw Error("Could not find a mailing list with dirName " + aDirName);
 }
 
 /* Given some address book, adds a collection of contacts to that
  * address book.
  * @param aAddressBook an address book to add the contacts to
- * @param aContacts a collection of contacts, where each contact has
- *                  members "email" and "displayName"
+ * @param aContacts a collection of nsIAbCards, or contacts,
+ *                  where each contact has members "email"
+ *                  and "displayName"
  *
  *                  Example:
  *                  [{email: 'test@test.com', displayName: 'Sammy Jenkis'}]
  */
 function load_contacts_into_address_book(aAddressBook, aContacts)
 {
   for each (contact_info in aContacts) {
-    let contact = create_contact(contact_info.email,
+    let contact;
+
+    if (contact_info instanceof Ci.nsIAbCard)
+      contact = contact_info.QueryInterface(Ci.nsIAbCard);
+    else
+      contact = create_contact(contact_info.email,
                                  contact_info.displayName, true);
+
     aAddressBook.addCard(contact);
   }
 }
 
 /* Given some mailing list, adds a collection of contacts to that
  * mailing list.
  * @param aMailingList a mailing list to add the contacts to
  * @param aContacts a collection of contacts, where each contact has
@@ -264,31 +276,51 @@ function load_contacts_into_mailing_list
 {
   for each (contact_info in aContacts) {
     let contact = create_contact(contact_info.email,
                                  contact_info.displayName, true);
     aMailingList.addressLists.appendElement(contact, false);
   }
 }
 
-/* Given some address book, return the row index for that address book
- * in the tree view.  Throws an error if it cannot find the address book.
- * @param aAddrBook an address book to search for
+/* given some address book, return the row index for that address book
+ * in the tree view.  throws an error if it cannot find the address book.
+ * @param aaddrbook an address book to search for
  * @return the row index for that address book
  */
-function get_address_book_tree_view_index(aAddrBook)
+function get_address_book_tree_view_index(aaddrbook)
 {
-  let addrBooks = abController.window.gDirectoryTreeView._rowMap;
-  for (let i = 0; i < addrBooks.length; i++) {
-    if (addrBooks[i]._directory == aAddrBook) {
+  let addrbooks = abController.window.gDirectoryTreeView._rowMap;
+  for (let i = 0; i < addrbooks.length; i++) {
+    if (addrbooks[i]._directory == aaddrbook) {
       return i;
     }
   }
-  throw Error("Could not find the index for the address book named "
-              + aAddrbook.dirName);
+  throw error("could not find the index for the address book named "
+              + aaddrbook.dirname);
+}
+
+/* Given some contact, return the row index for that contact in the
+ * address book view.  Assumes that the address book that the contact
+ * belongs to is currently selected.  Throws an error if it cannot
+ * find the contact.
+ * @param aContact a contact to search for
+ * @return the row index for that contact
+ */
+function get_contact_ab_view_index(aContact)
+{
+  let contacts = abController.window.gAbView;
+  for (let i = 0; i < contacts.rowCount; i++) {
+    let contact = contacts.getCardFromRow(i);
+    if (contact.localId == aContact.localId &&
+        !contact.isMailList)
+      return i;
+  }
+  throw Error("Could not find the index for the contact named "
+              + aContact.displayName);
 }
 
 /* Determines whether or not an address book is collapsed in
  * the tree view.
  * @param aAddrBook the address book to check
  * @return true if the address book is collapsed, otherwise false
  */
 function is_address_book_collapsed(aAddrbook)
@@ -369,8 +401,26 @@ function get_name_of_address_book_elemen
 /* Selects a given address book in the tree view.
  * @param aAddrBook an address book to select
  */
 function select_address_book(aAddrBook)
 {
   let aIndex = get_address_book_tree_view_index(aAddrBook);
   abController.window.gDirectoryTreeView.selection.select(aIndex);
 }
+
+/* Selects one or more contacts in an address book, assuming that
+ * the address book is already selected.  Pass a single nsIAbCard
+ * to select one contact, or an array of nsIAbCards to select
+ * multiple.
+ */
+function select_contacts(aContacts)
+{
+  if (!Array.isArray(aContacts))
+    aContacts = [aContacts];
+
+  abController.window.gAbView.selection.clearSelection();
+  for (let i = 0; i < aContacts.length; i++) {
+    let aIndex = get_contact_ab_view_index(aContacts[i]);
+    abController.window.gAbView.selection.toggleSelect(aIndex);
+  }
+}
+