allow html inside label and descriptions
TNL-5557
This commit is contained in:
@@ -31,6 +31,8 @@ import capa.responsetypes as responsetypes
|
||||
from capa.util import contextualize_text, convert_files_to_filenames
|
||||
import capa.xqueue_interface as xqueue_interface
|
||||
from capa.safe_exec import safe_exec
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
from xmodule.stringify import stringify_children
|
||||
|
||||
|
||||
# extra things displayed after "show answers" is pressed
|
||||
@@ -926,7 +928,7 @@ class LoncapaProblem(object):
|
||||
group_label_tag.tag = 'p'
|
||||
group_label_tag.set('id', responsetype_id)
|
||||
group_label_tag.set('class', 'multi-inputs-group-label')
|
||||
group_label_tag_text = group_label_tag.text
|
||||
group_label_tag_text = stringify_children(group_label_tag)
|
||||
|
||||
for inputfield in inputfields:
|
||||
problem_data[inputfield.get('id')] = {
|
||||
@@ -938,7 +940,7 @@ class LoncapaProblem(object):
|
||||
# Extract label value from <label> tag or label attribute from inside the responsetype
|
||||
responsetype_label_tag = response.find('label')
|
||||
if responsetype_label_tag is not None:
|
||||
label = responsetype_label_tag.text
|
||||
label = stringify_children(responsetype_label_tag)
|
||||
# store <label> tag containing question text to delete
|
||||
# it later otherwise question will be rendered twice
|
||||
element_to_be_deleted = responsetype_label_tag
|
||||
@@ -950,21 +952,15 @@ class LoncapaProblem(object):
|
||||
p_tag = response.xpath('preceding-sibling::*[1][self::p]')
|
||||
|
||||
if p_tag and p_tag[0].text == inputfields[0].attrib['label']:
|
||||
label = p_tag[0].text
|
||||
|
||||
p_tag_children = list(p_tag[0])
|
||||
if len(p_tag_children) == 0:
|
||||
element_to_be_deleted = p_tag[0]
|
||||
else:
|
||||
# Delete the text from the p-tag, but leave the children.
|
||||
p_tag[0].text = ''
|
||||
label = stringify_children(p_tag[0])
|
||||
element_to_be_deleted = p_tag[0]
|
||||
else:
|
||||
# In this case the problems don't have tag or label attribute inside the responsetype
|
||||
# so we will get the first preceding label tag w.r.t to this responsetype.
|
||||
# This will take care of those multi-question problems that are not using --- in their markdown.
|
||||
label_tag = response.xpath('preceding-sibling::*[1][self::label]')
|
||||
if label_tag:
|
||||
label = label_tag[0].text
|
||||
label = stringify_children(label_tag[0])
|
||||
element_to_be_deleted = label_tag[0]
|
||||
|
||||
# delete label or p element only if inputtype is fully accessible
|
||||
@@ -978,11 +974,11 @@ class LoncapaProblem(object):
|
||||
for description in description_tags:
|
||||
descriptions[
|
||||
"description_%s_%i" % (responsetype_id, description_id)
|
||||
] = description.text
|
||||
] = HTML(stringify_children(description))
|
||||
response.remove(description)
|
||||
description_id += 1
|
||||
|
||||
problem_data[inputfields[0].get('id')] = {
|
||||
'label': label.strip() if label else '',
|
||||
'label': HTML(label.strip()) if label else '',
|
||||
'descriptions': descriptions
|
||||
}
|
||||
|
||||
@@ -322,14 +322,14 @@ class InputTypeBase(object):
|
||||
'msg': self.msg,
|
||||
'response_data': self.response_data,
|
||||
'STATIC_URL': self.capa_system.STATIC_URL,
|
||||
'describedby': '',
|
||||
'describedby_html': '',
|
||||
}
|
||||
|
||||
# Don't add aria-describedby attribute if there are no descriptions
|
||||
if self.response_data.get('descriptions'):
|
||||
description_ids = ' '.join(self.response_data.get('descriptions').keys())
|
||||
context.update(
|
||||
{'describedby': 'aria-describedby="{}"'.format(description_ids)}
|
||||
{'describedby_html': 'aria-describedby="{}"'.format(description_ids)}
|
||||
)
|
||||
|
||||
context.update(
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<%! from capa.util import remove_markup %>
|
||||
<div id="chemicalequationinput_${id}" class="chemicalequationinput">
|
||||
<div class="script_placeholder" data-src="${previewer}"/>
|
||||
|
||||
<div class="${status.classname}" id="status_${id}">
|
||||
|
||||
<input type="text" name="input_${id}" id="input_${id}" aria-label="${response_data['label']}" aria-describedby="answer_${id}" data-input-id="${id}" value="${value|h}"
|
||||
<input type="text" name="input_${id}" id="input_${id}" aria-label="${remove_markup(response_data['label'])}" aria-describedby="answer_${id}" data-input-id="${id}" value="${value|h}"
|
||||
% if size:
|
||||
size="${size}"
|
||||
% endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%! from openedx.core.djangolib.markup import HTML %>
|
||||
<%
|
||||
def is_radio_input(choice_id):
|
||||
@@ -6,7 +7,7 @@
|
||||
))
|
||||
%>
|
||||
<form class="choicegroup capa_inputtype" id="inputtype_${id}">
|
||||
<fieldset ${describedby}>
|
||||
<fieldset ${HTML(describedby_html)}>
|
||||
% if response_data['label']:
|
||||
<legend id="${id}-legend" class="response-fieldset-legend field-group-hd">${response_data['label']}</legend>
|
||||
% endif
|
||||
@@ -36,7 +37,7 @@
|
||||
% endif
|
||||
% endif
|
||||
class="${label_class}"
|
||||
${describedby}
|
||||
${HTML(describedby_html)}
|
||||
>
|
||||
<input type="${input_type}" name="input_${id}${name_array_suffix}" id="input_${id}_${choice_id}" class="field-input input-${input_type}" value="${choice_id}"
|
||||
## If the student selected this choice...
|
||||
@@ -45,7 +46,7 @@
|
||||
% elif input_type != 'radio' and choice_id in value:
|
||||
checked="true"
|
||||
% endif
|
||||
/> ${choice_label}
|
||||
/> ${HTML(choice_label)}
|
||||
|
||||
% if is_radio_input(choice_id):
|
||||
% if status in ('correct', 'partially-correct', 'incorrect') and not show_correctness == 'never':
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%! from capa.util import remove_markup %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<% element_checked = False %>
|
||||
% for choice_id, _ in choices:
|
||||
@@ -10,7 +11,7 @@
|
||||
<form class="choicetextgroup capa_inputtype" id="inputtype_${id}">
|
||||
<div class="script_placeholder" data-src="${STATIC_URL}js/capa/choicetextinput.js"/>
|
||||
|
||||
<fieldset aria-label="${response_data['label']}">
|
||||
<fieldset aria-label="${remove_markup(response_data['label'])}">
|
||||
% for choice_id, choice_description in choices:
|
||||
<% choice_id = choice_id %>
|
||||
<section id="forinput${choice_id}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
% endfor
|
||||
<input type="text" name="input_${id}" id="input_${id}"
|
||||
data-input-id="${id}" value="${value}"
|
||||
${describedby | n, decode.utf8}
|
||||
${HTML(describedby_html)}
|
||||
% if size:
|
||||
size="${size}"
|
||||
% endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%! from openedx.core.djangolib.markup import HTML %>
|
||||
<% doinline = "inline" if inline else "" %>
|
||||
|
||||
@@ -10,7 +11,7 @@
|
||||
<p class="question-description" id="${description_id}">${description_text}</p>
|
||||
% endfor
|
||||
|
||||
<select name="input_${id}" id="input_${id}" ${describedby}>
|
||||
<select name="input_${id}" id="input_${id}" ${HTML(describedby_html)}>
|
||||
<option value="option_${id}_dummy_default">${default_option_text}</option>
|
||||
% for option_id, option_description in options:
|
||||
<option value="${option_id}"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%! from capa.util import remove_markup %>
|
||||
<div>
|
||||
<div class="script_placeholder" data-src="${setup_script}"/>
|
||||
<input type="hidden"
|
||||
@@ -8,7 +9,7 @@
|
||||
analyses="${analyses}"
|
||||
name="input_${id}"
|
||||
id="input_${id}"
|
||||
aria-label="${response_data['label']}"
|
||||
aria-label="${remove_markup(response_data['label'])}"
|
||||
aria-describedby="answer_${id}"
|
||||
value="${value|h}"
|
||||
initial_value="${initial_value|h}"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
% for description_id, description_text in response_data['descriptions'].items():
|
||||
<p class="question-description" id="${description_id}">${description_text}</p>
|
||||
% endfor
|
||||
<input type="text" name="input_${id}" id="input_${id}" ${describedby | n, decode.utf8} value="${value}"
|
||||
<input type="text" name="input_${id}" id="input_${id}" ${HTML(describedby_html)} value="${value}"
|
||||
% if do_math:
|
||||
class="math"
|
||||
% endif
|
||||
|
||||
@@ -13,7 +13,12 @@ from capa.tests.helpers import new_loncapa_problem
|
||||
class CAPAProblemTest(unittest.TestCase):
|
||||
""" CAPA problem related tests"""
|
||||
|
||||
def test_label_and_description_inside_responsetype(self):
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{'question': 'Select the correct synonym of paranoid?'},
|
||||
{'question': 'Select the correct <em>synonym</em> of <strong>paranoid</strong>?'},
|
||||
)
|
||||
def test_label_and_description_inside_responsetype(self, question):
|
||||
"""
|
||||
Verify that
|
||||
* label is extracted
|
||||
@@ -25,7 +30,7 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
xml = """
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>Select the correct synonym of paranoid?</label>
|
||||
<label>{question}</label>
|
||||
<description>Only the paranoid survive.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">over-suspicious</choice>
|
||||
@@ -33,25 +38,35 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
"""
|
||||
""".format(question=question)
|
||||
problem = new_loncapa_problem(xml)
|
||||
self.assertEqual(
|
||||
problem.problem_data,
|
||||
{
|
||||
'1_2_1':
|
||||
{
|
||||
'label': 'Select the correct synonym of paranoid?',
|
||||
'label': question,
|
||||
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}
|
||||
}
|
||||
}
|
||||
)
|
||||
self.assertEqual(len(problem.tree.xpath('//label')), 0)
|
||||
|
||||
def test_legacy_problem(self):
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{
|
||||
'question': 'Once we become predictable, we become ______?',
|
||||
'label_attr': 'Once we become predictable, we become ______?'
|
||||
},
|
||||
{
|
||||
'question': 'Once we become predictable, we become ______?<img src="img/src"/>',
|
||||
'label_attr': 'Once we become predictable, we become ______?'
|
||||
},
|
||||
)
|
||||
def test_legacy_problem(self, question, label_attr):
|
||||
"""
|
||||
Verify that legacy problem is handled correctly.
|
||||
"""
|
||||
question = "Once we become predictable, we become ______?"
|
||||
xml = """
|
||||
<problem>
|
||||
<p>Be sure to check your spelling.</p>
|
||||
@@ -60,7 +75,7 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
<textline label="{}" size="40"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
""".format(question, question)
|
||||
""".format(question, label_attr)
|
||||
problem = new_loncapa_problem(xml)
|
||||
self.assertEqual(
|
||||
problem.problem_data,
|
||||
@@ -77,7 +92,18 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
0
|
||||
)
|
||||
|
||||
def test_neither_label_tag_nor_attribute(self):
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{
|
||||
'question1': 'People who say they have nothing to ____ almost always do?',
|
||||
'question2': 'Select the correct synonym of paranoid?'
|
||||
},
|
||||
{
|
||||
'question1': '<b>People</b> who say they have <mark>nothing</mark> to ____ almost always do?',
|
||||
'question2': 'Select the <sup>correct</sup> synonym of <mark>paranoid</mark>?'
|
||||
},
|
||||
)
|
||||
def test_neither_label_tag_nor_attribute(self, question1, question2):
|
||||
"""
|
||||
Verify that label is extracted correctly.
|
||||
|
||||
@@ -86,8 +112,6 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
tag and label attribute inside responsetype. But we have a label tag
|
||||
before the responsetype.
|
||||
"""
|
||||
question1 = 'People who say they have nothing to ____ almost always do?'
|
||||
question2 = 'Select the correct synonym of paranoid?'
|
||||
xml = """
|
||||
<problem>
|
||||
<p>Be sure to check your spelling.</p>
|
||||
@@ -131,17 +155,19 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
"""
|
||||
Verify that multiple descriptions are handled correctly.
|
||||
"""
|
||||
desc1 = "The problem with trying to be the <em>bad guy</em>, there's always someone <strong>worse</strong>."
|
||||
desc2 = "Anyone who looks the world as if it was a game of chess deserves to lose."
|
||||
xml = """
|
||||
<problem>
|
||||
<p>Be sure to check your spelling.</p>
|
||||
<stringresponse answer="War" type="ci">
|
||||
<label>___ requires sacrifices.</label>
|
||||
<description>The problem with trying to be the bad guy, there's always someone worse.</description>
|
||||
<description>Anyone who looks the world as if it was a game of chess deserves to lose.</description>
|
||||
<description>{}</description>
|
||||
<description>{}</description>
|
||||
<textline size="40"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
"""
|
||||
""".format(desc1, desc2)
|
||||
problem = new_loncapa_problem(xml)
|
||||
self.assertEqual(
|
||||
problem.problem_data,
|
||||
@@ -150,8 +176,8 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
{
|
||||
'label': '___ requires sacrifices.',
|
||||
'descriptions': {
|
||||
'description_1_1_1': "The problem with trying to be the bad guy, there's always someone worse.",
|
||||
'description_1_1_2': "Anyone who looks the world as if it was a game of chess deserves to lose."
|
||||
'description_1_1_1': desc1,
|
||||
'description_1_1_2': desc2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,11 +324,15 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
1
|
||||
)
|
||||
|
||||
def test_multiple_inputtypes(self):
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{'group_label': 'Choose the correct color'},
|
||||
{'group_label': 'Choose the <b>correct</b> <mark>color</mark>'},
|
||||
)
|
||||
def test_multiple_inputtypes(self, group_label):
|
||||
"""
|
||||
Verify that group label and labels for individual inputtypes are extracted correctly.
|
||||
"""
|
||||
group_label = 'Choose the correct color'
|
||||
input1_label = 'What color is the sky?'
|
||||
input2_label = 'What color are pine needles?'
|
||||
xml = """
|
||||
@@ -424,39 +454,6 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
self.assert_question_tag(question1, question2, tag='label', label_attr=False)
|
||||
self.assert_question_tag(question1, question2, tag='p', label_attr=True)
|
||||
|
||||
def test_question_tag_child_left(self):
|
||||
"""
|
||||
If the "old" question tag has children, don't delete the children when
|
||||
transforming to the new label tag.
|
||||
"""
|
||||
xml = """
|
||||
<problem>
|
||||
<p>Question<img src='img/src'/></p>
|
||||
<choiceresponse>
|
||||
<checkboxgroup label="Question">
|
||||
<choice correct="true">choice1</choice>
|
||||
<choice correct="false">choice2</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
"""
|
||||
|
||||
problem = new_loncapa_problem(xml)
|
||||
self.assertEqual(
|
||||
problem.problem_data,
|
||||
{
|
||||
'1_2_1':
|
||||
{
|
||||
'label': "Question",
|
||||
'descriptions': {}
|
||||
}
|
||||
}
|
||||
)
|
||||
# img tag is still present within the paragraph, but p text has been deleted
|
||||
self.assertEqual(len(problem.tree.xpath('//p')), 1)
|
||||
self.assertEqual(problem.tree.xpath('//p')[0].text, '')
|
||||
self.assertEqual(len(problem.tree.xpath('//p/img')), 1)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CAPAMultiInputProblemTest(unittest.TestCase):
|
||||
|
||||
@@ -186,7 +186,7 @@ class CapaHtmlRenderTest(unittest.TestCase):
|
||||
'trailing_text': '',
|
||||
'size': None,
|
||||
'response_data': {'label': '', 'descriptions': {}},
|
||||
'describedby': ''
|
||||
'describedby_html': ''
|
||||
}
|
||||
|
||||
expected_solution_context = {'id': '1_solution_1'}
|
||||
|
||||
@@ -11,6 +11,7 @@ from lxml import etree
|
||||
from mako.template import Template as MakoTemplate
|
||||
from mako import exceptions
|
||||
from capa.inputtypes import Status
|
||||
from xmodule.stringify import stringify_children
|
||||
|
||||
|
||||
class TemplateError(Exception):
|
||||
@@ -31,7 +32,12 @@ class TemplateTestCase(unittest.TestCase):
|
||||
# for example: choicegroup.html
|
||||
TEMPLATE_NAME = None
|
||||
DESCRIBEDBY = 'aria-describedby="desc-1 desc-2"'
|
||||
DESCRIPTIONS = OrderedDict([('desc-1', 'description text 1'), ('desc-2', 'description text 2')])
|
||||
DESCRIPTIONS = OrderedDict(
|
||||
[
|
||||
('desc-1', 'description text 1'),
|
||||
('desc-2', '<em>description</em> <mark>text</mark> 2')
|
||||
]
|
||||
)
|
||||
DESCRIPTION_IDS = ' '.join(DESCRIPTIONS.keys())
|
||||
RESPONSE_DATA = {
|
||||
'label': 'question text 101',
|
||||
@@ -48,7 +54,7 @@ class TemplateTestCase(unittest.TestCase):
|
||||
'templates',
|
||||
self.TEMPLATE_NAME)
|
||||
with open(self.template_path) as f:
|
||||
self.template = MakoTemplate(f.read())
|
||||
self.template = MakoTemplate(f.read(), default_filters=['decode.utf8'])
|
||||
|
||||
self.context = {}
|
||||
|
||||
@@ -118,27 +124,24 @@ class TemplateTestCase(unittest.TestCase):
|
||||
self.assertGreater(len(element_list), 0, "Could not find element at '%s'" % str(xpath))
|
||||
|
||||
if exact:
|
||||
self.assertEqual(text, element_list[0].text)
|
||||
self.assertEqual(text, element_list[0].text.strip())
|
||||
else:
|
||||
self.assertIn(text, element_list[0].text)
|
||||
self.assertIn(text, element_list[0].text.strip())
|
||||
|
||||
def assert_description(self, describedby_xpaths, descriptions=True):
|
||||
def assert_description(self, describedby_xpaths):
|
||||
"""
|
||||
Verify that descriptions information is correct.
|
||||
|
||||
Arguments:
|
||||
describedby_xpaths (list): list of xpaths to check aria-describedby attribute
|
||||
descriptions (bool): tells whether we need to check description <p> tags
|
||||
"""
|
||||
xml = self.render_to_xml(self.context)
|
||||
|
||||
# TODO! This check should be removed once description <p> tags are added into all templates.
|
||||
if descriptions:
|
||||
# Verify that each description <p> tag has correct id, text and order
|
||||
descriptions = OrderedDict(
|
||||
(tag.get('id'), tag.text) for tag in xml.xpath('//p[@class="question-description"]')
|
||||
)
|
||||
self.assertEqual(self.DESCRIPTIONS, descriptions)
|
||||
# Verify that each description <p> tag has correct id, text and order
|
||||
descriptions = OrderedDict(
|
||||
(tag.get('id'), stringify_children(tag)) for tag in xml.xpath('//p[@class="question-description"]')
|
||||
)
|
||||
self.assertEqual(self.DESCRIPTIONS, descriptions)
|
||||
|
||||
# for each xpath verify that description_ids are set correctly
|
||||
for describedby_xpath in describedby_xpaths:
|
||||
@@ -157,7 +160,7 @@ class TemplateTestCase(unittest.TestCase):
|
||||
Arguments:
|
||||
describedby_xpaths (list): list of xpaths to check aria-describedby attribute
|
||||
"""
|
||||
self.context['describedby'] = ''
|
||||
self.context['describedby_html'] = ''
|
||||
xml = self.render_to_xml(self.context)
|
||||
|
||||
# for each xpath verify that description_ids are set correctly
|
||||
@@ -199,6 +202,44 @@ class TemplateTestCase(unittest.TestCase):
|
||||
self.context['status'].display_tooltip
|
||||
)
|
||||
|
||||
def assert_label(self, xpath=None, aria_label=False):
|
||||
"""
|
||||
Verify label is rendered correctly.
|
||||
|
||||
Arguments:
|
||||
xpath (str): xpath expression for label element
|
||||
aria_label (bool): check aria-label attribute value
|
||||
"""
|
||||
labels = [
|
||||
{
|
||||
'actual': "You see, but you do not observe. The distinction is clear.",
|
||||
'expected': "You see, but you do not observe. The distinction is clear.",
|
||||
},
|
||||
{
|
||||
'actual': "I choose to have <mark>faith</mark> because without that, I have <em>nothing</em>.",
|
||||
'expected': "I choose to have faith because without that, I have nothing.",
|
||||
}
|
||||
]
|
||||
|
||||
response_data = {
|
||||
'response_data': {
|
||||
'descriptions': {},
|
||||
'label': ''
|
||||
}
|
||||
}
|
||||
self.context.update(response_data)
|
||||
|
||||
for label in labels:
|
||||
self.context['response_data']['label'] = label['actual']
|
||||
xml = self.render_to_xml(self.context)
|
||||
|
||||
if aria_label:
|
||||
self.assert_has_xpath(xml, "//*[@aria-label='%s']" % label['expected'], self.context)
|
||||
else:
|
||||
element_list = xml.xpath(xpath)
|
||||
self.assertEqual(len(element_list), 1)
|
||||
self.assertEqual(stringify_children(element_list[0]), label['actual'])
|
||||
|
||||
|
||||
class ChoiceGroupTemplateTest(TemplateTestCase):
|
||||
"""
|
||||
@@ -218,7 +259,7 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
|
||||
'name_array_suffix': '1',
|
||||
'value': '3',
|
||||
'response_data': self.RESPONSE_DATA,
|
||||
'describedby': self.DESCRIBEDBY,
|
||||
'describedby_html': self.DESCRIBEDBY,
|
||||
}
|
||||
|
||||
def test_problem_marked_correct(self):
|
||||
@@ -429,9 +470,10 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
|
||||
self.assert_no_xpath(xml, "//div[@class='capa_alert']", self.context)
|
||||
|
||||
def test_label(self):
|
||||
xml = self.render_to_xml(self.context)
|
||||
xpath = "//legend"
|
||||
self.assert_has_text(xml, xpath, self.context['response_data']['label'])
|
||||
"""
|
||||
Verify label element value rendering.
|
||||
"""
|
||||
self.assert_label(xpath="//legend")
|
||||
|
||||
def test_description(self):
|
||||
"""
|
||||
@@ -464,7 +506,7 @@ class TextlineTemplateTest(TemplateTestCase):
|
||||
'preprocessor': None,
|
||||
'trailing_text': None,
|
||||
'response_data': self.RESPONSE_DATA,
|
||||
'describedby': self.DESCRIBEDBY,
|
||||
'describedby_html': self.DESCRIBEDBY,
|
||||
}
|
||||
|
||||
def test_section_class(self):
|
||||
@@ -487,8 +529,10 @@ class TextlineTemplateTest(TemplateTestCase):
|
||||
self.assert_status(status_div=True)
|
||||
|
||||
def test_label(self):
|
||||
xml = self.render_to_xml(self.context)
|
||||
self.assert_has_xpath(xml, "//label[@class='problem-group-label']", self.RESPONSE_DATA['label'])
|
||||
"""
|
||||
Verify label element value rendering.
|
||||
"""
|
||||
self.assert_label(xpath="//label[@class='problem-group-label']")
|
||||
|
||||
def test_hidden(self):
|
||||
self.context['hidden'] = True
|
||||
@@ -588,7 +632,7 @@ class FormulaEquationInputTemplateTest(TemplateTestCase):
|
||||
'reported_status': 'REPORTED_STATUS',
|
||||
'trailing_text': None,
|
||||
'response_data': self.RESPONSE_DATA,
|
||||
'describedby': self.DESCRIBEDBY,
|
||||
'describedby_html': self.DESCRIBEDBY,
|
||||
}
|
||||
|
||||
def test_no_size(self):
|
||||
@@ -615,6 +659,12 @@ class FormulaEquationInputTemplateTest(TemplateTestCase):
|
||||
"""
|
||||
self.assert_status(status_div=True)
|
||||
|
||||
def test_label(self):
|
||||
"""
|
||||
Verify label element value rendering.
|
||||
"""
|
||||
self.assert_label(xpath="//label[@class='problem-group-label']")
|
||||
|
||||
|
||||
class AnnotationInputTemplateTest(TemplateTestCase):
|
||||
"""
|
||||
@@ -802,13 +852,13 @@ class OptionInputTemplateTest(TemplateTestCase):
|
||||
'value': 0,
|
||||
'default_option_text': 'Select an option',
|
||||
'response_data': self.RESPONSE_DATA,
|
||||
'describedby': self.DESCRIBEDBY,
|
||||
'describedby_html': self.DESCRIBEDBY,
|
||||
}
|
||||
|
||||
def test_select_options(self):
|
||||
|
||||
# Create options 0-4, and select option 2
|
||||
self.context['options'] = [(id_num, '<b>Option {0}</b>'.format(id_num))
|
||||
self.context['options'] = [(id_num, 'Option {0}'.format(id_num))
|
||||
for id_num in range(5)]
|
||||
self.context['value'] = 2
|
||||
|
||||
@@ -818,15 +868,12 @@ class OptionInputTemplateTest(TemplateTestCase):
|
||||
xpath = "//option[@value='option_2_dummy_default']"
|
||||
self.assert_has_xpath(xml, xpath, self.context)
|
||||
|
||||
# Should have each of the options, with the correct description
|
||||
# The description HTML should NOT be escaped
|
||||
# (that's why we descend into the <b> tag)
|
||||
for id_num in range(5):
|
||||
xpath = "//option[@value='{0}']/b".format(id_num)
|
||||
xpath = "//option[@value='{0}']".format(id_num)
|
||||
self.assert_has_text(xml, xpath, 'Option {0}'.format(id_num))
|
||||
|
||||
# Should have the correct option selected
|
||||
xpath = "//option[@selected='true']/b"
|
||||
xpath = "//option[@selected='true']"
|
||||
self.assert_has_text(xml, xpath, 'Option 2')
|
||||
|
||||
def test_status(self):
|
||||
@@ -836,9 +883,10 @@ class OptionInputTemplateTest(TemplateTestCase):
|
||||
self.assert_status(status_class=True)
|
||||
|
||||
def test_label(self):
|
||||
xml = self.render_to_xml(self.context)
|
||||
xpath = "//label[@class='problem-group-label']"
|
||||
self.assert_has_xpath(xml, xpath, self.RESPONSE_DATA['label'])
|
||||
"""
|
||||
Verify label element value rendering.
|
||||
"""
|
||||
self.assert_label(xpath="//label[@class='problem-group-label']")
|
||||
|
||||
def test_description(self):
|
||||
"""
|
||||
@@ -1077,7 +1125,59 @@ class ChoiceTextGroupTemplateTest(TemplateTestCase):
|
||||
xpath = "//div[@class='indicator-container']/span"
|
||||
self.assert_no_xpath(xml, xpath, self.context)
|
||||
|
||||
def test_label(self):
|
||||
xml = self.render_to_xml(self.context)
|
||||
xpath = "//fieldset[@aria-label='%s']" % self.context['response_data']['label']
|
||||
self.assert_has_xpath(xml, xpath, self.context)
|
||||
def test_aria_label(self):
|
||||
"""
|
||||
Verify aria-label attribute rendering.
|
||||
"""
|
||||
self.assert_label(aria_label=True)
|
||||
|
||||
|
||||
class ChemicalEquationTemplateTest(TemplateTestCase):
|
||||
"""Test mako template for `<chemicalequationinput>` input"""
|
||||
|
||||
TEMPLATE_NAME = 'chemicalequationinput.html'
|
||||
|
||||
def setUp(self):
|
||||
super(ChemicalEquationTemplateTest, self).setUp()
|
||||
self.context = {
|
||||
'id': '1',
|
||||
'status': Status('correct'),
|
||||
'previewer': 'dummy.js',
|
||||
'value': '101',
|
||||
}
|
||||
|
||||
def test_aria_label(self):
|
||||
"""
|
||||
Verify aria-label attribute rendering.
|
||||
"""
|
||||
self.assert_label(aria_label=True)
|
||||
|
||||
|
||||
class SchematicInputTemplateTest(TemplateTestCase):
|
||||
"""Test mako template for `<schematic>` input"""
|
||||
|
||||
TEMPLATE_NAME = 'schematicinput.html'
|
||||
|
||||
def setUp(self):
|
||||
super(SchematicInputTemplateTest, self).setUp()
|
||||
self.context = {
|
||||
'id': '1',
|
||||
'status': Status('correct'),
|
||||
'previewer': 'dummy.js',
|
||||
'value': '101',
|
||||
'STATIC_URL': '/dummy-static/',
|
||||
'msg': '',
|
||||
'initial_value': 'two large batteries',
|
||||
'width': '100',
|
||||
'height': '100',
|
||||
'parts': 'resistors, capacitors, and flowers',
|
||||
'setup_script': '/dummy-static/js/capa/schematicinput.js',
|
||||
'analyses': 'fast, slow, and pink',
|
||||
'submit_analyses': 'maybe',
|
||||
}
|
||||
|
||||
def test_aria_label(self):
|
||||
"""
|
||||
Verify aria-label attribute rendering.
|
||||
"""
|
||||
self.assert_label(aria_label=True)
|
||||
|
||||
@@ -78,7 +78,7 @@ class OptionInputTest(unittest.TestCase):
|
||||
'id': 'sky_input',
|
||||
'default_option_text': 'Select an option',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -147,7 +147,7 @@ class ChoiceGroupTest(unittest.TestCase):
|
||||
'submitted_message': 'Answer received.',
|
||||
'name_array_suffix': expected_suffix, # what is this for??
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -201,7 +201,7 @@ class JavascriptInputTest(unittest.TestCase):
|
||||
'display_class': display_class,
|
||||
'problem_state': problem_state,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -239,7 +239,7 @@ class TextLineTest(unittest.TestCase):
|
||||
'trailing_text': '',
|
||||
'preprocessor': None,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
self.assertEqual(context, expected)
|
||||
|
||||
@@ -278,7 +278,7 @@ class TextLineTest(unittest.TestCase):
|
||||
'script_src': script,
|
||||
},
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
self.assertEqual(context, expected)
|
||||
|
||||
@@ -323,7 +323,7 @@ class TextLineTest(unittest.TestCase):
|
||||
'trailing_text': expected_text,
|
||||
'preprocessor': None,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
self.assertEqual(context, expected)
|
||||
|
||||
@@ -366,7 +366,7 @@ class FileSubmissionTest(unittest.TestCase):
|
||||
'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]',
|
||||
'required_files': '["cookies.py"]',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -422,7 +422,7 @@ class CodeInputTest(unittest.TestCase):
|
||||
'tabsize': int(tabsize),
|
||||
'queue_len': '3',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -484,7 +484,7 @@ class MatlabTest(unittest.TestCase):
|
||||
'queue_len': '3',
|
||||
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
|
||||
'response_data': {},
|
||||
'describedby': ''
|
||||
'describedby_html': ''
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -519,7 +519,7 @@ class MatlabTest(unittest.TestCase):
|
||||
'queue_len': '3',
|
||||
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -553,7 +553,7 @@ class MatlabTest(unittest.TestCase):
|
||||
'queue_len': '0',
|
||||
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -587,7 +587,7 @@ class MatlabTest(unittest.TestCase):
|
||||
'queue_len': '1',
|
||||
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -705,13 +705,14 @@ class MatlabTest(unittest.TestCase):
|
||||
textwrap.dedent("""
|
||||
<div>{\'status\': Status(\'queued\'), \'button_enabled\': True,
|
||||
\'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\',
|
||||
\'tabsize\': 4, \'cols\': \'80\', \'STATIC_URL\': \'/dummy-static/\',
|
||||
\'describedby\': \'\', \'queue_msg\': \'\',
|
||||
\'tabsize\': 4, \'cols\': \'80\',
|
||||
\'STATIC_URL\': \'/dummy-static/\', \'linenumbers\': \'true\', \'queue_msg\': \'\',
|
||||
\'value\': \'print "good evening"\',
|
||||
\'msg\': u\'Submitted. As soon as a response is returned,
|
||||
this message will be replaced by that feedback.\',
|
||||
\'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/octave.js\',
|
||||
\'hidden\': \'\', \'linenumbers\': \'true\', \'id\': \'prob_1_2\', \'response_data\': {}}</div>
|
||||
\'hidden\': \'\', \'id\': \'prob_1_2\', \'describedby_html\': \'\',
|
||||
\'response_data\': {}}</div>
|
||||
""").replace('\n', ' ').strip()
|
||||
)
|
||||
|
||||
@@ -818,7 +819,7 @@ class MatlabTest(unittest.TestCase):
|
||||
'queue_len': '3',
|
||||
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
|
||||
'response_data': {},
|
||||
'describedby': ''
|
||||
'describedby_html': ''
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -929,7 +930,7 @@ class SchematicTest(unittest.TestCase):
|
||||
'analyses': analyses,
|
||||
'submit_analyses': submit_analyses,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -975,7 +976,7 @@ class ImageInputTest(unittest.TestCase):
|
||||
'gy': egy,
|
||||
'msg': '',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -1031,7 +1032,7 @@ class CrystallographyTest(unittest.TestCase):
|
||||
'width': width,
|
||||
'height': height,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -1079,7 +1080,7 @@ class VseprTest(unittest.TestCase):
|
||||
'molecules': molecules,
|
||||
'geometries': geometries,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -1115,7 +1116,7 @@ class ChemicalEquationTest(unittest.TestCase):
|
||||
'size': self.size,
|
||||
'previewer': '/dummy-static/js/capa/chemical_equation_preview.js',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
self.assertEqual(context, expected)
|
||||
|
||||
@@ -1210,7 +1211,7 @@ class FormulaEquationTest(unittest.TestCase):
|
||||
'inline': False,
|
||||
'trailing_text': '',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
self.assertEqual(context, expected)
|
||||
|
||||
@@ -1256,7 +1257,7 @@ class FormulaEquationTest(unittest.TestCase):
|
||||
'inline': False,
|
||||
'trailing_text': expected_text,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
@@ -1388,7 +1389,7 @@ class DragAndDropTest(unittest.TestCase):
|
||||
'msg': '',
|
||||
'drag_and_drop_json': json.dumps(user_input),
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
# as we are dumping 'draggables' dicts while dumping user_input, string
|
||||
@@ -1457,7 +1458,7 @@ class AnnotationInputTest(unittest.TestCase):
|
||||
'debug': False,
|
||||
'return_to_annotation': True,
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
|
||||
self.maxDiff = None
|
||||
@@ -1522,7 +1523,7 @@ class TestChoiceText(unittest.TestCase):
|
||||
'show_correctness': 'always',
|
||||
'submitted_message': 'Answer received.',
|
||||
'response_data': RESPONSE_DATA,
|
||||
'describedby': DESCRIBEDBY
|
||||
'describedby_html': DESCRIBEDBY
|
||||
}
|
||||
expected.update(state)
|
||||
the_input = lookup_tag(tag)(test_capa_system(), element, state)
|
||||
|
||||
@@ -5,7 +5,7 @@ import unittest
|
||||
from lxml import etree
|
||||
|
||||
from capa.tests.helpers import test_capa_system
|
||||
from capa.util import compare_with_tolerance, sanitize_html, get_inner_html_from_xpath
|
||||
from capa.util import compare_with_tolerance, sanitize_html, get_inner_html_from_xpath, remove_markup
|
||||
|
||||
|
||||
class UtilTest(unittest.TestCase):
|
||||
@@ -126,3 +126,12 @@ class UtilTest(unittest.TestCase):
|
||||
"""
|
||||
xpath_node = etree.XML('<hint style="smtng">aa<a href="#">bb</a>cc</hint>')
|
||||
self.assertEqual(get_inner_html_from_xpath(xpath_node), 'aa<a href="#">bb</a>cc')
|
||||
|
||||
def test_remove_markup(self):
|
||||
"""
|
||||
Test for markup removal with bleach.
|
||||
"""
|
||||
self.assertEqual(
|
||||
remove_markup("The <mark>Truth</mark> is <em>Out There</em> & you need to <strong>find</strong> it"),
|
||||
"The Truth is Out There & you need to find it"
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from calc import evaluator
|
||||
from cmath import isinf, isnan
|
||||
import re
|
||||
from lxml import etree
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# Utility functions used in CAPA responsetypes
|
||||
@@ -195,3 +196,15 @@ def get_inner_html_from_xpath(xpath_node):
|
||||
# strips outer tag from html string
|
||||
inner_html = re.sub('(?ms)<%s[^>]*>(.*)</%s>' % (xpath_node.tag, xpath_node.tag), '\\1', html)
|
||||
return inner_html.strip()
|
||||
|
||||
|
||||
def remove_markup(html):
|
||||
"""
|
||||
Return html with markup stripped and text HTML-escaped.
|
||||
|
||||
>>> bleach.clean("<b>Rock & Roll</b>", tags=[], strip=True)
|
||||
u'Rock & Roll'
|
||||
>>> bleach.clean("<b>Rock & Roll</b>", tags=[], strip=True)
|
||||
u'Rock & Roll'
|
||||
"""
|
||||
return HTML(bleach.clean(html, tags=[], strip=True))
|
||||
|
||||
Reference in New Issue
Block a user