From 0e06e90599bb3d0bc7bc25ed8e2fae593d1feb4b Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Tue, 7 Feb 2017 11:45:31 -0500 Subject: [PATCH] Finish transition to catalog for program data Updates Mako and Underscore templates as well as Backbone models and views so they work with catalog programs. Removes all remaining response munging from the back end. ECOM-4422 --- common/djangoapps/student/views.py | 8 +- .../learner_dashboard/tests/test_programs.py | 11 +- lms/djangoapps/learner_dashboard/views.py | 10 +- .../models/course_card_model.js | 168 +++++++----- .../learner_dashboard/models/program_model.js | 14 +- .../views/course_enroll_view.js | 34 ++- .../views/program_card_view.js | 23 +- .../views/program_details_view.js | 2 +- .../collection_list_view_spec.js | 138 ++++++---- .../course_card_view_spec.js | 149 +++++----- .../course_enroll_view_spec.js | 257 +++++++++++------- .../program_card_view_spec.js | 96 ++++--- .../program_details_header_spec.js | 61 +++-- lms/templates/dashboard.html | 2 +- .../dashboard/_dashboard_course_listing.html | 10 +- .../learner_dashboard/course_card.underscore | 8 +- .../course_enroll.underscore | 18 +- .../learner_dashboard/program_card.underscore | 32 +-- .../program_header_view.underscore | 18 +- .../djangoapps/catalog/tests/test_utils.py | 58 ---- openedx/core/djangoapps/catalog/utils.py | 58 ---- .../djangoapps/programs/tests/factories.py | 9 +- .../djangoapps/programs/tests/test_utils.py | 190 +++++-------- openedx/core/djangoapps/programs/utils.py | 110 +++----- themes/edx.org/lms/templates/dashboard.html | 2 +- 25 files changed, 710 insertions(+), 776 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 9d8d8efc63..ca60f6fe7e 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -122,15 +122,14 @@ import newrelic_custom_metrics # Note that this lives in LMS, so this dependency should be refactored. from notification_prefs.views import enable_notifications +from openedx.core.djangoapps.catalog.utils import get_programs_with_type_logo from openedx.core.djangoapps.credit.email_utils import get_credit_provider_display_names, make_providers_strings from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY -from openedx.core.djangoapps.catalog.utils import munge_catalog_program from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.utils import ProgramProgressMeter from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.theming import helpers as theming_helpers from openedx.core.djangoapps.user_api.preferences import api as preferences_api -from openedx.core.djangoapps.catalog.utils import get_programs_with_type_logo log = logging.getLogger("edx.student") @@ -670,9 +669,6 @@ def dashboard(request): meter = ProgramProgressMeter(user, enrollments=course_enrollments) inverted_programs = meter.invert_programs() - for program_list in inverted_programs.itervalues(): - program_list[:] = [munge_catalog_program(program) for program in program_list] - # Construct a dictionary of course mode information # used to render the course list. We re-use the course modes dict # we loaded earlier to avoid hitting the database. @@ -795,7 +791,7 @@ def dashboard(request): 'order_history_list': order_history_list, 'courses_requirements_not_met': courses_requirements_not_met, 'nav_hidden': True, - 'programs_by_run': inverted_programs, + 'inverted_programs': inverted_programs, 'show_program_listing': ProgramsApiConfig.current().show_program_listing, 'disable_courseware_js': True, 'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard, diff --git a/lms/djangoapps/learner_dashboard/tests/test_programs.py b/lms/djangoapps/learner_dashboard/tests/test_programs.py index da4547fc48..a390864a82 100644 --- a/lms/djangoapps/learner_dashboard/tests/test_programs.py +++ b/lms/djangoapps/learner_dashboard/tests/test_programs.py @@ -16,7 +16,6 @@ import mock from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory, CourseFactory, CourseRunFactory from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin -from openedx.core.djangoapps.catalog.utils import munge_catalog_program from openedx.core.djangoapps.credentials.tests.factories import UserCredential, ProgramCredential from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin @@ -64,13 +63,7 @@ class TestProgramListing(ProgramsApiConfigMixin, CredentialsApiConfigMixin, Shar """ Helper function used to sort dictionaries representing programs. """ - try: - return program['title'] - except: # pylint: disable=bare-except - # This is here temporarily because programs are still being munged - # to look like they came from the programs service before going out - # to the front end. - return program['name'] + return program['title'] def credential_sort_key(self, credential): """ @@ -157,7 +150,7 @@ class TestProgramListing(ProgramsApiConfigMixin, CredentialsApiConfigMixin, Shar actual = sorted(actual, key=self.program_sort_key) for index, actual_program in enumerate(actual): - expected_program = munge_catalog_program(self.data[index]) + expected_program = self.data[index] self.assert_dict_contains_subset(actual_program, expected_program) def test_program_discovery(self, mock_get_programs): diff --git a/lms/djangoapps/learner_dashboard/views.py b/lms/djangoapps/learner_dashboard/views.py index 89bcf60827..3336a91313 100644 --- a/lms/djangoapps/learner_dashboard/views.py +++ b/lms/djangoapps/learner_dashboard/views.py @@ -6,12 +6,11 @@ from django.views.decorators.http import require_GET from edxmako.shortcuts import render_to_response from lms.djangoapps.learner_dashboard.utils import strip_course_id, FAKE_COURSE_KEY -from openedx.core.djangoapps.catalog.utils import get_programs, munge_catalog_program +from openedx.core.djangoapps.catalog.utils import get_programs from openedx.core.djangoapps.credentials.utils import get_programs_credentials from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.utils import ( get_program_marketing_url, - munge_progress_map, ProgramProgressMeter, ProgramDataExtender, ) @@ -27,16 +26,14 @@ def program_listing(request): raise Http404 meter = ProgramProgressMeter(request.user) - engaged_programs = [munge_catalog_program(program) for program in meter.engaged_programs] - progress = [munge_progress_map(progress_map) for progress_map in meter.progress] context = { 'credentials': get_programs_credentials(request.user), 'disable_courseware_js': True, 'marketing_url': get_program_marketing_url(programs_config), 'nav_hidden': True, - 'programs': engaged_programs, - 'progress': progress, + 'programs': meter.engaged_programs, + 'progress': meter.progress, 'show_program_listing': programs_config.show_program_listing, 'uses_pattern_library': True, } @@ -56,7 +53,6 @@ def program_details(request, program_uuid): if not program_data: raise Http404 - program_data = munge_catalog_program(program_data) program_data = ProgramDataExtender(program_data, request.user).extend() urls = { diff --git a/lms/static/js/learner_dashboard/models/course_card_model.js b/lms/static/js/learner_dashboard/models/course_card_model.js index 2652d600c4..cc04551f5b 100644 --- a/lms/static/js/learner_dashboard/models/course_card_model.js +++ b/lms/static/js/learner_dashboard/models/course_card_model.js @@ -5,60 +5,93 @@ 'use strict'; define([ 'backbone', + 'underscore', + 'jquery', 'edx-ui-toolkit/js/utils/date-utils' ], - function(Backbone, DateUtils) { + function(Backbone, _, $, DateUtils) { return Backbone.Model.extend({ initialize: function(data) { if (data) { this.context = data; - this.setActiveRunMode(this.getRunMode(data.run_modes), data.user_preferences); + this.setActiveCourseRun(this.getCourseRun(data.course_runs), data.user_preferences); } }, - getUnselectedRunMode: function(runModes) { - if (runModes && runModes.length > 0) { - return { - course_image_url: runModes[0].course_image_url, - marketing_url: runModes[0].marketing_url, - is_enrollment_open: runModes[0].is_enrollment_open - }; - } + getCourseRun: function(courseRuns) { + var enrolledCourseRun = _.findWhere(courseRuns, {is_enrolled: true}), + openEnrollmentCourseRuns = this.getEnrollableCourseRuns(), + desiredCourseRun; - return {}; - }, - - getRunMode: function(runModes) { - var enrolled_mode = _.findWhere(runModes, {is_enrolled: true}), - openEnrollmentRunModes = this.getEnrollableRunModes(), - desiredRunMode; - // We populate our model by looking at the run modes. - if (enrolled_mode) { - // If the learner is already enrolled in a run mode, return that one. - desiredRunMode = enrolled_mode; - } else if (openEnrollmentRunModes.length > 0) { - if (openEnrollmentRunModes.length === 1) { - desiredRunMode = openEnrollmentRunModes[0]; + // We populate our model by looking at the course runs. + if (enrolledCourseRun) { + // If the learner is already enrolled in a course run, return that one. + desiredCourseRun = enrolledCourseRun; + } else if (openEnrollmentCourseRuns.length > 0) { + if (openEnrollmentCourseRuns.length === 1) { + desiredCourseRun = openEnrollmentCourseRuns[0]; } else { - desiredRunMode = this.getUnselectedRunMode(openEnrollmentRunModes); + desiredCourseRun = this.getUnselectedCourseRun(openEnrollmentCourseRuns); } } else { - desiredRunMode = this.getUnselectedRunMode(runModes); + desiredCourseRun = this.getUnselectedCourseRun(courseRuns); } - return desiredRunMode; + return desiredCourseRun; }, - getEnrollableRunModes: function() { - return _.where(this.context.run_modes, { + getUnselectedCourseRun: function(courseRuns) { + var unselectedRun = {}, + courseRun, + courseImageUrl; + + if (courseRuns && courseRuns.length > 0) { + courseRun = courseRuns[0]; + + if (courseRun.hasOwnProperty('image')) { + courseImageUrl = courseRun.image.src; + } else { + // The course_image_url property is attached by setActiveCourseRun. + // If that hasn't been called, it won't be present yet. + courseImageUrl = courseRun.course_image_url; + } + + $.extend(unselectedRun, { + course_image_url: courseImageUrl, + marketing_url: courseRun.marketing_url, + is_enrollment_open: courseRun.is_enrollment_open + }); + } + + return unselectedRun; + }, + + getEnrollableCourseRuns: function() { + var rawCourseRuns, + enrollableCourseRuns; + + rawCourseRuns = _.where(this.context.course_runs, { is_enrollment_open: true, is_enrolled: false, is_course_ended: false }); + + // Deep copy to avoid mutating this.context. + enrollableCourseRuns = $.extend(true, [], rawCourseRuns); + + // These are raw course runs from the server. The start + // dates are ISO-8601 formatted strings that need to be + // prepped for display. + _.each(enrollableCourseRuns, (function(courseRun) { + // eslint-disable-next-line no-param-reassign + courseRun.start_date = this.formatDate(courseRun.start); + }).bind(this)); + + return enrollableCourseRuns; }, - getUpcomingRunModes: function() { - return _.where(this.context.run_modes, { + getUpcomingCourseRuns: function() { + return _.where(this.context.course_runs, { is_enrollment_open: false, is_enrolled: false, is_course_ended: false @@ -82,51 +115,54 @@ return DateUtils.localize(context); }, - setActiveRunMode: function(runMode, userPreferences) { - var startDateString; - if (runMode) { - if (runMode.advertised_start !== undefined && runMode.advertised_start !== 'None') { - startDateString = runMode.advertised_start; + setActiveCourseRun: function(courseRun, userPreferences) { + var startDateString, + courseImageUrl; + + if (courseRun) { + if (courseRun.advertised_start !== undefined && courseRun.advertised_start !== 'None') { + startDateString = courseRun.advertised_start; } else { - startDateString = this.formatDate( - runMode.start_date, - userPreferences - ); + startDateString = this.formatDate(courseRun.start, userPreferences); } + + if (courseRun.hasOwnProperty('image')) { + courseImageUrl = courseRun.image.src; + } else { + courseImageUrl = courseRun.course_image_url; + } + this.set({ - certificate_url: runMode.certificate_url, - course_image_url: runMode.course_image_url || '', - course_key: runMode.course_key, - course_url: runMode.course_url || '', - display_name: this.context.display_name, - end_date: this.formatDate( - runMode.end_date, - userPreferences - ), - enrollable_run_modes: this.getEnrollableRunModes(), - is_course_ended: runMode.is_course_ended, - is_enrolled: runMode.is_enrolled, - is_enrollment_open: runMode.is_enrollment_open, - key: this.context.key, - marketing_url: runMode.marketing_url, - mode_slug: runMode.mode_slug, - run_key: runMode.run_key, + certificate_url: courseRun.certificate_url, + course_image_url: courseImageUrl || '', + course_run_key: courseRun.key, + course_url: courseRun.course_url || '', + title: this.context.title, + end_date: this.formatDate(courseRun.end, userPreferences), + enrollable_course_runs: this.getEnrollableCourseRuns(), + is_course_ended: courseRun.is_course_ended, + is_enrolled: courseRun.is_enrolled, + is_enrollment_open: courseRun.is_enrollment_open, + course_key: this.context.key, + marketing_url: courseRun.marketing_url, + mode_slug: courseRun.type, start_date: startDateString, - upcoming_run_modes: this.getUpcomingRunModes(), - upgrade_url: runMode.upgrade_url + upcoming_course_runs: this.getUpcomingCourseRuns(), + upgrade_url: courseRun.upgrade_url }); } }, + setUnselected: function() { - // Called to reset the model back to the unselected state. - var unselectedMode = this.getUnselectedRunMode(this.get('enrollable_run_modes')); - this.setActiveRunMode(unselectedMode); + // Called to reset the model back to the unselected state. + var unselectedCourseRun = this.getUnselectedCourseRun(this.get('enrollable_course_runs')); + this.setActiveCourseRun(unselectedCourseRun); }, - updateRun: function(runKey) { - var selectedRun = _.findWhere(this.get('run_modes'), {run_key: runKey}); - if (selectedRun) { - this.setActiveRunMode(selectedRun); + updateCourseRun: function(courseRunKey) { + var selectedCourseRun = _.findWhere(this.get('course_runs'), {key: courseRunKey}); + if (selectedCourseRun) { + this.setActiveCourseRun(selectedCourseRun); } } }); diff --git a/lms/static/js/learner_dashboard/models/program_model.js b/lms/static/js/learner_dashboard/models/program_model.js index d5921daa6d..66f937edea 100644 --- a/lms/static/js/learner_dashboard/models/program_model.js +++ b/lms/static/js/learner_dashboard/models/program_model.js @@ -11,17 +11,17 @@ initialize: function(data) { if (data) { this.set({ - name: data.name, - category: data.category, + title: data.title, + type: data.type, subtitle: data.subtitle, - organizations: data.organizations, + authoring_organizations: data.authoring_organizations, detailUrl: data.detail_url, - smallBannerUrl: data.banner_image_urls.w348h116, - mediumBannerUrl: data.banner_image_urls.w435h145, - largeBannerUrl: data.banner_image_urls.w726h242, + xsmallBannerUrl: data.banner_image['x-small'].url, + smallBannerUrl: data.banner_image.small.url, + mediumBannerUrl: data.banner_image.medium.url, breakpoints: { max: { - tiny: '320px', + xsmall: '320px', small: '540px', medium: '768px', large: '979px' diff --git a/lms/static/js/learner_dashboard/views/course_enroll_view.js b/lms/static/js/learner_dashboard/views/course_enroll_view.js index f8d45bbee2..c66211fb37 100644 --- a/lms/static/js/learner_dashboard/views/course_enroll_view.js +++ b/lms/static/js/learner_dashboard/views/course_enroll_view.js @@ -21,7 +21,7 @@ events: { 'click .enroll-button': 'handleEnroll', - 'change .run-select': 'handleRunSelect' + 'change .run-select': 'handleCourseRunSelect' }, initialize: function(options) { @@ -45,12 +45,12 @@ handleEnroll: function() { // Enrollment click event handled here - if (!this.model.get('course_key')) { + if (!this.model.get('course_run_key')) { this.$('.select-error').css('visibility', 'visible'); } else if (!this.model.get('is_enrolled')) { - // actually enroll + // Create the enrollment. this.enrollModel.save({ - course_id: this.model.get('course_key') + course_id: this.model.get('course_run_key') }, { success: _.bind(this.enrollSuccess, this), error: _.bind(this.enrollError, this) @@ -58,24 +58,22 @@ } }, - handleRunSelect: function(event) { - var runKey; - if (event.target) { - runKey = $(event.target).val(); - if (runKey) { - this.model.updateRun(runKey); - } else { - // Set back the unselected states - this.model.setUnselected(); - } + handleCourseRunSelect: function(event) { + var courseRunKey = $(event.target).val(); + + if (courseRunKey) { + this.model.updateCourseRun(courseRunKey); + } else { + // Set back the unselected states + this.model.setUnselected(); } }, enrollSuccess: function() { - var courseKey = this.model.get('course_key'); + var courseRunKey = this.model.get('course_run_key'); if (this.trackSelectionUrl) { - // Go to track selection page - this.redirect(this.trackSelectionUrl + courseKey); + // Go to track selection page + this.redirect(this.trackSelectionUrl + courseRunKey); } else { this.model.set({ is_enrolled: true @@ -98,7 +96,7 @@ * 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')); + this.redirect(this.trackSelectionUrl + this.model.get('course_run_key')); } }, diff --git a/lms/static/js/learner_dashboard/views/program_card_view.js b/lms/static/js/learner_dashboard/views/program_card_view.js index bbbf9209e0..05cb63d389 100644 --- a/lms/static/js/learner_dashboard/views/program_card_view.js +++ b/lms/static/js/learner_dashboard/views/program_card_view.js @@ -22,7 +22,7 @@ attributes: function() { return { - 'aria-labelledby': 'program-' + this.model.get('id'), + 'aria-labelledby': 'program-' + this.model.get('uuid'), 'role': 'group' }; }, @@ -33,14 +33,14 @@ this.progressCollection = data.context.progressCollection; if (this.progressCollection) { this.progressModel = this.progressCollection.findWhere({ - id: this.model.get('id') + uuid: this.model.get('uuid') }); } this.render(); }, render: function() { - var orgList = _.map(this.model.get('organizations'), function(org) { + var orgList = _.map(this.model.get('authoring_organizations'), function(org) { return gettext(org.key); }), data = $.extend( @@ -56,7 +56,7 @@ postRender: function() { // Add describedby to parent only if progess is present if (this.progressModel) { - this.$el.attr('aria-describedby', 'status-' + this.model.get('id')); + this.$el.attr('aria-describedby', 'status-' + this.model.get('uuid')); } if (navigator.userAgent.indexOf('MSIE') !== -1 || @@ -73,19 +73,14 @@ var progress = this.progressModel ? this.progressModel.toJSON() : false; if (progress) { - progress.total = { - completed: progress.completed.length, - in_progress: progress.in_progress.length, - not_started: progress.not_started.length - }; - progress.total.courses = progress.total.completed + - progress.total.in_progress + - progress.total.not_started; + progress.total = progress.completed + + progress.in_progress + + progress.not_started; progress.percentage = { - completed: this.getWidth(progress.total.completed, progress.total.courses), - in_progress: this.getWidth(progress.total.in_progress, progress.total.courses) + completed: this.getWidth(progress.completed, progress.total), + in_progress: this.getWidth(progress.in_progress, progress.total) }; } diff --git a/lms/static/js/learner_dashboard/views/program_details_view.js b/lms/static/js/learner_dashboard/views/program_details_view.js index 6536375c7e..afb267669a 100644 --- a/lms/static/js/learner_dashboard/views/program_details_view.js +++ b/lms/static/js/learner_dashboard/views/program_details_view.js @@ -33,7 +33,7 @@ this.options = options; this.programModel = new Backbone.Model(this.options.programData); this.courseCardCollection = new CourseCardCollection( - this.programModel.get('course_codes'), + this.programModel.get('courses'), this.options.userPreferences ); this.render(); diff --git a/lms/static/js/spec/learner_dashboard/collection_list_view_spec.js b/lms/static/js/spec/learner_dashboard/collection_list_view_spec.js index ee3e0c0cc3..c3f7a9b0c0 100644 --- a/lms/static/js/spec/learner_dashboard/collection_list_view_spec.js +++ b/lms/static/js/spec/learner_dashboard/collection_list_view_spec.js @@ -5,10 +5,9 @@ define([ 'js/learner_dashboard/collections/program_collection', 'js/learner_dashboard/views/collection_list_view', 'js/learner_dashboard/collections/program_progress_collection' -], function(Backbone, $, ProgramCardView, ProgramCollection, CollectionListView, - ProgressCollection) { +], function(Backbone, $, ProgramCardView, ProgramCollection, CollectionListView, ProgressCollection) { 'use strict'; - /* jslint maxlen: 500 */ + /* jslint maxlen: 500 */ describe('Collection List View', function() { var view = null, @@ -17,62 +16,90 @@ define([ context = { programsData: [ { - category: 'xseries', - status: 'active', - subtitle: 'program 1', - name: 'test program 1', - organizations: [ - { - display_name: 'edX', - key: 'edx' + uuid: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', + title: 'Food Security and Sustainability', + subtitle: 'Learn how to feed all people in the world in a sustainable way.', + type: 'XSeries', + detail_url: 'https://www.edx.org/foo/bar', + banner_image: { + medium: { + height: 242, + width: 726, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.medium.jpg' + }, + 'x-small': { + height: 116, + width: 348, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.x-small.jpg' + }, + small: { + height: 145, + width: 435, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.small.jpg' + }, + large: { + height: 480, + width: 1440, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.large.jpg' } - ], - created: '2016-03-03T19:18:50.061136Z', - modified: '2016-03-25T13:45:21.220732Z', - marketing_slug: 'p_2?param=haha&test=b', - id: 146, - marketing_url: 'http://www.edx.org/xseries/p_2?param=haha&test=b', - banner_image_urls: { - w348h116: 'http://www.edx.org/images/org1/test1', - w435h145: 'http://www.edx.org/images/org1/test2', - w726h242: 'http://www.edx.org/images/org1/test3' - } + }, + authoring_organizations: [ + { + uuid: '0c6e5fa2-96e8-40b2-9ebe-c8b0df2a3b22', + key: 'WageningenX', + name: 'Wageningen University & Research' + } + ] }, { - category: 'xseries', - status: 'active', - subtitle: 'fda', - name: 'fda', - organizations: [ - { - display_name: 'edX', - key: 'edx' + uuid: '91d144d2-1bb1-4afe-90df-d5cff63fa6e2', + title: 'edX Course Creator', + subtitle: 'Become an expert in creating courses for the edX platform.', + type: 'XSeries', + detail_url: 'https://www.edx.org/foo/bar', + banner_image: { + medium: { + height: 242, + width: 726, + url: 'https://example.com/91d144d2-1bb1-4afe-90df-d5cff63fa6e2.medium.jpg' + }, + 'x-small': { + height: 116, + width: 348, + url: 'https://example.com/91d144d2-1bb1-4afe-90df-d5cff63fa6e2.x-small.jpg' + }, + small: { + height: 145, + width: 435, + url: 'https://example.com/91d144d2-1bb1-4afe-90df-d5cff63fa6e2.small.jpg' + }, + large: { + height: 480, + width: 1440, + url: 'https://example.com/91d144d2-1bb1-4afe-90df-d5cff63fa6e2.large.jpg' } - ], - created: '2016-03-09T14:30:41.484848Z', - modified: '2016-03-09T14:30:52.840898Z', - marketing_slug: 'gdaf', - id: 147, - marketing_url: 'http://www.edx.org/xseries/gdaf', - banner_image_urls: { - w348h116: 'http://www.edx.org/images/org2/test1', - w435h145: 'http://www.edx.org/images/org2/test2', - w726h242: 'http://www.edx.org/images/org2/test3' - } + }, + authoring_organizations: [ + { + uuid: '4f8cb2c9-589b-4d1e-88c1-b01a02db3a9c', + key: 'edX', + name: 'edX' + } + ] } ], userProgress: [ { - id: 146, - completed: ['courses', 'the', 'user', 'completed'], - in_progress: ['in', 'progress'], - not_started: ['courses', 'not', 'yet', 'started'] + uuid: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', + completed: 4, + in_progress: 2, + not_started: 4 }, { - id: 147, - completed: ['Course 1'], - in_progress: [], - not_started: ['Course 2', 'Course 3', 'Course 4'] + uuid: '91d144d2-1bb1-4afe-90df-d5cff63fa6e2', + completed: 1, + in_progress: 0, + not_started: 3 } ] }; @@ -105,7 +132,8 @@ define([ var $cards = view.$el.find('.program-card'); expect($cards.length).toBe(2); $cards.each(function(index, el) { - expect($(el).find('.title').html().trim()).toEqual(context.programsData[index].name); + // eslint-disable-next-line newline-per-chained-call + expect($(el).find('.title').html().trim()).toEqual(context.programsData[index].title); }); }); @@ -116,13 +144,14 @@ define([ view = new CollectionListView({ el: '.program-cards-container', childView: ProgramCardView, - context: {'xseriesUrl': '/programs'}, + context: {}, collection: programCollection }); view.render(); $cards = view.$el.find('.program-card'); expect($cards.length).toBe(0); }); + it('should have no title when title not provided', function() { var $title; setFixtures('
'); @@ -132,15 +161,18 @@ define([ $title = view.$el.parent().find('.collection-title'); expect($title.html()).not.toBeDefined(); }); + it('should display screen reader header when provided', function() { - var $title, titleContext = {el: 'h2', title: 'list start'}; + var titleContext = {el: 'h2', title: 'list start'}, + $title; + view.remove(); setFixtures('
'); programCollection = new ProgramCollection(context.programsData); view = new CollectionListView({ el: '.program-cards-container', childView: ProgramCardView, - context: {'xseriesUrl': '/programs'}, + context: context, collection: programCollection, titleContext: titleContext }); diff --git a/lms/static/js/spec/learner_dashboard/course_card_view_spec.js b/lms/static/js/spec/learner_dashboard/course_card_view_spec.js index 9754f70d8c..019ac5b7ff 100644 --- a/lms/static/js/spec/learner_dashboard/course_card_view_spec.js +++ b/lms/static/js/spec/learner_dashboard/course_card_view_spec.js @@ -9,12 +9,14 @@ define([ describe('Course Card View', function() { var view = null, courseCardModel, - context, + course, + startDate = 'Feb 28, 2017', + endDate = 'May 30, 2017', setupView = function(data, isEnrolled) { var programData = $.extend({}, data); - programData.run_modes[0].is_enrolled = isEnrolled; + programData.course_runs[0].is_enrolled = isEnrolled; setFixtures('
'); courseCardModel = new CourseCardModel(programData); view = new CourseCardView({ @@ -24,48 +26,49 @@ define([ 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.$('.header-img').attr('src')).toEqual(course.course_runs[0].image.src); + expect(view.$('.course-details .course-title-link').text().trim()).toEqual(course.title); expect(view.$('.course-details .course-title-link').attr('href')).toEqual( - context.run_modes[0].marketing_url + course.course_runs[0].marketing_url ); - expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key); + expect(view.$('.course-details .course-text .course-key').html()).toEqual(course.key); expect(view.$('.course-details .course-text .run-period').html()).toEqual( - context.run_modes[0].start_date + ' - ' + context.run_modes[0].end_date + startDate + ' - ' + endDate ); }; beforeEach(function() { - // Redefine this data prior to each test case so that tests can't - // break each other by modifying data copied by reference. - context = { - course_modes: [], - display_name: 'Astrophysics: Exploring Exoplanets', - key: 'ANU-ASTRO1x', - organization: { - display_name: 'Australian National University', - key: 'ANUx' - }, - run_modes: [{ - certificate_url: '', - course_image_url: 'http://test.com/image1', - course_key: 'course-v1:ANUx+ANU-ASTRO1x+3T2015', - course_started: true, - course_url: 'https://courses.example.com/courses/course-v1:edX+DemoX+Demo_Course', - end_date: 'Jun 13, 2019', - enrollment_open_date: 'Apr 1, 2016', - is_course_ended: false, - is_enrolled: true, - is_enrollment_open: true, - marketing_url: 'https://www.example.com/marketing/site', - mode_slug: 'verified', - run_key: '2T2016', - start_date: 'Apr 25, 2016', - upgrade_url: '' - }] + // NOTE: This data is redefined prior to each test case so that tests + // can't break each other by modifying data copied by reference. + course = { + key: 'WageningenX+FFESx', + uuid: '9f8562eb-f99b-45c7-b437-799fd0c15b6a', + title: 'Systems thinking and environmental sustainability', + course_runs: [ + { + key: 'course-v1:WageningenX+FFESx+1T2017', + title: 'Food Security and Sustainability: Systems thinking and environmental sustainability', + image: { + src: 'https://example.com/9f8562eb-f99b-45c7-b437-799fd0c15b6a.jpg' + }, + marketing_url: 'https://www.edx.org/course/food-security-sustainability', + start: '2017-02-28T05:00:00Z', + end: '2017-05-30T23:00:00Z', + enrollment_start: '2017-01-18T00:00:00Z', + enrollment_end: null, + type: 'verified', + certificate_url: '', + course_url: 'https://courses.example.com/courses/course-v1:WageningenX+FFESx+1T2017', + enrollment_open_date: 'Jan 18, 2016', + is_course_ended: false, + is_enrolled: true, + is_enrollment_open: true, + upgrade_url: '' + } + ] }; - setupView(context, false); + setupView(course, false); }); afterEach(function() { @@ -78,7 +81,7 @@ define([ it('should render the course card based on the data enrolled', function() { view.remove(); - setupView(context, true); + setupView(course, true); validateCourseInfoDisplay(); }); @@ -94,11 +97,11 @@ define([ }); it('should show the course advertised start date', function() { - var advertisedStart = 'This is an advertised start'; - context.run_modes[0].advertised_start = advertisedStart; - setupView(context, false); + var advertisedStart = 'A long time ago...'; + course.course_runs[0].advertised_start = advertisedStart; + setupView(course, false); expect(view.$('.course-details .course-text .run-period').html()).toEqual( - advertisedStart + ' - ' + context.run_modes[0].end_date + advertisedStart + ' - ' + endDate ); }); @@ -108,8 +111,8 @@ define([ expect(view.$('.certificate-status').length).toEqual(0); view.remove(); - context.run_modes[0].certificate_url = certUrl; - setupView(context, false); + course.course_runs[0].certificate_url = certUrl; + setupView(course, false); expect(view.$('.certificate-status').length).toEqual(1); expect(view.$('.certificate-status .cta-secondary').attr('href')).toEqual(certUrl); }); @@ -120,53 +123,53 @@ define([ expect(view.$('.upgrade-message').length).toEqual(0); view.remove(); - context.run_modes[0].upgrade_url = upgradeUrl; - setupView(context, false); + course.course_runs[0].upgrade_url = upgradeUrl; + setupView(course, false); expect(view.$('.upgrade-message').length).toEqual(1); expect(view.$('.upgrade-message .cta-primary').attr('href')).toEqual(upgradeUrl); }); it('should not show both the upgrade message and certificate status sections', function() { - // Verify that no empty elements are left in the DOM. - context.run_modes[0].upgrade_url = ''; - context.run_modes[0].certificate_url = ''; - setupView(context, false); + // Verify that no empty elements are left in the DOM. + course.course_runs[0].upgrade_url = ''; + course.course_runs[0].certificate_url = ''; + setupView(course, false); expect(view.$('.upgrade-message').length).toEqual(0); expect(view.$('.certificate-status').length).toEqual(0); view.remove(); - // Verify that the upgrade message takes priority. - context.run_modes[0].upgrade_url = '/path/to/upgrade'; - context.run_modes[0].certificate_url = '/path/to/certificate'; - setupView(context, false); + // Verify that the upgrade message takes priority. + course.course_runs[0].upgrade_url = '/path/to/upgrade'; + course.course_runs[0].certificate_url = '/path/to/certificate'; + setupView(course, false); expect(view.$('.upgrade-message').length).toEqual(1); expect(view.$('.certificate-status').length).toEqual(0); }); it('should show a message if an there is an upcoming course run', function() { - context.run_modes[0].is_enrollment_open = false; + course.course_runs[0].is_enrollment_open = false; - setupView(context, false); + setupView(course, false); - expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url); - expect(view.$('.course-details .course-title').text().trim()).toEqual(context.display_name); - expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key); + expect(view.$('.header-img').attr('src')).toEqual(course.course_runs[0].image.src); + expect(view.$('.course-details .course-title').text().trim()).toEqual(course.title); + expect(view.$('.course-details .course-text .course-key').html()).toEqual(course.key); expect(view.$('.course-details .course-text .run-period').length).toBe(0); expect(view.$('.no-action-message').text().trim()).toBe('Coming Soon'); expect(view.$('.enrollment-open-date').text().trim()).toEqual( - context.run_modes[0].enrollment_open_date - ); + course.course_runs[0].enrollment_open_date + ); }); - it('should show a message if there are no known upcoming course runs', function() { - context.run_modes[0].is_enrollment_open = false; - context.run_modes[0].is_course_ended = true; + it('should show a message if there are no upcoming course runs', function() { + course.course_runs[0].is_enrollment_open = false; + course.course_runs[0].is_course_ended = true; - setupView(context, false); + setupView(course, false); - expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url); - expect(view.$('.course-details .course-title').text().trim()).toEqual(context.display_name); - expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key); + expect(view.$('.header-img').attr('src')).toEqual(course.course_runs[0].image.src); + expect(view.$('.course-details .course-title').text().trim()).toEqual(course.title); + expect(view.$('.course-details .course-text .course-key').html()).toEqual(course.key); expect(view.$('.course-details .course-text .run-period').length).toBe(0); expect(view.$('.no-action-message').text().trim()).toBe('Not Currently Available'); expect(view.$('.enrollment-opens').length).toEqual(0); @@ -174,23 +177,23 @@ define([ it('should link to the marketing site when a URL is available', function() { $.each(['.course-image-link', '.course-title-link'], function(index, selector) { - expect(view.$(selector).attr('href')).toEqual(context.run_modes[0].marketing_url); + expect(view.$(selector).attr('href')).toEqual(course.course_runs[0].marketing_url); }); }); it('should link to the course home when no marketing URL is available', function() { - context.run_modes[0].marketing_url = null; - setupView(context, false); + course.course_runs[0].marketing_url = null; + setupView(course, false); $.each(['.course-image-link', '.course-title-link'], function(index, selector) { - expect(view.$(selector).attr('href')).toEqual(context.run_modes[0].course_url); + expect(view.$(selector).attr('href')).toEqual(course.course_runs[0].course_url); }); }); it('should not link to the marketing site or the course home if neither URL is available', function() { - context.run_modes[0].marketing_url = null; - context.run_modes[0].course_url = null; - setupView(context, false); + course.course_runs[0].marketing_url = null; + course.course_runs[0].course_url = null; + setupView(course, false); $.each(['.course-image-link', '.course-title-link'], function(index, selector) { expect(view.$(selector).length).toEqual(0); diff --git a/lms/static/js/spec/learner_dashboard/course_enroll_view_spec.js b/lms/static/js/spec/learner_dashboard/course_enroll_view_spec.js index 95423f2fdb..c445d38e54 100644 --- a/lms/static/js/spec/learner_dashboard/course_enroll_view_spec.js +++ b/lms/static/js/spec/learner_dashboard/course_enroll_view_spec.js @@ -13,75 +13,102 @@ define([ courseEnrollModel, urlModel, setupView, - singleRunModeList, - multiRunModeList, - context = { - display_name: 'Edx Demo course', - key: 'edX+DemoX+Demo_Course', - organization: { - display_name: 'edx.org', - key: 'edX' - } + singleCourseRunList, + multiCourseRunList, + course = { + key: 'WageningenX+FFESx', + uuid: '9f8562eb-f99b-45c7-b437-799fd0c15b6a', + title: 'Systems thinking and environmental sustainability', + owners: [ + { + uuid: '0c6e5fa2-96e8-40b2-9ebe-c8b0df2a3b22', + key: 'WageningenX', + name: 'Wageningen University & Research' + } + ] }, urls = { - dashboard_url: '/dashboard', - id_verification_url: '/verify_student/start_flow/', + commerce_api_url: '/commerce', track_selection_url: '/select_track/course/' }; beforeEach(function() { - // Redefine this data prior to each test case so that tests can't - // break each other by modifying data copied by reference. - 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', + // NOTE: This data is redefined prior to each test case so that tests + // can't break each other by modifying data copied by reference. + singleCourseRunList = [{ + key: 'course-v1:WageningenX+FFESx+1T2017', + uuid: '2f2edf03-79e6-4e39-aef0-65436a6ee344', + title: 'Food Security and Sustainability: Systems thinking and environmental sustainability', + image: { + src: 'https://example.com/2f2edf03-79e6-4e39-aef0-65436a6ee344.jpg' + }, + marketing_url: 'https://www.edx.org/course/food-security-sustainability-systems-wageningenx-ffesx', + start: '2017-02-28T05:00:00Z', + end: '2017-05-30T23:00:00Z', + enrollment_start: '2017-01-18T00:00:00Z', + enrollment_end: null, + type: 'verified', + certificate_url: '', + course_url: 'https://courses.example.com/courses/course-v1:edX+DemoX+Demo_Course', + enrollment_open_date: 'Jan 18, 2016', is_course_ended: false, - mode_slug: 'audit', - run_key: '2T2016', is_enrolled: false, - is_enrollment_open: true + is_enrollment_open: true, + upgrade_url: '' }]; - 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', + multiCourseRunList = [{ + key: 'course-v1:WageningenX+FFESx+2T2016', + uuid: '9bbb7844-4848-44ab-8e20-0be6604886e9', + title: 'Food Security and Sustainability: Systems thinking and environmental sustainability', + image: { + src: 'https://example.com/9bbb7844-4848-44ab-8e20-0be6604886e9.jpg' + }, + short_description: 'Learn how to apply systems thinking to improve food production systems.', + marketing_url: 'https://www.edx.org/course/food-security-sustainability-systems-wageningenx-stesx', + start: '2016-09-08T04:00:00Z', + end: '2016-11-11T00:00:00Z', + enrollment_start: null, + enrollment_end: null, + pacing_type: 'instructor_paced', + type: 'verified', + certificate_url: '', + course_url: 'https://courses.example.com/courses/course-v1:WageningenX+FFESx+2T2016', + enrollment_open_date: 'Jan 18, 2016', is_course_ended: false, - run_key: '1T2015', is_enrolled: false, is_enrollment_open: true }, { - 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', + key: 'course-v1:WageningenX+FFESx+1T2017', + uuid: '2f2edf03-79e6-4e39-aef0-65436a6ee344', + title: 'Food Security and Sustainability: Systems thinking and environmental sustainability', + image: { + src: 'https://example.com/2f2edf03-79e6-4e39-aef0-65436a6ee344.jpg' + }, + marketing_url: 'https://www.edx.org/course/food-security-sustainability-systems-wageningenx-ffesx', + start: '2017-02-28T05:00:00Z', + end: '2017-05-30T23:00:00Z', + enrollment_start: '2017-01-18T00:00:00Z', + enrollment_end: null, + type: 'verified', + certificate_url: '', + course_url: 'https://courses.example.com/courses/course-v1:WageningenX+FFESx+1T2017', + enrollment_open_date: 'Jan 18, 2016', is_course_ended: false, - mode_slug: 'verified', - run_key: '2T2015', is_enrolled: false, is_enrollment_open: true }]; }); - setupView = function(runModes, urls) { - context.run_modes = runModes; + setupView = function(courseRuns, urlMap) { + course.course_runs = courseRuns; setFixtures('
'); - courseCardModel = new CourseCardModel(context); + courseCardModel = new CourseCardModel(course); courseEnrollModel = new CourseEnrollModel({}, { - courseId: courseCardModel.get('course_key') + courseId: courseCardModel.get('course_run_key') }); - if (urls) { - urlModel = new Backbone.Model(urls); + if (urlMap) { + urlModel = new Backbone.Model(urlMap); } view = new CourseEnrollView({ $parentEl: $('.course-actions'), @@ -99,143 +126,183 @@ define([ }); it('should exist', function() { - setupView(singleRunModeList); + setupView(singleCourseRunList); 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'); + it('should render the course enroll view when not enrolled', function() { + setupView(singleCourseRunList); + + 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() { - singleRunModeList[0].is_enrolled = true; + it('should render the course enroll view when enrolled', function() { + singleCourseRunList[0].is_enrolled = true; - setupView(singleRunModeList); + setupView(singleCourseRunList); 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').attr('href')).toEqual(course.course_runs[0].course_url); expect(view.$('.view-course-link').text().trim()).toEqual('View Course'); expect(view.$('.run-select').length).toBe(0); }); it('should allow the learner to view an archived course', function() { - // Regression test for ECOM-4974. - singleRunModeList[0].is_enrolled = true; - singleRunModeList[0].is_enrollment_open = false; - singleRunModeList[0].is_course_ended = true; + // Regression test for ECOM-4974. + singleCourseRunList[0].is_enrolled = true; + singleCourseRunList[0].is_enrollment_open = false; + singleCourseRunList[0].is_course_ended = true; - setupView(singleRunModeList); + setupView(singleCourseRunList); expect(view.$('.view-course-link').text().trim()).toEqual('View Archived Course'); }); - it('should not render anything if run modes is empty', function() { + it('should not render anything if course runs are 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); + it('should render run selection dropdown if multiple course runs are available', function() { + setupView(multiCourseRunList); + expect(view.$('.run-select').length).toBe(1); expect(view.$('.run-select').val()).toEqual(''); expect(view.$('.run-select option').length).toBe(3); }); - it('should switch run context if dropdown selection changed', function() { - setupView(multiRunModeList); - spyOn(courseCardModel, 'updateRun').and.callThrough(); + it('should switch course run context if an option is selected from the dropdown', function() { + setupView(multiCourseRunList); + + spyOn(courseCardModel, 'updateCourseRun').and.callThrough(); + expect(view.$('.run-select').val()).toEqual(''); - view.$('.run-select').val(multiRunModeList[1].run_key); + + view.$('.run-select').val(multiCourseRunList[1].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); + + expect(view.$('.run-select').val()).toEqual(multiCourseRunList[1].key); + expect(courseCardModel.updateCourseRun) + .toHaveBeenCalledWith(multiCourseRunList[1].key); + expect(courseCardModel.get('course_key')).toEqual(course.key); }); - it('should enroll learner when enroll button clicked', function() { - setupView(singleRunModeList); + it('should enroll learner when enroll button is clicked with one course run available', function() { + setupView(singleCourseRunList); + 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); + it('should enroll learner when enroll button is clicked with multiple course runs available', function() { + setupView(multiCourseRunList); + spyOn(courseEnrollModel, 'save'); - view.$('.run-select').val(multiRunModeList[1].run_key); + + view.$('.run-select').val(multiCourseRunList[1].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); + it('should redirect to track selection when audit enrollment succeeds', function() { + singleCourseRunList[0].is_enrolled = false; + singleCourseRunList[0].mode_slug = 'audit'; + + setupView(singleCourseRunList, 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')); + view.trackSelectionUrl + courseCardModel.get('course_run_key')); }); + it('should redirect to track selection when enrollment in an unspecified mode is attempted', function() { + singleCourseRunList[0].is_enrolled = false; + singleCourseRunList[0].mode_slug = null; + + setupView(singleCourseRunList, urls); - 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')); + view.trackSelectionUrl + courseCardModel.get('course_run_key') + ); }); - it('should not redirect when urls not provided', function() { - singleRunModeList[0].is_enrolled = false; - singleRunModeList[0].mode_slug = 'verified'; - setupView(singleRunModeList); + it('should not redirect when urls are not provided', function() { + singleCourseRunList[0].is_enrolled = false; + singleCourseRunList[0].mode_slug = 'verified'; + + setupView(singleCourseRunList); + 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); + setupView(singleCourseRunList, 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')); + view.trackSelectionUrl + courseCardModel.get('course_run_key') + ); }); it('should redirect to login on 403 error', function() { var response = { status: 403, responseJSON: { - user_message_url: 'test_url/haha' - }}; - setupView(singleRunModeList, urls); + user_message_url: 'redirect/to/this' + } + }; + + setupView(singleCourseRunList, 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); + response.responseJSON.user_message_url + ); }); }); } diff --git a/lms/static/js/spec/learner_dashboard/program_card_view_spec.js b/lms/static/js/spec/learner_dashboard/program_card_view_spec.js index 32603ecb2a..6b4fa1bbb9 100644 --- a/lms/static/js/spec/learner_dashboard/program_card_view_spec.js +++ b/lms/static/js/spec/learner_dashboard/program_card_view_spec.js @@ -1,58 +1,73 @@ define([ 'backbone', + 'underscore', 'jquery', 'js/learner_dashboard/collections/program_progress_collection', 'js/learner_dashboard/models/program_model', 'js/learner_dashboard/views/program_card_view' -], function(Backbone, $, ProgressCollection, ProgramModel, ProgramCardView) { +], function(Backbone, _, $, ProgressCollection, ProgramModel, ProgramCardView) { 'use strict'; - /* jslint maxlen: 500 */ + /* jslint maxlen: 500 */ describe('Program card View', function() { var view = null, programModel, program = { - category: 'FooBar', - status: 'active', - subtitle: 'program 1', - name: 'test program 1', - organizations: [ - { - display_name: 'edX', - key: 'edx' + uuid: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', + title: 'Food Security and Sustainability', + subtitle: 'Learn how to feed all people in the world in a sustainable way.', + type: 'XSeries', + detail_url: 'https://www.edx.org/foo/bar', + banner_image: { + medium: { + height: 242, + width: 726, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.medium.jpg' + }, + 'x-small': { + height: 116, + width: 348, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.x-small.jpg' + }, + small: { + height: 145, + width: 435, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.small.jpg' + }, + large: { + height: 480, + width: 1440, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.large.jpg' } - ], - created: '2016-03-03T19:18:50.061136Z', - modified: '2016-03-25T13:45:21.220732Z', - marketing_slug: 'p_2?param=haha&test=b', - id: 146, - detail_url: 'http://courses.edx.org/dashboard/programs/1/foo', - banner_image_urls: { - w348h116: 'http://www.edx.org/images/test1', - w435h145: 'http://www.edx.org/images/test2', - w726h242: 'http://www.edx.org/images/test3' - } + }, + authoring_organizations: [ + { + uuid: '0c6e5fa2-96e8-40b2-9ebe-c8b0df2a3b22', + key: 'WageningenX', + name: 'Wageningen University & Research' + } + ] }, userProgress = [ { - id: 146, - completed: ['courses', 'the', 'user', 'completed'], - in_progress: ['in', 'progress'], - not_started: ['courses', 'not', 'yet', 'started'] + uuid: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', + completed: 4, + in_progress: 2, + not_started: 4 }, { - id: 147, - completed: ['Course 1'], - in_progress: [], - not_started: ['Course 2', 'Course 3', 'Course 4'] + uuid: '91d144d2-1bb1-4afe-90df-d5cff63fa6e2', + completed: 1, + in_progress: 0, + not_started: 3 } ], progressCollection = new ProgressCollection(), cardRenders = function($card) { expect($card).toBeDefined(); - expect($card.find('.title').html().trim()).toEqual(program.name); - expect($card.find('.category span').html().trim()).toEqual(program.category); - expect($card.find('.organization').html().trim()).toEqual(program.organizations[0].key); + expect($card.find('.title').html().trim()).toEqual(program.title); + expect($card.find('.category span').html().trim()).toEqual(program.type); + expect($card.find('.organization').html().trim()).toEqual(program.authoring_organizations[0].key); expect($card.find('.card-link').attr('href')).toEqual(program.detail_url); }; @@ -87,12 +102,16 @@ define([ }); it('should handle exceptions from reEvaluatePicture', function() { + var message = 'Picturefill had exceptions'; + spyOn(view, 'reEvaluatePicture').and.callFake(function() { - throw {name: 'Picturefill had exceptions'}; + var error = {name: message}; + + throw error; }); view.reLoadBannerImage(); expect(view.reEvaluatePicture).toHaveBeenCalled(); - expect(view.reLoadBannerImage).not.toThrow('Picturefill had exceptions'); + expect(view.reLoadBannerImage).not.toThrow(message); }); it('should calculate the correct percentages for progress bars', function() { @@ -101,11 +120,12 @@ define([ }); it('should display the correct completed courses message', function() { - var program = _.findWhere(userProgress, {id: 146}), - completed = program.completed.length, - total = completed + program.in_progress.length + program.not_started.length; + var programProgress = _.findWhere(userProgress, {uuid: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8'}), + completed = programProgress.completed, + total = completed + programProgress.in_progress + programProgress.not_started; - expect(view.$('.certificate-status .status-text').not('.secondary').html()).toEqual('You have earned certificates in ' + completed + ' of the ' + total + ' courses so far.'); + expect(view.$('.certificate-status .status-text').not('.secondary').html()) + .toEqual('You have earned certificates in ' + completed + ' of the ' + total + ' courses so far.'); }); it('should render cards if there is no progressData', function() { diff --git a/lms/static/js/spec/learner_dashboard/program_details_header_spec.js b/lms/static/js/spec/learner_dashboard/program_details_header_spec.js index 954ea62667..5d692dd749 100644 --- a/lms/static/js/spec/learner_dashboard/program_details_header_spec.js +++ b/lms/static/js/spec/learner_dashboard/program_details_header_spec.js @@ -12,23 +12,42 @@ define([ program_listing_url: '/dashboard/programs' }, programData: { - uuid: '12-ab', - name: 'Astrophysics', - subtitle: 'Learn contemporary astrophysics from the leaders in the field.', - category: 'xseries', - organizations: [ - { - display_name: 'Australian National University', - img: 'common/test/data/static/picture1.jpg', - key: 'ANUx' + uuid: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', + title: 'Food Security and Sustainability', + subtitle: 'Learn how to feed all people in the world in a sustainable way.', + type: 'XSeries', + detail_url: 'https://www.edx.org/foo/bar', + banner_image: { + medium: { + height: 242, + width: 726, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.medium.jpg' + }, + 'x-small': { + height: 116, + width: 348, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.x-small.jpg' + }, + small: { + height: 145, + width: 435, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.small.jpg' + }, + large: { + height: 480, + width: 1440, + url: 'https://example.com/a87e5eac-3c93-45a1-a8e1-4c79ca8401c8.large.jpg' } - ], - banner_image_urls: { - w1440h480: 'common/test/data/static/picture1.jpg', - w726h242: 'common/test/data/static/picture2.jpg', - w348h116: 'common/test/data/static/picture3.jpg' }, - program_details_url: '/dashboard/programs' + authoring_organizations: [ + { + uuid: '0c6e5fa2-96e8-40b2-9ebe-c8b0df2a3b22', + key: 'WageningenX', + name: 'Wageningen University & Research', + certificate_logo_image_url: 'https://example.com/org-certificate-logo.jpg', + logo_image_url: 'https://example.com/org-logo.jpg' + } + ] } }; @@ -51,13 +70,13 @@ define([ it('should render the header based on the passed in model', function() { var programListUrl = view.$('.breadcrumb-list .crumb:nth-of-type(2) .crumb-link').attr('href'); - expect(view.$('.title').html()).toEqual(context.programData.name); + expect(view.$('.title').html()).toEqual(context.programData.title); expect(view.$('.subtitle').html()).toEqual(context.programData.subtitle); - expect(view.$('.org-logo').length).toEqual(context.programData.organizations.length); - expect(view.$('.org-logo').attr('src')).toEqual(context.programData.organizations[0].img); - expect(view.$('.org-logo').attr('alt')).toEqual( - context.programData.organizations[0].display_name + '\'s logo' - ); + expect(view.$('.org-logo').length).toEqual(context.programData.authoring_organizations.length); + expect(view.$('.org-logo').attr('src')) + .toEqual(context.programData.authoring_organizations[0].certificate_logo_image_url); + expect(view.$('.org-logo').attr('alt')) + .toEqual(context.programData.authoring_organizations[0].name + '\'s logo'); expect(programListUrl).toEqual(context.urls.program_listing_url); }); }); diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index b92099ff48..002e12a730 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -98,7 +98,7 @@ from openedx.core.djangolib.markup import HTML, Text <% is_course_blocked = (enrollment.course_id in block_courses) %> <% course_verification_status = verification_status_by_course.get(enrollment.course_id, {}) %> <% course_requirements = courses_requirements_not_met.get(enrollment.course_id) %> - <% related_programs = programs_by_run.get(unicode(enrollment.course_id)) %> + <% related_programs = inverted_programs.get(unicode(enrollment.course_id)) %> <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option=show_refund_option, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard' /> % endfor diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 6cf070ae8a..e685a494d5 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -300,8 +300,8 @@ from student.helpers import ( @@ -397,12 +397,6 @@ from student.helpers import ( %endif - % if course_program_info and course_program_info.get('category'): - %for program_data in course_program_info.get('course_program_list', []): - <%include file = "_dashboard_program_info.html" args="program_data=program_data, enrollment_mode=enrollment.mode, category=course_program_info['category']" /> - %endfor - % endif - % if is_course_blocked:

${Text(_("You can no longer access this course because payment has not yet been received. " diff --git a/lms/templates/learner_dashboard/course_card.underscore b/lms/templates/learner_dashboard/course_card.underscore index fe035c459c..e93ff8eec1 100644 --- a/lms/templates/learner_dashboard/course_card.underscore +++ b/lms/templates/learner_dashboard/course_card.underscore @@ -7,7 +7,7 @@ class="header-img" src="<%- course_image_url %>" <% // safe-lint: disable=underscore-not-escaped %> - alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true) %>"/> + alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: title}, true) %>"/> <% } else { %> @@ -18,10 +18,10 @@

<% if ( marketing_url || course_url ) { %> - <%- display_name %> + <%- title %> <% } else { %> - <%- display_name %> + <%- title %> <% } %>

@@ -29,7 +29,7 @@ <%- start_date %> - <%- end_date %> - <% } %> - <%- key %> + <%- course_key %>
diff --git a/lms/templates/learner_dashboard/course_enroll.underscore b/lms/templates/learner_dashboard/course_enroll.underscore index f58899f6ec..843677ac0a 100644 --- a/lms/templates/learner_dashboard/course_enroll.underscore +++ b/lms/templates/learner_dashboard/course_enroll.underscore @@ -10,9 +10,9 @@ <% } %> <% } else { %> - <% if (enrollable_run_modes.length > 0) { %> -
<%- gettext('not enrolled') %>
- <% if (enrollable_run_modes.length > 1) { %> + <% if (enrollable_course_runs.length > 0) { %> +
<%- gettext('Not Enrolled') %>
+ <% if (enrollable_course_runs.length > 1) { %>
<%- gettext('Please select a course date') %> @@ -24,16 +24,16 @@ - <% _.each (enrollable_run_modes, function(runMode) { %> + <% _.each (enrollable_course_runs, function(courseRun) { %> @@ -44,14 +44,14 @@ - <% } else if (upcoming_run_modes.length > 0) {%> + <% } else if (upcoming_course_runs.length > 0) {%>
<%- gettext('Coming Soon') %>
<%- gettext('Enrollment Opens on') %> - <%- upcoming_run_modes[0].enrollment_open_date %> + <%- upcoming_course_runs[0].enrollment_open_date %>
<% } else { %> diff --git a/lms/templates/learner_dashboard/program_card.underscore b/lms/templates/learner_dashboard/program_card.underscore index c44ffbb3b1..5cb4c61849 100644 --- a/lms/templates/learner_dashboard/program_card.underscore +++ b/lms/templates/learner_dashboard/program_card.underscore @@ -1,35 +1,35 @@
-

<%- gettext(name) %>

+

<%- gettext(title) %>

<%- orgList %>
- <%- gettext(category) %> - + <%- gettext(type) %> +
<% if (progress) { %>

- <%= interpolate( + <%= interpolate( ngettext( '%(count)s course is in progress.', '%(count)s courses are in progress.', - progress.total.in_progress + progress.in_progress ), - {count: progress.total.in_progress}, true + {count: progress.in_progress}, true ) %> - <%= interpolate( + <%= interpolate( ngettext( '%(count)s course has not been started.', '%(count)s courses have not been started.', - progress.total.not_started + progress.not_started ), - {count: progress.total.not_started}, true + {count: progress.not_started}, true ) %> - <%= interpolate( + <%= interpolate( gettext('You have earned certificates in %(completed_courses)s of the %(total_courses)s courses so far.'), - {completed_courses: progress.total.completed, total_courses: progress.total.courses}, true + {completed_courses: progress.completed, total_courses: progress.total}, true ) %>

<% } %> @@ -44,11 +44,11 @@ diff --git a/lms/templates/learner_dashboard/program_header_view.underscore b/lms/templates/learner_dashboard/program_header_view.underscore index 7fe8c90703..edcd3171a9 100644 --- a/lms/templates/learner_dashboard/program_header_view.underscore +++ b/lms/templates/learner_dashboard/program_header_view.underscore @@ -1,19 +1,19 @@