diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 161f04155e..bd86b72796 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -851,31 +851,30 @@ class ChoiceResponse(LoncapaResponse): def setup_response(self): self.assign_choice_names() - correct_xml = self.xml.xpath( - '//*[@id=$id]//choice[@correct="true"]', - id=self.xml.get('id') - ) + self.correct_choices = set() + self.incorrect_choices = set() + for choice in self.get_choices(): - self.correct_choices = set([ - choice.get('name') for choice in correct_xml - ]) + # contextualize the name and correct attributes + name = contextualize_text(choice.get('name'), self.context) + correct = contextualize_text(choice.get('correct'), self.context).upper() - incorrect_xml = self.xml.xpath( - '//*[@id=$id]//choice[@correct="false"]', - id=self.xml.get('id') - ) + # divide choices into correct and incorrect + if correct == 'TRUE': + self.correct_choices.add(name) + elif correct == 'FALSE': + self.incorrect_choices.add(name) - self.incorrect_choices = set([ - choice.get('name') for choice in incorrect_xml - ]) + def get_choices(self): + """Returns this response's XML choice elements.""" + return self.xml.xpath('//*[@id=$id]//choice', id=self.xml.get('id')) def assign_choice_names(self): """ Initialize name attributes in tags for this response. """ - for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice', - id=self.xml.get('id'))): + for index, choice in enumerate(self.get_choices()): choice.set("name", "choice_" + str(index)) # If a choice does not have an id, assign 'A' 'B', .. used by CompoundHint if not choice.get('id'): diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index b01dbf4fc8..65e4311fbe 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -172,6 +172,8 @@ class ResponseXMLFactory(object): correctness = 'false' elif 'partial' in correct_val: correctness = 'partial' + else: + correctness = correct_val choice_element.set('correct', correctness) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index e3f486ccbf..54caaa783c 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -169,6 +169,35 @@ class MultiChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstrin correct_map = problem.grade_answers({'1_2_1': 'choice_2'}) self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0) + def test_contextualized_choices(self): + script = textwrap.dedent(""" + a = 2 + b = 9 + c = a + b + + ok0 = c % 2 == 0 # check remainder modulo 2 + text0 = "$a + $b is even" + + ok1 = c % 2 == 1 # check remainder modulo 2 + text1 = "$a + $b is odd" + + ok2 = "partial" + text2 = "infinity may be both" + """) + choices = ["$ok0", "$ok1", "$ok2"] + choice_names = ["$text0 ... (should be $ok0)", + "$text1 ... (should be $ok1)", + "$text2 ... (should be $ok2)"] + problem = self.build_problem(script=script, + choices=choices, + choice_names=choice_names, + credit_type='points') + + # Ensure the expected correctness and choice names + self.assert_grade(problem, 'choice_2 + 9 is even ... (should be False)', 'incorrect') + self.assert_grade(problem, 'choice_2 + 9 is odd ... (should be True)', 'correct') + self.assert_grade(problem, 'choice_infinity may be both ... (should be partial)', 'partially-correct') + class TrueFalseResponseTest(ResponseTest): # pylint: disable=missing-docstring xml_factory_class = TrueFalseResponseXMLFactory @@ -1292,6 +1321,26 @@ class ChoiceResponseTest(ResponseTest): # pylint: disable=missing-docstring correct_map = problem.grade_answers({}) self.assertEqual(correct_map.get_correctness('1_2_1'), 'incorrect') + def test_contextualized_choices(self): + script = textwrap.dedent(""" + a = 6 + b = 4 + c = a + b + + ok0 = c % 2 == 0 # check remainder modulo 2 + ok1 = c % 3 == 0 # check remainder modulo 3 + ok2 = c % 5 == 0 # check remainder modulo 5 + ok3 = not any([ok0, ok1, ok2]) + """) + choices = ["$ok0", "$ok1", "$ok2", "$ok3"] + problem = self.build_problem(script=script, + choice_type='checkbox', + choices=choices) + + # Ensure the expected correctness + self.assert_grade(problem, ['choice_0', 'choice_2'], 'correct') + self.assert_grade(problem, ['choice_1', 'choice_3'], 'incorrect') + class JavascriptResponseTest(ResponseTest): # pylint: disable=missing-docstring xml_factory_class = JavascriptResponseXMLFactory