diff --git a/resources/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js index f6b4157abf13b4ff7ecc28e060dcdc2aaa96fe04..323fc4e1e3c4ca1132ca91a172723d296113665a 100644 --- a/resources/static/dialog/controllers/authenticate_controller.js +++ b/resources/static/dialog/controllers/authenticate_controller.js @@ -44,6 +44,7 @@ validation = bid.Validation, tooltip = bid.Tooltip, helpers = bid.Helpers, + dialogHelpers = helpers.Dialog, dom = bid.DOM, lastEmail = ""; @@ -76,7 +77,7 @@ cancelEvent(event); if(email) { - helpers.createUser.call(self, email); + dialogHelpers.createUser.call(self, email); } } @@ -88,10 +89,12 @@ cancelEvent(event); if(email && pass) { - helpers.authenticateUser.call(self, email, pass, function() { - self.close("authenticated", { - email: email - }); + dialogHelpers.authenticateUser.call(self, email, pass, function(authenticated) { + if (authenticated) { + self.close("authenticated", { + email: email + }); + } }); } } @@ -102,7 +105,7 @@ cancelEvent(event); if(email) { - helpers.resetPassword.call(this, email); + dialogHelpers.resetPassword.call(this, email); } } diff --git a/resources/static/dialog/controllers/pickemail_controller.js b/resources/static/dialog/controllers/pickemail_controller.js index 57bcb677c286b282dab402dc4fd14f9e63116f4a..e5e02d13d51276c1255bdb8530fc476c9156cba7 100644 --- a/resources/static/dialog/controllers/pickemail_controller.js +++ b/resources/static/dialog/controllers/pickemail_controller.js @@ -43,6 +43,7 @@ errors = bid.Errors, storage = bid.Storage, helpers = bid.Helpers, + dialogHelpers = helpers.Dialog, dom = bid.DOM, assertion; @@ -109,7 +110,7 @@ storage.site.set(origin, "remember", $("#remember").is(":checked")); } - helpers.getAssertion.call(self, email); + dialogHelpers.getAssertion.call(self, email); } } @@ -128,7 +129,7 @@ bid.Tooltip.showTooltip("#already_taken"); } else { - helpers.addEmail.call(self, email); + dialogHelpers.addEmail.call(self, email); } }, self.getErrorDialog(errors.isEmailRegistered)); } diff --git a/resources/static/dialog/controllers/required_email_controller.js b/resources/static/dialog/controllers/required_email_controller.js index 812c48dacf826bb3c9919630f98a6e90758c91fa..c9acd8d5415aca9d13956a6fff52cdfa1ce547b7 100644 --- a/resources/static/dialog/controllers/required_email_controller.js +++ b/resources/static/dialog/controllers/required_email_controller.js @@ -42,6 +42,7 @@ user = bid.User, errors = bid.Errors, helpers = bid.Helpers, + dialogHelpers = helpers.Dialog, dom = bid.DOM, assertion; @@ -54,7 +55,7 @@ // If the user is already authenticated and they own this address, sign // them right in. if(self.authenticated) { - helpers.getAssertion.call(self, email); + dialogHelpers.getAssertion.call(self, email); } else { // If the user is not already authenticated, but they potentially own @@ -62,12 +63,14 @@ // get the password right. var password = helpers.getAndValidatePassword("#password"); if (password) { - helpers.authenticateUser.call(self, email, password, function() { - // Now that the user has authenticated, sync their emails and get an - // assertion for the email we care about. - user.syncEmailKeypair(email, function() { - helpers.getAssertion.call(self, email); - }, self.getErrorDialog(errors.syncEmailKeypair)); + dialogHelpers.authenticateUser.call(self, email, password, function(authenticated) { + if (authenticated) { + // Now that the user has authenticated, sync their emails and get an + // assertion for the email we care about. + user.syncEmailKeypair(email, function() { + dialogHelpers.getAssertion.call(self, email); + }, self.getErrorDialog(errors.syncEmailKeypair)); + } }); } } @@ -88,10 +91,10 @@ // If the address is registered, it means another account has control of // the address and we are consolidating. If the email is not registered // then it means add the address to the current user's account. - helpers.addEmail.call(self, self.email); + dialogHelpers.addEmail.call(self, self.email); } else { - helpers.createUser.call(self, self.email); + dialogHelpers.createUser.call(self, self.email); } } @@ -99,7 +102,7 @@ event && event.preventDefault(); var self=this; - helpers.resetPassword.call(self, self.email); + dialogHelpers.resetPassword.call(self, self.email); } diff --git a/resources/static/dialog/dialog.js b/resources/static/dialog/dialog.js index 46beb215dc08ac90235dbb0472657e0411158200..7ccca0ba918d6b06d2c5e95cb931a3ae19117a0d 100644 --- a/resources/static/dialog/dialog.js +++ b/resources/static/dialog/dialog.js @@ -44,8 +44,7 @@ steal 'jquery/controller', // a widget factory 'jquery/controller/subscribe') // subscribe to OpenAjax.hub - .resources( - 'channel') + .resources( 'channel') .then( '../lib/jschannel', '../lib/base64', @@ -67,7 +66,8 @@ steal '../shared/browser-support', '../shared/browserid-extensions', '../shared/wait-messages', - '../shared/helpers' + '../shared/helpers', + 'resources/helpers' ) .controllers('page', diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..c1f1a61cb3c435e2716e4e2a005d08b6dca2c7e6 --- /dev/null +++ b/resources/static/dialog/resources/helpers.js @@ -0,0 +1,158 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global 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 ***** */ + +// The way this works, when the dialog is opened from a web page, it opens +// the window with a #host=<requesting_host_name> parameter in its URL. +// window.setupChannel is called automatically when the dialog is opened. We +// assume that navigator.id.getVerifiedEmail was the function called, we will +// keep this assumption until we start experimenting. Since IE has some +// serious problems iwth postMessage from a window to a child window, we are now +// communicating not directly with the calling window, but with an iframe +// on the same domain as us that we place into the calling window. We use a +// function within this iframe to relay messages back to the calling window. +// We do so by searching for the frame within the calling window, and then +// getting a reference to the proxy function. When getVerifiedEmail is +// complete, it calls the proxy function in the iframe, which then sends a +// message back to the calling window. + +(function() { + "use strict"; + + var bid = BrowserID, + helpers = bid.Helpers, + user = bid.User, + tooltip = bid.Tooltip, + errors = bid.Errors; + + function animateClose(callback) { + var body = $("body"), + doAnimation = $("#signIn").length && body.innerWidth() > 640; + + if (doAnimation) { + $("#signIn").animate({"width" : "685px"}, "slow", function () { + // post animation + body.delay(500).animate({ "opacity" : "0.5"}, "fast", function () { + callback(); + }); + }); + } + else { + callback(); + } + } + + function getAssertion(email, callback) { + var self=this; + user.getAssertion(email, function(assert) { + assert = assert || null; + animateClose(function() { + self.close("assertion_generated", { + assertion: assert + }); + + if (callback) callback(assert); + }); + }, self.getErrorDialog(errors.getAssertion)); + } + + function authenticateUser(email, pass, callback) { + var self=this; + user.authenticate(email, pass, + function onComplete(authenticated) { + if (!authenticated) { + tooltip.showTooltip("#cannot_authenticate"); + } + if (callback) callback(authenticated); + }, self.getErrorDialog(errors.authenticate)); + } + + function createUser(email, callback) { + var self=this; + user.createUser(email, function(staged) { + if (staged) { + self.close("user_staged", { + email: email + }); + } + else { + tooltip.showTooltip("#could_not_add"); + } + if (callback) callback(staged); + }, self.getErrorDialog(errors.createUser)); + } + + function resetPassword(email, callback) { + var self=this; + user.requestPasswordReset(email, function(status) { + if(status.success) { + self.close("reset_password", { + email: email + }); + } + else { + tooltip.showTooltip("#could_not_add"); + } + if (callback) callback(status.success); + }, self.getErrorDialog(errors.requestPasswordReset)); + } + + function addEmail(email, callback) { + var self=this; + user.addEmail(email, function(added) { + if (added) { + self.close("email_staged", { + email: email + }); + } + else { + tooltip.showTooltip("#could_not_add"); + } + if (callback) callback(added); + }, self.getErrorDialog(errors.addEmail)); + } + + helpers.Dialog = helpers.Dialog || {}; + + helpers.extend(helpers.Dialog, { + getAssertion: getAssertion, + authenticateUser: authenticateUser, + createUser: createUser, + addEmail: addEmail, + resetPassword: resetPassword + }); + +}()); diff --git a/resources/static/shared/helpers.js b/resources/static/shared/helpers.js index 339211313ae841c31b5f31793291f63d94175bc9..041c8b82d5f48fe4a984e48304d475272f269070 100644 --- a/resources/static/shared/helpers.js +++ b/resources/static/shared/helpers.js @@ -66,97 +66,6 @@ return password; } - /** - * XXX add a test for the next two and move them into a dialog specific - * helper module! - */ - function animateClose(callback) { - var body = $("body"), - doAnimation = $("#signIn").length && body.innerWidth() > 640; - - if (doAnimation) { - $("#signIn").animate({"width" : "685px"}, "slow", function () { - // post animation - body.delay(500).animate({ "opacity" : "0.5"}, "fast", function () { - callback(); - }); - }); - } - else { - callback(); - } - } - - // XXX Move this to a dialog specific module - function getAssertion(email) { - var self=this; - user.getAssertion(email, function(assert) { - assert = assert || null; - animateClose(function() { - self.close("assertion_generated", { - assertion: assert - }); - }); - }, self.getErrorDialog(errors.getAssertion)); - } - - // XXX Move this to a dialog specific module - function authenticateUser(email, pass, callback) { - var self=this; - user.authenticate(email, pass, - function onComplete(authenticated) { - if (authenticated) { - callback(); - } else { - tooltip.showTooltip("#cannot_authenticate"); - } - }, self.getErrorDialog(errors.authenticate)); - } - - function createUser(email) { - var self=this; - user.createUser(email, function(staged) { - if (staged) { - self.close("user_staged", { - email: email - }); - } - else { - tooltip.showTooltip("#could_not_add"); - } - }, self.getErrorDialog(errors.createUser)); - } - - function resetPassword(email) { - var self=this; - user.requestPasswordReset(email, function(status) { - if(status.success) { - self.close("reset_password", { - email: email - }); - } - else { - tooltip.showTooltip("#could_not_add"); - } - }, self.getErrorDialog(errors.requestPasswordReset)); - } - - function addEmail(email) { - var self=this; - user.addEmail(email, function(added) { - if (added) { - self.close("email_staged", { - email: email - }); - } - else { - tooltip.showTooltip("#could_not_add"); - } - }, function onFailure() { - tooltip.showTooltip("#could_not_add"); - }); - } - extend(helpers, { /** * Extend an object with the properties of another object. Overwrites @@ -181,17 +90,8 @@ * @param {string} target - target containing the password * @return {string} password if password is valid, null otw. */ - getAndValidatePassword: getAndValidatePassword, + getAndValidatePassword: getAndValidatePassword - /** - * XXX Get from here down out of here and into a specific dialog helpers - * module. - */ - getAssertion: getAssertion, - authenticateUser: authenticateUser, - createUser: createUser, - addEmail: addEmail, - resetPassword: resetPassword }); diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js index d129414f8a9110270966219c336d14c3e6b551ef..4fc861768aea63c06198d76fb461fd614a0173fa 100644 --- a/resources/static/test/qunit/qunit.js +++ b/resources/static/test/qunit/qunit.js @@ -22,6 +22,8 @@ steal.plugins( "/shared/validation", "/shared/helpers", + "/dialog/resources/helpers", + "/dialog/controllers/page_controller", "/dialog/controllers/required_email_controller", @@ -49,6 +51,7 @@ steal.plugins( "shared/network_unit_test", "shared/user_unit_test", "resources/channel_unit_test", + "resources/helpers_unit_test", "controllers/page_controller_unit_test", "controllers/pickemail_controller_unit_test", diff --git a/resources/static/test/qunit/resources/channel_unit_test.js b/resources/static/test/qunit/resources/channel_unit_test.js index 94c86024750ee65457228e4595406fda387b37af..edc8d821772b693bf5121fbb0a8e7869bb089140 100644 --- a/resources/static/test/qunit/resources/channel_unit_test.js +++ b/resources/static/test/qunit/resources/channel_unit_test.js @@ -35,6 +35,8 @@ * * ***** END LICENSE BLOCK ***** */ steal.then("/dialog/resources/channel", function() { + "use strict"; + var channel = BrowserID.Channel; var navMock = { diff --git a/resources/static/test/qunit/resources/helpers_unit_test.js b/resources/static/test/qunit/resources/helpers_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..75c8fcaa3dddddc201740b63123ffffe43ea8043 --- /dev/null +++ b/resources/static/test/qunit/resources/helpers_unit_test.js @@ -0,0 +1,262 @@ +/*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.then(function() { + "use strict"; + + var bid = BrowserID, + helpers = bid.Helpers, + dialogHelpers = helpers.Dialog, + network = bid.Network, + xhr = bid.Mocks.xhr, + user = bid.User, + storage = bid.Storage, + tooltip = bid.Tooltip, + closeCB, + errorCB; + + var controllerMock = { + close: function(message, info) { + closeCB && closeCB(message, info); + }, + + getErrorDialog: function(info) { + return function() { + errorCB && errorCB(info); + } + } + }; + + function expectedClose(message, field, value) { + return function(m, info) { + ok(m, message, "correct message: " + message); + + if(value) { + equal(info[field], value, field + " has correct value of " + value); + } + else { + ok(info[field], field + " has a value"); + } + } + } + + function badError() { + ok(false, "error should have never been called"); + } + + function expectedError() { + ok(true, "error condition expected"); + start(); + } + + function badClose() { + ok(false, "close should have never been called"); + } + + module("resources/helpers", { + setup: function() { + network.setXHR(xhr); + xhr.useResult("valid"); + storage.clear(); + closeCB = errorCB = null; + errorCB = badError; + }, + + teardown: function() { + network.setXHR($); + } + }); + + test("getAssertion happy case", function() { + closeCB = expectedClose("assertion_generated", "assertion"); + + storage.addEmail("registered@testuser.com", {}); + dialogHelpers.getAssertion.call(controllerMock, "registered@testuser.com", function(assertion) { + ok(assertion, "assertion given to close"); + start(); + }); + + stop(); + }); + + test("getAssertion with XHR error", function() { + closeCB = badClose; + errorCB = expectedError; + + xhr.useResult("ajaxError"); + storage.addEmail("registered@testuser.com", {}); + dialogHelpers.getAssertion.call(controllerMock, "registered@testuser.com", function() { + ok(false, "unexpected finish"); + start(); + }); + stop(); + }); + + test("authenticateUser happy case", function() { + dialogHelpers.authenticateUser.call(controllerMock, "testuser@testuser.com", "password", function(authenticated) { + equal(authenticated, true, "user is authenticated"); + start(); + }); + + stop(); + }); + + test("authenticateUser invalid credentials", function() { + xhr.useResult("invalid"); + dialogHelpers.authenticateUser.call(controllerMock, "testuser@testuser.com", "password", function(authenticated) { + equal(authenticated, false, "user is not authenticated"); + start(); + }); + + stop(); + }); + + test("authenticateUser XHR error", function() { + errorCB = expectedError; + + xhr.useResult("ajaxError"); + dialogHelpers.authenticateUser.call(controllerMock, "testuser@testuser.com", "password", function() { + ok(false, "unexpected success callback"); + start(); + }); + + stop(); + }); + + test("createUser happy case", function() { + closeCB = expectedClose("user_staged", "email", "unregistered@testuser.com"); + + dialogHelpers.createUser.call(controllerMock, "unregistered@testuser.com", function(staged) { + equal(staged, true, "user was staged"); + start(); + }); + + stop(); + }); + + test("createUser could not create case", function() { + closeCB = badClose; + + xhr.useResult("invalid"); + dialogHelpers.createUser.call(controllerMock, "registered@testuser.com", function(staged) { + equal(staged, false, "user was not staged"); + start(); + }); + + stop(); + }); + + + test("createUser with XHR error", function() { + errorCB = expectedError; + + xhr.useResult("ajaxError"); + dialogHelpers.createUser.call(controllerMock, "registered@testuser.com", function(staged) { + ok(false, "complete should not have been called"); + start(); + }); + stop(); + }); + + test("addEmail happy case", function() { + closeCB = expectedClose("email_staged", "email", "unregistered@testuser.com"); + dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) { + ok(added, "email added"); + start(); + }); + + stop(); + }); + + + test("addEmail throttled", function() { + xhr.useResult("throttle"); + dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) { + equal(added, false, "email not added"); + start(); + }); + + stop(); + }); + + test("addEmail with XHR error", function() { + errorCB = expectedError; + + xhr.useResult("ajaxError"); + dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) { + ok(false, "unexpected close"); + start(); + }); + + stop(); + }); + + test("resetPassword happy case", function() { + closeCB = expectedClose("reset_password", "email", "registered@testuser.com"); + dialogHelpers.resetPassword.call(controllerMock, "registered@testuser.com", function(reset) { + ok(reset, "password reset"); + start(); + }); + + stop(); + }); + + + test("resetPassword throttled", function() { + xhr.useResult("throttle"); + dialogHelpers.resetPassword.call(controllerMock, "registered@testuser.com", function(reset) { + equal(reset, false, "password not reset"); + start(); + }); + + stop(); + }); + + test("resetPassword with XHR error", function() { + errorCB = expectedError; + + xhr.useResult("ajaxError"); + dialogHelpers.resetPassword.call(controllerMock, "registered@testuser.com", function(reset) { + ok(false, "unexpected close"); + start(); + }); + + stop(); + }); +}); + + +