From f9ecd8849aac62b43c0a616b146675427b5d8c47 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Thu, 23 Oct 2014 17:17:00 -0400 Subject: [PATCH] Fix logistration Jasmine tests Includes several bug fixes caught during test writing, general clean-up, and adds js-url, a lightweight URL parser --- .../djangoapps/user_api/tests/test_views.py | 4 - common/static/js/vendor/url.min.js | 1 + lms/static/js/spec/main.js | 95 ++++-- .../js/spec/student_account/access_spec.js | 193 ++++++++---- .../{account.js => account_spec.js} | 0 .../enrollment_interface_spec.js | 20 +- .../js/spec/student_account/login_spec.js | 208 ++++++++++-- .../student_account/password_reset_spec.js | 148 +++++---- .../js/spec/student_account/register_spec.js | 297 ++++++++++++++++-- .../{profile.js => profile_spec.js} | 0 lms/static/js/student_account/account.js | 2 +- .../student_account/enrollment_interface.js | 51 +-- .../js/student_account/models/LoginModel.js | 29 +- .../models/PasswordResetModel.js | 8 +- .../student_account/models/RegisterModel.js | 28 +- .../js/student_account/views/AccessView.js | 27 +- .../js/student_account/views/FormView.js | 4 +- .../js/student_account/views/LoginView.js | 28 ++ .../views/PasswordResetView.js | 13 +- .../js/student_account/views/RegisterView.js | 29 +- lms/static/js/student_profile/profile.js | 2 +- lms/static/js_test.yml | 2 + .../student_account/login_and_register.html | 7 +- 23 files changed, 880 insertions(+), 316 deletions(-) create mode 100644 common/static/js/vendor/url.min.js rename lms/static/js/spec/student_account/{account.js => account_spec.js} (100%) rename lms/static/js/spec/student_profile/{profile.js => profile_spec.js} (100%) diff --git a/common/djangoapps/user_api/tests/test_views.py b/common/djangoapps/user_api/tests/test_views.py index 8c84d3dedd..01ed1be80c 100644 --- a/common/djangoapps/user_api/tests/test_views.py +++ b/common/djangoapps/user_api/tests/test_views.py @@ -728,10 +728,6 @@ class LoginSessionViewTest(ApiTestCase): class PasswordResetViewTest(ApiTestCase): """Tests of the user API's password reset endpoint. """ - USERNAME = "bob" - EMAIL = "bob@example.com" - PASSWORD = "password" - def setUp(self): super(PasswordResetViewTest, self).setUp() self.url = reverse("user_api_password_reset") diff --git a/common/static/js/vendor/url.min.js b/common/static/js/vendor/url.min.js new file mode 100644 index 0000000000..796e0f6791 --- /dev/null +++ b/common/static/js/vendor/url.min.js @@ -0,0 +1 @@ +/*! url - v1.8.4 - 2013-08-14 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||"80",e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port||"80";if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}}); \ No newline at end of file diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index b246460f92..722692ad95 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -23,6 +23,7 @@ 'jquery.inputnumber': 'xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill', 'jquery.immediateDescendents': 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents', 'jquery.simulate': 'xmodule_js/common_static/js/vendor/jquery.simulate', + 'jquery.url': 'xmodule_js/common_static/js/vendor/url.min', 'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair', 'date': 'xmodule_js/common_static/js/vendor/date', 'underscore': 'xmodule_js/common_static/js/vendor/underscore-min', @@ -43,7 +44,6 @@ 'jasmine.async': 'xmodule_js/common_static/js/vendor/jasmine.async', 'draggabilly': 'xmodule_js/common_static/js/vendor/draggabilly.pkgd', 'domReady': 'xmodule_js/common_static/js/vendor/domReady', - 'URI': 'xmodule_js/common_static/js/vendor/URI.min', 'mathjax': '//edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured', 'youtube': '//www.youtube.com/player_api?noext', 'tender': '//edxedge.tenderapp.com/tender_widget', @@ -65,7 +65,17 @@ 'js/views/cohort_editor': 'js/views/cohort_editor', 'js/views/cohorts': 'js/views/cohorts', 'js/views/notification': 'js/views/notification', - 'js/models/notification': 'js/models/notification' + 'js/models/notification': 'js/models/notification', + 'js/student_account/account': 'js/student_account/account', + 'js/student_account/views/FormView': 'js/student_account/views/FormView', + 'js/student_account/models/LoginModel': 'js/student_account/models/LoginModel', + 'js/student_account/views/LoginView': 'js/student_account/views/LoginView', + 'js/student_account/models/PasswordResetModel': 'js/student_account/models/PasswordResetModel', + 'js/student_account/views/PasswordResetView': 'js/student_account/views/PasswordResetView', + 'js/student_account/models/RegisterModel': 'js/student_account/models/RegisterModel', + 'js/student_account/views/RegisterView': 'js/student_account/views/RegisterView', + 'js/student_account/views/AccessView': 'js/student_account/views/AccessView', + 'js/student_profile/profile': 'js/student_profile/profile' }, shim: { 'gettext': { @@ -133,11 +143,31 @@ deps: ['jquery', 'tinymce'], exports: 'jQuery.fn.tinymce' }, + 'jquery.url': { + deps: ['jquery'], + exports: 'jQuery.fn.url' + }, 'datepair': { deps: ['jquery.ui', 'jquery.timepicker'] }, 'underscore': { - exports: '_' + deps: ['underscore.string'], + exports: '_', + init: function(UnderscoreString) { + /* Mix non-conflicting functions from underscore.string + * (all but include, contains, and reverse) into the + * Underscore namespace. This allows the login, register, + * and password reset templates to render independent of the + * access view. + */ + _.mixin(UnderscoreString.exports()); + + /* Since the access view is not using RequireJS, we also + * expose underscore.string at _.str, so that the access + * view can perform the mixin on its own. + */ + _.str = UnderscoreString; + } }, 'backbone': { deps: ['underscore', 'jquery'], @@ -216,7 +246,6 @@ exports: 'js/student_account/account', deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie'] }, - 'js/student_profile/profile': { exports: 'js/student_profile/profile', deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie'] @@ -261,60 +290,76 @@ deps: ['backbone', 'jquery', 'underscore'] }, 'js/student_account/enrollment_interface': { - exports: 'js/student_account/enrollment_interface', - deps: ['jquery', 'underscore', 'gettext'] + exports: 'edx.student.account.EnrollmentInterface', + deps: ['jquery', 'jquery.cookie', 'underscore', 'gettext'] }, // Student account registration/login // Loaded explicitly until these are converted to RequireJS 'js/student_account/views/FormView': { - exports: 'js/student_account/views/FormView', + exports: 'edx.student.account.FormView', deps: ['jquery', 'underscore', 'backbone', 'gettext'] }, 'js/student_account/models/LoginModel': { - exports: 'js/student_account/models/LoginModel', - deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie'] + exports: 'edx.student.account.LoginModel', + deps: ['jquery', 'jquery.cookie', 'backbone'] }, 'js/student_account/views/LoginView': { - exports: 'js/student_account/views/LoginView', + exports: 'edx.student.account.LoginView', deps: [ + 'jquery', + 'jquery.url', + 'underscore', + 'gettext', 'js/student_account/models/LoginModel', - 'js/student_account/views/FormView', - 'underscore.string' + 'js/student_account/views/FormView' ] }, 'js/student_account/models/PasswordResetModel': { - exports: 'js/student_account/models/PasswordResetModel', - deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie'] + exports: 'edx.student.account.PasswordResetModel', + deps: ['jquery', 'jquery.cookie', 'backbone'] }, 'js/student_account/views/PasswordResetView': { - exports: 'js/student_account/views/PasswordResetView', + exports: 'edx.student.account.PasswordResetView', deps: [ + 'jquery', + 'underscore', + 'gettext', 'js/student_account/models/PasswordResetModel', 'js/student_account/views/FormView' ] }, 'js/student_account/models/RegisterModel': { - exports: 'js/student_account/models/RegisterModel', - deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie'] + exports: 'edx.student.account.RegisterModel', + deps: ['jquery', 'jquery.cookie', 'backbone'] }, 'js/student_account/views/RegisterView': { - exports: 'js/student_account/views/RegisterView', + exports: 'edx.student.account.RegisterView', deps: [ + 'jquery', + 'jquery.url', + 'underscore', + 'gettext', 'js/student_account/models/RegisterModel', - 'js/student_account/views/FormView', - 'underscore.string' + 'js/student_account/views/FormView' ] }, 'js/student_account/views/AccessView': { - exports: 'js/student_account/views/AccessView', + exports: 'edx.student.account.AccessView', deps: [ + 'jquery', + 'underscore', + 'backbone', + 'gettext', 'js/student_account/views/LoginView', 'js/student_account/views/PasswordResetView', 'js/student_account/views/RegisterView', - 'underscore.string' + 'js/student_account/models/LoginModel', + 'js/student_account/models/PasswordResetModel', + 'js/student_account/models/RegisterModel', + 'js/student_account/views/FormView' ] } - }, + } }); // TODO: why do these need 'lms/include' at the front but the CMS equivalent logic doesn't? @@ -325,13 +370,13 @@ 'lms/include/js/spec/staff_debug_actions_spec.js', 'lms/include/js/spec/views/notification_spec.js', 'lms/include/js/spec/dashboard/donation.js', - 'lms/include/js/spec/student_account/account.js', + 'lms/include/js/spec/student_account/account_spec.js', 'lms/include/js/spec/student_account/access_spec.js', 'lms/include/js/spec/student_account/login_spec.js', 'lms/include/js/spec/student_account/register_spec.js', 'lms/include/js/spec/student_account/password_reset_spec.js', 'lms/include/js/spec/student_account/enrollment_interface_spec.js', - 'lms/include/js/spec/student_profile/profile.js', + 'lms/include/js/spec/student_profile/profile_spec.js' ]); }).call(this, requirejs, define); diff --git a/lms/static/js/spec/student_account/access_spec.js b/lms/static/js/spec/student_account/access_spec.js index 9915060819..10e0c6ff5e 100644 --- a/lms/static/js/spec/student_account/access_spec.js +++ b/lms/static/js/spec/student_account/access_spec.js @@ -1,96 +1,161 @@ -define(['js/common_helpers/template_helpers', 'js/student_account/views/AccessView'], - function(TemplateHelpers, AccessView) { +define([ + 'jquery', + 'js/common_helpers/template_helpers', + 'js/common_helpers/ajax_helpers', + 'js/student_account/views/AccessView', + 'js/student_account/views/FormView' +], function($, TemplateHelpers, AjaxHelpers, AccessView) { describe('edx.student.account.AccessView', function() { 'use strict'; - var view = null, - ajaxSuccess = true; - - var assertForms = function(visible, hidden) { - expect($(visible)).not.toHaveClass('hidden'); - expect($(hidden)).toHaveClass('hidden'); - expect($('#password-reset-wrapper')).toBeEmpty(); - }; - - beforeEach(function() { - setFixtures("
"); - TemplateHelpers.installTemplate('templates/student_account/access'); - TemplateHelpers.installTemplate('templates/student_account/login'); - TemplateHelpers.installTemplate('templates/student_account/register'); - TemplateHelpers.installTemplate('templates/student_account/password_reset'); - TemplateHelpers.installTemplate('templates/student_account/form_field'); - - // Used to populate forms - var form_description = { - "method": "post", - "submit_url": "/submit", - "fields": [ + var requests = null, + view = null, + AJAX_INFO = { + register: { + url: '/user_api/v1/account/registration/', + requestIndex: 1 + }, + login: { + url: '/user_api/v1/account/login_session/', + requestIndex: 0 + }, + password_reset: { + url: '/user_api/v1/account/password_reset/', + requestIndex: 1 + } + }, + FORM_DESCRIPTION = { + method: 'post', + submit_url: '/submit', + fields: [ { - "name": "email", - "label": "Email", - "default": "", - "type": "text", - "required": true, - "placeholder": "xsy@edx.org", - "instructions": "Enter your email here.", - "restrictions": {}, + name: 'email', + label: 'Email', + defaultValue: '', + type: 'text', + required: true, + placeholder: 'xsy@edx.org', + instructions: 'Enter your email here.', + restrictions: {}, }, { - "name": "username", - "label": "Username", - "default": "", - "type": "text", - "required": true, - "placeholder": "Xsy", - "instructions": "Enter your username here.", - "restrictions": { - "max_length": 200 + name: 'username', + label: 'Username', + defaultValue: '', + type: 'text', + required: true, + placeholder: 'Xsy', + instructions: 'Enter your username here.', + restrictions: { + max_length: 200 } } ] }; - // Stub AJAX calls and force them to return a form description - spyOn($, 'ajax').andCallFake(function() { - return $.Deferred(function(defer) { - if (ajaxSuccess) { - defer.resolveWith(this, [form_description]); - } else { - defer.reject(); - } - }).promise(); - }); + var ajaxAssertAndRespond = function(url, requestIndex) { + // Verify that the client contacts the server as expected + AjaxHelpers.expectJsonRequest(requests, 'GET', url, null, requestIndex); - view = new edx.student.account.AccessView({ - mode: 'login', + /* Simulate a response from the server containing + /* a dummy form description + */ + AjaxHelpers.respondWithJson(requests, FORM_DESCRIPTION); + }; + + var ajaxSpyAndInitialize = function(that, mode) { + // Spy on AJAX requests + requests = AjaxHelpers.requests(that); + + // Initialize the access view + view = new AccessView({ + mode: mode, thirdPartyAuth: { currentProvider: null, providers: [] - } + }, + platformName: 'edX' }); + + ajaxAssertAndRespond(AJAX_INFO[mode].url); + }; + + var assertForms = function(visibleType, hiddenType) { + expect($(visibleType)).not.toHaveClass('hidden'); + expect($(hiddenType)).toHaveClass('hidden'); + expect($('#password-reset-wrapper')).toBeEmpty(); + }; + + var selectForm = function(type) { + // Create a fake change event to control form toggling + var changeEvent = $.Event('change'); + changeEvent.currentTarget = $('#' + type + '-option'); + + // Load form corresponding to the change event + view.toggleForm(changeEvent); + + ajaxAssertAndRespond(AJAX_INFO[type].url, AJAX_INFO[type].requestIndex); + }; + + beforeEach(function() { + setFixtures('
'); + TemplateHelpers.installTemplate('templates/student_account/access'); + TemplateHelpers.installTemplate('templates/student_account/login'); + TemplateHelpers.installTemplate('templates/student_account/register'); + TemplateHelpers.installTemplate('templates/student_account/password_reset'); + TemplateHelpers.installTemplate('templates/student_account/form_field'); }); - it("initially displays the correct form", function() { - assertForms($('#login-form'), $('#register-form')); + it('can initially display the login form', function() { + ajaxSpyAndInitialize(this, 'login'); + + /* Verify that the login form is expanded, and that the + /* registration form is collapsed. + */ + assertForms('#login-form', '#register-form'); }); - it("toggles between the login and registration forms", function() { - var registerChangeEvent = $.Event('change', {currentTarget: $('#register-option')}), - loginChangeEvent = $.Event('change', {currentTarget: $('#login-option')}); + it('can initially display the registration form', function() { + ajaxSpyAndInitialize(this, 'register'); + + /* Verify that the registration form is expanded, and that the + /* login form is collapsed. + */ + assertForms('#register-form', '#login-form'); + }); + + it('toggles between the login and registration forms', function() { + ajaxSpyAndInitialize(this, 'login'); // Simulate selection of the registration form - view.toggleForm(registerChangeEvent) - assertForms($('#register-form'), $('#login-form')); + selectForm('register'); + assertForms('#register-form', '#login-form'); // Simulate selection of the login form - view.toggleForm(loginChangeEvent) - assertForms($('#login-form'), $('#register-form')); + selectForm('login'); + assertForms('#login-form', '#register-form'); }); - it("displays the reset password form", function() { + it('displays the reset password form', function() { + ajaxSpyAndInitialize(this, 'login'); + + // Simulate a click on the reset password link view.resetPassword(); + + ajaxAssertAndRespond( + AJAX_INFO['password_reset'].url, + AJAX_INFO['password_reset'].requestIndex + ); + + // Verify that the password reset wrapper is populated expect($('#password-reset-wrapper')).not.toBeEmpty(); }); + + it('displays an error if a form definition could not be loaded', function() { + /* TODO: Not yet implemeted in the access view; currently, it only + * logs to the console. + */ + }); }); } ); diff --git a/lms/static/js/spec/student_account/account.js b/lms/static/js/spec/student_account/account_spec.js similarity index 100% rename from lms/static/js/spec/student_account/account.js rename to lms/static/js/spec/student_account/account_spec.js diff --git a/lms/static/js/spec/student_account/enrollment_interface_spec.js b/lms/static/js/spec/student_account/enrollment_interface_spec.js index eb0d98d022..204861e24c 100644 --- a/lms/static/js/spec/student_account/enrollment_interface_spec.js +++ b/lms/static/js/spec/student_account/enrollment_interface_spec.js @@ -1,21 +1,13 @@ -define(['js/common_helpers/template_helpers', 'js/student_account/enrollment_interface'], - function(TemplateHelpers, EnrollmentInterface) { +define(['js/student_account/enrollment_interface'], + function(EnrollmentInterface) { describe("edx.student.account.EnrollmentInterface", function() { 'use strict'; - it("find course modes using modeInArray ", function() { - var course_modes = [ - { - slug: 'honor' - }, - { - slug: 'professional' - } - ], - - expect(EnrollmentInterface.modeInArray('professional')).toBe(true); - expect(EnrollmentInterface.modeInArray('audit')).toBe(false); + it('checks if a given course mode slug exists in an array of mode objects', function() { + var courseModes = [ { slug: 'honor' }, { slug: 'professional' } ] + expect( EnrollmentInterface.modeInArray( courseModes, 'professional' ) ).toBe(true); + expect( EnrollmentInterface.modeInArray( courseModes, 'audit' ) ).toBe(false); }); }); } diff --git a/lms/static/js/spec/student_account/login_spec.js b/lms/static/js/spec/student_account/login_spec.js index 9f8ff48f2b..ec75902074 100644 --- a/lms/static/js/spec/student_account/login_spec.js +++ b/lms/static/js/spec/student_account/login_spec.js @@ -1,47 +1,203 @@ -define(['js/common_helpers/template_helpers', 'js/student_account/views/LoginView'], - function(TemplateHelpers) { - describe("edx.student.account.LoginView", function() { +define([ + 'jquery', + 'underscore', + 'js/common_helpers/template_helpers', + 'js/common_helpers/ajax_helpers', + 'js/student_account/models/LoginModel', + 'js/student_account/views/LoginView' +], function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView) { + describe('edx.student.account.LoginView', function() { 'use strict'; + var model = null, + view = null, + requests = null, + PLATFORM_NAME = 'edX', + USER_DATA = { + email: 'xsy@edx.org', + password: 'xsyisawesome', + remember: true + }, + THIRD_PARTY_AUTH = { + currentProvider: null, + providers: [ + { + name: 'Google', + iconClass: 'icon-google-plus', + loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', + registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' + }, + { + name: 'Facebook', + iconClass: 'icon-facebook', + loginUrl: '/auth/login/facebook/?auth_entry=account_login', + registerUrl: '/auth/login/facebook/?auth_entry=account_register' + } + ] + }, + FORM_DESCRIPTION = { + method: 'post', + submit_url: '/user_api/v1/account/login_session/', + fields: [ + { + name: 'email', + label: 'Email', + defaultValue: '', + type: 'email', + required: true, + placeholder: 'place@holder.org', + instructions: 'Enter your email.', + restrictions: {} + }, + { + name: 'password', + label: 'Password', + defaultValue: '', + type: 'password', + required: true, + instructions: 'Enter your password.', + restrictions: {} + }, + { + name: 'remember', + label: 'Remember me', + defaultValue: '', + type: 'checkbox', + required: true, + instructions: "Agree to the terms of service.", + restrictions: {} + } + ] + }; + + var createLoginView = function(test) { + // Initialize the login model + model = new LoginModel({ url: FORM_DESCRIPTION.submit_url }); + + // Initialize the login view + view = new LoginView({ + fields: FORM_DESCRIPTION.fields, + model: model, + thirdPartyAuth: THIRD_PARTY_AUTH, + platformName: PLATFORM_NAME + }); + + // Spy on AJAX requests + requests = AjaxHelpers.requests(test); + + // Mock out redirection logic + spyOn(view, 'redirect').andCallFake(function() { + return true; + }); + }; + + var submitForm = function(validationSuccess) { + // Simulate manual entry of login form data + $('#login-email').val(USER_DATA.email); + $('#login-password').val(USER_DATA.password); + + // Check the "Remember me" checkbox + $('#login-remember').prop('checked', USER_DATA.remember); + + // Create a fake click event + var clickEvent = $.Event('click'); + + // If validationSuccess isn't passed, we avoid + // spying on `view.validate` twice + if ( !_.isUndefined(validationSuccess) ) { + // Force validation to return as expected + spyOn(view, 'validate').andReturn({ + isValid: validationSuccess, + message: 'Submission was validated.' + }); + } + + // Submit the email address + view.submitForm(clickEvent); + }; + beforeEach(function() { - setFixtures("
"); - TemplateHelpers.installTemplate("templates/student_account/login"); + setFixtures('
'); + TemplateHelpers.installTemplate('templates/student_account/login'); + TemplateHelpers.installTemplate('templates/student_account/form_field'); }); - it("logs the user in", function() { - // TODO + it('logs the user in', function() { + createLoginView(this); + + // Submit the form, with successful validation + submitForm(true); + + // Verify that the client contacts the server with the expected data + AjaxHelpers.expectRequest( + requests, 'POST', FORM_DESCRIPTION.submit_url, $.param( + $.extend({url: FORM_DESCRIPTION.submit_url}, USER_DATA) + ) + ); + + // Respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Verify that the user is redirected to the dashboard + expect(view.redirect).toHaveBeenCalledWith('/dashboard'); }); - it("displays third party auth login buttons", function() { - // TODO + it('displays third-party auth login buttons', function() { + createLoginView(this); + + // Verify that Google and Facebook registration buttons are displayed + expect($('.button-Google')).toBeVisible(); + expect($('.button-Facebook')).toBeVisible(); }); - it("validates the email field", function() { - // TODO + it('displays a link to the password reset form', function() { + createLoginView(this); + + // Verify that the password reset link is displayed + expect($('.forgot-password')).toBeVisible(); }); - it("validates the password field", function() { - // TODO + it('validates login form fields', function() { + createLoginView(this); + + submitForm(true); + + // Verify that validation of form fields occurred + expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]); + expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]); }); - it("displays login errors", function() { - // TODO + it('displays login form validation errors', function() { + createLoginView(this); + + // Submit the form, with failed validation + submitForm(false); + + // Verify that submission errors are visible + expect(view.$errors).not.toHaveClass('hidden'); }); - it("displays an error if the form definition could not be loaded", function() { - // TODO - }); + it('displays an error if the server returns an error while logging in', function() { + createLoginView(this); - it("displays an error if the server could not be contacted while logging in", function() { - // TODO - }); + // Submit the form, with successful validation + submitForm(true); - it("allows the user to navigate to the password assistance form", function() { - // TODO - }); + // Simulate an error from the LMS servers + AjaxHelpers.respondWithError(requests); - it("enrolls the student into the right location and forwards them properly", function() { - // TODO + // Expect that an error is displayed, and that we haven't been redirected + expect(view.$errors).not.toHaveClass('hidden'); + expect(view.redirect).not.toHaveBeenCalled(); + + // If we try again and succeed, the error should go away + submitForm(); + + // This time, respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Expect that the error is hidden + expect(view.$errors).toHaveClass('hidden'); }); }); } diff --git a/lms/static/js/spec/student_account/password_reset_spec.js b/lms/static/js/spec/student_account/password_reset_spec.js index ce8dbe69b5..78128c9a5d 100644 --- a/lms/static/js/spec/student_account/password_reset_spec.js +++ b/lms/static/js/spec/student_account/password_reset_spec.js @@ -1,34 +1,61 @@ -define(['js/common_helpers/template_helpers', 'js/student_account/views/PasswordResetView'], - function(TemplateHelpers) { +define([ + 'jquery', + 'underscore', + 'js/common_helpers/template_helpers', + 'js/common_helpers/ajax_helpers', + 'js/student_account/models/PasswordResetModel', + 'js/student_account/views/PasswordResetView', +], function($, _, TemplateHelpers, AjaxHelpers, PasswordResetModel, PasswordResetView) { describe('edx.student.account.PasswordResetView', function() { 'use strict'; - var view = null, - ajaxSuccess = true, - model = new edx.student.account.PasswordResetModel(), - data = [{ - label: 'E-mail', - instructions: 'This is the e-mail address you used to register with edX', - name: 'email', - required: true, - type: 'email', - restrictions: [], - defaultValue: '' - }]; + var model = null, + view = null, + requests = null, + EMAIL = 'xsy@edx.org', + FORM_DESCRIPTION = { + method: 'post', + submit_url: '/account/password', + fields: [{ + name: 'email', + label: 'Email', + defaultValue: '', + type: 'text', + required: true, + placeholder: 'place@holder.org', + instructions: 'Enter your email.', + restrictions: {} + }] + }; + + var createPasswordResetView = function(that) { + // Initialize the password reset model + model = new PasswordResetModel({ url: FORM_DESCRIPTION.submit_url }); + + // Initialize the password reset view + view = new PasswordResetView({ + fields: FORM_DESCRIPTION.fields, + model: model + }); + + // Spy on AJAX requests + requests = AjaxHelpers.requests(that); + }; var submitEmail = function(validationSuccess) { // Simulate manual entry of an email address - $('#password-reset-email').val('foo@bar.baz'); + $('#password-reset-email').val(EMAIL); // Create a fake click event var clickEvent = $.Event('click'); - // Used to avoid spying on view.validate twice - if (typeof validationSuccess !== 'undefined') { + // If validationSuccess isn't passed, we avoid + // spying on `view.validate` twice + if ( !_.isUndefined(validationSuccess) ) { // Force validation to return as expected spyOn(view, 'validate').andReturn({ isValid: validationSuccess, - message: "We're all good." + message: 'Submission was validated.' }); } @@ -36,65 +63,74 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/Password view.submitForm(clickEvent); }; - var assertAjax = function(url, method, data) { - expect($.ajax).toHaveBeenCalled(); - var ajaxArgs = $.ajax.mostRecentCall.args[0]; - expect(ajaxArgs.url).toEqual(url); - expect(ajaxArgs.type).toEqual(method); - expect(ajaxArgs.data).toEqual(data) - expect(ajaxArgs.headers.hasOwnProperty("X-CSRFToken")).toBe(true); - }; - beforeEach(function() { - setFixtures("
"); + setFixtures('
'); TemplateHelpers.installTemplate('templates/student_account/password_reset'); TemplateHelpers.installTemplate('templates/student_account/form_field'); - - // Stub AJAX calls - spyOn($, 'ajax').andCallFake(function() { - return $.Deferred(function(defer) { - if (ajaxSuccess) { - defer.resolve(); - } else { - defer.rejectWith(this, ["The server could not be contacted."]); - } - }).promise(); - }); - - view = new edx.student.account.PasswordResetView({ - fields: data, - model: model - }); }); - it("allows the user to request a new password", function() { + it('allows the user to request a new password', function() { + createPasswordResetView(this); + + // Submit the form, with successful validation submitEmail(true); - assertAjax('/account/password', 'POST', {email: 'foo@bar.baz'}); + + // Verify that the client contacts the server with the expected data + AjaxHelpers.expectRequest( + requests, 'POST', FORM_DESCRIPTION.submit_url, $.param({ + url: FORM_DESCRIPTION.submit_url, + email: EMAIL + }) + ); + + // Respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Verify that the success message is visible expect($('.js-reset-success')).not.toHaveClass('hidden'); }); - it("validates the email field", function() { + it('validates the email field', function() { + createPasswordResetView(this); + + // Submit the form, with successful validation submitEmail(true); - expect(view.validate).toHaveBeenCalled() + + // Verify that validation of the email field occurred + expect(view.validate).toHaveBeenCalledWith($('#password-reset-email')[0]); + + // Verify that no submission errors are visible expect(view.$errors).toHaveClass('hidden'); }); - it("displays password reset validation errors", function() { + it('displays password reset validation errors', function() { + createPasswordResetView(this); + + // Submit the form, with failed validation submitEmail(false); + + // Verify that submission errors are visible expect(view.$errors).not.toHaveClass('hidden'); }); - it("displays an error if the server could not be contacted", function() { - // If we get an error status on the AJAX request, display an error - ajaxSuccess = false; + it('displays an error if the server returns an error while sending a password reset email', function() { + createPasswordResetView(this); submitEmail(true); - expect(view.$'#submission-error').not.toHaveClass('hidden'); + + // Simulate an error from the LMS servers + AjaxHelpers.respondWithError(requests); + + // Expect that an error is displayed + expect(view.$errors).not.toHaveClass('hidden'); // If we try again and succeed, the error should go away - ajaxSuccess = true; - // No argument means we won't spy on view.validate again submitEmail(); - expect(view.$'#submission-error').toHaveClass('hidden'); + + // This time, respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Expect that the error is hidden + expect(view.$errors).toHaveClass('hidden'); }); }); } diff --git a/lms/static/js/spec/student_account/register_spec.js b/lms/static/js/spec/student_account/register_spec.js index 36e08e3d08..0a50409a5d 100644 --- a/lms/static/js/spec/student_account/register_spec.js +++ b/lms/static/js/spec/student_account/register_spec.js @@ -1,35 +1,296 @@ -define(['js/common_helpers/template_helpers', 'js/student_account/views/RegisterView'], - function(TemplateHelpers) { - describe("edx.student.account.RegisterView", function() { +define([ + 'jquery', + 'underscore', + 'js/common_helpers/template_helpers', + 'js/common_helpers/ajax_helpers', + 'js/student_account/models/RegisterModel', + 'js/student_account/views/RegisterView' +], function($, _, TemplateHelpers, AjaxHelpers, RegisterModel, RegisterView) { + describe('edx.student.account.RegisterView', function() { 'use strict'; + var model = null, + view = null, + requests = null, + PLATFORM_NAME = 'edX', + USER_DATA = { + email: 'xsy@edx.org', + name: 'Xsy M. Education', + username: 'Xsy', + password: 'xsyisawesome', + level_of_education: 'p', + gender: 'm', + year_of_birth: 2014, + mailing_address: '141 Portland', + goals: 'To boldly learn what no letter of the alphabet has learned before', + terms_of_service: true + }, + THIRD_PARTY_AUTH = { + currentProvider: null, + providers: [ + { + name: 'Google', + iconClass: 'icon-google-plus', + loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', + registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' + }, + { + name: 'Facebook', + iconClass: 'icon-facebook', + loginUrl: '/auth/login/facebook/?auth_entry=account_login', + registerUrl: '/auth/login/facebook/?auth_entry=account_register' + } + ] + }, + FORM_DESCRIPTION = { + method: 'post', + submit_url: '/user_api/v1/account/registration/', + fields: [ + { + name: 'email', + label: 'Email', + defaultValue: '', + type: 'email', + required: true, + placeholder: 'place@holder.org', + instructions: 'Enter your email.', + restrictions: {} + }, + { + name: 'name', + label: 'Full Name', + defaultValue: '', + type: 'text', + required: true, + instructions: 'Enter your username.', + restrictions: {} + }, + { + name: 'username', + label: 'Username', + defaultValue: '', + type: 'text', + required: true, + instructions: 'Enter your username.', + restrictions: {} + }, + { + name: 'password', + label: 'Password', + defaultValue: '', + type: 'password', + required: true, + instructions: 'Enter your password.', + restrictions: {} + }, + { + name: 'level_of_education', + label: 'Highest Level of Education Completed', + defaultValue: '', + type: 'select', + options: [ + {value: "", name: "--"}, + {value: "p", name: "Doctorate"}, + {value: "m", name: "Master's or professional degree"}, + {value: "b", name: "Bachelor's degree"}, + ], + required: false, + instructions: 'Select your education level.', + restrictions: {} + }, + { + name: 'gender', + label: 'Gender', + defaultValue: '', + type: 'select', + options: [ + {value: "", name: "--"}, + {value: "m", name: "Male"}, + {value: "f", name: "Female"}, + {value: "o", name: "Other"}, + ], + required: false, + instructions: 'Select your gender.', + restrictions: {} + }, + { + name: 'year_of_birth', + label: 'Year of Birth', + defaultValue: '', + type: 'select', + options: [ + {value: "", name: "--"}, + {value: 1900, name: "1900"}, + {value: 1950, name: "1950"}, + {value: 2014, name: "2014"}, + ], + required: false, + instructions: 'Select your year of birth.', + restrictions: {} + }, + { + name: 'mailing_address', + label: 'Mailing Address', + defaultValue: '', + type: 'textarea', + required: false, + instructions: 'Enter your mailing address.', + restrictions: {} + }, + { + name: 'goals', + label: 'Goals', + defaultValue: '', + type: 'textarea', + required: false, + instructions: "If you'd like, tell us why you're interested in edX.", + restrictions: {} + }, + { + name: 'terms_of_service', + label: 'Terms of Service', + defaultValue: '', + type: 'checkbox', + required: true, + instructions: "Agree to the terms of service.", + restrictions: {} + } + ] + }; + + var createRegisterView = function(that) { + // Initialize the register model + model = new RegisterModel({ url: FORM_DESCRIPTION.submit_url }); + + // Initialize the register view + view = new RegisterView({ + fields: FORM_DESCRIPTION.fields, + model: model, + thirdPartyAuth: THIRD_PARTY_AUTH, + platformName: PLATFORM_NAME + }); + + // Spy on AJAX requests + requests = AjaxHelpers.requests(that); + + // Mock out redirection logic + spyOn(view, 'redirect').andCallFake(function() { + return true; + }); + }; + + var submitForm = function(validationSuccess) { + // Simulate manual entry of registration form data + $('#register-email').val(USER_DATA.email); + $('#register-name').val(USER_DATA.name); + $('#register-username').val(USER_DATA.username); + $('#register-password').val(USER_DATA.password); + $('#register-level_of_education').val(USER_DATA.level_of_education); + $('#register-gender').val(USER_DATA.gender); + $('#register-year_of_birth').val(USER_DATA.year_of_birth); + $('#register-mailing_address').val(USER_DATA.mailing_address); + $('#register-goals').val(USER_DATA.goals); + + // Check the terms of service checkbox + $('#register-terms_of_service').prop('checked', USER_DATA.terms_of_service); + + // Create a fake click event + var clickEvent = $.Event('click'); + + // If validationSuccess isn't passed, we avoid + // spying on `view.validate` twice + if ( !_.isUndefined(validationSuccess) ) { + // Force validation to return as expected + spyOn(view, 'validate').andReturn({ + isValid: validationSuccess, + message: 'Submission was validated.' + }); + } + + // Submit the email address + view.submitForm(clickEvent); + }; + beforeEach(function() { - setFixtures("
"); - TemplateHelpers.installTemplate("templates/student_account/register"); + setFixtures('
'); + TemplateHelpers.installTemplate('templates/student_account/register'); + TemplateHelpers.installTemplate('templates/student_account/form_field'); }); - it("registers a new user", function() { - // TODO + it('registers a new user', function() { + createRegisterView(this); + + // Submit the form, with successful validation + submitForm(true); + + // Verify that the client contacts the server with the expected data + AjaxHelpers.expectRequest( + requests, 'POST', FORM_DESCRIPTION.submit_url, $.param( + $.extend({url: FORM_DESCRIPTION.submit_url}, USER_DATA) + ) + ); + + // Respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Verify that the user is redirected to the dashboard + expect(view.redirect).toHaveBeenCalledWith('/dashboard'); }); - it("displays third party auth registration buttons", function() { - // TODO + it('displays third-party auth registration buttons', function() { + createRegisterView(this); + + // Verify that Google and Facebook registration buttons are displayed + expect($('.button-Google')).toBeVisible(); + expect($('.button-Facebook')).toBeVisible(); }); - it("validates form fields", function() { - // TODO + it('validates registration form fields', function() { + createRegisterView(this); + + // Submit the form, with successful validation + submitForm(true); + + // Verify that validation of form fields occurred + expect(view.validate).toHaveBeenCalledWith($('#register-email')[0]); + expect(view.validate).toHaveBeenCalledWith($('#register-name')[0]); + expect(view.validate).toHaveBeenCalledWith($('#register-username')[0]); + expect(view.validate).toHaveBeenCalledWith($('#register-password')[0]); + + // Verify that no submission errors are visible + expect(view.$errors).toHaveClass('hidden'); }); - it("displays registration errors", function() { - // TODO + it('displays registration form validation errors', function() { + createRegisterView(this); + + // Submit the form, with failed validation + submitForm(false); + + // Verify that submission errors are visible + expect(view.$errors).not.toHaveClass('hidden'); }); - it("displays an error if the form definition could not be loaded", function() { - // TODO - }); + it('displays an error if the server returns an error while registering', function() { + createRegisterView(this); - it("displays an error if the server could not be contacted while registering", function() { - // TODO + // Submit the form, with successful validation + submitForm(true); + + // Simulate an error from the LMS servers + AjaxHelpers.respondWithError(requests); + + // Expect that an error is displayed + expect(view.$errors).not.toHaveClass('hidden'); + + // If we try again and succeed, the error should go away + submitForm(); + + // This time, respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Expect that the error is hidden + expect(view.$errors).toHaveClass('hidden'); }); }); } diff --git a/lms/static/js/spec/student_profile/profile.js b/lms/static/js/spec/student_profile/profile_spec.js similarity index 100% rename from lms/static/js/spec/student_profile/profile.js rename to lms/static/js/spec/student_profile/profile_spec.js diff --git a/lms/static/js/student_account/account.js b/lms/static/js/student_account/account.js index 21906d9706..9569ed42c9 100644 --- a/lms/static/js/student_account/account.js +++ b/lms/static/js/student_account/account.js @@ -4,7 +4,7 @@ var edx = edx || {}; 'use strict'; edx.student = edx.student || {}; - edx.student.account = {}; + edx.student.account = edx.student.account || {}; edx.student.account.AccountModel = Backbone.Model.extend({ // These should be the same length limits enforced by the server diff --git a/lms/static/js/student_account/enrollment_interface.js b/lms/static/js/student_account/enrollment_interface.js index 09aa02360d..3572458c12 100644 --- a/lms/static/js/student_account/enrollment_interface.js +++ b/lms/static/js/student_account/enrollment_interface.js @@ -8,71 +8,80 @@ var edx = edx || {}; edx.student.account.EnrollmentInterface = { courseUrl: '/enrollment/v0/course/', + studentUrl: '/enrollment/v0/student', + trackSelectionUrl: '/course_modes/choose/', + headers: { 'X-CSRFToken': $.cookie('csrftoken') }, - studentInformation: function(course_key) { + studentInformation: function(courseKey) { // retrieve student enrollment information }, - courseInformation: function(course_key) { + courseInformation: function(courseKey) { // retrieve course information from the enrollment API }, - modeInArray: function(mode_slug, course_modes) { - // finds whether or not a particular course mode slug exists - // in an array of course modes - var result = _.find(course_modes, function(mode){ return mode.slug === mode_slug; }); - return result != undefined; + modeInArray: function(modeObjects, targetMode) { + // Check if a given course mode slug exists in an array of mode objects + var result = _.find(modeObjects, function(mode) { + return mode.slug === targetMode; + }); + + /* _.find returns the first value which passes the provided truth test, + /* or undefined if no values pass the test + */ + return !_.isUndefined(result); }, - enroll: function(course_key, forward_url){ + enroll: function(courseKey, forwardUrl){ var me = this; // attempt to enroll a student in a course $.ajax({ - url: this.courseUrl + course_key, + url: this.courseUrl + courseKey, type: 'POST', data: {}, headers: this.headers }).done(function(data){ - me.postEnrollmentHandler(course_key, data, forward_url); + me.postEnrollmentHandler(courseKey, data, forwardUrl); } ).fail(function(data, textStatus) { - me.enrollmentFailureHandler(course_key, data, forward_url); + me.enrollmentFailureHandler(courseKey, data, forwardUrl); }); }, - enrollmentFailureHandler: function(course_key, data, forward_url) { + enrollmentFailureHandler: function(courseKey, data, forwardUrl) { // handle failures to enroll via the API if(data.status == 400) { - // This status code probably means we don't have permissions to register for this course. - // look at the contents of the response + /* This status code probably means we don't have permissions to register + /* for this course; look at the contents of the response + */ var course = $.parseJSON(data.responseText); // see if it's a professional ed course - if('course_modes' in course && this.modeInArray('professional', course.course_modes)) { + if( 'course_modes' in course && this.modeInArray(course.course_modes, 'professional') ) { // forward appropriately - forward_url = this.trackSelectionUrl + course_key; + forwardUrl = this.trackSelectionUrl + courseKey; } } // TODO: if we have a paid registration mode, add item to the cart and send them along - // TODO: we should figure out how to handle errors here eventually - window.location.href = forward_url; + // TODO: we should figure out how to handle errors here + window.location.href = forwardUrl; }, - postEnrollmentHandler: function(course_key, data, forward_url) { + postEnrollmentHandler: function(courseKey, data, forwardUrl) { // Determine whether or not the course needs to be redirected to // a particular page. var course = data.course, course_modes = course.course_modes; // send the user to the track selection page, because it will do the right thing - forward_url = this.trackSelectionUrl + course_key; + forwardUrl = this.trackSelectionUrl + courseKey; - window.location.href = forward_url; + window.location.href = forwardUrl; } }; })(jQuery, _, gettext); diff --git a/lms/static/js/student_account/models/LoginModel.js b/lms/static/js/student_account/models/LoginModel.js index f852d01c71..f2cef540c2 100644 --- a/lms/static/js/student_account/models/LoginModel.js +++ b/lms/static/js/student_account/models/LoginModel.js @@ -1,12 +1,11 @@ var edx = edx || {}; -(function($, _, Backbone, gettext) { +(function($, Backbone) { 'use strict'; edx.student = edx.student || {}; edx.student.account = edx.student.account || {}; - edx.student.account.LoginModel = Backbone.Model.extend({ defaults: { @@ -33,35 +32,11 @@ var edx = edx || {}; headers: headers }) .done(function() { - var enrollment = edx.student.account.EnrollmentInterface, - query = new URI(window.location.search), - url = '/dashboard', - query_map = query.search(true), - next = ''; - - // check for forwarding url - if("next" in query_map) { - next = query_map['next']; - if(!window.isExternal(next)){ - url = next; - } - } - model.trigger('sync'); - - // if we need to enroll in the course, mark as enrolled - if('enrollment_action' in query_map && query_map['enrollment_action'] === 'enroll'){ - enrollment.enroll(query_map['course_id'], url); - } - else { - window.location.href = url; - } - - }) .fail( function( error ) { model.trigger('error', error); }); } }); -})(jQuery, _, Backbone, gettext); +})(jQuery, Backbone); diff --git a/lms/static/js/student_account/models/PasswordResetModel.js b/lms/static/js/student_account/models/PasswordResetModel.js index 64a1e37932..5f997a2b51 100644 --- a/lms/static/js/student_account/models/PasswordResetModel.js +++ b/lms/static/js/student_account/models/PasswordResetModel.js @@ -1,6 +1,6 @@ var edx = edx || {}; -(function($, _, Backbone, gettext) { +(function($, Backbone) { 'use strict'; edx.student = edx.student || {}; @@ -31,11 +31,11 @@ var edx = edx || {}; headers: headers }) .done(function() { - model.trigger('success'); + model.trigger('sync'); }) .fail( function( error ) { - model.trigger( 'error', error ); + model.trigger('error', error); }); } }); -})(jQuery, _, Backbone, gettext); +})(jQuery, Backbone); diff --git a/lms/static/js/student_account/models/RegisterModel.js b/lms/static/js/student_account/models/RegisterModel.js index 3e36e9b727..c29d26fa18 100644 --- a/lms/static/js/student_account/models/RegisterModel.js +++ b/lms/static/js/student_account/models/RegisterModel.js @@ -1,6 +1,6 @@ var edx = edx || {}; -(function($, _, Backbone, gettext) { +(function($, Backbone) { 'use strict'; edx.student = edx.student || {}; @@ -18,7 +18,7 @@ var edx = edx || {}; year_of_birth: '', mailing_address: '', goals: '', - termsofservice: false + terms_of_service: false }, urlRoot: '', @@ -39,33 +39,11 @@ var edx = edx || {}; headers: headers }) .done(function() { - var enrollment = edx.student.account.EnrollmentInterface, - query = new URI(window.location.search), - url = '/dashboard', - query_map = query.search(true), - next = ''; - - // check for forwarding url - if("next" in query_map) { - next = query_map['next']; - if(!window.isExternal(next)){ - url = next; - } - } - model.trigger('sync'); - - // if we need to enroll in the course, mark as enrolled - if('enrollment_action' in query_map && query_map['enrollment_action'] === 'enroll'){ - enrollment.enroll(query_map['course_id'], url); - } - else { - window.location.href = url; - } }) .fail( function( error ) { model.trigger('error', error); }); } }); -})(jQuery, _, Backbone, gettext); +})(jQuery, Backbone); diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index b3ffc8c2e9..57f4798e57 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -1,6 +1,6 @@ var edx = edx || {}; -(function($, _, Backbone, gettext, analytics) { +(function($, _, _s, Backbone, gettext) { 'use strict'; edx.student = edx.student || {}; @@ -29,7 +29,7 @@ var edx = edx || {}; * (all but include, contains, and reverse) into the * Underscore namespace */ - _.mixin( _.str.exports() ); + _.mixin( _s.exports() ); this.tpl = $(this.tpl).html(); this.activeForm = obj.mode || 'login'; @@ -112,20 +112,20 @@ var edx = edx || {}; }; $.ajax({ - type: 'GET', - dataType: 'json', url: '/user_api/v1/account/' + urls[type] + '/', - success: function( data ) { - callback( data, context ); - }, - error: function( jqXHR, textStatus, errorThrown ) { - console.log('fail ', errorThrown); - } + type: 'GET', + dataType: 'json' + }) + .done(function( data ) { + callback( data, context ); + }) + .fail(function( jqXHR, textStatus, errorThrown ) { + console.log('fail ', errorThrown); }); }, resetPassword: function() { - analytics.track('edx.bi.password_reset_form.viewed', { + window.analytics.track('edx.bi.password_reset_form.viewed', { category: 'user-engagement' }); @@ -139,7 +139,7 @@ var edx = edx || {}; $form = $('#' + type + '-form'), $anchor = $('#' + type + '-anchor'); - analytics.track('edx.bi.' + type + '_form.toggled', { + window.analytics.track('edx.bi.' + type + '_form.toggled', { category: 'user-engagement' }); @@ -177,5 +177,4 @@ var edx = edx || {}; } } }); - -})(jQuery, _, Backbone, gettext, analytics); +})(jQuery, _, _.str, Backbone, gettext); diff --git a/lms/static/js/student_account/views/FormView.js b/lms/static/js/student_account/views/FormView.js index 66663d363a..4595fc3c0b 100644 --- a/lms/static/js/student_account/views/FormView.js +++ b/lms/static/js/student_account/views/FormView.js @@ -29,12 +29,12 @@ var edx = edx || {}; requiredStr: '*', initialize: function( data ) { + this.model = data.model; this.preRender( data ); + this.tpl = $(this.tpl).html(); this.fieldTpl = $(this.fieldTpl).html(); - this.buildForm( data.fields ); - this.model = data.model; this.listenTo( this.model, 'error', this.saveError ); }, diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js index 0480731c40..b10e4d666b 100644 --- a/lms/static/js/student_account/views/LoginView.js +++ b/lms/static/js/student_account/views/LoginView.js @@ -25,6 +25,8 @@ var edx = edx || {}; this.providers = data.thirdPartyAuth.providers || []; this.currentProvider = data.thirdPartyAuth.currentProvider || ''; this.platformName = data.platformName; + + this.listenTo( this.model, 'sync', this.saveSuccess ); }, render: function( html ) { @@ -76,6 +78,32 @@ var edx = edx || {}; } }, + saveSuccess: function () { + var enrollment = edx.student.account.EnrollmentInterface, + redirectUrl = '/dashboard', + next = null; + + // Check for forwarding url + if ( !_.isNull( $.url('?next') ) ) { + next = decodeURIComponent( $.url('?next') ); + + if ( !window.isExternal(next) ) { + redirectUrl = next; + } + } + + // If we need to enroll in a course, mark as enrolled + if ( $.url('?enrollment_action') === 'enroll' ) { + enrollment.enroll( decodeURIComponent( $.url('?course_id') ), redirectUrl ); + } else { + this.redirect(redirectUrl); + } + }, + + redirect: function( url ) { + window.location.href = url; + }, + saveError: function( error ) { this.errors = ['
  • ' + error.responseText + '
  • ']; this.setErrors(); diff --git a/lms/static/js/student_account/views/PasswordResetView.js b/lms/static/js/student_account/views/PasswordResetView.js index 979cfbb0d7..da85293999 100644 --- a/lms/static/js/student_account/views/PasswordResetView.js +++ b/lms/static/js/student_account/views/PasswordResetView.js @@ -19,15 +19,8 @@ var edx = edx || {}; requiredStr: '', - postRender: function() { - var $container = $(this.el); - - this.$form = $container.find('form'); - - this.$errors = $container.find('.submission-error'); - - this.listenTo( this.model, 'success', this.resetComplete ); - this.listenTo( this.model, 'error', this.saveError ); + preRender: function( data ) { + this.listenTo( this.model, 'sync', this.saveSuccess ); }, toggleErrorMsg: function( show ) { @@ -38,7 +31,7 @@ var edx = edx || {}; } }, - resetComplete: function() { + saveSuccess: function() { var $el = $(this.el); this.element.hide( $el.find('#password-reset-form') ); diff --git a/lms/static/js/student_account/views/RegisterView.js b/lms/static/js/student_account/views/RegisterView.js index ec3b4ff287..afa250d7bb 100644 --- a/lms/static/js/student_account/views/RegisterView.js +++ b/lms/static/js/student_account/views/RegisterView.js @@ -22,6 +22,8 @@ var edx = edx || {}; this.providers = data.thirdPartyAuth.providers || []; this.currentProvider = data.thirdPartyAuth.currentProvider || ''; this.platformName = data.platformName; + + this.listenTo( this.model, 'sync', this.saveSuccess ); }, render: function( html ) { @@ -50,7 +52,32 @@ var edx = edx || {}; if ( providerUrl ) { window.location.href = providerUrl; } + }, + + saveSuccess: function() { + var enrollment = edx.student.account.EnrollmentInterface, + redirectUrl = '/dashboard', + next = null; + + // Check for forwarding url + if ( !_.isNull( $.url('?next') ) ) { + next = decodeURIComponent( $.url('?next') ); + + if ( !window.isExternal(next) ) { + redirectUrl = next; + } + } + + // If we need to enroll in a course, mark as enrolled + if ( $.url('?enrollment_action') === 'enroll' ) { + enrollment.enroll( decodeURIComponent( $.url('?course_id') ), redirectUrl ); + } else { + this.redirect(redirectUrl); + } + }, + + redirect: function( url ) { + window.location.href = url; } }); - })(jQuery, _, gettext); diff --git a/lms/static/js/student_profile/profile.js b/lms/static/js/student_profile/profile.js index 664b198d65..77bc8ae7a2 100644 --- a/lms/static/js/student_profile/profile.js +++ b/lms/static/js/student_profile/profile.js @@ -4,7 +4,7 @@ var edx = edx || {}; 'use strict'; edx.student = edx.student || {}; - edx.student.profile = {}; + edx.student.profile = edx.student.profile || {}; var syncErrorMessage = gettext("The data could not be saved."); diff --git a/lms/static/js_test.yml b/lms/static/js_test.yml index fa0bb93b48..10f140a9e3 100644 --- a/lms/static/js_test.yml +++ b/lms/static/js_test.yml @@ -41,6 +41,7 @@ lib_paths: - xmodule_js/common_static/js/vendor/flot/jquery.flot.js - xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js - xmodule_js/common_static/js/vendor/URI.min.js + - xmodule_js/common_static/js/vendor/url.min.js - xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js - xmodule_js/common_static/coffee/src/xblock - xmodule_js/common_static/js/vendor/sinon-1.7.1.js @@ -49,6 +50,7 @@ lib_paths: - xmodule_js/src/xmodule.js - xmodule_js/common_static/js/src/ - xmodule_js/common_static/js/vendor/underscore-min.js + - xmodule_js/common_static/js/vendor/underscore.string.min.js - xmodule_js/common_static/js/vendor/backbone-min.js # Paths to source JavaScript files diff --git a/lms/templates/student_account/login_and_register.html b/lms/templates/student_account/login_and_register.html index debe7910fe..e7d9c5df5d 100644 --- a/lms/templates/student_account/login_and_register.html +++ b/lms/templates/student_account/login_and_register.html @@ -6,9 +6,10 @@ <%block name="pagetitle">${_("Log in or Register")} <%block name="js_extra"> - - - + + + + <%static:js group='student_account'/>