Merge pull request #12408 from edx/alasdair/ECOM-4215-program-details-page-setup
ECOM-4215 adding setup for new program details page
This commit is contained in:
@@ -65,4 +65,5 @@ class ProgramsConfigMixin(object):
|
||||
'enable_certification': is_enabled,
|
||||
'xseries_ad_enabled': is_enabled,
|
||||
'program_listing_enabled': is_enabled,
|
||||
'program_details_enabled': is_enabled,
|
||||
}).install()
|
||||
|
||||
@@ -20,3 +20,11 @@ class ProgramListingPage(PageObject):
|
||||
def is_sidebar_present(self):
|
||||
"""Check whether sidebar is present."""
|
||||
return self.q(css='.sidebar').present
|
||||
|
||||
|
||||
class ProgramDetailsPage(PageObject):
|
||||
"""Program details page."""
|
||||
url = BASE_URL + '/dashboard/programs/123'
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='.js-program-details-wrapper').present
|
||||
|
||||
@@ -5,16 +5,15 @@ from ...fixtures.programs import FakeProgram, ProgramsFixture, ProgramsConfigMix
|
||||
from ...fixtures.course import CourseFixture
|
||||
from ..helpers import UniqueCourseTest
|
||||
from ...pages.lms.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.programs import ProgramListingPage
|
||||
from ...pages.lms.programs import ProgramListingPage, ProgramDetailsPage
|
||||
|
||||
|
||||
class ProgramListingPageBase(ProgramsConfigMixin, UniqueCourseTest):
|
||||
class ProgramPageBase(ProgramsConfigMixin, UniqueCourseTest):
|
||||
"""Base class used for program listing page tests."""
|
||||
def setUp(self):
|
||||
super(ProgramListingPageBase, self).setUp()
|
||||
super(ProgramPageBase, self).setUp()
|
||||
|
||||
self.set_programs_api_configuration(is_enabled=True)
|
||||
self.listing_page = ProgramListingPage(self.browser)
|
||||
|
||||
def stub_api(self, course_id=None):
|
||||
"""Stub out the programs API with fake data."""
|
||||
@@ -35,8 +34,13 @@ class ProgramListingPageBase(ProgramsConfigMixin, UniqueCourseTest):
|
||||
AutoAuthPage(self.browser, course_id=course_id).visit()
|
||||
|
||||
|
||||
class ProgramListingPageTest(ProgramListingPageBase):
|
||||
class ProgramListingPageTest(ProgramPageBase):
|
||||
"""Verify user-facing behavior of the program listing page."""
|
||||
def setUp(self):
|
||||
super(ProgramListingPageTest, self).setUp()
|
||||
|
||||
self.listing_page = ProgramListingPage(self.browser)
|
||||
|
||||
def test_no_enrollments(self):
|
||||
"""Verify that no cards appear when the user has no enrollments."""
|
||||
self.stub_api()
|
||||
@@ -76,8 +80,12 @@ class ProgramListingPageTest(ProgramListingPageBase):
|
||||
|
||||
|
||||
@attr('a11y')
|
||||
class ProgramListingPageA11yTest(ProgramListingPageBase):
|
||||
class ProgramListingPageA11yTest(ProgramPageBase):
|
||||
"""Test program listing page accessibility."""
|
||||
def setUp(self):
|
||||
super(ProgramListingPageA11yTest, self).setUp()
|
||||
|
||||
self.listing_page = ProgramListingPage(self.browser)
|
||||
|
||||
def test_empty_a11y(self):
|
||||
"""Test a11y of the page's empty state."""
|
||||
@@ -100,3 +108,19 @@ class ProgramListingPageA11yTest(ProgramListingPageBase):
|
||||
self.assertTrue(self.listing_page.are_cards_present)
|
||||
|
||||
self.listing_page.a11y_audit.check_for_accessibility_errors()
|
||||
|
||||
|
||||
@attr('a11y')
|
||||
class ProgramDetailsPageA11yTest(ProgramPageBase):
|
||||
"""Test program details page accessibility."""
|
||||
def setUp(self):
|
||||
super(ProgramDetailsPageA11yTest, self).setUp()
|
||||
|
||||
self.details_page = ProgramDetailsPage(self.browser)
|
||||
|
||||
def test_a11y(self):
|
||||
"""Test a11y of the page's state."""
|
||||
self.auth(enroll=False)
|
||||
self.details_page.visit()
|
||||
|
||||
self.details_page.a11y_audit.check_for_accessibility_errors()
|
||||
|
||||
@@ -8,7 +8,7 @@ from urlparse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
from django.test import override_settings, TestCase
|
||||
from edx_oauth2_provider.tests.factories import ClientFactory
|
||||
from opaque_keys.edx import locator
|
||||
from provider.constants import CONFIDENTIAL
|
||||
@@ -205,3 +205,39 @@ class TestProgramListing(
|
||||
for certificate in self._expected_credentials_data():
|
||||
self.assertNotContains(response, certificate['display_name'])
|
||||
self.assertNotContains(response, certificate['credential_url'])
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@override_settings(MKTG_URLS={'ROOT': 'http://edx.org'})
|
||||
class TestProgramDetails(ProgramsApiConfigMixin, TestCase):
|
||||
"""
|
||||
Unit tests for the program details page
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestProgramDetails, self).setUp()
|
||||
self.user = UserFactory()
|
||||
self.details_page = reverse('program_details_view', args=['123'])
|
||||
|
||||
def test_login_required(self):
|
||||
"""
|
||||
Verify that login is required to access the page.
|
||||
"""
|
||||
self.create_programs_config()
|
||||
response = self.client.get(self.details_page)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
'{}?next={}'.format(reverse('signin_user'), self.details_page)
|
||||
)
|
||||
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
response = self.client.get(self.details_page)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
def test_404_if_disabled(self):
|
||||
"""
|
||||
Verify that the page 404s if disabled.
|
||||
"""
|
||||
self.create_programs_config(program_details_enabled=False)
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
response = self.client.get(self.details_page)
|
||||
self.assertEquals(response.status_code, 404)
|
||||
|
||||
@@ -6,5 +6,6 @@ from django.conf.urls import url
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^programs/(?P<program_uuid>[0-9a-f-]+)/$', views.program_details, name='program_details_view'),
|
||||
url(r'^programs/$', views.view_programs, name='program_listing_view'),
|
||||
]
|
||||
|
||||
@@ -45,3 +45,20 @@ def view_programs(request):
|
||||
}
|
||||
|
||||
return render_to_response('learner_dashboard/programs.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_GET
|
||||
def program_details(request, program_uuid): # pylint: disable=unused-argument
|
||||
"""View programs in which the user is engaged."""
|
||||
show_program_details = ProgramsApiConfig.current().show_program_details
|
||||
if not show_program_details:
|
||||
raise Http404
|
||||
|
||||
context = {
|
||||
'nav_hidden': True,
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True
|
||||
}
|
||||
|
||||
return render_to_response('learner_dashboard/program_details.html', context)
|
||||
|
||||
13
lms/static/js/learner_dashboard/program_details_factory.js
Normal file
13
lms/static/js/learner_dashboard/program_details_factory.js
Normal file
@@ -0,0 +1,13 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'js/learner_dashboard/views/program_details_view'
|
||||
],
|
||||
function(ProgramDetailsView) {
|
||||
return function (options) {
|
||||
var ProgramDetails = new ProgramDetailsView(options);
|
||||
return ProgramDetails;
|
||||
};
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,38 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'text!../../../templates/learner_dashboard/program_details_view.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
el: '.js-program-details-wrapper',
|
||||
|
||||
tpl: _.template(pageTpl),
|
||||
|
||||
initialize: function(data) {
|
||||
this.context = data.context;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.tpl(this.context));
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
// Add subviews
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -35,6 +35,7 @@
|
||||
'support/js/certificates_factory',
|
||||
'support/js/enrollment_factory',
|
||||
'js/bookmarks/bookmarks_factory',
|
||||
'js/learner_dashboard/program_details_factory',
|
||||
'js/learner_dashboard/program_list_factory',
|
||||
'js/api_admin/catalog_preview_factory'
|
||||
]),
|
||||
|
||||
24
lms/templates/learner_dashboard/program_details.html
Normal file
24
lms/templates/learner_dashboard/program_details.html
Normal file
@@ -0,0 +1,24 @@
|
||||
## Override the default styles_version to the Pattern Library version (version 2)
|
||||
<%! main_css = "style-learner-dashboard" %>
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="../main.html" />
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.js_utils import (
|
||||
dump_js_escaped_json, js_escaped_string
|
||||
)
|
||||
%>
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="js/learner_dashboard/program_details_factory" class_name="ProgramDetailsFactory">
|
||||
ProgramDetailsFactory({});
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
<%block name="pagetitle">${_("Program Details")}</%block>
|
||||
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<div class="js-program-details-wrapper grid-container"></div>
|
||||
</main>
|
||||
@@ -0,0 +1,9 @@
|
||||
<header class="program-header">
|
||||
<div>
|
||||
<h2 class="hd-2">Program Title</h2>
|
||||
<p>Program Subtitle</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="js-program-progress-view"></div>
|
||||
<div class="js-course-list"></div>
|
||||
<aside class="js-course-sidebar"></aside>
|
||||
@@ -85,7 +85,7 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
</a>
|
||||
</li>
|
||||
<li class="tab-nav-item">
|
||||
<a class="${'active ' if reverse('program_listing_view') == request.path else ''}tab-nav-link" href="${reverse('program_listing_view')}">
|
||||
<a class="${'active ' if reverse('program_listing_view') in request.path else ''}tab-nav-link" href="${reverse('program_listing_view')}">
|
||||
${_("Programs")}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('programs', '0007_programsapiconfig_program_listing_enabled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='programsapiconfig',
|
||||
name='program_details_enabled',
|
||||
field=models.BooleanField(default=False, verbose_name='Do we want to show program details pages'),
|
||||
),
|
||||
]
|
||||
@@ -84,6 +84,11 @@ class ProgramsApiConfig(ConfigurationModel):
|
||||
default=False
|
||||
)
|
||||
|
||||
program_details_enabled = models.BooleanField(
|
||||
verbose_name=_("Do we want to show program details pages"),
|
||||
default=False
|
||||
)
|
||||
|
||||
@property
|
||||
def internal_api_url(self):
|
||||
"""
|
||||
@@ -156,3 +161,10 @@ class ProgramsApiConfig(ConfigurationModel):
|
||||
Indicates whether we want to show program listing page
|
||||
"""
|
||||
return self.enabled and self.program_listing_enabled
|
||||
|
||||
@property
|
||||
def show_program_details(self):
|
||||
"""
|
||||
Indicates whether we want to show program details pages
|
||||
"""
|
||||
return self.enabled and self.program_details_enabled
|
||||
|
||||
@@ -22,6 +22,7 @@ class ProgramsApiConfigMixin(object):
|
||||
'enable_studio_tab': True,
|
||||
'enable_certification': True,
|
||||
'xseries_ad_enabled': True,
|
||||
'program_details_enabled': True,
|
||||
}
|
||||
|
||||
def create_programs_config(self, **kwargs):
|
||||
|
||||
@@ -92,7 +92,7 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
</a>
|
||||
</li>
|
||||
<li class="tab-nav-item">
|
||||
<a class="${'active ' if reverse('program_listing_view') == request.path else ''}tab-nav-link" href="${reverse('program_listing_view')}">
|
||||
<a class="${'active ' if reverse('program_listing_view') in request.path else ''}tab-nav-link" href="${reverse('program_listing_view')}">
|
||||
${_("Programs")}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -81,7 +81,7 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
</a>
|
||||
</li>
|
||||
<li class="tab-nav-item">
|
||||
<a class="${'active ' if reverse('program_listing_view') == request.path else ''}tab-nav-link" href="${reverse('program_listing_view')}">
|
||||
<a class="${'active ' if reverse('program_listing_view') in request.path else ''}tab-nav-link" href="${reverse('program_listing_view')}">
|
||||
${_("Programs")}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user