diff --git a/resources/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js index 323fc4e1e3c4ca1132ca91a172723d296113665a..31e8a114566b07d442895ffb4b25a3f176782e79 100644 --- a/resources/static/dialog/controllers/authenticate_controller.js +++ b/resources/static/dialog/controllers/authenticate_controller.js @@ -76,7 +76,7 @@ cancelEvent(event); - if(email) { + if (email) { dialogHelpers.createUser.call(self, email); } } @@ -88,7 +88,7 @@ cancelEvent(event); - if(email && pass) { + if (email && pass) { dialogHelpers.authenticateUser.call(self, email, pass, function(authenticated) { if (authenticated) { self.close("authenticated", { @@ -99,16 +99,6 @@ } } - function resetPassword(event) { - var email = getEmail(); - - cancelEvent(event); - - if(email) { - dialogHelpers.resetPassword.call(this, email); - } - } - function animateSwap(fadeOutSelector, fadeInSelector, callback) { // XXX instead of using jQuery here, think about using CSS animations. $(fadeOutSelector).fadeOut(ANIMATION_TIME, function() { @@ -123,7 +113,7 @@ function enterEmailState(el) { if (!el.is(":disabled")) { this.submit = checkEmail; - animateSwap(".returning:visible,.newuser:visible,.forgot:visible", ".start"); + animateSwap(".returning:visible,.newuser:visible", ".start"); } } @@ -138,20 +128,12 @@ }); } - function forgotPasswordState(event) { + function forgotPassword(event) { cancelEvent(event); - - this.submit = resetPassword; - dom.setAttr("#email", "disabled", "disabled"); - - animateSwap(".start:visible,.newuser:visible,.returning:visible", ".forgot"); - } - - function cancelForgotPassword(event) { - cancelEvent(event); - - dom.removeAttr("#email", "disabled"); - enterPasswordState.call(this); + var email = getEmail(); + if (email) { + this.close("forgot_password", { email: email }); + } } function createUserState(event) { @@ -161,7 +143,7 @@ self.publish("create_user"); self.submit = createUser; - animateSwap(".start:visible,.returning:visible,.forgot:visible", ".newuser"); + animateSwap(".start:visible,.returning:visible", ".newuser"); } @@ -188,8 +170,7 @@ self.bind("#email", "keyup", emailKeyUp); - self.bind("#forgotPassword", "click", forgotPasswordState); - self.bind("#cancel_forgot_password", "click", cancelForgotPassword); + self.bind("#forgotPassword", "click", forgotPassword); self._super(); }, @@ -197,7 +178,7 @@ checkEmail: checkEmail, createUser: createUser, authenticate: authenticate, - resetPassword: resetPassword + forgotPassword: forgotPassword }); }()); diff --git a/resources/static/dialog/controllers/dialog_controller.js b/resources/static/dialog/controllers/dialog_controller.js index e19312e8dc4aa2f97f8d2b2a8dd298e885c5b9fe..82c2e7688e93586e577bce6e6e1105a7b76299b2 100644 --- a/resources/static/dialog/controllers/dialog_controller.js +++ b/resources/static/dialog/controllers/dialog_controller.js @@ -177,6 +177,15 @@ self.syncEmails(); }); + subscribe("forgot_password", function(msg, info) { + self.doForgotPassword(info.email); + }); + + subscribe("cancel_forgot_password", function(msg, info) { + user.cancelUserValidation(); + self.returnFromStageCancel(); + }); + subscribe("reset_password", function(msg, info) { self.doConfirmUser(info.email); }); diff --git a/resources/static/dialog/controllers/forgotpassword_controller.js b/resources/static/dialog/controllers/forgotpassword_controller.js new file mode 100644 index 0000000000000000000000000000000000000000..d1bdda2d765b1308f6974a93ef0a77b289cdae2b --- /dev/null +++ b/resources/static/dialog/controllers/forgotpassword_controller.js @@ -0,0 +1,82 @@ +/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */ +/*global BrowserID:true, PageController: 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 ***** */ +(function() { + "use strict"; + + var ANIMATION_TIME = 250, + bid = BrowserID, + helpers = bid.Helpers, + dialogHelpers = helpers.Dialog, + dom = bid.DOM, + lastEmail = ""; + + function cancelEvent(event) { + if (event) event.preventDefault(); + } + + function resetPassword(event) { + cancelEvent(event); + + var self=this; + dialogHelpers.resetPassword.call(self, self.email); + } + + function cancelResetPassword(event) { + cancelEvent(event); + + this.close("cancel_forgot_password"); + } + + PageController.extend("Forgotpassword", {}, { + start: function(options) { + var self=this; + self.email = options.email; + self.renderDialog("forgotpassword", { + email: options.email || "" + }); + + self.bind("#cancel_forgot_password", "click", cancelResetPassword); + + self._super(); + }, + + submit: resetPassword, + resetPassword: resetPassword, + cancelResetPassword: cancelResetPassword + }); + +}()); diff --git a/resources/static/dialog/controllers/required_email_controller.js b/resources/static/dialog/controllers/required_email_controller.js index c9acd8d5415aca9d13956a6fff52cdfa1ce547b7..eb49e0c32af1e9840c1fa4927d2fa57ec8c83ab4 100644 --- a/resources/static/dialog/controllers/required_email_controller.js +++ b/resources/static/dialog/controllers/required_email_controller.js @@ -102,7 +102,7 @@ event && event.preventDefault(); var self=this; - dialogHelpers.resetPassword.call(self, self.email); + self.close("forgot_password", { email: self.email }); } diff --git a/resources/static/dialog/dialog.js b/resources/static/dialog/dialog.js index c1f5ad54aa0b99fa6bcd88950f200802e71cc12a..c84c1a7457661b209bbad3210b7c0de09ac49cc4 100644 --- a/resources/static/dialog/dialog.js +++ b/resources/static/dialog/dialog.js @@ -73,6 +73,7 @@ steal .controllers('page', 'dialog', 'authenticate', + 'forgotpassword', 'checkregistration', 'pickemail', 'addemail', diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js index c1f1a61cb3c435e2716e4e2a005d08b6dca2c7e6..f0cec248daf32dd9fb1e26bfd21355dc8d5a1fa0 100644 --- a/resources/static/dialog/resources/helpers.js +++ b/resources/static/dialog/resources/helpers.js @@ -34,21 +34,6 @@ * 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"; diff --git a/resources/static/dialog/views/authenticate.ejs b/resources/static/dialog/views/authenticate.ejs index 41e3c4fc6cd580330912fbea270c6aa7deecde09..9923581c2ea9db4e7b7bc6624ba1262cdcfedffd 100644 --- a/resources/static/dialog/views/authenticate.ejs +++ b/resources/static/dialog/views/authenticate.ejs @@ -13,10 +13,6 @@ <div id="email_required" class="tooltip" for="email"> The email field is required. </div> - - <div id="could_not_add" class="tooltip" for="email"> - We just sent an email to that address! If you really want to send another, wait a minute or two and try again. - </div> </li> <li id="hint_section" class="start"> @@ -36,7 +32,6 @@ </div> <input id="password" class="sans" type="password" maxlength="80" tabindex="2" /> - <div id="password_required" class="tooltip" for="password"> The password field is required. </div> @@ -53,8 +48,5 @@ <button class="newuser" tabindex="3">verify email</button> <button class="returning" tabindex="3">select email</button> - - <button class="forgot" tabindex="3">Reset Password</button> - <button id="cancel_forgot_password" class="forgot" tabindex="4">Cancel</button> </div> </div> diff --git a/resources/static/dialog/views/forgotpassword.ejs b/resources/static/dialog/views/forgotpassword.ejs new file mode 100644 index 0000000000000000000000000000000000000000..00a9fc757f18bfc50bc609e302d5ce6e83648779 --- /dev/null +++ b/resources/static/dialog/views/forgotpassword.ejs @@ -0,0 +1,19 @@ + <strong>Sign in using</strong> + <div class="form_section"> + <ul class="inputs"> + <li> + <label for="email" class="serif">Email</label> + <input id="email" class="sans" type="email" value="<%= email %>" disabled /> + + <div id="could_not_add" class="tooltip" for="email"> + We just sent an email to that address! If you really want to send another, wait a minute or two and try again. + </div> + </li> + + </ul> + + <div class="submit cf"> + <button tabindex="1">Reset Password</button> + <button id="cancel_forgot_password" tabindex="2">Cancel</button> + </div> + </div> diff --git a/resources/static/dialog/views/requiredemail.ejs b/resources/static/dialog/views/requiredemail.ejs index 932a600a33def4056e98456223b69d41e1c5ee03..64627c9a940d284db3ac1acda0192308bf45a61c 100644 --- a/resources/static/dialog/views/requiredemail.ejs +++ b/resources/static/dialog/views/requiredemail.ejs @@ -1,9 +1,9 @@ - <!--strong>Required Email Sign In</strong--> + <strong>The site requested you sign in with:</strong> <div class="form_section"> <ul class="inputs"> - The site requested you sign in with - <div id="required_email"><%= email %></div> + <label for="email" class="serif">Email</label> + <input id="required_email" class="sans" type="email" value="<%= email %>" disabled /> <div id="could_not_add" class="tooltip" for="required_email"> We just sent an email to that address! If you really want to send another, wait a minute or two and try again. </div> diff --git a/resources/static/test/qunit/controllers/addemail_controller_unit_test.js b/resources/static/test/qunit/controllers/addemail_controller_unit_test.js index 0d560514ce559aaf63c7770c2737e9ddbcc61b6f..26fb2bc148a4494a0494a43fd27cf69af9953895 100644 --- a/resources/static/test/qunit/controllers/addemail_controller_unit_test.js +++ b/resources/static/test/qunit/controllers/addemail_controller_unit_test.js @@ -58,7 +58,6 @@ steal.then(function() { hub.unsubscribe(registration); } } - var controller; module("controllers/addemail_controller", { setup: function() { diff --git a/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js b/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js index a09787facf69e5dfa1ab91476919533c12e9e94e..9039e019ac95685ae58a344736014f2a4fb2cbf7 100644 --- a/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js +++ b/resources/static/test/qunit/controllers/authenticate_controller_unit_test.js @@ -34,7 +34,7 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -steal.then("/dialog/controllers/page_controller", "/dialog/controllers/authenticate_controller", function() { +steal.then(function() { "use strict"; var controller, @@ -46,7 +46,18 @@ steal.then("/dialog/controllers/page_controller", "/dialog/controllers/authentic emailRegistered = false, userCreated = true, hub = OpenAjax.hub, - subscriptions = []; + registrations = []; + + function register(message, cb) { + registrations.push(hub.subscribe(message, cb)); + } + + function unregisterAll() { + var registration; + while(registration = registrations.pop()) { + hub.unsubscribe(registration); + } + } function reset() { el = $("#controller_head"); @@ -56,12 +67,6 @@ steal.then("/dialog/controllers/page_controller", "/dialog/controllers/authentic emailRegistered = false; userCreated = true; - - - var subscription; - while(subscription = subscriptions.pop()) { - hub.unsubscribe(subscription); - } } module("controllers/authenticate_controller", { @@ -84,24 +89,23 @@ steal.then("/dialog/controllers/page_controller", "/dialog/controllers/authentic reset(); storage.clear(); network.setXHR($); + unregisterAll(); } }); test("setting email address prefills address field", function() { controller.destroy(); $("#email").val(""); - controller = el.authenticate({ email: "testuser@testuser.com" }).controller(); - equal($("#email").val(), "testuser@testuser.com", "email prefilled"); + controller = el.authenticate({ email: "registered@testuser.com" }).controller(); + equal($("#email").val(), "registered@testuser.com", "email prefilled"); }); function testUserUnregistered() { - var id = hub.subscribe("create_user", function() { + register("create_user", function() { ok(true, "email was valid, user not registered"); start(); }); - subscriptions.push(id); - controller.checkEmail(); stop(); } @@ -119,40 +123,115 @@ steal.then("/dialog/controllers/page_controller", "/dialog/controllers/authentic test("checkEmail with normal email, user registered", function() { $("#email").val("registered@testuser.com"); - var id = hub.subscribe("enter_password", function() { + register("enter_password", function() { ok(true, "email was valid, user registered"); start(); }); - subscriptions.push(id); controller.checkEmail(); stop(); }); function testAuthenticated() { - var id = hub.subscribe("authenticated", function() { + register("authenticated", function() { ok(true, "user authenticated as expected"); start(); }); - subscriptions.push(id); controller.authenticate(); stop(); } test("normal authentication is kosher", function() { - $("#email").val("testuser@testuser.com"); + $("#email").val("registered@testuser.com"); $("#password").val("password"); testAuthenticated(); }); test("leading/trailing whitespace on the username is stripped for authentication", function() { - $("#email").val(" testuser@testuser.com "); + $("#email").val(" registered@testuser.com "); $("#password").val("password"); testAuthenticated(); }); + test("forgotPassword triggers forgot_password message", function() { + $("#email").val("registered@testuser.com"); + + register("forgot_password", function(msg, info) { + equal(info.email, "registered@testuser.com", "forgot_password with correct email triggered"); + start(); + }); + + controller.forgotPassword(); + stop(); + }); + + test("createUser with valid email", function() { + $("#email").val("unregistered@testuser.com"); + register("user_staged", function(msg, info) { + equal(info.email, "unregistered@testuser.com", "user_staged with correct email triggered"); + start(); + }); + + controller.createUser(); + stop(); + }); + + test("createUser with invalid email", function() { + $("#email").val("unregistered"); + + var handlerCalled = false; + register("user_staged", function(msg, info) { + handlerCalled = true; + }); + + controller.createUser(); + setTimeout(function() { + equal(handlerCalled, false, "bad jiji, user_staged should not have been called with invalid email"); + start(); + }, 100); + + stop(); + }); + + test("createUser with valid email but throttling", function() { + $("#email").val("unregistered@testuser.com"); + + var handlerCalled = false; + register("user_staged", function(msg, info) { + handlerCalled = true; + }); + + xhr.useResult("throttle"); + controller.createUser(); + setTimeout(function() { + equal(handlerCalled, false, "bad jiji, user_staged should not have been called with throttling"); + equal(bid.Tooltip.shown, true, "tooltip is shown"); + start(); + }, 100); + + stop(); + }); + + test("createUser with valid email, XHR error", function() { + $("#email").val("unregistered@testuser.com"); + + var handlerCalled = false; + register("user_staged", function(msg, info) { + handlerCalled = true; + }); + + xhr.useResult("ajaxError"); + controller.createUser(); + setTimeout(function() { + equal(handlerCalled, false, "bad jiji, user_staged should not have been called with XHR error"); + start(); + }, 100); + + stop(); + }); + }); diff --git a/resources/static/test/qunit/controllers/forgotpassword_controller_unit_test.js b/resources/static/test/qunit/controllers/forgotpassword_controller_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..d42e9fa9301d6ed7bfadc801fe51ed313b08562e --- /dev/null +++ b/resources/static/test/qunit/controllers/forgotpassword_controller_unit_test.js @@ -0,0 +1,118 @@ +/*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 controller, + el = $("body"), + bid = BrowserID, + storage = bid.Storage, + network = bid.Network, + xhr = bid.Mocks.xhr, + hub = OpenAjax.hub, + registrations = []; + + function register(message, cb) { + registrations.push(hub.subscribe(message, cb)); + } + + function unregisterAll() { + var registration; + while(registration = registrations.pop()) { + hub.unsubscribe(registration); + } + } + + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + } + + module("controllers/forgotpassword_controller", { + setup: function() { + $("#email").val(""); + reset(); + storage.clear(); + network.setXHR(xhr); + xhr.useResult("valid"); + controller = el.forgotpassword({ email: "registered@testuser.com" }).controller(); + }, + + teardown: function() { + if (controller) { + try { + controller.destroy(); + controller = null; + } catch(e) { + // may already be destroyed from close inside of the controller. + } + } + reset(); + storage.clear(); + network.setXHR($); + unregisterAll(); + } + }); + + test("email address prefills address field", function() { + equal($("#email").val(), "registered@testuser.com", "email prefilled"); + }); + + test("resetPassword raises 'reset_password' with email address", function() { + register("reset_password", function(msg, info) { + equal(info.email, "registered@testuser.com", "reset_password raised with correct email address"); + start(); + }); + + controller.resetPassword(); + stop(); + }); + + test("cancelResetPassword raises 'cancel_forgot_password'", function() { + register("cancel_forgot_password", function(msg, info) { + ok(true, "cancel_forgot_password triggered"); + start(); + }); + + controller.cancelResetPassword(); + stop(); + + }); +}); + diff --git a/resources/static/test/qunit/controllers/required_email_controller_unit_test.js b/resources/static/test/qunit/controllers/required_email_controller_unit_test.js index cb9463994026fc308b5f2e3a69de921fdbceda8c..5d9d486e95de7ffdd35b7fe8ec45e3b8703d645c 100644 --- a/resources/static/test/qunit/controllers/required_email_controller_unit_test.js +++ b/resources/static/test/qunit/controllers/required_email_controller_unit_test.js @@ -316,7 +316,7 @@ steal.then(function() { test("verifyAddress of un-authenticated user, forgot password", function() { var email = "registered@testuser.com", authenticated = false, - message = "reset_password"; + message = "forgot_password"; xhr.setContextInfo({ authenticated: authenticated @@ -337,35 +337,6 @@ steal.then(function() { stop(); }); - test("verifyAddress of un-authenticated user, forgot password, mail throttled", function() { - var email = "registered@testuser.com", - authenticated = false, - message = "reset_password"; - - xhr.setContextInfo({ - authenticated: authenticated - }); - - controller = el.requiredemail({ - email: email, - authenticated: authenticated - }).controller(); - - - subscribe(message, function(item, info) { - ok(false, "throttling should have prevented close from happening"); - }); - - xhr.useResult("throttle"); - controller.forgotPassword(); - - setTimeout(function() { - ok(bid.Tooltip.shown, "tooltip is shown"); - start(); - }, 100); - stop(); - }); - test("cancel raises the cancel message", function() { var email = "registered@testuser.com", message = "cancel", diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js index 778176945340e5182c9fbddf47b52c6c892001e4..92c6acc6072d1ea4670d9fe0c5d3b0cf8c56cacb 100644 --- a/resources/static/test/qunit/qunit.js +++ b/resources/static/test/qunit/qunit.js @@ -28,6 +28,8 @@ steal.plugins( "/dialog/controllers/required_email_controller", "/dialog/controllers/pickemail_controller", "/dialog/controllers/addemail_controller", + "/dialog/controllers/authenticate_controller", + "/dialog/controllers/forgotpassword_controller", "/pages/page_helpers", @@ -61,6 +63,7 @@ steal.plugins( "controllers/dialog_controller_unit_test", "controllers/checkregistration_controller_unit_test", "controllers/authenticate_controller_unit_test", + "controllers/forgotpassword_controller_unit_test", "controllers/required_email_controller_unit_test" );