diff --git a/lms/static/coffee/fixtures/accordion.html b/lms/static/coffee/fixtures/accordion.html deleted file mode 100644 index 5d5d7f146b..0000000000 --- a/lms/static/coffee/fixtures/accordion.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
- close -
-
- -
-
\ No newline at end of file diff --git a/lms/static/coffee/spec/courseware_spec.coffee b/lms/static/coffee/spec/courseware_spec.coffee index c6b0c605c7..129b4308a6 100644 --- a/lms/static/coffee/spec/courseware_spec.coffee +++ b/lms/static/coffee/spec/courseware_spec.coffee @@ -1,10 +1,5 @@ describe 'Courseware', -> describe 'start', -> - it 'create the navigation', -> - spyOn(window, 'Navigation') - Courseware.start() - expect(window.Navigation).toHaveBeenCalled() - it 'binds the Logger', -> spyOn(Logger, 'bind') Courseware.start() diff --git a/lms/static/coffee/spec/navigation_spec.coffee b/lms/static/coffee/spec/navigation_spec.coffee deleted file mode 100644 index 4970c545eb..0000000000 --- a/lms/static/coffee/spec/navigation_spec.coffee +++ /dev/null @@ -1,97 +0,0 @@ -describe 'Navigation', -> - beforeEach -> - loadFixtures 'coffee/fixtures/accordion.html' - @navigation = new Navigation - - describe 'constructor', -> - describe 'when the #accordion exists', -> - describe 'when there is an active section', -> - beforeEach -> - spyOn $.fn, 'accordion' - $('#accordion').append('
') - new Navigation - - it 'activate the accordion with correct active section', -> - expect($('#accordion').accordion).toHaveBeenCalledWith - active: 1 - header: 'h3' - autoHeight: false - heightStyle: 'content' - - describe 'when there is no active section', -> - beforeEach -> - spyOn $.fn, 'accordion' - $('#accordion').append('
') - new Navigation - - it 'activate the accordian with no section as active', -> - expect($('#accordion').accordion).toHaveBeenCalledWith - active: 0 - header: 'h3' - autoHeight: false - heightStyle: 'content' - - it 'binds the accordionchange event', -> - expect($('#accordion')).toHandleWith 'accordionchange', @navigation.log - - it 'bind the navigation toggle', -> - expect($('#open_close_accordion a')).toHandleWith 'click', @navigation.toggle - - it 'bind the setChapter', -> - expect($('#accordion .chapter')).toHandleWith 'click', @navigation.setChapter - - describe 'when the #accordion does not exists', -> - beforeEach -> - $('#accordion').remove() - - it 'does not activate the accordion', -> - spyOn $.fn, 'accordion' - expect($('#accordion').accordion).wasNotCalled() - - describe 'toggle', -> - it 'toggle closed class on the wrapper', -> - $('.course-wrapper').removeClass('closed') - - @navigation.toggle() - expect($('.course-wrapper')).toHaveClass('closed') - - @navigation.toggle() - expect($('.course-wrapper')).not.toHaveClass('closed') - - describe 'log', -> - beforeEach -> - spyOn Logger, 'log' - - it 'submit event log', -> - @navigation.log {}, { - newHeader: - text: -> "new" - oldHeader: - text: -> "old" - } - - expect(Logger.log).toHaveBeenCalledWith 'accordion', - newheader: 'new' - oldheader: 'old' - - describe 'setChapter', -> - beforeEach -> - $('#accordion').append('
') - new Navigation - it 'Chapter opened', -> - e = jQuery.Event('click') - $('#accordion .chapter').trigger(e) - expect($('.chapter')).toHaveClass('is-open') - - it 'content active on chapter opened', -> - e = jQuery.Event('click') - $('#accordion .chapter').trigger(e) - expect($('.chapter').next('div').children('div')).toHaveClass('ui-accordion-content-active') - expect($('.ui-accordion-content-active')).toHaveAttr('aria-hidden', 'false') - - it 'focus move to first child on chapter opened', -> - spyOn($.fn, 'focus') - e = jQuery.Event('click') - $('#accordion .chapter').trigger(e) - expect($('.ui-accordion-content-active li:first-child a').focus).toHaveBeenCalled() - diff --git a/lms/static/coffee/src/courseware.coffee b/lms/static/coffee/src/courseware.coffee index 50fcb3ecec..49e27c484b 100644 --- a/lms/static/coffee/src/courseware.coffee +++ b/lms/static/coffee/src/courseware.coffee @@ -2,7 +2,6 @@ class @Courseware @prefix: '' constructor: -> - new Navigation Logger.bind() @render() diff --git a/lms/static/js/fixtures/accordion.html b/lms/static/js/fixtures/accordion.html new file mode 100644 index 0000000000..40cb03a7c0 --- /dev/null +++ b/lms/static/js/fixtures/accordion.html @@ -0,0 +1,47 @@ +
+
+ close +
+
+ + + + +
+
\ No newline at end of file diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index cb97519c78..5040318115 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -64,6 +64,7 @@ '_split': 'js/split', 'mathjax_delay_renderer': 'coffee/src/mathjax_delay_renderer', 'MathJaxProcessor': 'coffee/src/customwmd', + 'js/utils/navigation': 'js/utils/navigation', // Manually specify LMS files that are not converted to RequireJS 'history': 'js/vendor/history', @@ -803,6 +804,8 @@ 'lms/include/teams/js/spec/views/topic_teams_spec.js', 'lms/include/teams/js/spec/views/topics_spec.js', 'lms/include/teams/js/spec/views/team_profile_header_actions_spec.js' + 'lms/include/teams/js/spec/views/team_join_spec.js', + 'lms/include/js/spec/navigation_spec.js' ]); }).call(this, requirejs, define); diff --git a/lms/static/js/spec/navigation_spec.js b/lms/static/js/spec/navigation_spec.js new file mode 100644 index 0000000000..90a0173df6 --- /dev/null +++ b/lms/static/js/spec/navigation_spec.js @@ -0,0 +1,74 @@ +define(['jquery', 'js/utils/navigation'], function($) { + 'use strict'; + + describe('Course Navigation Accordion', function() { + var accordion, button, heading, chapterContent, chapterMenu; + + beforeEach(function() { + loadFixtures('js/fixtures/accordion.html'); + + accordion = $('#accordion'); + button = accordion.children('.button-chapter'); + heading = button.children('.group-heading'); + chapterContent = accordion.children('.chapter-content-container'); + chapterMenu = chapterContent.children('.chapter-menu'); + + spyOn($.fn, 'focus').andCallThrough(); + edx.util.navigation.init(); + }); + + describe('constructor', function() { + + describe('always', function() { + + it('ensures accordion is present', function() { + expect(accordion.length).toBe(1); + }); + + it('ensures aria attributes are present', function() { + expect(button).toHaveAttr({ + 'aria-pressed': 'true' + }); + + expect(chapterContent).toHaveAttr({ + 'aria-expanded': 'true' + }); + }); + + it('ensures only one active item', function() { + expect(chapterMenu.find('.active').length).toBe(1); + }); + }); + + describe('open section', function() { + + it('ensures new section is opened and previous section is closed', function() { + button.last().click(); + + expect(chapterContent.first()).toBeHidden(); + expect(chapterContent.last()).not.toBeHidden(); + + expect(button.first()).not.toHaveClass('is-open'); + expect(button.last()).toHaveClass('is-open'); + + expect(chapterContent.last().focus).toHaveBeenCalled(); + }); + + it('ensure proper aria and attrs', function() { + expect(button.last()).toHaveAttr({ + 'aria-pressed': 'false' + }); + expect(button.first()).toHaveAttr({ + 'aria-pressed': 'true' + }); + expect(chapterContent.last()).toHaveAttr({ + 'aria-expanded': 'false' + }); + expect(chapterContent.first()).toHaveAttr({ + 'aria-expanded': 'true' + }) + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/lms/static/js/utils/navigation.js b/lms/static/js/utils/navigation.js new file mode 100644 index 0000000000..71d3080361 --- /dev/null +++ b/lms/static/js/utils/navigation.js @@ -0,0 +1,89 @@ +var edx = edx || {}, + + Navigation = (function() { + + var navigation = { + + init: function() { + + if ($('#accordion').length) { + + navigation.openAccordion(); + navigation.checkForCurrent(); + navigation.listenForClick(); + } + }, + + openAccordion: function() { + $('#open_close_accordion a').click(); + $('.course-wrapper').toggleClass('closed'); + $('#accordion').show(); + }, + + checkForCurrent: function() { + var active; + + active = $('#accordion div div:has(a.active)').index('#accordion div div'); + + if (active < 0) { + active = $('#accordion group-heading.active').index('#accordion h3'); + } + + if (active < 0) { + active = 0; + } + + if (active > 0) { + $('#accordion').find('.button-chapter:eq(' + active + ')').trigger('click'); + } + }, + + listenForClick: function() { + $('#accordion').on('click', '.button-chapter', function(event) { + // close and reset all accrdions + navigation.resetAllAccordions(); + + // open this accordion and send focus + navigation.openAccordionSection(event.currentTarget); + + // assign classes and set open aria + navigation.setAriaAttrs(event.currentTarget); + }); + }, + + resetAllAccordions: function() { + $('.chapter-content-container').hide(); + $('.chapter-content-container .chapter-menu').hide(); + + $('#accordion .button-chapter').each(function(event) { + $(this).removeClass('is-open').attr('aria-pressed', 'false'); + $(this).next('.chapter-content-container').attr('aria-expanded', 'false'); + $(this).children('.group-heading').removeClass('active'); + $(this).children('.group-heading').find('.icon').addClass('fa-caret-right').removeClass('fa-caret-down'); + }); + }, + + openAccordionSection: function(section) { + $(section).next('.chapter-content-container').show().focus(); + $(section).next('.chapter-content-container').find('.chapter-menu').show(); + + navigation.setAriaAttrs(section); + }, + + setAriaAttrs: function(section) { + $(section).addClass('is-open').attr('aria-pressed', 'true'); + $(section).next('.chapter-content-container').attr('aria-expanded', 'true'); + $(section).children('.group-heading').addClass('active'); + $(section).children('.group-heading').find('.icon').removeClass('fa-caret-right').addClass('fa-caret-down'); + } + }; + + return { + init: navigation.init + }; + + })(); + + edx.util = edx.util || {}; + edx.util.navigation = Navigation; + edx.util.navigation.init(); diff --git a/lms/templates/main.html b/lms/templates/main.html index 40c62ee6f3..9bda41e64a 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -159,6 +159,7 @@ from branding import api as branding_api <%block name="js_extra"/> +