diff --git a/common/djangoapps/third_party_auth/tests/specs/test_lti.py b/common/djangoapps/third_party_auth/tests/specs/test_lti.py index fa9e2398e4..d6622def7e 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_lti.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_lti.py @@ -69,8 +69,8 @@ class IntegrationTestLTI(testutil.TestCase): self.assertTrue(login_response['Location'].endswith(reverse('signin_user'))) register_response = self.client.get(login_response['Location']) self.assertEqual(register_response.status_code, 200) - self.assertIn('currentProvider": "LTI Test Tool Consumer"', register_response.content) - self.assertIn('"errorMessage": null', register_response.content) + self.assertIn('"currentProvider": "LTI Test Tool Consumer"', register_response.content) + self.assertIn('"errorMessage": null', register_response.content) # Now complete the form: ajax_register_response = self.client.post( @@ -153,7 +153,7 @@ class IntegrationTestLTI(testutil.TestCase): register_response = self.client.get(login_response['Location']) self.assertEqual(register_response.status_code, 200) self.assertIn( - 'currentProvider": "Tool Consumer with Secret in Settings"', + '"currentProvider": "Tool Consumer with Secret in Settings"', register_response.content ) - self.assertIn('"errorMessage": null', register_response.content) + self.assertIn('"errorMessage": null', register_response.content) diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py index dc4ca99880..b833efab27 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py @@ -1,13 +1,20 @@ """ Third_party_auth integration tests using a mock version of the TestShib provider """ -from django.core.urlresolvers import reverse + +import json +import unittest import httpretty from mock import patch + +from django.core.urlresolvers import reverse + +from openedx.core.lib.json_utils import EscapedEdxJSONEncoder + from student.tests.factories import UserFactory from third_party_auth.tasks import fetch_saml_metadata from third_party_auth.tests import testutil -import unittest + TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth' TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml' @@ -81,11 +88,11 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): # We'd now like to see if the "You've successfully signed into TestShib" message is # shown, but it's managed by a JavaScript runtime template, and we can't run JS in this # type of test, so we just check for the variable that triggers that message. - self.assertIn('"currentProvider": "TestShib"', register_response.content) - self.assertIn('"errorMessage": null', register_response.content) + self.assertIn('"currentProvider": "TestShib"', register_response.content) + self.assertIn('"errorMessage": null', register_response.content) # Now do a crude check that the data (e.g. email) from the provider is displayed in the form: - self.assertIn('"defaultValue": "myself@testshib.org"', register_response.content) - self.assertIn('"defaultValue": "Me Myself And I"', register_response.content) + self.assertIn('"defaultValue": "myself@testshib.org"', register_response.content) + self.assertIn('"defaultValue": "Me Myself And I"', register_response.content) # Now complete the form: ajax_register_response = self.client.post( reverse('user_api_registration'), @@ -128,8 +135,8 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): # We'd now like to see if the "You've successfully signed into TestShib" message is # shown, but it's managed by a JavaScript runtime template, and we can't run JS in this # type of test, so we just check for the variable that triggers that message. - self.assertIn('"currentProvider": "TestShib"', login_response.content) - self.assertIn('"errorMessage": null', login_response.content) + self.assertIn('"currentProvider": "TestShib"', login_response.content) + self.assertIn('"errorMessage": null', login_response.content) # Now the user enters their username and password. # The AJAX on the page will log them in: ajax_login_response = self.client.post( @@ -183,7 +190,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): response = self.client.get(self.login_page_url) self.assertEqual(response.status_code, 200) self.assertIn("TestShib", response.content) - self.assertIn(TPA_TESTSHIB_LOGIN_URL.replace('&', '&'), response.content) + self.assertIn(json.dumps(TPA_TESTSHIB_LOGIN_URL, cls=EscapedEdxJSONEncoder), response.content) return response def _check_register_page(self): @@ -191,7 +198,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): response = self.client.get(self.register_page_url) self.assertEqual(response.status_code, 200) self.assertIn("TestShib", response.content) - self.assertIn(TPA_TESTSHIB_REGISTER_URL.replace('&', '&'), response.content) + self.assertIn(json.dumps(TPA_TESTSHIB_REGISTER_URL, cls=EscapedEdxJSONEncoder), response.content) return response def _configure_testshib_provider(self, **kwargs): diff --git a/common/static/js/spec/edx.utils.validate_spec.js b/common/static/js/spec/edx.utils.validate_spec.js index d6664c4647..b85b99d733 100644 --- a/common/static/js/spec/edx.utils.validate_spec.js +++ b/common/static/js/spec/edx.utils.validate_spec.js @@ -1,192 +1,195 @@ -describe('edx.utils.validate', function () { +;(function (define) { 'use strict'; + define(['jquery', 'js/utils/edx.utils.validate'], + function($) { - var fixture = null, - field = null, - result = null, - MIN_LENGTH = 2, - MAX_LENGTH = 20, - VALID_STRING = 'xsy_is_awesome', - SHORT_STRING = 'x', - LONG_STRING = 'xsy_is_way_too_awesome', - EMAIL_ERROR_FRAGMENT = 'formatted', - MIN_ERROR_FRAGMENT = 'least', - MAX_ERROR_FRAGMENT = 'up to', - REQUIRED_ERROR_FRAGMENT = 'Please enter your', - CUSTOM_MESSAGE = 'custom message'; + var fixture = null, + field = null, + result = null, + MIN_LENGTH = 2, + MAX_LENGTH = 20, + VALID_STRING = 'xsy_is_awesome', + SHORT_STRING = 'x', + LONG_STRING = 'xsy_is_way_too_awesome', + EMAIL_ERROR_FRAGMENT = 'formatted', + MIN_ERROR_FRAGMENT = 'least', + MAX_ERROR_FRAGMENT = 'up to', + REQUIRED_ERROR_FRAGMENT = 'Please enter your', + CUSTOM_MESSAGE = 'custom message'; - var createFixture = function( type, name, required, minlength, maxlength, value ) { - setFixtures(''); + var createFixture = function( type, name, required, minlength, maxlength, value ) { + setFixtures(''); - field = $('#field'); - field.prop('required', required); - field.attr({ - name: name, - minlength: minlength, - maxlength: maxlength, - value: value + field = $('#field'); + field.prop('required', required); + field.attr({ + name: name, + minlength: minlength, + maxlength: maxlength, + value: value + }); + }; + + var expectValid = function() { + result = edx.utils.validate(field); + expect(result.isValid).toBe(true); + }; + + var expectInvalid = function( errorFragment ) { + result = edx.utils.validate(field); + expect(result.isValid).toBe(false); + expect(result.message).toMatch(errorFragment); + }; + + it('succeeds if an optional field is left blank', function () { + createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, ''); + expectValid(); }); - }; - var expectValid = function() { - result = edx.utils.validate(field); - expect(result.isValid).toBe(true); - }; + it('succeeds if a required field is provided a valid value', function () { + createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, VALID_STRING); + expectValid(); + }); - var expectInvalid = function( errorFragment ) { - result = edx.utils.validate(field); - expect(result.isValid).toBe(false); - expect(result.message).toMatch(errorFragment); - }; + it('fails if a required field is left blank', function () { + createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + }); - it('succeeds if an optional field is left blank', function () { - createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, ''); - expectValid(); + it('fails if a field is provided a value below its minimum character limit', function () { + createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, SHORT_STRING); + + // Verify optional field behavior + expectInvalid(MIN_ERROR_FRAGMENT); + + // Verify required field behavior + field.prop('required', true); + expectInvalid(MIN_ERROR_FRAGMENT); + }); + + it('succeeds if a field with no minimum character limit is provided a value below its maximum character limit', function () { + createFixture('text', 'username', false, null, MAX_LENGTH, SHORT_STRING); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('fails if a required field with no minimum character limit is left blank', function () { + createFixture('text', 'username', true, null, MAX_LENGTH, ''); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + }); + + it('fails if a field is provided a value above its maximum character limit', function () { + createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, LONG_STRING); + + // Verify optional field behavior + expectInvalid(MAX_ERROR_FRAGMENT); + + // Verify required field behavior + field.prop('required', true); + expectInvalid(MAX_ERROR_FRAGMENT); + }); + + it('succeeds if a field with no maximum character limit is provided a value above its minimum character limit', function () { + createFixture('text', 'username', false, MIN_LENGTH, null, LONG_STRING); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('succeeds if a field with no character limits is provided a value', function () { + createFixture('text', 'username', false, null, null, VALID_STRING); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('fails if an email field is provided an invalid address', function () { + createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart'); + + // Verify optional field behavior + expectInvalid(EMAIL_ERROR_FRAGMENT); + + // Verify required field behavior + field.prop('required', false); + expectInvalid(EMAIL_ERROR_FRAGMENT); + }); + + it('succeeds if an email field is provided a valid address', function () { + createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart@label.tld'); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('succeeds if a checkbox is optional, or required and checked, but fails if a required checkbox is unchecked', function () { + createFixture('checkbox', 'checkbox', false, null, null, 'value'); + + // Optional, unchecked + expectValid(); + + // Optional, checked + field.prop('checked', true); + expectValid(); + + // Required, checked + field.prop('required', true); + expectValid(); + + // Required, unchecked + field.prop('checked', false); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + }); + + it('succeeds if a select is optional, or required and default is selected, but fails if a required select has the default option selected', function () { + var select = [ + '' + ].join(''); + + setFixtures(select); + + field = $('#dropdown'); + + // Optional + expectValid(); + + // Required, default text selected + field.attr('required', true); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + + // Required, country selected + field.val('BE'); + expectValid(); + }); + + it('returns a custom error message if an invalid field has one attached', function () { + // Create a blank required field + createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); + + // Attach a custom error message to the field + field.data('errormsg-required', CUSTOM_MESSAGE); + + expectInvalid(CUSTOM_MESSAGE); + }); }); - - it('succeeds if a required field is provided a valid value', function () { - createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, VALID_STRING); - expectValid(); - }); - - it('fails if a required field is left blank', function () { - createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); - expectInvalid(REQUIRED_ERROR_FRAGMENT); - }); - - it('fails if a field is provided a value below its minimum character limit', function () { - createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, SHORT_STRING); - - // Verify optional field behavior - expectInvalid(MIN_ERROR_FRAGMENT); - - // Verify required field behavior - field.prop('required', true); - expectInvalid(MIN_ERROR_FRAGMENT); - }); - - it('succeeds if a field with no minimum character limit is provided a value below its maximum character limit', function () { - createFixture('text', 'username', false, null, MAX_LENGTH, SHORT_STRING); - - // Verify optional field behavior - expectValid(); - - // Verify required field behavior - field.prop('required', true); - expectValid(); - }); - - it('fails if a required field with no minimum character limit is left blank', function () { - createFixture('text', 'username', true, null, MAX_LENGTH, ''); - expectInvalid(REQUIRED_ERROR_FRAGMENT); - }); - - it('fails if a field is provided a value above its maximum character limit', function () { - createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, LONG_STRING); - - // Verify optional field behavior - expectInvalid(MAX_ERROR_FRAGMENT); - - // Verify required field behavior - field.prop('required', true); - expectInvalid(MAX_ERROR_FRAGMENT); - }); - - it('succeeds if a field with no maximum character limit is provided a value above its minimum character limit', function () { - createFixture('text', 'username', false, MIN_LENGTH, null, LONG_STRING); - - // Verify optional field behavior - expectValid(); - - // Verify required field behavior - field.prop('required', true); - expectValid(); - }); - - it('succeeds if a field with no character limits is provided a value', function () { - createFixture('text', 'username', false, null, null, VALID_STRING); - - // Verify optional field behavior - expectValid(); - - // Verify required field behavior - field.prop('required', true); - expectValid(); - }); - - it('fails if an email field is provided an invalid address', function () { - createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart'); - - // Verify optional field behavior - expectInvalid(EMAIL_ERROR_FRAGMENT); - - // Verify required field behavior - field.prop('required', false); - expectInvalid(EMAIL_ERROR_FRAGMENT); - }); - - it('succeeds if an email field is provided a valid address', function () { - createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart@label.tld'); - - // Verify optional field behavior - expectValid(); - - // Verify required field behavior - field.prop('required', true); - expectValid(); - }); - - it('succeeds if a checkbox is optional, or required and checked, but fails if a required checkbox is unchecked', function () { - createFixture('checkbox', 'checkbox', false, null, null, 'value'); - - // Optional, unchecked - expectValid(); - - // Optional, checked - field.prop('checked', true); - expectValid(); - - // Required, checked - field.prop('required', true); - expectValid(); - - // Required, unchecked - field.prop('checked', false); - expectInvalid(REQUIRED_ERROR_FRAGMENT); - }); - - it('succeeds if a select is optional, or required and default is selected, but fails if a required select has the default option selected', function () { - var select = [ - '' - ].join(''); - - setFixtures(select); - - field = $('#dropdown'); - - // Optional - expectValid(); - - // Required, default text selected - field.attr('required', true); - expectInvalid(REQUIRED_ERROR_FRAGMENT); - - // Required, country selected - field.val('BE'); - expectValid(); - }); - - it('returns a custom error message if an invalid field has one attached', function () { - // Create a blank required field - createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); - - // Attach a custom error message to the field - field.data('errormsg-required', CUSTOM_MESSAGE); - - expectInvalid(CUSTOM_MESSAGE); - }); -}); +}).call(this, define || RequireJS.define); diff --git a/common/static/js/utils/edx.utils.validate.js b/common/static/js/utils/edx.utils.validate.js index 392c99bc46..8fff89cd81 100644 --- a/common/static/js/utils/edx.utils.validate.js +++ b/common/static/js/utils/edx.utils.validate.js @@ -1,186 +1,193 @@ -var edx = edx || {}; - -(function( $, _, _s, gettext ) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'underscore', + 'underscore.string', + 'gettext' + ], + function($, _, _s, gettext) { + var utils; - /* Mix non-conflicting functions from underscore.string - * (all but include, contains, and reverse) into the - * Underscore namespace. In practice, this mixin is done - * by the access view, but doing it here helps keep the - * utility self-contained. - */ - _.mixin( _.str.exports() ); + /* Mix non-conflicting functions from underscore.string + * (all but include, contains, and reverse) into the + * Underscore namespace. In practice, this mixin is done + * by the access view, but doing it here helps keep the + * utility self-contained. + */ + if (_.isUndefined(_s)) { + _s = _.str; + } + _.mixin( _s.exports() ); - edx.utils = edx.utils || {}; + utils = (function(){ + var _fn = { + validate: { - var utils = (function(){ - var _fn = { - validate: { + msg: { + email: '
  • <%- gettext("The email address you\'ve provided isn\'t formatted correctly.") %>
  • ', + min: '
  • <%- _.sprintf( gettext("%(field)s must have at least %(count)d characters."), context ) %>
  • ', + max: '
  • <%- _.sprintf( gettext("%(field)s can only contain up to %(count)d characters."), context ) %>
  • ', + required: '
  • <%- _.sprintf( gettext("Please enter your %(field)s."), context ) %>
  • ', + custom: '
  • <%= content %>
  • ' + }, - msg: { - email: '
  • <%- gettext("The email address you\'ve provided isn\'t formatted correctly.") %>
  • ', - min: '
  • <%- _.sprintf( gettext("%(field)s must have at least %(count)d characters."), context ) %>
  • ', - max: '
  • <%- _.sprintf( gettext("%(field)s can only contain up to %(count)d characters."), context ) %>
  • ', - required: '
  • <%- _.sprintf( gettext("Please enter your %(field)s."), context ) %>
  • ', - custom: '
  • <%= content %>
  • ' - }, + field: function( el ) { + var $el = $(el), + required = true, + min = true, + max = true, + email = true, + response = {}, + isBlank = _fn.validate.isBlank( $el ); - field: function( el ) { - var $el = $(el), - required = true, - min = true, - max = true, - email = true, - response = {}, - isBlank = _fn.validate.isBlank( $el ); - - if ( _fn.validate.isRequired( $el ) ) { - if ( isBlank ) { - required = false; - } else { + if ( _fn.validate.isRequired( $el ) ) { + if ( isBlank ) { + required = false; + } else { + min = _fn.validate.str.minlength( $el ); + max = _fn.validate.str.maxlength( $el ); + email = _fn.validate.email.valid( $el ); + } + } else if ( !isBlank ) { min = _fn.validate.str.minlength( $el ); max = _fn.validate.str.maxlength( $el ); email = _fn.validate.email.valid( $el ); } - } else if ( !isBlank ) { - min = _fn.validate.str.minlength( $el ); - max = _fn.validate.str.maxlength( $el ); - email = _fn.validate.email.valid( $el ); - } - response.isValid = required && min && max && email; + response.isValid = required && min && max && email; - if ( !response.isValid ) { - _fn.validate.removeDefault( $el ); + if ( !response.isValid ) { + _fn.validate.removeDefault( $el ); - response.message = _fn.validate.getMessage( $el, { - required: required, - min: min, - max: max, - email: email - }); - } - - return response; - }, - - str: { - minlength: function( $el ) { - var min = $el.attr('minlength') || 0; - - return min <= $el.val().length; - }, - - maxlength: function( $el ) { - var max = $el.attr('maxlength') || false; - - return ( !!max ) ? max >= $el.val().length : true; - } - }, - - isRequired: function( $el ) { - return $el.attr('required'); - }, - - isBlank: function( $el ) { - var type = $el.attr('type'), - isBlank; - - if ( type === 'checkbox' ) { - isBlank = !$el.prop('checked'); - } else if ( type === 'select' ) { - isBlank = ( $el.data('isdefault') === true ); - } else { - isBlank = !$el.val(); - } - - return isBlank; - }, - - email: { - // This is the same regex used to validate email addresses in Django 1.4 - regex: new RegExp( - [ - '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*', - '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"', - ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)', - '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$' - ].join(''), 'i' - ), - - valid: function( $el ) { - return $el.attr('type') === 'email' ? _fn.validate.email.format( $el.val() ) : true; - }, - - format: function( str ) { - return _fn.validate.email.regex.test( str ); - } - }, - - getLabel: function( id ) { - // Extract the field label, remove the asterisk (if it appears) and any extra whitespace - return $("label[for=" + id + "]").text().split("*")[0].trim(); - }, - - getMessage: function( $el, tests ) { - var txt = [], - tpl, - label, - obj, - customMsg; - - _.each( tests, function( value, key ) { - if ( !value ) { - label = _fn.validate.getLabel( $el.attr('id') ); - customMsg = $el.data('errormsg-' + key) || false; - - // If the field has a custom error msg attached, use it - if ( customMsg ) { - tpl = _fn.validate.msg.custom; - - obj = { - content: customMsg - }; - } else { - tpl = _fn.validate.msg[key]; - - obj = { - // We pass the context object to the template so that - // we can perform variable interpolation using sprintf - context: { - field: label - } - }; - - if ( key === 'min' ) { - obj.context.count = parseInt( $el.attr('minlength'), 10 ); - } else if ( key === 'max' ) { - obj.context.count = parseInt( $el.attr('maxlength'), 10 ); - } - } - - txt.push( _.template( tpl, obj ) ); + response.message = _fn.validate.getMessage( $el, { + required: required, + min: min, + max: max, + email: email + }); } - }); - return txt.join(' '); - }, + return response; + }, - // Removes the default HTML5 validation pop-up - removeDefault: function( $el ) { - if ( $el.setCustomValidity ) { - $el.setCustomValidity(' '); + str: { + minlength: function( $el ) { + var min = $el.attr('minlength') || 0; + + return min <= $el.val().length; + }, + + maxlength: function( $el ) { + var max = $el.attr('maxlength') || false; + + return ( !!max ) ? max >= $el.val().length : true; + } + }, + + isRequired: function( $el ) { + return $el.attr('required'); + }, + + isBlank: function( $el ) { + var type = $el.attr('type'), + isBlank; + + if ( type === 'checkbox' ) { + isBlank = !$el.prop('checked'); + } else if ( type === 'select' ) { + isBlank = ( $el.data('isdefault') === true ); + } else { + isBlank = !$el.val(); + } + + return isBlank; + }, + + email: { + // This is the same regex used to validate email addresses in Django 1.4 + regex: new RegExp( + [ + '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*', + '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"', + ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)', + '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$' + ].join(''), 'i' + ), + + valid: function( $el ) { + return $el.attr('type') === 'email' ? _fn.validate.email.format( $el.val() ) : true; + }, + + format: function( str ) { + return _fn.validate.email.regex.test( str ); + } + }, + + getLabel: function( id ) { + // Extract the field label, remove the asterisk (if it appears) and any extra whitespace + return $("label[for=" + id + "]").text().split("*")[0].trim(); + }, + + getMessage: function( $el, tests ) { + var txt = [], + tpl, + label, + obj, + customMsg; + + _.each( tests, function( value, key ) { + if ( !value ) { + label = _fn.validate.getLabel( $el.attr('id') ); + customMsg = $el.data('errormsg-' + key) || false; + + // If the field has a custom error msg attached, use it + if ( customMsg ) { + tpl = _fn.validate.msg.custom; + + obj = { + content: customMsg + }; + } else { + tpl = _fn.validate.msg[key]; + + obj = { + // We pass the context object to the template so that + // we can perform variable interpolation using sprintf + context: { + field: label + } + }; + + if ( key === 'min' ) { + obj.context.count = parseInt( $el.attr('minlength'), 10 ); + } else if ( key === 'max' ) { + obj.context.count = parseInt( $el.attr('maxlength'), 10 ); + } + } + + txt.push( _.template( tpl, obj ) ); + } + }); + + return txt.join(' '); + }, + + // Removes the default HTML5 validation pop-up + removeDefault: function( $el ) { + if ( $el.setCustomValidity ) { + $el.setCustomValidity(' '); + } } } - } - }; + }; - return { - validate: _fn.validate.field - }; + return { + validate: _fn.validate.field + }; - })(); + })(); - edx.utils.validate = utils.validate; - -})( jQuery, _, _.str, gettext ); + return utils; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index a845414562..b2f00de285 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -8,7 +8,6 @@ import json import mock import ddt -import markupsafe from django.conf import settings from django.core.urlresolvers import reverse from django.core import mail @@ -20,6 +19,7 @@ from django.test.client import RequestFactory from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH +from openedx.core.lib.json_utils import EscapedEdxJSONEncoder from student.tests.factories import UserFactory from student_account.views import account_settings_context from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin @@ -223,7 +223,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi @ddt.unpack def test_login_and_registration_form(self, url_name, initial_mode): response = self.client.get(reverse(url_name)) - expected_data = u"data-initial-mode=\"{mode}\"".format(mode=initial_mode) + expected_data = '"initial_mode": "{mode}"'.format(mode=initial_mode) self.assertContains(response, expected_data) @ddt.data("signin_user", "register_user") @@ -255,6 +255,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi # that preserves the querystring params with mock.patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': is_edx_domain}): response = self.client.get(reverse(url_name), params) + expected_url = '/login?{}'.format(self._finish_auth_url_param(params + [('next', '/dashboard')])) self.assertContains(response, expected_url) @@ -330,7 +331,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi def test_hinted_login(self): params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")] response = self.client.get(reverse('signin_user'), params) - self.assertContains(response, "data-third-party-auth-hint='oa2-google-oauth2'") + self.assertContains(response, '"third_party_auth_hint": "oa2-google-oauth2"') @override_settings(SITE_NAME=settings.MICROSITE_TEST_HOSTNAME) def test_microsite_uses_old_login_page(self): @@ -358,17 +359,17 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi finish_auth_url = None if current_backend: finish_auth_url = reverse("social:complete", kwargs={"backend": current_backend}) + "?" - auth_info = markupsafe.escape( - json.dumps({ - "currentProvider": current_provider, - "providers": providers, - "secondaryProviders": [], - "finishAuthUrl": finish_auth_url, - "errorMessage": None, - }) - ) - expected_data = u"data-third-party-auth='{auth_info}'".format( + auth_info = { + "currentProvider": current_provider, + "providers": providers, + "secondaryProviders": [], + "finishAuthUrl": finish_auth_url, + "errorMessage": None, + } + auth_info = json.dumps(auth_info, cls=EscapedEdxJSONEncoder) + + expected_data = '"third_party_auth": {auth_info}'.format( auth_info=auth_info ) diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 706ffaa94b..62094945b7 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -95,22 +95,25 @@ def login_and_registration_form(request, initial_mode="login"): # Otherwise, render the combined login/registration page context = { - 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header - 'disable_courseware_js': True, - 'initial_mode': initial_mode, - 'third_party_auth': json.dumps(_third_party_auth_context(request, redirect_to)), - 'third_party_auth_hint': third_party_auth_hint or '', - 'platform_name': settings.PLATFORM_NAME, + 'data': { + 'login_redirect_url': redirect_to, + 'initial_mode': initial_mode, + 'third_party_auth': _third_party_auth_context(request, redirect_to), + 'third_party_auth_hint': third_party_auth_hint or '', + 'platform_name': settings.PLATFORM_NAME, + + # Include form descriptions retrieved from the user API. + # We could have the JS client make these requests directly, + # but we include them in the initial page load to avoid + # the additional round-trip to the server. + 'login_form_desc': json.loads(form_descriptions['login']), + 'registration_form_desc': json.loads(form_descriptions['registration']), + 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), + }, + 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, - - # Include form descriptions retrieved from the user API. - # We could have the JS client make these requests directly, - # but we include them in the initial page load to avoid - # the additional round-trip to the server. - 'login_form_desc': form_descriptions['login'], - 'registration_form_desc': form_descriptions['registration'], - 'password_reset_form_desc': form_descriptions['password_reset'], + 'disable_courseware_js': True, } return render_to_response('student_account/login_and_register.html', context) diff --git a/lms/envs/common.py b/lms/envs/common.py index c1ebcfbb04..a7c42abea3 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1303,26 +1303,6 @@ instructor_dash_js = ( sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js')) ) -# JavaScript used by the student account and profile pages -# These are not courseware, so they do not need many of the courseware-specific -# JavaScript modules. -student_account_js = [ - 'js/utils/edx.utils.validate.js', - 'js/sticky_filter.js', - 'js/query-params.js', - 'js/student_account/models/LoginModel.js', - 'js/student_account/models/RegisterModel.js', - 'js/student_account/models/PasswordResetModel.js', - 'js/student_account/views/FormView.js', - 'js/student_account/views/LoginView.js', - 'js/student_account/views/HintedLoginView.js', - 'js/student_account/views/RegisterView.js', - 'js/student_account/views/PasswordResetView.js', - 'js/student_account/views/AccessView.js', - 'js/student_account/views/InstitutionLoginView.js', - 'js/student_account/accessApp.js', -] - verify_student_js = [ 'js/sticky_filter.js', 'js/query-params.js', @@ -1574,10 +1554,6 @@ PIPELINE_JS = { 'source_filenames': dashboard_js, 'output_filename': 'js/dashboard.js' }, - 'student_account': { - 'source_filenames': student_account_js, - 'output_filename': 'js/student_account.js' - }, 'verify_student': { 'source_filenames': verify_student_js, 'output_filename': 'js/verify_student.js' diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index cb97519c78..76b2d32a63 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -75,16 +75,6 @@ 'js/views/file_uploader': 'js/views/file_uploader', 'js/views/notification': 'js/views/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/views/InstitutionLoginView': 'js/student_account/views/InstitutionLoginView', - '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_account/views/HintedLoginView': 'js/student_account/views/HintedLoginView', 'js/student_profile/views/learner_profile_fields': 'js/student_profile/views/learner_profile_fields', 'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory', 'js/student_profile/views/learner_profile_view': 'js/student_profile/views/learner_profile_view', @@ -94,7 +84,10 @@ 'DiscussionModuleView': 'xmodule_js/common_static/coffee/src/discussion/discussion_module_view', // edxnotes - 'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min' + 'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min', + + // Common edx utils + 'js/utils/edx.utils.validate': 'xmodule_js/common_static/js/utils/edx.utils.validate' }, shim: { 'gettext': { @@ -337,91 +330,6 @@ 'js/models/notification', 'jquery.fileupload' ] }, - // Student account registration/login - // Loaded explicitly until these are converted to RequireJS - 'js/student_account/views/FormView': { - exports: 'edx.student.account.FormView', - deps: ['jquery', 'underscore', 'backbone', 'gettext'] - }, - 'js/student_account/models/LoginModel': { - exports: 'edx.student.account.LoginModel', - deps: ['jquery', 'jquery.cookie', 'backbone'] - }, - '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' - ] - }, - 'js/student_account/views/InstitutionLoginView': { - exports: 'edx.student.account.InstitutionLoginView', - deps: [ - 'jquery', - 'underscore', - 'backbone' - ] - }, - 'js/student_account/models/PasswordResetModel': { - exports: 'edx.student.account.PasswordResetModel', - deps: ['jquery', 'jquery.cookie', 'backbone'] - }, - '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: 'edx.student.account.RegisterModel', - deps: ['jquery', 'jquery.cookie', 'backbone'] - }, - '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' - ] - }, - 'js/student_account/views/HintedLoginView': { - exports: 'edx.student.account.HintedLoginView', - deps: [ - 'jquery', - 'underscore', - 'backbone', - 'gettext' - ] - }, - 'js/student_account/views/AccessView': { - exports: 'edx.student.account.AccessView', - deps: [ - 'jquery', - 'underscore', - 'backbone', - 'history', - 'utility', - 'js/student_account/views/LoginView', - 'js/student_account/views/PasswordResetView', - 'js/student_account/views/RegisterView', - 'js/student_account/views/InstitutionLoginView', - 'js/student_account/models/LoginModel', - 'js/student_account/models/PasswordResetModel', - 'js/student_account/models/RegisterModel', - 'js/student_account/views/FormView' - ] - }, 'js/verify_student/models/verification_model': { exports: 'edx.verify_student.VerificationModel', deps: [ 'jquery', 'underscore', 'backbone', 'jquery.cookie' ] @@ -731,6 +639,7 @@ 'lms/include/js/spec/instructor_dashboard/student_admin_spec.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/logistration_factory_spec.js', 'lms/include/js/spec/student_account/finish_auth_spec.js', 'lms/include/js/spec/student_account/hinted_login_spec.js', 'lms/include/js/spec/student_account/login_spec.js', diff --git a/lms/static/js/spec/student_account/access_spec.js b/lms/static/js/spec/student_account/access_spec.js index 590c9df58f..88a73c1506 100644 --- a/lms/static/js/spec/student_account/access_spec.js +++ b/lms/static/js/spec/student_account/access_spec.js @@ -1,14 +1,20 @@ -define([ - 'jquery', - 'common/js/spec_helpers/template_helpers', - 'common/js/spec_helpers/ajax_helpers', - 'js/student_account/views/AccessView', - 'js/student_account/views/FormView', - 'js/student_account/enrollment', - 'js/student_account/shoppingcart', - 'js/student_account/emailoptin' -], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) { - "use strict"; +;(function (define) { + 'use strict'; + define([ + 'jquery', + 'underscore', + 'backbone', + 'common/js/spec_helpers/template_helpers', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/views/AccessView', + 'js/student_account/views/FormView', + 'js/student_account/enrollment', + 'js/student_account/shoppingcart', + 'js/student_account/emailoptin' + ], + function($, _, Backbone, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, + ShoppingCartInterface) { + describe('edx.student.account.AccessView', function() { var requests = null, view = null, @@ -24,7 +30,7 @@ define([ required: true, placeholder: 'xsy@edx.org', instructions: 'Enter your email here.', - restrictions: {}, + restrictions: {} }, { name: 'username', @@ -49,24 +55,27 @@ define([ THIRD_PARTY_COMPLETE_URL = '/auth/complete/provider/'; var ajaxSpyAndInitialize = function(that, mode, nextUrl, finishAuthUrl) { + var options = { + initial_mode: mode, + third_party_auth: { + currentProvider: null, + providers: [], + secondaryProviders: [{name: "provider"}], + finishAuthUrl: finishAuthUrl + }, + login_redirect_url: nextUrl, // undefined for default + platform_name: 'edX', + login_form_desc: FORM_DESCRIPTION, + registration_form_desc: FORM_DESCRIPTION, + password_reset_form_desc: FORM_DESCRIPTION + }, + $logistrationElement = $('#login-and-registration-container'); + // Spy on AJAX requests requests = AjaxHelpers.requests(that); // Initialize the access view - view = new AccessView({ - mode: mode, - thirdPartyAuth: { - currentProvider: null, - providers: [], - secondaryProviders: [{name: "provider"}], - finishAuthUrl: finishAuthUrl - }, - nextUrl: nextUrl, // undefined for default - platformName: 'edX', - loginFormDesc: FORM_DESCRIPTION, - registrationFormDesc: FORM_DESCRIPTION, - passwordResetFormDesc: FORM_DESCRIPTION - }); + view = new AccessView(_.extend(options, {el: $logistrationElement})); // Mock the redirect call spyOn( view, 'redirect' ).andCallFake( function() {} ); @@ -92,7 +101,7 @@ define([ }; beforeEach(function() { - setFixtures('
    '); + setFixtures('
    '); TemplateHelpers.installTemplate('templates/student_account/access'); TemplateHelpers.installTemplate('templates/student_account/login'); TemplateHelpers.installTemplate('templates/student_account/register'); @@ -105,6 +114,10 @@ define([ window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'pageview', 'trackLink']); }); + afterEach(function() { + Backbone.history.stop(); + }); + it('can initially display the login form', function() { ajaxSpyAndInitialize(this, 'login'); @@ -217,5 +230,5 @@ define([ }); }); - } -); + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/student_account/finish_auth_spec.js b/lms/static/js/spec/student_account/finish_auth_spec.js index 1dd9e4dece..f29291747b 100644 --- a/lms/static/js/spec/student_account/finish_auth_spec.js +++ b/lms/static/js/spec/student_account/finish_auth_spec.js @@ -1,13 +1,18 @@ -define([ - 'jquery', - 'utility', - 'common/js/spec_helpers/ajax_helpers', - 'js/student_account/views/FinishAuthView', - 'js/student_account/enrollment', - 'js/student_account/shoppingcart', - 'js/student_account/emailoptin' -], function($, utility, AjaxHelpers, FinishAuthView, EnrollmentInterface, ShoppingCartInterface, EmailOptInInterface) { - 'use strict'; +;(function (define) { + 'use strict'; + define([ + 'jquery', + 'jquery.url', + 'utility', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/views/FinishAuthView', + 'js/student_account/enrollment', + 'js/student_account/shoppingcart', + 'js/student_account/emailoptin' + ], + function($, url, utility, AjaxHelpers, FinishAuthView, EnrollmentInterface, ShoppingCartInterface, + EmailOptInInterface) { + describe('FinishAuthView', function() { var requests = null, view = null, @@ -167,5 +172,5 @@ define([ expect( view.redirect ).toHaveBeenCalledWith( "/dashboard" ); }); }); - } -); + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/student_account/hinted_login_spec.js b/lms/static/js/spec/student_account/hinted_login_spec.js index a03f036722..2a751b32d2 100644 --- a/lms/static/js/spec/student_account/hinted_login_spec.js +++ b/lms/static/js/spec/student_account/hinted_login_spec.js @@ -1,87 +1,90 @@ -define([ - 'jquery', - 'underscore', - 'common/js/spec_helpers/template_helpers', - 'common/js/spec_helpers/ajax_helpers', - 'js/student_account/views/HintedLoginView', -], function($, _, TemplateHelpers, AjaxHelpers, HintedLoginView) { +;(function (define) { 'use strict'; - describe('edx.student.account.HintedLoginView', function() { + define([ + 'jquery', + 'underscore', + 'common/js/spec_helpers/template_helpers', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/views/HintedLoginView' + ], + function($, _, TemplateHelpers, AjaxHelpers, HintedLoginView) { - var view = null, - requests = null, - PLATFORM_NAME = 'edX', - THIRD_PARTY_AUTH = { - currentProvider: null, - providers: [ - { - id: 'oa2-google-oauth2', - name: 'Google', - iconClass: 'fa-google-plus', - loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', - registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' - }, - { - id: 'oa2-facebook', - name: 'Facebook', - iconClass: 'fa-facebook', - loginUrl: '/auth/login/facebook/?auth_entry=account_login', - registerUrl: '/auth/login/facebook/?auth_entry=account_register' - } - ], - secondaryProviders: [ - { - id: 'saml-harvard', - name: 'Harvard', - iconClass: 'fa-university', - loginUrl: '/auth/login/tpa-saml/?auth_entry=account_login&idp=harvard', - registerUrl: '/auth/login/tpa-saml/?auth_entry=account_register&idp=harvard' - } - ] + describe('edx.student.account.HintedLoginView', function() { + var view = null, + requests = null, + PLATFORM_NAME = 'edX', + THIRD_PARTY_AUTH = { + currentProvider: null, + providers: [ + { + id: 'oa2-google-oauth2', + name: 'Google', + iconClass: 'fa-google-plus', + loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', + registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' + }, + { + id: 'oa2-facebook', + name: 'Facebook', + iconClass: 'fa-facebook', + loginUrl: '/auth/login/facebook/?auth_entry=account_login', + registerUrl: '/auth/login/facebook/?auth_entry=account_register' + } + ], + secondaryProviders: [ + { + id: 'saml-harvard', + name: 'Harvard', + iconClass: 'fa-university', + loginUrl: '/auth/login/tpa-saml/?auth_entry=account_login&idp=harvard', + registerUrl: '/auth/login/tpa-saml/?auth_entry=account_register&idp=harvard' + } + ] + }; + + var createHintedLoginView = function(hintedProvider) { + // Initialize the login view + view = new HintedLoginView({ + thirdPartyAuth: THIRD_PARTY_AUTH, + hintedProvider: hintedProvider, + platformName: PLATFORM_NAME + }); + + // Mock the redirect call + spyOn( view, 'redirect' ).andCallFake( function() {} ); + + view.render(); }; - var createHintedLoginView = function(hintedProvider) { - // Initialize the login view - view = new HintedLoginView({ - thirdPartyAuth: THIRD_PARTY_AUTH, - hintedProvider: hintedProvider, - platformName: PLATFORM_NAME + beforeEach(function() { + setFixtures('
    '); + TemplateHelpers.installTemplate('templates/student_account/hinted_login'); }); - // Mock the redirect call - spyOn( view, 'redirect' ).andCallFake( function() {} ); + it('displays a choice as two buttons', function() { + createHintedLoginView("oa2-google-oauth2"); - view.render(); - }; + expect($('.proceed-button.button-oa2-google-oauth2')).toBeVisible(); + expect($('.form-toggle')).toBeVisible(); + expect($('.proceed-button.button-oa2-facebook')).not.toBeVisible(); + }); - beforeEach(function() { - setFixtures('
    '); - TemplateHelpers.installTemplate('templates/student_account/hinted_login'); - }); + it('works with secondary providers as well', function() { + createHintedLoginView("saml-harvard"); - it('displays a choice as two buttons', function() { - createHintedLoginView("oa2-google-oauth2"); + expect($('.proceed-button.button-saml-harvard')).toBeVisible(); + expect($('.form-toggle')).toBeVisible(); + expect($('.proceed-button.button-oa2-google-oauth2')).not.toBeVisible(); + }); - expect($('.proceed-button.button-oa2-google-oauth2')).toBeVisible(); - expect($('.form-toggle')).toBeVisible(); - expect($('.proceed-button.button-oa2-facebook')).not.toBeVisible(); - }); + it('redirects the user to the hinted provider if the user clicks the proceed button', function() { + createHintedLoginView('oa2-google-oauth2'); - it('works with secondary providers as well', function() { - createHintedLoginView("saml-harvard"); + // Click the "Yes, proceed" button + $('.proceed-button').click(); - expect($('.proceed-button.button-saml-harvard')).toBeVisible(); - expect($('.form-toggle')).toBeVisible(); - expect($('.proceed-button.button-oa2-google-oauth2')).not.toBeVisible(); - }); - - it('redirects the user to the hinted provider if the user clicks the proceed button', function() { - createHintedLoginView("oa2-google-oauth2"); - - // Click the "Yes, proceed" button - $('.proceed-button').click(); - - expect(view.redirect).toHaveBeenCalledWith( '/auth/login/google-oauth2/?auth_entry=account_login' ); + expect(view.redirect).toHaveBeenCalledWith( '/auth/login/google-oauth2/?auth_entry=account_login' ); + }); }); }); -}); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/student_account/institution_login_spec.js b/lms/static/js/spec/student_account/institution_login_spec.js index 208c975550..d087509ecc 100644 --- a/lms/static/js/spec/student_account/institution_login_spec.js +++ b/lms/static/js/spec/student_account/institution_login_spec.js @@ -1,80 +1,87 @@ -define([ - 'jquery', - 'underscore', - 'common/js/spec_helpers/template_helpers', - 'js/student_account/views/InstitutionLoginView', -], function($, _, TemplateHelpers, InstitutionLoginView) { +;(function (define) { 'use strict'; - describe('edx.student.account.InstitutionLoginView', function() { + define([ + 'jquery', + 'underscore', + 'common/js/spec_helpers/template_helpers', + 'js/student_account/views/InstitutionLoginView' + ], + function($, _, TemplateHelpers, InstitutionLoginView) { - var view = null, - PLATFORM_NAME = 'edX', - THIRD_PARTY_AUTH = { - currentProvider: null, - providers: [], - secondaryProviders: [ - { - id: 'oa2-google-oauth2', - name: 'Google', - iconClass: 'fa-google-plus', - loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', - registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' - }, - { - id: 'oa2-facebook', - name: 'Facebook', - iconClass: 'fa-facebook', - loginUrl: '/auth/login/facebook/?auth_entry=account_login', - registerUrl: '/auth/login/facebook/?auth_entry=account_register' - } - ] + describe('edx.student.account.InstitutionLoginView', function() { + var view = null, + PLATFORM_NAME = 'edX', + THIRD_PARTY_AUTH = { + currentProvider: null, + providers: [], + secondaryProviders: [ + { + id: 'oa2-google-oauth2', + name: 'Google', + iconClass: 'fa-google-plus', + loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', + registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' + }, + { + id: 'oa2-facebook', + name: 'Facebook', + iconClass: 'fa-facebook', + loginUrl: '/auth/login/facebook/?auth_entry=account_login', + registerUrl: '/auth/login/facebook/?auth_entry=account_register' + } + ] + }; + + var createInstLoginView = function(mode) { + // Initialize the login view + view = new InstitutionLoginView({ + mode: mode, + thirdPartyAuth: THIRD_PARTY_AUTH, + platformName: PLATFORM_NAME + }); + view.render(); }; - var createInstLoginView = function(mode) { - // Initialize the login view - view = new InstitutionLoginView({ - mode: mode, - thirdPartyAuth: THIRD_PARTY_AUTH, - platformName: PLATFORM_NAME + beforeEach(function() { + setFixtures('
    '); + TemplateHelpers.installTemplate('templates/student_account/institution_login'); + TemplateHelpers.installTemplate('templates/student_account/institution_register'); + }); + + it('displays a list of providers', function() { + var $google, $facebook; + + createInstLoginView('login'); + expect($('#institution_login-form').html()).not.toBe(""); + $google = $('li a:contains("Google")'); + expect($google).toBeVisible(); + expect($google).toHaveAttr( + 'href', '/auth/login/google-oauth2/?auth_entry=account_login' + ); + $facebook = $('li a:contains("Facebook")'); + expect($facebook).toBeVisible(); + expect($facebook).toHaveAttr( + 'href', '/auth/login/facebook/?auth_entry=account_login' + ); + }); + + it('displays a list of providers', function() { + var $google, $facebook; + + createInstLoginView('register'); + expect($('#institution_login-form').html()).not.toBe(""); + $google = $('li a:contains("Google")'); + expect($google).toBeVisible(); + expect($google).toHaveAttr( + 'href', '/auth/login/google-oauth2/?auth_entry=account_register' + ); + $facebook = $('li a:contains("Facebook")'); + expect($facebook).toBeVisible(); + expect($facebook).toHaveAttr( + 'href', '/auth/login/facebook/?auth_entry=account_register' + ); }); - view.render(); - }; - beforeEach(function() { - setFixtures('
    '); - TemplateHelpers.installTemplate('templates/student_account/institution_login'); - TemplateHelpers.installTemplate('templates/student_account/institution_register'); }); - - it('displays a list of providers', function() { - createInstLoginView('login'); - expect($('#institution_login-form').html()).not.toBe(""); - var $google = $('li a:contains("Google")'); - expect($google).toBeVisible(); - expect($google).toHaveAttr( - 'href', '/auth/login/google-oauth2/?auth_entry=account_login' - ); - var $facebook = $('li a:contains("Facebook")'); - expect($facebook).toBeVisible(); - expect($facebook).toHaveAttr( - 'href', '/auth/login/facebook/?auth_entry=account_login' - ); - }); - - it('displays a list of providers', function() { - createInstLoginView('register'); - expect($('#institution_login-form').html()).not.toBe(""); - var $google = $('li a:contains("Google")'); - expect($google).toBeVisible(); - expect($google).toHaveAttr( - 'href', '/auth/login/google-oauth2/?auth_entry=account_register' - ); - var $facebook = $('li a:contains("Facebook")'); - expect($facebook).toBeVisible(); - expect($facebook).toHaveAttr( - 'href', '/auth/login/facebook/?auth_entry=account_register' - ); - }); - }); -}); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/student_account/login_spec.js b/lms/static/js/spec/student_account/login_spec.js index d61f49be87..f66a4d60e1 100644 --- a/lms/static/js/spec/student_account/login_spec.js +++ b/lms/static/js/spec/student_account/login_spec.js @@ -1,265 +1,270 @@ -define([ - 'jquery', - 'underscore', - 'common/js/spec_helpers/template_helpers', - 'common/js/spec_helpers/ajax_helpers', - 'js/student_account/models/LoginModel', - 'js/student_account/views/LoginView', - 'js/student_account/models/PasswordResetModel' -], function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView, PasswordResetModel) { +;(function (define) { 'use strict'; - describe('edx.student.account.LoginView', function() { + define([ + 'jquery', + 'underscore', + 'common/js/spec_helpers/template_helpers', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/models/LoginModel', + 'js/student_account/views/LoginView', + 'js/student_account/models/PasswordResetModel' + ], + function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView, PasswordResetModel) { - var model = null, - resetModel = null, - view = null, - requests = null, - authComplete = false, - PLATFORM_NAME = 'edX', - USER_DATA = { - email: 'xsy@edx.org', - password: 'xsyisawesome', - remember: true - }, - THIRD_PARTY_AUTH = { - currentProvider: null, - providers: [ - { - id: 'oa2-google-oauth2', - name: 'Google', - iconClass: 'fa-google-plus', - loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', - registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' - }, - { - id: 'oa2-facebook', - name: 'Facebook', - iconClass: 'fa-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: [ - { - placeholder: 'username@domain.com', - name: 'email', - label: 'Email', - defaultValue: '', - type: 'email', - required: true, - instructions: 'Enter your email.', - restrictions: {} - }, - { - placeholder: '', - name: 'password', - label: 'Password', - defaultValue: '', - type: 'password', - required: true, - instructions: 'Enter your password.', - restrictions: {} - }, - { - placeholder: '', - name: 'remember', - label: 'Remember me', - defaultValue: '', - type: 'checkbox', - required: true, - instructions: "Agree to the terms of service.", - restrictions: {} - } - ] - }, - COURSE_ID = "edX/demoX/Fall"; + describe('edx.student.account.LoginView', function() { + var model = null, + resetModel = null, + view = null, + requests = null, + authComplete = false, + PLATFORM_NAME = 'edX', + USER_DATA = { + email: 'xsy@edx.org', + password: 'xsyisawesome', + remember: true + }, + THIRD_PARTY_AUTH = { + currentProvider: null, + providers: [ + { + id: 'oa2-google-oauth2', + name: 'Google', + iconClass: 'fa-google-plus', + loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', + registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' + }, + { + id: 'oa2-facebook', + name: 'Facebook', + iconClass: 'fa-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: [ + { + placeholder: 'username@domain.com', + name: 'email', + label: 'Email', + defaultValue: '', + type: 'email', + required: true, + instructions: 'Enter your email.', + restrictions: {} + }, + { + placeholder: '', + name: 'password', + label: 'Password', + defaultValue: '', + type: 'password', + required: true, + instructions: 'Enter your password.', + restrictions: {} + }, + { + placeholder: '', + name: 'remember', + label: 'Remember me', + defaultValue: '', + type: 'checkbox', + required: true, + instructions: 'Agree to the terms of service.', + restrictions: {} + } + ] + }, + COURSE_ID = 'edX/demoX/Fall'; - var createLoginView = function(test) { - // Initialize the login model - model = new LoginModel({}, { - url: FORM_DESCRIPTION.submit_url, - method: FORM_DESCRIPTION.method - }); - - // Initialize the passwordReset model - resetModel = new PasswordResetModel({}, { - method: 'GET', - url: '#' - }); - - // Initialize the login view - view = new LoginView({ - fields: FORM_DESCRIPTION.fields, - model: model, - resetModel: resetModel, - thirdPartyAuth: THIRD_PARTY_AUTH, - platformName: PLATFORM_NAME - }); - - // Spy on AJAX requests - requests = AjaxHelpers.requests(test); - - // Intercept events from the view - authComplete = false; - view.on("auth-complete", function() { - authComplete = 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.' + var createLoginView = function(test) { + // Initialize the login model + model = new LoginModel({}, { + url: FORM_DESCRIPTION.submit_url, + method: FORM_DESCRIPTION.method }); - } - // Submit the email address - view.submitForm(clickEvent); - }; + // Initialize the passwordReset model + resetModel = new PasswordResetModel({}, { + method: 'GET', + url: '#' + }); - beforeEach(function() { - setFixtures('
    '); - TemplateHelpers.installTemplate('templates/student_account/login'); - TemplateHelpers.installTemplate('templates/student_account/form_field'); - }); + // Initialize the login view + view = new LoginView({ + fields: FORM_DESCRIPTION.fields, + model: model, + resetModel: resetModel, + thirdPartyAuth: THIRD_PARTY_AUTH, + platformName: PLATFORM_NAME + }); - it('logs the user in', function() { - createLoginView(this); + // Spy on AJAX requests + requests = AjaxHelpers.requests(test); - // Submit the form, with successful validation - submitForm(true); + // Intercept events from the view + authComplete = false; + view.on("auth-complete", function() { + authComplete = true; + }); + }; - // Form button should be disabled on success. - expect(view.$submitButton).toHaveAttr('disabled'); + var submitForm = function(validationSuccess) { + // Create a fake click event + var clickEvent = $.Event('click'); - // Verify that the client contacts the server with the expected data - AjaxHelpers.expectRequest( - requests, 'POST', - FORM_DESCRIPTION.submit_url, - $.param(USER_DATA) - ); + // Simulate manual entry of login form data + $('#login-email').val(USER_DATA.email); + $('#login-password').val(USER_DATA.password); - // Respond with status code 200 - AjaxHelpers.respondWithJson(requests, {}); + // Check the 'Remember me' checkbox + $('#login-remember').prop('checked', USER_DATA.remember); - // Verify that auth-complete is triggered - expect(authComplete).toBe(true); - }); - - it('sends analytics info containing the enrolled course ID', function() { - createLoginView(this); - - // Simulate that the user is attempting to enroll in a course - // by setting the course_id query string param. - spyOn($, 'url').andCallFake(function( param ) { - if (param === "?course_id") { - return encodeURIComponent( COURSE_ID ); + // 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'); + TemplateHelpers.installTemplate('templates/student_account/form_field'); }); - // Attempt to login - submitForm( true ); + it('logs the user in', function() { + createLoginView(this); - // Verify that the client sent the course ID for analytics - var expectedData = {}; - $.extend(expectedData, USER_DATA, { - analytics: JSON.stringify({ - enroll_course_id: COURSE_ID - }) + // Submit the form, with successful validation + submitForm(true); + + // Form button should be disabled on success. + expect(view.$submitButton).toHaveAttr('disabled'); + + // Verify that the client contacts the server with the expected data + AjaxHelpers.expectRequest( + requests, 'POST', + FORM_DESCRIPTION.submit_url, + $.param(USER_DATA) + ); + + // Respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Verify that auth-complete is triggered + expect(authComplete).toBe(true); }); - AjaxHelpers.expectRequest( - requests, 'POST', - FORM_DESCRIPTION.submit_url, - $.param( expectedData ) - ); - }); + it('sends analytics info containing the enrolled course ID', function() { + var expectedData; - it('displays third-party auth login buttons', function() { - createLoginView(this); + createLoginView(this); - // Verify that Google and Facebook registration buttons are displayed - expect($('.button-oa2-google-oauth2')).toBeVisible(); - expect($('.button-oa2-facebook')).toBeVisible(); - }); + // Simulate that the user is attempting to enroll in a course + // by setting the course_id query string param. + spyOn($, 'url').andCallFake(function( param ) { + if (param === '?course_id') { + return encodeURIComponent( COURSE_ID ); + } + }); - it('displays a link to the password reset form', function() { - createLoginView(this); + // Attempt to login + submitForm( true ); - // Verify that the password reset link is displayed - expect($('.forgot-password')).toBeVisible(); - }); + // Verify that the client sent the course ID for analytics + expectedData = {}; + $.extend(expectedData, USER_DATA, { + analytics: JSON.stringify({ + enroll_course_id: COURSE_ID + }) + }); - it('validates login form fields', function() { - createLoginView(this); + AjaxHelpers.expectRequest( + requests, 'POST', + FORM_DESCRIPTION.submit_url, + $.param( expectedData ) + ); + }); - submitForm(true); + it('displays third-party auth login buttons', function() { + createLoginView(this); - // Verify that validation of form fields occurred - expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]); - expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]); - }); + // Verify that Google and Facebook registration buttons are displayed + expect($('.button-oa2-google-oauth2')).toBeVisible(); + expect($('.button-oa2-facebook')).toBeVisible(); + }); - it('displays login form validation errors', function() { - createLoginView(this); + it('displays a link to the password reset form', function() { + createLoginView(this); - // Submit the form, with failed validation - submitForm(false); + // Verify that the password reset link is displayed + expect($('.forgot-password')).toBeVisible(); + }); - // Verify that submission errors are visible - expect(view.$errors).not.toHaveClass('hidden'); + it('validates login form fields', function() { + createLoginView(this); - // Expect auth complete NOT to have been triggered - expect(authComplete).toBe(false); - // Form button should be re-enabled when errors occur - expect(view.$submitButton).not.toHaveAttr('disabled'); - }); + submitForm(true); - it('displays an error if the server returns an error while logging in', function() { - createLoginView(this); + // Verify that validation of form fields occurred + expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]); + expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]); + }); - // Submit the form, with successful validation - submitForm(true); + it('displays login form validation errors', function() { + createLoginView(this); - // Simulate an error from the LMS servers - AjaxHelpers.respondWithError(requests); + // Submit the form, with failed validation + submitForm(false); - // Expect that an error is displayed and that auth complete is not triggered - expect(view.$errors).not.toHaveClass('hidden'); - expect(authComplete).toBe(false); - // Form button should be re-enabled on server failure. - expect(view.$submitButton).not.toHaveAttr('disabled'); + // Verify that submission errors are visible + expect(view.$errors).not.toHaveClass('hidden'); - // If we try again and succeed, the error should go away - submitForm(); + // Expect auth complete NOT to have been triggered + expect(authComplete).toBe(false); + // Form button should be re-enabled when errors occur + expect(view.$submitButton).not.toHaveAttr('disabled'); + }); - // Form button should be disabled on success. - expect(view.$submitButton).toHaveAttr('disabled'); + it('displays an error if the server returns an error while logging in', function() { + createLoginView(this); - // This time, respond with status code 200 - AjaxHelpers.respondWithJson(requests, {}); + // Submit the form, with successful validation + submitForm(true); - // Expect that the error is hidden and auth complete is triggered - expect(view.$errors).toHaveClass('hidden'); - expect(authComplete).toBe(true); + // Simulate an error from the LMS servers + AjaxHelpers.respondWithError(requests); + + // Expect that an error is displayed and that auth complete is not triggered + expect(view.$errors).not.toHaveClass('hidden'); + expect(authComplete).toBe(false); + // Form button should be re-enabled on server failure. + expect(view.$submitButton).not.toHaveAttr('disabled'); + + // If we try again and succeed, the error should go away + submitForm(); + + // Form button should be disabled on success. + expect(view.$submitButton).toHaveAttr('disabled'); + + // This time, respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); + + // Expect that the error is hidden and auth complete is triggered + expect(view.$errors).toHaveClass('hidden'); + expect(authComplete).toBe(true); + }); }); }); -}); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/student_account/logistration_factory_spec.js b/lms/static/js/spec/student_account/logistration_factory_spec.js new file mode 100644 index 0000000000..8ccc66a436 --- /dev/null +++ b/lms/static/js/spec/student_account/logistration_factory_spec.js @@ -0,0 +1,130 @@ +;(function (define) { + 'use strict'; + define([ + 'jquery', + 'underscore', + 'backbone', + 'common/js/spec_helpers/template_helpers', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/logistration_factory' + ], + function($, _, Backbone, TemplateHelpers, AjaxHelpers, LogistrationFactory) { + + describe('Logistration Factory', function() { + var FORM_DESCRIPTION = { + method: 'post', + submit_url: '/submit', + fields: [ + { + name: 'email', + label: 'Email', + defaultValue: '', + type: 'text', + required: true, + placeholder: 'xsy@edx.org', + instructions: 'Enter your email here.', + restrictions: {} + }, + { + name: 'username', + label: 'Username', + defaultValue: '', + type: 'text', + required: true, + placeholder: 'Xsy', + instructions: 'Enter your username here.', + restrictions: { + max_length: 200 + } + } + ] + }; + + var initializeLogistrationFactory = function(that, mode, nextUrl, finishAuthUrl) { + var options = { + initial_mode: mode, + third_party_auth: { + currentProvider: null, + providers: [], + secondaryProviders: [{name: 'provider'}], + finishAuthUrl: finishAuthUrl + }, + login_redirect_url: nextUrl, // undefined for default + platform_name: 'edX', + login_form_desc: FORM_DESCRIPTION, + registration_form_desc: FORM_DESCRIPTION, + password_reset_form_desc: FORM_DESCRIPTION + }; + + // Initialize the logistration Factory + LogistrationFactory(options); + }; + + var assertForms = function(visibleForm, hiddenFormsList) { + expect($(visibleForm)).not.toHaveClass('hidden'); + + _.each(hiddenFormsList, function (hiddenForm) { + expect($(hiddenForm)).toHaveClass('hidden'); + }, this); + }; + + beforeEach(function() { + setFixtures('
    '); + TemplateHelpers.installTemplate('templates/student_account/access'); + TemplateHelpers.installTemplate('templates/student_account/form_field'); + TemplateHelpers.installTemplate('templates/student_account/login'); + TemplateHelpers.installTemplate('templates/student_account/register'); + TemplateHelpers.installTemplate('templates/student_account/password_reset') + }); + + afterEach(function() { + Backbone.history.stop(); + }); + + it('can initially render the login form', function() { + var hiddenFormsList; + + initializeLogistrationFactory(this, 'login'); + + /* Verify that only login form is expanded, and that the + /* all other logistration forms are collapsed. + */ + hiddenFormsList = [ + '#register-form', + '#password-reset-form' + ]; + assertForms('#login-form', hiddenFormsList); + }); + + it('can initially render the registration form', function() { + var hiddenFormsList; + + initializeLogistrationFactory(this, 'register'); + + /* Verify that only registration form is expanded, and that the + /* all other logistration forms are collapsed. + */ + hiddenFormsList = [ + '#login-form', + '#password-reset-form' + ]; + assertForms('#register-form', hiddenFormsList); + }); + + it('can initially render the password reset form', function() { + var hiddenFormsList; + + initializeLogistrationFactory(this, 'reset'); + + /* Verify that only password reset form is expanded, and that the + /* all other logistration forms are collapsed. + */ + hiddenFormsList = [ + '#login-form', + '#register-form' + ]; + assertForms('#password-reset-form', hiddenFormsList); + }); + }); + }); +}).call(this, define || RequireJS.define); 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 0aedc4011b..b3ebbc4e69 100644 --- a/lms/static/js/spec/student_account/password_reset_spec.js +++ b/lms/static/js/spec/student_account/password_reset_spec.js @@ -1,14 +1,16 @@ -define([ - 'jquery', - 'underscore', - 'common/js/spec_helpers/template_helpers', - 'common/js/spec_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'; +;(function (define) { + 'use strict'; + define([ + 'jquery', + 'underscore', + 'common/js/spec_helpers/template_helpers', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/models/PasswordResetModel', + 'js/student_account/views/PasswordResetView' + ], + function($, _, TemplateHelpers, AjaxHelpers, PasswordResetModel, PasswordResetView) { + describe('edx.student.account.PasswordResetView', function() { var model = null, view = null, requests = null, @@ -46,12 +48,12 @@ define([ }; var submitEmail = function(validationSuccess) { - // Simulate manual entry of an email address - $('#password-reset-email').val(EMAIL); - // Create a fake click event var clickEvent = $.Event('click'); + // Simulate manual entry of an email address + $('#password-reset-email').val(EMAIL); + // If validationSuccess isn't passed, we avoid // spying on `view.validate` twice if ( !_.isUndefined(validationSuccess) ) { @@ -141,5 +143,5 @@ define([ expect(view.$errors).toHaveClass('hidden'); }); }); - } -); + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/student_account/register_spec.js b/lms/static/js/spec/student_account/register_spec.js index ac9064e376..f827e13e9d 100644 --- a/lms/static/js/spec/student_account/register_spec.js +++ b/lms/static/js/spec/student_account/register_spec.js @@ -1,352 +1,356 @@ -define([ - 'jquery', - 'underscore', - 'common/js/spec_helpers/template_helpers', - 'common/js/spec_helpers/ajax_helpers', - 'js/student_account/models/RegisterModel', - 'js/student_account/views/RegisterView' -], function($, _, TemplateHelpers, AjaxHelpers, RegisterModel, RegisterView) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'underscore', + 'common/js/spec_helpers/template_helpers', + 'common/js/spec_helpers/ajax_helpers', + 'js/student_account/models/RegisterModel', + 'js/student_account/views/RegisterView' + ], + function($, _, TemplateHelpers, AjaxHelpers, RegisterModel, RegisterView) { - describe('edx.student.account.RegisterView', function() { + describe('edx.student.account.RegisterView', function() { + var model = null, + view = null, + requests = null, + authComplete = false, + PLATFORM_NAME = 'edX', + COURSE_ID = 'edX/DemoX/Fall', + 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', + honor_code: true + }, + THIRD_PARTY_AUTH = { + currentProvider: null, + providers: [ + { + id: 'oa2-google-oauth2', + name: 'Google', + iconClass: 'fa-google-plus', + loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', + registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' + }, + { + id: 'oa2-facebook', + name: 'Facebook', + iconClass: 'fa-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: [ + { + placeholder: 'username@domain.com', + name: 'email', + label: 'Email', + defaultValue: '', + type: 'email', + required: true, + instructions: 'Enter your email.', + restrictions: {} + }, + { + placeholder: 'Jane Doe', + name: 'name', + label: 'Full Name', + defaultValue: '', + type: 'text', + required: true, + instructions: 'Enter your username.', + restrictions: {} + }, + { + placeholder: 'JaneDoe', + name: 'username', + label: 'Username', + defaultValue: '', + type: 'text', + required: true, + instructions: 'Enter your username.', + restrictions: {} + }, + { + placeholder: '', + name: 'password', + label: 'Password', + defaultValue: '', + type: 'password', + required: true, + instructions: 'Enter your password.', + restrictions: {} + }, + { + placeholder: '', + 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: {} + }, + { + placeholder: '', + 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: {} + }, + { + placeholder: '', + 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: {} + }, + { + placeholder: '', + name: 'mailing_address', + label: 'Mailing Address', + defaultValue: '', + type: 'textarea', + required: false, + instructions: 'Enter your mailing address.', + restrictions: {} + }, + { + placeholder: '', + name: 'goals', + label: 'Goals', + defaultValue: '', + type: 'textarea', + required: false, + instructions: "If you'd like, tell us why you're interested in edX.", + restrictions: {} + }, + { + placeholder: '', + name: 'honor_code', + label: 'I agree to the Terms of Service and Honor Code', + defaultValue: '', + type: 'checkbox', + required: true, + instructions: '', + restrictions: {} + } + ] + }; - var model = null, - view = null, - requests = null, - authComplete = false, - PLATFORM_NAME = 'edX', - COURSE_ID = "edX/DemoX/Fall", - 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', - honor_code: true - }, - THIRD_PARTY_AUTH = { - currentProvider: null, - providers: [ - { - id: 'oa2-google-oauth2', - name: 'Google', - iconClass: 'fa-google-plus', - loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', - registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' - }, - { - id: 'oa2-facebook', - name: 'Facebook', - iconClass: 'fa-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: [ - { - placeholder: 'username@domain.com', - name: 'email', - label: 'Email', - defaultValue: '', - type: 'email', - required: true, - instructions: 'Enter your email.', - restrictions: {} - }, - { - placeholder: 'Jane Doe', - name: 'name', - label: 'Full Name', - defaultValue: '', - type: 'text', - required: true, - instructions: 'Enter your username.', - restrictions: {} - }, - { - placeholder: 'JaneDoe', - name: 'username', - label: 'Username', - defaultValue: '', - type: 'text', - required: true, - instructions: 'Enter your username.', - restrictions: {} - }, - { - placeholder: '', - name: 'password', - label: 'Password', - defaultValue: '', - type: 'password', - required: true, - instructions: 'Enter your password.', - restrictions: {} - }, - { - placeholder: '', - 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: {} - }, - { - placeholder: '', - 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: {} - }, - { - placeholder: '', - 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: {} - }, - { - placeholder: '', - name: 'mailing_address', - label: 'Mailing Address', - defaultValue: '', - type: 'textarea', - required: false, - instructions: 'Enter your mailing address.', - restrictions: {} - }, - { - placeholder: '', - name: 'goals', - label: 'Goals', - defaultValue: '', - type: 'textarea', - required: false, - instructions: "If you'd like, tell us why you're interested in edX.", - restrictions: {} - }, - { - placeholder: '', - name: 'honor_code', - label: 'I agree to the Terms of Service and Honor Code', - defaultValue: '', - type: 'checkbox', - required: true, - instructions: '', - restrictions: {} - } - ] + var createRegisterView = function(that) { + // Initialize the register model + model = new RegisterModel({}, { + url: FORM_DESCRIPTION.submit_url, + method: FORM_DESCRIPTION.method + }); + + // 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); + + // Intercept events from the view + authComplete = false; + view.on("auth-complete", function() { + authComplete = true; + }); }; - var createRegisterView = function(that) { - // Initialize the register model - model = new RegisterModel({}, { - url: FORM_DESCRIPTION.submit_url, - method: FORM_DESCRIPTION.method - }); + var submitForm = function(validationSuccess) { + // Create a fake click event + var clickEvent = $.Event('click'); - // Initialize the register view - view = new RegisterView({ - fields: FORM_DESCRIPTION.fields, - model: model, - thirdPartyAuth: THIRD_PARTY_AUTH, - platformName: PLATFORM_NAME - }); + // 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); - // Spy on AJAX requests - requests = AjaxHelpers.requests(that); + // Check the honor code checkbox + $('#register-honor_code').prop('checked', USER_DATA.honor_code); - // Intercept events from the view - authComplete = false; - view.on("auth-complete", function() { - authComplete = 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 honor code checkbox - $('#register-honor_code').prop('checked', USER_DATA.honor_code); - - // 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'); - TemplateHelpers.installTemplate('templates/student_account/form_field'); - }); - - 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( USER_DATA ) - ); - - // Respond with status code 200 - AjaxHelpers.respondWithJson(requests, {}); - - // Verify that auth complete is triggered - expect(authComplete).toBe(true); - // Form button should be disabled on success. - expect(view.$submitButton).toHaveAttr('disabled'); - }); - - it('sends analytics info containing the enrolled course ID', function() { - createRegisterView(this); - - // Simulate that the user is attempting to enroll in a course - // by setting the course_id query string param. - spyOn($, 'url').andCallFake(function( param ) { - if (param === "?course_id") { - return encodeURIComponent( COURSE_ID ); + // 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'); + TemplateHelpers.installTemplate('templates/student_account/form_field'); }); - // Attempt to register - submitForm( true ); + it('registers a new user', function() { + createRegisterView(this); - // Verify that the client sent the course ID for analytics - var expectedData = {course_id: COURSE_ID}; - $.extend(expectedData, USER_DATA); + // Submit the form, with successful validation + submitForm(true); - AjaxHelpers.expectRequest( - requests, 'POST', - FORM_DESCRIPTION.submit_url, - $.param( expectedData ) - ); - }); + // Verify that the client contacts the server with the expected data + AjaxHelpers.expectRequest( + requests, 'POST', + FORM_DESCRIPTION.submit_url, + $.param( USER_DATA ) + ); - it('displays third-party auth registration buttons', function() { - createRegisterView(this); + // Respond with status code 200 + AjaxHelpers.respondWithJson(requests, {}); - // Verify that Google and Facebook registration buttons are displayed - expect($('.button-oa2-google-oauth2')).toBeVisible(); - expect($('.button-oa2-facebook')).toBeVisible(); - }); + // Verify that auth complete is triggered + expect(authComplete).toBe(true); + // Form button should be disabled on success. + expect(view.$submitButton).toHaveAttr('disabled'); + }); - it('validates registration form fields', function() { - createRegisterView(this); + it('sends analytics info containing the enrolled course ID', function() { + var expectedData; - // Submit the form, with successful validation - submitForm(true); + createRegisterView(this); - // 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]); + // Simulate that the user is attempting to enroll in a course + // by setting the course_id query string param. + spyOn($, 'url').andCallFake(function( param ) { + if (param === '?course_id') { + return encodeURIComponent( COURSE_ID ); + } + }); - // Verify that no submission errors are visible - expect(view.$errors).toHaveClass('hidden'); - // Form button should be disabled on success. - expect(view.$submitButton).toHaveAttr('disabled'); - }); + // Attempt to register + submitForm( true ); - it('displays registration form validation errors', function() { - createRegisterView(this); + // Verify that the client sent the course ID for analytics + expectedData = {course_id: COURSE_ID}; + $.extend(expectedData, USER_DATA); - // Submit the form, with failed validation - submitForm(false); + AjaxHelpers.expectRequest( + requests, 'POST', + FORM_DESCRIPTION.submit_url, + $.param( expectedData ) + ); + }); - // Verify that submission errors are visible - expect(view.$errors).not.toHaveClass('hidden'); + it('displays third-party auth registration buttons', function() { + createRegisterView(this); - // Expect that auth complete is NOT triggered - expect(authComplete).toBe(false); - // Form button should be re-enabled on error. - expect(view.$submitButton).not.toHaveAttr('disabled'); - }); + // Verify that Google and Facebook registration buttons are displayed + expect($('.button-oa2-google-oauth2')).toBeVisible(); + expect($('.button-oa2-facebook')).toBeVisible(); + }); - it('displays an error if the server returns an error while registering', function() { - createRegisterView(this); + it('validates registration form fields', function() { + createRegisterView(this); - // Submit the form, with successful validation - submitForm(true); + // Submit the form, with successful validation + submitForm(true); - // Simulate an error from the LMS servers - AjaxHelpers.respondWithError(requests); + // 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]); - // Expect that an error is displayed and that auth complete is NOT triggered - expect(view.$errors).not.toHaveClass('hidden'); - expect(authComplete).toBe(false); + // Verify that no submission errors are visible + expect(view.$errors).toHaveClass('hidden'); + // Form button should be disabled on success. + expect(view.$submitButton).toHaveAttr('disabled'); + }); - // If we try again and succeed, the error should go away - submitForm(); + it('displays registration form validation errors', function() { + createRegisterView(this); - // This time, respond with status code 200 - AjaxHelpers.respondWithJson(requests, {}); + // Submit the form, with failed validation + submitForm(false); - // Expect that the error is hidden and that auth complete is triggered - expect(view.$errors).toHaveClass('hidden'); - expect(authComplete).toBe(true); - // Form button should be disabled on success. - expect(view.$submitButton).toHaveAttr('disabled'); + // Verify that submission errors are visible + expect(view.$errors).not.toHaveClass('hidden'); + + // Expect that auth complete is NOT triggered + expect(authComplete).toBe(false); + // Form button should be re-enabled on error. + expect(view.$submitButton).not.toHaveAttr('disabled'); + }); + + it('displays an error if the server returns an error while registering', function() { + createRegisterView(this); + + // Submit the form, with successful validation + submitForm(true); + + // Simulate an error from the LMS servers + AjaxHelpers.respondWithError(requests); + + // Expect that an error is displayed and that auth complete is NOT triggered + expect(view.$errors).not.toHaveClass('hidden'); + expect(authComplete).toBe(false); + + // 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 and that auth complete is triggered + expect(view.$errors).toHaveClass('hidden'); + expect(authComplete).toBe(true); + // Form button should be disabled on success. + expect(view.$submitButton).toHaveAttr('disabled'); + }); }); }); -}); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/accessApp.js b/lms/static/js/student_account/accessApp.js deleted file mode 100644 index 2510bc1cff..0000000000 --- a/lms/static/js/student_account/accessApp.js +++ /dev/null @@ -1,21 +0,0 @@ -var edx = edx || {}; - -(function($) { - 'use strict'; - - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; - - var container = $('#login-and-registration-container'); - - return new edx.student.account.AccessView({ - mode: container.data('initial-mode'), - thirdPartyAuth: container.data('third-party-auth'), - thirdPartyAuthHint: container.data('third-party-auth-hint'), - nextUrl: container.data('next-url'), - platformName: container.data('platform-name'), - loginFormDesc: container.data('login-form-desc'), - registrationFormDesc: container.data('registration-form-desc'), - passwordResetFormDesc: container.data('password-reset-form-desc') - }); -})(jQuery); diff --git a/lms/static/js/student_account/logistration_factory.js b/lms/static/js/student_account/logistration_factory.js new file mode 100644 index 0000000000..88ec6b1fba --- /dev/null +++ b/lms/static/js/student_account/logistration_factory.js @@ -0,0 +1,15 @@ +;(function (define) { + 'use strict'; + define([ + 'jquery', + 'js/student_account/views/AccessView' + ], + function($, AccessView) { + return function(options) { + var $logistrationElement = $('#login-and-registration-container'); + + new AccessView(_.extend(options, {el: $logistrationElement})); + }; + } + ); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/models/LoginModel.js b/lms/static/js/student_account/models/LoginModel.js index 5e994f2f61..6fcce3774d 100644 --- a/lms/static/js/student_account/models/LoginModel.js +++ b/lms/static/js/student_account/models/LoginModel.js @@ -1,58 +1,57 @@ -var edx = edx || {}; - -(function($, Backbone) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'backbone', + 'jquery.url' + ], function ($, Backbone) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + return Backbone.Model.extend({ + defaults: { + email: '', + password: '', + remember: false + }, - edx.student.account.LoginModel = Backbone.Model.extend({ + ajaxType: '', + urlRoot: '', - defaults: { - email: '', - password: '', - remember: false - }, + initialize: function (attributes, options) { + this.ajaxType = options.method; + this.urlRoot = options.url; + }, - ajaxType: '', + sync: function (method, model) { + var headers = {'X-CSRFToken': $.cookie('csrftoken')}, + data = {}, + analytics, + courseId = $.url('?course_id'); - urlRoot: '', + // If there is a course ID in the query string param, + // send that to the server as well so it can be included + // in analytics events. + if (courseId) { + analytics = JSON.stringify({ + enroll_course_id: decodeURIComponent(courseId) + }); + } - initialize: function( attributes, options ) { - this.ajaxType = options.method; - this.urlRoot = options.url; - }, + // Include all form fields and analytics info in the data sent to the server + $.extend(data, model.attributes, {analytics: analytics}); - sync: function(method, model) { - var headers = { 'X-CSRFToken': $.cookie('csrftoken') }, - data = {}, - analytics, - courseId = $.url( '?course_id' ); - - // If there is a course ID in the query string param, - // send that to the server as well so it can be included - // in analytics events. - if ( courseId ) { - analytics = JSON.stringify({ - enroll_course_id: decodeURIComponent( courseId ) + $.ajax({ + url: model.urlRoot, + type: model.ajaxType, + data: data, + headers: headers, + success: function () { + model.trigger('sync'); + }, + error: function (error) { + model.trigger('error', error); + } }); } - - // Include all form fields and analytics info in the data sent to the server - $.extend( data, model.attributes, { analytics: analytics }); - - $.ajax({ - url: model.urlRoot, - type: model.ajaxType, - data: data, - headers: headers, - success: function() { - model.trigger('sync'); - }, - error: function( error ) { - model.trigger('error', error); - } - }); - } + }); }); -})(jQuery, Backbone); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/models/PasswordResetModel.js b/lms/static/js/student_account/models/PasswordResetModel.js index 691b4a86f5..a6ebf0b97d 100644 --- a/lms/static/js/student_account/models/PasswordResetModel.js +++ b/lms/static/js/student_account/models/PasswordResetModel.js @@ -1,44 +1,39 @@ -var edx = edx || {}; - -(function($, Backbone) { +;(function (define) { 'use strict'; + define(['jquery', 'backbone'], + function($, Backbone) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + return Backbone.Model.extend({ + defaults: { + email: '' + }, + ajaxType: '', + urlRoot: '', - edx.student.account.PasswordResetModel = Backbone.Model.extend({ + initialize: function( attributes, options ) { + this.ajaxType = options.method; + this.urlRoot = options.url; + }, - defaults: { - email: '' - }, + sync: function( method, model ) { + var headers = { + 'X-CSRFToken': $.cookie('csrftoken') + }; - ajaxType: '', - - urlRoot: '', - - initialize: function( attributes, options ) { - this.ajaxType = options.method; - this.urlRoot = options.url; - }, - - sync: function( method, model ) { - var headers = { - 'X-CSRFToken': $.cookie('csrftoken') - }; - - // Only expects an email address. - $.ajax({ - url: model.urlRoot, - type: model.ajaxType, - data: model.attributes, - headers: headers, - success: function() { - model.trigger('sync'); - }, - error: function( error ) { - model.trigger('error', error); - } - }); - } + // Only expects an email address. + $.ajax({ + url: model.urlRoot, + type: model.ajaxType, + data: model.attributes, + headers: headers, + success: function() { + model.trigger('sync'); + }, + error: function( error ) { + model.trigger('error', error); + } + }); + } + }); }); -})(jQuery, Backbone); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/models/RegisterModel.js b/lms/static/js/student_account/models/RegisterModel.js index 47a97c2367..fa30beb1ef 100644 --- a/lms/static/js/student_account/models/RegisterModel.js +++ b/lms/static/js/student_account/models/RegisterModel.js @@ -1,61 +1,56 @@ -var edx = edx || {}; - -(function($, Backbone) { +;(function (define) { 'use strict'; + define(['jquery', 'backbone', 'jquery.url'], + function($, Backbone) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + return Backbone.Model.extend({ + defaults: { + email: '', + name: '', + username: '', + password: '', + level_of_education: '', + gender: '', + year_of_birth: '', + mailing_address: '', + goals: '' + }, + ajaxType: '', + urlRoot: '', - edx.student.account.RegisterModel = Backbone.Model.extend({ + initialize: function( attributes, options ) { + this.ajaxType = options.method; + this.urlRoot = options.url; + }, - defaults: { - email: '', - name: '', - username: '', - password: '', - level_of_education: '', - gender: '', - year_of_birth: '', - mailing_address: '', - goals: '', - }, + sync: function(method, model) { + var headers = { 'X-CSRFToken': $.cookie('csrftoken') }, + data = {}, + courseId = $.url( '?course_id' ); - ajaxType: '', - - urlRoot: '', - - initialize: function( attributes, options ) { - this.ajaxType = options.method; - this.urlRoot = options.url; - }, - - sync: function(method, model) { - var headers = { 'X-CSRFToken': $.cookie('csrftoken') }, - data = {}, - courseId = $.url( '?course_id' ); - - // If there is a course ID in the query string param, - // send that to the server as well so it can be included - // in analytics events. - if ( courseId ) { - data.course_id = decodeURIComponent(courseId); - } - - // Include all form fields and analytics info in the data sent to the server - $.extend( data, model.attributes); - - $.ajax({ - url: model.urlRoot, - type: model.ajaxType, - data: data, - headers: headers, - success: function() { - model.trigger('sync'); - }, - error: function( error ) { - model.trigger('error', error); + // If there is a course ID in the query string param, + // send that to the server as well so it can be included + // in analytics events. + if ( courseId ) { + data.course_id = decodeURIComponent(courseId); } - }); - } + + // Include all form fields and analytics info in the data sent to the server + $.extend( data, model.attributes); + + $.ajax({ + url: model.urlRoot, + type: model.ajaxType, + data: data, + headers: headers, + success: function() { + model.trigger('sync'); + }, + error: function( error ) { + model.trigger('error', error); + } + }); + } + }); }); -})(jQuery, Backbone); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index a909c61fae..abac4df615 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -1,280 +1,294 @@ -var edx = edx || {}; - -(function($, _, _s, Backbone, History) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'utility', + 'underscore', + 'underscore.string', + 'backbone', + 'js/student_account/models/LoginModel', + 'js/student_account/models/PasswordResetModel', + 'js/student_account/models/RegisterModel', + 'js/student_account/views/LoginView', + 'js/student_account/views/PasswordResetView', + 'js/student_account/views/RegisterView', + 'js/student_account/views/InstitutionLoginView', + 'js/student_account/views/HintedLoginView', + 'js/vendor/history' + ], + function($, utility, _, _s, Backbone, LoginModel, PasswordResetModel, RegisterModel, LoginView, + PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + if (_.isUndefined(_s)) { + _s = _.str; + } - edx.student.account.AccessView = Backbone.View.extend({ - el: '#login-and-registration-container', + return Backbone.View.extend({ + tpl: '#access-tpl', + events: { + 'click .form-toggle': 'toggleForm' + }, + subview: { + login: {}, + register: {}, + passwordHelp: {}, + institutionLogin: {}, + hintedLogin: {} + }, + nextUrl: '/dashboard', + // The form currently loaded + activeForm: '', - tpl: '#access-tpl', + initialize: function( options ) { - events: { - 'click .form-toggle': 'toggleForm' - }, + /* Mix non-conflicting functions from underscore.string + * (all but include, contains, and reverse) into the + * Underscore namespace + */ + _.mixin( _s.exports() ); - subview: { - login: {}, - register: {}, - passwordHelp: {}, - institutionLogin: {}, - hintedLogin: {} - }, + this.tpl = $(this.tpl).html(); - nextUrl: '/dashboard', + this.activeForm = options.initial_mode || 'login'; - // The form currently loaded - activeForm: '', + this.thirdPartyAuth = options.third_party_auth || { + currentProvider: null, + providers: [] + }; - initialize: function( obj ) { - /* Mix non-conflicting functions from underscore.string - * (all but include, contains, and reverse) into the - * Underscore namespace - */ - _.mixin( _s.exports() ); + this.thirdPartyAuthHint = options.third_party_auth_hint || null; - this.tpl = $(this.tpl).html(); - - this.activeForm = obj.mode || 'login'; - - this.thirdPartyAuth = obj.thirdPartyAuth || { - currentProvider: null, - providers: [] - }; - - this.thirdPartyAuthHint = obj.thirdPartyAuthHint || null; - - if (obj.nextUrl) { - // Ensure that the next URL is internal for security reasons - if ( ! window.isExternal( obj.nextUrl ) ) { - this.nextUrl = obj.nextUrl; + if (options.login_redirect_url) { + // Ensure that the next URL is internal for security reasons + if ( ! window.isExternal( options.login_redirect_url ) ) { + this.nextUrl = options.login_redirect_url; + } } - } - this.formDescriptions = { - login: obj.loginFormDesc, - register: obj.registrationFormDesc, - reset: obj.passwordResetFormDesc, - institution_login: null, - hinted_login: null - }; + this.formDescriptions = { + login: options.login_form_desc, + register: options.registration_form_desc, + reset: options.password_reset_form_desc, + institution_login: null, + hinted_login: null + }; - this.platformName = obj.platformName; + this.platformName = options.platform_name; - // The login view listens for 'sync' events from the reset model - this.resetModel = new edx.student.account.PasswordResetModel({}, { - method: 'GET', - url: '#' - }); - - this.render(); - - // Once the third party error message has been shown once, - // there is no need to show it again, if the user changes mode: - this.thirdPartyAuth.errorMessage = null; - }, - - render: function() { - $(this.el).html( _.template( this.tpl, { - mode: this.activeForm - })); - - this.postRender(); - - return this; - }, - - postRender: function() { - //get & check current url hash part & load form accordingly - if (Backbone.history.getHash() === "forgot-password-modal") { - this.resetPassword(); - } else { - this.loadForm(this.activeForm); - } - }, - - loadForm: function( type ) { - var loadFunc = _.bind( this.load[type], this ); - loadFunc( this.formDescriptions[type] ); - }, - - load: { - login: function( data ) { - var model = new edx.student.account.LoginModel({}, { - method: data.method, - url: data.submit_url + // The login view listens for 'sync' events from the reset model + this.resetModel = new PasswordResetModel({}, { + method: 'GET', + url: '#' }); - this.subview.login = new edx.student.account.LoginView({ - fields: data.fields, - model: model, - resetModel: this.resetModel, - thirdPartyAuth: this.thirdPartyAuth, - platformName: this.platformName - }); - - // Listen for 'password-help' event to toggle sub-views - this.listenTo( this.subview.login, 'password-help', this.resetPassword ); - - // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. - this.listenTo( this.subview.login, 'auth-complete', this.authComplete ); + this.render(); + // Once the third party error message has been shown once, + // there is no need to show it again, if the user changes mode: + this.thirdPartyAuth.errorMessage = null; }, - reset: function( data ) { - this.resetModel.ajaxType = data.method; - this.resetModel.urlRoot = data.submit_url; + render: function() { + $(this.el).html( _.template( this.tpl, { + mode: this.activeForm + })); - this.subview.passwordHelp = new edx.student.account.PasswordResetView({ - fields: data.fields, - model: this.resetModel + this.postRender(); + + return this; + }, + + postRender: function() { + //get & check current url hash part & load form accordingly + if (Backbone.history.getHash() === 'forgot-password-modal') { + this.resetPassword(); + } else { + this.loadForm(this.activeForm); + } + }, + + loadForm: function( type ) { + var loadFunc = _.bind( this.load[type], this ); + loadFunc( this.formDescriptions[type] ); + }, + + load: { + login: function( data ) { + var model = new LoginModel({}, { + method: data.method, + url: data.submit_url + }); + + this.subview.login = new LoginView({ + fields: data.fields, + model: model, + resetModel: this.resetModel, + thirdPartyAuth: this.thirdPartyAuth, + platformName: this.platformName + }); + + // Listen for 'password-help' event to toggle sub-views + this.listenTo( this.subview.login, 'password-help', this.resetPassword ); + + // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. + this.listenTo( this.subview.login, 'auth-complete', this.authComplete ); + + }, + + reset: function( data ) { + this.resetModel.ajaxType = data.method; + this.resetModel.urlRoot = data.submit_url; + + this.subview.passwordHelp = new PasswordResetView({ + fields: data.fields, + model: this.resetModel + }); + + // Listen for 'password-email-sent' event to toggle sub-views + this.listenTo( this.subview.passwordHelp, 'password-email-sent', this.passwordEmailSent ); + + // Focus on the form + $('.password-reset-form').focus(); + }, + + register: function( data ) { + var model = new RegisterModel({}, { + method: data.method, + url: data.submit_url + }); + + this.subview.register = new RegisterView({ + fields: data.fields, + model: model, + thirdPartyAuth: this.thirdPartyAuth, + platformName: this.platformName + }); + + // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. + this.listenTo( this.subview.register, 'auth-complete', this.authComplete ); + }, + + institution_login: function ( unused ) { + this.subview.institutionLogin = new InstitutionLoginView({ + thirdPartyAuth: this.thirdPartyAuth, + platformName: this.platformName, + mode: this.activeForm + }); + + this.subview.institutionLogin.render(); + }, + + hinted_login: function ( unused ) { + this.subview.hintedLogin = new HintedLoginView({ + thirdPartyAuth: this.thirdPartyAuth, + hintedProvider: this.thirdPartyAuthHint, + platformName: this.platformName + }); + + this.subview.hintedLogin.render(); + } + }, + + passwordEmailSent: function() { + var $loginAnchorElement = $('#login-anchor'); + this.element.hide( $(this.el).find('#password-reset-anchor') ); + this.element.show( $loginAnchorElement ); + this.element.scrollTop( $loginAnchorElement ); + }, + + resetPassword: function() { + window.analytics.track('edx.bi.password_reset_form.viewed', { + category: 'user-engagement' }); - // Listen for 'password-email-sent' event to toggle sub-views - this.listenTo( this.subview.passwordHelp, 'password-email-sent', this.passwordEmailSent ); + this.element.hide( $(this.el).find('#login-anchor') ); + this.loadForm('reset'); + this.element.scrollTop( $('#password-reset-anchor') ); + }, + + toggleForm: function( e ) { + var type = $(e.currentTarget).data('type'), + $form = $('#' + type + '-form'), + $anchor = $('#' + type + '-anchor'), + queryParams = url('?'), + queryStr = queryParams.length > 0 ? '?' + queryParams : ''; + + e.preventDefault(); + + window.analytics.track('edx.bi.' + type + '_form.toggled', { + category: 'user-engagement' + }); + + // Load the form. Institution login is always refreshed since it changes based on the previous form. + if ( !this.form.isLoaded( $form ) || type == 'institution_login') { + this.loadForm( type ); + } + this.activeForm = type; + + this.element.hide( $(this.el).find('.submission-success') ); + this.element.hide( $(this.el).find('.form-wrapper') ); + this.element.show( $form ); + this.element.scrollTop( $anchor ); + + // Update url without reloading page + if (type != 'institution_login') { + History.pushState( null, document.title, '/' + type + queryStr ); + } + analytics.page( 'login_and_registration', type ); // Focus on the form - $('.password-reset-form').focus(); + $('#' + type).focus(); }, - register: function( data ) { - var model = new edx.student.account.RegisterModel({}, { - method: data.method, - url: data.submit_url - }); - - this.subview.register = new edx.student.account.RegisterView({ - fields: data.fields, - model: model, - thirdPartyAuth: this.thirdPartyAuth, - platformName: this.platformName - }); - - // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. - this.listenTo( this.subview.register, 'auth-complete', this.authComplete ); + /** + * Once authentication has completed successfully: + * + * If we're in a third party auth pipeline, we must complete the pipeline. + * Otherwise, redirect to the specified next step. + * + */ + authComplete: function() { + if (this.thirdPartyAuth && this.thirdPartyAuth.finishAuthUrl) { + this.redirect(this.thirdPartyAuth.finishAuthUrl); + // Note: the third party auth URL likely contains another redirect URL embedded inside + } else { + this.redirect(this.nextUrl); + } }, - institution_login: function ( unused ) { - this.subview.institutionLogin = new edx.student.account.InstitutionLoginView({ - thirdPartyAuth: this.thirdPartyAuth, - platformName: this.platformName, - mode: this.activeForm - }); - - this.subview.institutionLogin.render(); + /** + * Redirect to a URL. Mainly useful for mocking out in tests. + * @param {string} url The URL to redirect to. + */ + redirect: function( url ) { + window.location.replace(url); }, - hinted_login: function ( unused ) { - this.subview.hintedLogin = new edx.student.account.HintedLoginView({ - thirdPartyAuth: this.thirdPartyAuth, - hintedProvider: this.thirdPartyAuthHint, - platformName: this.platformName - }); - - this.subview.hintedLogin.render(); - } - }, - - passwordEmailSent: function() { - this.element.hide( $(this.el).find('#password-reset-anchor') ); - this.element.show( $('#login-anchor') ); - this.element.scrollTop( $('#login-anchor') ); - }, - - resetPassword: function() { - window.analytics.track('edx.bi.password_reset_form.viewed', { - category: 'user-engagement' - }); - - this.element.hide( $(this.el).find('#login-anchor') ); - this.loadForm('reset'); - this.element.scrollTop( $('#password-reset-anchor') ); - }, - - toggleForm: function( e ) { - var type = $(e.currentTarget).data('type'), - $form = $('#' + type + '-form'), - $anchor = $('#' + type + '-anchor'), - queryParams = url('?'), - queryStr = queryParams.length > 0 ? '?' + queryParams : ''; - - e.preventDefault(); - - window.analytics.track('edx.bi.' + type + '_form.toggled', { - category: 'user-engagement' - }); - - // Load the form. Institution login is always refreshed since it changes based on the previous form. - if ( !this.form.isLoaded( $form ) || type == "institution_login") { - this.loadForm( type ); - } - this.activeForm = type; - - this.element.hide( $(this.el).find('.submission-success') ); - this.element.hide( $(this.el).find('.form-wrapper') ); - this.element.show( $form ); - this.element.scrollTop( $anchor ); - - // Update url without reloading page - if (type != "institution_login") { - History.pushState( null, document.title, '/' + type + queryStr ); - } - analytics.page( 'login_and_registration', type ); - - // Focus on the form - $("#" + type).focus(); - }, - - /** - * Once authentication has completed successfully: - * - * If we're in a third party auth pipeline, we must complete the pipeline. - * Otherwise, redirect to the specified next step. - * - */ - authComplete: function() { - if (this.thirdPartyAuth && this.thirdPartyAuth.finishAuthUrl) { - this.redirect(this.thirdPartyAuth.finishAuthUrl); - // Note: the third party auth URL likely contains another redirect URL embedded inside - } else { - this.redirect(this.nextUrl); - } - }, - - /** - * Redirect to a URL. Mainly useful for mocking out in tests. - * @param {string} url The URL to redirect to. - */ - redirect: function( url ) { - window.location.replace(url); - }, - - form: { - isLoaded: function( $form ) { - return $form.html().length > 0; - } - }, - - /* Helper method to toggle display - * including accessibility considerations - */ - element: { - hide: function( $el ) { - $el.addClass('hidden'); + form: { + isLoaded: function( $form ) { + return $form.html().length > 0; + } }, - scrollTop: function( $el ) { - // Scroll to top of selected element - $('html,body').animate({ - scrollTop: $el.offset().top - },'slow'); - }, + /* Helper method to toggle display + * including accessibility considerations + */ + element: { + hide: function( $el ) { + $el.addClass('hidden'); + }, - show: function( $el ) { - $el.removeClass('hidden'); + scrollTop: function( $el ) { + // Scroll to top of selected element + $('html,body').animate({ + scrollTop: $el.offset().top + },'slow'); + }, + + show: function( $el ) { + $el.removeClass('hidden'); + } } - } + }); }); -})(jQuery, _, _.str, Backbone, History); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/views/FormView.js b/lms/static/js/student_account/views/FormView.js index 989be0bc86..389950119d 100644 --- a/lms/static/js/student_account/views/FormView.js +++ b/lms/static/js/student_account/views/FormView.js @@ -1,270 +1,271 @@ -var edx = edx || {}; - -(function($, _, Backbone, gettext) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'underscore', + 'backbone', + 'js/utils/edx.utils.validate' + ], + function($, _, Backbone, EdxUtilsValidate) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + return Backbone.View.extend({ + tagName: 'form', - edx.student.account.FormView = Backbone.View.extend({ - tagName: 'form', + el: '', - el: '', + tpl: '', - tpl: '', + fieldTpl: '#form_field-tpl', - fieldTpl: '#form_field-tpl', + events: {}, - events: {}, + errors: [], - errors: [], + formType: '', - formType: '', + $form: {}, - $form: {}, + fields: [], - fields: [], + // String to append to required label fields + requiredStr: '*', - // String to append to required label fields - requiredStr: '*', + submitButton: '', - submitButton: '', + initialize: function( data ) { + this.model = data.model; + this.preRender( data ); - 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.tpl = $(this.tpl).html(); - this.fieldTpl = $(this.fieldTpl).html(); - this.buildForm( data.fields ); - - this.listenTo( this.model, 'error', this.saveError ); - }, - - /* Allows extended views to add custom - * init steps without needing to repeat - * default init steps - */ - preRender: function( data ) { - /* Custom code goes here */ - return data; - }, - - render: function( html ) { - var fields = html || ''; - - $(this.el).html( _.template( this.tpl, { - fields: fields - })); - - this.postRender(); - - return this; - }, - - postRender: function() { - var $container = $(this.el); - - this.$form = $container.find('form'); - this.$errors = $container.find('.submission-error'); - this.$submitButton = $container.find(this.submitButton); - }, - - buildForm: function( data ) { - var html = [], - i, - len = data.length, - fieldTpl = this.fieldTpl; - - this.fields = data; - - for ( i=0; i' + error.responseText + '']; - this.setErrors(); - this.toggleDisableButton(false); - }, + focusFirstError: function() { + var $error = this.$form.find('.error').first(), + $field = {}, + $parent = {}; - setErrors: function() { - var $msg = this.$errors.find('.message-copy'), - html = [], - errors = this.errors, - i, - len = errors.length; + if ( $error.is('label') ) { + $parent = $error.parent('.form-field'); + $error = $parent.find('input') || $parent.find('select'); + } else { + $field = $error; + } - for ( i=0; i' + error.responseText + '']; this.setErrors(); this.toggleDisableButton(false); - } else { - this.element.hide( this.$errors ); - } - }, + }, - /** - * If a form button is defined for this form, this will disable the button on - * submit, and re-enable the button if an error occurs. - * - * Args: - * disabled (boolean): If set to TRUE, disable the button. - * - */ - toggleDisableButton: function ( disabled ) { - if (this.$submitButton) { - this.$submitButton.attr('disabled', disabled); - } - }, + setErrors: function() { + var $msg = this.$errors.find('.message-copy'), + html = [], + errors = this.errors, + i, + len = errors.length; - validate: function( $el ) { - return edx.utils.validate( $el ); - } + for ( i=0; i' + error.responseText + '']; - this.setErrors(); - this.element.hide( this.$resetSuccess ); - - /* If we've gotten a 403 error, it means that we've successfully - * authenticated with a third-party provider, but we haven't - * linked the account to an EdX account. In this case, - * we need to prompt the user to enter a little more information - * to complete the registration process. - */ - if ( error.status === 403 && - error.responseText === 'third-party-auth' && - this.currentProvider ) { - this.element.show( this.$authError ); + resetEmail: function() { this.element.hide( this.$errors ); - } else { - this.element.hide( this.$authError ); - this.element.show( this.$errors ); + this.element.show( this.$resetSuccess ); + }, + + thirdPartyAuth: function( event ) { + var providerUrl = $(event.currentTarget).data('provider-url') || ''; + + if (providerUrl) { + window.location.href = providerUrl; + } + }, + + saveSuccess: function() { + this.trigger('auth-complete'); + this.element.hide( this.$resetSuccess ); + }, + + saveError: function( error ) { + this.errors = ['
  • ' + error.responseText + '
  • ']; + this.setErrors(); + this.element.hide( this.$resetSuccess ); + + /* If we've gotten a 403 error, it means that we've successfully + * authenticated with a third-party provider, but we haven't + * linked the account to an EdX account. In this case, + * we need to prompt the user to enter a little more information + * to complete the registration process. + */ + if ( error.status === 403 && + error.responseText === 'third-party-auth' && + this.currentProvider ) { + this.element.show( this.$authError ); + this.element.hide( this.$errors ); + } else { + this.element.hide( this.$authError ); + this.element.show( this.$errors ); + } + this.toggleDisableButton(false); } - this.toggleDisableButton(false); - } + }); }); -})(jQuery, _, gettext); +}).call(this, define || RequireJS.define); + diff --git a/lms/static/js/student_account/views/PasswordResetView.js b/lms/static/js/student_account/views/PasswordResetView.js index d9667218cb..c7b11b2298 100644 --- a/lms/static/js/student_account/views/PasswordResetView.js +++ b/lms/static/js/student_account/views/PasswordResetView.js @@ -1,48 +1,48 @@ -var edx = edx || {}; - -(function($, gettext) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'js/student_account/views/FormView' + ], + function($, FormView) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + return FormView.extend({ + el: '#password-reset-form', - edx.student.account.PasswordResetView = edx.student.account.FormView.extend({ - el: '#password-reset-form', + tpl: '#password_reset-tpl', - tpl: '#password_reset-tpl', + events: { + 'click .js-reset': 'submitForm' + }, - events: { - 'click .js-reset': 'submitForm' - }, + formType: 'password-reset', - formType: 'password-reset', + requiredStr: '', - requiredStr: '', + submitButton: '.js-reset', - submitButton: '.js-reset', + preRender: function() { + this.element.show( $( this.el ) ); + this.element.show( $( this.el ).parent() ); + this.listenTo( this.model, 'sync', this.saveSuccess ); + }, - preRender: function() { - this.element.show( $( this.el ) ); - this.element.show( $( this.el ).parent() ); - this.listenTo( this.model, 'sync', this.saveSuccess ); - }, + toggleErrorMsg: function( show ) { + if ( show ) { + this.setErrors(); + this.toggleDisableButton(false); + } else { + this.element.hide( this.$errors ); + } + }, - toggleErrorMsg: function( show ) { - if ( show ) { - this.setErrors(); - this.toggleDisableButton(false); - } else { - this.element.hide( this.$errors ); + saveSuccess: function() { + this.trigger('password-email-sent'); + + // Destroy the view (but not el) and unbind events + this.$el.empty().off(); + this.stopListening(); } - }, - - saveSuccess: function() { - this.trigger('password-email-sent'); - - // Destroy the view (but not el) and unbind events - this.$el.empty().off(); - this.stopListening(); - } + }); }); - -})(jQuery, gettext); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/views/RegisterView.js b/lms/static/js/student_account/views/RegisterView.js index bf52b942d4..9e8d6903a6 100644 --- a/lms/static/js/student_account/views/RegisterView.js +++ b/lms/static/js/student_account/views/RegisterView.js @@ -1,100 +1,102 @@ -var edx = edx || {}; - -(function($, _, gettext) { +;(function (define) { 'use strict'; + define([ + 'jquery', + 'underscore', + 'js/student_account/views/FormView' + ], + function($, _, FormView) { - edx.student = edx.student || {}; - edx.student.account = edx.student.account || {}; + return FormView.extend({ + el: '#register-form', - edx.student.account.RegisterView = edx.student.account.FormView.extend({ - el: '#register-form', + tpl: '#register-tpl', - tpl: '#register-tpl', + events: { + 'click .js-register': 'submitForm', + 'click .login-provider': 'thirdPartyAuth' + }, - events: { - 'click .js-register': 'submitForm', - 'click .login-provider': 'thirdPartyAuth' - }, + formType: 'register', - formType: 'register', + submitButton: '.js-register', - submitButton: '.js-register', + preRender: function( data ) { + this.providers = data.thirdPartyAuth.providers || []; + this.hasSecondaryProviders = ( + data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length + ); + this.currentProvider = data.thirdPartyAuth.currentProvider || ''; + this.errorMessage = data.thirdPartyAuth.errorMessage || ''; + this.platformName = data.platformName; + this.autoSubmit = data.thirdPartyAuth.autoSubmitRegForm; - preRender: function( data ) { - this.providers = data.thirdPartyAuth.providers || []; - this.hasSecondaryProviders = ( - data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length - ); - this.currentProvider = data.thirdPartyAuth.currentProvider || ''; - this.errorMessage = data.thirdPartyAuth.errorMessage || ''; - this.platformName = data.platformName; - this.autoSubmit = data.thirdPartyAuth.autoSubmitRegForm; + this.listenTo( this.model, 'sync', this.saveSuccess ); + }, - this.listenTo( this.model, 'sync', this.saveSuccess ); - }, + render: function( html ) { + var fields = html || ''; - render: function( html ) { - var fields = html || ''; - - $(this.el).html( _.template( this.tpl, { - /* We pass the context object to the template so that - * we can perform variable interpolation using sprintf - */ - context: { - fields: fields, - currentProvider: this.currentProvider, - errorMessage: this.errorMessage, - providers: this.providers, - hasSecondaryProviders: this.hasSecondaryProviders, - platformName: this.platformName - } - })); - - this.postRender(); - - if (this.autoSubmit) { - $(this.el).hide(); - $('#register-honor_code').prop('checked', true); - this.submitForm(); - } - - return this; - }, - - thirdPartyAuth: function( event ) { - var providerUrl = $(event.currentTarget).data('provider-url') || ''; - - if ( providerUrl ) { - window.location.href = providerUrl; - } - }, - - saveSuccess: function() { - this.trigger('auth-complete'); - }, - - saveError: function( error ) { - $(this.el).show(); // Show in case the form was hidden for auto-submission - this.errors = _.flatten( - _.map( - JSON.parse(error.responseText), - function(error_list) { - return _.map( - error_list, - function(error) { return "
  • " + error.user_message + "
  • "; } - ); + $(this.el).html( _.template( this.tpl, { + /* We pass the context object to the template so that + * we can perform variable interpolation using sprintf + */ + context: { + fields: fields, + currentProvider: this.currentProvider, + errorMessage: this.errorMessage, + providers: this.providers, + hasSecondaryProviders: this.hasSecondaryProviders, + platformName: this.platformName } - ) - ); - this.setErrors(); - this.toggleDisableButton(false); - }, + })); - postFormSubmission: function() { - if (_.compact(this.errors).length) { - // The form did not get submitted due to validation errors. + this.postRender(); + + if (this.autoSubmit) { + $(this.el).hide(); + $('#register-honor_code').prop('checked', true); + this.submitForm(); + } + + return this; + }, + + thirdPartyAuth: function( event ) { + var providerUrl = $(event.currentTarget).data('provider-url') || ''; + + if ( providerUrl ) { + window.location.href = providerUrl; + } + }, + + saveSuccess: function() { + this.trigger('auth-complete'); + }, + + saveError: function( error ) { $(this.el).show(); // Show in case the form was hidden for auto-submission + this.errors = _.flatten( + _.map( + JSON.parse(error.responseText), + function(error_list) { + return _.map( + error_list, + function(error) { return '
  • ' + error.user_message + '
  • '; } + ); + } + ) + ); + this.setErrors(); + this.toggleDisableButton(false); + }, + + postFormSubmission: function() { + if (_.compact(this.errors).length) { + // The form did not get submitted due to validation errors. + $(this.el).show(); // Show in case the form was hidden for auto-submission + } } - }, + }); }); -})(jQuery, _, gettext); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js_test.yml b/lms/static/js_test.yml index b6eaabd622..49e154ecbd 100644 --- a/lms/static/js_test.yml +++ b/lms/static/js_test.yml @@ -63,6 +63,7 @@ lib_paths: - xmodule_js/common_static/js/test/i18n.js - xmodule_js/common_static/js/vendor/date.js - xmodule_js/common_static/js/vendor/moment.min.js + - xmodule_js/common_static/js/utils/edx.utils.validate.js # Paths to source JavaScript files src_paths: diff --git a/lms/static/lms/js/build.js b/lms/static/lms/js/build.js index a79d96c847..ec66f97ad0 100644 --- a/lms/static/lms/js/build.js +++ b/lms/static/lms/js/build.js @@ -24,6 +24,7 @@ 'js/groups/views/cohorts_dashboard_factory', 'js/search/course/course_search_factory', 'js/search/dashboard/dashboard_search_factory', + 'js/student_account/logistration_factory', 'js/student_account/views/account_settings_factory', 'js/student_account/views/finish_auth_factory', 'js/student_profile/views/learner_profile_factory', diff --git a/lms/templates/student_account/login_and_register.html b/lms/templates/student_account/login_and_register.html index 8d3ac85907..2e1143118b 100644 --- a/lms/templates/student_account/login_and_register.html +++ b/lms/templates/student_account/login_and_register.html @@ -1,4 +1,8 @@ -<%! from django.utils.translation import ugettext as _ %> +<%! + import json + from django.utils.translation import ugettext as _ + from openedx.core.lib.json_utils import EscapedEdxJSONEncoder +%> <%namespace name='static' file='/static_content.html'/> <%inherit file="../main.html" /> @@ -6,9 +10,10 @@ <%block name="pagetitle">${_("Sign in or Register")} <%block name="js_extra"> - - - <%static:js group='student_account'/> + <%static:require_module module_name="js/student_account/logistration_factory" class_name="LogistrationFactory"> + var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) }; + LogistrationFactory(options); + <%block name="header_extras"> @@ -20,17 +25,7 @@
    -