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 @@