diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..a152ac51b7deb7e0122be3d367317dce0048d930 --- /dev/null +++ b/resources/static/dialog/controllers/actions.js @@ -0,0 +1,166 @@ +/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */ +/*global _: true, 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 ***** */ +BrowserID.Modules.Actions = (function() { + "use strict"; + + var bid = BrowserID, + sc, + serviceManager = bid.module, + user = bid.User, + errors = bid.Errors, + runningService, + onsuccess, + onerror; + + function startService(name, options) { + // Only one service outside of the main dialog allowed. + if(runningService) { + serviceManager.stop(runningService); + } + runningService = name; + return serviceManager.start(name, options); + } + + function startRegCheckService(email, verifier, message) { + this.confirmEmail = email; + + var controller = startService("check_registration", { + email: email, + verifier: verifier, + verificationMessage: message + }); + controller.startCheck(); + } + + var Module = bid.Modules.PageModule.extend({ + start: function(data) { + var self=this; + + data = data || {}; + + onsuccess = data.onsuccess; + onerror = data.onerror; + + sc.start.call(self, data); + + if(data.ready) { + data.ready(); + } + }, + + doOffline: function() { + this.renderError("offline", {}); + }, + + doCancel: function() { + onsuccess && onsuccess(null); + }, + + doConfirmUser: function(email) { + startRegCheckService.call(this, email, "waitForUserValidation", "user_confirmed"); + }, + + doPickEmail: function(info) { + startService("pick_email", info); + }, + + doAddEmail: function() { + startService("add_email", {}); + }, + + doAuthenticate: function(info) { + startService("authenticate", info); + }, + + doAuthenticateWithRequiredEmail: function(info) { + startService("required_email", info); + }, + + doForgotPassword: function(email) { + startService("forgot_password", { + email: email + }); + }, + + doConfirmEmail: function(email) { + startRegCheckService.call(this, email, "waitForEmailValidation", "email_confirmed"); + }, + + doEmailConfirmed: function() { + var self=this; + // yay! now we need to produce an assertion. + user.getAssertion(self.confirmEmail, function(assertion) { + self.publish("assertion_generated", { + assertion: assertion + }); + }, self.getErrorDialog(errors.getAssertion)); + }, + + doAssertionGenerated: function(assertion) { + // Clear onerror before the call to onsuccess - the code to onsuccess + // calls window.close, which would trigger the onerror callback if we + // tried this afterwards. + onerror = null; + onsuccess && onsuccess(assertion); + }, + + doNotMe: function() { + var self=this; + user.logoutUser(self.publish.bind(self, "logged_out"), self.getErrorDialog(errors.logoutUser)); + }, + + doSyncThenPickEmail: function() { + var self = this; + user.syncEmails(self.doPickEmail.bind(self), + self.getErrorDialog(errors.syncEmails)); + }, + + doCheckAuth: function() { + var self=this; + user.checkAuthenticationAndSync(function onSuccess() {}, function onComplete(authenticated) { + self.publish("authentication_checked", { + authenticated: authenticated + }); + }, self.getErrorDialog(errors.checkAuthentication)); + } + + }); + + sc = Module.sc; + + return Module; +}()); diff --git a/resources/static/dialog/controllers/addemail.js b/resources/static/dialog/controllers/addemail.js index 13564465bafec60324376ae704509600ce56ce35..c3656c7a980457ff376596db1ebe4f810a7c6734 100644 --- a/resources/static/dialog/controllers/addemail.js +++ b/resources/static/dialog/controllers/addemail.js @@ -77,7 +77,7 @@ BrowserID.Modules.AddEmail = (function() { AddEmail.sc.start.call(self, options); }, submit: addEmail - // START TESTING API + // BEGIN TESTING API , addEmail: addEmail, cancelAddEmail: cancelAddEmail diff --git a/resources/static/dialog/controllers/checkregistration.js b/resources/static/dialog/controllers/checkregistration.js index d497b18eee914a63502f890ee2174707c8002a87..cb6ca63c24ed7f749270fd9c8c723ee39e69ce65 100644 --- a/resources/static/dialog/controllers/checkregistration.js +++ b/resources/static/dialog/controllers/checkregistration.js @@ -57,23 +57,25 @@ BrowserID.Modules.CheckRegistration = (function() { CheckRegistration.sc.start.call(self, options); }, - startCheck: function() { + startCheck: function(oncomplete) { var self=this; user[self.verifier](self.email, function(status) { if (status === "complete") { user.syncEmails(function() { self.close(self.verificationMessage); + oncomplete && oncomplete(); }); } else if (status === "mustAuth") { self.close("auth", { email: self.email }); + oncomplete && oncomplete(); } - }, self.getErrorDialog(errors.registration)); + }, self.getErrorDialog(errors.registration, oncomplete)); }, cancel: function() { var self=this; - // XXX this should change to cancelEmailValidation for email, but this + // XXX this should change to cancelEmailValidation for email, but this // will work. user.cancelUserValidation(); self.close("cancel_state"); diff --git a/resources/static/dialog/controllers/code_check.js b/resources/static/dialog/controllers/code_check.js new file mode 100644 index 0000000000000000000000000000000000000000..692b3d61cae3b91982ad72980eb50ba7b6a3ee1d --- /dev/null +++ b/resources/static/dialog/controllers/code_check.js @@ -0,0 +1,109 @@ +/*jshint browser:true, jQuery: 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 ***** */ + + +BrowserID.Modules.CodeCheck = (function() { + "use strict"; + + var bid = BrowserID, + dom = bid.DOM, + sc, + expectedCodeVer; + + function getMostRecentCodeVersion(oncomplete) { + bid.Network.codeVersion(oncomplete, this.getErrorDialog(bid.Errors.checkScriptVersion, oncomplete)); + } + + function updateCodeIfNeeded(oncomplete, version) { + var mostRecent = version === expectedCodeVer; + + function ready() { + oncomplete && oncomplete(mostRecent); + } + + if(mostRecent) { + ready(); + } + else { + loadScript(version, ready); + } + } + + function loadScript(version, oncomplete) { + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = "https://browserid.org/dialog/production_v" + version + ".js"; + document.head.appendChild(script); + + oncomplete(); + } + + var Module = bid.Modules.PageModule.extend({ + start: function(data) { + var self=this; + + function complete(val) { + data.ready && data.ready(val); + } + + data = data || {}; + + if (data.code_ver) { + expectedCodeVer = data.code_ver; + } + else { + throw "init: code_ver must be defined"; + } + + getMostRecentCodeVersion.call(self, function(version) { + if(version) { + updateCodeIfNeeded.call(self, complete, version); + } + else { + complete(); + } + }); + sc.start.call(self, data); + } + }); + + sc = Module.sc; + + return Module; + +}()); + diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js index 380932446cd7e7f1ac3ca8394663f9cd54bfc1d0..92a97b5293f1cecec9f7a786ab558f6e17511067 100644 --- a/resources/static/dialog/controllers/dialog.js +++ b/resources/static/dialog/controllers/dialog.js @@ -1,5 +1,5 @@ /*jshint browser:true, jQuery: true, forin: true, laxbreak:true */ -/*global setupChannel:true, BrowserID: true */ +/*global BrowserID: true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -42,56 +42,41 @@ BrowserID.Modules.Dialog = (function() { var bid = BrowserID, user = bid.User, errors = bid.Errors, + channel = bid.Channel, dom = bid.DOM, - offline = false, - win = window, - serviceManager = bid.module, - runningService; - - function startService(name, options) { - // Only one service outside of the main dialog allowed. - if(runningService) { - serviceManager.stop(runningService); - } - runningService = name; - return serviceManager.start(name, options); - } - - function startRegCheckService(email, verifier, message) { - this.confirmEmail = email; - - var controller = startService("check_registration", { - email: email, - verifier: verifier, - verificationMessage: message - }); - controller.startCheck(); - } + win = window; function checkOnline() { if ('onLine' in navigator && !navigator.onLine) { - this.doOffline(); + this.publish("offline"); return false; } return true; } - function onWinUnload() { - // do this only if something else hasn't declared success - var self=this; - if (!self.success) { - bid.Storage.setStagedOnBehalfOf(""); - self.doCancel(); - } - window.teardownChannel(); + function startActions(onsuccess, onerror) { + var actions = BrowserID.Modules.Actions.create(); + actions.start({ + onsuccess: onsuccess, + onerror: onerror + }); + return actions; } - function setupChannel() { + function startStateMachine(controller) { + // start this directly because it should always be running. + var machine = BrowserID.StateMachine.create(); + machine.start({ + controller: controller + }); + } + + function startChannel() { var self = this; try { - win.setupChannel(self); + channel.open(self); } catch (e) { self.renderError("error", { action: errors.relaySetup @@ -104,161 +89,53 @@ BrowserID.Modules.Dialog = (function() { dom.setInner("#sitename", user.getHostname()); } - var Dialog = bid.Modules.PageModule.extend({ - init: function(options) { - offline = false; - - options = options || {}; - - if (options.window) { - win = options.window; - } - - var self=this; - - Dialog.sc.init.call(self, options); - - // keep track of where we are and what we do on success and error - self.onsuccess = null; - self.onerror = null; - - // start this directly because it should always be running. - var machine = BrowserID.StateMachine.create(); - machine.start({ - controller: this - }); - - setupChannel.call(self); - }, - - getVerifiedEmail: function(origin_url, onsuccess, onerror) { - return this.get(origin_url, {}, onsuccess, onerror); - }, - - get: function(origin_url, params, onsuccess, onerror) { - var self=this; - - if(checkOnline.call(self)) { - self.onsuccess = onsuccess; - self.onerror = onerror; - - params = params || {}; - - self.allowPersistent = !!params.allowPersistent; - self.requiredEmail = params.requiredEmail; - - setOrigin(origin_url); - - self.bind(win, "unload", onWinUnload); - - self.doCheckAuth(); - } - }, - - doOffline: function() { - this.renderError("offline", {}); - offline = true; - }, + function onWindowUnload() { + this.publish("window_unload"); + channel.close(); + } - doXHRError: function(info) { - if (!offline) { - this.renderError("error", $.extend({ - action: errors.xhrError - }, info)); - } - }, + var Dialog = bid.Modules.PageModule.extend({ + init: function(options) { + var self=this; - doConfirmUser: function(email) { - startRegCheckService.call(this, email, "waitForUserValidation", "user_confirmed"); - }, + options = options || {}; - doCancel: function() { - var self=this; - if (self.onsuccess) { - self.onsuccess(null); - } - }, + win = options.window || window; - doPickEmail: function() { - var self=this; - startService("pick_email", { - // XXX ideal is to get rid of this and have a User function - // that takes care of getting email addresses AND the last used email - // for this site. - origin: user.getHostname(), - allow_persistent: self.allowPersistent - }); - }, + Dialog.sc.init.call(self, options); - doAddEmail: function() { - startService("add_email", {}); - }, + startChannel.call(self); - doAuthenticate: function(info) { - startService("authenticate", info); - }, + options.ready && _.defer(options.ready); + }, - doAuthenticateWithRequiredEmail: function(info) { - startService("required_email", info); - }, + getVerifiedEmail: function(origin_url, success, error) { + return this.get(origin_url, {}, success, error); + }, - doForgotPassword: function(email) { - startService("forgot_password", { - email: email - }); - }, + get: function(origin_url, params, success, error) { + var self=this; - doConfirmEmail: function(email) { - startRegCheckService.call(this, email, "waitForEmailValidation", "email_confirmed"); - }, + setOrigin(origin_url); - doEmailConfirmed: function() { - var self=this; - // yay! now we need to produce an assertion. - user.getAssertion(self.confirmEmail, self.doAssertionGenerated.bind(self), - self.getErrorDialog(errors.getAssertion)); - }, + var actions = startActions.call(self, success, error); + startStateMachine.call(self, actions); - doAssertionGenerated: function(assertion) { - var self=this; - // Clear onerror before the call to onsuccess - the code to onsuccess - // calls window.close, which would trigger the onerror callback if we - // tried this afterwards. - self.onerror = null; - self.success = true; - self.onsuccess(assertion); - }, + if(checkOnline.call(self)) { + params = params || {}; - doNotMe: function() { - var self=this; - user.logoutUser(self.publish.bind(self, "auth"), self.getErrorDialog(errors.logoutUser)); - }, + params.hostname = user.getHostname(); - doSyncThenPickEmail: function() { - var self = this; - user.syncEmails(self.doPickEmail.bind(self), - self.getErrorDialog(errors.signIn)); - }, + self.bind(win, "unload", onWindowUnload); - doCheckAuth: function() { - var self=this; - user.checkAuthenticationAndSync(function onSuccess() {}, - function onComplete(authenticated) { - if (self.requiredEmail) { - self.publish("authenticate_with_required_email", { - email: self.requiredEmail, - authenticated: authenticated - }); - } - else if (authenticated) { - self.publish("pick_email"); - } else { - self.publish("auth"); - } - }, self.getErrorDialog(errors.checkAuthentication)); - }, + self.publish("start", params); + } + } - doWinUnload: onWinUnload + // BEGIN TESTING API + , + onWindowUnload: onWindowUnload + // END TESTING API }); diff --git a/resources/static/dialog/controllers/page.js b/resources/static/dialog/controllers/page.js index b26e4372750edca51dfad49df298b7f9d8b412cc..149c1a349756ab5647080456a339e1c086412121 100644 --- a/resources/static/dialog/controllers/page.js +++ b/resources/static/dialog/controllers/page.js @@ -124,18 +124,12 @@ BrowserID.Modules.PageModule = (function() { screens.wait.show(body, body_vars); }, - renderError: function(body, body_vars) { + renderError: function(body, body_vars, oncomplete) { screens.error.show(body, body_vars); - /** - * TODO XXX - Use the error-display for this. - */ - this.bind("#openMoreInfo", "click", function(event) { - event.preventDefault(); + bid.ErrorDisplay.start(); - $("#moreInfo").slideDown(); - $("#openMoreInfo").css({visibility: "hidden"}); - }); + $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME, oncomplete); }, validate: function() { @@ -160,14 +154,16 @@ BrowserID.Modules.PageModule = (function() { * Get a curried function to an error dialog. * @method getErrorDialog * @method {object} action - info to use for the error dialog. Should have + * @method {function} [onerror] - callback to call after the + * error has been displayed. * two fields, message, description. */ - getErrorDialog: function(action) { + getErrorDialog: function(action, onerror) { var self=this; return function(lowLevelInfo) { self.renderError("error", $.extend({ action: action - }, lowLevelInfo)); + }, lowLevelInfo), onerror); } } }); diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js index 48d695f329e9534357b30cd50a7bfe6376105cdc..765627699fea0b77b4881b22be116159c70b74fd 100644 --- a/resources/static/dialog/controllers/required_email.js +++ b/resources/static/dialog/controllers/required_email.js @@ -139,7 +139,7 @@ BrowserID.Modules.RequiredEmail = (function() { // verify ownership of the address. showTemplate(registered, registered); ready(); - }, self.getErrorDialog(errors.isEmailRegistered)); + }, self.getErrorDialog(errors.isEmailRegistered, ready)); } function showTemplate(requireSignin, showPassword) { diff --git a/resources/static/dialog/resources/channel.js b/resources/static/dialog/resources/channel.js index a2dad83d7475d8e0d7cd0a1f97ac01afb4e38ba1..0df7dc8c224402ff73abdef5096a7320de8bbc36 100644 --- a/resources/static/dialog/resources/channel.js +++ b/resources/static/dialog/resources/channel.js @@ -143,29 +143,27 @@ } - if(window.BrowserID) { - BrowserID.Channel = { - /** - * Used to intialize the channel, mostly for unit testing to override - * window and navigator. - * @method init - */ - init: init, - - /** - * Open the channel. - * @method open - * @param {object} options - contains: - * * options.getVerifiedEmail {function} - function to /get - */ - open: open, - - /** - * Close the channel - */ - close: close - }; - } + BrowserID.Channel = { + /** + * Used to intialize the channel, mostly for unit testing to override + * window and navigator. + * @method init + */ + init: init, + + /** + * Open the channel. + * @method open + * @param {object} options - contains: + * * options.getVerifiedEmail {function} - function to /get + */ + open: open, + + /** + * Close the channel + */ + close: close + }; /** * This is here as a legacy API for addons/etc that are depending on diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js index 3bd9962b618c4bc0221eea80b123796cd4e3b515..5c5d516672d8ad92e56d7f176732b8877466448c 100644 --- a/resources/static/dialog/resources/helpers.js +++ b/resources/static/dialog/resources/helpers.js @@ -102,7 +102,7 @@ tooltip.showTooltip("#could_not_add"); } if (callback) callback(staged); - }, self.getErrorDialog(errors.createUser)); + }, self.getErrorDialog(errors.createUser, callback)); } function resetPassword(email, callback) { diff --git a/resources/static/dialog/resources/state_machine.js b/resources/static/dialog/resources/state_machine.js index 93704afaa4b29de38ec268911e9eba483211ed6f..948a9e5bf0952eb6f2502c48af8361b8c5ecafa7 100644 --- a/resources/static/dialog/resources/state_machine.js +++ b/resources/static/dialog/resources/state_machine.js @@ -36,10 +36,10 @@ * ***** END LICENSE BLOCK ***** */ (function() { var bid = BrowserID, - user = bid.User, mediator = bid.Mediator, subscriptions = [], - stateStack = []; + stateStack = [], + controller; function subscribe(message, cb) { subscriptions.push(mediator.subscribe(message, cb)); @@ -52,8 +52,7 @@ } function pushState(funcName) { - var args = [].slice.call(arguments, 1), - controller = this.controller; + var args = [].slice.call(arguments, 1); // Remember the state and the information for the state in case we have to // go back to it. @@ -75,14 +74,12 @@ var gotoState = stateStack[stateStack.length - 1]; if(gotoState) { - var controller = this.controller; controller[gotoState.funcName].apply(controller, gotoState.args); } } function startStateMachine() { var self = this, - controller = self.controller, gotoState = pushState.bind(self), cancelState = popState.bind(self); @@ -90,8 +87,50 @@ gotoState("doOffline"); }); - subscribe("cancel_state", function(msg, info) { - cancelState(); + subscribe("start", function(msg, info) { + info = info || {}; + + self.hostname = info.hostname; + self.allowPersistent = !!info.allowPersistent; + self.requiredEmail = info.requiredEmail; + + gotoState("doCheckAuth"); + }); + + subscribe("cancel", function() { + gotoState("doCancel"); + }); + + subscribe("window_unload", function() { + if(!self.success) { + bid.Storage.setStagedOnBehalfOf(""); + gotoState("doCancel"); + } + }); + + subscribe("authentication_checked", function(msg, info) { + var authenticated = info.authenticated; + + if (self.requiredEmail) { + // XXX get this out of here and into the state machine! + gotoState("doAuthenticateWithRequiredEmail", { + email: self.requiredEmail, + authenticated: authenticated + }); + } + else if (authenticated) { + mediator.publish("pick_email"); + } else { + mediator.publish("authenticate"); + } + }); + + subscribe("authenticate", function(msg, info) { + info = info || {}; + + gotoState("doAuthenticate", { + email: info.email + }); }); subscribe("user_staged", function(msg, info) { @@ -107,7 +146,18 @@ }); subscribe("pick_email", function() { - gotoState("doPickEmail"); + gotoState("doPickEmail", { + origin: self.hostname, + allow_persistent: self.allowPersistent + }); + }); + + subscribe("notme", function() { + gotoState("doNotMe"); + }); + + subscribe("logged_out", function() { + mediator.publish("authenticate"); }); subscribe("authenticated", function(msg, info) { @@ -123,6 +173,7 @@ }); subscribe("assertion_generated", function(msg, info) { + self.success = true; if (info.assertion !== null) { gotoState("doAssertionGenerated", info.assertion); } @@ -143,25 +194,10 @@ gotoState("doEmailConfirmed"); }); - subscribe("notme", function() { - gotoState("doNotMe"); - }); - - subscribe("auth", function(msg, info) { - info = info || {}; - - gotoState("doAuthenticate", { - email: info.email - }); - }); - - subscribe("start", function() { - gotoState("doCheckAuth"); + subscribe("cancel_state", function(msg, info) { + cancelState(); }); - subscribe("cancel", function() { - gotoState("doCancel"); - }); } var StateMachine = BrowserID.Class({ @@ -171,7 +207,12 @@ start: function(options) { options = options || {}; - this.controller = options.controller; + + controller = options.controller; + if(!controller) { + throw "start: controller must be specified"; + } + startStateMachine.call(this); }, diff --git a/resources/static/dialog/start.js b/resources/static/dialog/start.js index e5b21f761c531869e9a363d17154b1cf91d824b2..a6787584ab511e2a3559278560f33ea12aae4733 100644 --- a/resources/static/dialog/start.js +++ b/resources/static/dialog/start.js @@ -2,16 +2,26 @@ var bid = BrowserID, moduleManager = bid.module, modules = bid.Modules; - - moduleManager.register("dialog", modules.Dialog); - moduleManager.register("add_email", modules.AddEmail); - moduleManager.register("authenticate", modules.Authenticate); - moduleManager.register("check_registration", modules.CheckRegistration); - moduleManager.register("forgot_password", modules.ForgotPassword); - moduleManager.register("pick_email", modules.PickEmail); - moduleManager.register("required_email", modules.RequiredEmail); - moduleManager.start("dialog"); + moduleManager.register("code_check", modules.CodeCheck); + moduleManager.start("code_check", { + code_ver: "ABC123", + ready: function(status) { + // if status is false, that means the javascript is out of date and we + // have to reload. + if(status) { + moduleManager.register("dialog", modules.Dialog); + moduleManager.register("add_email", modules.AddEmail); + moduleManager.register("authenticate", modules.Authenticate); + moduleManager.register("check_registration", modules.CheckRegistration); + moduleManager.register("forgot_password", modules.ForgotPassword); + moduleManager.register("pick_email", modules.PickEmail); + moduleManager.register("required_email", modules.RequiredEmail); + + moduleManager.start("dialog"); + } + } + }); }()); diff --git a/resources/static/shared/error-messages.js b/resources/static/shared/error-messages.js index 38c321996f8aec42f1a911f57e0110bc88453907..f1e6cbb681922c7fce6ce5f9978b0a3094b34fa8 100644 --- a/resources/static/shared/error-messages.js +++ b/resources/static/shared/error-messages.js @@ -53,10 +53,14 @@ BrowserID.Errors = (function(){ title: "Checking Authentication" }, + checkScriptVersion: { + title: "Checking Script Version" + }, + completeUserRegistration: { title: "Completing User Registration" }, - + createUser: { title: "Creating Account" }, diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js index d44f8fd0cbcf2ec6c2657ba3a2e01e2b040f238e..d15d8d345f41f2834949b6f78bbe1bd859fe35f8 100644 --- a/resources/static/shared/network.js +++ b/resources/static/shared/network.js @@ -42,6 +42,7 @@ BrowserID.Network = (function() { server_time, domain_key_creation_time, auth_status, + code_version, mediator = BrowserID.Mediator; function deferResponse(cb) { @@ -73,8 +74,8 @@ BrowserID.Network = (function() { xhr.ajax({ type: "GET", url: options.url, - // We defer the responses because otherwise jQuery eats any exceptions - // that are thrown in the response handlers and it becomes very difficult + // We defer the responses because otherwise jQuery eats any exceptions + // that are thrown in the response handlers and it becomes very difficult // to debug. success: deferResponse(options.success), error: deferResponse(xhrError(options.error, { @@ -91,7 +92,7 @@ BrowserID.Network = (function() { withContext(function() { var data = options.data || {}; - if(!data.csrf) { + if (!data.csrf) { data.csrf = csrf_token; } @@ -99,8 +100,8 @@ BrowserID.Network = (function() { type: "POST", url: options.url, data: data, - // We defer the responses because otherwise jQuery eats any exceptions - // that are thrown in the response handlers and it becomes very difficult + // We defer the responses because otherwise jQuery eats any exceptions + // that are thrown in the response handlers and it becomes very difficult // to debug. success: deferResponse(options.success), error: deferResponse(xhrError(options.error, { @@ -128,7 +129,10 @@ BrowserID.Network = (function() { }; domain_key_creation_time = result.domain_key_creation_time; auth_status = result.authenticated; - cb(); + // XXX remove the ABC123 + code_version = result.code_version || "ABC123"; + + _.defer(cb); }, error: deferResponse(xhrError(onFailure, { network: { @@ -187,7 +191,7 @@ BrowserID.Network = (function() { // session, let's set it to perhaps save a network request // (to fetch session context). auth_status = authenticated; - if(onSuccess) onSuccess(authenticated); + if (onSuccess) onSuccess(authenticated); } catch (e) { onFailure("unexpected server response: " + e); } @@ -200,7 +204,7 @@ BrowserID.Network = (function() { /** * Check whether a user is currently logged in. * @method checkAuth - * @param {function} [onSuccess] - Success callback, called with one + * @param {function} [onSuccess] - Success callback, called with one * boolean parameter, whether the user is authenticated. * @param {function} [onFailure] - called on XHR failure. */ @@ -257,8 +261,8 @@ BrowserID.Network = (function() { }, error: function(info) { // 403 is throttling. - if(info.network.status === 403) { - if (onSuccess) onSuccess(false); + if (info.network.status === 403) { + if (onSuccess) onSuccess(false); } else if (onFailure) onFailure(info); } @@ -325,7 +329,7 @@ BrowserID.Network = (function() { * Call with a token to prove an email address ownership. * @method completeEmailRegistration * @param {string} token - token proving email ownership. - * @param {function} [onSuccess] - Callback to call when complete. Called + * @param {function} [onSuccess] - Callback to call when complete. Called * with one boolean parameter that specifies the validity of the token. * @param {function} [onFailure] - Called on XHR failure. */ @@ -364,7 +368,7 @@ BrowserID.Network = (function() { * @param {string} password - new password. * @param {function} [onSuccess] - Callback to call when complete. * @param {function} [onFailure] - Called on XHR failure. - */ + */ resetPassword: function(password, onSuccess, onFailure) { // XXX fill this in. if (onSuccess) onSuccess(); @@ -375,10 +379,10 @@ BrowserID.Network = (function() { * @method changePassword * @param {string} oldpassword - old password. * @param {string} newpassword - new password. - * @param {function} [onSuccess] - Callback to call when complete. Will be + * @param {function} [onSuccess] - Callback to call when complete. Will be * called with true if successful, false otw. * @param {function} [onFailure] - Called on XHR failure. - */ + */ changePassword: function(oldPassword, newPassword, onSuccess, onFailure) { // XXX fill this in if (onSuccess) { @@ -421,8 +425,8 @@ BrowserID.Network = (function() { }, error: function(info) { // 403 is throttling. - if(info.network.status === 403) { - if (onSuccess) onSuccess(false); + if (info.network.status === 403) { + if (onSuccess) onSuccess(false); } else if (onFailure) onFailure(info); } @@ -450,8 +454,8 @@ BrowserID.Network = (function() { * Check whether the email is already registered. * @method emailRegistered * @param {string} email - Email address to check. - * @param {function} [onSuccess] - Called with one boolean parameter when - * complete. Parameter is true if `email` is already registered, false + * @param {function} [onSuccess] - Called with one boolean parameter when + * complete. Parameter is true if `email` is already registered, false * otw. * @param {function} [onFailure] - Called on XHR failure. */ @@ -459,7 +463,7 @@ BrowserID.Network = (function() { get({ url: "/wsapi/have_email?email=" + encodeURIComponent(email), success: function(data, textStatus, xhr) { - if(onSuccess) onSuccess(data.email_known); + if (onSuccess) onSuccess(data.email_known); }, error: onFailure }); @@ -528,9 +532,9 @@ BrowserID.Network = (function() { try { if (!server_time) throw "can't get server time!"; var offset = (new Date()).getTime() - server_time.local; - onSuccess(new Date(offset + server_time.remote)); + if (onSuccess) onSuccess(new Date(offset + server_time.remote)); } catch(e) { - onFailure(e.toString()); + if (onFailure) onFailure(e.toString()); } }, onFailure); }, @@ -548,9 +552,29 @@ BrowserID.Network = (function() { withContext(function() { try { if (!domain_key_creation_time) throw "can't get domain key creation time!"; - onSuccess(new Date(domain_key_creation_time)); + if (onSuccess) onSuccess(new Date(domain_key_creation_time)); + } catch(e) { + if (onFailure) onFailure(e.toString()); + } + }, onFailure); + }, + + /** + * Get the most recent code version + * + * Note: this function will perform a network request if + * during this session /wsapi/session_context has not + * been called. + * + * @method codeVersion + */ + codeVersion: function(onComplete, onFailure) { + withContext(function() { + try { + if (!code_version) throw "can't get code version!"; + if (onComplete) onComplete(code_version); } catch(e) { - onFailure(e.toString()); + if (onFailure) onFailure(e.toString()); } }, onFailure); } diff --git a/resources/static/test/index.html b/resources/static/test/index.html index 9be2191ec3936350a3ec0f7936b5478119b01e2e..30ea349272d58cc956a828dd7eeb91fb8cef2f3f 100644 --- a/resources/static/test/index.html +++ b/resources/static/test/index.html @@ -109,6 +109,8 @@ <script type="text/javascript" src="/dialog/resources/state_machine.js"></script> <script type="text/javascript" src="/dialog/controllers/page.js"></script> + <script type="text/javascript" src="/dialog/controllers/code_check.js"></script> + <script type="text/javascript" src="/dialog/controllers/actions.js"></script> <script type="text/javascript" src="/dialog/controllers/pickemail.js"></script> <script type="text/javascript" src="/dialog/controllers/addemail.js"></script> <script type="text/javascript" src="/dialog/controllers/dialog.js"></script> @@ -158,6 +160,8 @@ <script type="text/javascript" src="qunit/resources/channel_unit_test.js"></script> <script type="text/javascript" src="qunit/controllers/page_unit_test.js"></script> + <script type="text/javascript" src="qunit/controllers/code_check_unit_test.js"></script> + <script type="text/javascript" src="qunit/controllers/actions_unit_test.js"></script> <script type="text/javascript" src="qunit/controllers/pickemail_unit_test.js"></script> <script type="text/javascript" src="qunit/controllers/addemail_unit_test.js"></script> <script type="text/javascript" src="qunit/controllers/checkregistration_unit_test.js"></script> diff --git a/resources/static/test/qunit/controllers/actions_unit_test.js b/resources/static/test/qunit/controllers/actions_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..54bcb64e31a9f3aae6e8cddde1cddf4035766ac4 --- /dev/null +++ b/resources/static/test/qunit/controllers/actions_unit_test.js @@ -0,0 +1,88 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global 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 ***** */ +(function() { + "use strict"; + + var bid = BrowserID, + controller, + el; + + function reset() { + el = $("#controller_head"); + el.find("#formWrap .contents").html(""); + el.find("#wait .contents").html(""); + el.find("#error .contents").html(""); + } + + function createController(config) { + controller = BrowserID.Modules.Actions.create(); + controller.start(config); + } + + // XXX Make a test helper class for this. + function checkNetworkError() { + ok($("#error .contents").text().length, "contents have been written"); + ok($("#error #action").text().length, "action contents have been written"); + ok($("#error #network").text().length, "network contents have been written"); + } + + module("controllers/actions", { + setup: function() { + reset(); + }, + + teardown: function() { + if(controller) { + controller.destroy(); + } + reset(); + } + }); + + asyncTest("doOffline", function() { + createController({ + ready: function() { + controller.doOffline(); + ok($("#error .contents").text().length, "contents have been written"); + ok($("#error #offline").text().length, "offline error message has been written"); + start(); + } + }); + }); + +}()); + diff --git a/resources/static/test/qunit/controllers/authenticate_unit_test.js b/resources/static/test/qunit/controllers/authenticate_unit_test.js index 497145e20ccbb61953810869cc0b14e48483da2e..7a0d73ed193d26e88687d874f29e307b5a7326c0 100644 --- a/resources/static/test/qunit/controllers/authenticate_unit_test.js +++ b/resources/static/test/qunit/controllers/authenticate_unit_test.js @@ -202,12 +202,10 @@ }); xhr.useResult("ajaxError"); - controller.createUser() - - setTimeout(function() { + controller.createUser(function() { equal(handlerCalled, false, "bad jiji, user_staged should not have been called with XHR error"); start(); - }, 50); + }); }); }()); diff --git a/resources/static/test/qunit/controllers/checkregistration_unit_test.js b/resources/static/test/qunit/controllers/checkregistration_unit_test.js index 4ee5076b6db9974db90a3fc3c37536552112b83b..28db5714a279988204f97763b079f54ee44abe15 100644 --- a/resources/static/test/qunit/controllers/checkregistration_unit_test.js +++ b/resources/static/test/qunit/controllers/checkregistration_unit_test.js @@ -88,36 +88,34 @@ xhr.useResult("pending"); testVerifiedUserEvent("user_verified", "User verified"); + // use setTimeout to simulate a delay in the user opening the email. setTimeout(function() { xhr.useResult("complete"); - }, 1000); + }, 500); }); asyncTest("user validation with XHR error", function() { xhr.useResult("ajaxError"); createController("waitForUserValidation", "user_verified"); - register("user_verified", function() { - ok(false, "on XHR error, should not complete"); - }); - controller.startCheck(); - - setTimeout(function() { + controller.startCheck(function() { + register("user_verified", function() { + ok(false, "on XHR error, should not complete"); + }); ok(testHelpers.errorVisible(), "Error message is visible"); start(); - }, 500); + }); }); asyncTest("cancel raises cancel_state", function() { createController("waitForUserValidation", "user_verified"); - register("cancel_state", function() { - ok(true, "on cancel, cancel_state is triggered"); - start(); + controller.startCheck(function() { + register("cancel_state", function() { + ok(true, "on cancel, cancel_state is triggered"); + start(); + }); + controller.cancel(); }); - controller.startCheck(); - controller.cancel(); - - }); }()); diff --git a/resources/static/test/qunit/controllers/code_check_unit_test.js b/resources/static/test/qunit/controllers/code_check_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..aab8a19daa197ac192864c62f653c7e357a4fd15 --- /dev/null +++ b/resources/static/test/qunit/controllers/code_check_unit_test.js @@ -0,0 +1,123 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global 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 ***** */ +(function() { + "use strict"; + + var bid = BrowserID, + network = bid.Network, + xhr = bid.Mocks.xhr, + helpers = bid.TestHelpers, + controller; + + function createController(config) { + var config = $.extend({ + code_ver: "ABC123" + }, config); + + controller = BrowserID.Modules.CodeCheck.create(); + controller.start(config); + } + + module("controllers/code_check", { + setup: function() { + helpers.setup(); + }, + + teardown: function() { + helpers.teardown(); + + controller.destroy(); + } + }); + + asyncTest("create controller with most recent scripts", function() { + createController({ + ready: function(mostRecent) { + equal(mostRecent, true, "scripts are the most recent"); + start(); + } + }); + }); + + test("create controller without code_ver specified", function() { + raises(function() { + createController({ + code_ver: null + }); + }, "init: code_ver must be defined", "code version not specified throws exception"); + }); + + asyncTest("create controller with out of date scripts", function() { + var scriptCount = $("head > script").length; + + createController({ + code_ver: "ABC122", + ready: function(mostRecent) { + equal(mostRecent, false, "scripts are not the most recent"); + var scripts = $("head > script"); + var scriptAdded = scripts.length !== scriptCount; + + equal(scriptAdded, true, "a script was added to the dom to force reload"); + + if(scriptAdded) { + // Only remove the last script if the script was actually added. + scripts.last().remove(); + } + + start(); + } + }); + }); + + asyncTest("create controller with XHR error during script check", function() { + xhr.useResult("contextAjaxError"); + var scriptCount = $("head > script").length; + + createController({ + ready: function() { + helpers.checkNetworkError(); + var scripts = $("head > script"); + var scriptAdded = scripts.length !== scriptCount; + + equal(scriptAdded, false, "a script was not added on XHR error"); + start(); + } + }); + }); + +}()); + diff --git a/resources/static/test/qunit/controllers/dialog_unit_test.js b/resources/static/test/qunit/controllers/dialog_unit_test.js index e6c2ea1a6b280bce3106765788e92a8fa50dc7e9..cc2738de22306b7263d3d1d16d30b28d226bef28 100644 --- a/resources/static/test/qunit/controllers/dialog_unit_test.js +++ b/resources/static/test/qunit/controllers/dialog_unit_test.js @@ -39,14 +39,14 @@ var bid = BrowserID, channel = bid.Channel, + network = bid.Network, + xhr = bid.Mocks.xhr, controller, el, - channelError = false, winMock, navMock; function reset() { - channelError = false; } function WinMock() { @@ -54,10 +54,6 @@ } WinMock.prototype = { - setupChannel: function() { - if (channelError) throw "Channel error"; - }, - // Oh so beautiful. opener: { frames: { @@ -82,14 +78,14 @@ } function createController(config) { - var config = $.extend(config, { + var config = $.extend({ window: winMock - }); + }, config); controller = BrowserID.Modules.Dialog.create(config); } - module("controllers/dialog_controller", { + module("controllers/dialog", { setup: function() { winMock = new WinMock(); channel.init({ @@ -111,20 +107,24 @@ } }); - test("initialization with channel error", function() { - channelError = true; - createController(); - + function checkNetworkError() { ok($("#error .contents").text().length, "contents have been written"); - }); + ok($("#error #action").text().length, "action contents have been written"); + ok($("#error #network").text().length, "network contents have been written"); + } - test("doOffline", function() { - createController(); - controller.doOffline(); - ok($("#error .contents").text().length, "contents have been written"); - ok($("#error #offline").text().length, "offline error message has been written"); + asyncTest("initialization with channel error", function() { + // Set the hash so that the channel cannot be found. + winMock.location.hash = "#1235"; + createController({ + ready: function() { + ok($("#error .contents").text().length, "contents have been written"); + start(); + } + }); }); + /* test("doXHRError while online, no network info given", function() { createController(); controller.doXHRError(); @@ -141,9 +141,7 @@ url: "browserid.org/verify" } }); - ok($("#error .contents").text().length, "contents have been written"); - ok($("#error #action").text().length, "action contents have been written"); - ok($("#error #network").text().length, "network contents have been written"); + checkNetworkError(); }); test("doXHRError while offline does not update contents", function() { @@ -154,7 +152,7 @@ controller.doXHRError(); ok(!$("#error #action").text().length, "XHR error is not reported if the user is offline."); }); - +*/ /* test("doCheckAuth with registered requiredEmail, authenticated", function() { @@ -190,21 +188,23 @@ }); */ - test("doWinUnload", function() { + asyncTest("onWindowUnload", function() { createController({ - requiredEmail: "registered@testuser.com" - }); + requiredEmail: "registered@testuser.com", + ready: function() { + var error; - var error; - - try { - controller.doWinUnload(); - } - catch(e) { - error = e; - } + try { + controller.onWindowUnload(); + } + catch(e) { + error = e; + } - equal(typeof error, "undefined", "unexpected error thrown when unloading window (" + error + ")"); + equal(typeof error, "undefined", "unexpected error thrown when unloading window (" + error + ")"); + start(); + } + }); }); }()); diff --git a/resources/static/test/qunit/controllers/page_unit_test.js b/resources/static/test/qunit/controllers/page_unit_test.js index ea295702e90757d4e0640f899a39e3e3876d0fc4..012c55d93d3fdd5201f5d7b2310bb96638b2fe1b 100644 --- a/resources/static/test/qunit/controllers/page_unit_test.js +++ b/resources/static/test/qunit/controllers/page_unit_test.js @@ -123,7 +123,7 @@ ok(html.length, "with error template specified, error text is loaded"); }); - test("renderError renders an error message", function() { + asyncTest("renderError renders an error message", function() { createController({ waitTemplate: waitTemplate, waitVars: { @@ -135,14 +135,15 @@ controller.renderError("wait", { title: "error title", message: "error message" + }, function() { + var html = el.find("#error .contents").html(); + // XXX underpowered test, we don't actually check the contents. + ok(html.length, "with error template specified, error text is loaded"); + start(); }); - - var html = el.find("#error .contents").html(); - // XXX underpowered test, we don't actually check the contents. - ok(html.length, "with error template specified, error text is loaded"); }); - test("renderError allows us to open expanded error info", function() { + asyncTest("renderError allows us to open expanded error info", function() { createController(); controller.renderError("error", { @@ -150,26 +151,22 @@ title: "expanded action info", message: "expanded message" } - }); - - var html = el.find("#error .contents").html(); + }, function() { + var html = el.find("#error .contents").html(); - $("#moreInfo").hide(); + $("#moreInfo").hide(); - var evt = $.Event("click"); - $("#openMoreInfo").trigger( evt ); + $("#openMoreInfo").click(); - /* - setTimeout(function() { - equal($("#showMoreInfo").is(":visible"), false, "button is not visible after clicking expanded info"); - equal($("#moreInfo").is(":visible"), true, "expanded error info is visible after clicking expanded info"); - start(); - }, 500); - stop(); -*/ + setTimeout(function() { + equal($("#showMoreInfo").is(":visible"), false, "button is not visible after clicking expanded info"); + equal($("#moreInfo").is(":visible"), true, "expanded error info is visible after clicking expanded info"); + start(); + }, 1); + }); }); - test("getErrorDialog gets a function that can be used to render an error message", function() { + asyncTest("getErrorDialog gets a function that can be used to render an error message", function() { createController({ waitTemplate: waitTemplate, waitVars: { @@ -182,15 +179,16 @@ var func = controller.getErrorDialog({ title: "medium level info error title", message: "medium level info error message" + }, function() { + ok(true, "onerror callback called when returned function is called"); + var html = el.find("#error .contents").html(); + // XXX underpowered test, we don't actually check the contents. + ok(html.length, "when function is run, error text is loaded"); + start(); }); equal(typeof func, "function", "a function was returned from getErrorDialog"); func(); - - var html = el.find("#error .contents").html(); - // XXX underpowered test, we don't actually check the contents. - ok(html.length, "when function is run, error text is loaded"); - }); asyncTest("bind DOM Events", function() { diff --git a/resources/static/test/qunit/controllers/required_email_unit_test.js b/resources/static/test/qunit/controllers/required_email_unit_test.js index 43fa76ffbc96a70b7be482eeb6d0b55d04215f1b..4bd12a94b99ba8436c6d5d8df202a16836c4ce52 100644 --- a/resources/static/test/qunit/controllers/required_email_unit_test.js +++ b/resources/static/test/qunit/controllers/required_email_unit_test.js @@ -131,13 +131,10 @@ email: email, authenticated: false, ready: function() { + ok(testHelpers.errorVisible(), "Error message is visible"); + start(); } }); - - setTimeout(function() { - ok(testHelpers.errorVisible(), "Error message is visible"); - start(); - }, 500); }); asyncTest("user who is authenticated, email belongs to user", function() { diff --git a/resources/static/test/qunit/mocks/xhr.js b/resources/static/test/qunit/mocks/xhr.js index 7a5a5d865a3ff54b4a1bc23cddac665edf5a123a..a65e6e5bfd0a89c83b719b86e4dc151e70421679 100644 --- a/resources/static/test/qunit/mocks/xhr.js +++ b/resources/static/test/qunit/mocks/xhr.js @@ -40,7 +40,8 @@ BrowserID.Mocks.xhr = (function() { server_time: new Date().getTime(), domain_key_creation_time: (new Date().getTime() - (30 * 24 * 60 * 60 * 1000)), csrf_token: "csrf", - authenticated: false + authenticated: false, + code_version: "ABC123" }; // this cert is meaningless, but it has the right format diff --git a/resources/static/test/qunit/resources/state_machine_unit_test.js b/resources/static/test/qunit/resources/state_machine_unit_test.js index 1d796e8dc5aef1a5a376d4fe1f65ea06156c3770..65601ee80ca77124758e7071a77116c3be0c184e 100644 --- a/resources/static/test/qunit/resources/state_machine_unit_test.js +++ b/resources/static/test/qunit/resources/state_machine_unit_test.js @@ -36,7 +36,7 @@ * ***** END LICENSE BLOCK ***** */ (function() { "use strict"; - + var bid = BrowserID, mediator = bid.Mediator, machine, @@ -122,12 +122,19 @@ ok(machine, "Machine has been created"); }); + test("attempt to create a state machine without a controller", function() { + raises(function() { + var badmachine = bid.StateMachine.create(); + badmachine.start(); + }, "start: controller must be specified", "creating a state machine without a controller fails"); + }); + test("offline does offline", function() { mediator.publish("offline"); equal(controllerMock.offline, true, "controller is offline"); }); - + test("user_staged", function() { // XXX rename user_staged to confirm_user or something to that effect. mediator.publish("user_staged", { @@ -206,7 +213,7 @@ test("cancel_state", function() { mediator.publish("add_email"); mediator.publish("email_staged", { - email: "testuser@testuser.com" + email: "testuser@testuser.com" }); controllerMock.requestAddEmail = false; @@ -221,9 +228,9 @@ ok(controllerMock.notMe, "notMe has been called"); }); - test("auth", function() { - mediator.publish("auth", { - email: "testuser@testuser.com" + test("authenticate", function() { + mediator.publish("authenticate", { + email: "testuser@testuser.com" }); equal(controllerMock.email, "testuser@testuser.com", "authenticate with testuser@testuser.com"); diff --git a/resources/static/test/qunit/shared/network_unit_test.js b/resources/static/test/qunit/shared/network_unit_test.js index 84028ca9b06735e4674b6c25686bba34e8506e2b..d8d3fa46745c3111a52e938a9058458ebe17e288 100644 --- a/resources/static/test/qunit/shared/network_unit_test.js +++ b/resources/static/test/qunit/shared/network_unit_test.js @@ -640,15 +640,26 @@ }); - /* - wrappedAsyncTest("body offline message triggers offline message", function() { - mediator.subscribe("offline", function() { - ok(true, "offline event caught and application notified"); - start(); + wrappedAsyncTest("codeVersion", function() { + network.codeVersion(function onComplete(version) { + equal(version, "ABC123", "version returned properly"); + wrappedStart(); + }, function onFailure() { + ok(false, "unexpected failure"); + wrappedStart(); + }); + }); + + wrappedAsyncTest("codeVersion with XHR error", function() { + xhr.useResult("contextAjaxError"); + + network.codeVersion(function onComplete(version) { + ok(false, "XHR failure should never call complete"); + wrappedStart(); + }, function onFailure() { + ok(true, "XHR fialure should always return failure"); + wrappedStart(); }); - var evt = $.Event("offline"); - $("body").trigger(evt); }); - */ }()); diff --git a/resources/static/test/qunit/testHelpers/helpers.js b/resources/static/test/qunit/testHelpers/helpers.js index a55d948047a8ce038d30367496123d694c9a6cca..d7ca0c676660680ba8737649b1fa02ba827b4cfc 100644 --- a/resources/static/test/qunit/testHelpers/helpers.js +++ b/resources/static/test/qunit/testHelpers/helpers.js @@ -28,6 +28,12 @@ calls = {}; } + function checkNetworkError() { + ok($("#error .contents").text().length, "contents have been written"); + ok($("#error #action").text().length, "action contents have been written"); + ok($("#error #network").text().length, "network contents have been written"); + } + BrowserID.TestHelpers = { setup: function() { network.setXHR(xhr); @@ -58,6 +64,7 @@ register: register, errorVisible: function() { return screens.error.visible; - } + }, + checkNetworkError: checkNetworkError }; }()); diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs index 06753109d0c09b477df753f0f28713dd4682a8cd..4cacf37b83fa947c7350072755b18d457584ca5e 100644 --- a/resources/views/dialog_layout.ejs +++ b/resources/views/dialog_layout.ejs @@ -50,7 +50,6 @@ <script type="text/javascript" src="/dialog/production.js"></script> <% } else { %> - <script type="text/javascript" src="/dialog/resources/channel.js"></script> <script type="text/javascript" src="/lib/jquery-1.6.2.min.js"></script> <script type="text/javascript" src="/lib/jschannel.js"></script> <script type="text/javascript" src="/lib/underscore-min.js"></script> @@ -76,10 +75,15 @@ <script type="text/javascript" src="/shared/browser-support.js"></script> <script type="text/javascript" src="/shared/wait-messages.js"></script> <script type="text/javascript" src="/shared/helpers.js"></script> + <script type="text/javascript" src="/dialog/resources/internal_api.js"></script> + <script type="text/javascript" src="/dialog/resources/channel.js"></script> <script type="text/javascript" src="/dialog/resources/helpers.js"></script> <script type="text/javascript" src="/dialog/resources/state_machine.js"></script> + <script type="text/javascript" src="/dialog/controllers/page.js"></script> + <script type="text/javascript" src="/dialog/controllers/code_check.js"></script> + <script type="text/javascript" src="/dialog/controllers/actions.js"></script> <script type="text/javascript" src="/dialog/controllers/dialog.js"></script> <script type="text/javascript" src="/dialog/controllers/authenticate.js"></script> <script type="text/javascript" src="/dialog/controllers/forgotpassword.js"></script> diff --git a/scripts/compress.sh b/scripts/compress.sh index 65176a3332a7dff2264c6d66f956fa5b0d0db3ff..9f0eefc285b70a58dfa44f2cb90a59fc7f9755f1 100755 --- a/scripts/compress.sh +++ b/scripts/compress.sh @@ -42,7 +42,7 @@ cp shared/templates.js shared/templates.js.orig cp dialog/views/templates.js shared/templates.js # produce the dialog js -cat dialog/resources/channel.js lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js shared/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgotpassword.js dialog/controllers/checkregistration.js dialog/controllers/pickemail.js dialog/controllers/addemail.js dialog/controllers/required_email.js dialog/start.js > dialog/production.js +cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js shared/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/channel.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgotpassword.js dialog/controllers/checkregistration.js dialog/controllers/pickemail.js dialog/controllers/addemail.js dialog/controllers/required_email.js dialog/start.js > dialog/production.js # produce the non interactive frame js cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js shared/javascript-extensions.js shared/browserid.js shared/storage.js shared/network.js shared/user.js communication_iframe/start.js > communication_iframe/production.js