tag is removed to avoid duplication
"""
xml = """
Select the correct synonym of paranoid?
Only the paranoid survive.
over-suspicious
funny
one more question
What Apple device competed with the portable CD player?
Device looks like an egg plant.
The iPad
Napster
The iPod
The vegetable peeler
"""
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': 'Select the correct synonym of paranoid?',
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}},
'1_3_1': {'label': 'What Apple device competed with the portable CD player?',
'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}}}
assert len(problem.tree.xpath('//label')) == 0
def test_question_title_not_removed_got_children(self):
"""
Verify that question text before responsetype not deleted when
it contains other children and label is picked from label attribute of inputtype
This is the case when author updated the
immediately before
responsetype to contain other elements. We do not want to delete information in that case.
"""
question = 'Is egg plant a fruit?'
xml = """
Choose wisely.
Select the correct synonym of paranoid?
over-suspicious
funny
""".format(question)
problem = new_loncapa_problem(xml)
assert problem.problem_data == {'1_2_1': {'label': '', 'descriptions': {}}}
assert len(problem.tree.xpath('//p/img')) == 1
@ddt.unpack
@ddt.data(
{'group_label': 'Choose the correct color'},
{'group_label': 'Choose the correct color '},
)
def test_multiple_inputtypes(self, group_label):
"""
Verify that group label and labels for individual inputtypes are extracted correctly.
"""
input1_label = 'What color is the sky?'
input2_label = 'What color are pine needles?'
xml = """
{}
""".format(group_label, input1_label, input2_label)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'group_label': group_label, 'label': input1_label, 'descriptions': {}},
'1_2_2': {'group_label': group_label, 'label': input2_label, 'descriptions': {}}}
def test_single_inputtypes(self):
"""
Verify that HTML is correctly rendered when there is single inputtype.
"""
question = 'Enter sum of 1+2'
xml = textwrap.dedent("""
{}
""".format(question))
problem = new_loncapa_problem(xml, use_capa_render_template=True)
problem_html = etree.XML(problem.get_html())
# verify that only no multi input group div is present
multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]')
assert len(multi_inputs_group) == 0
# verify that question is rendered only once
question = problem_html.xpath("//*[normalize-space(text())='{}']".format(question))
assert len(question) == 1
def assert_question_tag(self, question1, question2, tag, label_attr=False):
"""
Verify question tag correctness.
"""
question1_tag = '<{tag}>{}{tag}>'.format(question1, tag=tag) if question1 else ''
question2_tag = '<{tag}>{}{tag}>'.format(question2, tag=tag) if question2 else ''
question1_label_attr = 'label="{}"'.format(question1) if label_attr else ''
question2_label_attr = 'label="{}"'.format(question2) if label_attr else ''
xml = """
{question1_tag}
choice1
choice2
{question2_tag}
choice1
choice2
""".format(
question1_tag=question1_tag,
question2_tag=question2_tag,
question1_label_attr=question1_label_attr,
question2_label_attr=question2_label_attr,
)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
assert len(problem.tree.xpath('//{}'.format(tag))) == 0
@ddt.unpack
@ddt.data(
{'question1': 'question 1 label', 'question2': 'question 2 label'},
{'question1': '', 'question2': 'question 2 label'},
{'question1': 'question 1 label', 'question2': ''}
)
def test_correct_question_tag_is_picked(self, question1, question2):
"""
For a problem with multiple questions verify that correct question tag is picked.
"""
self.assert_question_tag(question1, question2, tag='label', label_attr=False)
self.assert_question_tag(question1, question2, tag='p', label_attr=True)
def test_optionresponse_xml_compatibility(self):
"""
Verify that an optionresponse problem with multiple correct answers is not instantiated.
Scenario:
Given an optionresponse/Dropdown problem
If there are multiple correct answers
Then the problem is not instantiated
And Loncapa problem error exception is raised
If the problem is corrected by including only one correct answer
Then the problem is created successfully
"""
xml = """
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.
Add the question text, or prompt, here. This text is required.
You can add an optional tip or note related to the prompt like this.
an incorrect answer
the correct answer
an incorrect answer
"""
with pytest.raises(LoncapaProblemError):
new_loncapa_problem(xml.format(correctness=True))
problem = new_loncapa_problem(xml.format(correctness=False))
assert problem is not None
def test_optionresponse_option_with_empty_text(self):
"""
Verify successful instantiation of an optionresponse problem
with an option with empty text
"""
xml = """
Select True or False
True Not this one
False
Not this empty one either
"""
problem = new_loncapa_problem(xml)
assert problem is not None
@ddt.ddt
class CAPAMultiInputProblemTest(unittest.TestCase):
""" TestCase for CAPA problems with multiple inputtypes """
def capa_problem(self, xml):
"""
Create capa problem.
"""
return new_loncapa_problem(xml, use_capa_render_template=True)
def assert_problem_data(self, problem_data):
"""Verify problem data is in expected state"""
for problem_value in problem_data.values():
assert isinstance(problem_value['label'], Markup)
def assert_problem_html(self, problem_html, group_label, *input_labels):
"""
Verify that correct html is rendered for multiple inputtypes.
Arguments:
problem_html (str): problem HTML
group_label (str or None): multi input group label or None if label is not present
input_labels (tuple): individual input labels
"""
html = etree.XML(problem_html)
# verify that only one multi input group div is present at correct path
multi_inputs_group = html.xpath(
'//div[@class="wrapper-problem-response"]/div[@class="multi-inputs-group"]'
)
assert len(multi_inputs_group) == 1
if group_label is None:
# if multi inputs group label is not present then there shouldn't be `aria-labelledby` attribute
assert multi_inputs_group[0].attrib.get('aria-labelledby') is None
else:
# verify that multi input group label
tag exists and its
# id matches with correct multi input group aria-labelledby
multi_inputs_group_label_id = multi_inputs_group[0].attrib.get('aria-labelledby')
multi_inputs_group_label = html.xpath('//p[@id="{}"]'.format(multi_inputs_group_label_id))
assert len(multi_inputs_group_label) == 1
assert multi_inputs_group_label[0].text == group_label
# verify that label for each input comes only once
for input_label in input_labels:
# normalize-space is used to remove whitespace around the text
input_label_element = multi_inputs_group[0].xpath('//*[normalize-space(text())="{}"]'.format(input_label))
assert len(input_label_element) == 1
@ddt.unpack
@ddt.data(
{'label_html': 'Choose the correct color ', 'group_label': 'Choose the correct color'},
{'label_html': '', 'group_label': None}
)
def test_optionresponse(self, label_html, group_label):
"""
Verify that optionresponse problem with multiple inputtypes is rendered correctly.
"""
input1_label = 'What color is the sky?'
input2_label = 'What color are pine needles?'
xml = """
{label_html}
""".format(label_html=label_html, input1_label=input1_label, input2_label=input2_label)
problem = self.capa_problem(xml)
self.assert_problem_html(problem.get_html(), group_label, input1_label, input2_label)
self.assert_problem_data(problem.problem_data)
@ddt.unpack
@ddt.data(
{'inputtype': 'textline'},
{'inputtype': 'formulaequationinput'}
)
def test_customresponse(self, inputtype):
"""
Verify that customresponse problem with multiple textline
and formulaequationinput inputtypes is rendered correctly.
"""
group_label = 'Enter two integers that sum to 10.'
input1_label = 'Integer 1'
input2_label = 'Integer 2'
xml = textwrap.dedent("""
{}
<{inputtype} size="40" correct_answer="3" label="{}" />
<{inputtype} size="40" correct_answer="7" label="{}" />
""".format(group_label, input1_label, input2_label, inputtype=inputtype))
problem = self.capa_problem(xml)
self.assert_problem_html(problem.get_html(), group_label, input1_label, input2_label)
self.assert_problem_data(problem.problem_data)
@ddt.unpack
@ddt.data(
{
'descriptions': ('desc1', 'desc2'),
'descriptions_html': 'desc1 desc2 '
},
{
'descriptions': (),
'descriptions_html': ''
}
)
def test_descriptions(self, descriptions, descriptions_html):
"""
Verify that groups descriptions are rendered correctly.
"""
xml = """
group label
{descriptions_html}
""".format(descriptions_html=descriptions_html)
problem = self.capa_problem(xml)
problem_html = etree.XML(problem.get_html())
multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]')[0]
description_ids = multi_inputs_group.attrib.get('aria-describedby', '').split()
# Verify that number of descriptions matches description_ids
assert len(description_ids) == len(descriptions)
# For each description, check its order and text is correct
for index, description_id in enumerate(description_ids):
description_element = multi_inputs_group.xpath('//p[@id="{}"]'.format(description_id))
assert len(description_element) == 1
assert description_element[0].text == descriptions[index]
@ddt.ddt
class CAPAProblemReportHelpersTest(unittest.TestCase):
""" TestCase for CAPA methods for finding question labels and answer text """
@ddt.data(
('answerid_2_1', 'label', 'label'),
('answerid_2_2', 'label html ', 'label html'),
('answerid_2_2', ' label html ', 'label html'),
('answerid_2_3', None, 'Question 1'),
('answerid_2_3', '', 'Question 1'),
('answerid_3_3', '', 'Question 2'),
)
@ddt.unpack
def test_find_question_label(self, answer_id, label, stripped_label):
problem = new_loncapa_problem(
' '.format(answer_id)
)
mock_problem_data = {
answer_id: {
'label': HTML(label) if label else ''
}
}
with patch.object(problem, 'problem_data', mock_problem_data):
assert problem.find_question_label(answer_id) == stripped_label
@ddt.data(None, {}, [None])
def test_find_answer_test_not_implemented(self, current_answer):
problem = new_loncapa_problem(' ')
self.assertRaises(NotImplementedError, problem.find_answer_text, '', current_answer)
@ddt.data(
('1_2_1', 'choice_0', 'over-suspicious'),
('1_2_1', 'choice_1', 'funny'),
('1_3_1', 'choice_0', 'The iPad'),
('1_3_1', 'choice_2', 'The iPod'),
('1_3_1', ['choice_0', 'choice_1'], 'The iPad, Napster'),
('1_4_1', 'yellow', 'yellow'),
('1_4_1', 'blue', 'blue'),
)
@ddt.unpack
def test_find_answer_text_choices(self, answer_id, choice_id, answer_text):
problem = new_loncapa_problem(
"""
over-suspicious
funny
The iPad
Napster
The iPod
"""
)
assert problem.find_answer_text(answer_id, choice_id) == answer_text
@ddt.data(
# Test for ChoiceResponse
('1_2_1', 'choice_0', 'Answer Text Missing'),
('1_2_1', 'choice_1', 'funny'),
# Test for MultipleChoiceResponse
('1_3_1', 'choice_0', 'The iPad'),
('1_3_1', 'choice_2', 'Answer Text Missing'),
('1_3_1', ['choice_0', 'choice_1'], 'The iPad, Answer Text Missing'),
# Test for OptionResponse
('1_4_1', '', 'Answer Text Missing'),
)
@ddt.unpack
def test_find_answer_text_choices_with_missing_text(self, answer_id, choice_id, answer_text):
problem = new_loncapa_problem(
"""
funny
The iPad
"""
)
assert problem.find_answer_text(answer_id, choice_id) == answer_text
@ddt.data(
# Test for ChoiceResponse
('1_2_1', 'over-suspicious'),
# Test for MultipleChoiceResponse
('1_3_1', 'The iPad, Napster'),
# Test for OptionResponse
('1_4_1', 'blue'),
)
@ddt.unpack
def test_find_correct_answer_text_choices(self, answer_id, answer_text):
"""
Verify that ``find_correct_answer_text`` can find the correct answer for
ChoiceResponse, MultipleChoiceResponse and OptionResponse problems.
"""
problem = new_loncapa_problem(
"""
over-suspicious
funny
The iPad
Napster
The iPod
"""
)
assert problem.find_correct_answer_text(answer_id) == answer_text
def test_find_answer_text_textinput(self):
problem = new_loncapa_problem(
"""
"""
)
assert problem.find_answer_text('1_2_1', 'hide') == 'hide'
def test_get_question_answer(self):
problem = new_loncapa_problem(
"""
Explanation
Blue is the answer.
"""
)
# Ensure that the answer is a string so that the dict returned from this
# function can eventualy be serialized to json without issues.
assert isinstance(problem.get_question_answers()['1_solution_1'], str)