Moving ignore rules to relevant classes instead general rule.
Also replaces section with div, updates short_id, and makes textbox accessible. TNL-5576, TNL-5575, TNL-5574, TNL-5671, TNL-5376
This commit is contained in:
@@ -46,6 +46,7 @@ ACCESSIBLE_CAPA_INPUT_TYPES = [
|
||||
'optioninput',
|
||||
'textline',
|
||||
'formulaequationinput',
|
||||
'textbox',
|
||||
]
|
||||
|
||||
# these get captured as student responses
|
||||
|
||||
@@ -818,8 +818,17 @@ class CodeInput(InputTypeBase):
|
||||
self.setup_code_response_rendering()
|
||||
|
||||
def _extra_context(self):
|
||||
"""Defined queue_len, add it """
|
||||
return {'queue_len': self.queue_len, }
|
||||
"""
|
||||
Define queue_len, arial_label and code mirror exit message context variables
|
||||
"""
|
||||
_ = self.capa_system.i18n.ugettext
|
||||
return {
|
||||
'queue_len': self.queue_len,
|
||||
'aria_label': _('{programming_language} editor').format(
|
||||
programming_language=self.loaded_attributes.get('mode')
|
||||
),
|
||||
'code_mirror_exit_message': _('Press ESC then TAB or click outside of the code editor to exit')
|
||||
}
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -253,23 +253,26 @@ class LoncapaResponse(object):
|
||||
"""
|
||||
_ = self.capa_system.i18n.ugettext
|
||||
|
||||
# get responsetype index to make responsetype label
|
||||
response_index = self.xml.attrib['id'].split('_')[-1]
|
||||
# response_id = problem_id + response index
|
||||
response_id = self.xml.attrib['id']
|
||||
|
||||
response_index = response_id.split('_')[-1]
|
||||
# Translators: index here could be 1,2,3 and so on
|
||||
response_label = _(u'Question {index}').format(index=response_index)
|
||||
|
||||
# wrap the content inside a section
|
||||
tree = etree.Element('section')
|
||||
tree = etree.Element('div')
|
||||
tree.set('class', 'wrapper-problem-response')
|
||||
tree.set('tabindex', '-1')
|
||||
tree.set('aria-label', response_label)
|
||||
tree.set('role', 'group')
|
||||
|
||||
if self.xml.get('multiple_inputtypes'):
|
||||
# add <div> to wrap all inputtypes
|
||||
content = etree.SubElement(tree, 'div')
|
||||
content.set('class', 'multi-inputs-group')
|
||||
content.set('role', 'group')
|
||||
content.set('aria-labelledby', self.xml.get('id'))
|
||||
content.set('aria-labelledby', response_id)
|
||||
else:
|
||||
content = tree
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
%>
|
||||
<section id="textbox_${id}" class="capa_inputtype textbox cminput">
|
||||
<textarea rows="${rows}" cols="${cols}" name="input_${id}"
|
||||
aria-label="${_("{programming_language} editor").format(programming_language=mode)}"
|
||||
aria-describedby="answer_${id}"
|
||||
id="input_${id}"
|
||||
<div id="textbox_${id}" class="capa_inputtype textbox cminput">
|
||||
% if response_data['label']:
|
||||
<label class="problem-group-label" for="cm-textarea-${id}">${response_data['label']}</label>
|
||||
% endif
|
||||
<textarea rows="${rows}" cols="${cols}" name="input_${id}"
|
||||
aria-label="${aria_label}"
|
||||
aria-describedby="answer_${id}"
|
||||
id="input_${id}"
|
||||
tabindex="0"
|
||||
data-mode="${mode}"
|
||||
data-tabsize="${tabsize}"
|
||||
@@ -16,7 +20,10 @@ from openedx.core.djangolib.markup import HTML
|
||||
% if hidden:
|
||||
style="display:none;"
|
||||
% endif
|
||||
>${value|h}</textarea>
|
||||
>${value}</textarea>
|
||||
<span class="cm-editor-exit-message capa-message" id="cm-editor-exit-message-${id}">
|
||||
${code_mirror_exit_message}
|
||||
</span>
|
||||
|
||||
<div class="grader-status" tabindex="-1">
|
||||
<span id="status_${id}"
|
||||
@@ -41,4 +48,4 @@ from openedx.core.djangolib.markup import HTML
|
||||
<div class="external-grader-message">
|
||||
${HTML(msg)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -90,10 +90,10 @@ def mock_capa_module():
|
||||
return capa_module
|
||||
|
||||
|
||||
def new_loncapa_problem(xml, capa_system=None, seed=723, use_capa_render_template=False):
|
||||
def new_loncapa_problem(xml, problem_id='1', capa_system=None, seed=723, use_capa_render_template=False):
|
||||
"""Construct a `LoncapaProblem` suitable for unit tests."""
|
||||
render_template = capa_render_template if use_capa_render_template else None
|
||||
return LoncapaProblem(xml, id='1', seed=seed, capa_system=capa_system or test_capa_system(render_template),
|
||||
return LoncapaProblem(xml, id=problem_id, seed=seed, capa_system=capa_system or test_capa_system(render_template),
|
||||
capa_module=mock_capa_module())
|
||||
|
||||
|
||||
|
||||
@@ -473,7 +473,7 @@ class CAPAMultiInputProblemTest(unittest.TestCase):
|
||||
|
||||
# verify that only one multi input group div is present at correct path
|
||||
multi_inputs_group = html.xpath(
|
||||
'//section[@class="wrapper-problem-response"]/div[@class="multi-inputs-group"]'
|
||||
'//div[@class="wrapper-problem-response"]/div[@class="multi-inputs-group"]'
|
||||
)
|
||||
self.assertEqual(len(multi_inputs_group), 1)
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
"""
|
||||
CAPA HTML rendering tests.
|
||||
"""
|
||||
import ddt
|
||||
import unittest
|
||||
from lxml import etree
|
||||
import os
|
||||
@@ -9,7 +13,11 @@ from .response_xml_factory import StringResponseXMLFactory, CustomResponseXMLFac
|
||||
from capa.tests.helpers import test_capa_system, new_loncapa_problem
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CapaHtmlRenderTest(unittest.TestCase):
|
||||
"""
|
||||
CAPA HTML rendering tests class.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(CapaHtmlRenderTest, self).setUp()
|
||||
@@ -142,28 +150,28 @@ class CapaHtmlRenderTest(unittest.TestCase):
|
||||
# Mock out the template renderer
|
||||
the_system = test_capa_system()
|
||||
the_system.render_template = mock.Mock()
|
||||
the_system.render_template.return_value = "<div>Input Template Render</div>"
|
||||
the_system.render_template.return_value = "<div class='input-template-render'>Input Template Render</div>"
|
||||
|
||||
# Create the problem and render the HTML
|
||||
problem = new_loncapa_problem(xml_str, capa_system=the_system)
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
# Expect problem has been turned into a <div>
|
||||
self.assertEqual(rendered_html.tag, "div")
|
||||
|
||||
# Expect that the response has been turned into a <section> with correct attributes
|
||||
response_element = rendered_html.find("section")
|
||||
self.assertEqual(response_element.tag, "section")
|
||||
# Expect that the response has been turned into a <div> with correct attributes
|
||||
response_element = rendered_html.find('div')
|
||||
|
||||
self.assertEqual(response_element.tag, "div")
|
||||
self.assertEqual(response_element.attrib["aria-label"], "Question 1")
|
||||
|
||||
# Expect that the response <section>
|
||||
# Expect that the response div.wrapper-problem-response
|
||||
# that contains a <div> for the textline
|
||||
textline_element = response_element.find("div")
|
||||
textline_element = response_element.find('div')
|
||||
self.assertEqual(textline_element.text, 'Input Template Render')
|
||||
|
||||
# Expect a child <div> for the solution
|
||||
# with the rendered template
|
||||
solution_element = rendered_html.find("div")
|
||||
solution_element = rendered_html.xpath('//div[@class="input-template-render"]')[0]
|
||||
self.assertEqual(solution_element.text, 'Input Template Render')
|
||||
|
||||
# Expect that the template renderer was called with the correct
|
||||
@@ -218,9 +226,9 @@ class CapaHtmlRenderTest(unittest.TestCase):
|
||||
"""
|
||||
problem = new_loncapa_problem(xml)
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
sections = rendered_html.findall('section')
|
||||
self.assertEqual(sections[0].attrib['aria-label'], 'Question 1')
|
||||
self.assertEqual(sections[1].attrib['aria-label'], 'Question 2')
|
||||
response_elements = rendered_html.findall('div')
|
||||
self.assertEqual(response_elements[0].attrib['aria-label'], 'Question 1')
|
||||
self.assertEqual(response_elements[1].attrib['aria-label'], 'Question 2')
|
||||
|
||||
def test_render_response_with_overall_msg(self):
|
||||
# CustomResponse script that sets an overall_message
|
||||
|
||||
@@ -1181,3 +1181,43 @@ class SchematicInputTemplateTest(TemplateTestCase):
|
||||
Verify aria-label attribute rendering.
|
||||
"""
|
||||
self.assert_label(aria_label=True)
|
||||
|
||||
|
||||
class CodeinputTemplateTest(TemplateTestCase):
|
||||
"""
|
||||
Test mako template for `<textbox>` input
|
||||
"""
|
||||
|
||||
TEMPLATE_NAME = 'codeinput.html'
|
||||
|
||||
def setUp(self):
|
||||
super(CodeinputTemplateTest, self).setUp()
|
||||
self.context = {
|
||||
'id': '1',
|
||||
'status': Status('correct'),
|
||||
'mode': 'parrot',
|
||||
'linenumbers': 'false',
|
||||
'rows': '37',
|
||||
'cols': '11',
|
||||
'tabsize': '7',
|
||||
'hidden': '',
|
||||
'msg': '',
|
||||
'value': 'print "good evening"',
|
||||
'aria_label': 'python editor',
|
||||
'code_mirror_exit_message': 'Press ESC then TAB or click outside of the code editor to exit',
|
||||
'response_data': self.RESPONSE_DATA,
|
||||
'describedby': self.DESCRIBEDBY,
|
||||
}
|
||||
|
||||
def test_label(self):
|
||||
"""
|
||||
Verify question label is rendered correctly.
|
||||
"""
|
||||
self.assert_label(xpath="//label[@class='problem-group-label']")
|
||||
|
||||
def test_editor_exit_message(self):
|
||||
"""
|
||||
Verify that editor exit message is rendered.
|
||||
"""
|
||||
xml = self.render_to_xml(self.context)
|
||||
self.assert_has_text(xml, '//span[@id="cm-editor-exit-message-1"]', self.context['code_mirror_exit_message'])
|
||||
|
||||
@@ -421,6 +421,8 @@ class CodeInputTest(unittest.TestCase):
|
||||
'hidden': '',
|
||||
'tabsize': int(tabsize),
|
||||
'queue_len': '3',
|
||||
'aria_label': '{mode} editor'.format(mode=mode),
|
||||
'code_mirror_exit_message': 'Press ESC then TAB or click outside of the code editor to exit',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
@@ -908,6 +908,12 @@ div.problem {
|
||||
}
|
||||
}
|
||||
|
||||
.capa-message {
|
||||
display: inline-block;
|
||||
color: $gray-d1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
// +Problem - Actions
|
||||
// ====================
|
||||
div.problem .action {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<div id="textbox_101" class="capa_inputtype textbox cminput">
|
||||
<label class="problem-group-label" for="cm-textarea-101">question label here</label>
|
||||
<textarea rows="40" cols="80" name="input_101"
|
||||
aria-label="python editor"
|
||||
aria-describedby="answer_101"
|
||||
id="input_101"
|
||||
tabindex="0"
|
||||
data-mode="python"
|
||||
data-tabsize="4"
|
||||
data-linenums="true"
|
||||
>write some awesome code</textarea>
|
||||
<span class="cm-editor-exit-message capa-message" id="cm-editor-exit-message-101">
|
||||
Press ESC then TAB or click outside of the code editor to exit
|
||||
</span>
|
||||
|
||||
<div class="grader-status" tabindex="-1">
|
||||
<span id="status_101" class="correct" aria-describedby="input_101">
|
||||
<span class="status sr">correct</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -834,3 +834,25 @@ describe 'Problem', ->
|
||||
expect(@problem.poll.calls.count()).toEqual(6)
|
||||
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("The grading process is still running. Refresh the page to see updates.")
|
||||
|
||||
describe 'codeinput problem', ->
|
||||
codeinputProblemHtml = readFixtures('codeinput_problem.html')
|
||||
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback html: codeinputProblemHtml
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.render(codeinputProblemHtml)
|
||||
|
||||
it 'has rendered with correct a11y info', ->
|
||||
CodeMirrorTextArea = $('textarea')[1]
|
||||
CodeMirrorTextAreaId = 'cm-textarea-101'
|
||||
|
||||
# verify that question label has correct `for` attribute value
|
||||
expect($('.problem-group-label').attr('for')).toEqual(CodeMirrorTextAreaId)
|
||||
|
||||
# verify that codemirror textarea has correct `id` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('id')).toEqual(CodeMirrorTextAreaId)
|
||||
|
||||
# verify that codemirror textarea has correct `aria-describedby` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('aria-describedby')).toEqual('cm-editor-exit-message-101 status_101')
|
||||
|
||||
@@ -397,7 +397,7 @@ class @Problem
|
||||
labeled_status.push($(element).text())
|
||||
|
||||
return labeled_status
|
||||
|
||||
|
||||
reset: =>
|
||||
@disableAllButtonsWhileRunning @reset_internal, false
|
||||
|
||||
@@ -671,7 +671,7 @@ class @Problem
|
||||
mode = element.data("mode")
|
||||
linenumbers = element.data("linenums")
|
||||
spaces = Array(parseInt(tabsize) + 1).join(" ")
|
||||
CodeMirror.fromTextArea element[0], {
|
||||
CodeMirrorEditor = CodeMirror.fromTextArea element[0], {
|
||||
lineNumbers: linenumbers
|
||||
indentUnit: tabsize
|
||||
tabSize: tabsize
|
||||
@@ -688,7 +688,12 @@ class @Problem
|
||||
cm.replaceSelection(spaces, "end")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
id = element.attr("id").replace(/^input_/, "")
|
||||
CodeMirrorTextArea = CodeMirrorEditor.getInputField()
|
||||
CodeMirrorTextArea.setAttribute("id", "cm-textarea-#{id}")
|
||||
CodeMirrorTextArea.setAttribute("aria-describedby", "cm-editor-exit-message-#{id} status_#{id}")
|
||||
return CodeMirrorEditor
|
||||
|
||||
inputtypeShowAnswerMethods:
|
||||
choicegroup: (element, display, answers) =>
|
||||
|
||||
@@ -79,7 +79,7 @@ class StaffDebugPage(PageObject):
|
||||
url = None
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='section.staff-modal').present
|
||||
return self.q(css='.staff-modal').present
|
||||
|
||||
def reset_attempts(self, user=None):
|
||||
"""
|
||||
|
||||
@@ -658,7 +658,7 @@ class CAPAProblemA11yBaseTestMixin(object):
|
||||
|
||||
# Set the scope to the problem question
|
||||
problem_page.a11y_audit.config.set_scope(
|
||||
include=['section.wrapper-problem-response']
|
||||
include=['.wrapper-problem-response']
|
||||
)
|
||||
|
||||
# Run the accessibility audit.
|
||||
|
||||
@@ -371,15 +371,6 @@ class ProblemTypeTestMixin(object):
|
||||
self.problem_page.a11y_audit.config.set_scope(
|
||||
include=['div#seq_content'])
|
||||
|
||||
self.problem_page.a11y_audit.config.set_rules({
|
||||
"ignore": [
|
||||
'checkboxgroup', # TODO: AC-491
|
||||
'radiogroup', # TODO: AC-491
|
||||
'section', # TODO: AC-491
|
||||
'label', # TODO: AC-491
|
||||
]
|
||||
})
|
||||
|
||||
# Run the accessibility audit.
|
||||
self.problem_page.a11y_audit.check_for_accessibility_errors()
|
||||
|
||||
@@ -422,6 +413,12 @@ class AnnotationProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
|
||||
"""
|
||||
super(AnnotationProblemTypeTest, self).setUp(*args, **kwargs)
|
||||
|
||||
self.problem_page.a11y_audit.config.set_rules({
|
||||
"ignore": [
|
||||
'label', # TODO: AC-491
|
||||
]
|
||||
})
|
||||
|
||||
def answer_problem(self, correctness):
|
||||
"""
|
||||
Answer annotation problem.
|
||||
@@ -939,6 +936,14 @@ class RadioTextProblemTypeTest(ChoiceTextProbelmTypeTestBase, ProblemTypeTestMix
|
||||
"""
|
||||
super(RadioTextProblemTypeTest, self).setUp(*args, **kwargs)
|
||||
|
||||
self.problem_page.a11y_audit.config.set_rules({
|
||||
"ignore": [
|
||||
'radiogroup', # TODO: AC-491
|
||||
'label', # TODO: AC-491
|
||||
'section', # TODO: AC-491
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
class CheckboxTextProblemTypeTest(ChoiceTextProbelmTypeTestBase, ProblemTypeTestMixin):
|
||||
"""
|
||||
@@ -966,6 +971,14 @@ class CheckboxTextProblemTypeTest(ChoiceTextProbelmTypeTestBase, ProblemTypeTest
|
||||
"""
|
||||
super(CheckboxTextProblemTypeTest, self).setUp(*args, **kwargs)
|
||||
|
||||
self.problem_page.a11y_audit.config.set_rules({
|
||||
"ignore": [
|
||||
'checkboxgroup', # TODO: AC-491
|
||||
'label', # TODO: AC-491
|
||||
'section', # TODO: AC-491
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
class ImageProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
|
||||
"""
|
||||
|
||||
@@ -560,7 +560,7 @@ html.video-fullscreen {
|
||||
}
|
||||
}
|
||||
|
||||
section.xqa-modal, section.staff-modal, section.history-modal {
|
||||
.xqa-modal, .staff-modal, .history-modal {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
left: left(20%);
|
||||
|
||||
@@ -1 +1 @@
|
||||
<div id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}" data-progress_status="${progress_status}" data-progress_detail="${progress_detail}" data-content="${content | h}" data-graded="${graded}"></div>
|
||||
<div id="problem_${element_id}" class="problems-wrapper" role="group" aria-labelledby="${element_id}-problem-title" data-problem-id="${id}" data-url="${ajax_url}" data-progress_status="${progress_status}" data-progress_detail="${progress_detail}" data-content="${content | h}" data-graded="${graded}"></div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
${'' if notification_name == 'submit' else 'is-hidden' }"
|
||||
tabindex="-1">
|
||||
<span class="icon fa ${notification_icon}" aria-hidden="true"></span>
|
||||
<span class="notification-message" aria-describedby="${ id }-problem-title">${notification_message}
|
||||
<span class="notification-message" aria-describedby="${ short_id }-problem-title">${notification_message}
|
||||
</span>
|
||||
<div class="notification-btn-wrapper">
|
||||
% if notification_name is 'hint':
|
||||
|
||||
@@ -31,7 +31,7 @@ ${block_content}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<section aria-hidden="true" role="dialog" tabindex="-1" id="${element_id}_xqa-modal" class="modal xqa-modal">
|
||||
<div aria-hidden="true" role="dialog" tabindex="-1" id="${element_id}_xqa-modal" class="modal xqa-modal">
|
||||
<div class="inner-wrapper">
|
||||
<header>
|
||||
<h2>${_("{platform_name} Content Quality Assessment").format(platform_name=settings.PLATFORM_NAME)}</h2>
|
||||
@@ -51,9 +51,9 @@ ${block_content}
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section aria-hidden="true" role="dialog" tabindex="-1" class="modal staff-modal" id="${element_id}_debug" >
|
||||
<div aria-hidden="true" role="dialog" tabindex="-1" class="modal staff-modal" id="${element_id}_debug" >
|
||||
<div class="inner-wrapper">
|
||||
<header>
|
||||
<h2>${_('Staff Debug')}</h2>
|
||||
@@ -106,9 +106,9 @@ category = ${category | h}
|
||||
<div id="histogram_${element_id}" class="histogram" data-histogram="${histogram}"></div>
|
||||
%endif
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section aria-hidden="true" role="dialog" tabindex="-1" class="modal history-modal" id="${element_id}_history">
|
||||
<div aria-hidden="true" role="dialog" tabindex="-1" class="modal history-modal" id="${element_id}_history">
|
||||
<div class="inner-wrapper">
|
||||
<header>
|
||||
<h2>${_("Submission History Viewer")}</h2>
|
||||
@@ -125,7 +125,7 @@ category = ${category | h}
|
||||
<div id="${element_id}_history_text" class="staff_info" style="display:block">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="${element_id}_setup"></div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user