diff --git a/resources/static/dialog/resources/user.js b/resources/static/dialog/resources/user.js
index 2f0a576dbd853ad4e26adf74ca29fd82498ac9cf..d843f79396860a99412278ab96f4cea5223e2724 100644
--- a/resources/static/dialog/resources/user.js
+++ b/resources/static/dialog/resources/user.js
@@ -709,5 +709,6 @@ BrowserID.User = (function() {
     }
   };
 
+  User.setOrigin(document.location.host);
   return User;
 }());
diff --git a/resources/static/js/pages/manage_account.js b/resources/static/js/pages/manage_account.js
index ee8ac18dfbe9b8ccc3b7317ea7802c002356d33f..2602352af96fa2d3b8134bd88c39a4bc35f4b590 100644
--- a/resources/static/js/pages/manage_account.js
+++ b/resources/static/js/pages/manage_account.js
@@ -34,13 +34,15 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
-(function() {
+BrowserID.manageAccount = (function() {
   "use strict";
 
   var bid = BrowserID,
-      User = bid.User,
+      user = bid.User,
       errors = bid.Errors,
-      pageHelpers = bid.PageHelpers;
+      pageHelpers = bid.PageHelpers,
+      confirmAction = confirm,
+      doc = document;
 
   function relativeDate(date) {
     var diff = (((new Date()).getTime() - date.getTime()) / 1000),
@@ -127,8 +129,8 @@
   function syncAndDisplayEmails() {
     var emails = {};
 
-    User.syncEmails(function() {
-      emails = User.getStoredEmailKeypairs();
+    user.syncEmails(function() {
+      emails = user.getStoredEmailKeypairs();
       if (_.isEmpty(emails)) {
         $("#content").hide();
       } else {
@@ -140,19 +142,19 @@
   }
 
   function onRemoveEmail(email, event) {
-    event.preventDefault();
+    event && event.preventDefault();
 
-    var emails = User.getStoredEmailKeypairs();
+    var emails = user.getStoredEmailKeypairs();
 
     if (_.size(emails) > 1) {
-      if (confirm("Remove " + email + " from your BrowserID?")) {
-        User.removeEmail(email, syncAndDisplayEmails, pageHelpers.getFailure(errors.removeEmail));
+      if (confirmAction("Remove " + email + " from your BrowserID?")) {
+        user.removeEmail(email, syncAndDisplayEmails, pageHelpers.getFailure(errors.removeEmail));
       }
     }
     else {
-      if (confirm('Removing the last address will cancel your BrowserID account.\nAre you sure you want to continue?')) {
-        User.cancelUser(function() {
-          document.location="/";
+      if (confirmAction('Removing the last address will cancel your BrowserID account.\nAre you sure you want to continue?')) {
+        user.cancelUser(function() {
+          doc.location="/";
         }, pageHelpers.getFailure(errors.cancelUser));
       }
     }
@@ -183,34 +185,56 @@
 
   }
 
-  BrowserID.manageAccount = function() {
-    $('#cancelAccount').click(function(event) {
-      event.preventDefault();
-      if (confirm('Are you sure you want to cancel your BrowserID account?')) {
-        User.cancelUser(function() {
-          document.location="/";
-        }, pageHelpers.getFailure(errors.cancelUser));
-      }
-    });
+  function cancelAccount(event) {
+    event && event.preventDefault();
+
+    if (confirmAction('Are you sure you want to cancel your BrowserID account?')) {
+      user.cancelUser(function() {
+        doc.location="/";
+      }, pageHelpers.getFailure(errors.cancelUser));
+    }
+  }
 
-    $('#manageAccounts').click(function(event) {
-        event.preventDefault();
+  function manageAccounts(event) {
+      event && event.preventDefault();
 
-        $('#emailList').addClass('remove');
-        $(this).hide();
-        $("#cancelManage").show();
-    });
-    
-    $('#cancelManage').click(function(event) {
-        event.preventDefault();
+      $('#emailList').addClass('remove');
+      $(this).hide();
+      $("#cancelManage").show();
+  }
 
-        $('#emailList').removeClass('remove');
-        $(this).hide();
-        $("#manageAccounts").show();
-    });
+  function cancelManage(event) {
+      event && event.preventDefault();
+
+      $('#emailList').removeClass('remove');
+      $(this).hide();
+      $("#manageAccounts").show();
+  }
+
+  function init(options) {
+    options = options || {};
+
+    if (options.document) doc = options.document;
+    if (options.confirm) confirmAction = options.confirm;
+
+    $('#cancelAccount').click(cancelAccount);
+    $('#manageAccounts').click(manageAccounts);
+    $('#cancelManage').click(cancelManage);
 
     syncAndDisplayEmails();
-  };
+  }
+
+  function reset() {
+    $('#cancelAccount').unbind("click", cancelAccount);
+    $('#manageAccounts').unbind("click", manageAccounts);
+    $('#cancelManage').unbind("click", cancelManage);
+  }
+
+  init.reset = reset;
+  init.cancelAccount = cancelAccount;
+  init.removeEmail = onRemoveEmail;
+
+  return init;
 
 }());
 
diff --git a/resources/static/test/qunit.html b/resources/static/test/qunit.html
index fcea3eb4bcd0fe7475890b87b05fd7f1e7bd182a..66a3ce39f6e3dfc823ed0023048c05f5fa7e2c75 100644
--- a/resources/static/test/qunit.html
+++ b/resources/static/test/qunit.html
@@ -62,6 +62,9 @@
       <li class="notification doh">doh</li> 
     </ul>
 
+    <ul id="emailList"> 
+    </ul>
+
     <script type="text/html" id="templateError">
       <div class="message">{{ action.title }}</div>
     </script>
@@ -71,5 +74,9 @@
         {{ contents }}
       </div>
     </script>
+
+    <script type="text/html" id="templateUser">
+      <li>{{email}}</li>
+    </script>
 	</body>
 </html>
diff --git a/resources/static/test/qunit/pages/manage_account_unit_test.js b/resources/static/test/qunit/pages/manage_account_unit_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..c981153d2974acab134053d479a2282f25845463
--- /dev/null
+++ b/resources/static/test/qunit/pages/manage_account_unit_test.js
@@ -0,0 +1,213 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global steal: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+steal.plugins("jquery").then("/dialog/resources/network", "/js/pages/manage_account", function() {
+  "use strict";
+
+  var bid = BrowserID,
+      network = bid.Network,
+      storage = bid.Storage,
+      user = bid.User,
+      xhr = bid.Mocks.xhr,
+      validToken = true,
+      TEST_ORIGIN = "http://browserid.org",
+      TEST_DELAY = 100,
+      ERROR_DELAY = 250,
+      mocks = {
+        confirm: function() { return true; },
+        document: { location: "" }
+      };
+
+  module("pages/manage_account", {
+    setup: function() {
+      network.setXHR(xhr);  
+      xhr.useResult("valid");
+      user.setOrigin(TEST_ORIGIN);
+      $("#emailList").empty();
+      $("#error").hide();
+      mocks.document.location = "";
+      storage.clear();
+    },
+    teardown: function() {
+      network.setXHR($);  
+      $("#emailList").empty();
+      $("#error").hide();
+    }
+  });
+
+  test("no email addresses are displayed if there are no children", function() {
+    xhr.useResult("noidentities");
+
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      equal($("#emailList").children().length, 0, "no children have been added"); 
+      start();
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("email addresses added if there are children", function() {
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      equal($("#emailList").children().length, 1, "there has been one child added"); 
+      start();
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("sync XHR error on startup", function() {
+    xhr.useResult("ajaxError");
+
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      equal($("#error").is(":visible"), true, "error message is visible on XHR error"); 
+      start();
+    }, ERROR_DELAY);
+
+    stop();
+  });
+
+  test("removeEmail with multiple emails", function() {
+    // start with multiple addresses.
+    xhr.useResult("multiple");
+
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      // switch to a single address return on the sync.
+      xhr.useResult("valid");
+      bid.manageAccount.removeEmail("testuser@testuser.com");
+
+      setTimeout(function() {
+        equal($("#emailList").children().length, 1, "after removing an email, only one remains"); 
+        start();
+      }, TEST_DELAY);
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("removeEmail with multiple emails and XHR error", function() {
+    // start with multiple addresses.
+    xhr.useResult("multiple");
+
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      xhr.useResult("ajaxError");
+      bid.manageAccount.removeEmail("testuser@testuser.com");
+
+      setTimeout(function() {
+        equal($("#error").is(":visible"), true, "error message is visible on XHR error"); 
+        start();
+      }, ERROR_DELAY);
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("removeEmail with single email cancels account", function() {
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      bid.manageAccount.removeEmail("testuser@testuser.com");
+
+      setTimeout(function() {
+        equal(mocks.document.location, "/", "redirection happened");
+        start();
+      }, TEST_DELAY);
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("removeEmail with single email cancels account and XHR error", function() {
+    xhr.useResult("valid");
+
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      xhr.useResult("ajaxError");
+
+      bid.manageAccount.removeEmail("testuser@testuser.com");
+
+      setTimeout(function() {
+        equal($("#error").is(":visible"), true, "error message is visible on XHR error"); 
+        start();
+      }, ERROR_DELAY);
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("cancelAccount", function() {
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      bid.manageAccount.cancelAccount();
+
+      setTimeout(function() {
+        equal(mocks.document.location, "/", "redirection happened");
+        start();
+      }, TEST_DELAY);
+
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+  test("cancelAccount with XHR error", function() {
+    bid.manageAccount(mocks);
+
+    setTimeout(function() {
+      xhr.useResult("ajaxError");
+      bid.manageAccount.cancelAccount();
+
+      setTimeout(function() {
+        equal($("#error").is(":visible"), true, "error message is visible on XHR error"); 
+        start();
+      }, ERROR_DELAY);
+    }, TEST_DELAY);
+
+    stop();
+  });
+
+});
diff --git a/resources/static/dialog/test/qunit/pages/signin_unit_test.js b/resources/static/test/qunit/pages/signin_unit_test.js
similarity index 96%
rename from resources/static/dialog/test/qunit/pages/signin_unit_test.js
rename to resources/static/test/qunit/pages/signin_unit_test.js
index 5bb7a20aded96b4ba8aa8c362c51fdf64a91b8ae..ad6738d65ec8df41717e0c25135f7371673c713b 100644
--- a/resources/static/dialog/test/qunit/pages/signin_unit_test.js
+++ b/resources/static/test/qunit/pages/signin_unit_test.js
@@ -34,7 +34,7 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/signin", function() {
+steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/signin", function() {
   "use strict";
 
   var bid = BrowserID,
diff --git a/resources/static/dialog/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js
similarity index 93%
rename from resources/static/dialog/test/qunit/pages/signup_unit_test.js
rename to resources/static/test/qunit/pages/signup_unit_test.js
index 473ab14fa5d96a70d1f0ceac3e20f37240c6379b..3644704e374b6ec0103cbcac5e93dd7c4af44017 100644
--- a/resources/static/dialog/test/qunit/pages/signup_unit_test.js
+++ b/resources/static/test/qunit/pages/signup_unit_test.js
@@ -34,14 +34,15 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/signup", function() {
+steal.plugins("jquery").then("/test/qunit/mocks/xhr", "/dialog/resources/network", "/dialog/resources/user", "/js/pages/signup", function() {
   "use strict";
 
   var bid = BrowserID,
       network = bid.Network,
       user = bid.User,
       xhr = bid.Mocks.xhr,
-      CHECK_DELAY = 500;
+      CHECK_DELAY = 500,
+      testOrigin = "http://browserid.org";
 
   module("pages/signup", {
     setup: function() {
@@ -49,6 +50,7 @@ steal.plugins("jquery").then("/dialog/test/qunit/mocks/xhr", "/dialog/resources/
       $("#error").stop().hide();
       $(".notification").stop().hide();
       xhr.useResult("valid");
+      user.setOrigin(testOrigin);
       bid.signUp();
     },
     teardown: function() {
diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js
index 916a3e5a0dd0e6a709d7181950257c9c23be0f5c..f8869dd091de4aaad46d0479a1df212696dc52d4 100644
--- a/resources/static/test/qunit/qunit.js
+++ b/resources/static/test/qunit/qunit.js
@@ -30,6 +30,7 @@ steal("/dialog/resources/browserid.js",
   .then("pages/forgot_unit_test")
   .then("pages/signin_unit_test")
   .then("pages/signup_unit_test")
+  .then("pages/manage_account_unit_test")
   .then("resources/tooltip_unit_test")
   .then("resources/error-display_unit_test")
   .then("resources/channel_unit_test")
diff --git a/resources/static/dialog/test/qunit/resources/error-display_unit_test.js b/resources/static/test/qunit/resources/error-display_unit_test.js
similarity index 100%
rename from resources/static/dialog/test/qunit/resources/error-display_unit_test.js
rename to resources/static/test/qunit/resources/error-display_unit_test.js