diff --git a/common/test/acceptance/tests/lms/test_account_settings.py b/common/test/acceptance/tests/lms/test_account_settings.py index f6f935d301..ede578d60e 100644 --- a/common/test/acceptance/tests/lms/test_account_settings.py +++ b/common/test/acceptance/tests/lms/test_account_settings.py @@ -461,7 +461,7 @@ class AccountSettingsA11yTest(AccountSettingsTestMixin, WebAppTest): self.visit_account_settings_page() self.account_settings_page.a11y_audit.config.set_rules({ 'ignore': [ - 'link-href', # TODO: AC-233, AC-238 + 'link-href', # TODO: AC-233 ], }) self.account_settings_page.a11y_audit.check_for_accessibility_errors() diff --git a/common/test/acceptance/tests/lms/test_lms_dashboard.py b/common/test/acceptance/tests/lms/test_lms_dashboard.py index ab81dfd3f2..6269f3af71 100644 --- a/common/test/acceptance/tests/lms/test_lms_dashboard.py +++ b/common/test/acceptance/tests/lms/test_lms_dashboard.py @@ -232,11 +232,4 @@ class LmsDashboardA11yTest(BaseLmsDashboardTest): """ course_listings = self.dashboard_page.get_course_listings() self.assertEqual(len(course_listings), 1) - - self.dashboard_page.a11y_audit.config.set_rules({ - "ignore": [ - 'link-href', # TODO: AC-238 - ], - }) - self.dashboard_page.a11y_audit.check_for_accessibility_errors() diff --git a/lms/static/js/dashboard/dropdown.js b/lms/static/js/dashboard/dropdown.js new file mode 100644 index 0000000000..d310fb3220 --- /dev/null +++ b/lms/static/js/dashboard/dropdown.js @@ -0,0 +1,90 @@ +var edx = edx || {}; + +(function ($) { + 'use strict'; + + edx.dashboard = edx.dashboard || {}; + edx.dashboard.dropdown = {}; + + // Generate the properties object to be passed along with business intelligence events. + edx.dashboard.dropdown.toggleCourseActionsDropdownMenu = function (event) { + // define variables for code legibility + var dashboardIndex = $(event.currentTarget).data().dashboardIndex, + dropdown = $('#actions-dropdown-' + dashboardIndex), + dropdownButton = $('#actions-dropdown-link-' + dashboardIndex), + ariaExpandedState = (dropdownButton.attr('aria-expanded') === 'true'), + menuItems = dropdown.find('a'); + + var catchKeyPress = function(object, event) { + // get currently focused item + var focusedItem = $(':focus'); + + // get the index of the currently focused item + var focusedItemIndex = menuItems.index(focusedItem); + + // var to store next focused item index + var itemToFocusIndex; + + // if space or escape key pressed + if ( event.which === 32 || event.which === 27) { + dropdownButton.click(); + event.preventDefault(); + } + + // if up arrow key pressed or shift+tab + else if (event.which === 38 || (event.which === 9 && event.shiftKey)) { + // if first item go to last + if (focusedItemIndex === 0 || focusedItemIndex === -1) { + menuItems.last().focus(); + } else { + itemToFocusIndex = focusedItemIndex - 1; + menuItems.get(itemToFocusIndex).focus(); + } + event.preventDefault(); + } + + // if down arrow key pressed or tab key + else if (event.which === 40 || event.which === 9) { + // if last item go to first + if (focusedItemIndex === menuItems.length - 1 || focusedItemIndex === -1) { + menuItems.first().focus(); + } else { + itemToFocusIndex = focusedItemIndex + 1; + menuItems.get(itemToFocusIndex).focus(); + } + event.preventDefault(); + } + }; + + // Toggle the visibility control for the selected element and set the focus + dropdown.toggleClass('is-visible'); + if (dropdown.hasClass('is-visible')) { + dropdown.attr('tabindex', -1); + dropdown.focus(); + } else { + dropdown.removeAttr('tabindex'); + dropdownButton.focus(); + } + + // Inform the ARIA framework that the dropdown has been expanded + dropdownButton.attr('aria-expanded', !ariaExpandedState); + + //catch keypresses when inside dropdownMenu (we want to catch spacebar; + // escape; up arrow or shift+tab; and down arrow or tab) + dropdown.on('keydown', function(event){ + catchKeyPress($(this), event); + }); + }; + + edx.dashboard.dropdown.bindToggleButtons = function() { + $('.action-more').bind( + 'click', + edx.dashboard.dropdown.toggleCourseActionsDropdownMenu + ); + }; + + $(document).ready(function() { + edx.dashboard.dropdown.bindToggleButtons(); + }); + +})(jQuery); diff --git a/lms/static/js/dashboard/legacy.js b/lms/static/js/dashboard/legacy.js index 050f2a493f..5438475a84 100644 --- a/lms/static/js/dashboard/legacy.js +++ b/lms/static/js/dashboard/legacy.js @@ -37,8 +37,6 @@ notifications.focus(); } - $('.action-more').bind('click', toggleCourseActionsDropdown); - // Track clicks of the upgrade button. The `trackLink` method is a helper that makes // a `track` call whenever a bound link is clicked. Usually the page would change before // `track` had time to execute; `trackLink` inserts a small timeout to give the `track` @@ -79,33 +77,6 @@ return properties; } - function toggleCourseActionsDropdownInternal(element) { - var dashboard_index = element.data('dashboard-index'); - - // Toggle the visibility control for the selected element and set the focus - var dropdown_selector = 'div#actions-dropdown-' + dashboard_index; - var dropdown = $(dropdown_selector); - dropdown.toggleClass('is-visible'); - if (dropdown.hasClass('is-visible')) { - dropdown.attr('tabindex', -1); - } else { - dropdown.removeAttr('tabindex'); - } - - // Inform the ARIA framework that the dropdown has been expanded - var anchor_selector = 'a#actions-dropdown-link-' + dashboard_index; - var anchor = $(anchor_selector); - var aria_expanded_state = (anchor.attr('aria-expanded') === 'true'); - anchor.attr('aria-expanded', !aria_expanded_state); - } - - function toggleCourseActionsDropdown(event) { - toggleCourseActionsDropdownInternal($(this)); - - // Suppress the actual click event from the browser - event.preventDefault(); - } - $("#failed-verification-button-dismiss").click(function() { $.ajax({ url: urls.verifyToggleBannerFailedOff, @@ -128,7 +99,7 @@ if($(event.target).data("optout") === "False") { $("#receive_emails").prop('checked', true); } - toggleCourseActionsDropdownInternal(element); + edx.dashboard.dropdown.toggleCourseActionsDropdownMenu(event); }); $(".action-unenroll").click(function(event) { @@ -144,7 +115,7 @@ }, true)); $('#refund-info').html( element.data("refund-info") ); $("#unenroll_course_id").val( element.data("course-id") ); - toggleCourseActionsDropdownInternal(element); + edx.dashboard.dropdown.toggleCourseActionsDropdownMenu(event); }); $('#unenroll_form').on('ajax:complete', function(event, xhr) { @@ -179,7 +150,6 @@ return false; }); - $(".action-email-settings").each(function(index){ $(this).attr("id", "email-settings-" + index); // a bit of a hack, but gets the unique selector for the modal trigger diff --git a/lms/static/js/fixtures/dashboard/dashboard.html b/lms/static/js/fixtures/dashboard/dashboard.html index 07c5c00a0e..b83627523d 100644 --- a/lms/static/js/fixtures/dashboard/dashboard.html +++ b/lms/static/js/fixtures/dashboard/dashboard.html @@ -1,3 +1,7 @@ + DemoX @@ -21,12 +25,13 @@ +

XSeries Program Course

-
+
+
+ +
+
+
+
+ +

XSeries Program Course

+
+
+
+ + +
+

Introduction to Drinking Water Treatment

+
+ DelftX - + CTB3365DWx + Starts - Tuesday at 12pm UTC +
+
+
+
+
-
+
@@ -96,7 +193,7 @@ This course is 1 of 3 courses in the Water Management XSeries.

- + diff --git a/lms/static/js/spec/dashboard/dropdown_spec.js b/lms/static/js/spec/dashboard/dropdown_spec.js new file mode 100644 index 0000000000..2b9b18177c --- /dev/null +++ b/lms/static/js/spec/dashboard/dropdown_spec.js @@ -0,0 +1,87 @@ +define(['js/dashboard/dropdown', 'jquery.simulate'], + function() { + 'use strict'; + var keys = $.simulate.keyCode, + toggleButtonSelector = '#actions-dropdown-link-2', + dropdownSelector = '#actions-dropdown-2', + dropdownItemSelector = '#actions-dropdown-2 li a', + clickToggleButton = function() { + $(toggleButtonSelector).click(); + }, + verifyDropdownVisible = function() { + expect($(dropdownSelector)).toBeVisible(); + }, + verifyDropdownNotVisible = function() { + expect($(dropdownSelector)).not.toBeVisible(); + }, + waitForElementToBeFocused = function(element, desc) { + // This is being used instead of toBeFocused which is flaky + waitsFor( + function () { + return element === document.activeElement; + }, + desc + ' element to have focus', + 500 + ); + }, + openDropDownMenu = function() { + verifyDropdownNotVisible(); + clickToggleButton(); + verifyDropdownVisible(); + }, + keydown = function(keyInfo) { + $(document.activeElement).simulate("keydown", keyInfo); + }; + + describe("edx.dashboard.dropdown.toggleCourseActionsDropdownMenu", function() { + + beforeEach(function() { + loadFixtures('js/fixtures/dashboard/dashboard.html'); + window.edx.dashboard.dropdown.bindToggleButtons(); + }); + + it("Clicking the .action-more button toggles the menu", function() { + verifyDropdownNotVisible(); + clickToggleButton(); + verifyDropdownVisible(); + clickToggleButton(); + verifyDropdownNotVisible(); + }); + it("ESCAPE will close dropdown and return focus to the button", function() { + openDropDownMenu(); + keydown({ keyCode: keys.ESCAPE }); + verifyDropdownNotVisible(); + waitForElementToBeFocused($(toggleButtonSelector)[0], "button"); + }); + it("SPACE will close dropdown and return focus to the button", function() { + openDropDownMenu(); + keydown({ keyCode: keys.SPACE }); + verifyDropdownNotVisible(); + waitForElementToBeFocused($(toggleButtonSelector)[0], "button"); + }); + + describe("Focus is trapped when navigating with", function() { + it("TAB key", function() { + openDropDownMenu(); + keydown({ keyCode: keys.TAB }); + waitForElementToBeFocused($(dropdownItemSelector)[0], "first"); + }); + it("DOWN key", function() { + openDropDownMenu(); + keydown({ keyCode: keys.DOWN }); + waitForElementToBeFocused($(dropdownItemSelector)[0], "first"); + }); + it("TAB key + SHIFT key", function() { + openDropDownMenu(); + keydown({ keyCode: keys.TAB, shiftKey: true }); + waitForElementToBeFocused($(dropdownItemSelector)[1], "last"); + }); + it("UP key", function() { + openDropDownMenu(); + keydown({ keyCode: keys.UP }); + waitForElementToBeFocused($(dropdownItemSelector)[1], "last"); + }); + }); + }); + } +); diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index 3d0ae676d1..ebd345b5d2 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -317,6 +317,10 @@ exports: 'js/dashboard/donation', deps: ['jquery', 'underscore', 'gettext'] }, + 'js/dashboard/dropdown.js': { + exports: 'js/dashboard/dropdown', + deps: ['jquery'] + }, 'js/shoppingcart/shoppingcart.js': { exports: 'js/shoppingcart/shoppingcart', deps: ['jquery', 'underscore', 'gettext'] @@ -649,6 +653,7 @@ 'lms/include/js/spec/views/notification_spec.js', 'lms/include/js/spec/views/file_uploader_spec.js', 'lms/include/js/spec/dashboard/donation.js', + 'lms/include/js/spec/dashboard/dropdown_spec.js', 'lms/include/js/spec/dashboard/track_events_spec.js', 'lms/include/js/spec/groups/views/cohorts_spec.js', 'lms/include/js/spec/shoppingcart/shoppingcart_spec.js', diff --git a/lms/static/js_test.yml b/lms/static/js_test.yml index 0958b68d93..56b8574fdb 100644 --- a/lms/static/js_test.yml +++ b/lms/static/js_test.yml @@ -38,6 +38,7 @@ lib_paths: - xmodule_js/common_static/js/vendor/requirejs/text.js - xmodule_js/common_static/js/vendor/jquery.min.js - xmodule_js/common_static/js/vendor/jquery-ui.min.js + - xmodule_js/common_static/js/vendor/jquery.simulate.js - xmodule_js/common_static/js/vendor/jquery.cookie.js - xmodule_js/common_static/js/vendor/jquery.timeago.js - xmodule_js/common_static/js/vendor/flot/jquery.flot.js diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index dc76ae6444..b2bdac8fe8 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -474,6 +474,16 @@ position: relative; @include float(right); + .action-more { + @include font-size(14); + box-shadow: none; + background: $white; + background-image: none; + color: $gray; + line-height: 16px; + text-shadow: none; + } + .actions-dropdown { @extend %ui-no-list; @extend %ui-depth1; diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 1852a21db2..24b62833c8 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -178,11 +178,14 @@ from student.helpers import ( % endif % endif