Commit c773d9fa by Matjaz Gregoric Committed by GitHub

Merge pull request #111 from open-craft/mtyaka/usability-improvements

Usability improvements for screen reader users
parents 8cb4774d d087caf3
......@@ -671,15 +671,37 @@
padding: 7px;
background-color: #e5e5e5;
text-align: left;
direction: ltr;
z-index: 1500;
}
.rtl .xblock--drag-and-drop .modal-window {
transform: translate(50%, -50%);
}
.xblock--drag-and-drop .modal-dismiss-button {
font-size: 24px;
position: absolute;
top: 3px;
right: 3px;
padding: 5px 8px;
}
.rtl .xblock--drag-and-drop .modal-dismiss-button {
right: inherit;
left: 3px;
}
.xblock--drag-and-drop .modal-header h2 {
height: 30px;
line-height: 30px;
margin-bottom: 5px;
}
.xblock--drag-and-drop .modal-content {
border-radius: 5px;
background-color: #ffffff;
margin-bottom: 5px;
padding: 5px;
padding: 8px;
}
.xblock--drag-and-drop .modal-content li {
......
......@@ -527,7 +527,11 @@ msgid ""
msgstr ""
#: public/js/drag_and_drop.js
msgid "OK"
msgid "Close"
msgstr ""
#: public/js/drag_and_drop.js
msgid "Go to Beginning"
msgstr ""
#: public/js/drag_and_drop.js
......
......@@ -627,8 +627,12 @@ msgstr ""
"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ є#"
#: public/js/drag_and_drop.js
msgid "OK"
msgstr "ÖK Ⱡ'σя#"
msgid "Close"
msgstr "Çlösé Ⱡ'σя#"
#: public/js/drag_and_drop.js
msgid "Go to Beginning"
msgstr "Gö tö Bégïnnïng Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
#: public/js/drag_and_drop.js
msgid "Problem"
......
......@@ -124,6 +124,9 @@ class BaseIntegrationTest(SeleniumBaseTest):
def _get_keyboard_help_dialog(self):
return self._page.find_element_by_css_selector(".keyboard-help-dialog")
def _get_go_to_beginning_button(self):
return self._page.find_element_by_css_selector('.go-to-beginning-button')
def _get_reset_button(self):
return self._page.find_element_by_css_selector('.reset-button')
......@@ -152,6 +155,14 @@ class BaseIntegrationTest(SeleniumBaseTest):
query = 'return $("{selector}").get(0).style.{style}'
return self.browser.execute_script(query.format(selector=selector, style=style))
def assertFocused(self, element):
focused_element = self.browser.switch_to.active_element
self.assertTrue(element == focused_element, 'expected element to have focus')
def assertNotFocused(self, element):
focused_element = self.browser.switch_to.active_element
self.assertTrue(element != focused_element, 'expected element to not have focus')
@staticmethod
def get_element_html(element):
return element.get_attribute('innerHTML').strip()
......
......@@ -63,7 +63,7 @@ class EventsFiredTest(DefaultDataTestMixin, ParameterizedTestsMixin, BaseEventsT
{
'name': 'edx.drag_and_drop_v2.feedback.closed',
'data': {
'manually': False,
'manually': True,
'content': ITEM_CORRECT_FEEDBACK.format(zone=TOP_ZONE_TITLE),
'truncated': False,
},
......
......@@ -27,6 +27,29 @@ ITEM_DRAG_KEYBOARD_KEYS = (None, Keys.RETURN, Keys.CONTROL+'m')
class ParameterizedTestsMixin(object):
def _test_popup_focus_and_close(self, popup, action_key):
dismiss_popup_button = popup.find_element_by_css_selector('.close-feedback-popup-button')
self.assertFocused(dismiss_popup_button)
# Assert focus is trapped - trying to tab out of the popup does not work, focus remains on the close button.
ActionChains(self.browser).send_keys(Keys.TAB).perform()
self.assertFocused(dismiss_popup_button)
# Close the popup now.
if action_key:
ActionChains(self.browser).send_keys(Keys.RETURN).perform()
else:
dismiss_popup_button.click()
self.assertFalse(popup.is_displayed())
# Assert focus moves to first enabled button in item bank after closing the popup.
focusable_items_in_bank = [item for item in self._get_items() if item.get_attribute('tabindex') == '0']
if len(focusable_items_in_bank) > 0:
self.assertFocused(focusable_items_in_bank[0])
def _test_next_tab_goes_to_go_to_beginning_button(self):
go_to_beginning_button = self._get_go_to_beginning_button()
self.assertNotFocused(go_to_beginning_button)
ActionChains(self.browser).send_keys(Keys.TAB).perform()
self.assertFocused(go_to_beginning_button)
def parameterized_item_positive_feedback_on_good_move(
self, items_map, scroll_down=100, action_key=None, assessment_mode=False
):
......@@ -44,10 +67,14 @@ class ParameterizedTestsMixin(object):
if assessment_mode:
self.assertEqual(feedback_popup_html, '')
self.assertFalse(popup.is_displayed())
if action_key:
# Next TAB keypress should move focus to "Go to Beginning button"
self._test_next_tab_goes_to_go_to_beginning_button()
else:
self.assertEqual(feedback_popup_html, "<p>{}</p>".format(definition.feedback_positive))
self.assert_popup_correct(popup)
self.assertTrue(popup.is_displayed())
self._test_popup_focus_and_close(popup, action_key)
def parameterized_item_negative_feedback_on_bad_move(
self, items_map, all_zones, scroll_down=100, action_key=None, assessment_mode=False
......@@ -75,11 +102,14 @@ class ParameterizedTestsMixin(object):
self.assertEqual(feedback_popup_html, '')
self.assertFalse(popup.is_displayed())
self.assert_placed_item(definition.item_id, zone_title, assessment_mode=True)
if action_key:
self._test_next_tab_goes_to_go_to_beginning_button()
else:
self.wait_until_html_in(definition.feedback_negative, feedback_popup_content)
self.assert_popup_incorrect(popup)
self.assertTrue(popup.is_displayed())
self.assert_reverted_item(definition.item_id)
self._test_popup_focus_and_close(popup, action_key)
def parameterized_move_items_between_zones(self, items_map, all_zones, scroll_down=100, action_key=None):
# Scroll drop zones into view to make sure Selenium can successfully drop items
......@@ -90,6 +120,8 @@ class ParameterizedTestsMixin(object):
for zone_id, zone_title in all_zones:
self.place_item(item_key, zone_id, action_key)
self.assert_placed_item(item_key, zone_title, assessment_mode=True)
if action_key:
self._test_next_tab_goes_to_go_to_beginning_button()
# Finally, move them all back to the bank.
self.place_item(item_key, None, action_key)
self.assert_reverted_item(item_key)
......
......@@ -2,6 +2,7 @@
from ddt import ddt, unpack, data
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys
from xblockutils.resources import ResourceLoader
......@@ -210,6 +211,33 @@ class TestDragAndDropRender(BaseIntegrationTest):
self.assertEqual(popup_content.text, "")
self.assertEqual(popup_wrapper.get_attribute('aria-live'), 'polite')
@data(None, Keys.RETURN)
def test_go_to_beginning_button(self, action_key):
self.load_scenario()
self.scroll_down(250)
button = self._get_go_to_beginning_button()
# Button is only visible to screen reader users by default.
self.assertIn('sr', button.get_attribute('class').split())
# Set focus to the element. We have to use execute_script here because while TAB-ing
# to the button to make it the active element works in selenium, the focus event is not
# emitted unless the Firefox window controlled by selenium is the focused window, which
# usually is not the case when running integration tests.
# See: https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/7346
self.browser.execute_script('$("button.go-to-beginning-button").focus()')
self.assertFocused(button)
# Button should be visible when focused.
self.assertNotIn('sr', button.get_attribute('class').split())
# Click/activate the button to move focus to the top.
if action_key:
button.send_keys(action_key)
else:
button.click()
first_focusable_item = self._get_items()[0]
self.assertFocused(first_focusable_item)
# Button should only be visible to screen readers again.
self.assertIn('sr', button.get_attribute('class').split())
def test_keyboard_help(self):
self.load_scenario()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment