Commit d3459be0 by Will Daly

Add Jasmine tests for the payment/verification flow.

parent 9bd1f3a8
define(['sinon', 'underscore'], function(sinon, _) { define(['sinon', 'underscore'], function(sinon, _) {
var fakeServer, fakeRequests, expectRequest, expectJsonRequest, var fakeServer, fakeRequests, expectRequest, expectJsonRequest,
respondWithJson, respondWithError, respondToDelete; respondWithJson, respondWithError, respondWithTextError, respondToDelete;
/* These utility methods are used by Jasmine tests to create a mock server or /* These utility methods are used by Jasmine tests to create a mock server or
* get reference to mock requests. In either case, the cleanup (restore) is done with * get reference to mock requests. In either case, the cleanup (restore) is done with
...@@ -93,6 +93,22 @@ define(['sinon', 'underscore'], function(sinon, _) { ...@@ -93,6 +93,22 @@ define(['sinon', 'underscore'], function(sinon, _) {
); );
}; };
respondWithTextError = function(requests, statusCode, textResponse, requestIndex) {
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
if (_.isUndefined(statusCode)) {
statusCode = 500;
}
if (_.isUndefined(textResponse)) {
textResponse = "";
}
requests[requestIndex].respond(statusCode,
{ 'Content-Type': 'text/plain' },
textResponse
);
};
respondToDelete = function(requests, requestIndex) { respondToDelete = function(requests, requestIndex) {
if (_.isUndefined(requestIndex)) { if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1; requestIndex = requests.length - 1;
...@@ -108,6 +124,7 @@ define(['sinon', 'underscore'], function(sinon, _) { ...@@ -108,6 +124,7 @@ define(['sinon', 'underscore'], function(sinon, _) {
'expectJsonRequest': expectJsonRequest, 'expectJsonRequest': expectJsonRequest,
'respondWithJson': respondWithJson, 'respondWithJson': respondWithJson,
'respondWithError': respondWithError, 'respondWithError': respondWithError,
'respondWithTextError': respondWithTextError,
'respondToDelete': respondToDelete 'respondToDelete': respondToDelete
}; };
}); });
...@@ -512,7 +512,7 @@ class PayAndVerifyView(View): ...@@ -512,7 +512,7 @@ class PayAndVerifyView(View):
'disable_courseware_js': True, 'disable_courseware_js': True,
'display_steps': display_steps, 'display_steps': display_steps,
'contribution_amount': contribution_amount, 'contribution_amount': contribution_amount,
'is_active': request.user.is_active, 'is_active': json.dumps(request.user.is_active),
'messages': self._messages( 'messages': self._messages(
message, message,
course.display_name, course.display_name,
......
...@@ -384,7 +384,116 @@ ...@@ -384,7 +384,116 @@
'js/student_account/enrollment', 'js/student_account/enrollment',
'js/student_account/shoppingcart', 'js/student_account/shoppingcart',
] ]
} },
'js/verify_student/models/verification_model': {
exports: 'edx.verify_student.VerificationModel',
deps: [ 'jquery', 'underscore', 'backbone', 'jquery.cookie' ]
},
'js/verify_student/views/error_view': {
exports: 'edx.verify_student.ErrorView',
deps: [ 'jquery', 'underscore', 'backbone' ]
},
'js/verify_student/views/webcam_photo_view': {
exports: 'edx.verify_student.WebcamPhotoView',
deps: [ 'jquery', 'underscore', 'backbone', 'gettext' ]
},
'js/verify_student/views/progress_view': {
exports: 'edx.verify_student.ProgressView',
deps: [ 'jquery', 'underscore', 'backbone', 'gettext' ]
},
'js/verify_student/views/requirements_view': {
exports: 'edx.verify_student.RequirementsView',
deps: [ 'jquery', 'backbone', 'underscore', 'gettext' ]
},
'js/verify_student/views/step_view': {
exports: 'edx.verify_student.StepView',
deps: [ 'jquery', 'underscore', 'underscore.string', 'backbone', 'gettext' ]
},
'js/verify_student/views/intro_step_view': {
exports: 'edx.verify_student.IntroStepView',
deps: [
'jquery',
'js/verify_student/views/step_view',
'js/verify_student/views/requirements_view'
]
},
'js/verify_student/views/make_payment_step_view': {
exports: 'edx.verify_student.MakePaymentStepView',
deps: [
'jquery',
'underscore',
'gettext',
'jquery.cookie',
'jquery.url',
'js/verify_student/views/step_view',
'js/verify_student/views/requirements_view'
]
},
'js/verify_student/views/payment_confirmation_step_view': {
exports: 'edx.verify_student.PaymentConfirmationStepView',
deps: [
'jquery',
'underscore',
'gettext',
'js/verify_student/views/step_view',
'js/verify_student/views/requirements_view'
]
},
'js/verify_student/views/face_photo_step_view': {
exports: 'edx.verify_student.FacePhotoStepView',
deps: [
'jquery',
'underscore',
'gettext',
'js/verify_student/views/step_view',
'js/verify_student/views/webcam_photo_view'
]
},
'js/verify_student/views/id_photo_step_view': {
exports: 'edx.verify_student.IDPhotoStepView',
deps: [
'jquery',
'underscore',
'gettext',
'js/verify_student/views/step_view',
'js/verify_student/views/webcam_photo_view'
]
},
'js/verify_student/views/review_photos_step_view': {
exports: 'edx.verify_student.ReviewPhotosStepView',
deps: [
'jquery',
'underscore',
'gettext',
'js/verify_student/views/step_view',
'js/verify_student/views/webcam_photo_view'
]
},
'js/verify_student/views/enrollment_confirmation_step_view': {
exports: 'edx.verify_student.EnrollmentConfirmationStepView',
deps: [
'jquery',
'js/verify_student/views/step_view',
]
},
'js/verify_student/views/pay_and_verify_view': {
exports: 'edx.verify_student.PayAndVerifyView',
deps: [
'jquery',
'underscore',
'backbone',
'gettext',
'js/verify_student/models/verification_model',
'js/verify_student/views/progress_view',
'js/verify_student/views/intro_step_view',
'js/verify_student/views/make_payment_step_view',
'js/verify_student/views/payment_confirmation_step_view',
'js/verify_student/views/face_photo_step_view',
'js/verify_student/views/id_photo_step_view',
'js/verify_student/views/review_photos_step_view',
'js/verify_student/views/enrollment_confirmation_step_view'
]
},
} }
}); });
...@@ -406,7 +515,11 @@ ...@@ -406,7 +515,11 @@
'lms/include/js/spec/student_account/enrollment_spec.js', 'lms/include/js/spec/student_account/enrollment_spec.js',
'lms/include/js/spec/student_account/emailoptin_spec.js', 'lms/include/js/spec/student_account/emailoptin_spec.js',
'lms/include/js/spec/student_account/shoppingcart_spec.js', 'lms/include/js/spec/student_account/shoppingcart_spec.js',
'lms/include/js/spec/student_profile/profile_spec.js' 'lms/include/js/spec/student_profile/profile_spec.js',
'lms/include/js/spec/verify_student/pay_and_verify_view_spec.js',
'lms/include/js/spec/verify_student/webcam_photo_view_spec.js',
'lms/include/js/spec/verify_student/review_photos_step_view_spec.js',
'lms/include/js/spec/verify_student/make_payment_step_view_spec.js'
]); ]);
}).call(this, requirejs, define); }).call(this, requirejs, define);
...@@ -52,4 +52,4 @@ define(['backbone', 'jquery', 'js/verify_student/photocapture'], ...@@ -52,4 +52,4 @@ define(['backbone', 'jquery', 'js/verify_student/photocapture'],
}); });
}); });
}); });
\ No newline at end of file
define([
'jquery',
'underscore',
'backbone',
'js/common_helpers/ajax_helpers',
'js/common_helpers/template_helpers',
'js/verify_student/views/make_payment_step_view'
],
function( $, _, Backbone, AjaxHelpers, TemplateHelpers, MakePaymentStepView ) {
'use strict';
describe( 'edx.verify_student.MakePaymentStepView', function() {
var PAYMENT_URL = "/pay";
var PAYMENT_PARAMS = {
orderId: "test-order",
signature: "abcd1234"
};
var STEP_DATA = {
minPrice: "12",
suggestedPrices: ["34.56", "78.90"],
currency: "usd",
purchaseEndpoint: PAYMENT_URL,
courseKey: "edx/test/test"
};
var SERVER_ERROR_MSG = "An error occurred!";
var createView = function( stepDataOverrides ) {
var view = new MakePaymentStepView({
el: $( '#current-step-container' ),
templateName: 'make_payment_step',
stepData: _.extend( _.clone( STEP_DATA ), stepDataOverrides ),
errorModel: new ( Backbone.Model.extend({}) )()
}).render();
// Stub the payment form submission
spyOn( view, 'submitForm' ).andCallFake( function() {} );
return view;
};
var expectPriceOptions = function( prices ) {
var sel;
_.each( prices, function( price ) {
sel = _.sprintf( 'input[name="contribution"][value="%s"]', price );
expect( $( sel ).length > 0 ).toBe( true );
});
};
var expectPriceSelected = function( price ) {
var sel = $( _.sprintf( 'input[name="contribution"][value="%s"]', price ) );
// If the option is available, it should be selected
if ( sel.length > 0 ) {
expect( sel.prop( 'checked' ) ).toBe( true );
} else {
// Otherwise, the text box amount should be filled in
expect( $( '#contribution-other' ).prop( 'checked' ) ).toBe( true );
expect( $( '#contribution-other-amt' ).val() ).toEqual( price );
}
};
var choosePriceOption = function( price ) {
var sel = _.sprintf( 'input[name="contribution"][value="%s"]', price );
$( sel ).trigger( 'click' );
};
var enterPrice = function( price ) {
$( '#contribution-other' ).trigger( 'click' );
$( '#contribution-other-amt' ).val( price );
};
var expectSinglePriceDisplayed = function( price ) {
var displayedPrice = $( '.contribution-option .label-value' ).text();
expect( displayedPrice ).toEqual( price );
};
var expectPaymentButtonEnabled = function( isEnabled ) {
var isDisabled = $( '#pay_button' ).hasClass('is-disabled');
expect( !isDisabled ).toEqual( isEnabled );
};
var expectPaymentDisabledBecauseInactive = function() {
var payButton = $( '#pay_button'),
activateButton = $( '#activate_button' );
// Payment button should be hidden
expect( payButton.length ).toEqual(0);
// Activate button should be displayed and disabled
expect( activateButton.length ).toEqual(1);
expect( activateButton.hasClass( 'is-disabled' ) ).toBe( true );
};
var goToPayment = function( requests, kwargs ) {
var params = {
contribution: kwargs.amount || "",
course_id: kwargs.courseId || ""
};
// Click the "go to payment" button
$( '#pay_button' ).click();
// Verify that the request was made to the server
AjaxHelpers.expectRequest(
requests, "POST", "/verify_student/create_order/",
$.param( params )
);
// Simulate the server response
if ( kwargs.succeeds ) {
AjaxHelpers.respondWithJson( requests, PAYMENT_PARAMS );
} else {
AjaxHelpers.respondWithTextError( requests, 400, SERVER_ERROR_MSG );
}
};
var expectPaymentSubmitted = function( view, params ) {
var form;
expect(view.submitForm).toHaveBeenCalled();
form = view.submitForm.mostRecentCall.args[0];
expect(form.serialize()).toEqual($.param(params));
expect(form.attr('method')).toEqual("POST");
expect(form.attr('action')).toEqual(PAYMENT_URL);
};
var expectErrorDisplayed = function( errorTitle ) {
var actualTitle = $( '#error h3.title' ).text();
expect( actualTitle ).toEqual( errorTitle );
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
setFixtures( '<div id="current-step-container"></div>' );
TemplateHelpers.installTemplate( 'templates/verify_student/make_payment_step' );
TemplateHelpers.installTemplate( 'templates/verify_student/requirements' );
});
it( 'allows users to choose a suggested price', function() {
var view = createView({}),
requests = AjaxHelpers.requests(this);
expectPriceOptions( STEP_DATA.suggestedPrices );
expectPaymentButtonEnabled( false );
choosePriceOption( STEP_DATA.suggestedPrices[1] );
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: STEP_DATA.suggestedPrices[1],
courseId: STEP_DATA.courseKey,
succeeds: true
});
expectPaymentSubmitted( view, PAYMENT_PARAMS );
});
it( 'allows users to pay the minimum price if no suggested prices are given', function() {
var view = createView({ suggestedPrices: [] }),
requests = AjaxHelpers.requests( this );
expectSinglePriceDisplayed( STEP_DATA.minPrice );
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: STEP_DATA.minPrice,
courseId: STEP_DATA.courseKey,
succeeds: true
});
expectPaymentSubmitted( view, PAYMENT_PARAMS );
});
it( 'allows the user to enter a contribution amount', function() {
var view = createView({}),
requests = AjaxHelpers.requests( this );
enterPrice( "67.89" );
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: "67.89",
courseId: STEP_DATA.courseKey,
succeeds: true
});
expectPaymentSubmitted( view, PAYMENT_PARAMS );
});
it( 'selects in the contribution amount if provided', function() {
// Pre-select one of the suggested prices
createView({
contributionAmount: STEP_DATA.suggestedPrices[1]
});
// Expect that the price is selected
expectPriceSelected( STEP_DATA.suggestedPrices[1]);
});
it( 'fills in the contribution amount if provided', function() {
// Pre-select a price NOT in the suggestions
createView({
contributionAmount: '99.99'
});
// Expect that the price is filled in
expectPriceSelected( '99.99' );
});
it( 'ignores the contribution pre-selected if no suggested prices are given', function() {
// No suggested prices, but a contribution is set
createView({
suggestedPrices: [],
contributionAmount: '99.99'
});
// Expect that the single price is displayed
expectSinglePriceDisplayed( STEP_DATA.minPrice );
});
it( 'disables payment for inactive users', function() {
createView({ isActive: false });
expectPaymentDisabledBecauseInactive();
});
it( 'displays an error if the order could not be created', function() {
var requests = AjaxHelpers.requests( this ),
view = createView({});
choosePriceOption( STEP_DATA.suggestedPrices[0] );
goToPayment( requests, {
amount: STEP_DATA.suggestedPrices[0],
courseId: STEP_DATA.courseKey,
succeeds: false
});
// Expect that an error is displayed
expect( view.errorModel.get('shown') ).toBe( true );
expect( view.errorModel.get('errorTitle') ).toEqual( 'Could not submit order' );
expect( view.errorModel.get('errorMsg') ).toEqual( SERVER_ERROR_MSG );
// Expect that the payment button is re-enabled
expectPaymentButtonEnabled( true );
});
});
}
);
define(['jquery', 'js/common_helpers/template_helpers', 'js/verify_student/views/pay_and_verify_view'],
function( $, TemplateHelpers, PayAndVerifyView ) {
'use strict';
describe( 'edx.verify_student.PayAndVerifyView', function() {
var TEMPLATES = [
'enrollment_confirmation_step',
'error',
'face_photo_step',
'id_photo_step',
'intro_step',
'make_payment_step',
'payment_confirmation_step',
'progress',
'requirements',
'review_photos_step',
'webcam_photo'
];
var INTRO_STEP = {
templateName: "intro_step",
name: "intro-step",
title: "Intro"
};
var DISPLAY_STEPS_FOR_PAYMENT = [
{
templateName: "make_payment_step",
name: "make-payment-step",
title: "Make Payment"
},
{
templateName: "payment_confirmation_step",
name: "payment-confirmation-step",
title: "Payment Confirmation"
}
];
var DISPLAY_STEPS_FOR_VERIFICATION = [
{
templateName: "face_photo_step",
name: "face-photo-step",
title: "Take Face Photo"
},
{
templateName: "id_photo_step",
name: "id-photo-step",
title: "ID Photo"
},
{
templateName: "review_photos_step",
name: "review-photos-step",
title: "Review Photos"
},
{
templateName: "enrollment_confirmation_step",
name: "enrollment-confirmation-step",
title: "Enrollment Confirmation"
}
];
var createView = function( displaySteps, currentStep ) {
return new PayAndVerifyView({
displaySteps: displaySteps,
currentStep: currentStep
}).render();
};
var expectStepRendered = function( stepName, stepNum, numSteps ) {
var i, j, sel;
// Expect that the step container div rendered
expect( $( '.' + stepName ).length > 0 ).toBe( true );
// Expect that the progress indicator shows the correct step
expect( $( '#progress-step-' + stepNum ).hasClass( 'is-current' ) ).toBe( true );
// Expect that all steps before this step are completed
for ( i = 1; i < stepNum; i++ ) {
sel = $( '#progress-step-' + i );
expect( sel.hasClass('is-completed') ).toBe( true );
expect( sel.hasClass('is-current') ).toBe( false );
}
// Expect that all steps after this step are neither completed nor current
for ( j = stepNum + 1; j <= numSteps; j++ ) {
sel = $( '#progress-step-' + j );
expect( sel.hasClass('is-completed') ).toBe( false );
expect( sel.hasClass('is-current') ).toBe( false );
}
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
setFixtures('<div id="pay-and-verify-container"></div>');
$.each( TEMPLATES, function( index, templateName ) {
TemplateHelpers.installTemplate('templates/verify_student/' + templateName );
});
});
it( 'renders payment and verification steps', function() {
// Create the view, starting on the first step
var view = createView(
DISPLAY_STEPS_FOR_PAYMENT.concat(DISPLAY_STEPS_FOR_VERIFICATION),
'make-payment-step'
);
// Verify that the first step rendered
expectStepRendered('make-payment-step', 1, 6);
// Iterate through the steps, ensuring that each is rendered
view.nextStep();
expectStepRendered('payment-confirmation-step', 2, 6);
view.nextStep();
expectStepRendered('face-photo-step', 3, 6);
view.nextStep();
expectStepRendered('id-photo-step', 4, 6);
view.nextStep();
expectStepRendered('review-photos-step', 5, 6);
view.nextStep();
expectStepRendered('enrollment-confirmation-step', 6, 6);
// Going past the last step stays on the last step
view.nextStep();
expectStepRendered('enrollment-confirmation-step', 6, 6);
});
it( 'renders intro and verification steps', function() {
var view = createView(
[INTRO_STEP].concat(DISPLAY_STEPS_FOR_VERIFICATION),
'intro-step'
);
// Verify that the first step rendered
expectStepRendered('intro-step', 1, 5);
// Iterate through the steps, ensuring that each is rendered
view.nextStep();
expectStepRendered('face-photo-step', 2, 5);
view.nextStep();
expectStepRendered('id-photo-step', 3, 5);
view.nextStep();
expectStepRendered('review-photos-step', 4, 5);
view.nextStep();
expectStepRendered('enrollment-confirmation-step', 5, 5);
});
it( 'starts from a later step', function() {
// Start from the payment confirmation step
var view = createView(
DISPLAY_STEPS_FOR_PAYMENT.concat(DISPLAY_STEPS_FOR_VERIFICATION),
'payment-confirmation-step'
);
// Verify that we start on the right step
expectStepRendered('payment-confirmation-step', 2, 6);
// Try moving to the next step
view.nextStep();
expectStepRendered('face-photo-step', 3, 6);
});
it( 'jumps to a particular step', function() {
// Start on the review photos step
var view = createView(
DISPLAY_STEPS_FOR_VERIFICATION,
'review-photos-step'
);
// Jump back to the face photo step
view.goToStep('face-photo-step');
expectStepRendered('face-photo-step', 1, 4);
});
});
}
);
define([
'jquery',
'underscore',
'backbone',
'js/common_helpers/ajax_helpers',
'js/common_helpers/template_helpers',
'js/verify_student/views/review_photos_step_view',
'js/verify_student/models/verification_model'
],
function( $, _, Backbone, AjaxHelpers, TemplateHelpers, ReviewPhotosStepView, VerificationModel ) {
'use strict';
describe( 'edx.verify_student.ReviewPhotosStepView', function() {
var STEP_DATA = {},
FULL_NAME = "Test User",
FACE_IMAGE = "abcd1234",
PHOTO_ID_IMAGE = "efgh56789",
SERVER_ERROR_MSG = "An error occurred!";
var createView = function() {
return new ReviewPhotosStepView({
el: $( '#current-step-container' ),
templateName: 'review_photos_step',
stepData: STEP_DATA,
model: new VerificationModel({
faceImage: FACE_IMAGE,
identificationImage: PHOTO_ID_IMAGE
}),
errorModel: new ( Backbone.Model.extend({}) )()
}).render();
};
var confirmPhotos = function( isConfirmed ) {
$('#confirm_pics_good').trigger( 'click' );
};
var submitPhotos = function( requests, expectedParams, succeeds ) {
// Submit the photos
$( '#next_step_button' ).click();
// Expect a request to the server
AjaxHelpers.expectRequest(
requests, "POST", "/verify_student/submit-photos/",
$.param( expectedParams )
);
// Simulate the server response
if ( succeeds ) {
AjaxHelpers.respondWithJson( requests );
} else {
AjaxHelpers.respondWithTextError( requests, 400, SERVER_ERROR_MSG );
}
};
var setFullName = function( fullName ) {
$('#new-name').val( fullName );
};
var expectSubmitEnabled = function( isEnabled ) {
var isDisabled = $('#next_step_button').hasClass('is-disabled');
expect( !isDisabled ).toBe( isEnabled );
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
setFixtures( '<div id="current-step-container"></div>' );
TemplateHelpers.installTemplate( 'templates/verify_student/review_photos_step' );
});
it( 'requires the user to confirm before submitting photos', function() {
createView();
// Initially disabled
expectSubmitEnabled( false );
// Confirm the photos, enabling submission
confirmPhotos( true );
expectSubmitEnabled( true );
// Unconfirm the photos, disabling submission
confirmPhotos( false );
expectSubmitEnabled( false );
});
it( 'allows the user to change her full name', function() {
var requests = AjaxHelpers.requests( this );
createView();
setFullName( FULL_NAME );
confirmPhotos( true );
submitPhotos(
requests,
{
face_image: FACE_IMAGE,
photo_id_image: PHOTO_ID_IMAGE,
full_name: FULL_NAME
},
true
);
});
it( 'submits photos for verification', function() {
var requests = AjaxHelpers.requests( this );
createView();
confirmPhotos( true );
submitPhotos(
requests,
{
face_image: FACE_IMAGE,
photo_id_image: PHOTO_ID_IMAGE
},
true
);
// Expect that submission is disabled to prevent
// duplicate submission.
expectSubmitEnabled( false );
});
it( 'displays an error if photo submission fails', function() {
var view = createView(),
requests = AjaxHelpers.requests( this );
confirmPhotos( true );
submitPhotos(
requests,
{
face_image: FACE_IMAGE,
photo_id_image: PHOTO_ID_IMAGE
},
false
);
// Expect the submit button is re-enabled to allow
// the user to retry.
expectSubmitEnabled( true );
// Expect that an error message is displayed
expect( view.errorModel.get('shown') ).toBe( true );
expect( view.errorModel.get('errorTitle') ).toEqual( 'Could not submit photos' );
expect( view.errorModel.get('errorMsg') ).toEqual( SERVER_ERROR_MSG );
});
});
}
);
define([
'jquery',
'backbone',
'js/common_helpers/template_helpers',
'js/common_helpers/ajax_helpers',
'js/verify_student/views/webcam_photo_view',
'js/verify_student/models/verification_model'
],
function( $, Backbone, TemplateHelpers, AjaxHelpers, WebcamPhotoView, VerificationModel ) {
'use strict';
describe( 'edx.verify_student.WebcamPhotoView', function() {
var IMAGE_DATA = "abcd1234",
VIDEO_ERROR_TITLE = "video capture error",
VIDEO_ERROR_MSG = "video error msg";
/**
* For the purposes of these tests, we stub out the backend
* video capture implementation.
* This allows us to easily test the application logic
* without needing to handle the subtleties of video capture
* (especially cross-browser).
* However, this means that the test suite does NOT adequately
* cover the HTML5 / Flash webcam integration. We will need
* cross-browser manual testing to verify that this works correctly.
*/
var StubBackend = function( name, isSupported, snapshotSuccess ) {
if ( _.isUndefined( isSupported ) ) {
isSupported = true;
}
if ( _.isUndefined( snapshotSuccess ) ) {
snapshotSuccess = true;
}
return {
name: name,
initialize: function() {},
isSupported: function() { return isSupported; },
snapshot: function() { return snapshotSuccess; },
getImageData: function() { return IMAGE_DATA; },
reset: function() {}
};
};
var createView = function( backends ) {
return new WebcamPhotoView({
el: $( '#current-step-container' ),
model: new VerificationModel({}),
modelAttribute: 'faceImage',
errorModel: new ( Backbone.Model.extend({}) )(),
submitButton: $( '#submit_button' ),
backends: backends
}).render();
};
var takeSnapshot = function() {
$( '#webcam_capture_button' ).click();
};
var resetWebcam = function() {
$( '#webcam_reset_button' ).click();
};
var expectButtonShown = function( obj ) {
var resetButton = $( '#webcam_reset_button' ),
captureButton = $( '#webcam_capture_button' );
expect( captureButton.hasClass( 'is-hidden') ).toBe( !obj.snapshot );
expect( resetButton.hasClass( 'is-hidden') ).toBe( !obj.reset );
};
var expectSubmitEnabled = function( isEnabled ) {
var isDisabled = $( '#submit_button' ).hasClass( 'is-disabled' );
expect( !isDisabled ).toEqual( isEnabled );
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
setFixtures(
'<div id="current-step-container"></div>' +
'<input type="button" id="submit_button" class="is-disabled"></input>'
);
TemplateHelpers.installTemplate( 'templates/verify_student/webcam_photo' );
});
it( 'takes a snapshot', function() {
var view = createView( [ StubBackend( "html5" ) ] );
// Spy on the backend
spyOn( view.backend, 'snapshot' ).andCallThrough();
// Initially, only the snapshot button is shown
expectButtonShown({
snapshot: true,
reset: false
});
expectSubmitEnabled( false );
// Take the snapshot
takeSnapshot();
// Expect that the backend was used to take the snapshot
expect( view.backend.snapshot ).toHaveBeenCalled();
// Expect that buttons were updated
expectButtonShown({
snapshot: false,
reset: true
});
expectSubmitEnabled( true );
// Expect that the image data was saved to the model
expect( view.model.get( 'faceImage' ) ).toEqual( IMAGE_DATA );
});
it( 'resets the camera', function() {
var view = createView( [ StubBackend( "html5" ) ]);
// Spy on the backend
spyOn( view.backend, 'reset' ).andCallThrough();
// Take the snapshot, then reset
takeSnapshot();
resetWebcam();
// Expect that the backend was reset
expect( view.backend.reset ).toHaveBeenCalled();
// Expect that we're back to the initial button shown state
expectButtonShown({
snapshot: true,
reset: false
});
expectSubmitEnabled( false );
// Expect that the image data is wiped from the model
expect( view.model.get( 'faceImage' ) ).toEqual( "" );
});
it( 'falls back to a second video capture backend', function() {
var backends = [ StubBackend( "html5", false ), StubBackend( "flash", true ) ],
view = createView( backends );
// Expect that the second backend is chosen
expect( view.backend.name ).toEqual( backends[1].name );
});
it( 'displays an error if no video backend is supported', function() {
var backends = [ StubBackend( "html5", false ), StubBackend( "flash", false ) ],
view = createView( backends );
// Expect an error
expect( view.errorModel.get( 'errorTitle' ) ).toEqual( 'No Flash Detected' );
expect( view.errorModel.get( 'errorMsg' ) ).toContain( 'Get Flash' );
expect( view.errorModel.get( 'shown' ) ).toBe( true );
// Expect that submission is disabled
expectSubmitEnabled( false );
});
it( 'displays an error if the snapshot fails', function() {
var backends = [ StubBackend( "html5", true, false ) ],
view = createView( backends );
// Take a snapshot
takeSnapshot();
// Do NOT expect an error displayed
expect( view.errorModel.get( 'shown' ) ).not.toBe( true );
// Expect that the capture button is still enabled
// so the user can retry.
expectButtonShown({
snapshot: true,
reset: false
});
// Expect that submit is NOT enabled, since the user didn't
// successfully take a snapshot.
expectSubmitEnabled( false );
});
it( 'displays an error triggered by the backend', function() {
var view = createView( [ StubBackend( "html5") ] );
// Simulate an error triggered by the backend
// This could occur at any point, including
// while the video capture is being set up.
view.backend.trigger( 'error', VIDEO_ERROR_TITLE, VIDEO_ERROR_MSG );
// Verify that the error is displayed
expect( view.errorModel.get( 'errorTitle' ) ).toEqual( VIDEO_ERROR_TITLE );
expect( view.errorModel.get( 'errorMsg' ) ).toEqual( VIDEO_ERROR_MSG );
expect( view.errorModel.get( 'shown' ) ).toBe( true );
// Expect that buttons are hidden
expectButtonShown({
snapshot: false,
reset: false
});
expectSubmitEnabled( false );
});
});
}
);
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
*/ */
var edx = edx || {}; var edx = edx || {};
(function($) { (function( $, _ ) {
'use strict'; 'use strict';
var errorView, var errorView,
el = $('#pay-and-verify-container'); el = $('#pay-and-verify-container');
...@@ -47,8 +47,11 @@ var edx = edx || {}; ...@@ -47,8 +47,11 @@ var edx = edx || {};
requirements: el.data('requirements'), requirements: el.data('requirements'),
courseKey: el.data('course-key'), courseKey: el.data('course-key'),
minPrice: el.data('course-mode-min-price'), minPrice: el.data('course-mode-min-price'),
suggestedPrices: (el.data('course-mode-suggested-prices') || "").split(","),
contributionAmount: el.data('contribution-amount'), contributionAmount: el.data('contribution-amount'),
suggestedPrices: _.filter(
(el.data('course-mode-suggested-prices') || "").split(","),
function( price ) { return Boolean( price ); }
),
currency: el.data('course-mode-currency'), currency: el.data('course-mode-currency'),
purchaseEndpoint: el.data('purchase-endpoint') purchaseEndpoint: el.data('purchase-endpoint')
}, },
...@@ -68,4 +71,4 @@ var edx = edx || {}; ...@@ -68,4 +71,4 @@ var edx = edx || {};
} }
} }
}).render(); }).render();
})(jQuery); })( jQuery, _ );
...@@ -15,6 +15,14 @@ var edx = edx || {}; ...@@ -15,6 +15,14 @@ var edx = edx || {};
postRender: function() { postRender: function() {
// Track a virtual pageview, for easy funnel reconstruction. // Track a virtual pageview, for easy funnel reconstruction.
window.analytics.page( 'verification', this.templateName ); window.analytics.page( 'verification', this.templateName );
},
defaultContext: function() {
return {
courseName: '',
courseStartDate: '',
coursewareUrl: ''
};
} }
}); });
......
...@@ -10,6 +10,14 @@ var edx = edx || {}; ...@@ -10,6 +10,14 @@ var edx = edx || {};
edx.verify_student.IntroStepView = edx.verify_student.StepView.extend({ edx.verify_student.IntroStepView = edx.verify_student.StepView.extend({
defaultContext: function() {
return {
introTitle: '',
introMsg: '',
isActive: false
};
},
// Currently, this view doesn't need to install any custom event handlers, // Currently, this view doesn't need to install any custom event handlers,
// since the button in the template reloads the page with a // since the button in the template reloads the page with a
// ?skip-intro=1 GET parameter. The reason for this is that we // ?skip-intro=1 GET parameter. The reason for this is that we
......
...@@ -10,6 +10,15 @@ var edx = edx || {}; ...@@ -10,6 +10,15 @@ var edx = edx || {};
edx.verify_student.MakePaymentStepView = edx.verify_student.StepView.extend({ edx.verify_student.MakePaymentStepView = edx.verify_student.StepView.extend({
defaultContext: function() {
return {
isActive: true,
suggestedPrices: [],
minPrice: 0,
currency: "usd"
};
},
postRender: function() { postRender: function() {
// Render requirements // Render requirements
new edx.verify_student.RequirementsView({ new edx.verify_student.RequirementsView({
...@@ -26,8 +35,14 @@ var edx = edx || {}; ...@@ -26,8 +35,14 @@ var edx = edx || {};
this.selectPaymentAmount( this.stepData.contributionAmount ); this.selectPaymentAmount( this.stepData.contributionAmount );
} }
// Enable the payment button once an amount is chosen if ( this.templateContext().suggestedPrices.length > 0 ) {
$( "input[name='contribution']" ).on( 'click', _.bind( this.enablePaymentButton, this ) ); // Enable the payment button once an amount is chosen
$( "input[name='contribution']" ).on( 'click', _.bind( this.enablePaymentButton, this ) );
} else {
// If there is only one payment option, then the user isn't shown
// radio buttons, so we need to enable the radio button.
this.enablePaymentButton();
}
// Handle payment submission // Handle payment submission
$( "#pay_button" ).on( 'click', _.bind( this.createOrder, this ) ); $( "#pay_button" ).on( 'click', _.bind( this.createOrder, this ) );
...@@ -89,41 +104,49 @@ var edx = edx || {}; ...@@ -89,41 +104,49 @@ var edx = edx || {};
// this page. A virtual pageview can be used to do this. // this page. A virtual pageview can be used to do this.
window.analytics.page( 'payment', 'payment_processor_step' ); window.analytics.page( 'payment', 'payment_processor_step' );
form.submit(); this.submitForm( form );
}, },
handleCreateOrderError: function( xhr ) { handleCreateOrderError: function( xhr ) {
var errorMsg = gettext( 'An unexpected error occurred. Please try again.' );
if ( xhr.status === 400 ) { if ( xhr.status === 400 ) {
this.errorModel.set({ errorMsg = xhr.responseText;
errorTitle: gettext( 'Could not submit order' ),
errorMsg: xhr.responseText,
shown: true
});
} else {
this.errorModel.set({
errorTitle: gettext( 'Could not submit order' ),
errorMsg: gettext( 'An unexpected error occurred. Please try again' ),
shown: true
});
} }
this.errorModel.set({
errorTitle: gettext( 'Could not submit order' ),
errorMsg: errorMsg,
shown: true
});
// Re-enable the button so the user can re-try // Re-enable the button so the user can re-try
$( "#payment-processor-form" ).removeClass("is-disabled"); $( "#pay_button" ).removeClass("is-disabled");
}, },
getPaymentAmount: function() { getPaymentAmount: function() {
var contributionInput = $("input[name='contribution']:checked", this.el); var contributionInput = $("input[name='contribution']:checked", this.el),
amount = null;
if ( contributionInput.attr('id') === 'contribution-other' ) { if ( contributionInput.attr('id') === 'contribution-other' ) {
return $( "input[name='contribution-other-amt']", this.el ).val(); amount = $( "input[name='contribution-other-amt']", this.el ).val();
} else { } else {
return contributionInput.val(); amount = contributionInput.val();
}
// If no suggested prices are available, then the user does not
// get the option to select a price. Default to the minimum.
if ( !amount ) {
amount = this.templateContext().minPrice;
} }
return amount;
}, },
selectPaymentAmount: function( amount ) { selectPaymentAmount: function( amount ) {
var amountFloat = parseFloat( amount ), var amountFloat = parseFloat( amount ),
foundPrice; foundPrice,
sel;
// Check if we have a suggested price that matches the amount // Check if we have a suggested price that matches the amount
foundPrice = _.find( foundPrice = _.find(
...@@ -135,7 +158,8 @@ var edx = edx || {}; ...@@ -135,7 +158,8 @@ var edx = edx || {};
// If we've found an option for the price, select it. // If we've found an option for the price, select it.
if ( foundPrice ) { if ( foundPrice ) {
$( '#contribution-' + foundPrice, this.el ).prop( 'checked', true ); sel = _.sprintf( 'input[name="contribution"][value="%s"]', foundPrice );
$( sel ).prop( 'checked', true );
} else { } else {
// Otherwise, enter the value into the text box // Otherwise, enter the value into the text box
$( '#contribution-other-amt', this.el ).val( amount ); $( '#contribution-other-amt', this.el ).val( amount );
...@@ -144,6 +168,13 @@ var edx = edx || {}; ...@@ -144,6 +168,13 @@ var edx = edx || {};
// In either case, enable the payment button // In either case, enable the payment button
this.enablePaymentButton(); this.enablePaymentButton();
return amount;
},
// Stubbed out in tests
submitForm: function( form ) {
form.submit();
} }
}); });
......
...@@ -16,8 +16,6 @@ var edx = edx || {}; ...@@ -16,8 +16,6 @@ var edx = edx || {};
edx.verify_student.PayAndVerifyView = Backbone.View.extend({ edx.verify_student.PayAndVerifyView = Backbone.View.extend({
el: '#pay-and-verify-container', el: '#pay-and-verify-container',
template: '#progress-tpl',
subviews: {}, subviews: {},
VERIFICATION_VIEW_NAMES: [ VERIFICATION_VIEW_NAMES: [
...@@ -27,7 +25,7 @@ var edx = edx || {}; ...@@ -27,7 +25,7 @@ var edx = edx || {};
], ],
initialize: function( obj ) { initialize: function( obj ) {
this.errorModel = obj.errorModel || {}; this.errorModel = obj.errorModel || null;
this.displaySteps = obj.displaySteps || []; this.displaySteps = obj.displaySteps || [];
this.progressView = new edx.verify_student.ProgressView({ this.progressView = new edx.verify_student.ProgressView({
...@@ -43,7 +41,7 @@ var edx = edx || {}; ...@@ -43,7 +41,7 @@ var edx = edx || {};
) )
}); });
this.initializeStepViews( obj.stepInfo ); this.initializeStepViews( obj.stepInfo || {} );
}, },
initializeStepViews: function( stepInfo ) { initializeStepViews: function( stepInfo ) {
......
...@@ -10,9 +10,14 @@ var edx = edx || {}; ...@@ -10,9 +10,14 @@ var edx = edx || {};
edx.verify_student.ReviewPhotosStepView = edx.verify_student.StepView.extend({ edx.verify_student.ReviewPhotosStepView = edx.verify_student.StepView.extend({
postRender: function() { defaultContext: function() {
var model = this.model; return {
platformName: "",
fullName: "",
};
},
postRender: function() {
// Load the photos from the previous steps // Load the photos from the previous steps
$( '#face_image' )[0].src = this.model.get('faceImage'); $( '#face_image' )[0].src = this.model.get('faceImage');
$( '#photo_id_image' )[0].src = this.model.get('identificationImage'); $( '#photo_id_image' )[0].src = this.model.get('identificationImage');
...@@ -49,6 +54,8 @@ var edx = edx || {}; ...@@ -49,6 +54,8 @@ var edx = edx || {};
}, },
submitPhotos: function() { submitPhotos: function() {
var fullName = $( '#new-name' ).val();
// Disable the submit button to prevent duplicate submissions // Disable the submit button to prevent duplicate submissions
$( '#next_step_button' ).addClass( 'is-disabled' ); $( '#next_step_button' ).addClass( 'is-disabled' );
...@@ -59,30 +66,28 @@ var edx = edx || {}; ...@@ -59,30 +66,28 @@ var edx = edx || {};
this.listenToOnce( this.model, 'error', _.bind( this.handleSubmissionError, this ) ); this.listenToOnce( this.model, 'error', _.bind( this.handleSubmissionError, this ) );
// Submit // Submit
this.model.set( 'fullName', $( '#new-name' ).val() ); if ( fullName ) {
this.model.set( 'fullName', fullName );
}
this.model.save(); this.model.save();
}, },
handleSubmissionError: function( xhr ) { handleSubmissionError: function( xhr ) {
var isConfirmChecked = $( "#confirm_pics_good" ).prop('checked'),
errorMsg = gettext( 'An unexpected error occurred. Please try again later.' );
// Re-enable the submit button to allow the user to retry // Re-enable the submit button to allow the user to retry
var isConfirmChecked = $( '#confirm_pics_good' ).prop( 'checked' );
$( '#next_step_button' ).toggleClass( 'is-disabled', !isConfirmChecked ); $( '#next_step_button' ).toggleClass( 'is-disabled', !isConfirmChecked );
// Display the error
if ( xhr.status === 400 ) { if ( xhr.status === 400 ) {
this.errorModel.set({ errorMsg = xhr.responseText;
errorTitle: gettext( 'Could not submit photos' ),
errorMsg: xhr.responseText,
shown: true
});
}
else {
this.errorModel.set({
errorTitle: gettext( 'Could not submit photos' ),
errorMsg: gettext( 'An unexpected error occurred. Please try again later.' ),
shown: true
});
} }
this.errorModel.set({
errorTitle: gettext( 'Could not submit photos' ),
errorMsg: errorMsg,
shown: true
});
}, },
expandCallback: function( event ) { expandCallback: function( event ) {
......
...@@ -26,19 +26,11 @@ ...@@ -26,19 +26,11 @@
}, },
render: function() { render: function() {
var templateHtml = $( "#" + this.templateName + "-tpl" ).html(), var templateHtml = $( "#" + this.templateName + "-tpl" ).html();
templateContext = {
nextStepNum: this.nextStepNum,
nextStepTitle: this.nextStepTitle
};
// Include step-specific information from the server
// (passed in from data- attributes to the parent view)
_.extend( templateContext, this.stepData );
// Allow subclasses to add additional information // Allow subclasses to add additional information
// to the template context, perhaps asynchronously. // to the template context, perhaps asynchronously.
this.updateContext( templateContext ).done( this.updateContext( this.templateContext() ).done(
function( templateContext ) { function( templateContext ) {
// Render the template into the DOM // Render the template into the DOM
$( this.el ).html( _.template( templateHtml, templateContext ) ); $( this.el ).html( _.template( templateHtml, templateContext ) );
...@@ -47,6 +39,8 @@ ...@@ -47,6 +39,8 @@
this.postRender(); this.postRender();
} }
).fail( _.bind( this.handleError, this ) ); ).fail( _.bind( this.handleError, this ) );
return this;
}, },
handleResponse: function( data ) { handleResponse: function( data ) {
...@@ -58,10 +52,8 @@ ...@@ -58,10 +52,8 @@
// Include step-specific information // Include step-specific information
_.extend( context, this.stepData ); _.extend( context, this.stepData );
this.renderedHtml = _.template( data, context ); // Track a virtual pageview, for easy funnel reconstruction.
$( this.el ).html( this.renderedHtml ); window.analytics.page( 'verification', this.templateName );
this.postRender();
}, },
handleError: function( errorTitle, errorMsg ) { handleError: function( errorTitle, errorMsg ) {
...@@ -72,6 +64,26 @@ ...@@ -72,6 +64,26 @@
}); });
}, },
templateContext: function() {
var context = {
nextStepNum: this.nextStepNum,
nextStepTitle: this.nextStepTitle
};
return _.extend( context, this.defaultContext(), this.stepData );
},
/**
* Provide default values for the template context.
* Subclasses can use this to fill in values that
* the underscore templates expect to be defined.
* This is especially useful for testing, so that the
* tests can pass in only the values relevant
* to the test.
*/
defaultContext: function() {
return {};
},
/** /**
* Subclasses can override this to add information to * Subclasses can override this to add information to
* the template context. This returns an asynchronous * the template context. This returns an asynchronous
......
...@@ -13,9 +13,10 @@ ...@@ -13,9 +13,10 @@
template: "#webcam_photo-tpl", template: "#webcam_photo-tpl",
videoCaptureBackend: { backends: [
{
name: "html5",
html5: {
initialize: function( obj ) { initialize: function( obj ) {
this.URL = (window.URL || window.webkitURL); this.URL = (window.URL || window.webkitURL);
this.video = obj.video || ""; this.video = obj.video || "";
...@@ -90,7 +91,9 @@ ...@@ -90,7 +91,9 @@
} }
}, },
flash: { {
name: "flash",
initialize: function( obj ) { initialize: function( obj ) {
this.wrapper = obj.wrapper || ""; this.wrapper = obj.wrapper || "";
this.imageData = ""; this.imageData = "";
...@@ -193,15 +196,18 @@ ...@@ -193,15 +196,18 @@
// so we don't need to keep checking. // so we don't need to keep checking.
} }
} }
}, ],
videoBackendPriority: ['html5', 'flash'],
initialize: function( obj ) { initialize: function( obj ) {
this.submitButton = obj.submitButton || ""; this.submitButton = obj.submitButton || "";
this.modelAttribute = obj.modelAttribute || ""; this.modelAttribute = obj.modelAttribute || "";
this.errorModel = obj.errorModel || {}; this.errorModel = obj.errorModel || null;
this.backend = this.chooseVideoCaptureBackend(); this.backend = _.find(
obj.backends || this.backends,
function( backend ) {
return backend.isSupported();
}
);
if ( !this.backend ) { if ( !this.backend ) {
this.handleError( this.handleError(
...@@ -232,15 +238,20 @@ ...@@ -232,15 +238,20 @@
// Initialize the video capture backend // Initialize the video capture backend
// We need to do this after rendering the template // We need to do this after rendering the template
// so that the backend has the opportunity to modify the DOM. // so that the backend has the opportunity to modify the DOM.
this.backend.initialize({ if ( this.backend ) {
wrapper: "#camera", this.backend.initialize({
video: '#photo_id_video', wrapper: "#camera",
canvas: '#photo_id_canvas' video: '#photo_id_video',
}); canvas: '#photo_id_canvas'
});
// Install event handlers
$( "#webcam_reset_button", this.el ).on( 'click', _.bind( this.reset, this ) ); // Install event handlers
$( "#webcam_capture_button", this.el ).on( 'click', _.bind( this.capture, this ) ); $( "#webcam_reset_button", this.el ).on( 'click', _.bind( this.reset, this ) );
$( "#webcam_capture_button", this.el ).on( 'click', _.bind( this.capture, this ) );
// Show the capture button
$( "#webcam_capture_button", this.el ).removeClass('is-hidden');
}
return this; return this;
}, },
...@@ -252,9 +263,12 @@ ...@@ -252,9 +263,12 @@
// Reset the video capture // Reset the video capture
this.backend.reset(); this.backend.reset();
// Reset data on the model
this.model.set( this.modelAttribute, "" );
// Go back to the initial button state // Go back to the initial button state
$( "#webcam_reset_button", this.el ).hide(); $( "#webcam_reset_button", this.el ).addClass('is-hidden');
$( "#webcam_capture_button", this.el ).show(); $( "#webcam_capture_button", this.el ).removeClass('is-hidden');
}, },
capture: function() { capture: function() {
...@@ -267,8 +281,8 @@ ...@@ -267,8 +281,8 @@
this.trigger( 'imageCaptured' ); this.trigger( 'imageCaptured' );
// Hide the capture button, and show the reset button // Hide the capture button, and show the reset button
$( "#webcam_capture_button", this.el ).hide(); $( "#webcam_capture_button", this.el ).addClass('is-hidden');
$( "#webcam_reset_button", this.el ).show(); $( "#webcam_reset_button", this.el ).removeClass('is-hidden');
// Save the data to the model // Save the data to the model
this.model.set( this.modelAttribute, this.backend.getImageData() ); this.model.set( this.modelAttribute, this.backend.getImageData() );
...@@ -278,29 +292,19 @@ ...@@ -278,29 +292,19 @@
} }
}, },
chooseVideoCaptureBackend: function() {
var i, backendName, backend;
for ( i = 0; i < this.videoBackendPriority.length; i++ ) {
backendName = this.videoBackendPriority[i];
backend = this.videoCaptureBackend[backendName];
if ( backend.isSupported() ) {
return backend;
}
}
},
handleError: function( errorTitle, errorMsg ) { handleError: function( errorTitle, errorMsg ) {
// Hide the buttons // Hide the buttons
$( "#webcam_capture_button", this.el ).hide(); $( "#webcam_capture_button", this.el ).addClass('is-hidden');
$( "#webcam_reset_button", this.el ).hide(); $( "#webcam_reset_button", this.el ).addClass('is-hidden');
// Show the error message // Show the error message
this.errorModel.set({ if ( this.errorModel ) {
errorTitle: errorTitle, this.errorModel.set({
errorMsg: errorMsg, errorTitle: errorTitle,
shown: true errorMsg: errorMsg,
}); shown: true
});
}
} }
}); });
......
...@@ -79,6 +79,7 @@ fixture_paths: ...@@ -79,6 +79,7 @@ fixture_paths:
- templates/dashboard - templates/dashboard
- templates/student_account - templates/student_account
- templates/student_profile - templates/student_profile
- templates/verify_student
- templates/file-upload.underscore - templates/file-upload.underscore
requirejs: requirejs:
......
<div class="wrapper-content-main"> <div class="wrapper-content-main enrollment-confirmation-step">
<article class="content-main"> <article class="content-main">
<h3 class="title"><%- gettext( "Congratulations! You are now enrolled in the verified track." ) %></h3> <h3 class="title"><%- gettext( "Congratulations! You are now enrolled in the verified track." ) %></h3>
<div class="instruction"> <div class="instruction">
......
<div id="wrapper-facephoto" class="wrapper-view block-photo"> <div id="wrapper-facephoto" class="wrapper-view block-photo face-photo-step">
<div class="facephoto view"> <div class="facephoto view">
<h3 class="title"><%- gettext( "Take Your Photo" ) %></h3> <h3 class="title"><%- gettext( "Take Your Photo" ) %></h3>
<div class="instruction"> <div class="instruction">
......
<div id="wrapper-idphoto" class="wrapper-view block-photo"> <div id="wrapper-idphoto" class="wrapper-view block-photo id-photo-step">
<div class="idphoto view"> <div class="idphoto view">
<h3 class="title"><%- gettext( "Show Us Your ID" ) %></h3> <h3 class="title"><%- gettext( "Show Us Your ID" ) %></h3>
<div class="instruction"> <div class="instruction">
......
<div class="wrapper-content-main"> <div class="wrapper-content-main intro-step">
<article class="content-main"> <article class="content-main">
<h3 class="title"><%- introTitle %></h3> <h3 class="title"><%- introTitle %></h3>
<div class="instruction"><p><%- introMsg %></p></div> <div class="instruction"><p><%- introMsg %></p></div>
...@@ -9,11 +9,11 @@ ...@@ -9,11 +9,11 @@
<nav class="nav-wizard is-ready"> <nav class="nav-wizard is-ready">
<ol class="wizard-steps"> <ol class="wizard-steps">
<li class="wizard-step"> <li class="wizard-step">
<a class="next action-primary" <% if (isActive == "False") { %>disabled="true"<% } %> id="next_step_button" href="?skip-first-step=1"> <a class="next action-primary" <% if ( !isActive ) { %>disabled="true"<% } %> id="next_step_button" href="?skip-first-step=1">
<% if ( isActive == "False" ) { %> <% if ( !isActive ) { %>
<%- gettext( "Activate Your Account" ) %> <%- gettext( "Activate Your Account" ) %>
<% } else { %> <% } else { %>
<%- _.sprintf( gettext( "Go to Step %s" ), nextStepNum ) %>: <%- nextStepTitle %> <%- _.sprintf( gettext( "Go to Step %(stepNumber)s" ), { stepNumber: nextStepNum } ) %>: <%- nextStepTitle %>
<% } %> <% } %>
</a> </a>
</li> </li>
......
<div id="wrapper-review" class="wrapper-view"> <div id="wrapper-review" class="wrapper-view make-payment-step">
<div class="review view"> <div class="review view">
<h3 class="title"><%- gettext( "Make Payment" ) %></h3> <h3 class="title"><%- gettext( "Make Payment" ) %></h3>
<div class="instruction"> <div class="instruction">
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<div class="requirements-container"></div> <div class="requirements-container"></div>
<% if ( isActive == "True" ) { %> <% if ( isActive ) { %>
<div class="wrapper-task"> <div class="wrapper-task">
<ol class="review-tasks"> <ol class="review-tasks">
<% if ( suggestedPrices.length > 0 ) { %> <% if ( suggestedPrices.length > 0 ) { %>
...@@ -79,13 +79,15 @@ ...@@ -79,13 +79,15 @@
<nav class="nav-wizard is-ready"> <nav class="nav-wizard is-ready">
<ol class="wizard-steps"> <ol class="wizard-steps">
<li class="wizard-step"> <li class="wizard-step">
<% if ( isActive ) { %>
<a class="next action-primary is-disabled" id="pay_button"> <a class="next action-primary is-disabled" id="pay_button">
<% if ( isActive == "False" ) { %> <%- gettext( "Go to payment" ) %>
<%- gettext( "Activate Your Account" ) %> </a>
<% } else { %> <% } else { %>
<%- gettext( "Go to payment" ) %> <a class="next action-primary is-disabled" id="activate_button">
<% } %> <%- gettext( "Activate Your Account" ) %>
</a> </a>
<% } %>
</li> </li>
</ol> </ol>
</nav> </nav>
......
...@@ -71,7 +71,7 @@ from verify_student.views import PayAndVerifyView ...@@ -71,7 +71,7 @@ from verify_student.views import PayAndVerifyView
data-current-step='${current_step}' data-current-step='${current_step}'
data-requirements='${json.dumps(requirements)}' data-requirements='${json.dumps(requirements)}'
data-msg-key='${message_key}' data-msg-key='${message_key}'
data-is-active="${is_active}" data-is-active='${is_active}'
data-intro-title='${messages.intro_title}' data-intro-title='${messages.intro_title}'
data-intro-msg='${messages.intro_msg}' data-intro-msg='${messages.intro_msg}'
></div> ></div>
......
<div class="wrapper-content-main"> <div class="wrapper-content-main payment-confirmation-step">
<article class="content-main"> <article class="content-main">
<h3 class="title"><%- gettext( "Congratulations! You are now enrolled in the verified track." ) %></h3> <h3 class="title"><%- gettext( "Congratulations! You are now enrolled in the verified track." ) %></h3>
<div class="instruction"> <div class="instruction">
......
<div class="wrapper-progress"> <div class="wrapper-progress progress">
<section class="progress"> <section class="progress">
<h3 class="sr title"><%- gettext("Your Progress") %></h3> <h3 class="sr title"><%- gettext("Your Progress") %></h3>
......
<div id="wrapper-review" class="wrapper-view"> <div id="wrapper-review" class="wrapper-view review-photos-step">
<div class="review view"> <div class="review view">
<h3 class="title"><%- gettext( "Verify Your Submission" ) %></h3> <h3 class="title"><%- gettext( "Verify Your Submission" ) %></h3>
<div class="instruction"> <div class="instruction">
......
...@@ -9,13 +9,13 @@ ...@@ -9,13 +9,13 @@
<div class="controls photo-controls"> <div class="controls photo-controls">
<ul class="list-controls"> <ul class="list-controls">
<li class="control control-retake" id="webcam_reset_button" style="display: none;"> <li class="control control-retake is-hidden" id="webcam_reset_button">
<a class="action action-redo"> <a class="action action-redo">
<i class="icon-undo"></i> <span class="sr"><%- gettext( "Retake" ) %></span> <i class="icon-undo"></i> <span class="sr"><%- gettext( "Retake" ) %></span>
</a> </a>
</li> </li>
<li class="control control-do" id="webcam_capture_button"> <li class="control control-do is-hidden" id="webcam_capture_button">
<a class="action action-do"> <a class="action action-do">
<i class="icon-camera"></i> <span class="sr"><%- gettext( "Take photo" ) %></span> <i class="icon-camera"></i> <span class="sr"><%- gettext( "Take photo" ) %></span>
</a> </a>
......
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