ECOM-3206 allow run selection and enrollment (#12685)
This commit is contained in:
@@ -284,7 +284,8 @@ class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
|
||||
def _assert_program_data_present(self, response):
|
||||
"""Verify that program data is present."""
|
||||
self.assertContains(response, 'programData')
|
||||
self.assertContains(response, 'programListingUrl')
|
||||
self.assertContains(response, 'urls')
|
||||
self.assertContains(response, 'program_listing_url')
|
||||
self.assertContains(response, self.data['name'])
|
||||
self._assert_programs_tab_present(response)
|
||||
|
||||
|
||||
23
lms/djangoapps/learner_dashboard/tests/test_utils.py
Normal file
23
lms/djangoapps/learner_dashboard/tests/test_utils.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Unit test module covering utils module
|
||||
"""
|
||||
|
||||
import ddt
|
||||
from django.test import TestCase
|
||||
|
||||
from lms.djangoapps.learner_dashboard import utils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestUtils(TestCase):
|
||||
"""
|
||||
The test case class covering the all the utils functions
|
||||
"""
|
||||
@ddt.data('path1/', '/path1/path2/', '/', '')
|
||||
def test_strip_course_id(self, path):
|
||||
"""
|
||||
Test to make sure the function 'strip_course_id'
|
||||
handles various url input
|
||||
"""
|
||||
actual = utils.strip_course_id(path + unicode(utils.FAKE_COURSE_KEY))
|
||||
self.assertEqual(actual, path)
|
||||
16
lms/djangoapps/learner_dashboard/utils.py
Normal file
16
lms/djangoapps/learner_dashboard/utils.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
The utility methods and functions to help the djangoapp logic
|
||||
"""
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
|
||||
FAKE_COURSE_KEY = CourseKey.from_string('course-v1:fake+course+run')
|
||||
|
||||
|
||||
def strip_course_id(path):
|
||||
"""
|
||||
The utility function to help remove the fake
|
||||
course ID from the url path
|
||||
"""
|
||||
course_id = unicode(FAKE_COURSE_KEY)
|
||||
return path.split(course_id)[0]
|
||||
@@ -11,6 +11,10 @@ from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.credentials.utils import get_programs_credentials
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.programs import utils
|
||||
from lms.djangoapps.learner_dashboard.utils import (
|
||||
FAKE_COURSE_KEY,
|
||||
strip_course_id
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -63,9 +67,16 @@ def program_details(request, program_id):
|
||||
program_data = utils.supplement_program_data(program_data, request.user)
|
||||
show_program_listing = ProgramsApiConfig.current().show_program_listing
|
||||
|
||||
urls = {
|
||||
'program_listing_url': reverse('program_listing_view'),
|
||||
'track_selection_url': strip_course_id(
|
||||
reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY})),
|
||||
'commerce_api_url': reverse('commerce_api:v0:baskets:create')
|
||||
}
|
||||
|
||||
context = {
|
||||
'program_data': program_data,
|
||||
'program_listing_url': reverse('program_listing_view'),
|
||||
'urls': urls,
|
||||
'show_program_listing': show_program_listing,
|
||||
'nav_hidden': True,
|
||||
'disable_courseware_js': True,
|
||||
|
||||
@@ -11,27 +11,49 @@
|
||||
initialize: function(data) {
|
||||
if (data){
|
||||
this.context = data;
|
||||
//we should populate our model by looking at the run_modes
|
||||
if (data.run_modes.length > 0){
|
||||
//We only have 1 run mode for this program
|
||||
this.setActiveRunMode(data.run_modes[0]);
|
||||
}
|
||||
this.setActiveRunMode(this.getRunMode(data.run_modes));
|
||||
}
|
||||
},
|
||||
|
||||
getRunMode: function(runModes){
|
||||
//we should populate our model by looking at the run_modes
|
||||
if (runModes.length > 0){
|
||||
if(runModes.length === 1){
|
||||
return runModes[0];
|
||||
}else{
|
||||
//We need to implement logic here to select the
|
||||
//most relevant run mode for the student to enroll
|
||||
return runModes[0];
|
||||
}
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
setActiveRunMode: function(runMode){
|
||||
this.set({
|
||||
display_name: this.context.display_name,
|
||||
key: this.context.key,
|
||||
marketing_url: runMode.marketing_url || '',
|
||||
start_date: runMode.start_date,
|
||||
end_date: runMode.end_date,
|
||||
is_enrolled: runMode.is_enrolled,
|
||||
is_enrollment_open: runMode.is_enrollment_open,
|
||||
course_url: runMode.course_url || '',
|
||||
course_image_url: runMode.course_image_url || '',
|
||||
mode_slug: runMode.mode_slug
|
||||
});
|
||||
if (runMode){
|
||||
this.set({
|
||||
display_name: this.context.display_name,
|
||||
key: this.context.key,
|
||||
marketing_url: runMode.marketing_url || '',
|
||||
start_date: runMode.start_date,
|
||||
end_date: runMode.end_date,
|
||||
is_enrolled: runMode.is_enrolled,
|
||||
is_enrollment_open: runMode.is_enrollment_open,
|
||||
course_key: runMode.course_key,
|
||||
course_url: runMode.course_url || '',
|
||||
course_image_url: runMode.course_image_url || '',
|
||||
mode_slug: runMode.mode_slug,
|
||||
run_key: runMode.run_key
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateRun: function(runKey){
|
||||
var selectedRun = _.findWhere(this.get('run_modes'), {run_key: runKey});
|
||||
if (selectedRun){
|
||||
this.setActiveRunMode(selectedRun);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Store data to enroll learners into the course
|
||||
*/
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'backbone'
|
||||
],
|
||||
function( Backbone) {
|
||||
return Backbone.Model.extend({
|
||||
defaults: {
|
||||
course_id: '',
|
||||
optIn: false,
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -46,6 +46,7 @@
|
||||
if (this.titleContext){
|
||||
this.$el.before(HtmlUtils.ensureHtml(this.getTitleHtml()).toString());
|
||||
}
|
||||
|
||||
this.$el.html(childList);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/models/course_enroll_model',
|
||||
'js/learner_dashboard/views/course_enroll_view',
|
||||
'text!../../../templates/learner_dashboard/course_card.underscore'
|
||||
],
|
||||
@@ -15,6 +16,7 @@
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
EnrollModel,
|
||||
CourseEnrollView,
|
||||
pageTpl
|
||||
) {
|
||||
@@ -23,8 +25,14 @@
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
|
||||
initialize: function() {
|
||||
initialize: function(options) {
|
||||
this.enrollModel = new EnrollModel();
|
||||
if(options.context && options.context.urls){
|
||||
this.urlModel = new Backbone.Model(options.context.urls);
|
||||
this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url');
|
||||
}
|
||||
this.render();
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@@ -35,9 +43,10 @@
|
||||
|
||||
postRender: function(){
|
||||
this.enrollView = new CourseEnrollView({
|
||||
$el: this.$('.course-actions'),
|
||||
$parentEl: this.$('.course-actions'),
|
||||
model: this.model,
|
||||
context: this.context
|
||||
urlModel: this.urlModel,
|
||||
enrollModel: this.enrollModel
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,23 +20,89 @@
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
|
||||
events: {
|
||||
'click .enroll-button': 'handleEnroll'
|
||||
'click .enroll-button': 'handleEnroll',
|
||||
'change .run-select': 'handleRunSelect',
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
if (options.$el){
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
this.$parentEl = options.$parentEl;
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.urlModel = options.urlModel;
|
||||
this.render();
|
||||
if(this.urlModel){
|
||||
this.trackSelectionUrl = this.urlModel.get('track_selection_url');
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var filledTemplate = this.tpl(this.model.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, filledTemplate);
|
||||
var filledTemplate;
|
||||
if (this.$parentEl &&
|
||||
this.enrollModel &&
|
||||
this.model.get('course_key')){
|
||||
|
||||
filledTemplate = this.tpl(this.model.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, filledTemplate);
|
||||
HtmlUtils.setHtml(this.$parentEl, HtmlUtils.HTML(this.$el));
|
||||
}
|
||||
},
|
||||
|
||||
handleEnroll: function(){
|
||||
//Enrollment click event handled here
|
||||
if (!this.model.get('is_enrolled')){
|
||||
// actually enroll
|
||||
this.enrollModel.save({
|
||||
course_id: this.model.get('course_key')
|
||||
}, {
|
||||
success: _.bind(this.enrollSuccess, this),
|
||||
error: _.bind(this.enrollError, this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleRunSelect: function(event){
|
||||
var runKey;
|
||||
if (event.target){
|
||||
runKey = $(event.target).val();
|
||||
if (runKey){
|
||||
this.model.updateRun(runKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
enrollSuccess: function(){
|
||||
var courseKey = this.model.get('course_key');
|
||||
if (this.trackSelectionUrl) {
|
||||
// Go to track selection page
|
||||
this.redirect( this.trackSelectionUrl + courseKey );
|
||||
}else{
|
||||
this.model.set({
|
||||
is_enrolled: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
enrollError: function(model, response) {
|
||||
|
||||
if (response.status === 403 && response.responseJSON.user_message_url) {
|
||||
/**
|
||||
* Check if we've been blocked from the course
|
||||
* because of country access rules.
|
||||
* If so, redirect to a page explaining to the user
|
||||
* why they were blocked.
|
||||
*/
|
||||
this.redirect( response.responseJSON.user_message_url );
|
||||
} else if (this.trackSelectionUrl){
|
||||
/**
|
||||
* Otherwise, go to the track selection page as usual.
|
||||
* This can occur, for example, when a course does not
|
||||
* have a free enrollment mode, so we can't auto-enroll.
|
||||
*/
|
||||
this.redirect( this.trackSelectionUrl + this.model.get('course_key') );
|
||||
}
|
||||
},
|
||||
|
||||
redirect: function( url ) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
el: '.js-course-list',
|
||||
childView: CourseCardView,
|
||||
collection: this.courseCardCollection,
|
||||
context: this.programModel.toJSON(),
|
||||
context: this.options,
|
||||
titleContext: {
|
||||
el: 'h2',
|
||||
title: 'Course List'
|
||||
|
||||
@@ -10,7 +10,6 @@ define([
|
||||
describe('Course Card View', function () {
|
||||
var view = null,
|
||||
courseCardModel,
|
||||
setupView,
|
||||
context = {
|
||||
course_modes: [],
|
||||
display_name: 'Astrophysics: Exploring Exoplanets',
|
||||
@@ -32,7 +31,7 @@ define([
|
||||
is_enrolled: true,
|
||||
certificate_url: '',
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
setupView = function(isEnrolled){
|
||||
context.run_modes[0].is_enrolled = isEnrolled;
|
||||
@@ -41,6 +40,17 @@ define([
|
||||
view = new CourseCardView({
|
||||
model: courseCardModel
|
||||
});
|
||||
},
|
||||
|
||||
validateCourseInfoDisplay = function(){
|
||||
//DRY validation for course card in enrolled state
|
||||
expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url);
|
||||
expect(view.$('.course-details .course-title-link').text().trim()).toEqual(context.display_name);
|
||||
expect(view.$('.course-details .course-title-link').attr('href')).toEqual(
|
||||
context.run_modes[0].course_url);
|
||||
expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key);
|
||||
expect(view.$('.course-details .course-text .run-period').html())
|
||||
.toEqual(context.run_modes[0].start_date + ' - ' + context.run_modes[0].end_date);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -58,22 +68,18 @@ define([
|
||||
it('should render the course card based on the data enrolled', function() {
|
||||
view.remove();
|
||||
setupView(true);
|
||||
expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url);
|
||||
expect(view.$('.course-details .course-title-link').text().trim()).toEqual(context.display_name);
|
||||
expect(view.$('.course-details .course-title-link').attr('href')).toEqual(
|
||||
context.run_modes[0].course_url);
|
||||
expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key);
|
||||
expect(view.$('.course-details .course-text .run-period').html())
|
||||
.toEqual(context.run_modes[0].start_date + ' - ' + context.run_modes[0].end_date);
|
||||
validateCourseInfoDisplay();
|
||||
});
|
||||
|
||||
it('should render the course card based on the data not enrolled', function() {
|
||||
expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url);
|
||||
expect(view.$('.course-details .course-title-link').text().trim()).toEqual(context.display_name);
|
||||
expect(view.$('.course-details .course-title-link').attr('href')).toEqual(
|
||||
context.run_modes[0].course_url);
|
||||
expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key);
|
||||
expect(view.$('.course-details .course-text .run-period').html()).not.toBeDefined();
|
||||
validateCourseInfoDisplay();
|
||||
});
|
||||
|
||||
it('should update render if the course card is_enrolled updated', function(){
|
||||
courseCardModel.set({
|
||||
is_enrolled: true
|
||||
});
|
||||
validateCourseInfoDisplay();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,70 +2,215 @@ define([
|
||||
'backbone',
|
||||
'jquery',
|
||||
'js/learner_dashboard/models/course_card_model',
|
||||
'js/learner_dashboard/models/course_enroll_model',
|
||||
'js/learner_dashboard/views/course_enroll_view'
|
||||
], function (Backbone, $, CourseCardModel, CourseEnrollView) {
|
||||
], function (Backbone, $, CourseCardModel, CourseEnrollModel, CourseEnrollView) {
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('Course Enroll View', function () {
|
||||
var view = null,
|
||||
courseCardModel,
|
||||
courseEnrollModel,
|
||||
urlModel,
|
||||
setupView,
|
||||
singleRunModeList = [{
|
||||
start_date: 'Apr 25, 2016',
|
||||
end_date: 'Jun 13, 2016',
|
||||
course_key: 'course-v1:course-v1:edX+DemoX+Demo_Course',
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
course_image_url: 'http://test.com/image1',
|
||||
marketing_url: 'http://test.com/image2',
|
||||
mode_slug: 'audit',
|
||||
run_key: '2T2016',
|
||||
is_enrolled: false
|
||||
}],
|
||||
multiRunModeList = [{
|
||||
start_date: 'May 21, 2015',
|
||||
end_date: 'Sep 21, 2015',
|
||||
course_key: 'course-v1:course-v1:edX+DemoX+Demo_Course',
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
course_image_url: 'http://test.com/run_2_image_1',
|
||||
marketing_url: 'http://test.com/run_2_image_2',
|
||||
mode_slug: 'verified',
|
||||
run_key: '1T2015',
|
||||
is_enrolled: false
|
||||
},{
|
||||
start_date: 'Sep 22, 2015',
|
||||
end_date: 'Dec 28, 2015',
|
||||
course_key: 'course-v1:course-v1:edX+DemoX+Demo_Course',
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
course_image_url: 'http://test.com/run_3_image_1',
|
||||
marketing_url: 'http://test.com/run_3_image_2',
|
||||
mode_slug: 'verified',
|
||||
run_key: '2T2015',
|
||||
is_enrolled: false
|
||||
}],
|
||||
context = {
|
||||
display_name: 'Astrophysics: Exploring Exoplanets',
|
||||
key: 'ANU-ASTRO1x',
|
||||
display_name: 'Edx Demo course',
|
||||
key: 'edX+DemoX+Demo_Course',
|
||||
organization: {
|
||||
display_name: 'Australian National University',
|
||||
key: 'ANUx'
|
||||
},
|
||||
run_modes: [{
|
||||
start_date: 'Apr 25, 2016',
|
||||
end_date: 'Jun 13, 2016',
|
||||
course_key: 'course-v1:ANUx+ANU-ASTRO1x+3T2015',
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
course_image_url: 'http://test.com/image1',
|
||||
marketing_url: 'http://test.com/image2',
|
||||
mode_slug: 'verified',
|
||||
run_key: '2T2016',
|
||||
is_enrolled: false,
|
||||
certificate_url: '',
|
||||
}]
|
||||
display_name: 'edx.org',
|
||||
key: 'edX'
|
||||
}
|
||||
},
|
||||
urls = {
|
||||
dashboard_url: '/dashboard',
|
||||
id_verification_url: '/verify_student/start_flow/',
|
||||
track_selection_url: '/select_track/course/'
|
||||
};
|
||||
|
||||
setupView = function(isEnrolled){
|
||||
context.run_modes[0].is_enrolled = isEnrolled;
|
||||
setupView = function(runModes, urls){
|
||||
context.run_modes = runModes;
|
||||
setFixtures('<div class="course-actions"></div>');
|
||||
courseCardModel = new CourseCardModel(context);
|
||||
courseEnrollModel = new CourseEnrollModel({}, {
|
||||
courseId: courseCardModel.get('course_key')
|
||||
});
|
||||
if(urls){
|
||||
urlModel = new Backbone.Model(urls);
|
||||
}
|
||||
view = new CourseEnrollView({
|
||||
$el: $('.course-actions'),
|
||||
model: courseCardModel
|
||||
$parentEl: $('.course-actions'),
|
||||
model: courseCardModel,
|
||||
enrollModel: courseEnrollModel,
|
||||
urlModel: urlModel
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setupView(false);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
view.remove();
|
||||
urlModel = null;
|
||||
courseCardModel = null;
|
||||
courseEnrollModel = null;
|
||||
});
|
||||
|
||||
it('should exist', function() {
|
||||
setupView(singleRunModeList);
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render the course enroll view based on not enrolled data', function() {
|
||||
setupView(singleRunModeList);
|
||||
expect(view.$('.enrollment-info').html().trim()).toEqual('not enrolled');
|
||||
expect(view.$('.enroll-button').text().trim()).toEqual('Enroll Now');
|
||||
expect(view.$('.run-select').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should render the course enroll view based on enrolled data', function(){
|
||||
view.remove();
|
||||
setupView(true);
|
||||
singleRunModeList[0].is_enrolled = true;
|
||||
setupView(singleRunModeList);
|
||||
expect(view.$('.enrollment-info').html().trim()).toEqual('enrolled');
|
||||
expect(view.$('.view-course-link').attr('href')).toEqual(
|
||||
context.run_modes[0].course_url);
|
||||
expect(view.$('.view-course-link').text().trim()).toEqual('View Course');
|
||||
expect(view.$('.run-select').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not render anything if run modes is empty', function(){
|
||||
setupView([]);
|
||||
expect(view.$('.enrollment-info').length).toBe(0);
|
||||
expect(view.$('.run-select').length).toBe(0);
|
||||
expect(view.$('.enroll-button').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should render run selection drop down if mulitple run available', function(){
|
||||
setupView(multiRunModeList);
|
||||
expect(view.$('.run-select').length).toBe(1);
|
||||
expect(view.$('.run-select').val()).toEqual(multiRunModeList[0].run_key);
|
||||
});
|
||||
|
||||
it('should switch run context if dropdown selection changed', function(){
|
||||
setupView(multiRunModeList);
|
||||
spyOn(courseCardModel, 'updateRun').and.callThrough();
|
||||
expect(view.$('.run-select').val()).toEqual(multiRunModeList[0].run_key);
|
||||
view.$('.run-select').val(multiRunModeList[1].run_key);
|
||||
view.$('.run-select').trigger("change");
|
||||
expect(view.$('.run-select').val()).toEqual(multiRunModeList[1].run_key);
|
||||
expect(courseCardModel.updateRun)
|
||||
.toHaveBeenCalledWith(multiRunModeList[1].run_key);
|
||||
expect(courseCardModel.get('run_key')).toEqual(multiRunModeList[1].run_key);
|
||||
});
|
||||
|
||||
it('should enroll learner when enroll button clicked', function(){
|
||||
singleRunModeList[0].is_enrolled = false;
|
||||
setupView(singleRunModeList);
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
spyOn(courseEnrollModel, 'save');
|
||||
view.$('.enroll-button').click();
|
||||
expect(courseEnrollModel.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should enroll learner into the updated run with button click', function(){
|
||||
setupView(multiRunModeList);
|
||||
spyOn(courseEnrollModel, 'save');
|
||||
view.$('.run-select').val(multiRunModeList[1].run_key);
|
||||
view.$('.run-select').trigger("change");
|
||||
view.$('.enroll-button').click();
|
||||
expect(courseEnrollModel.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect to trackSelectionUrl when enrollment success for audit track', function(){
|
||||
singleRunModeList[0].is_enrolled = false;
|
||||
singleRunModeList[0].mode_slug = 'audit';
|
||||
setupView(singleRunModeList, urls);
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
spyOn(view, 'redirect');
|
||||
view.enrollSuccess();
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
view.trackSelectionUrl + courseCardModel.get('course_key'));
|
||||
});
|
||||
|
||||
|
||||
it('should redirect when enrollment success for no track', function(){
|
||||
singleRunModeList[0].is_enrolled = false;
|
||||
singleRunModeList[0].mode_slug = null;
|
||||
setupView(singleRunModeList, urls);
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
spyOn(view, 'redirect');
|
||||
view.enrollSuccess();
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
view.trackSelectionUrl + courseCardModel.get('course_key'));
|
||||
});
|
||||
|
||||
it('should not redirect when urls not provided', function(){
|
||||
singleRunModeList[0].is_enrolled = false;
|
||||
singleRunModeList[0].mode_slug = 'verified';
|
||||
setupView(singleRunModeList);
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.verificationUrl).not.toBeDefined();
|
||||
expect(view.dashboardUrl).not.toBeDefined();
|
||||
expect(view.trackSelectionUrl).not.toBeDefined();
|
||||
spyOn(view, 'redirect');
|
||||
view.enrollSuccess();
|
||||
expect(view.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect to track selection on error', function(){
|
||||
setupView(singleRunModeList, urls);
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
spyOn(view, 'redirect');
|
||||
view.enrollError(courseEnrollModel, {status: 500});
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
view.trackSelectionUrl + courseCardModel.get('course_key'));
|
||||
});
|
||||
|
||||
it('should redirect to login on 403 error', function(){
|
||||
var response = {
|
||||
status: 403,
|
||||
responseJSON:{
|
||||
user_message_url: 'test_url/haha'
|
||||
}};
|
||||
setupView(singleRunModeList, urls);
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
spyOn(view, 'redirect');
|
||||
view.enrollError(courseEnrollModel, response);
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
response.responseJSON.user_message_url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ define([
|
||||
|
||||
describe('Program Details Header View', function () {
|
||||
var view = null,
|
||||
programModel,
|
||||
context = {
|
||||
programListingUrl: '/dashboard/programs',
|
||||
urls: {
|
||||
program_listing_url: '/dashboard/programs'
|
||||
},
|
||||
programData: {
|
||||
uuid: '12-ab',
|
||||
name: 'Astrophysics',
|
||||
@@ -58,7 +59,7 @@ define([
|
||||
expect(view.$('.org-logo').attr('alt')).toEqual(
|
||||
context.programData.organizations[0].display_name + '\'s logo'
|
||||
);
|
||||
expect(programListUrl).toEqual(context.programListingUrl);
|
||||
expect(programListUrl).toEqual(context.urls.program_listing_url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.course-card{
|
||||
@include span(10);
|
||||
margin-left: $baseline*2 + px;
|
||||
margin-bottom: $baseline + px;
|
||||
margin-left: $baseline*2;
|
||||
margin-bottom: $baseline;
|
||||
.course-image-link{
|
||||
@include float(left);
|
||||
.header-img{
|
||||
@@ -12,25 +12,34 @@
|
||||
@include float(right);
|
||||
width: 100%;
|
||||
@include susy-media($bp-screen-sm) { width: calc(100% - 191px); }
|
||||
padding-left: $baseline*1.5 + px;
|
||||
padding-left: $baseline*1.5;
|
||||
.course-title{
|
||||
font-size: font-size(x-large);
|
||||
font-weight: font-weight(normal);
|
||||
margin-bottom: $baseline/4 + px;
|
||||
margin-bottom: $baseline/4;
|
||||
}
|
||||
.course-text{
|
||||
color: palette(grayscale, dark);
|
||||
.run-period{
|
||||
color: palette(grayscale, black);
|
||||
}
|
||||
}
|
||||
}
|
||||
.course-actions{
|
||||
.enrollment-info{
|
||||
width: $baseline*10 + px;
|
||||
width: $baseline*10;
|
||||
text-align: center;
|
||||
margin-bottom: $baseline/2 + px;
|
||||
margin-bottom: $baseline/2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.run-select-container{
|
||||
margin-bottom: $baseline;
|
||||
.run-select{
|
||||
width: $baseline*10;
|
||||
}
|
||||
}
|
||||
.enroll-button{
|
||||
width: $baseline*10 + px;
|
||||
width: $baseline*10;
|
||||
text-align: center;
|
||||
background-color: palette(success, dark);
|
||||
border-color: palette(success, dark);
|
||||
@@ -42,7 +51,7 @@
|
||||
}
|
||||
|
||||
.view-course-link{
|
||||
width: $baseline*10 + px;
|
||||
width: $baseline*10;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<img
|
||||
class="header-img"
|
||||
src="<%- course_image_url %>"
|
||||
alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true)%>"/>
|
||||
alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true) %>"/>
|
||||
</a>
|
||||
<div class="course-details">
|
||||
<h3 class="course-title">
|
||||
@@ -13,9 +13,8 @@
|
||||
</a>
|
||||
</h3>
|
||||
<div class="course-text">
|
||||
<% if (is_enrolled){ %>
|
||||
<span class="run-period"><%- start_date %> - <%- end_date %></span>
|
||||
<% } %>
|
||||
<span class="run-period"><%- start_date %> - <%- end_date %></span>
|
||||
-
|
||||
<span class="course-key"><%- key %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,27 @@
|
||||
</a>
|
||||
<% }else{ %>
|
||||
<div class="enrollment-info"><%- gettext('not enrolled') %></div>
|
||||
<% if (run_modes.length > 1){ %>
|
||||
<div class="run-select-container">
|
||||
<label class="sr-only" for="select-<%- course_key %>-run">Select Course Run</label>
|
||||
<select id="select-<%- course_key %>-run" class="run-select" autocomplete="off">
|
||||
<% _.each (run_modes, function(runMode){ %>
|
||||
<option
|
||||
value="<%- runMode.run_key %>"
|
||||
<% if(run_key === runMode.run_key){ %>
|
||||
selected="selected"
|
||||
<% }%>
|
||||
>
|
||||
<%= interpolate(
|
||||
gettext('Starts %(start)s'),
|
||||
{ start: runMode.start_date },
|
||||
true)
|
||||
%>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<% } %>
|
||||
<button type="button" class="btn-brand btn enroll-button">
|
||||
<%- gettext('Enroll Now') %>
|
||||
</button>
|
||||
|
||||
@@ -15,7 +15,7 @@ from openedx.core.djangolib.js_utils import (
|
||||
<%static:require_module module_name="js/learner_dashboard/program_details_factory" class_name="ProgramDetailsFactory">
|
||||
ProgramDetailsFactory({
|
||||
programData: ${program_data | n, dump_js_escaped_json},
|
||||
programListingUrl: '${program_listing_url | n, js_escaped_string}',
|
||||
urls: ${urls | n, dump_js_escaped_json},
|
||||
});
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<span class="crumb-separator fa fa-chevron-right" aria-hidden="true"></span>
|
||||
</li>
|
||||
<li class="crumb">
|
||||
<a href="<%- programListingUrl %>" class="crumb-link"><%- gettext('Programs') %></a>
|
||||
<a href="<%- urls.program_listing_url %>" class="crumb-link"><%- gettext('Programs') %></a>
|
||||
<span class="crumb-separator fa fa-chevron-right" aria-hidden="true"></span>
|
||||
</li>
|
||||
<li class="crumb active">
|
||||
|
||||
Reference in New Issue
Block a user