Commit e639738e by Nick Parlante Committed by Jason Bau

Update answer-pool within Jeffs 3x

-Moved answer-pool attribute down to <choicegroup>
-Refactored the randomization, requiring a lot of
updated tests :(
parent 5e9f6844
...@@ -136,7 +136,7 @@ duplicated "\index". ...@@ -136,7 +136,7 @@ duplicated "\index".
Studio: Support answer pools for multiple choice question choices, so authors can provide Studio: Support answer pools for multiple choice question choices, so authors can provide
multiple incorrect and correct choices for a question and have 1 correct choice and n-1 multiple incorrect and correct choices for a question and have 1 correct choice and n-1
incorrect choices randomly selected and shuffled before being presented to the student. incorrect choices randomly selected and shuffled before being presented to the student.
In XML: <multiplechoiceresponse answer-pool="4"> enables an answer pool of 4 choices: 3 In XML: <choicegroup answer-pool="4"> enables an answer pool of 4 choices: 3
correct choices and 1 incorrect choice. To provide multiple solution expanations, wrap correct choices and 1 incorrect choice. To provide multiple solution expanations, wrap
all solution elements within a <solutionset>, and make sure to add an attribute called all solution elements within a <solutionset>, and make sure to add an attribute called
"explanation-id" to both the <solution> tag and its corresponding <choice> tag, and be "explanation-id" to both the <solution> tag and its corresponding <choice> tag, and be
......
...@@ -383,103 +383,92 @@ class LoncapaProblem(object): ...@@ -383,103 +383,92 @@ class LoncapaProblem(object):
answer_ids.append(results.keys()) answer_ids.append(results.keys())
return answer_ids return answer_ids
def sample_from_answer_pool(self, choices, rnd, num_choices): def sample_from_answer_pool(self, choices, rnd, num_pool):
""" """
Takes in: Takes in:
1. list of choices 1. list of choices
2. random number generator 2. random number generator
3. max number of total choices to return 3. the requested size "answer-pool" number, in effect a max
Returns a list with 2 items: Returns a tuple with 2 items:
1. the solution_id corresponding with the chosen correct answer 1. the solution_id corresponding with the chosen correct answer
2. (subset) list of choice nodes with 3 incorrect and 1 correct 2. (subset) list of choice nodes with num-1 incorrect and 1 correct
""" """
correct_choices = [] correct_choices = []
incorrect_choices = [] incorrect_choices = []
subset_choices = []
for choice in choices: for choice in choices:
if choice.get('correct') == 'true': if choice.get('correct') == 'true':
correct_choices.append(choice) correct_choices.append(choice)
elif choice.get('correct') == 'false': else:
incorrect_choices.append(choice) incorrect_choices.append(choice)
# TODO: check if we should require correct == "false"
# Always 1 correct and num_choices at least as large as this; if not, return list with no choices # We throw an error if the problem is highly ill-formed.
num_correct = 1 # There must be at least one correct and one incorrect choice.
if len(correct_choices) < num_correct or num_choices < num_correct: # TODO: perhaps this makes more sense for *all* problems, not just down in this corner.
return [] if len(correct_choices) < 1 or len(incorrect_choices) < 1:
raise responsetypes.LoncapaProblemError("Choicegroup must include at last 1 correct and 1 incorrect choice")
# Ensure number of incorrect choices is no more than the number of incorrect choices to choose from # Limit the number of incorrect choices to what we actually have
num_incorrect = num_choices - num_correct num_incorrect = num_pool - 1
num_incorrect = min(num_incorrect, len(incorrect_choices)) num_incorrect = min(num_incorrect, len(incorrect_choices))
# Use rnd given to us to generate a random number (see details in tree_using_answer_pool method) # Select the one correct choice
index = rnd.randint(0, len(correct_choices) - 1) index = rnd.randint(0, len(correct_choices) - 1)
correct_choice = correct_choices[index] correct_choice = correct_choices[index]
subset_choices.append(correct_choice)
solution_id = correct_choice.get('explanation-id') solution_id = correct_choice.get('explanation-id')
# Add incorrect choices # Put together the result, pushing most of the work onto rnd.shuffle()
to_add = num_incorrect subset_choices = [correct_choice]
while to_add > 0: rnd.shuffle(incorrect_choices)
index = rnd.randint(0, len(incorrect_choices) - 1) subset_choices += incorrect_choices[:num_incorrect]
choice = incorrect_choices[index] rnd.shuffle(subset_choices)
subset_choices.append(choice)
incorrect_choices.remove(choice) return (solution_id, subset_choices)
to_add = to_add - 1
def do_answer_pool(self, tree):
# Randomize correct answer position
index = rnd.randint(0, num_incorrect)
if index != 0:
tmp = subset_choices[index]
subset_choices[index] = subset_choices[0] # where we put the correct answer
subset_choices[0] = tmp
return [solution_id, subset_choices]
def tree_using_answer_pool(self, tree):
""" """
Implements the answer-pool subsetting operation in-place on the tree.
Allows for problem questions with a pool of answers, from which answer options shown to the student Allows for problem questions with a pool of answers, from which answer options shown to the student
and randomly selected so that there is always 1 correct answer and n-1 incorrect answers, and randomly selected so that there is always 1 correct answer and n-1 incorrect answers,
where the user specifies n as the value of the attribute "answer-pool" within <multiplechoiceresponse> where the author specifies n as the value of the attribute "answer-pool" within <choicegroup>
The <multiplechoiceresponse> tag must have an attribute 'answer-pool' with integer value of n
- if so, this method will modify the tree
- if not, this method will not modify the tree
These problems are colloquially known as "Gradiance" problems. The <choicegroup> tag must have an attribute 'answer-pool' giving the desired
pool size. If that attribute is zero or not present, no operation is performed.
Calling this a second time does nothing.
""" """
query = '//multiplechoiceresponse[@answer-pool]' # If called a second time, don't do anything, since it's in-place destructive
if hasattr(self, 'answerpool_done'):
# There are no questions with an answer pool
if not tree.xpath(query):
return return
self.answerpool_done = True
choicegroups = tree.xpath("//choicegroup[@answer-pool]")
# Uses self.seed -- but want to randomize every time reaches this problem, # Uses self.seed -- but want to randomize every time reaches this problem,
# so problem's "randomization" should be set to "always" # so problem's "randomization" should be set to "always"
rnd = Random(self.seed) rnd = Random(self.seed)
for mult_choice_response in tree.xpath(query): for choicegroup in choicegroups:
# Determine number of choices to display; if invalid number of choices, skip over num_str = choicegroup.get('answer-pool')
num_choices = mult_choice_response.get('answer-pool') try:
if not num_choices.isdigit(): num_choices = int(num_str)
continue except ValueError:
num_choices = int(num_choices) raise responsetypes.LoncapaProblemError("answer-pool value should be an integer")
if num_choices < 1: # choices == 0 disables the feature
continue if num_choices == 0:
break
# Grab the first choicegroup (there should only be one within each <multiplechoiceresponse> tag) choices_list = list(choicegroup.getchildren())
choicegroup = mult_choice_response.xpath('./choicegroup[@type="MultipleChoice"]')[0]
choices_list = list(choicegroup.iter('choice'))
# Remove all choices in the choices_list (we will add some back in later) # Remove all choices in the choices_list (we will add some back in later)
for choice in choices_list: for choice in choices_list:
choicegroup.remove(choice) choicegroup.remove(choice)
# Sample from the answer pool to get the subset choices and solution id # Sample from the answer pool to get the subset choices and solution id
[solution_id, subset_choices] = self.sample_from_answer_pool(choices_list, rnd, num_choices) (solution_id, subset_choices) = self.sample_from_answer_pool(choices_list, rnd, num_choices)
# Add back in randomly selected choices # Add back in randomly selected choices
for choice in subset_choices: for choice in subset_choices:
...@@ -487,7 +476,7 @@ class LoncapaProblem(object): ...@@ -487,7 +476,7 @@ class LoncapaProblem(object):
# Filter out solutions that don't correspond to the correct answer we selected to show # Filter out solutions that don't correspond to the correct answer we selected to show
# Note that this means that if the user simply provides a <solution> tag, nothing is filtered # Note that this means that if the user simply provides a <solution> tag, nothing is filtered
solutionset = mult_choice_response.xpath('./following-sibling::solutionset') solutionset = choicegroup.xpath('../following-sibling::solutionset')
if len(solutionset) != 0: if len(solutionset) != 0:
solutionset = solutionset[0] solutionset = solutionset[0]
solutions = solutionset.xpath('./solution') solutions = solutionset.xpath('./solution')
...@@ -495,7 +484,7 @@ class LoncapaProblem(object): ...@@ -495,7 +484,7 @@ class LoncapaProblem(object):
if solution.get('explanation-id') != solution_id: if solution.get('explanation-id') != solution_id:
solutionset.remove(solution) solutionset.remove(solution)
def tree_using_targeted_feedback(self, tree): def do_targeted_feedback(self, tree):
""" """
Allows for problem questions to show targeted feedback, which are choice-level explanations. Allows for problem questions to show targeted feedback, which are choice-level explanations.
Targeted feedback is automatically visible after a student has submitted their answers. Targeted feedback is automatically visible after a student has submitted their answers.
...@@ -511,7 +500,11 @@ the "Show Answer" setting to "Never" because now there's no need for a "Show Ans ...@@ -511,7 +500,11 @@ the "Show Answer" setting to "Never" because now there's no need for a "Show Ans
button because no solution will show up if you were to click the "Show Answer" button button because no solution will show up if you were to click the "Show Answer" button
""" """
# Note that if there are no questions with targeted feedback, the body of the for loop is not executed # If called a second time, don't do anything, since it's in-place destructive
if hasattr(self, 'targeted_done'):
return
self.targeted_done = True
for mult_choice_response in tree.xpath('//multiplechoiceresponse[@targeted-feedback]'): for mult_choice_response in tree.xpath('//multiplechoiceresponse[@targeted-feedback]'):
show_explanation = mult_choice_response.get('targeted-feedback') == 'alwaysShowCorrectChoiceExplanation' show_explanation = mult_choice_response.get('targeted-feedback') == 'alwaysShowCorrectChoiceExplanation'
...@@ -578,8 +571,8 @@ button because no solution will show up if you were to click the "Show Answer" b ...@@ -578,8 +571,8 @@ button because no solution will show up if you were to click the "Show Answer" b
''' '''
Main method called externally to get the HTML to be rendered for this capa Problem. Main method called externally to get the HTML to be rendered for this capa Problem.
''' '''
self.tree_using_answer_pool(self.tree) self.do_answer_pool(self.tree)
self.tree_using_targeted_feedback(self.tree) self.do_targeted_feedback(self.tree)
html = contextualize_text(etree.tostring(self._extract_html(self.tree)), self.context) html = contextualize_text(etree.tostring(self._extract_html(self.tree)), self.context)
return html return html
......
""" """
Tests the logic of the "answer-pool" attribute for MultipleChoice questions, Tests the logic of the "answer-pool" attribute, e.g.
i.e. those with the <multiplechoiceresponse> element <choicegroup answer-pool="4"
""" """
import unittest import unittest
import textwrap import textwrap
from . import test_system, new_loncapa_problem from . import test_system, new_loncapa_problem
from capa.responsetypes import LoncapaProblemError
class CapaAnswerPoolTest(unittest.TestCase): class CapaAnswerPoolTest(unittest.TestCase):
...@@ -22,8 +24,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -22,8 +24,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="4"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="4">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -57,16 +59,20 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -57,16 +59,20 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem = new_loncapa_problem(xml_str) problem = new_loncapa_problem(xml_str)
problem.seed = 723 problem.seed = 723
the_html = problem.get_html() the_html = problem.get_html()
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-3'.*'wrong-1'.*'wrong-2'.*'correct-2'.*\].*</div>") # [('choice_3', u'wrong-3'), ('choice_5', u'correct-2'), ('choice_1', u'wrong-2'), ('choice_4', u'wrong-4')]
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-3'.*'correct-2'.*'wrong-2'.*'wrong-4'.*\].*</div>")
self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_2'.*\}</div>") self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_2'.*\}</div>")
# Calling get_html multiple times should yield the same thing
the_html2 = problem.get_html()
self.assertEquals(the_html, the_html2)
def test_answer_pool_4_choices_1_multiplechoiceresponse_seed2(self): def test_answer_pool_4_choices_1_multiplechoiceresponse_seed2(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="4"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="4">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -100,7 +106,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -100,7 +106,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem = new_loncapa_problem(xml_str) problem = new_loncapa_problem(xml_str)
problem.seed = 9 problem.seed = 9
the_html = problem.get_html() the_html = problem.get_html()
self.assertRegexpMatches(the_html, r"<div>.*\[.*'correct-1'.*'wrong-2'.*'wrong-1'.*'wrong-4'.*\].*</div>") # [('choice_0', u'wrong-1'), ('choice_4', u'wrong-4'), ('choice_3', u'wrong-3'), ('choice_2', u'correct-1')]
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-1'.*'wrong-4'.*'wrong-3'.*'correct-1'.*\].*</div>")
self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*\}</div>") self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*\}</div>")
def test_no_answer_pool_4_choices_1_multiplechoiceresponse(self): def test_no_answer_pool_4_choices_1_multiplechoiceresponse(self):
...@@ -150,8 +157,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -150,8 +157,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="0"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="0">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -187,13 +194,13 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -187,13 +194,13 @@ class CapaAnswerPoolTest(unittest.TestCase):
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*'wrong-4'.*'correct-2'.*\].*</div>") self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*'wrong-4'.*'correct-2'.*\].*</div>")
self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*'1_solution_2'.*\}</div>") self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*'1_solution_2'.*\}</div>")
def test_invalid_answer_pool_4_choices_1_multiplechoiceresponse(self): def test_invalid_answer_pool(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="2.3"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="2.3">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -225,17 +232,16 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -225,17 +232,16 @@ class CapaAnswerPoolTest(unittest.TestCase):
""") """)
problem = new_loncapa_problem(xml_str) problem = new_loncapa_problem(xml_str)
the_html = problem.get_html() with self.assertRaises(LoncapaProblemError):
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*'wrong-4'.*'correct-2'.*\].*</div>") the_html = problem.get_html()
self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*'1_solution_2'.*\}</div>")
def test_answer_pool_5_choices_1_multiplechoiceresponse_seed1(self): def test_answer_pool_5_choices_1_multiplechoiceresponse_seed1(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="5"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="5">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -269,7 +275,7 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -269,7 +275,7 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem = new_loncapa_problem(xml_str) problem = new_loncapa_problem(xml_str)
problem.seed = 723 problem.seed = 723
the_html = problem.get_html() the_html = problem.get_html()
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-2'.*'wrong-1'.*'correct-2'.*'wrong-3'.*'wrong-4'.*\].*</div>") self.assertRegexpMatches(the_html, r"<div>.*\[.*'correct-2'.*'wrong-1'.*'wrong-2'.*.*'wrong-3'.*'wrong-4'.*\].*</div>")
self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_2'.*\}</div>") self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_2'.*\}</div>")
def test_answer_pool_2_multiplechoiceresponses_seed1(self): def test_answer_pool_2_multiplechoiceresponses_seed1(self):
...@@ -277,8 +283,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -277,8 +283,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="4"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="4">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -306,8 +312,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -306,8 +312,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
</solutionset> </solutionset>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="3"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="3">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -342,8 +348,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -342,8 +348,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem.seed = 723 problem.seed = 723
the_html = problem.get_html() the_html = problem.get_html()
str1 = r"<div>.*\[.*'wrong-3'.*'wrong-1'.*'wrong-2'.*'correct-2'.*\].*</div>" str1 = r"<div>.*\[.*'wrong-3'.*'correct-2'.*'wrong-2'.*'wrong-4'.*\].*</div>"
str2 = r"<div>.*\[.*'wrong-4'.*'wrong-2'.*'correct-2'.*\].*</div>" str2 = r"<div>.*\[.*'wrong-2'.*'wrong-1'.*'correct-2'.*\].*</div>"
str3 = r"<div>\{.*'1_solution_2'.*\}</div>" str3 = r"<div>\{.*'1_solution_2'.*\}</div>"
str4 = r"<div>\{.*'1_solution_4'.*\}</div>" str4 = r"<div>\{.*'1_solution_4'.*\}</div>"
...@@ -362,8 +368,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -362,8 +368,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="3"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="3">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -391,8 +397,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -391,8 +397,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
</solutionset> </solutionset>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="4"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="4">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -427,10 +433,10 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -427,10 +433,10 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem.seed = 9 problem.seed = 9
the_html = problem.get_html() the_html = problem.get_html()
str1 = r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*\].*</div>" str1 = r"<div>.*\[.*'wrong-4'.*'wrong-3'.*'correct-1'.*\].*</div>"
str2 = r"<div>.*\[.*'wrong-4'.*'wrong-3'.*'correct-1'.*'wrong-1'.*\].*</div>" str2 = r"<div>.*\[.*'wrong-2'.*'wrong-3'.*'wrong-4'.*'correct-2'.*\].*</div>"
str3 = r"<div>\{.*'1_solution_1'.*\}</div>" str3 = r"<div>\{.*'1_solution_1'.*\}</div>"
str4 = r"<div>\{.*'1_solution_3'.*\}</div>" str4 = r"<div>\{.*'1_solution_4'.*\}</div>"
self.assertRegexpMatches(the_html, str1) self.assertRegexpMatches(the_html, str1)
self.assertRegexpMatches(the_html, str2) self.assertRegexpMatches(the_html, str2)
...@@ -466,8 +472,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -466,8 +472,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
</solution> </solution>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="4"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="4">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true" explanation-id="solution1">correct-1</choice> <choice correct="true" explanation-id="solution1">correct-1</choice>
...@@ -503,7 +509,7 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -503,7 +509,7 @@ class CapaAnswerPoolTest(unittest.TestCase):
the_html = problem.get_html() the_html = problem.get_html()
str1 = r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*'wrong-4'.*\].*</div>" str1 = r"<div>.*\[.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*'wrong-4'.*\].*</div>"
str2 = r"<div>.*\[.*'wrong-3'.*'wrong-1'.*'wrong-2'.*'correct-2'.*\].*</div>" str2 = r"<div>.*\[.*'wrong-3'.*'correct-2'.*'wrong-2'.*'wrong-4'.*\].*</div>"
str3 = r"<div>\{.*'1_solution_1'.*\}</div>" str3 = r"<div>\{.*'1_solution_1'.*\}</div>"
str4 = r"<div>\{.*'1_solution_3'.*\}</div>" str4 = r"<div>\{.*'1_solution_3'.*\}</div>"
...@@ -522,8 +528,8 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -522,8 +528,8 @@ class CapaAnswerPoolTest(unittest.TestCase):
<problem> <problem>
<p>What is the correct answer?</p> <p>What is the correct answer?</p>
<multiplechoiceresponse answer-pool="4"> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice" answer-pool="4">
<choice correct="false">wrong-1</choice> <choice correct="false">wrong-1</choice>
<choice correct="false">wrong-2</choice> <choice correct="false">wrong-2</choice>
<choice correct="true">correct-1</choice> <choice correct="true">correct-1</choice>
...@@ -549,5 +555,5 @@ class CapaAnswerPoolTest(unittest.TestCase): ...@@ -549,5 +555,5 @@ class CapaAnswerPoolTest(unittest.TestCase):
problem.seed = 723 problem.seed = 723
the_html = problem.get_html() the_html = problem.get_html()
self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-3'.*'wrong-1'.*'wrong-2'.*'correct-2'.*\].*</div>") self.assertRegexpMatches(the_html, r"<div>.*\[.*'wrong-3'.*'correct-2'.*'wrong-2'.*'wrong-4'.*\].*</div>")
self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*\}</div>") self.assertRegexpMatches(the_html, r"<div>\{.*'1_solution_1'.*\}</div>")
...@@ -142,6 +142,9 @@ class CapaTargetedFeedbackTest(unittest.TestCase): ...@@ -142,6 +142,9 @@ class CapaTargetedFeedbackTest(unittest.TestCase):
self.assertRegexpMatches(without_new_lines, r"<div>.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*</div>") self.assertRegexpMatches(without_new_lines, r"<div>.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*</div>")
self.assertNotRegexpMatches(without_new_lines, r"feedback1|feedback2|feedback3|feedbackC") self.assertNotRegexpMatches(without_new_lines, r"feedback1|feedback2|feedback3|feedbackC")
# Check that calling it multiple times yields the same thing
the_html2 = problem.get_html()
self.assertEquals(the_html, the_html2)
def test_targeted_feedback_student_answer1(self): def test_targeted_feedback_student_answer1(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
...@@ -207,6 +210,9 @@ class CapaTargetedFeedbackTest(unittest.TestCase): ...@@ -207,6 +210,9 @@ class CapaTargetedFeedbackTest(unittest.TestCase):
self.assertRegexpMatches(without_new_lines, r"<targetedfeedback explanation-id=\"feedback3\">.*3rd WRONG solution") self.assertRegexpMatches(without_new_lines, r"<targetedfeedback explanation-id=\"feedback3\">.*3rd WRONG solution")
self.assertNotRegexpMatches(without_new_lines, r"feedback1|feedback2|feedbackC") self.assertNotRegexpMatches(without_new_lines, r"feedback1|feedback2|feedbackC")
# Check that calling it multiple times yields the same thing
the_html2 = problem.get_html()
self.assertEquals(the_html, the_html2)
def test_targeted_feedback_student_answer2(self): def test_targeted_feedback_student_answer2(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
...@@ -340,6 +346,9 @@ class CapaTargetedFeedbackTest(unittest.TestCase): ...@@ -340,6 +346,9 @@ class CapaTargetedFeedbackTest(unittest.TestCase):
self.assertRegexpMatches(without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC\".*solution explanation") self.assertRegexpMatches(without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC\".*solution explanation")
self.assertNotRegexpMatches(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>") self.assertNotRegexpMatches(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
self.assertNotRegexpMatches(without_new_lines, r"feedback2|feedback3") self.assertNotRegexpMatches(without_new_lines, r"feedback2|feedback3")
# Check that calling it multiple times yields the same thing
the_html2 = problem.get_html()
self.assertEquals(the_html, the_html2)
def test_targeted_feedback_no_show_solution_explanation(self): def test_targeted_feedback_no_show_solution_explanation(self):
xml_str = textwrap.dedent(""" xml_str = textwrap.dedent("""
......
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