diff --git a/common/test/acceptance/tests/lms/test_lms_courseware.py b/common/test/acceptance/tests/lms/test_lms_courseware.py index a222c7ee46..33dd35f9bd 100644 --- a/common/test/acceptance/tests/lms/test_lms_courseware.py +++ b/common/test/acceptance/tests/lms/test_lms_courseware.py @@ -507,6 +507,39 @@ class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin): sequence_ui_events ) + def test_accordion_events(self): + self.course_nav.go_to_section('Test Section 1', 'Test Subsection 1,2') + + self.course_nav.go_to_section('Test Section 2', 'Test Subsection 2,1') + + # test UI events emitted by navigating via the course outline + filter_outline_ui_event = lambda event: event.get('name', '') == 'edx.ui.lms.outline.selected' + + outline_ui_events = self.wait_for_events(event_filter=filter_outline_ui_event, timeout=2) + + # note: target_url is tested in unit tests, as the url changes here with every test (it includes GUIDs). + self.assert_events_match( + [ + { + 'event_type': 'edx.ui.lms.outline.selected', + 'name': 'edx.ui.lms.outline.selected', + 'event': { + 'target_name': 'Test Subsection 1,2 ', + 'widget_placement': 'accordion', + } + }, + { + 'event_type': 'edx.ui.lms.outline.selected', + 'name': 'edx.ui.lms.outline.selected', + 'event': { + 'target_name': 'Test Subsection 2,1 ', + 'widget_placement': 'accordion', + } + }, + ], + outline_ui_events + ) + def assert_navigation_state( self, section_title, subsection_title, subsection_position, next_enabled, prev_enabled ): diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 016a98c5e6..eba5913f7b 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -3,7 +3,7 @@ Tests courseware views.py """ -from urllib import urlencode +from urllib import urlencode, quote import ddt import json import itertools @@ -191,19 +191,25 @@ class ViewsTestCase(ModuleStoreTestCase): """ Tests for views.py methods. """ + def setUp(self): super(ViewsTestCase, self).setUp() self.course = CourseFactory.create(display_name=u'teꜱᴛ course') - self.chapter = ItemFactory.create(category='chapter', parent_location=self.course.location) + self.chapter = ItemFactory.create( + category='chapter', + parent_location=self.course.location, + display_name="Chapter 1", + ) self.section = ItemFactory.create( category='sequential', parent_location=self.chapter.location, due=datetime(2013, 9, 18, 11, 30, 00), + display_name='Sequential 1', ) self.vertical = ItemFactory.create( category='vertical', parent_location=self.section.location, - display_name='Vertical 1' + display_name='Vertical 1', ) self.problem = ItemFactory.create( category='problem', @@ -213,12 +219,13 @@ class ViewsTestCase(ModuleStoreTestCase): self.section2 = ItemFactory.create( category='sequential', - parent_location=self.chapter.location + parent_location=self.chapter.location, + display_name='Sequential 2', ) self.vertical2 = ItemFactory.create( category='vertical', parent_location=self.section2.location, - display_name='Vertical 2' + display_name='Vertical 2', ) self.problem2 = ItemFactory.create( category='problem', @@ -240,6 +247,12 @@ class ViewsTestCase(ModuleStoreTestCase): self.org = u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" self.org_html = "

'+Stark/Industries+'

" + self.request = self.request_factory.get("foo") + self.request.user = self.user + + # refresh the course from the modulestore so that it has children + self.course = modulestore().get_course(self.course.id) + def test_index_success(self): response = self._verify_index_response() self.assertIn(unicode(self.problem2.location), response.content.decode("utf-8")) @@ -787,6 +800,33 @@ class ViewsTestCase(ModuleStoreTestCase): response = views.course_info(request, course_id) self.assertEqual(response.status_code, 200) + def test_accordion(self): + table_of_contents = toc_for_course( + self.request.user, + self.request, + self.course, + unicode(self.course.get_children()[0].scope_ids.usage_id), + None, + None + ) + + # removes newlines and whitespace from the returned view string + view = ''.join(render_accordion(self.request, self.course, table_of_contents['chapters']).split()) + # the course id unicode is re-encoded here because the quote function does not accept unicode + course_id = quote(unicode(self.course.id).encode("utf-8")) + + self.assertIn( + u'href="/courses/{}/courseware/Chapter_1/Sequential_1/">Sequential1

' + .format(course_id.decode("utf-8")), + view + ) + + self.assertIn( + u'href="/courses/{}/courseware/Chapter_1/Sequential_2/">Sequential2

' + .format(course_id.decode("utf-8")), + view + ) + @attr('shard_1') # setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly @@ -809,7 +849,11 @@ class BaseDueDateTests(ModuleStoreTestCase): """ course = CourseFactory.create(**course_kwargs) chapter = ItemFactory.create(category='chapter', parent_location=course.location) - section = ItemFactory.create(category='sequential', parent_location=chapter.location, due=datetime(2013, 9, 18, 11, 30, 00)) + section = ItemFactory.create( + category='sequential', + parent_location=chapter.location, + due=datetime(2013, 9, 18, 11, 30, 00) + ) vertical = ItemFactory.create(category='vertical', parent_location=section.location) ItemFactory.create(category='problem', parent_location=vertical.location) @@ -1028,7 +1072,6 @@ class ProgressPageTests(ModuleStoreTestCase): 'azU3N_8$', ] for invalid_id in invalid_student_ids: - self.assertRaises( Http404, views.progress, self.request, @@ -1117,7 +1160,7 @@ class ProgressPageTests(ModuleStoreTestCase): # Enable certificate generation for this course certs_api.set_cert_generation_enabled(self.course.id, True) - #course certificate configurations + # Course certificate configurations certificates = [ { 'id': 1, @@ -1324,7 +1367,7 @@ class GenerateUserCertTests(ModuleStoreTestCase): resp = self.client.post(self.url) self.assertEqual(resp.status_code, 200) - #Verify Google Analytics event fired after generating certificate + # Verify Google Analytics event fired after generating certificate mock_tracker.track.assert_called_once_with( # pylint: disable=no-member self.student.id, # pylint: disable=no-member 'edx.bi.user.certificate.generate', @@ -1335,8 +1378,7 @@ class GenerateUserCertTests(ModuleStoreTestCase): context={ 'ip': '127.0.0.1', - 'Google Analytics': - {'clientId': None} + 'Google Analytics': {'clientId': None} } ) mock_tracker.reset_mock() @@ -1521,6 +1563,7 @@ class TestIndexViewWithGating(ModuleStoreTestCase, MilestonesTestCaseMixin): """ Test the index view for a course with gated content """ + def setUp(self): """ Set up the initial test data @@ -1574,6 +1617,7 @@ class TestRenderXBlock(RenderXBlockTestMixin, ModuleStoreTestCase): This class overrides the get_response method, which is used by the tests defined in RenderXBlockTestMixin. """ + def setUp(self): reload_django_url_config() super(TestRenderXBlock, self).setUp() diff --git a/lms/envs/common.py b/lms/envs/common.py index d2a64e9532..bf87fdc0f6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1664,6 +1664,7 @@ REQUIRE_JS_PATH_OVERRIDES = { 'moment': 'js/vendor/moment.min.js', 'jquery.url': 'js/vendor/url.min.js', 'js/courseware/course_home_events': 'js/courseware/course_home_events.js', + 'js/courseware/accordion_events': 'js/courseware/accordion_events.js', 'js/courseware/toggle_element_visibility': 'js/courseware/toggle_element_visibility.js', 'js/student_account/logistration_factory': 'js/student_account/logistration_factory.js', 'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js', diff --git a/lms/static/js/courseware/accordion_events.js b/lms/static/js/courseware/accordion_events.js new file mode 100644 index 0000000000..98c68cc5d6 --- /dev/null +++ b/lms/static/js/courseware/accordion_events.js @@ -0,0 +1,20 @@ +;(function(define) { + 'use strict'; + + define(['jquery', 'logger'], function ($, Logger) { + return function () { + $(".accordion-nav").click(function(event) { + Logger.log( + "edx.ui.lms.outline.selected", + { + name: "edx.ui.lms.outline.selected", + event_type: "edx.ui.lms.outline.selected", + current_url: window.location.href, + target_url: event.currentTarget.href, + target_name: $(this).find("p.accordion-display-name").text(), + widget_placement: "accordion" + }); + }); + }; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/templates/courseware/accordion.html b/lms/templates/courseware/accordion.html index 921fee4e4d..0f78cb0365 100644 --- a/lms/templates/courseware/accordion.html +++ b/lms/templates/courseware/accordion.html @@ -1,4 +1,5 @@ <%page expression_filter="h"/> +<%namespace name='static' file='../static_content.html'/> <%! from django.core.urlresolvers import reverse from util.date_utils import get_time_display @@ -26,8 +27,8 @@ else: