Commit d2a47908 by zubair-arbi

use the standard syntax to load JavaScript dependencies on logistration js files

ECOM-2044
parent 1ba4200a
...@@ -69,8 +69,8 @@ class IntegrationTestLTI(testutil.TestCase): ...@@ -69,8 +69,8 @@ class IntegrationTestLTI(testutil.TestCase):
self.assertTrue(login_response['Location'].endswith(reverse('signin_user'))) self.assertTrue(login_response['Location'].endswith(reverse('signin_user')))
register_response = self.client.get(login_response['Location']) register_response = self.client.get(login_response['Location'])
self.assertEqual(register_response.status_code, 200) self.assertEqual(register_response.status_code, 200)
self.assertIn('currentProvider": "LTI Test Tool Consumer"', register_response.content) self.assertIn('"currentProvider": "LTI Test Tool Consumer"', register_response.content)
self.assertIn('"errorMessage": null', register_response.content) self.assertIn('"errorMessage": null', register_response.content)
# Now complete the form: # Now complete the form:
ajax_register_response = self.client.post( ajax_register_response = self.client.post(
...@@ -153,7 +153,7 @@ class IntegrationTestLTI(testutil.TestCase): ...@@ -153,7 +153,7 @@ class IntegrationTestLTI(testutil.TestCase):
register_response = self.client.get(login_response['Location']) register_response = self.client.get(login_response['Location'])
self.assertEqual(register_response.status_code, 200) self.assertEqual(register_response.status_code, 200)
self.assertIn( self.assertIn(
'currentProvider": "Tool Consumer with Secret in Settings"', '"currentProvider": "Tool Consumer with Secret in Settings"',
register_response.content register_response.content
) )
self.assertIn('"errorMessage": null', register_response.content) self.assertIn('"errorMessage": null', register_response.content)
""" """
Third_party_auth integration tests using a mock version of the TestShib provider 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 import httpretty
from mock import patch 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 student.tests.factories import UserFactory
from third_party_auth.tasks import fetch_saml_metadata from third_party_auth.tasks import fetch_saml_metadata
from third_party_auth.tests import testutil from third_party_auth.tests import testutil
import unittest
TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth' TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml' TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml'
...@@ -81,11 +88,11 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): ...@@ -81,11 +88,11 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
# We'd now like to see if the "You've successfully signed into TestShib" message is # 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 # 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. # type of test, so we just check for the variable that triggers that message.
self.assertIn('"currentProvider": "TestShib"', register_response.content) self.assertIn('"currentProvider": "TestShib"', register_response.content)
self.assertIn('"errorMessage": null', 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: # 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": "myself@testshib.org"', register_response.content)
self.assertIn('"defaultValue": "Me Myself And I"', register_response.content) self.assertIn('"defaultValue": "Me Myself And I"', register_response.content)
# Now complete the form: # Now complete the form:
ajax_register_response = self.client.post( ajax_register_response = self.client.post(
reverse('user_api_registration'), reverse('user_api_registration'),
...@@ -128,8 +135,8 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): ...@@ -128,8 +135,8 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
# We'd now like to see if the "You've successfully signed into TestShib" message is # 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 # 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. # type of test, so we just check for the variable that triggers that message.
self.assertIn('"currentProvider": "TestShib"', login_response.content) self.assertIn('"currentProvider": "TestShib"', login_response.content)
self.assertIn('"errorMessage": null', login_response.content) self.assertIn('"errorMessage": null', login_response.content)
# Now the user enters their username and password. # Now the user enters their username and password.
# The AJAX on the page will log them in: # The AJAX on the page will log them in:
ajax_login_response = self.client.post( ajax_login_response = self.client.post(
...@@ -183,7 +190,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): ...@@ -183,7 +190,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
response = self.client.get(self.login_page_url) response = self.client.get(self.login_page_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content) 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 return response
def _check_register_page(self): def _check_register_page(self):
...@@ -191,7 +198,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase): ...@@ -191,7 +198,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
response = self.client.get(self.register_page_url) response = self.client.get(self.register_page_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content) 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 return response
def _configure_testshib_provider(self, **kwargs): def _configure_testshib_provider(self, **kwargs):
......
describe('edx.utils.validate', function () { ;(function (define) {
'use strict'; 'use strict';
define(['jquery', 'js/utils/edx.utils.validate'],
var fixture = null, function($) {
field = null,
result = null, var fixture = null,
MIN_LENGTH = 2, field = null,
MAX_LENGTH = 20, result = null,
VALID_STRING = 'xsy_is_awesome', MIN_LENGTH = 2,
SHORT_STRING = 'x', MAX_LENGTH = 20,
LONG_STRING = 'xsy_is_way_too_awesome', VALID_STRING = 'xsy_is_awesome',
EMAIL_ERROR_FRAGMENT = 'formatted', SHORT_STRING = 'x',
MIN_ERROR_FRAGMENT = 'least', LONG_STRING = 'xsy_is_way_too_awesome',
MAX_ERROR_FRAGMENT = 'up to', EMAIL_ERROR_FRAGMENT = 'formatted',
REQUIRED_ERROR_FRAGMENT = 'Please enter your', MIN_ERROR_FRAGMENT = 'least',
CUSTOM_MESSAGE = 'custom message'; MAX_ERROR_FRAGMENT = 'up to',
REQUIRED_ERROR_FRAGMENT = 'Please enter your',
var createFixture = function( type, name, required, minlength, maxlength, value ) { CUSTOM_MESSAGE = 'custom message';
setFixtures('<input id="field" type=' + type + '>');
var createFixture = function( type, name, required, minlength, maxlength, value ) {
field = $('#field'); setFixtures('<input id="field" type=' + type + '>');
field.prop('required', required);
field.attr({ field = $('#field');
name: name, field.prop('required', required);
minlength: minlength, field.attr({
maxlength: maxlength, name: name,
value: value 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);
};
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();
});
it('succeeds if a required field is provided a valid value', function () { it('succeeds if a required field is provided a valid value', function () {
createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, VALID_STRING); createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, VALID_STRING);
expectValid(); expectValid();
}); });
it('fails if a required field is left blank', function () { it('fails if a required field is left blank', function () {
createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, '');
expectInvalid(REQUIRED_ERROR_FRAGMENT); expectInvalid(REQUIRED_ERROR_FRAGMENT);
}); });
it('fails if a field is provided a value below its minimum character limit', function () { 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); createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, SHORT_STRING);
// Verify optional field behavior // Verify optional field behavior
expectInvalid(MIN_ERROR_FRAGMENT); expectInvalid(MIN_ERROR_FRAGMENT);
// Verify required field behavior // Verify required field behavior
field.prop('required', true); field.prop('required', true);
expectInvalid(MIN_ERROR_FRAGMENT); expectInvalid(MIN_ERROR_FRAGMENT);
}); });
it('succeeds if a field with no minimum character limit is provided a value below its maximum character limit', function () { 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); createFixture('text', 'username', false, null, MAX_LENGTH, SHORT_STRING);
// Verify optional field behavior // Verify optional field behavior
expectValid(); expectValid();
// Verify required field behavior // Verify required field behavior
field.prop('required', true); field.prop('required', true);
expectValid(); expectValid();
}); });
it('fails if a required field with no minimum character limit is left blank', function () { it('fails if a required field with no minimum character limit is left blank', function () {
createFixture('text', 'username', true, null, MAX_LENGTH, ''); createFixture('text', 'username', true, null, MAX_LENGTH, '');
expectInvalid(REQUIRED_ERROR_FRAGMENT); expectInvalid(REQUIRED_ERROR_FRAGMENT);
}); });
it('fails if a field is provided a value above its maximum character limit', function () { 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); createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, LONG_STRING);
// Verify optional field behavior // Verify optional field behavior
expectInvalid(MAX_ERROR_FRAGMENT); expectInvalid(MAX_ERROR_FRAGMENT);
// Verify required field behavior // Verify required field behavior
field.prop('required', true); field.prop('required', true);
expectInvalid(MAX_ERROR_FRAGMENT); expectInvalid(MAX_ERROR_FRAGMENT);
}); });
it('succeeds if a field with no maximum character limit is provided a value above its minimum character limit', function () { 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); createFixture('text', 'username', false, MIN_LENGTH, null, LONG_STRING);
// Verify optional field behavior // Verify optional field behavior
expectValid(); expectValid();
// Verify required field behavior // Verify required field behavior
field.prop('required', true); field.prop('required', true);
expectValid(); expectValid();
}); });
it('succeeds if a field with no character limits is provided a value', function () { it('succeeds if a field with no character limits is provided a value', function () {
createFixture('text', 'username', false, null, null, VALID_STRING); createFixture('text', 'username', false, null, null, VALID_STRING);
// Verify optional field behavior // Verify optional field behavior
expectValid(); expectValid();
// Verify required field behavior // Verify required field behavior
field.prop('required', true); field.prop('required', true);
expectValid(); expectValid();
}); });
it('fails if an email field is provided an invalid address', function () { it('fails if an email field is provided an invalid address', function () {
createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart'); createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart');
// Verify optional field behavior // Verify optional field behavior
expectInvalid(EMAIL_ERROR_FRAGMENT); expectInvalid(EMAIL_ERROR_FRAGMENT);
// Verify required field behavior // Verify required field behavior
field.prop('required', false); field.prop('required', false);
expectInvalid(EMAIL_ERROR_FRAGMENT); expectInvalid(EMAIL_ERROR_FRAGMENT);
}); });
it('succeeds if an email field is provided a valid address', function () { it('succeeds if an email field is provided a valid address', function () {
createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart@label.tld'); createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart@label.tld');
// Verify optional field behavior // Verify optional field behavior
expectValid(); expectValid();
// Verify required field behavior // Verify required field behavior
field.prop('required', true); field.prop('required', true);
expectValid(); expectValid();
}); });
it('succeeds if a checkbox is optional, or required and checked, but fails if a required checkbox is unchecked', function () { 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'); createFixture('checkbox', 'checkbox', false, null, null, 'value');
// Optional, unchecked // Optional, unchecked
expectValid(); expectValid();
// Optional, checked // Optional, checked
field.prop('checked', true); field.prop('checked', true);
expectValid(); expectValid();
// Required, checked // Required, checked
field.prop('required', true); field.prop('required', true);
expectValid(); expectValid();
// Required, unchecked // Required, unchecked
field.prop('checked', false); field.prop('checked', false);
expectInvalid(REQUIRED_ERROR_FRAGMENT); 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 () { 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 = [ var select = [
'<select id="dropdown" name="country">', '<select id="dropdown" name="country">',
'<option value="" data-isdefault="true">Please select a country</option>', '<option value="" data-isdefault="true">Please select a country</option>',
'<option value="BE">Belgium</option>', '<option value="BE">Belgium</option>',
'<option value="DE">Germany</option>', '<option value="DE">Germany</option>',
'</select>' '</select>'
].join(''); ].join('');
setFixtures(select); setFixtures(select);
field = $('#dropdown'); field = $('#dropdown');
// Optional // Optional
expectValid(); expectValid();
// Required, default text selected // Required, default text selected
field.attr('required', true); field.attr('required', true);
expectInvalid(REQUIRED_ERROR_FRAGMENT); expectInvalid(REQUIRED_ERROR_FRAGMENT);
// Required, country selected // Required, country selected
field.val('BE'); field.val('BE');
expectValid(); expectValid();
}); });
it('returns a custom error message if an invalid field has one attached', function () { it('returns a custom error message if an invalid field has one attached', function () {
// Create a blank required field // Create a blank required field
createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, '');
// Attach a custom error message to the field // Attach a custom error message to the field
field.data('errormsg-required', CUSTOM_MESSAGE); field.data('errormsg-required', CUSTOM_MESSAGE);
expectInvalid(CUSTOM_MESSAGE); expectInvalid(CUSTOM_MESSAGE);
});
}); });
}); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function( $, _, _s, gettext ) {
'use strict'; '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.
*/
if (_.isUndefined(_s)) {
_s = _.str;
}
_.mixin( _s.exports() );
utils = (function(){
var _fn = {
validate: {
msg: {
email: '<li><%- gettext("The email address you\'ve provided isn\'t formatted correctly.") %></li>',
min: '<li><%- _.sprintf( gettext("%(field)s must have at least %(count)d characters."), context ) %></li>',
max: '<li><%- _.sprintf( gettext("%(field)s can only contain up to %(count)d characters."), context ) %></li>',
required: '<li><%- _.sprintf( gettext("Please enter your %(field)s."), context ) %></li>',
custom: '<li><%= content %></li>'
},
/* Mix non-conflicting functions from underscore.string field: function( el ) {
* (all but include, contains, and reverse) into the var $el = $(el),
* Underscore namespace. In practice, this mixin is done required = true,
* by the access view, but doing it here helps keep the min = true,
* utility self-contained. max = true,
*/ email = true,
_.mixin( _.str.exports() ); response = {},
isBlank = _fn.validate.isBlank( $el );
edx.utils = edx.utils || {};
if ( _fn.validate.isRequired( $el ) ) {
var utils = (function(){ if ( isBlank ) {
var _fn = { required = false;
validate: { } else {
min = _fn.validate.str.minlength( $el );
msg: { max = _fn.validate.str.maxlength( $el );
email: '<li><%- gettext("The email address you\'ve provided isn\'t formatted correctly.") %></li>', email = _fn.validate.email.valid( $el );
min: '<li><%- _.sprintf( gettext("%(field)s must have at least %(count)d characters."), context ) %></li>', }
max: '<li><%- _.sprintf( gettext("%(field)s can only contain up to %(count)d characters."), context ) %></li>', } else if ( !isBlank ) {
required: '<li><%- _.sprintf( gettext("Please enter your %(field)s."), context ) %></li>',
custom: '<li><%= content %></li>'
},
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 {
min = _fn.validate.str.minlength( $el ); min = _fn.validate.str.minlength( $el );
max = _fn.validate.str.maxlength( $el ); max = _fn.validate.str.maxlength( $el );
email = _fn.validate.email.valid( $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 ) { if ( !response.isValid ) {
_fn.validate.removeDefault( $el ); _fn.validate.removeDefault( $el );
response.message = _fn.validate.getMessage( $el, { response.message = _fn.validate.getMessage( $el, {
required: required, required: required,
min: min, min: min,
max: max, max: max,
email: email email: email
}); });
} }
return response; return response;
}, },
str: {
minlength: function( $el ) {
var min = $el.attr('minlength') || 0;
str: { return min <= $el.val().length;
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;
}
}, },
maxlength: function( $el ) { isRequired: function( $el ) {
var max = $el.attr('maxlength') || false; return $el.attr('required');
},
return ( !!max ) ? max >= $el.val().length : true; isBlank: function( $el ) {
} var type = $el.attr('type'),
}, isBlank;
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; if ( type === 'checkbox' ) {
}, isBlank = !$el.prop('checked');
} else if ( type === 'select' ) {
email: { isBlank = ( $el.data('isdefault') === true );
// This is the same regex used to validate email addresses in Django 1.4 } else {
regex: new RegExp( isBlank = !$el.val();
[ }
'(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*',
'|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"', return isBlank;
')@((?:[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 ) { email: {
return _fn.validate.email.regex.test( str ); // This is the same regex used to validate email addresses in Django 1.4
} regex: new RegExp(
}, [
'(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*',
getLabel: function( id ) { '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"',
// Extract the field label, remove the asterisk (if it appears) and any extra whitespace ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)',
return $("label[for=" + id + "]").text().split("*")[0].trim(); '|\\[(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'
),
getMessage: function( $el, tests ) {
var txt = [], valid: function( $el ) {
tpl, return $el.attr('type') === 'email' ? _fn.validate.email.format( $el.val() ) : true;
label, },
obj,
customMsg; format: function( str ) {
return _fn.validate.email.regex.test( str );
_.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 = { getLabel: function( id ) {
// We pass the context object to the template so that // Extract the field label, remove the asterisk (if it appears) and any extra whitespace
// we can perform variable interpolation using sprintf return $("label[for=" + id + "]").text().split("*")[0].trim();
context: { },
field: label
}
};
if ( key === 'min' ) { getMessage: function( $el, tests ) {
obj.context.count = parseInt( $el.attr('minlength'), 10 ); var txt = [],
} else if ( key === 'max' ) { tpl,
obj.context.count = parseInt( $el.attr('maxlength'), 10 ); 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 ) ); txt.push( _.template( tpl, obj ) );
} }
}); });
return txt.join(' '); return txt.join(' ');
}, },
// Removes the default HTML5 validation pop-up // Removes the default HTML5 validation pop-up
removeDefault: function( $el ) { removeDefault: function( $el ) {
if ( $el.setCustomValidity ) { if ( $el.setCustomValidity ) {
$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);
...@@ -8,7 +8,6 @@ import json ...@@ -8,7 +8,6 @@ import json
import mock import mock
import ddt import ddt
import markupsafe
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core import mail from django.core import mail
...@@ -20,6 +19,7 @@ from django.test.client import RequestFactory ...@@ -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.api import activate_account, create_account
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH 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.tests.factories import UserFactory
from student_account.views import account_settings_context from student_account.views import account_settings_context
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
...@@ -223,7 +223,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ...@@ -223,7 +223,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
@ddt.unpack @ddt.unpack
def test_login_and_registration_form(self, url_name, initial_mode): def test_login_and_registration_form(self, url_name, initial_mode):
response = self.client.get(reverse(url_name)) 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) self.assertContains(response, expected_data)
@ddt.data("signin_user", "register_user") @ddt.data("signin_user", "register_user")
...@@ -255,6 +255,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ...@@ -255,6 +255,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
# that preserves the querystring params # that preserves the querystring params
with mock.patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': is_edx_domain}): with mock.patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': is_edx_domain}):
response = self.client.get(reverse(url_name), params) response = self.client.get(reverse(url_name), params)
expected_url = '/login?{}'.format(self._finish_auth_url_param(params + [('next', '/dashboard')])) expected_url = '/login?{}'.format(self._finish_auth_url_param(params + [('next', '/dashboard')]))
self.assertContains(response, expected_url) self.assertContains(response, expected_url)
...@@ -330,7 +331,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ...@@ -330,7 +331,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
def test_hinted_login(self): def test_hinted_login(self):
params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")] params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
response = self.client.get(reverse('signin_user'), params) 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) @override_settings(SITE_NAME=settings.MICROSITE_TEST_HOSTNAME)
def test_microsite_uses_old_login_page(self): def test_microsite_uses_old_login_page(self):
...@@ -358,17 +359,17 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ...@@ -358,17 +359,17 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
finish_auth_url = None finish_auth_url = None
if current_backend: if current_backend:
finish_auth_url = reverse("social:complete", kwargs={"backend": 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 auth_info=auth_info
) )
......
...@@ -95,22 +95,25 @@ def login_and_registration_form(request, initial_mode="login"): ...@@ -95,22 +95,25 @@ def login_and_registration_form(request, initial_mode="login"):
# Otherwise, render the combined login/registration page # Otherwise, render the combined login/registration page
context = { context = {
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header 'data': {
'disable_courseware_js': True, 'login_redirect_url': redirect_to,
'initial_mode': initial_mode, 'initial_mode': initial_mode,
'third_party_auth': json.dumps(_third_party_auth_context(request, redirect_to)), 'third_party_auth': _third_party_auth_context(request, redirect_to),
'third_party_auth_hint': third_party_auth_hint or '', 'third_party_auth_hint': third_party_auth_hint or '',
'platform_name': settings.PLATFORM_NAME, '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, 'responsive': True,
'allow_iframing': True, 'allow_iframing': True,
'disable_courseware_js': 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'],
} }
return render_to_response('student_account/login_and_register.html', context) return render_to_response('student_account/login_and_register.html', context)
......
...@@ -1303,26 +1303,6 @@ instructor_dash_js = ( ...@@ -1303,26 +1303,6 @@ instructor_dash_js = (
sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.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 = [ verify_student_js = [
'js/sticky_filter.js', 'js/sticky_filter.js',
'js/query-params.js', 'js/query-params.js',
...@@ -1574,10 +1554,6 @@ PIPELINE_JS = { ...@@ -1574,10 +1554,6 @@ PIPELINE_JS = {
'source_filenames': dashboard_js, 'source_filenames': dashboard_js,
'output_filename': 'js/dashboard.js' 'output_filename': 'js/dashboard.js'
}, },
'student_account': {
'source_filenames': student_account_js,
'output_filename': 'js/student_account.js'
},
'verify_student': { 'verify_student': {
'source_filenames': verify_student_js, 'source_filenames': verify_student_js,
'output_filename': 'js/verify_student.js' 'output_filename': 'js/verify_student.js'
......
...@@ -75,16 +75,6 @@ ...@@ -75,16 +75,6 @@
'js/views/file_uploader': 'js/views/file_uploader', 'js/views/file_uploader': 'js/views/file_uploader',
'js/views/notification': 'js/views/notification', 'js/views/notification': 'js/views/notification',
'js/student_account/account': 'js/student_account/account', '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_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_factory': 'js/student_profile/views/learner_profile_factory',
'js/student_profile/views/learner_profile_view': 'js/student_profile/views/learner_profile_view', 'js/student_profile/views/learner_profile_view': 'js/student_profile/views/learner_profile_view',
...@@ -94,7 +84,10 @@ ...@@ -94,7 +84,10 @@
'DiscussionModuleView': 'xmodule_js/common_static/coffee/src/discussion/discussion_module_view', 'DiscussionModuleView': 'xmodule_js/common_static/coffee/src/discussion/discussion_module_view',
// edxnotes // 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: { shim: {
'gettext': { 'gettext': {
...@@ -337,91 +330,6 @@ ...@@ -337,91 +330,6 @@
'js/models/notification', 'jquery.fileupload' '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': { 'js/verify_student/models/verification_model': {
exports: 'edx.verify_student.VerificationModel', exports: 'edx.verify_student.VerificationModel',
deps: [ 'jquery', 'underscore', 'backbone', 'jquery.cookie' ] deps: [ 'jquery', 'underscore', 'backbone', 'jquery.cookie' ]
...@@ -731,6 +639,7 @@ ...@@ -731,6 +639,7 @@
'lms/include/js/spec/instructor_dashboard/student_admin_spec.js', '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/account_spec.js',
'lms/include/js/spec/student_account/access_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/finish_auth_spec.js',
'lms/include/js/spec/student_account/hinted_login_spec.js', 'lms/include/js/spec/student_account/hinted_login_spec.js',
'lms/include/js/spec/student_account/login_spec.js', 'lms/include/js/spec/student_account/login_spec.js',
......
define([ ;(function (define) {
'jquery', 'use strict';
'common/js/spec_helpers/template_helpers', define([
'common/js/spec_helpers/ajax_helpers', 'jquery',
'js/student_account/views/AccessView', 'underscore',
'js/student_account/views/FormView', 'backbone',
'js/student_account/enrollment', 'common/js/spec_helpers/template_helpers',
'js/student_account/shoppingcart', 'common/js/spec_helpers/ajax_helpers',
'js/student_account/emailoptin' 'js/student_account/views/AccessView',
], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) { 'js/student_account/views/FormView',
"use strict"; '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() { describe('edx.student.account.AccessView', function() {
var requests = null, var requests = null,
view = null, view = null,
...@@ -24,7 +30,7 @@ define([ ...@@ -24,7 +30,7 @@ define([
required: true, required: true,
placeholder: 'xsy@edx.org', placeholder: 'xsy@edx.org',
instructions: 'Enter your email here.', instructions: 'Enter your email here.',
restrictions: {}, restrictions: {}
}, },
{ {
name: 'username', name: 'username',
...@@ -49,24 +55,27 @@ define([ ...@@ -49,24 +55,27 @@ define([
THIRD_PARTY_COMPLETE_URL = '/auth/complete/provider/'; THIRD_PARTY_COMPLETE_URL = '/auth/complete/provider/';
var ajaxSpyAndInitialize = function(that, mode, nextUrl, finishAuthUrl) { 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 // Spy on AJAX requests
requests = AjaxHelpers.requests(that); requests = AjaxHelpers.requests(that);
// Initialize the access view // Initialize the access view
view = new AccessView({ view = new AccessView(_.extend(options, {el: $logistrationElement}));
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
});
// Mock the redirect call // Mock the redirect call
spyOn( view, 'redirect' ).andCallFake( function() {} ); spyOn( view, 'redirect' ).andCallFake( function() {} );
...@@ -92,7 +101,7 @@ define([ ...@@ -92,7 +101,7 @@ define([
}; };
beforeEach(function() { beforeEach(function() {
setFixtures('<div id="login-and-registration-container"></div>'); setFixtures('<div id="login-and-registration-container" class="login-register" />');
TemplateHelpers.installTemplate('templates/student_account/access'); TemplateHelpers.installTemplate('templates/student_account/access');
TemplateHelpers.installTemplate('templates/student_account/login'); TemplateHelpers.installTemplate('templates/student_account/login');
TemplateHelpers.installTemplate('templates/student_account/register'); TemplateHelpers.installTemplate('templates/student_account/register');
...@@ -105,6 +114,10 @@ define([ ...@@ -105,6 +114,10 @@ define([
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'pageview', 'trackLink']); window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'pageview', 'trackLink']);
}); });
afterEach(function() {
Backbone.history.stop();
});
it('can initially display the login form', function() { it('can initially display the login form', function() {
ajaxSpyAndInitialize(this, 'login'); ajaxSpyAndInitialize(this, 'login');
...@@ -217,5 +230,5 @@ define([ ...@@ -217,5 +230,5 @@ define([
}); });
}); });
} });
); }).call(this, define || RequireJS.define);
define([ ;(function (define) {
'jquery', 'use strict';
'utility', define([
'common/js/spec_helpers/ajax_helpers', 'jquery',
'js/student_account/views/FinishAuthView', 'jquery.url',
'js/student_account/enrollment', 'utility',
'js/student_account/shoppingcart', 'common/js/spec_helpers/ajax_helpers',
'js/student_account/emailoptin' 'js/student_account/views/FinishAuthView',
], function($, utility, AjaxHelpers, FinishAuthView, EnrollmentInterface, ShoppingCartInterface, EmailOptInInterface) { 'js/student_account/enrollment',
'use strict'; 'js/student_account/shoppingcart',
'js/student_account/emailoptin'
],
function($, url, utility, AjaxHelpers, FinishAuthView, EnrollmentInterface, ShoppingCartInterface,
EmailOptInInterface) {
describe('FinishAuthView', function() { describe('FinishAuthView', function() {
var requests = null, var requests = null,
view = null, view = null,
...@@ -167,5 +172,5 @@ define([ ...@@ -167,5 +172,5 @@ define([
expect( view.redirect ).toHaveBeenCalledWith( "/dashboard" ); expect( view.redirect ).toHaveBeenCalledWith( "/dashboard" );
}); });
}); });
} });
); }).call(this, define || RequireJS.define);
define([ ;(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) {
'use strict'; '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, describe('edx.student.account.HintedLoginView', function() {
requests = null, var view = null,
PLATFORM_NAME = 'edX', requests = null,
THIRD_PARTY_AUTH = { PLATFORM_NAME = 'edX',
currentProvider: null, THIRD_PARTY_AUTH = {
providers: [ currentProvider: null,
{ providers: [
id: 'oa2-google-oauth2', {
name: 'Google', id: 'oa2-google-oauth2',
iconClass: 'fa-google-plus', name: 'Google',
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', iconClass: 'fa-google-plus',
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
}, registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
{ },
id: 'oa2-facebook', {
name: 'Facebook', id: 'oa2-facebook',
iconClass: 'fa-facebook', name: 'Facebook',
loginUrl: '/auth/login/facebook/?auth_entry=account_login', iconClass: 'fa-facebook',
registerUrl: '/auth/login/facebook/?auth_entry=account_register' loginUrl: '/auth/login/facebook/?auth_entry=account_login',
} registerUrl: '/auth/login/facebook/?auth_entry=account_register'
], }
secondaryProviders: [ ],
{ secondaryProviders: [
id: 'saml-harvard', {
name: 'Harvard', id: 'saml-harvard',
iconClass: 'fa-university', name: 'Harvard',
loginUrl: '/auth/login/tpa-saml/?auth_entry=account_login&idp=harvard', iconClass: 'fa-university',
registerUrl: '/auth/login/tpa-saml/?auth_entry=account_register&idp=harvard' 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) { var createHintedLoginView = function(hintedProvider) {
// Initialize the login view // Initialize the login view
view = new HintedLoginView({ view = new HintedLoginView({
thirdPartyAuth: THIRD_PARTY_AUTH, thirdPartyAuth: THIRD_PARTY_AUTH,
hintedProvider: hintedProvider, hintedProvider: hintedProvider,
platformName: PLATFORM_NAME platformName: PLATFORM_NAME
}); });
// Mock the redirect call // Mock the redirect call
spyOn( view, 'redirect' ).andCallFake( function() {} ); spyOn( view, 'redirect' ).andCallFake( function() {} );
view.render(); view.render();
}; };
beforeEach(function() { beforeEach(function() {
setFixtures('<div id="hinted-login-form"></div>'); setFixtures('<div id="hinted-login-form"></div>');
TemplateHelpers.installTemplate('templates/student_account/hinted_login'); TemplateHelpers.installTemplate('templates/student_account/hinted_login');
}); });
it('displays a choice as two buttons', function() { it('displays a choice as two buttons', function() {
createHintedLoginView("oa2-google-oauth2"); createHintedLoginView("oa2-google-oauth2");
expect($('.proceed-button.button-oa2-google-oauth2')).toBeVisible(); expect($('.proceed-button.button-oa2-google-oauth2')).toBeVisible();
expect($('.form-toggle')).toBeVisible(); expect($('.form-toggle')).toBeVisible();
expect($('.proceed-button.button-oa2-facebook')).not.toBeVisible(); expect($('.proceed-button.button-oa2-facebook')).not.toBeVisible();
}); });
it('works with secondary providers as well', function() { it('works with secondary providers as well', function() {
createHintedLoginView("saml-harvard"); createHintedLoginView("saml-harvard");
expect($('.proceed-button.button-saml-harvard')).toBeVisible(); expect($('.proceed-button.button-saml-harvard')).toBeVisible();
expect($('.form-toggle')).toBeVisible(); expect($('.form-toggle')).toBeVisible();
expect($('.proceed-button.button-oa2-google-oauth2')).not.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() { it('redirects the user to the hinted provider if the user clicks the proceed button', function() {
createHintedLoginView("oa2-google-oauth2"); createHintedLoginView('oa2-google-oauth2');
// Click the "Yes, proceed" button // Click the "Yes, proceed" button
$('.proceed-button').click(); $('.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);
define([ ;(function (define) {
'jquery',
'underscore',
'common/js/spec_helpers/template_helpers',
'js/student_account/views/InstitutionLoginView',
], function($, _, TemplateHelpers, InstitutionLoginView) {
'use strict'; '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, describe('edx.student.account.InstitutionLoginView', function() {
PLATFORM_NAME = 'edX', var view = null,
THIRD_PARTY_AUTH = { PLATFORM_NAME = 'edX',
currentProvider: null, THIRD_PARTY_AUTH = {
providers: [], currentProvider: null,
secondaryProviders: [ providers: [],
{ secondaryProviders: [
id: 'oa2-google-oauth2', {
name: 'Google', id: 'oa2-google-oauth2',
iconClass: 'fa-google-plus', name: 'Google',
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', iconClass: 'fa-google-plus',
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
}, registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
{ },
id: 'oa2-facebook', {
name: 'Facebook', id: 'oa2-facebook',
iconClass: 'fa-facebook', name: 'Facebook',
loginUrl: '/auth/login/facebook/?auth_entry=account_login', iconClass: 'fa-facebook',
registerUrl: '/auth/login/facebook/?auth_entry=account_register' 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) { beforeEach(function() {
// Initialize the login view setFixtures('<div id="institution_login-form"></div>');
view = new InstitutionLoginView({ TemplateHelpers.installTemplate('templates/student_account/institution_login');
mode: mode, TemplateHelpers.installTemplate('templates/student_account/institution_register');
thirdPartyAuth: THIRD_PARTY_AUTH,
platformName: PLATFORM_NAME
}); });
view.render();
};
beforeEach(function() { it('displays a list of providers', function() {
setFixtures('<div id="institution_login-form"></div>'); var $google, $facebook;
TemplateHelpers.installTemplate('templates/student_account/institution_login');
TemplateHelpers.installTemplate('templates/student_account/institution_register');
});
it('displays a list of providers', function() { createInstLoginView('login');
createInstLoginView('login'); expect($('#institution_login-form').html()).not.toBe("");
expect($('#institution_login-form').html()).not.toBe(""); $google = $('li a:contains("Google")');
var $google = $('li a:contains("Google")'); expect($google).toBeVisible();
expect($google).toBeVisible(); expect($google).toHaveAttr(
expect($google).toHaveAttr( 'href', '/auth/login/google-oauth2/?auth_entry=account_login'
'href', '/auth/login/google-oauth2/?auth_entry=account_login' );
); $facebook = $('li a:contains("Facebook")');
var $facebook = $('li a:contains("Facebook")'); expect($facebook).toBeVisible();
expect($facebook).toBeVisible(); expect($facebook).toHaveAttr(
expect($facebook).toHaveAttr( 'href', '/auth/login/facebook/?auth_entry=account_login'
'href', '/auth/login/facebook/?auth_entry=account_login' );
); });
});
it('displays a list of providers', function() { it('displays a list of providers', function() {
createInstLoginView('register'); var $google, $facebook;
expect($('#institution_login-form').html()).not.toBe("");
var $google = $('li a:contains("Google")'); createInstLoginView('register');
expect($google).toBeVisible(); expect($('#institution_login-form').html()).not.toBe("");
expect($google).toHaveAttr( $google = $('li a:contains("Google")');
'href', '/auth/login/google-oauth2/?auth_entry=account_register' expect($google).toBeVisible();
); expect($google).toHaveAttr(
var $facebook = $('li a:contains("Facebook")'); 'href', '/auth/login/google-oauth2/?auth_entry=account_register'
expect($facebook).toBeVisible(); );
expect($facebook).toHaveAttr( $facebook = $('li a:contains("Facebook")');
'href', '/auth/login/facebook/?auth_entry=account_register' expect($facebook).toBeVisible();
); expect($facebook).toHaveAttr(
}); 'href', '/auth/login/facebook/?auth_entry=account_register'
);
});
});
}); });
}); }).call(this, define || RequireJS.define);
define([ ;(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) {
'use strict'; 'use strict';
describe('edx.student.account.LoginView', function() { define([
'jquery',
var model = null, 'underscore',
resetModel = null, 'common/js/spec_helpers/template_helpers',
view = null, 'common/js/spec_helpers/ajax_helpers',
requests = null, 'js/student_account/models/LoginModel',
authComplete = false, 'js/student_account/views/LoginView',
PLATFORM_NAME = 'edX', 'js/student_account/models/PasswordResetModel'
USER_DATA = { ],
email: 'xsy@edx.org', function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView, PasswordResetModel) {
password: 'xsyisawesome',
remember: true describe('edx.student.account.LoginView', function() {
}, var model = null,
THIRD_PARTY_AUTH = { resetModel = null,
currentProvider: null, view = null,
providers: [ requests = null,
{ authComplete = false,
id: 'oa2-google-oauth2', PLATFORM_NAME = 'edX',
name: 'Google', USER_DATA = {
iconClass: 'fa-google-plus', email: 'xsy@edx.org',
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login', password: 'xsyisawesome',
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register' remember: true
}, },
{ THIRD_PARTY_AUTH = {
id: 'oa2-facebook', currentProvider: null,
name: 'Facebook', providers: [
iconClass: 'fa-facebook', {
loginUrl: '/auth/login/facebook/?auth_entry=account_login', id: 'oa2-google-oauth2',
registerUrl: '/auth/login/facebook/?auth_entry=account_register' name: 'Google',
} iconClass: 'fa-google-plus',
] loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
}, registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
FORM_DESCRIPTION = { },
method: 'post', {
submit_url: '/user_api/v1/account/login_session/', id: 'oa2-facebook',
fields: [ name: 'Facebook',
{ iconClass: 'fa-facebook',
placeholder: 'username@domain.com', loginUrl: '/auth/login/facebook/?auth_entry=account_login',
name: 'email', registerUrl: '/auth/login/facebook/?auth_entry=account_register'
label: 'Email', }
defaultValue: '', ]
type: 'email', },
required: true, FORM_DESCRIPTION = {
instructions: 'Enter your email.', method: 'post',
restrictions: {} submit_url: '/user_api/v1/account/login_session/',
}, fields: [
{ {
placeholder: '', placeholder: 'username@domain.com',
name: 'password', name: 'email',
label: 'Password', label: 'Email',
defaultValue: '', defaultValue: '',
type: 'password', type: 'email',
required: true, required: true,
instructions: 'Enter your password.', instructions: 'Enter your email.',
restrictions: {} restrictions: {}
}, },
{ {
placeholder: '', placeholder: '',
name: 'remember', name: 'password',
label: 'Remember me', label: 'Password',
defaultValue: '', defaultValue: '',
type: 'checkbox', type: 'password',
required: true, required: true,
instructions: "Agree to the terms of service.", instructions: 'Enter your password.',
restrictions: {} restrictions: {}
} },
] {
}, placeholder: '',
COURSE_ID = "edX/demoX/Fall"; name: 'remember',
label: 'Remember me',
var createLoginView = function(test) { defaultValue: '',
// Initialize the login model type: 'checkbox',
model = new LoginModel({}, { required: true,
url: FORM_DESCRIPTION.submit_url, instructions: 'Agree to the terms of service.',
method: FORM_DESCRIPTION.method 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 // Initialize the passwordReset model
resetModel = new PasswordResetModel({}, { resetModel = new PasswordResetModel({}, {
method: 'GET', method: 'GET',
url: '#' url: '#'
}); });
// Initialize the login view // Initialize the login view
view = new LoginView({ view = new LoginView({
fields: FORM_DESCRIPTION.fields, fields: FORM_DESCRIPTION.fields,
model: model, model: model,
resetModel: resetModel, resetModel: resetModel,
thirdPartyAuth: THIRD_PARTY_AUTH, thirdPartyAuth: THIRD_PARTY_AUTH,
platformName: PLATFORM_NAME platformName: PLATFORM_NAME
}); });
// Spy on AJAX requests // Spy on AJAX requests
requests = AjaxHelpers.requests(test); requests = AjaxHelpers.requests(test);
// Intercept events from the view // Intercept events from the view
authComplete = false; authComplete = false;
view.on("auth-complete", function() { view.on("auth-complete", function() {
authComplete = true; 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 submitForm = function(validationSuccess) {
// Create a fake click event
var clickEvent = $.Event('click');
// 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);
// 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 // Submit the email address
view.submitForm(clickEvent); view.submitForm(clickEvent);
}; };
beforeEach(function() { beforeEach(function() {
setFixtures('<div id="login-form"></div>'); setFixtures('<div id="login-form"></div>');
TemplateHelpers.installTemplate('templates/student_account/login'); TemplateHelpers.installTemplate('templates/student_account/login');
TemplateHelpers.installTemplate('templates/student_account/form_field'); TemplateHelpers.installTemplate('templates/student_account/form_field');
}); });
it('logs the user in', function() { it('logs the user in', function() {
createLoginView(this); createLoginView(this);
// Submit the form, with successful validation // Submit the form, with successful validation
submitForm(true); submitForm(true);
// Form button should be disabled on success. // Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled'); expect(view.$submitButton).toHaveAttr('disabled');
// Verify that the client contacts the server with the expected data // Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest( AjaxHelpers.expectRequest(
requests, 'POST', requests, 'POST',
FORM_DESCRIPTION.submit_url, FORM_DESCRIPTION.submit_url,
$.param(USER_DATA) $.param(USER_DATA)
); );
// Respond with status code 200 // Respond with status code 200
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
// Verify that auth-complete is triggered // Verify that auth-complete is triggered
expect(authComplete).toBe(true); expect(authComplete).toBe(true);
}); });
it('sends analytics info containing the enrolled course ID', function() { it('sends analytics info containing the enrolled course ID', function() {
createLoginView(this); var expectedData;
// Simulate that the user is attempting to enroll in a course createLoginView(this);
// by setting the course_id query string param.
spyOn($, 'url').andCallFake(function( param ) {
if (param === "?course_id") {
return encodeURIComponent( COURSE_ID );
}
});
// Attempt to login // Simulate that the user is attempting to enroll in a course
submitForm( true ); // by setting the course_id query string param.
spyOn($, 'url').andCallFake(function( param ) {
if (param === '?course_id') {
return encodeURIComponent( COURSE_ID );
}
});
// Verify that the client sent the course ID for analytics // Attempt to login
var expectedData = {}; submitForm( true );
$.extend(expectedData, USER_DATA, {
analytics: JSON.stringify({
enroll_course_id: COURSE_ID
})
});
AjaxHelpers.expectRequest( // Verify that the client sent the course ID for analytics
requests, 'POST', expectedData = {};
FORM_DESCRIPTION.submit_url, $.extend(expectedData, USER_DATA, {
$.param( expectedData ) analytics: JSON.stringify({
); enroll_course_id: COURSE_ID
}); })
});
AjaxHelpers.expectRequest(
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param( expectedData )
);
});
it('displays third-party auth login buttons', function() { it('displays third-party auth login buttons', function() {
createLoginView(this); createLoginView(this);
// Verify that Google and Facebook registration buttons are displayed // Verify that Google and Facebook registration buttons are displayed
expect($('.button-oa2-google-oauth2')).toBeVisible(); expect($('.button-oa2-google-oauth2')).toBeVisible();
expect($('.button-oa2-facebook')).toBeVisible(); expect($('.button-oa2-facebook')).toBeVisible();
}); });
it('displays a link to the password reset form', function() { it('displays a link to the password reset form', function() {
createLoginView(this); createLoginView(this);
// Verify that the password reset link is displayed // Verify that the password reset link is displayed
expect($('.forgot-password')).toBeVisible(); expect($('.forgot-password')).toBeVisible();
}); });
it('validates login form fields', function() { it('validates login form fields', function() {
createLoginView(this); createLoginView(this);
submitForm(true); submitForm(true);
// Verify that validation of form fields occurred // Verify that validation of form fields occurred
expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]); expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]);
expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]); expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]);
}); });
it('displays login form validation errors', function() { it('displays login form validation errors', function() {
createLoginView(this); createLoginView(this);
// Submit the form, with failed validation // Submit the form, with failed validation
submitForm(false); submitForm(false);
// Verify that submission errors are visible // Verify that submission errors are visible
expect(view.$errors).not.toHaveClass('hidden'); expect(view.$errors).not.toHaveClass('hidden');
// Expect auth complete NOT to have been triggered // Expect auth complete NOT to have been triggered
expect(authComplete).toBe(false); expect(authComplete).toBe(false);
// Form button should be re-enabled when errors occur // Form button should be re-enabled when errors occur
expect(view.$submitButton).not.toHaveAttr('disabled'); expect(view.$submitButton).not.toHaveAttr('disabled');
}); });
it('displays an error if the server returns an error while logging in', function() { it('displays an error if the server returns an error while logging in', function() {
createLoginView(this); createLoginView(this);
// Submit the form, with successful validation // Submit the form, with successful validation
submitForm(true); submitForm(true);
// Simulate an error from the LMS servers // Simulate an error from the LMS servers
AjaxHelpers.respondWithError(requests); AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed and that auth complete is not triggered // Expect that an error is displayed and that auth complete is not triggered
expect(view.$errors).not.toHaveClass('hidden'); expect(view.$errors).not.toHaveClass('hidden');
expect(authComplete).toBe(false); expect(authComplete).toBe(false);
// Form button should be re-enabled on server failure. // Form button should be re-enabled on server failure.
expect(view.$submitButton).not.toHaveAttr('disabled'); expect(view.$submitButton).not.toHaveAttr('disabled');
// If we try again and succeed, the error should go away // If we try again and succeed, the error should go away
submitForm(); submitForm();
// Form button should be disabled on success. // Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled'); expect(view.$submitButton).toHaveAttr('disabled');
// This time, respond with status code 200 // This time, respond with status code 200
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden and auth complete is triggered // Expect that the error is hidden and auth complete is triggered
expect(view.$errors).toHaveClass('hidden'); expect(view.$errors).toHaveClass('hidden');
expect(authComplete).toBe(true); expect(authComplete).toBe(true);
});
}); });
}); });
}); }).call(this, define || RequireJS.define);
;(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('<div id="login-and-registration-container" class="login-register" />');
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);
define([ ;(function (define) {
'jquery', 'use strict';
'underscore', define([
'common/js/spec_helpers/template_helpers', 'jquery',
'common/js/spec_helpers/ajax_helpers', 'underscore',
'js/student_account/models/PasswordResetModel', 'common/js/spec_helpers/template_helpers',
'js/student_account/views/PasswordResetView', 'common/js/spec_helpers/ajax_helpers',
], function($, _, TemplateHelpers, AjaxHelpers, PasswordResetModel, PasswordResetView) { 'js/student_account/models/PasswordResetModel',
describe('edx.student.account.PasswordResetView', function() { 'js/student_account/views/PasswordResetView'
'use strict'; ],
function($, _, TemplateHelpers, AjaxHelpers, PasswordResetModel, PasswordResetView) {
describe('edx.student.account.PasswordResetView', function() {
var model = null, var model = null,
view = null, view = null,
requests = null, requests = null,
...@@ -46,12 +48,12 @@ define([ ...@@ -46,12 +48,12 @@ define([
}; };
var submitEmail = function(validationSuccess) { var submitEmail = function(validationSuccess) {
// Simulate manual entry of an email address
$('#password-reset-email').val(EMAIL);
// Create a fake click event // Create a fake click event
var clickEvent = $.Event('click'); var clickEvent = $.Event('click');
// Simulate manual entry of an email address
$('#password-reset-email').val(EMAIL);
// If validationSuccess isn't passed, we avoid // If validationSuccess isn't passed, we avoid
// spying on `view.validate` twice // spying on `view.validate` twice
if ( !_.isUndefined(validationSuccess) ) { if ( !_.isUndefined(validationSuccess) ) {
...@@ -141,5 +143,5 @@ define([ ...@@ -141,5 +143,5 @@ define([
expect(view.$errors).toHaveClass('hidden'); expect(view.$errors).toHaveClass('hidden');
}); });
}); });
} });
); }).call(this, define || RequireJS.define);
define([ ;(function (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) {
'use strict'; '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() {
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 <a href="/honor">Terms of Service and Honor Code</a>',
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
});
describe('edx.student.account.RegisterView', function() { // Initialize the register view
view = new RegisterView({
var model = null, fields: FORM_DESCRIPTION.fields,
view = null, model: model,
requests = null, thirdPartyAuth: THIRD_PARTY_AUTH,
authComplete = false, platformName: PLATFORM_NAME
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 <a href="/honor">Terms of Service and Honor Code</a>',
defaultValue: '',
type: 'checkbox',
required: true,
instructions: '',
restrictions: {}
}
]
};
var createRegisterView = function(that) { // Spy on AJAX requests
// Initialize the register model requests = AjaxHelpers.requests(that);
model = new RegisterModel({}, {
url: FORM_DESCRIPTION.submit_url,
method: FORM_DESCRIPTION.method
});
// Initialize the register view // Intercept events from the view
view = new RegisterView({ authComplete = false;
fields: FORM_DESCRIPTION.fields, view.on("auth-complete", function() {
model: model, authComplete = true;
thirdPartyAuth: THIRD_PARTY_AUTH, });
platformName: PLATFORM_NAME };
});
// Spy on AJAX requests var submitForm = function(validationSuccess) {
requests = AjaxHelpers.requests(that); // Create a fake click event
var clickEvent = $.Event('click');
// 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);
// 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.'
});
}
// Intercept events from the view // Submit the email address
authComplete = false; view.submitForm(clickEvent);
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 beforeEach(function() {
view.submitForm(clickEvent); setFixtures('<div id="register-form"></div>');
}; TemplateHelpers.installTemplate('templates/student_account/register');
TemplateHelpers.installTemplate('templates/student_account/form_field');
});
beforeEach(function() { it('registers a new user', function() {
setFixtures('<div id="register-form"></div>'); createRegisterView(this);
TemplateHelpers.installTemplate('templates/student_account/register');
TemplateHelpers.installTemplate('templates/student_account/form_field');
});
it('registers a new user', function() { // Submit the form, with successful validation
createRegisterView(this); submitForm(true);
// Submit the form, with successful validation // Verify that the client contacts the server with the expected data
submitForm(true); AjaxHelpers.expectRequest(
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param( USER_DATA )
);
// Verify that the client contacts the server with the expected data // Respond with status code 200
AjaxHelpers.expectRequest( AjaxHelpers.respondWithJson(requests, {});
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param( USER_DATA )
);
// Respond with status code 200 // Verify that auth complete is triggered
AjaxHelpers.respondWithJson(requests, {}); expect(authComplete).toBe(true);
// Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled');
});
// Verify that auth complete is triggered it('sends analytics info containing the enrolled course ID', function() {
expect(authComplete).toBe(true); var expectedData;
// Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled');
});
it('sends analytics info containing the enrolled course ID', function() { createRegisterView(this);
createRegisterView(this);
// Simulate that the user is attempting to enroll in a course // Simulate that the user is attempting to enroll in a course
// by setting the course_id query string param. // by setting the course_id query string param.
spyOn($, 'url').andCallFake(function( param ) { spyOn($, 'url').andCallFake(function( param ) {
if (param === "?course_id") { if (param === '?course_id') {
return encodeURIComponent( COURSE_ID ); return encodeURIComponent( COURSE_ID );
} }
}); });
// Attempt to register // Attempt to register
submitForm( true ); submitForm( true );
// Verify that the client sent the course ID for analytics // Verify that the client sent the course ID for analytics
var expectedData = {course_id: COURSE_ID}; expectedData = {course_id: COURSE_ID};
$.extend(expectedData, USER_DATA); $.extend(expectedData, USER_DATA);
AjaxHelpers.expectRequest( AjaxHelpers.expectRequest(
requests, 'POST', requests, 'POST',
FORM_DESCRIPTION.submit_url, FORM_DESCRIPTION.submit_url,
$.param( expectedData ) $.param( expectedData )
); );
}); });
it('displays third-party auth registration buttons', function() { it('displays third-party auth registration buttons', function() {
createRegisterView(this); createRegisterView(this);
// Verify that Google and Facebook registration buttons are displayed // Verify that Google and Facebook registration buttons are displayed
expect($('.button-oa2-google-oauth2')).toBeVisible(); expect($('.button-oa2-google-oauth2')).toBeVisible();
expect($('.button-oa2-facebook')).toBeVisible(); expect($('.button-oa2-facebook')).toBeVisible();
}); });
it('validates registration form fields', function() { it('validates registration form fields', function() {
createRegisterView(this); createRegisterView(this);
// Submit the form, with successful validation // Submit the form, with successful validation
submitForm(true); submitForm(true);
// Verify that validation of form fields occurred // Verify that validation of form fields occurred
expect(view.validate).toHaveBeenCalledWith($('#register-email')[0]); expect(view.validate).toHaveBeenCalledWith($('#register-email')[0]);
expect(view.validate).toHaveBeenCalledWith($('#register-name')[0]); expect(view.validate).toHaveBeenCalledWith($('#register-name')[0]);
expect(view.validate).toHaveBeenCalledWith($('#register-username')[0]); expect(view.validate).toHaveBeenCalledWith($('#register-username')[0]);
expect(view.validate).toHaveBeenCalledWith($('#register-password')[0]); expect(view.validate).toHaveBeenCalledWith($('#register-password')[0]);
// Verify that no submission errors are visible // Verify that no submission errors are visible
expect(view.$errors).toHaveClass('hidden'); expect(view.$errors).toHaveClass('hidden');
// Form button should be disabled on success. // Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled'); expect(view.$submitButton).toHaveAttr('disabled');
}); });
it('displays registration form validation errors', function() { it('displays registration form validation errors', function() {
createRegisterView(this); createRegisterView(this);
// Submit the form, with failed validation // Submit the form, with failed validation
submitForm(false); submitForm(false);
// Verify that submission errors are visible // Verify that submission errors are visible
expect(view.$errors).not.toHaveClass('hidden'); expect(view.$errors).not.toHaveClass('hidden');
// Expect that auth complete is NOT triggered // Expect that auth complete is NOT triggered
expect(authComplete).toBe(false); expect(authComplete).toBe(false);
// Form button should be re-enabled on error. // Form button should be re-enabled on error.
expect(view.$submitButton).not.toHaveAttr('disabled'); expect(view.$submitButton).not.toHaveAttr('disabled');
}); });
it('displays an error if the server returns an error while registering', function() { it('displays an error if the server returns an error while registering', function() {
createRegisterView(this); createRegisterView(this);
// Submit the form, with successful validation // Submit the form, with successful validation
submitForm(true); submitForm(true);
// Simulate an error from the LMS servers // Simulate an error from the LMS servers
AjaxHelpers.respondWithError(requests); AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed and that auth complete is NOT triggered // Expect that an error is displayed and that auth complete is NOT triggered
expect(view.$errors).not.toHaveClass('hidden'); expect(view.$errors).not.toHaveClass('hidden');
expect(authComplete).toBe(false); expect(authComplete).toBe(false);
// If we try again and succeed, the error should go away // If we try again and succeed, the error should go away
submitForm(); submitForm();
// This time, respond with status code 200 // This time, respond with status code 200
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden and that auth complete is triggered // Expect that the error is hidden and that auth complete is triggered
expect(view.$errors).toHaveClass('hidden'); expect(view.$errors).toHaveClass('hidden');
expect(authComplete).toBe(true); expect(authComplete).toBe(true);
// Form button should be disabled on success. // Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled'); expect(view.$submitButton).toHaveAttr('disabled');
});
}); });
}); });
}); }).call(this, define || RequireJS.define);
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);
;(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);
var edx = edx || {}; ;(function (define) {
(function($, Backbone) {
'use strict'; 'use strict';
define([
'jquery',
'backbone',
'jquery.url'
], function ($, Backbone) {
return Backbone.Model.extend({
defaults: {
email: '',
password: '',
remember: false
},
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 = {},
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)
});
}
edx.student = edx.student || {}; // Include all form fields and analytics info in the data sent to the server
edx.student.account = edx.student.account || {}; $.extend(data, model.attributes, {analytics: analytics});
edx.student.account.LoginModel = Backbone.Model.extend({ $.ajax({
url: model.urlRoot,
defaults: { type: model.ajaxType,
email: '', data: data,
password: '', headers: headers,
remember: false success: function () {
}, model.trigger('sync');
},
ajaxType: '', error: function (error) {
model.trigger('error', error);
urlRoot: '', }
initialize: function( attributes, options ) {
this.ajaxType = options.method;
this.urlRoot = options.url;
},
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 )
}); });
} }
});
// 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);
var edx = edx || {}; ;(function (define) {
(function($, Backbone) {
'use strict'; 'use strict';
define(['jquery', 'backbone'],
edx.student = edx.student || {}; function($, Backbone) {
edx.student.account = edx.student.account || {};
return Backbone.Model.extend({
edx.student.account.PasswordResetModel = Backbone.Model.extend({ defaults: {
email: ''
defaults: { },
email: '' ajaxType: '',
}, urlRoot: '',
ajaxType: '', initialize: function( attributes, options ) {
this.ajaxType = options.method;
urlRoot: '', this.urlRoot = options.url;
},
initialize: function( attributes, options ) {
this.ajaxType = options.method; sync: function( method, model ) {
this.urlRoot = options.url; var headers = {
}, 'X-CSRFToken': $.cookie('csrftoken')
};
sync: function( method, model ) {
var headers = { // Only expects an email address.
'X-CSRFToken': $.cookie('csrftoken') $.ajax({
}; url: model.urlRoot,
type: model.ajaxType,
// Only expects an email address. data: model.attributes,
$.ajax({ headers: headers,
url: model.urlRoot, success: function() {
type: model.ajaxType, model.trigger('sync');
data: model.attributes, },
headers: headers, error: function( error ) {
success: function() { model.trigger('error', error);
model.trigger('sync'); }
}, });
error: function( error ) { }
model.trigger('error', error); });
}
});
}
}); });
})(jQuery, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function($, Backbone) {
'use strict'; 'use strict';
define(['jquery', 'backbone', 'jquery.url'],
function($, Backbone) {
return Backbone.Model.extend({
defaults: {
email: '',
name: '',
username: '',
password: '',
level_of_education: '',
gender: '',
year_of_birth: '',
mailing_address: '',
goals: ''
},
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);
}
edx.student = edx.student || {}; // Include all form fields and analytics info in the data sent to the server
edx.student.account = edx.student.account || {}; $.extend( data, model.attributes);
edx.student.account.RegisterModel = Backbone.Model.extend({ $.ajax({
url: model.urlRoot,
defaults: { type: model.ajaxType,
email: '', data: data,
name: '', headers: headers,
username: '', success: function() {
password: '', model.trigger('sync');
level_of_education: '', },
gender: '', error: function( error ) {
year_of_birth: '', model.trigger('error', error);
mailing_address: '', }
goals: '', });
},
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);
}
});
}
}); });
})(jQuery, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function($, _, _s, Backbone, History) {
'use strict'; '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) {
if (_.isUndefined(_s)) {
_s = _.str;
}
edx.student = edx.student || {}; return Backbone.View.extend({
edx.student.account = edx.student.account || {}; tpl: '#access-tpl',
events: {
edx.student.account.AccessView = Backbone.View.extend({ 'click .form-toggle': 'toggleForm'
el: '#login-and-registration-container', },
subview: {
tpl: '#access-tpl', login: {},
register: {},
events: { passwordHelp: {},
'click .form-toggle': 'toggleForm' institutionLogin: {},
}, hintedLogin: {}
},
subview: { nextUrl: '/dashboard',
login: {}, // The form currently loaded
register: {}, activeForm: '',
passwordHelp: {},
institutionLogin: {},
hintedLogin: {}
},
nextUrl: '/dashboard',
// The form currently loaded initialize: function( options ) {
activeForm: '',
initialize: function( obj ) { /* Mix non-conflicting functions from underscore.string
/* Mix non-conflicting functions from underscore.string * (all but include, contains, and reverse) into the
* (all but include, contains, and reverse) into the * Underscore namespace
* Underscore namespace */
*/ _.mixin( _s.exports() );
_.mixin( _s.exports() );
this.tpl = $(this.tpl).html(); this.tpl = $(this.tpl).html();
this.activeForm = obj.mode || 'login'; this.activeForm = options.initial_mode || 'login';
this.thirdPartyAuth = obj.thirdPartyAuth || { this.thirdPartyAuth = options.third_party_auth || {
currentProvider: null, currentProvider: null,
providers: [] providers: []
}; };
this.thirdPartyAuthHint = obj.thirdPartyAuthHint || null; this.thirdPartyAuthHint = options.third_party_auth_hint || null;
if (obj.nextUrl) { if (options.login_redirect_url) {
// Ensure that the next URL is internal for security reasons // Ensure that the next URL is internal for security reasons
if ( ! window.isExternal( obj.nextUrl ) ) { if ( ! window.isExternal( options.login_redirect_url ) ) {
this.nextUrl = obj.nextUrl; this.nextUrl = options.login_redirect_url;
}
} }
}
this.formDescriptions = { this.formDescriptions = {
login: obj.loginFormDesc, login: options.login_form_desc,
register: obj.registrationFormDesc, register: options.registration_form_desc,
reset: obj.passwordResetFormDesc, reset: options.password_reset_form_desc,
institution_login: null, institution_login: null,
hinted_login: null hinted_login: null
}; };
this.platformName = obj.platformName;
// 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
});
this.subview.login = new edx.student.account.LoginView({ this.platformName = options.platform_name;
fields: data.fields,
model: model,
resetModel: this.resetModel,
thirdPartyAuth: this.thirdPartyAuth,
platformName: this.platformName
});
// Listen for 'password-help' event to toggle sub-views // The login view listens for 'sync' events from the reset model
this.listenTo( this.subview.login, 'password-help', this.resetPassword ); this.resetModel = new PasswordResetModel({}, {
method: 'GET',
url: '#'
});
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. this.render();
this.listenTo( this.subview.login, 'auth-complete', this.authComplete );
// 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 ) { render: function() {
this.resetModel.ajaxType = data.method; $(this.el).html( _.template( this.tpl, {
this.resetModel.urlRoot = data.submit_url; mode: this.activeForm
}));
this.subview.passwordHelp = new edx.student.account.PasswordResetView({ this.postRender();
fields: data.fields,
model: this.resetModel
});
// Listen for 'password-email-sent' event to toggle sub-views return this;
this.listenTo( this.subview.passwordHelp, 'password-email-sent', this.passwordEmailSent ); },
// Focus on the form postRender: function() {
$('.password-reset-form').focus(); //get & check current url hash part & load form accordingly
if (Backbone.history.getHash() === 'forgot-password-modal') {
this.resetPassword();
} else {
this.loadForm(this.activeForm);
}
}, },
register: function( data ) { loadForm: function( type ) {
var model = new edx.student.account.RegisterModel({}, { var loadFunc = _.bind( this.load[type], this );
method: data.method, loadFunc( this.formDescriptions[type] );
url: data.submit_url },
});
this.subview.register = new edx.student.account.RegisterView({ load: {
fields: data.fields, login: function( data ) {
model: model, var model = new LoginModel({}, {
thirdPartyAuth: this.thirdPartyAuth, method: data.method,
platformName: this.platformName 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();
}
},
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. passwordEmailSent: function() {
this.listenTo( this.subview.register, 'auth-complete', this.authComplete ); var $loginAnchorElement = $('#login-anchor');
this.element.hide( $(this.el).find('#password-reset-anchor') );
this.element.show( $loginAnchorElement );
this.element.scrollTop( $loginAnchorElement );
}, },
institution_login: function ( unused ) { resetPassword: function() {
this.subview.institutionLogin = new edx.student.account.InstitutionLoginView({ window.analytics.track('edx.bi.password_reset_form.viewed', {
thirdPartyAuth: this.thirdPartyAuth, category: 'user-engagement'
platformName: this.platformName,
mode: this.activeForm
}); });
this.subview.institutionLogin.render(); this.element.hide( $(this.el).find('#login-anchor') );
this.loadForm('reset');
this.element.scrollTop( $('#password-reset-anchor') );
}, },
hinted_login: function ( unused ) { toggleForm: function( e ) {
this.subview.hintedLogin = new edx.student.account.HintedLoginView({ var type = $(e.currentTarget).data('type'),
thirdPartyAuth: this.thirdPartyAuth, $form = $('#' + type + '-form'),
hintedProvider: this.thirdPartyAuthHint, $anchor = $('#' + type + '-anchor'),
platformName: this.platformName queryParams = url('?'),
queryStr = queryParams.length > 0 ? '?' + queryParams : '';
e.preventDefault();
window.analytics.track('edx.bi.' + type + '_form.toggled', {
category: 'user-engagement'
}); });
this.subview.hintedLogin.render(); // 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 );
}
passwordEmailSent: function() { this.activeForm = type;
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('.submission-success') );
this.element.hide( $(this.el).find('.form-wrapper') ); this.element.hide( $(this.el).find('.form-wrapper') );
this.element.show( $form ); this.element.show( $form );
this.element.scrollTop( $anchor ); this.element.scrollTop( $anchor );
// Update url without reloading page // Update url without reloading page
if (type != "institution_login") { if (type != 'institution_login') {
History.pushState( null, document.title, '/' + type + queryStr ); History.pushState( null, document.title, '/' + type + queryStr );
} }
analytics.page( 'login_and_registration', type ); analytics.page( 'login_and_registration', type );
// Focus on the form // Focus on the form
$("#" + type).focus(); $('#' + type).focus();
}, },
/** /**
* Once authentication has completed successfully: * Once authentication has completed successfully:
* *
* If we're in a third party auth pipeline, we must complete the pipeline. * If we're in a third party auth pipeline, we must complete the pipeline.
* Otherwise, redirect to the specified next step. * Otherwise, redirect to the specified next step.
* *
*/ */
authComplete: function() { authComplete: function() {
if (this.thirdPartyAuth && this.thirdPartyAuth.finishAuthUrl) { if (this.thirdPartyAuth && this.thirdPartyAuth.finishAuthUrl) {
this.redirect(this.thirdPartyAuth.finishAuthUrl); this.redirect(this.thirdPartyAuth.finishAuthUrl);
// Note: the third party auth URL likely contains another redirect URL embedded inside // Note: the third party auth URL likely contains another redirect URL embedded inside
} else { } else {
this.redirect(this.nextUrl); this.redirect(this.nextUrl);
} }
}, },
/** /**
* Redirect to a URL. Mainly useful for mocking out in tests. * Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to. * @param {string} url The URL to redirect to.
*/ */
redirect: function( url ) { redirect: function( url ) {
window.location.replace(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');
}, },
scrollTop: function( $el ) { form: {
// Scroll to top of selected element isLoaded: function( $form ) {
$('html,body').animate({ return $form.html().length > 0;
scrollTop: $el.offset().top }
},'slow');
}, },
show: function( $el ) { /* Helper method to toggle display
$el.removeClass('hidden'); * including accessibility considerations
*/
element: {
hide: function( $el ) {
$el.addClass('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);
var edx = edx || {}; ;(function (define) {
(function($, _, Backbone, gettext) {
'use strict'; 'use strict';
define([
'jquery',
'underscore',
'backbone',
'js/utils/edx.utils.validate'
],
function($, _, Backbone, EdxUtilsValidate) {
edx.student = edx.student || {}; return Backbone.View.extend({
edx.student.account = edx.student.account || {}; 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 // String to append to required label fields
requiredStr: '*', requiredStr: '*',
submitButton: '', submitButton: '',
initialize: function( data ) { initialize: function( data ) {
this.model = data.model; this.model = data.model;
this.preRender( data ); this.preRender( data );
this.tpl = $(this.tpl).html(); this.tpl = $(this.tpl).html();
this.fieldTpl = $(this.fieldTpl).html(); this.fieldTpl = $(this.fieldTpl).html();
this.buildForm( data.fields ); this.buildForm( data.fields );
this.listenTo( this.model, 'error', this.saveError ); this.listenTo( this.model, 'error', this.saveError );
}, },
/* Allows extended views to add custom /* Allows extended views to add custom
* init steps without needing to repeat * init steps without needing to repeat
* default init steps * default init steps
*/ */
preRender: function( data ) { preRender: function( data ) {
/* Custom code goes here */ /* Custom code goes here */
return data; return data;
}, },
render: function( html ) { render: function( html ) {
var fields = html || ''; var fields = html || '';
$(this.el).html( _.template( this.tpl, { $(this.el).html( _.template( this.tpl, {
fields: fields fields: fields
})); }));
this.postRender(); this.postRender();
return this; return this;
}, },
postRender: function() { postRender: function() {
var $container = $(this.el); var $container = $(this.el);
this.$form = $container.find('form');
this.$errors = $container.find('.submission-error');
this.$submitButton = $container.find(this.submitButton);
},
this.$form = $container.find('form'); buildForm: function( data ) {
this.$errors = $container.find('.submission-error'); var html = [],
this.$submitButton = $container.find(this.submitButton); i,
}, len = data.length,
fieldTpl = this.fieldTpl;
buildForm: function( data ) { this.fields = data;
var html = [],
i,
len = data.length,
fieldTpl = this.fieldTpl;
this.fields = data; for ( i=0; i<len; i++ ) {
if ( data[i].errorMessages ) {
data[i].errorMessages = this.escapeStrings( data[i].errorMessages );
}
for ( i=0; i<len; i++ ) { html.push( _.template( fieldTpl, $.extend( data[i], {
if ( data[i].errorMessages ) { form: this.formType,
data[i].errorMessages = this.escapeStrings( data[i].errorMessages ); requiredStr: this.requiredStr
}) ) );
} }
html.push( _.template( fieldTpl, $.extend( data[i], { this.render( html.join('') );
form: this.formType, },
requiredStr: this.requiredStr
}) ) );
}
this.render( html.join('') );
},
/* Helper method to toggle display /* Helper method to toggle display
* including accessibility considerations * including accessibility considerations
*/ */
element: { element: {
hide: function( $el ) { hide: function( $el ) {
if ( $el ) { if ( $el ) {
$el.addClass('hidden'); $el.addClass('hidden');
}
},
scrollTop: function( $el ) {
// Scroll to top of selected element
$('html,body').animate({
scrollTop: $el.offset().top
},'slow');
},
show: function( $el ) {
if ( $el ) {
$el.removeClass('hidden');
}
} }
}, },
scrollTop: function( $el ) { escapeStrings: function( obj ) {
// Scroll to top of selected element _.each( obj, function( val, key ) {
$('html,body').animate({ obj[key] = _.escape( val );
scrollTop: $el.offset().top });
},'slow');
return obj;
}, },
show: function( $el ) { focusFirstError: function() {
if ( $el ) { var $error = this.$form.find('.error').first(),
$el.removeClass('hidden'); $field = {},
$parent = {};
if ( $error.is('label') ) {
$parent = $error.parent('.form-field');
$error = $parent.find('input') || $parent.find('select');
} else {
$field = $error;
} }
}
},
escapeStrings: function( obj ) {
_.each( obj, function( val, key ) {
obj[key] = _.escape( val );
});
return obj;
},
focusFirstError: function() {
var $error = this.$form.find('.error').first(),
$field = {},
$parent = {};
if ( $error.is('label') ) {
$parent = $error.parent('.form-field');
$error = $parent.find('input') || $parent.find('select');
} else {
$field = $error;
}
$error.focus(); $error.focus();
}, },
forgotPassword: function( event ) { forgotPassword: function( event ) {
event.preventDefault(); event.preventDefault();
this.trigger('password-help'); this.trigger('password-help');
}, },
getFormData: function() { getFormData: function() {
var obj = {}, var obj = {},
$form = this.$form, $form = this.$form,
elements = $form[0].elements, elements = $form[0].elements,
i, i,
len = elements.length, len = elements.length,
$el, $el,
$label, $label,
key = '', key = '',
errors = [], errors = [],
test = {}; test = {};
for ( i=0; i<len; i++ ) { for ( i=0; i<len; i++ ) {
$el = $( elements[i] ); $el = $( elements[i] );
$label = $form.find('label[for=' + $el.attr('id') + ']'); $label = $form.find('label[for=' + $el.attr('id') + ']');
key = $el.attr('name') || false; key = $el.attr('name') || false;
if ( key ) { if ( key ) {
test = this.validate( elements[i] ); test = this.validate( elements[i] );
if ( test.isValid ) { if ( test.isValid ) {
obj[key] = $el.attr('type') === 'checkbox' ? $el.is(':checked') : $el.val(); obj[key] = $el.attr('type') === 'checkbox' ? $el.is(':checked') : $el.val();
$el.removeClass('error'); $el.removeClass('error');
$label.removeClass('error'); $label.removeClass('error');
} else { } else {
errors.push( test.message ); errors.push( test.message );
$el.addClass('error'); $el.addClass('error');
$label.addClass('error'); $label.addClass('error');
}
} }
} }
}
this.errors = _.uniq( errors ); this.errors = _.uniq( errors );
return obj;
},
return obj; saveError: function( error ) {
}, this.errors = ['<li>' + error.responseText + '</li>'];
this.setErrors();
this.toggleDisableButton(false);
},
saveError: function( error ) { setErrors: function() {
this.errors = ['<li>' + error.responseText + '</li>']; var $msg = this.$errors.find('.message-copy'),
this.setErrors(); html = [],
this.toggleDisableButton(false); errors = this.errors,
}, i,
len = errors.length;
setErrors: function() { for ( i=0; i<len; i++ ) {
var $msg = this.$errors.find('.message-copy'), html.push( errors[i] );
html = [], }
errors = this.errors,
i,
len = errors.length;
for ( i=0; i<len; i++ ) { $msg.html( html.join('') );
html.push( errors[i] );
}
$msg.html( html.join('') ); this.element.show( this.$errors );
this.element.show( this.$errors ); // Scroll to error messages
$('html,body').animate({
scrollTop: this.$errors.offset().top
},'slow');
// Focus on first error field
this.focusFirstError();
},
// Scroll to error messages submitForm: function( event ) {
$('html,body').animate({ var data = this.getFormData();
scrollTop: this.$errors.offset().top
},'slow');
// Focus on first error field if (!_.isUndefined(event)) {
this.focusFirstError(); event.preventDefault();
}, }
submitForm: function( event ) { this.toggleDisableButton(true);
var data = this.getFormData();
if (!_.isUndefined(event)) { if ( !_.compact(this.errors).length ) {
event.preventDefault(); this.model.set( data );
} this.model.save();
this.toggleErrorMsg( false );
} else {
this.toggleErrorMsg( true );
}
this.toggleDisableButton(true); this.postFormSubmission();
},
if ( !_.compact(this.errors).length ) { /* Allows extended views to add custom
this.model.set( data ); * code after form submission
this.model.save(); */
this.toggleErrorMsg( false ); postFormSubmission: function() {
} else { return true;
this.toggleErrorMsg( true ); },
}
this.postFormSubmission(); toggleErrorMsg: function( show ) {
}, if ( show ) {
this.setErrors();
this.toggleDisableButton(false);
} else {
this.element.hide( this.$errors );
}
},
/* Allows extended views to add custom /**
* code after form submission * 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.
postFormSubmission: function() { *
return true; * Args:
}, * disabled (boolean): If set to TRUE, disable the button.
*
*/
toggleDisableButton: function ( disabled ) {
if (this.$submitButton) {
this.$submitButton.attr('disabled', disabled);
}
},
toggleErrorMsg: function( show ) { validate: function( $el ) {
if ( show ) { return EdxUtilsValidate.validate( $el );
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);
}
},
validate: function( $el ) {
return edx.utils.validate( $el );
}
}); });
}).call(this, define || RequireJS.define);
})(jQuery, _, Backbone, gettext);
var edx = edx || {}; ;(function (define) {
(function($, _, gettext) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone'],
edx.student = edx.student || {}; function($, _, Backbone) {
edx.student.account = edx.student.account || {};
return Backbone.View.extend({
edx.student.account.HintedLoginView = Backbone.View.extend({ el: '#hinted-login-form',
el: '#hinted-login-form',
tpl: '#hinted_login-tpl',
tpl: '#hinted_login-tpl',
events: {
events: { 'click .proceed-button': 'proceedWithHintedAuth'
'click .proceed-button': 'proceedWithHintedAuth' },
},
formType: 'hinted-login',
formType: 'hinted-login',
initialize: function( data ) {
initialize: function( data ) { this.tpl = $(this.tpl).html();
this.tpl = $(this.tpl).html(); this.hintedProvider = (
this.hintedProvider = ( _.findWhere(data.thirdPartyAuth.providers, {id: data.hintedProvider}) ||
_.findWhere(data.thirdPartyAuth.providers, {id: data.hintedProvider}) || _.findWhere(data.thirdPartyAuth.secondaryProviders, {id: data.hintedProvider})
_.findWhere(data.thirdPartyAuth.secondaryProviders, {id: data.hintedProvider}) );
); },
},
render: function() {
render: function() { $(this.el).html( _.template( this.tpl, {
$(this.el).html( _.template( this.tpl, { hintedProvider: this.hintedProvider
hintedProvider: this.hintedProvider }));
}));
return this;
return this; },
},
proceedWithHintedAuth: function( event ) {
proceedWithHintedAuth: function( event ) { this.redirect(this.hintedProvider.loginUrl);
this.redirect(this.hintedProvider.loginUrl); },
},
/**
/** * Redirect to a URL. Mainly useful for mocking out in tests.
* Redirect to a URL. Mainly useful for mocking out in tests. * @param {string} url The URL to redirect to.
* @param {string} url The URL to redirect to. */
*/ redirect: function( url ) {
redirect: function( url ) { window.location.href = url;
window.location.href = url; }
} });
}); });
})(jQuery, _, gettext); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function($, _, Backbone) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone'],
function($, _, Backbone) {
edx.student = edx.student || {}; return Backbone.View.extend({
edx.student.account = edx.student.account || {}; el: '#institution_login-form',
edx.student.account.InstitutionLoginView = Backbone.View.extend({
el: '#institution_login-form',
initialize: function( data ) { initialize: function( data ) {
var tpl = data.mode == "register" ? '#institution_register-tpl' : '#institution_login-tpl'; var tpl = data.mode == "register" ? '#institution_register-tpl' : '#institution_login-tpl';
this.tpl = $(tpl).html(); this.tpl = $(tpl).html();
this.providers = data.thirdPartyAuth.secondaryProviders || []; this.providers = data.thirdPartyAuth.secondaryProviders || [];
this.platformName = data.platformName; this.platformName = data.platformName;
}, },
render: function() { render: function() {
$(this.el).html( _.template( this.tpl, { $(this.el).html( _.template( this.tpl, {
// We pass the context object to the template so that // We pass the context object to the template so that
// we can perform variable interpolation using sprintf // we can perform variable interpolation using sprintf
providers: this.providers, providers: this.providers,
platformName: this.platformName platformName: this.platformName
})); }));
return this; return this;
} }
});
}); });
})(jQuery, _, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function($, _, gettext) {
'use strict'; 'use strict';
define([
edx.student = edx.student || {}; 'jquery',
edx.student.account = edx.student.account || {}; 'underscore',
'js/student_account/views/FormView'
edx.student.account.LoginView = edx.student.account.FormView.extend({ ],
el: '#login-form', function($, _, FormView) {
tpl: '#login-tpl', return FormView.extend({
el: '#login-form',
events: { tpl: '#login-tpl',
'click .js-login': 'submitForm', events: {
'click .forgot-password': 'forgotPassword', 'click .js-login': 'submitForm',
'click .login-provider': 'thirdPartyAuth' 'click .forgot-password': 'forgotPassword',
}, 'click .login-provider': 'thirdPartyAuth'
},
formType: 'login', formType: 'login',
requiredStr: '',
requiredStr: '', submitButton: '.js-login',
submitButton: '.js-login', preRender: function( data ) {
this.providers = data.thirdPartyAuth.providers || [];
preRender: function( data ) { this.hasSecondaryProviders = (
this.providers = data.thirdPartyAuth.providers || []; data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
this.hasSecondaryProviders = ( );
data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length this.currentProvider = data.thirdPartyAuth.currentProvider || '';
); this.errorMessage = data.thirdPartyAuth.errorMessage || '';
this.currentProvider = data.thirdPartyAuth.currentProvider || ''; this.platformName = data.platformName;
this.errorMessage = data.thirdPartyAuth.errorMessage || ''; this.resetModel = data.resetModel;
this.platformName = data.platformName;
this.resetModel = data.resetModel; this.listenTo( this.model, 'sync', this.saveSuccess );
this.listenTo( this.resetModel, 'sync', this.resetEmail );
this.listenTo( this.model, 'sync', this.saveSuccess ); },
this.listenTo( this.resetModel, 'sync', this.resetEmail );
}, 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
$(this.el).html( _.template( this.tpl, { // we can perform variable interpolation using sprintf
// We pass the context object to the template so that context: {
// we can perform variable interpolation using sprintf fields: fields,
context: { currentProvider: this.currentProvider,
fields: fields, errorMessage: this.errorMessage,
currentProvider: this.currentProvider, providers: this.providers,
errorMessage: this.errorMessage, hasSecondaryProviders: this.hasSecondaryProviders,
providers: this.providers, platformName: this.platformName
hasSecondaryProviders: this.hasSecondaryProviders, }
platformName: this.platformName }));
this.postRender();
return this;
},
postRender: function() {
this.$container = $(this.el);
this.$form = this.$container.find('form');
this.$errors = this.$container.find('.submission-error');
this.$resetSuccess = this.$container.find('.js-reset-success');
this.$authError = this.$container.find('.already-authenticated-msg');
this.$submitButton = this.$container.find(this.submitButton);
/* If we're already authenticated with a third-party
* provider, try logging in. The easiest way to do this
* is to simply submit the form.
*/
if (this.currentProvider) {
this.model.save();
} }
})); },
this.postRender();
return this;
},
postRender: function() {
this.$container = $(this.el);
this.$form = this.$container.find('form');
this.$errors = this.$container.find('.submission-error');
this.$resetSuccess = this.$container.find('.js-reset-success');
this.$authError = this.$container.find('.already-authenticated-msg');
this.$submitButton = this.$container.find(this.submitButton);
/* If we're already authenticated with a third-party
* provider, try logging in. The easiest way to do this
* is to simply submit the form.
*/
if (this.currentProvider) {
this.model.save();
}
},
forgotPassword: function( event ) { forgotPassword: function( event ) {
event.preventDefault(); event.preventDefault();
this.trigger('password-help'); this.trigger('password-help');
this.element.hide( this.$resetSuccess ); this.element.hide( this.$resetSuccess );
}, },
postFormSubmission: function() { postFormSubmission: function() {
this.element.hide( this.$resetSuccess ); this.element.hide( this.$resetSuccess );
}, },
resetEmail: function() { resetEmail: function() {
this.element.hide( this.$errors ); this.element.hide( this.$errors );
this.element.show( this.$resetSuccess ); this.element.show( this.$resetSuccess );
}, },
thirdPartyAuth: function( event ) { thirdPartyAuth: function( event ) {
var providerUrl = $(event.currentTarget).data('provider-url') || ''; var providerUrl = $(event.currentTarget).data('provider-url') || '';
if (providerUrl) { if (providerUrl) {
window.location.href = providerUrl; window.location.href = providerUrl;
} }
}, },
saveSuccess: function() { saveSuccess: function() {
this.trigger('auth-complete'); this.trigger('auth-complete');
this.element.hide( this.$resetSuccess ); this.element.hide( this.$resetSuccess );
}, },
saveError: function( error ) { saveError: function( error ) {
this.errors = ['<li>' + error.responseText + '</li>']; this.errors = ['<li>' + error.responseText + '</li>'];
this.setErrors(); this.setErrors();
this.element.hide( this.$resetSuccess ); this.element.hide( this.$resetSuccess );
/* If we've gotten a 403 error, it means that we've successfully /* If we've gotten a 403 error, it means that we've successfully
* authenticated with a third-party provider, but we haven't * authenticated with a third-party provider, but we haven't
* linked the account to an EdX account. In this case, * linked the account to an EdX account. In this case,
* we need to prompt the user to enter a little more information * we need to prompt the user to enter a little more information
* to complete the registration process. * to complete the registration process.
*/ */
if ( error.status === 403 && if ( error.status === 403 &&
error.responseText === 'third-party-auth' && error.responseText === 'third-party-auth' &&
this.currentProvider ) { this.currentProvider ) {
this.element.show( this.$authError ); this.element.show( this.$authError );
this.element.hide( this.$errors ); this.element.hide( this.$errors );
} else { } else {
this.element.hide( this.$authError ); this.element.hide( this.$authError );
this.element.show( this.$errors ); this.element.show( this.$errors );
}
this.toggleDisableButton(false);
} }
this.toggleDisableButton(false); });
}
}); });
})(jQuery, _, gettext); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function($, gettext) {
'use strict'; 'use strict';
define([
'jquery',
'js/student_account/views/FormView'
],
function($, FormView) {
edx.student = edx.student || {}; return FormView.extend({
edx.student.account = edx.student.account || {}; 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: { events: {
'click .js-reset': 'submitForm' 'click .js-reset': 'submitForm'
}, },
formType: 'password-reset', formType: 'password-reset',
requiredStr: '', requiredStr: '',
submitButton: '.js-reset', submitButton: '.js-reset',
preRender: function() { preRender: function() {
this.element.show( $( this.el ) ); this.element.show( $( this.el ) );
this.element.show( $( this.el ).parent() ); this.element.show( $( this.el ).parent() );
this.listenTo( this.model, 'sync', this.saveSuccess ); this.listenTo( this.model, 'sync', this.saveSuccess );
}, },
toggleErrorMsg: function( show ) { toggleErrorMsg: function( show ) {
if ( show ) { if ( show ) {
this.setErrors(); this.setErrors();
this.toggleDisableButton(false); this.toggleDisableButton(false);
} else { } else {
this.element.hide( this.$errors ); this.element.hide( this.$errors );
} }
}, },
saveSuccess: function() { saveSuccess: function() {
this.trigger('password-email-sent'); this.trigger('password-email-sent');
// Destroy the view (but not el) and unbind events // Destroy the view (but not el) and unbind events
this.$el.empty().off(); this.$el.empty().off();
this.stopListening(); this.stopListening();
} }
});
}); });
}).call(this, define || RequireJS.define);
})(jQuery, gettext);
var edx = edx || {}; ;(function (define) {
(function($, _, gettext) {
'use strict'; 'use strict';
define([
'jquery',
'underscore',
'js/student_account/views/FormView'
],
function($, _, FormView) {
return FormView.extend({
el: '#register-form',
tpl: '#register-tpl',
events: {
'click .js-register': 'submitForm',
'click .login-provider': 'thirdPartyAuth'
},
formType: '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;
this.listenTo( this.model, 'sync', this.saveSuccess );
},
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
}
}));
edx.student = edx.student || {}; this.postRender();
edx.student.account = edx.student.account || {};
if (this.autoSubmit) {
edx.student.account.RegisterView = edx.student.account.FormView.extend({ $(this.el).hide();
el: '#register-form', $('#register-honor_code').prop('checked', true);
this.submitForm();
tpl: '#register-tpl',
events: {
'click .js-register': 'submitForm',
'click .login-provider': 'thirdPartyAuth'
},
formType: '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;
this.listenTo( this.model, 'sync', this.saveSuccess );
},
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(); return this;
},
if (this.autoSubmit) { thirdPartyAuth: function( event ) {
$(this.el).hide(); var providerUrl = $(event.currentTarget).data('provider-url') || '';
$('#register-honor_code').prop('checked', true);
this.submitForm();
}
return this; if ( providerUrl ) {
}, window.location.href = providerUrl;
}
},
thirdPartyAuth: function( event ) { saveSuccess: function() {
var providerUrl = $(event.currentTarget).data('provider-url') || ''; this.trigger('auth-complete');
},
if ( providerUrl ) { saveError: function( error ) {
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 "<li>" + error.user_message + "</li>"; }
);
}
)
);
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 $(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 '<li>' + error.user_message + '</li>'; }
);
}
)
);
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);
...@@ -63,6 +63,7 @@ lib_paths: ...@@ -63,6 +63,7 @@ lib_paths:
- xmodule_js/common_static/js/test/i18n.js - xmodule_js/common_static/js/test/i18n.js
- xmodule_js/common_static/js/vendor/date.js - xmodule_js/common_static/js/vendor/date.js
- xmodule_js/common_static/js/vendor/moment.min.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 # Paths to source JavaScript files
src_paths: src_paths:
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
'js/groups/views/cohorts_dashboard_factory', 'js/groups/views/cohorts_dashboard_factory',
'js/search/course/course_search_factory', 'js/search/course/course_search_factory',
'js/search/dashboard/dashboard_search_factory', 'js/search/dashboard/dashboard_search_factory',
'js/student_account/logistration_factory',
'js/student_account/views/account_settings_factory', 'js/student_account/views/account_settings_factory',
'js/student_account/views/finish_auth_factory', 'js/student_account/views/finish_auth_factory',
'js/student_profile/views/learner_profile_factory', 'js/student_profile/views/learner_profile_factory',
......
<%! 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'/> <%namespace name='static' file='/static_content.html'/>
<%inherit file="../main.html" /> <%inherit file="../main.html" />
...@@ -6,9 +10,10 @@ ...@@ -6,9 +10,10 @@
<%block name="pagetitle">${_("Sign in or Register")}</%block> <%block name="pagetitle">${_("Sign in or Register")}</%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <%static:require_module module_name="js/student_account/logistration_factory" class_name="LogistrationFactory">
<script src="${static.url('js/vendor/history.js')}"></script> var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) };
<%static:js group='student_account'/> LogistrationFactory(options);
</%static:require_module>
</%block> </%block>
<%block name="header_extras"> <%block name="header_extras">
...@@ -20,17 +25,7 @@ ...@@ -20,17 +25,7 @@
</%block> </%block>
<div class="section-bkg-wrapper"> <div class="section-bkg-wrapper">
<div id="login-and-registration-container" <div id="login-and-registration-container" class="login-register" />
class="login-register"
data-initial-mode="${initial_mode}"
data-third-party-auth='${third_party_auth|h}'
data-third-party-auth-hint='${third_party_auth_hint}'
data-next-url='${login_redirect_url|h}'
data-platform-name='${platform_name}'
data-login-form-desc='${login_form_desc|h}'
data-registration-form-desc='${registration_form_desc|h}'
data-password-reset-form-desc='${password_reset_form_desc|h}'
/>
</div> </div>
% if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'): % if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment