Commit 6ff762b6 by muhammad-ammar

allow html inside label and descriptions

TNL-5557
parent 2b80c619
...@@ -31,6 +31,8 @@ import capa.responsetypes as responsetypes ...@@ -31,6 +31,8 @@ import capa.responsetypes as responsetypes
from capa.util import contextualize_text, convert_files_to_filenames from capa.util import contextualize_text, convert_files_to_filenames
import capa.xqueue_interface as xqueue_interface import capa.xqueue_interface as xqueue_interface
from capa.safe_exec import safe_exec 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 # extra things displayed after "show answers" is pressed
...@@ -926,7 +928,7 @@ class LoncapaProblem(object): ...@@ -926,7 +928,7 @@ class LoncapaProblem(object):
group_label_tag.tag = 'p' group_label_tag.tag = 'p'
group_label_tag.set('id', responsetype_id) group_label_tag.set('id', responsetype_id)
group_label_tag.set('class', 'multi-inputs-group-label') 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: for inputfield in inputfields:
problem_data[inputfield.get('id')] = { problem_data[inputfield.get('id')] = {
...@@ -938,7 +940,7 @@ class LoncapaProblem(object): ...@@ -938,7 +940,7 @@ class LoncapaProblem(object):
# Extract label value from <label> tag or label attribute from inside the responsetype # Extract label value from <label> tag or label attribute from inside the responsetype
responsetype_label_tag = response.find('label') responsetype_label_tag = response.find('label')
if responsetype_label_tag is not None: 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 # store <label> tag containing question text to delete
# it later otherwise question will be rendered twice # it later otherwise question will be rendered twice
element_to_be_deleted = responsetype_label_tag element_to_be_deleted = responsetype_label_tag
...@@ -950,21 +952,15 @@ class LoncapaProblem(object): ...@@ -950,21 +952,15 @@ class LoncapaProblem(object):
p_tag = response.xpath('preceding-sibling::*[1][self::p]') p_tag = response.xpath('preceding-sibling::*[1][self::p]')
if p_tag and p_tag[0].text == inputfields[0].attrib['label']: if p_tag and p_tag[0].text == inputfields[0].attrib['label']:
label = p_tag[0].text label = stringify_children(p_tag[0])
element_to_be_deleted = p_tag[0]
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 = ''
else: else:
# In this case the problems don't have tag or label attribute inside the responsetype # 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. # 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. # 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]') label_tag = response.xpath('preceding-sibling::*[1][self::label]')
if label_tag: if label_tag:
label = label_tag[0].text label = stringify_children(label_tag[0])
element_to_be_deleted = label_tag[0] element_to_be_deleted = label_tag[0]
# delete label or p element only if inputtype is fully accessible # delete label or p element only if inputtype is fully accessible
...@@ -978,11 +974,11 @@ class LoncapaProblem(object): ...@@ -978,11 +974,11 @@ class LoncapaProblem(object):
for description in description_tags: for description in description_tags:
descriptions[ descriptions[
"description_%s_%i" % (responsetype_id, description_id) "description_%s_%i" % (responsetype_id, description_id)
] = description.text ] = HTML(stringify_children(description))
response.remove(description) response.remove(description)
description_id += 1 description_id += 1
problem_data[inputfields[0].get('id')] = { problem_data[inputfields[0].get('id')] = {
'label': label.strip() if label else '', 'label': HTML(label.strip()) if label else '',
'descriptions': descriptions 'descriptions': descriptions
} }
...@@ -322,14 +322,14 @@ class InputTypeBase(object): ...@@ -322,14 +322,14 @@ class InputTypeBase(object):
'msg': self.msg, 'msg': self.msg,
'response_data': self.response_data, 'response_data': self.response_data,
'STATIC_URL': self.capa_system.STATIC_URL, 'STATIC_URL': self.capa_system.STATIC_URL,
'describedby': '', 'describedby_html': '',
} }
# Don't add aria-describedby attribute if there are no descriptions # Don't add aria-describedby attribute if there are no descriptions
if self.response_data.get('descriptions'): if self.response_data.get('descriptions'):
description_ids = ' '.join(self.response_data.get('descriptions').keys()) description_ids = ' '.join(self.response_data.get('descriptions').keys())
context.update( context.update(
{'describedby': 'aria-describedby="{}"'.format(description_ids)} {'describedby_html': 'aria-describedby="{}"'.format(description_ids)}
) )
context.update( context.update(
......
<%! from capa.util import remove_markup %>
<div id="chemicalequationinput_${id}" class="chemicalequationinput"> <div id="chemicalequationinput_${id}" class="chemicalequationinput">
<div class="script_placeholder" data-src="${previewer}"/> <div class="script_placeholder" data-src="${previewer}"/>
<div class="${status.classname}" id="status_${id}"> <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: % if size:
size="${size}" size="${size}"
% endif % endif
......
<%page expression_filter="h"/>
<%! from openedx.core.djangolib.markup import HTML %> <%! from openedx.core.djangolib.markup import HTML %>
<% <%
def is_radio_input(choice_id): def is_radio_input(choice_id):
...@@ -6,7 +7,7 @@ ...@@ -6,7 +7,7 @@
)) ))
%> %>
<form class="choicegroup capa_inputtype" id="inputtype_${id}"> <form class="choicegroup capa_inputtype" id="inputtype_${id}">
<fieldset ${describedby}> <fieldset ${HTML(describedby_html)}>
% if response_data['label']: % if response_data['label']:
<legend id="${id}-legend" class="response-fieldset-legend field-group-hd">${response_data['label']}</legend> <legend id="${id}-legend" class="response-fieldset-legend field-group-hd">${response_data['label']}</legend>
% endif % endif
...@@ -36,7 +37,7 @@ ...@@ -36,7 +37,7 @@
% endif % endif
% endif % endif
class="${label_class}" 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}" <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... ## If the student selected this choice...
...@@ -45,7 +46,7 @@ ...@@ -45,7 +46,7 @@
% elif input_type != 'radio' and choice_id in value: % elif input_type != 'radio' and choice_id in value:
checked="true" checked="true"
% endif % endif
/> ${choice_label} /> ${HTML(choice_label)}
% if is_radio_input(choice_id): % if is_radio_input(choice_id):
% if status in ('correct', 'partially-correct', 'incorrect') and not show_correctness == 'never': % if status in ('correct', 'partially-correct', 'incorrect') and not show_correctness == 'never':
......
<%! from capa.util import remove_markup %>
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<% element_checked = False %> <% element_checked = False %>
% for choice_id, _ in choices: % for choice_id, _ in choices:
...@@ -10,7 +11,7 @@ ...@@ -10,7 +11,7 @@
<form class="choicetextgroup capa_inputtype" id="inputtype_${id}"> <form class="choicetextgroup capa_inputtype" id="inputtype_${id}">
<div class="script_placeholder" data-src="${STATIC_URL}js/capa/choicetextinput.js"/> <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: % for choice_id, choice_description in choices:
<% choice_id = choice_id %> <% choice_id = choice_id %>
<section id="forinput${choice_id}" <section id="forinput${choice_id}"
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
% endfor % endfor
<input type="text" name="input_${id}" id="input_${id}" <input type="text" name="input_${id}" id="input_${id}"
data-input-id="${id}" value="${value}" data-input-id="${id}" value="${value}"
${describedby | n, decode.utf8} ${HTML(describedby_html)}
% if size: % if size:
size="${size}" size="${size}"
% endif % endif
......
<%page expression_filter="h"/>
<%! from openedx.core.djangolib.markup import HTML %> <%! from openedx.core.djangolib.markup import HTML %>
<% doinline = "inline" if inline else "" %> <% doinline = "inline" if inline else "" %>
...@@ -10,7 +11,7 @@ ...@@ -10,7 +11,7 @@
<p class="question-description" id="${description_id}">${description_text}</p> <p class="question-description" id="${description_id}">${description_text}</p>
% endfor % 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> <option value="option_${id}_dummy_default">${default_option_text}</option>
% for option_id, option_description in options: % for option_id, option_description in options:
<option value="${option_id}" <option value="${option_id}"
......
<%! from capa.util import remove_markup %>
<div> <div>
<div class="script_placeholder" data-src="${setup_script}"/> <div class="script_placeholder" data-src="${setup_script}"/>
<input type="hidden" <input type="hidden"
...@@ -8,7 +9,7 @@ ...@@ -8,7 +9,7 @@
analyses="${analyses}" analyses="${analyses}"
name="input_${id}" name="input_${id}"
id="input_${id}" id="input_${id}"
aria-label="${response_data['label']}" aria-label="${remove_markup(response_data['label'])}"
aria-describedby="answer_${id}" aria-describedby="answer_${id}"
value="${value|h}" value="${value|h}"
initial_value="${initial_value|h}" initial_value="${initial_value|h}"
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
% for description_id, description_text in response_data['descriptions'].items(): % for description_id, description_text in response_data['descriptions'].items():
<p class="question-description" id="${description_id}">${description_text}</p> <p class="question-description" id="${description_id}">${description_text}</p>
% endfor % 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: % if do_math:
class="math" class="math"
% endif % endif
......
...@@ -13,7 +13,12 @@ from capa.tests.helpers import new_loncapa_problem ...@@ -13,7 +13,12 @@ from capa.tests.helpers import new_loncapa_problem
class CAPAProblemTest(unittest.TestCase): class CAPAProblemTest(unittest.TestCase):
""" CAPA problem related tests""" """ 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 Verify that
* label is extracted * label is extracted
...@@ -25,7 +30,7 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -25,7 +30,7 @@ class CAPAProblemTest(unittest.TestCase):
xml = """ xml = """
<problem> <problem>
<choiceresponse> <choiceresponse>
<label>Select the correct synonym of paranoid?</label> <label>{question}</label>
<description>Only the paranoid survive.</description> <description>Only the paranoid survive.</description>
<checkboxgroup> <checkboxgroup>
<choice correct="true">over-suspicious</choice> <choice correct="true">over-suspicious</choice>
...@@ -33,25 +38,35 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -33,25 +38,35 @@ class CAPAProblemTest(unittest.TestCase):
</checkboxgroup> </checkboxgroup>
</choiceresponse> </choiceresponse>
</problem> </problem>
""" """.format(question=question)
problem = new_loncapa_problem(xml) problem = new_loncapa_problem(xml)
self.assertEqual( self.assertEqual(
problem.problem_data, problem.problem_data,
{ {
'1_2_1': '1_2_1':
{ {
'label': 'Select the correct synonym of paranoid?', 'label': question,
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'} 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}
} }
} }
) )
self.assertEqual(len(problem.tree.xpath('//label')), 0) 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. Verify that legacy problem is handled correctly.
""" """
question = "Once we become predictable, we become ______?"
xml = """ xml = """
<problem> <problem>
<p>Be sure to check your spelling.</p> <p>Be sure to check your spelling.</p>
...@@ -60,7 +75,7 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -60,7 +75,7 @@ class CAPAProblemTest(unittest.TestCase):
<textline label="{}" size="40"/> <textline label="{}" size="40"/>
</stringresponse> </stringresponse>
</problem> </problem>
""".format(question, question) """.format(question, label_attr)
problem = new_loncapa_problem(xml) problem = new_loncapa_problem(xml)
self.assertEqual( self.assertEqual(
problem.problem_data, problem.problem_data,
...@@ -77,7 +92,18 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -77,7 +92,18 @@ class CAPAProblemTest(unittest.TestCase):
0 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. Verify that label is extracted correctly.
...@@ -86,8 +112,6 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -86,8 +112,6 @@ class CAPAProblemTest(unittest.TestCase):
tag and label attribute inside responsetype. But we have a label tag tag and label attribute inside responsetype. But we have a label tag
before the responsetype. before the responsetype.
""" """
question1 = 'People who say they have nothing to ____ almost always do?'
question2 = 'Select the correct synonym of paranoid?'
xml = """ xml = """
<problem> <problem>
<p>Be sure to check your spelling.</p> <p>Be sure to check your spelling.</p>
...@@ -131,17 +155,19 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -131,17 +155,19 @@ class CAPAProblemTest(unittest.TestCase):
""" """
Verify that multiple descriptions are handled correctly. 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 = """ xml = """
<problem> <problem>
<p>Be sure to check your spelling.</p> <p>Be sure to check your spelling.</p>
<stringresponse answer="War" type="ci"> <stringresponse answer="War" type="ci">
<label>___ requires sacrifices.</label> <label>___ requires sacrifices.</label>
<description>The problem with trying to be the bad guy, there's always someone worse.</description> <description>{}</description>
<description>Anyone who looks the world as if it was a game of chess deserves to lose.</description> <description>{}</description>
<textline size="40"/> <textline size="40"/>
</stringresponse> </stringresponse>
</problem> </problem>
""" """.format(desc1, desc2)
problem = new_loncapa_problem(xml) problem = new_loncapa_problem(xml)
self.assertEqual( self.assertEqual(
problem.problem_data, problem.problem_data,
...@@ -150,8 +176,8 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -150,8 +176,8 @@ class CAPAProblemTest(unittest.TestCase):
{ {
'label': '___ requires sacrifices.', 'label': '___ requires sacrifices.',
'descriptions': { 'descriptions': {
'description_1_1_1': "The problem with trying to be the bad guy, there's always someone worse.", 'description_1_1_1': desc1,
'description_1_1_2': "Anyone who looks the world as if it was a game of chess deserves to lose." 'description_1_1_2': desc2
} }
} }
} }
...@@ -298,11 +324,15 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -298,11 +324,15 @@ class CAPAProblemTest(unittest.TestCase):
1 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. 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?' input1_label = 'What color is the sky?'
input2_label = 'What color are pine needles?' input2_label = 'What color are pine needles?'
xml = """ xml = """
...@@ -424,39 +454,6 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -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='label', label_attr=False)
self.assert_question_tag(question1, question2, tag='p', label_attr=True) 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 @ddt.ddt
class CAPAMultiInputProblemTest(unittest.TestCase): class CAPAMultiInputProblemTest(unittest.TestCase):
......
...@@ -186,7 +186,7 @@ class CapaHtmlRenderTest(unittest.TestCase): ...@@ -186,7 +186,7 @@ class CapaHtmlRenderTest(unittest.TestCase):
'trailing_text': '', 'trailing_text': '',
'size': None, 'size': None,
'response_data': {'label': '', 'descriptions': {}}, 'response_data': {'label': '', 'descriptions': {}},
'describedby': '' 'describedby_html': ''
} }
expected_solution_context = {'id': '1_solution_1'} expected_solution_context = {'id': '1_solution_1'}
......
...@@ -78,7 +78,7 @@ class OptionInputTest(unittest.TestCase): ...@@ -78,7 +78,7 @@ class OptionInputTest(unittest.TestCase):
'id': 'sky_input', 'id': 'sky_input',
'default_option_text': 'Select an option', 'default_option_text': 'Select an option',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -147,7 +147,7 @@ class ChoiceGroupTest(unittest.TestCase): ...@@ -147,7 +147,7 @@ class ChoiceGroupTest(unittest.TestCase):
'submitted_message': 'Answer received.', 'submitted_message': 'Answer received.',
'name_array_suffix': expected_suffix, # what is this for?? 'name_array_suffix': expected_suffix, # what is this for??
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -201,7 +201,7 @@ class JavascriptInputTest(unittest.TestCase): ...@@ -201,7 +201,7 @@ class JavascriptInputTest(unittest.TestCase):
'display_class': display_class, 'display_class': display_class,
'problem_state': problem_state, 'problem_state': problem_state,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -239,7 +239,7 @@ class TextLineTest(unittest.TestCase): ...@@ -239,7 +239,7 @@ class TextLineTest(unittest.TestCase):
'trailing_text': '', 'trailing_text': '',
'preprocessor': None, 'preprocessor': None,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -278,7 +278,7 @@ class TextLineTest(unittest.TestCase): ...@@ -278,7 +278,7 @@ class TextLineTest(unittest.TestCase):
'script_src': script, 'script_src': script,
}, },
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -323,7 +323,7 @@ class TextLineTest(unittest.TestCase): ...@@ -323,7 +323,7 @@ class TextLineTest(unittest.TestCase):
'trailing_text': expected_text, 'trailing_text': expected_text,
'preprocessor': None, 'preprocessor': None,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -366,7 +366,7 @@ class FileSubmissionTest(unittest.TestCase): ...@@ -366,7 +366,7 @@ class FileSubmissionTest(unittest.TestCase):
'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]', 'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]',
'required_files': '["cookies.py"]', 'required_files': '["cookies.py"]',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -422,7 +422,7 @@ class CodeInputTest(unittest.TestCase): ...@@ -422,7 +422,7 @@ class CodeInputTest(unittest.TestCase):
'tabsize': int(tabsize), 'tabsize': int(tabsize),
'queue_len': '3', 'queue_len': '3',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -484,7 +484,7 @@ class MatlabTest(unittest.TestCase): ...@@ -484,7 +484,7 @@ class MatlabTest(unittest.TestCase):
'queue_len': '3', 'queue_len': '3',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js', 'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
'response_data': {}, 'response_data': {},
'describedby': '' 'describedby_html': ''
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -519,7 +519,7 @@ class MatlabTest(unittest.TestCase): ...@@ -519,7 +519,7 @@ class MatlabTest(unittest.TestCase):
'queue_len': '3', 'queue_len': '3',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js', 'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -553,7 +553,7 @@ class MatlabTest(unittest.TestCase): ...@@ -553,7 +553,7 @@ class MatlabTest(unittest.TestCase):
'queue_len': '0', 'queue_len': '0',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js', 'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -587,7 +587,7 @@ class MatlabTest(unittest.TestCase): ...@@ -587,7 +587,7 @@ class MatlabTest(unittest.TestCase):
'queue_len': '1', 'queue_len': '1',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js', 'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -705,13 +705,14 @@ class MatlabTest(unittest.TestCase): ...@@ -705,13 +705,14 @@ class MatlabTest(unittest.TestCase):
textwrap.dedent(""" textwrap.dedent("""
<div>{\'status\': Status(\'queued\'), \'button_enabled\': True, <div>{\'status\': Status(\'queued\'), \'button_enabled\': True,
\'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\', \'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\',
\'tabsize\': 4, \'cols\': \'80\', \'STATIC_URL\': \'/dummy-static/\', \'tabsize\': 4, \'cols\': \'80\',
\'describedby\': \'\', \'queue_msg\': \'\', \'STATIC_URL\': \'/dummy-static/\', \'linenumbers\': \'true\', \'queue_msg\': \'\',
\'value\': \'print "good evening"\', \'value\': \'print "good evening"\',
\'msg\': u\'Submitted. As soon as a response is returned, \'msg\': u\'Submitted. As soon as a response is returned,
this message will be replaced by that feedback.\', this message will be replaced by that feedback.\',
\'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/octave.js\', \'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() """).replace('\n', ' ').strip()
) )
...@@ -818,7 +819,7 @@ class MatlabTest(unittest.TestCase): ...@@ -818,7 +819,7 @@ class MatlabTest(unittest.TestCase):
'queue_len': '3', 'queue_len': '3',
'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js', 'matlab_editor_js': '/dummy-static/js/vendor/CodeMirror/octave.js',
'response_data': {}, 'response_data': {},
'describedby': '' 'describedby_html': ''
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -929,7 +930,7 @@ class SchematicTest(unittest.TestCase): ...@@ -929,7 +930,7 @@ class SchematicTest(unittest.TestCase):
'analyses': analyses, 'analyses': analyses,
'submit_analyses': submit_analyses, 'submit_analyses': submit_analyses,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -975,7 +976,7 @@ class ImageInputTest(unittest.TestCase): ...@@ -975,7 +976,7 @@ class ImageInputTest(unittest.TestCase):
'gy': egy, 'gy': egy,
'msg': '', 'msg': '',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -1031,7 +1032,7 @@ class CrystallographyTest(unittest.TestCase): ...@@ -1031,7 +1032,7 @@ class CrystallographyTest(unittest.TestCase):
'width': width, 'width': width,
'height': height, 'height': height,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -1079,7 +1080,7 @@ class VseprTest(unittest.TestCase): ...@@ -1079,7 +1080,7 @@ class VseprTest(unittest.TestCase):
'molecules': molecules, 'molecules': molecules,
'geometries': geometries, 'geometries': geometries,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -1115,7 +1116,7 @@ class ChemicalEquationTest(unittest.TestCase): ...@@ -1115,7 +1116,7 @@ class ChemicalEquationTest(unittest.TestCase):
'size': self.size, 'size': self.size,
'previewer': '/dummy-static/js/capa/chemical_equation_preview.js', 'previewer': '/dummy-static/js/capa/chemical_equation_preview.js',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -1210,7 +1211,7 @@ class FormulaEquationTest(unittest.TestCase): ...@@ -1210,7 +1211,7 @@ class FormulaEquationTest(unittest.TestCase):
'inline': False, 'inline': False,
'trailing_text': '', 'trailing_text': '',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -1256,7 +1257,7 @@ class FormulaEquationTest(unittest.TestCase): ...@@ -1256,7 +1257,7 @@ class FormulaEquationTest(unittest.TestCase):
'inline': False, 'inline': False,
'trailing_text': expected_text, 'trailing_text': expected_text,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.assertEqual(context, expected) self.assertEqual(context, expected)
...@@ -1388,7 +1389,7 @@ class DragAndDropTest(unittest.TestCase): ...@@ -1388,7 +1389,7 @@ class DragAndDropTest(unittest.TestCase):
'msg': '', 'msg': '',
'drag_and_drop_json': json.dumps(user_input), 'drag_and_drop_json': json.dumps(user_input),
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
# as we are dumping 'draggables' dicts while dumping user_input, string # as we are dumping 'draggables' dicts while dumping user_input, string
...@@ -1457,7 +1458,7 @@ class AnnotationInputTest(unittest.TestCase): ...@@ -1457,7 +1458,7 @@ class AnnotationInputTest(unittest.TestCase):
'debug': False, 'debug': False,
'return_to_annotation': True, 'return_to_annotation': True,
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
self.maxDiff = None self.maxDiff = None
...@@ -1522,7 +1523,7 @@ class TestChoiceText(unittest.TestCase): ...@@ -1522,7 +1523,7 @@ class TestChoiceText(unittest.TestCase):
'show_correctness': 'always', 'show_correctness': 'always',
'submitted_message': 'Answer received.', 'submitted_message': 'Answer received.',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby_html': DESCRIBEDBY
} }
expected.update(state) expected.update(state)
the_input = lookup_tag(tag)(test_capa_system(), element, state) the_input = lookup_tag(tag)(test_capa_system(), element, state)
......
...@@ -5,7 +5,7 @@ import unittest ...@@ -5,7 +5,7 @@ import unittest
from lxml import etree from lxml import etree
from capa.tests.helpers import test_capa_system 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): class UtilTest(unittest.TestCase):
...@@ -126,3 +126,12 @@ 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>') 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') 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 &amp; you need to find it"
)
...@@ -8,6 +8,7 @@ from calc import evaluator ...@@ -8,6 +8,7 @@ from calc import evaluator
from cmath import isinf, isnan from cmath import isinf, isnan
import re import re
from lxml import etree from lxml import etree
from openedx.core.djangolib.markup import HTML
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# #
# Utility functions used in CAPA responsetypes # Utility functions used in CAPA responsetypes
...@@ -195,3 +196,15 @@ def get_inner_html_from_xpath(xpath_node): ...@@ -195,3 +196,15 @@ def get_inner_html_from_xpath(xpath_node):
# strips outer tag from html string # strips outer tag from html string
inner_html = re.sub('(?ms)<%s[^>]*>(.*)</%s>' % (xpath_node.tag, xpath_node.tag), '\\1', html) inner_html = re.sub('(?ms)<%s[^>]*>(.*)</%s>' % (xpath_node.tag, xpath_node.tag), '\\1', html)
return inner_html.strip() 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 &amp; Roll'
>>> bleach.clean("<b>Rock &amp; Roll</b>", tags=[], strip=True)
u'Rock &amp; Roll'
"""
return HTML(bleach.clean(html, tags=[], strip=True))
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