Commit 84580b6f by Tim Krones Committed by GitHub

Merge pull request #15 from open-craft/itsjeyd/accessible-survey-choices

Associate survey options with corresponding question and answer
parents ade04ece d4388af5
...@@ -613,6 +613,19 @@ class SurveyBlock(PollBase): ...@@ -613,6 +613,19 @@ class SurveyBlock(PollBase):
choices = Dict(help=_("The user's answers"), scope=Scope.user_state) choices = Dict(help=_("The user's answers"), scope=Scope.user_state)
event_namespace = 'xblock.survey' event_namespace = 'xblock.survey'
def _get_block_id(self):
"""
Return ID of this Survey block.
Take into account the needs of both LMS/Studio and workbench runtimes:
- In LMS/Studio, usage_id is a UsageKey object.
- In the workbench, usage_id is a string.
"""
usage_id = self.scope_ids.usage_id
# Try accessing block ID. If usage_id does not have it, return usage_id itself:
return unicode(getattr(usage_id, 'block_id', usage_id))
def student_view(self, context=None): def student_view(self, context=None):
""" """
The primary view of the SurveyBlock, shown to students The primary view of the SurveyBlock, shown to students
...@@ -643,6 +656,8 @@ class SurveyBlock(PollBase): ...@@ -643,6 +656,8 @@ class SurveyBlock(PollBase):
'submissions_count': self.submissions_count, 'submissions_count': self.submissions_count,
'max_submissions': self.max_submissions, 'max_submissions': self.max_submissions,
'can_view_private_results': self.can_view_private_results(), 'can_view_private_results': self.can_view_private_results(),
# a11y: Transfer block ID to enable creating unique ids for questions and answers in the template
'block_id': self._get_block_id(),
}) })
return self.create_fragment( return self.create_fragment(
......
...@@ -8,13 +8,13 @@ ...@@ -8,13 +8,13 @@
<tr> <tr>
<td></td> <td></td>
{% for answer, label in answers %} {% for answer, label in answers %}
<th class="survey-answer">{{label}}</th> <th id="{{block_id}}-{{answer}}" class="survey-answer">{{label}}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
{% for key, question in questions %} {% for key, question in questions %}
<tr class="survey-row"> <tr class="survey-row">
<th class="survey-question"> <th id="{{block_id}}-{{key}}" class="survey-question">
{% if question.img %} {% if question.img %}
<div class="poll-image-td"> <div class="poll-image-td">
<img src="{{question.img}}" alt="{{question.img_alt|default_if_none:''}}"/> <img src="{{question.img}}" alt="{{question.img_alt|default_if_none:''}}"/>
...@@ -25,7 +25,11 @@ ...@@ -25,7 +25,11 @@
{% for answer, label in answers %} {% for answer, label in answers %}
<td class="survey-option"> <td class="survey-option">
<label> <label>
<input type="radio" name="{{key}}" value="{{answer}}"{% if question.choice == answer %} checked{% endif %}/> <input type="radio"
name="{{key}}"
value="{{answer}}"{% if question.choice == answer %} checked{% endif %}
aria-labelledby="{{block_id}}-{{key}} {{block_id}}-{{answer}}"
/>
<span class="sr">{{label}}</span> <span class="sr">{{label}}</span>
</label> </label>
</td> </td>
......
...@@ -44,7 +44,7 @@ def package_data(pkg, roots): ...@@ -44,7 +44,7 @@ def package_data(pkg, roots):
setup( setup(
name='xblock-poll', name='xblock-poll',
version='1.1', version='1.2',
description='An XBlock for polling users.', description='An XBlock for polling users.',
packages=[ packages=[
'poll', 'poll',
......
...@@ -24,8 +24,12 @@ ...@@ -24,8 +24,12 @@
Tests a realistic, configured Poll to make sure that everything works as it Tests a realistic, configured Poll to make sure that everything works as it
should. should.
""" """
import itertools
from .base_test import PollBaseTest from .base_test import PollBaseTest
ANSWER_SELECTOR = 'label.poll-answer-text' ANSWER_SELECTOR = 'label.poll-answer-text'
...@@ -149,6 +153,28 @@ class TestSurveyFunctions(PollBaseTest): ...@@ -149,6 +153,28 @@ class TestSurveyFunctions(PollBaseTest):
submit_button = self.get_submit() submit_button = self.get_submit()
self.assertFalse(submit_button.is_enabled()) self.assertFalse(submit_button.is_enabled())
def test_survey_options_a11y(self):
"""
Checks if radio buttons representing survey options are linked to corresponding question and answer.
This is to ensure that screen reader users can be certain that a given radio button
is tied to a specific question and answer.
"""
self.go_to_page('Survey Functions')
questions = self.browser.find_elements_by_css_selector('.survey-question')
answers = self.browser.find_elements_by_css_selector('.survey-answer')
question_ids = [question.get_attribute('id') for question in questions]
answer_ids = [answer.get_attribute('id') for answer in answers]
id_pairs = [
"{question_id} {answer_id}".format(question_id=question_id, answer_id=answer_id)
for question_id, answer_id in itertools.product(question_ids, answer_ids)
]
options = self.browser.find_elements_by_css_selector('.survey-option input')
self.assertEqual(len(options), len(id_pairs))
for option in options:
labelledby = option.get_attribute('aria-labelledby')
self.assertIn(labelledby, id_pairs)
def fill_survey(self, assert_submit=False): def fill_survey(self, assert_submit=False):
""" """
Fills out the survey. Optionally checks if the submit button is Fills out the survey. Optionally checks if the submit button is
......
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