Commit 95a435ab by Will Daly

Include course ID in analytics events for logistration

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