Commit 1122c40f by cahrens

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
parent c3b6b104
......@@ -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
......
<%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">
<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="${_("{programming_language} editor").format(programming_language=mode)}"
aria-label="${aria_label}"
aria-describedby="answer_${id}"
id="input_${id}"
tabindex="0"
......@@ -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)
......
"""
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 {
......
<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')
......@@ -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
......@@ -689,6 +689,11 @@ class @Problem
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%);
......
<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>
......
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