Include course ID in analytics events for logistration
This commit is contained in:
@@ -343,6 +343,20 @@ def shim_student_view(view_func, check_logged_in=False):
|
||||
if "course_id" in request.POST:
|
||||
del request.POST["course_id"]
|
||||
|
||||
# Include the course ID if it's specified in the analytics info
|
||||
# so it can be included in analytics events.
|
||||
if "analytics" in request.POST:
|
||||
try:
|
||||
analytics = json.loads(request.POST["analytics"])
|
||||
if "enroll_course_id" in analytics:
|
||||
request.POST["course_id"] = analytics.get("enroll_course_id")
|
||||
except (ValueError, TypeError):
|
||||
LOGGER.error(
|
||||
u"Could not parse analytics object sent to user API: {analytics}".format(
|
||||
analytics=analytics
|
||||
)
|
||||
)
|
||||
|
||||
# Backwards compatibility: the student view expects both
|
||||
# terms of service and honor code values. Since we're combining
|
||||
# these into a single checkbox, the only value we may get
|
||||
|
||||
@@ -150,6 +150,17 @@ class StudentViewShimTest(TestCase):
|
||||
self.assertNotIn("enrollment_action", self.captured_request.POST)
|
||||
self.assertNotIn("course_id", self.captured_request.POST)
|
||||
|
||||
def test_include_analytics_info(self):
|
||||
view = self._shimmed_view(HttpResponse())
|
||||
request = HttpRequest()
|
||||
request.POST["analytics"] = json.dumps({
|
||||
"enroll_course_id": "edX/DemoX/Fall"
|
||||
})
|
||||
view(request)
|
||||
|
||||
# Expect that the analytics course ID was passed to the view
|
||||
self.assertEqual(self.captured_request.POST.get("course_id"), "edX/DemoX/Fall")
|
||||
|
||||
def test_third_party_auth_login_failure(self):
|
||||
view = self._shimmed_view(
|
||||
HttpResponse(status=403),
|
||||
|
||||
@@ -133,6 +133,13 @@ class LoginSessionView(APIView):
|
||||
def post(self, request):
|
||||
"""Log in a user.
|
||||
|
||||
You must send all required form fields with the request.
|
||||
|
||||
You can optionally send an `analytics` param with a JSON-encoded
|
||||
object with additional info to include in the login analytics event.
|
||||
Currently, the only supported field is "enroll_course_id" to indicate
|
||||
that the user logged in while enrolling in a particular course.
|
||||
|
||||
Arguments:
|
||||
request (HttpRequest)
|
||||
|
||||
@@ -148,7 +155,7 @@ class LoginSessionView(APIView):
|
||||
Example Usage:
|
||||
|
||||
POST /user_api/v1/login_session
|
||||
with POST params `email` and `password`
|
||||
with POST params `email`, `password`, and `remember`.
|
||||
|
||||
200 OK
|
||||
|
||||
@@ -246,6 +253,13 @@ class RegistrationView(APIView):
|
||||
def post(self, request):
|
||||
"""Create the user's account.
|
||||
|
||||
You must send all required form fields with the request.
|
||||
|
||||
You can optionally send an `analytics` param with a JSON-encoded
|
||||
object with additional info to include in the registration analytics event.
|
||||
Currently, the only supported field is "enroll_course_id" to indicate
|
||||
that the user registered while enrolling in a particular course.
|
||||
|
||||
Arguments:
|
||||
request (HTTPRequest)
|
||||
|
||||
|
||||
@@ -6,208 +6,237 @@ define([
|
||||
'js/student_account/models/LoginModel',
|
||||
'js/student_account/views/LoginView'
|
||||
], function($, _, TemplateHelpers, AjaxHelpers, LoginModel, LoginView) {
|
||||
describe('edx.student.account.LoginView', function() {
|
||||
'use strict';
|
||||
'use strict';
|
||||
describe('edx.student.account.LoginView', function() {
|
||||
|
||||
var model = null,
|
||||
view = null,
|
||||
requests = null,
|
||||
authComplete = false,
|
||||
PLATFORM_NAME = 'edX',
|
||||
USER_DATA = {
|
||||
email: 'xsy@edx.org',
|
||||
password: 'xsyisawesome',
|
||||
remember: true
|
||||
},
|
||||
THIRD_PARTY_AUTH = {
|
||||
currentProvider: null,
|
||||
providers: [
|
||||
{
|
||||
name: 'Google',
|
||||
iconClass: 'icon-google-plus',
|
||||
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
iconClass: 'icon-facebook',
|
||||
loginUrl: '/auth/login/facebook/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/facebook/?auth_entry=account_register'
|
||||
}
|
||||
]
|
||||
},
|
||||
FORM_DESCRIPTION = {
|
||||
method: 'post',
|
||||
submit_url: '/user_api/v1/account/login_session/',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
defaultValue: '',
|
||||
type: 'email',
|
||||
required: true,
|
||||
placeholder: 'place@holder.org',
|
||||
instructions: 'Enter your email.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
defaultValue: '',
|
||||
type: 'password',
|
||||
required: true,
|
||||
instructions: 'Enter your password.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'remember',
|
||||
label: 'Remember me',
|
||||
defaultValue: '',
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
instructions: "Agree to the terms of service.",
|
||||
restrictions: {}
|
||||
}
|
||||
]
|
||||
};
|
||||
var model = null,
|
||||
view = null,
|
||||
requests = null,
|
||||
authComplete = false,
|
||||
PLATFORM_NAME = 'edX',
|
||||
USER_DATA = {
|
||||
email: 'xsy@edx.org',
|
||||
password: 'xsyisawesome',
|
||||
remember: true
|
||||
},
|
||||
THIRD_PARTY_AUTH = {
|
||||
currentProvider: null,
|
||||
providers: [
|
||||
{
|
||||
name: 'Google',
|
||||
iconClass: 'icon-google-plus',
|
||||
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
iconClass: 'icon-facebook',
|
||||
loginUrl: '/auth/login/facebook/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/facebook/?auth_entry=account_register'
|
||||
}
|
||||
]
|
||||
},
|
||||
FORM_DESCRIPTION = {
|
||||
method: 'post',
|
||||
submit_url: '/user_api/v1/account/login_session/',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
defaultValue: '',
|
||||
type: 'email',
|
||||
required: true,
|
||||
placeholder: 'place@holder.org',
|
||||
instructions: 'Enter your email.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
defaultValue: '',
|
||||
type: 'password',
|
||||
required: true,
|
||||
instructions: 'Enter your password.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'remember',
|
||||
label: 'Remember me',
|
||||
defaultValue: '',
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
instructions: "Agree to the terms of service.",
|
||||
restrictions: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
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
|
||||
var createLoginView = function(test) {
|
||||
// Initialize the login model
|
||||
model = new LoginModel({}, {
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
method: FORM_DESCRIPTION.method
|
||||
});
|
||||
|
||||
// Initialize the login view
|
||||
view = new LoginView({
|
||||
fields: FORM_DESCRIPTION.fields,
|
||||
model: model,
|
||||
thirdPartyAuth: THIRD_PARTY_AUTH,
|
||||
platformName: PLATFORM_NAME
|
||||
});
|
||||
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(test);
|
||||
|
||||
// Intercept events from the view
|
||||
authComplete = false;
|
||||
view.on("auth-complete", function() {
|
||||
authComplete = true;
|
||||
});
|
||||
};
|
||||
|
||||
var submitForm = function(validationSuccess) {
|
||||
// Simulate manual entry of login form data
|
||||
$('#login-email').val(USER_DATA.email);
|
||||
$('#login-password').val(USER_DATA.password);
|
||||
|
||||
// Check the "Remember me" checkbox
|
||||
$('#login-remember').prop('checked', USER_DATA.remember);
|
||||
|
||||
// Create a fake click event
|
||||
var clickEvent = $.Event('click');
|
||||
|
||||
// If validationSuccess isn't passed, we avoid
|
||||
// spying on `view.validate` twice
|
||||
if ( !_.isUndefined(validationSuccess) ) {
|
||||
// Force validation to return as expected
|
||||
spyOn(view, 'validate').andReturn({
|
||||
isValid: validationSuccess,
|
||||
message: 'Submission was validated.'
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the login view
|
||||
view = new LoginView({
|
||||
fields: FORM_DESCRIPTION.fields,
|
||||
model: model,
|
||||
thirdPartyAuth: THIRD_PARTY_AUTH,
|
||||
platformName: PLATFORM_NAME
|
||||
});
|
||||
// Submit the email address
|
||||
view.submitForm(clickEvent);
|
||||
};
|
||||
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(test);
|
||||
|
||||
// Intercept events from the view
|
||||
authComplete = false;
|
||||
view.on("auth-complete", function() {
|
||||
authComplete = true;
|
||||
});
|
||||
};
|
||||
|
||||
var submitForm = function(validationSuccess) {
|
||||
// Simulate manual entry of login form data
|
||||
$('#login-email').val(USER_DATA.email);
|
||||
$('#login-password').val(USER_DATA.password);
|
||||
|
||||
// Check the "Remember me" checkbox
|
||||
$('#login-remember').prop('checked', USER_DATA.remember);
|
||||
|
||||
// Create a fake click event
|
||||
var clickEvent = $.Event('click');
|
||||
|
||||
// If validationSuccess isn't passed, we avoid
|
||||
// spying on `view.validate` twice
|
||||
if ( !_.isUndefined(validationSuccess) ) {
|
||||
// Force validation to return as expected
|
||||
spyOn(view, 'validate').andReturn({
|
||||
isValid: validationSuccess,
|
||||
message: 'Submission was validated.'
|
||||
});
|
||||
}
|
||||
|
||||
// Submit the email address
|
||||
view.submitForm(clickEvent);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<div id="login-form"></div>');
|
||||
TemplateHelpers.installTemplate('templates/student_account/login');
|
||||
TemplateHelpers.installTemplate('templates/student_account/form_field');
|
||||
});
|
||||
|
||||
it('logs the user in', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( USER_DATA )
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that auth-complete is triggered
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
|
||||
it('displays third-party auth login buttons', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Verify that Google and Facebook registration buttons are displayed
|
||||
expect($('.button-Google')).toBeVisible();
|
||||
expect($('.button-Facebook')).toBeVisible();
|
||||
});
|
||||
|
||||
it('displays a link to the password reset form', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Verify that the password reset link is displayed
|
||||
expect($('.forgot-password')).toBeVisible();
|
||||
});
|
||||
|
||||
it('validates login form fields', function() {
|
||||
createLoginView(this);
|
||||
|
||||
submitForm(true);
|
||||
|
||||
// Verify that validation of form fields occurred
|
||||
expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]);
|
||||
});
|
||||
|
||||
it('displays login form validation errors', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Submit the form, with failed validation
|
||||
submitForm(false);
|
||||
|
||||
// Verify that submission errors are visible
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
|
||||
// Expect auth complete NOT to have been triggered
|
||||
expect(authComplete).toBe(false);
|
||||
});
|
||||
|
||||
it('displays an error if the server returns an error while logging in', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Simulate an error from the LMS servers
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Expect that an error is displayed and that auth complete is not triggered
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
expect(authComplete).toBe(false);
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitForm();
|
||||
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Expect that the error is hidden and auth complete is triggered
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
beforeEach(function() {
|
||||
setFixtures('<div id="login-form"></div>');
|
||||
TemplateHelpers.installTemplate('templates/student_account/login');
|
||||
TemplateHelpers.installTemplate('templates/student_account/form_field');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('logs the user in', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( USER_DATA )
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that auth-complete is triggered
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
|
||||
it('sends analytics info containing the enrolled course ID', function() {
|
||||
createLoginView( this );
|
||||
|
||||
// Simulate that the user is attempting to enroll in a course
|
||||
// by setting the course_id query string param.
|
||||
spyOn($, 'url').andCallFake(function( param ) {
|
||||
if (param === "?course_id") {
|
||||
return encodeURIComponent( COURSE_ID );
|
||||
}
|
||||
});
|
||||
|
||||
// Attempt to login
|
||||
submitForm( true );
|
||||
|
||||
// Verify that the client sent the course ID for analytics
|
||||
var expectedData = {};
|
||||
$.extend(expectedData, USER_DATA, {
|
||||
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() {
|
||||
createLoginView(this);
|
||||
|
||||
// Verify that Google and Facebook registration buttons are displayed
|
||||
expect($('.button-Google')).toBeVisible();
|
||||
expect($('.button-Facebook')).toBeVisible();
|
||||
});
|
||||
|
||||
it('displays a link to the password reset form', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Verify that the password reset link is displayed
|
||||
expect($('.forgot-password')).toBeVisible();
|
||||
});
|
||||
|
||||
it('validates login form fields', function() {
|
||||
createLoginView(this);
|
||||
|
||||
submitForm(true);
|
||||
|
||||
// Verify that validation of form fields occurred
|
||||
expect(view.validate).toHaveBeenCalledWith($('#login-email')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#login-password')[0]);
|
||||
});
|
||||
|
||||
it('displays login form validation errors', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Submit the form, with failed validation
|
||||
submitForm(false);
|
||||
|
||||
// Verify that submission errors are visible
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
|
||||
// Expect auth complete NOT to have been triggered
|
||||
expect(authComplete).toBe(false);
|
||||
});
|
||||
|
||||
it('displays an error if the server returns an error while logging in', function() {
|
||||
createLoginView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Simulate an error from the LMS servers
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Expect that an error is displayed and that auth complete is not triggered
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
expect(authComplete).toBe(false);
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitForm();
|
||||
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Expect that the error is hidden and auth complete is triggered
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,302 +6,332 @@ define([
|
||||
'js/student_account/models/RegisterModel',
|
||||
'js/student_account/views/RegisterView'
|
||||
], function($, _, TemplateHelpers, AjaxHelpers, RegisterModel, RegisterView) {
|
||||
describe('edx.student.account.RegisterView', function() {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
var model = null,
|
||||
view = null,
|
||||
requests = null,
|
||||
authComplete = false,
|
||||
PLATFORM_NAME = 'edX',
|
||||
USER_DATA = {
|
||||
email: 'xsy@edx.org',
|
||||
name: 'Xsy M. Education',
|
||||
username: 'Xsy',
|
||||
password: 'xsyisawesome',
|
||||
level_of_education: 'p',
|
||||
gender: 'm',
|
||||
year_of_birth: 2014,
|
||||
mailing_address: '141 Portland',
|
||||
goals: 'To boldly learn what no letter of the alphabet has learned before',
|
||||
honor_code: true
|
||||
},
|
||||
THIRD_PARTY_AUTH = {
|
||||
currentProvider: null,
|
||||
providers: [
|
||||
{
|
||||
name: 'Google',
|
||||
iconClass: 'icon-google-plus',
|
||||
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
iconClass: 'icon-facebook',
|
||||
loginUrl: '/auth/login/facebook/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/facebook/?auth_entry=account_register'
|
||||
}
|
||||
]
|
||||
},
|
||||
FORM_DESCRIPTION = {
|
||||
method: 'post',
|
||||
submit_url: '/user_api/v1/account/registration/',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
defaultValue: '',
|
||||
type: 'email',
|
||||
required: true,
|
||||
placeholder: 'place@holder.org',
|
||||
instructions: 'Enter your email.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Full Name',
|
||||
defaultValue: '',
|
||||
type: 'text',
|
||||
required: true,
|
||||
instructions: 'Enter your username.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
defaultValue: '',
|
||||
type: 'text',
|
||||
required: true,
|
||||
instructions: 'Enter your username.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
defaultValue: '',
|
||||
type: 'password',
|
||||
required: true,
|
||||
instructions: 'Enter your password.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'level_of_education',
|
||||
label: 'Highest Level of Education Completed',
|
||||
defaultValue: '',
|
||||
type: 'select',
|
||||
options: [
|
||||
{value: "", name: "--"},
|
||||
{value: "p", name: "Doctorate"},
|
||||
{value: "m", name: "Master's or professional degree"},
|
||||
{value: "b", name: "Bachelor's degree"},
|
||||
],
|
||||
required: false,
|
||||
instructions: 'Select your education level.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'gender',
|
||||
label: 'Gender',
|
||||
defaultValue: '',
|
||||
type: 'select',
|
||||
options: [
|
||||
{value: "", name: "--"},
|
||||
{value: "m", name: "Male"},
|
||||
{value: "f", name: "Female"},
|
||||
{value: "o", name: "Other"},
|
||||
],
|
||||
required: false,
|
||||
instructions: 'Select your gender.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'year_of_birth',
|
||||
label: 'Year of Birth',
|
||||
defaultValue: '',
|
||||
type: 'select',
|
||||
options: [
|
||||
{value: "", name: "--"},
|
||||
{value: 1900, name: "1900"},
|
||||
{value: 1950, name: "1950"},
|
||||
{value: 2014, name: "2014"},
|
||||
],
|
||||
required: false,
|
||||
instructions: 'Select your year of birth.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'mailing_address',
|
||||
label: 'Mailing Address',
|
||||
defaultValue: '',
|
||||
type: 'textarea',
|
||||
required: false,
|
||||
instructions: 'Enter your mailing address.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'goals',
|
||||
label: 'Goals',
|
||||
defaultValue: '',
|
||||
type: 'textarea',
|
||||
required: false,
|
||||
instructions: "If you'd like, tell us why you're interested in edX.",
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'honor_code',
|
||||
label: 'I agree to the <a href="/honor">Terms of Service and Honor Code</a>',
|
||||
defaultValue: '',
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
instructions: '',
|
||||
restrictions: {}
|
||||
}
|
||||
]
|
||||
};
|
||||
describe('edx.student.account.RegisterView', function() {
|
||||
|
||||
var createRegisterView = function(that) {
|
||||
// Initialize the register model
|
||||
model = new RegisterModel({}, {
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
method: FORM_DESCRIPTION.method
|
||||
});
|
||||
|
||||
// Initialize the register view
|
||||
view = new RegisterView({
|
||||
fields: FORM_DESCRIPTION.fields,
|
||||
model: model,
|
||||
thirdPartyAuth: THIRD_PARTY_AUTH,
|
||||
platformName: PLATFORM_NAME
|
||||
});
|
||||
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(that);
|
||||
|
||||
// Intercept events from the view
|
||||
authComplete = false;
|
||||
view.on("auth-complete", function() {
|
||||
authComplete = true;
|
||||
});
|
||||
var 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: [
|
||||
{
|
||||
name: 'Google',
|
||||
iconClass: 'icon-google-plus',
|
||||
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
iconClass: 'icon-facebook',
|
||||
loginUrl: '/auth/login/facebook/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/facebook/?auth_entry=account_register'
|
||||
}
|
||||
]
|
||||
},
|
||||
FORM_DESCRIPTION = {
|
||||
method: 'post',
|
||||
submit_url: '/user_api/v1/account/registration/',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
defaultValue: '',
|
||||
type: 'email',
|
||||
required: true,
|
||||
placeholder: 'place@holder.org',
|
||||
instructions: 'Enter your email.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Full Name',
|
||||
defaultValue: '',
|
||||
type: 'text',
|
||||
required: true,
|
||||
instructions: 'Enter your username.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
defaultValue: '',
|
||||
type: 'text',
|
||||
required: true,
|
||||
instructions: 'Enter your username.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
defaultValue: '',
|
||||
type: 'password',
|
||||
required: true,
|
||||
instructions: 'Enter your password.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'level_of_education',
|
||||
label: 'Highest Level of Education Completed',
|
||||
defaultValue: '',
|
||||
type: 'select',
|
||||
options: [
|
||||
{value: "", name: "--"},
|
||||
{value: "p", name: "Doctorate"},
|
||||
{value: "m", name: "Master's or professional degree"},
|
||||
{value: "b", name: "Bachelor's degree"},
|
||||
],
|
||||
required: false,
|
||||
instructions: 'Select your education level.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'gender',
|
||||
label: 'Gender',
|
||||
defaultValue: '',
|
||||
type: 'select',
|
||||
options: [
|
||||
{value: "", name: "--"},
|
||||
{value: "m", name: "Male"},
|
||||
{value: "f", name: "Female"},
|
||||
{value: "o", name: "Other"},
|
||||
],
|
||||
required: false,
|
||||
instructions: 'Select your gender.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'year_of_birth',
|
||||
label: 'Year of Birth',
|
||||
defaultValue: '',
|
||||
type: 'select',
|
||||
options: [
|
||||
{value: "", name: "--"},
|
||||
{value: 1900, name: "1900"},
|
||||
{value: 1950, name: "1950"},
|
||||
{value: 2014, name: "2014"},
|
||||
],
|
||||
required: false,
|
||||
instructions: 'Select your year of birth.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'mailing_address',
|
||||
label: 'Mailing Address',
|
||||
defaultValue: '',
|
||||
type: 'textarea',
|
||||
required: false,
|
||||
instructions: 'Enter your mailing address.',
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: 'goals',
|
||||
label: 'Goals',
|
||||
defaultValue: '',
|
||||
type: 'textarea',
|
||||
required: false,
|
||||
instructions: "If you'd like, tell us why you're interested in edX.",
|
||||
restrictions: {}
|
||||
},
|
||||
{
|
||||
name: '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 submitForm = function(validationSuccess) {
|
||||
// Simulate manual entry of registration form data
|
||||
$('#register-email').val(USER_DATA.email);
|
||||
$('#register-name').val(USER_DATA.name);
|
||||
$('#register-username').val(USER_DATA.username);
|
||||
$('#register-password').val(USER_DATA.password);
|
||||
$('#register-level_of_education').val(USER_DATA.level_of_education);
|
||||
$('#register-gender').val(USER_DATA.gender);
|
||||
$('#register-year_of_birth').val(USER_DATA.year_of_birth);
|
||||
$('#register-mailing_address').val(USER_DATA.mailing_address);
|
||||
$('#register-goals').val(USER_DATA.goals);
|
||||
|
||||
// Check the honor code checkbox
|
||||
$('#register-honor_code').prop('checked', USER_DATA.honor_code);
|
||||
|
||||
// Create a fake click event
|
||||
var clickEvent = $.Event('click');
|
||||
|
||||
// If validationSuccess isn't passed, we avoid
|
||||
// spying on `view.validate` twice
|
||||
if ( !_.isUndefined(validationSuccess) ) {
|
||||
// Force validation to return as expected
|
||||
spyOn(view, 'validate').andReturn({
|
||||
isValid: validationSuccess,
|
||||
message: 'Submission was validated.'
|
||||
});
|
||||
}
|
||||
|
||||
// Submit the email address
|
||||
view.submitForm(clickEvent);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<div id="register-form"></div>');
|
||||
TemplateHelpers.installTemplate('templates/student_account/register');
|
||||
TemplateHelpers.installTemplate('templates/student_account/form_field');
|
||||
var createRegisterView = function(that) {
|
||||
// Initialize the register model
|
||||
model = new RegisterModel({}, {
|
||||
url: FORM_DESCRIPTION.submit_url,
|
||||
method: FORM_DESCRIPTION.method
|
||||
});
|
||||
|
||||
it('registers a new user', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( USER_DATA )
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that auth complete is triggered
|
||||
expect(authComplete).toBe(true);
|
||||
// Initialize the register view
|
||||
view = new RegisterView({
|
||||
fields: FORM_DESCRIPTION.fields,
|
||||
model: model,
|
||||
thirdPartyAuth: THIRD_PARTY_AUTH,
|
||||
platformName: PLATFORM_NAME
|
||||
});
|
||||
|
||||
it('displays third-party auth registration buttons', function() {
|
||||
createRegisterView(this);
|
||||
// Spy on AJAX requests
|
||||
requests = AjaxHelpers.requests(that);
|
||||
|
||||
// Verify that Google and Facebook registration buttons are displayed
|
||||
expect($('.button-Google')).toBeVisible();
|
||||
expect($('.button-Facebook')).toBeVisible();
|
||||
// Intercept events from the view
|
||||
authComplete = false;
|
||||
view.on("auth-complete", function() {
|
||||
authComplete = true;
|
||||
});
|
||||
};
|
||||
|
||||
it('validates registration form fields', function() {
|
||||
createRegisterView(this);
|
||||
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);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
// Check the honor code checkbox
|
||||
$('#register-honor_code').prop('checked', USER_DATA.honor_code);
|
||||
|
||||
// Verify that validation of form fields occurred
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-email')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-name')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-username')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-password')[0]);
|
||||
// Create a fake click event
|
||||
var clickEvent = $.Event('click');
|
||||
|
||||
// Verify that no submission errors are visible
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
});
|
||||
// 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.'
|
||||
});
|
||||
}
|
||||
|
||||
it('displays registration form validation errors', function() {
|
||||
createRegisterView(this);
|
||||
// Submit the email address
|
||||
view.submitForm(clickEvent);
|
||||
};
|
||||
|
||||
// Submit the form, with failed validation
|
||||
submitForm(false);
|
||||
|
||||
// Verify that submission errors are visible
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
|
||||
// Expect that auth complete is NOT triggered
|
||||
expect(authComplete).toBe(false);
|
||||
});
|
||||
|
||||
it('displays an error if the server returns an error while registering', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Simulate an error from the LMS servers
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Expect that an error is displayed and that auth complete is NOT triggered
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
expect(authComplete).toBe(false);
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitForm();
|
||||
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Expect that the error is hidden and that auth complete is triggered
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
beforeEach(function() {
|
||||
setFixtures('<div id="register-form"></div>');
|
||||
TemplateHelpers.installTemplate('templates/student_account/register');
|
||||
TemplateHelpers.installTemplate('templates/student_account/form_field');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('registers a new user', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm( true );
|
||||
|
||||
// Verify that the client contacts the server with the expected data
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( USER_DATA )
|
||||
);
|
||||
|
||||
// Respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Verify that auth complete is triggered
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
|
||||
it('sends analytics info containing the enrolled course ID', function() {
|
||||
createRegisterView( this );
|
||||
|
||||
// Simulate that the user is attempting to enroll in a course
|
||||
// by setting the course_id query string param.
|
||||
spyOn($, 'url').andCallFake(function( param ) {
|
||||
if (param === "?course_id") {
|
||||
return encodeURIComponent( COURSE_ID );
|
||||
}
|
||||
});
|
||||
|
||||
// Attempt to register
|
||||
submitForm( true );
|
||||
|
||||
// Verify that the client sent the course ID for analytics
|
||||
var expectedData = {};
|
||||
$.extend(expectedData, USER_DATA, {
|
||||
analytics: JSON.stringify({
|
||||
enroll_course_id: COURSE_ID
|
||||
})
|
||||
});
|
||||
|
||||
AjaxHelpers.expectRequest(
|
||||
requests, 'POST',
|
||||
FORM_DESCRIPTION.submit_url,
|
||||
$.param( expectedData )
|
||||
);
|
||||
});
|
||||
|
||||
it('displays third-party auth registration buttons', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Verify that Google and Facebook registration buttons are displayed
|
||||
expect($('.button-Google')).toBeVisible();
|
||||
expect($('.button-Facebook')).toBeVisible();
|
||||
});
|
||||
|
||||
it('validates registration form fields', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Verify that validation of form fields occurred
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-email')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-name')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-username')[0]);
|
||||
expect(view.validate).toHaveBeenCalledWith($('#register-password')[0]);
|
||||
|
||||
// Verify that no submission errors are visible
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
it('displays registration form validation errors', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Submit the form, with failed validation
|
||||
submitForm(false);
|
||||
|
||||
// Verify that submission errors are visible
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
|
||||
// Expect that auth complete is NOT triggered
|
||||
expect(authComplete).toBe(false);
|
||||
});
|
||||
|
||||
it('displays an error if the server returns an error while registering', function() {
|
||||
createRegisterView(this);
|
||||
|
||||
// Submit the form, with successful validation
|
||||
submitForm(true);
|
||||
|
||||
// Simulate an error from the LMS servers
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
|
||||
// Expect that an error is displayed and that auth complete is NOT triggered
|
||||
expect(view.$errors).not.toHaveClass('hidden');
|
||||
expect(authComplete).toBe(false);
|
||||
|
||||
// If we try again and succeed, the error should go away
|
||||
submitForm();
|
||||
|
||||
// This time, respond with status code 200
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// Expect that the error is hidden and that auth complete is triggered
|
||||
expect(view.$errors).toHaveClass('hidden');
|
||||
expect(authComplete).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,14 +24,27 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
sync: function(method, model) {
|
||||
var headers = {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
};
|
||||
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: model.attributes,
|
||||
data: data,
|
||||
headers: headers,
|
||||
success: function() {
|
||||
model.trigger('sync');
|
||||
|
||||
@@ -30,14 +30,27 @@ var edx = edx || {};
|
||||
},
|
||||
|
||||
sync: function(method, model) {
|
||||
var headers = {
|
||||
'X-CSRFToken': $.cookie('csrftoken')
|
||||
};
|
||||
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: model.attributes,
|
||||
data: data,
|
||||
headers: headers,
|
||||
success: function() {
|
||||
model.trigger('sync');
|
||||
|
||||
Reference in New Issue
Block a user