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 = [
+ '',
+ 'Please select a country ',
+ 'Belgium ',
+ 'Germany ',
+ ' '
+ ].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 = [
- '',
- 'Please select a country ',
- 'Belgium ',
- 'Germany ',
- ' '
- ].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>
<%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);
+ %static:require_module>
%block>
<%block name="header_extras">
@@ -20,17 +25,7 @@
%block>
% if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):