671 lines
28 KiB
Python
671 lines
28 KiB
Python
"""
|
|
Tests the logic of the "targeted-feedback" attribute for MultipleChoice questions,
|
|
i.e. those with the <multiplechoiceresponse> element
|
|
"""
|
|
|
|
import textwrap
|
|
import unittest
|
|
|
|
from xmodule.capa.tests.helpers import load_fixture, mock_capa_system, new_loncapa_problem
|
|
|
|
|
|
class CapaTargetedFeedbackTest(unittest.TestCase):
|
|
"""
|
|
Testing class
|
|
"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.system = mock_capa_system()
|
|
|
|
def test_no_targeted_feedback(self):
|
|
"""Verify that no targeted feedback is shown when not finished."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse>
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback2">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 2nd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solution explanation-id="feedbackC">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</problem>
|
|
|
|
"""
|
|
)
|
|
|
|
problem = new_loncapa_problem(xml_str)
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
|
|
self.assertRegex(without_new_lines, r"<div>.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*</div>")
|
|
self.assertRegex(without_new_lines, r"feedback1|feedback2|feedback3|feedbackC")
|
|
|
|
def test_targeted_feedback_not_finished(self):
|
|
"""Check HTML output when targeted feedback is incomplete."""
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback.xml"))
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
|
|
self.assertRegex(without_new_lines, r"<div>.*'wrong-1'.*'wrong-2'.*'correct-1'.*'wrong-3'.*</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback1|feedback2|feedback3|feedbackC")
|
|
assert the_html == problem.get_html(), "Should be able to call get_html() twice"
|
|
|
|
def test_targeted_feedback_student_answer1(self):
|
|
"""Test targeted feedback rendering for a specific wrong student answer."""
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback.xml"))
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_3"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\\n", "").replace("\n", "")
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
(
|
|
r"<targetedfeedback explanation-id=\"feedback3\" role=\"group\" "
|
|
r"aria-describedby=\"1_2_1-legend\">\s*"
|
|
r"<span class=\"sr\">Incorrect</span>.*3rd WRONG solution"
|
|
),
|
|
)
|
|
self.assertNotRegex(without_new_lines, r"feedback1|feedback2|feedbackC")
|
|
# Check that calling it multiple times yields the same thing
|
|
the_html2 = problem.get_html()
|
|
assert the_html == the_html2
|
|
|
|
def test_targeted_feedback_student_answer2(self):
|
|
"""Test targeted feedback rendering for another wrong student answer."""
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback.xml"))
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\\n", "").replace("\n", "")
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
(
|
|
r"<targetedfeedback explanation-id=\"feedback1\" role=\"group\" "
|
|
r"aria-describedby=\"1_2_1-legend\">\s*"
|
|
r"<span class=\"sr\">Incorrect</span>.*1st WRONG solution"
|
|
),
|
|
)
|
|
self.assertRegex(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback2|feedback3|feedbackC")
|
|
|
|
def test_targeted_feedback_correct_answer(self):
|
|
"""Test the case of targeted feedback for a correct answer."""
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback.xml"))
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_2"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\\n", "").replace("\n", "")
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
(
|
|
r"<targetedfeedback explanation-id=\"feedbackC\" role=\"group\" "
|
|
r"aria-describedby=\"1_2_1-legend\">\s*"
|
|
r"<span class=\"sr\">Correct</span>.*Feedback on your correct solution..."
|
|
),
|
|
)
|
|
self.assertNotRegex(without_new_lines, r"feedback1|feedback2|feedback3")
|
|
|
|
def test_targeted_feedback_id_typos(self):
|
|
"""Cases where the explanation-id's don't match anything."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1TYPO">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackCTYPO">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback2">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 2nd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solution explanation-id="feedbackC">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</problem>
|
|
"""
|
|
)
|
|
|
|
# explanation-id does not match anything: fall back to empty targetedfeedbackset
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0"}
|
|
the_html = problem.get_html()
|
|
self.assertRegex(the_html, r"<targetedfeedbackset>\s*</targetedfeedbackset>")
|
|
|
|
# New problem with same XML -- try the correct choice.
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_2"} # correct
|
|
the_html = problem.get_html()
|
|
self.assertRegex(the_html, r"<targetedfeedbackset>\s*</targetedfeedbackset>")
|
|
|
|
def test_targeted_feedback_no_solution_element(self):
|
|
"""Check behavior when the solution element is missing."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false">wrong-1</choice>
|
|
<choice correct="false">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
</targetedfeedbackset>
|
|
</problem>
|
|
"""
|
|
)
|
|
|
|
# Solution element not found
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_2"}
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
# </div> right after </targetedfeedbackset>
|
|
self.assertRegex(without_new_lines, r"<div>.*<targetedfeedbackset>.*</targetedfeedbackset>\s*</div>")
|
|
|
|
def test_targeted_feedback_show_solution_explanation(self):
|
|
"""Verify that solution explanation is shown when configured to always show."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="alwaysShowCorrectChoiceExplanation">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback2">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 2nd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solution explanation-id="feedbackC">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</problem>
|
|
|
|
"""
|
|
)
|
|
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
(
|
|
r"<targetedfeedback explanation-id=\"feedback1\" role=\"group\" "
|
|
r"aria-describedby=\"1_2_1-legend\">.*1st WRONG solution"
|
|
),
|
|
)
|
|
self.assertRegex(without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC\".*solution explanation")
|
|
self.assertNotRegex(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback2|feedback3")
|
|
# Check that calling it multiple times yields the same thing
|
|
the_html2 = problem.get_html()
|
|
assert the_html == the_html2
|
|
|
|
def test_targeted_feedback_no_show_solution_explanation(self):
|
|
"""Verify that solution explanation is hidden when not configured to show."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback2">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 2nd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solution explanation-id="feedbackC">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</problem>
|
|
|
|
"""
|
|
)
|
|
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
(
|
|
r"<targetedfeedback explanation-id=\"feedback1\" role=\"group\" "
|
|
r"aria-describedby=\"1_2_1-legend\">.*1st WRONG solution"
|
|
),
|
|
)
|
|
self.assertNotRegex(without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC\".*solution explanation")
|
|
self.assertRegex(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback2|feedback3|feedbackC")
|
|
|
|
def test_targeted_feedback_with_solutionset_explanation(self):
|
|
"""Test targeted feedback when multiple correct solutions exist."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="alwaysShowCorrectChoiceExplanation">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
<choice correct="true" explanation-id="feedbackC2">correct-2</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback2">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 2nd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC2">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on the other solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solutionset>
|
|
<solution explanation-id="feedbackC2">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the other solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</solutionset>
|
|
</problem>
|
|
|
|
"""
|
|
)
|
|
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
(
|
|
r"<targetedfeedback explanation-id=\"feedback1\" role=\"group\" "
|
|
r"aria-describedby=\"1_2_1-legend\">.*1st WRONG solution"
|
|
),
|
|
)
|
|
self.assertRegex(
|
|
without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC2\".*other solution explanation"
|
|
)
|
|
self.assertNotRegex(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback2|feedback3")
|
|
|
|
def test_targeted_feedback_no_feedback_for_selected_choice1(self):
|
|
"""Check behavior when selected choice has no feedback but correct solution should show."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="alwaysShowCorrectChoiceExplanation">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solutionset>
|
|
<solution explanation-id="feedbackC">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</solutionset>
|
|
</problem>
|
|
|
|
"""
|
|
)
|
|
|
|
# The student choses one with no feedback, but alwaysShowCorrectChoiceExplanation
|
|
# is in force, so we should see the correct solution feedback.
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_1"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
|
|
self.assertRegex(without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC\".*solution explanation")
|
|
self.assertNotRegex(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback1|feedback3")
|
|
|
|
def test_targeted_feedback_no_feedback_for_selected_choice2(self):
|
|
"""Check behavior when selected choice has no feedback and no solution explanation is shown."""
|
|
xml_str = textwrap.dedent(
|
|
"""
|
|
<problem>
|
|
<p>What is the correct answer?</p>
|
|
<multiplechoiceresponse targeted-feedback="">
|
|
<choicegroup type="MultipleChoice">
|
|
<choice correct="false" explanation-id="feedback1">wrong-1</choice>
|
|
<choice correct="false" explanation-id="feedback2">wrong-2</choice>
|
|
<choice correct="true" explanation-id="feedbackC">correct-1</choice>
|
|
<choice correct="false" explanation-id="feedback3">wrong-3</choice>
|
|
</choicegroup>
|
|
</multiplechoiceresponse>
|
|
|
|
<targetedfeedbackset>
|
|
<targetedfeedback explanation-id="feedback1">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 1st WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedback3">
|
|
<div class="detailed-targeted-feedback">
|
|
<p>Targeted Feedback</p>
|
|
<p>This is the 3rd WRONG solution</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
<targetedfeedback explanation-id="feedbackC">
|
|
<div class="detailed-targeted-feedback-correct">
|
|
<p>Targeted Feedback</p>
|
|
<p>Feedback on your correct solution...</p>
|
|
</div>
|
|
</targetedfeedback>
|
|
|
|
</targetedfeedbackset>
|
|
|
|
<solutionset>
|
|
<solution explanation-id="feedbackC">
|
|
<div class="detailed-solution">
|
|
<p>Explanation</p>
|
|
<p>This is the solution explanation</p>
|
|
<p>Not much to explain here, sorry!</p>
|
|
</div>
|
|
</solution>
|
|
</solutionset>
|
|
</problem>
|
|
|
|
"""
|
|
)
|
|
|
|
# The student chooses one with no feedback set, so we check that there's no feedback.
|
|
problem = new_loncapa_problem(xml_str)
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_1"}
|
|
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
|
|
self.assertNotRegex(without_new_lines, r"<targetedfeedback explanation-id=\"feedbackC\".*solution explanation")
|
|
self.assertRegex(without_new_lines, r"<div>\{.*'1_solution_1'.*\}</div>")
|
|
self.assertNotRegex(without_new_lines, r"feedback1|feedback3|feedbackC")
|
|
|
|
def test_targeted_feedback_multiple_not_answered(self):
|
|
"""Ensure empty targeted feedback is rendered for unanswered multiple questions."""
|
|
# Not answered -> empty targeted feedback
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback_multiple.xml"))
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
# Q1 and Q2 have no feedback
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
r"<targetedfeedbackset>\s*</targetedfeedbackset>.*<targetedfeedbackset>\s*</targetedfeedbackset>",
|
|
)
|
|
|
|
def test_targeted_feedback_multiple_answer_1(self):
|
|
"""Test feedback rendering for the first question answered in a multi-question problem."""
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback_multiple.xml"))
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0"} # feedback1
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
# Q1 has feedback1 and Q2 has nothing
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
r'<targetedfeedbackset.*?>.*?explanation-id="feedback1".*?</targetedfeedbackset>.*'
|
|
+ r"<targetedfeedbackset>\s*</targetedfeedbackset>",
|
|
)
|
|
|
|
def test_targeted_feedback_multiple_answer_2(self):
|
|
"""Test feedback rendering for multiple answered questions in a multi-question problem."""
|
|
problem = new_loncapa_problem(load_fixture("targeted_feedback_multiple.xml"))
|
|
problem.done = True
|
|
problem.student_answers = {"1_2_1": "choice_0", "1_3_1": "choice_2"} # Q1 wrong, Q2 correct
|
|
the_html = problem.get_html()
|
|
without_new_lines = the_html.replace("\n", "")
|
|
# Q1 has feedback1 and Q2 has feedbackC
|
|
self.assertRegex(
|
|
without_new_lines,
|
|
r'<targetedfeedbackset.*?>.*?explanation-id="feedback1".*?</targetedfeedbackset>.*'
|
|
+ r'<targetedfeedbackset.*?>.*explanation-id="feedbackC".*?</targetedfeedbackset>',
|
|
)
|