"""Tests the capa shuffle and name-masking."""
import textwrap
import unittest
from xmodule.capa.responsetypes import LoncapaProblemError
from xmodule.capa.tests.helpers import mock_capa_system, new_loncapa_problem
class CapaShuffleTest(unittest.TestCase):
"""Capa problem tests for shuffling and choice-name masking."""
def setUp(self):
super().setUp()
self.system = mock_capa_system()
def test_shuffle_4_choices(self):
"""Verify shuffling and name-masking for four choices."""
xml_str = textwrap.dedent(
"""
Apple
Banana
Chocolate
Donut
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
# shuffling 4 things with seed of 0 yields: B A C D
# Check that the choices are shuffled
the_html = problem.get_html()
self.assertRegex(the_html, r"
.*\[.*'Banana'.*'Apple'.*'Chocolate'.*'Donut'.*\].*
")
# Check that choice name masking is enabled and that unmasking works
response = list(problem.responders.values())[0]
assert not response.has_mask()
assert response.unmask_order() == ["choice_1", "choice_0", "choice_2", "choice_3"]
assert the_html == problem.get_html(), "should be able to call get_html() twice"
def test_shuffle_custom_names(self):
"""Verify shuffling preserves custom choice names."""
xml_str = textwrap.dedent(
"""
Apple
Banana
Chocolate
Donut
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
# B A C D
# Check that the custom name= names come through
response = list(problem.responders.values())[0]
assert not response.has_mask()
assert response.has_shuffle()
assert response.unmask_order() == ["choice_0", "choice_aaa", "choice_1", "choice_ddd"]
def test_shuffle_different_seed(self):
"""Check that shuffling produces different order with a different seed."""
xml_str = textwrap.dedent(
"""
Apple
Banana
Chocolate
Donut
"""
)
problem = new_loncapa_problem(xml_str, seed=341) # yields D A B C
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'Donut'.*'Apple'.*'Banana'.*'Chocolate'.*\].*
")
def test_shuffle_1_choice(self):
"""Verify shuffling behavior with only one choice."""
xml_str = textwrap.dedent(
"""
Apple
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'Apple'.*\].*
")
response = list(problem.responders.values())[0]
assert not response.has_mask()
assert response.has_shuffle()
assert response.unmask_order() == ["choice_0"]
def test_shuffle_6_choices(self):
"""Test shuffling with six choices to ensure proper randomization."""
xml_str = textwrap.dedent(
"""
Apple
Banana
Chocolate
Zonut
Eggplant
Filet Mignon
"""
)
problem = new_loncapa_problem(xml_str, seed=0) # yields: C E A B D F
# Donut -> Zonut to show that there is not some hidden alphabetic ordering going on
the_html = problem.get_html()
self.assertRegex(
the_html, r".*\[.*'Chocolate'.*'Eggplant'.*'Apple'.*'Banana'.*'Zonut'.*'Filet Mignon'.*\].*
"
)
def test_shuffle_false(self):
"""Verify that shuffle='false' keeps original order."""
xml_str = textwrap.dedent(
"""
Apple
Banana
Chocolate
Donut
"""
)
problem = new_loncapa_problem(xml_str)
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'Apple'.*'Banana'.*'Chocolate'.*'Donut'.*\].*
")
response = list(problem.responders.values())[0]
assert not response.has_mask()
assert not response.has_shuffle()
def test_shuffle_fixed_head_end(self):
"""Ensure choices fixed at the head remain in place during shuffle."""
xml_str = textwrap.dedent(
"""
Alpha
Beta
A
B
C
D
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
# Alpha Beta held back from shuffle (head end)
self.assertRegex(the_html, r".*\[.*'Alpha'.*'Beta'.*'B'.*'A'.*'C'.*'D'.*\].*
")
def test_shuffle_fixed_tail_end(self):
"""Ensure choices fixed at the tail remain in place during shuffle."""
xml_str = textwrap.dedent(
"""
A
B
C
D
Alpha
Beta
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
# Alpha Beta held back from shuffle (tail end)
self.assertRegex(the_html, r".*\[.*'B'.*'A'.*'C'.*'D'.*'Alpha'.*'Beta'.*\].*
")
def test_shuffle_fixed_both_ends(self):
"""Ensure choices fixed at both ends remain in place during shuffle."""
xml_str = textwrap.dedent(
"""
Alpha
Beta
A
B
C
D
Psi
Omega
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'Alpha'.*'Beta'.*'B'.*'A'.*'C'.*'D'.*'Psi'.*'Omega'.*\].*
")
def test_shuffle_fixed_both_ends_thin(self):
"""Test shuffling with only three choices, two of which are fixed."""
xml_str = textwrap.dedent(
"""
Alpha
A
Omega
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'Alpha'.*'A'.*'Omega'.*\].*
")
def test_shuffle_fixed_all(self):
"""Verify that all fixed choices remain in order with no shuffle."""
xml_str = textwrap.dedent(
"""
A
B
C
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'A'.*'B'.*'C'.*\].*
")
def test_shuffle_island(self):
"""A fixed 'island' choice not at the head or tail end gets lumped into the tail end."""
xml_str = textwrap.dedent(
"""
A
Mid
C
Mid
D
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
the_html = problem.get_html()
self.assertRegex(the_html, r".*\[.*'A'.*'Mid'.*'Mid'.*'C'.*'D'.*\].*
")
def test_multiple_shuffle_responses(self):
"""Check shuffling for multiple choice groups in the same problem."""
xml_str = textwrap.dedent(
"""
Apple
Banana
Chocolate
Donut
Here is some text
A
B
C
D
"""
)
problem = new_loncapa_problem(xml_str, seed=0)
orig_html = problem.get_html()
assert orig_html == problem.get_html(), "should be able to call get_html() twice"
html = orig_html.replace("\n", " ") # avoid headaches with .* matching
self.assertRegex(
html,
r".*\[.*'Banana'.*'Apple'.*'Chocolate'.*'Donut'.*\].*
.*"
+ r".*\[.*'C'.*'A'.*'D'.*'B'.*\].*
",
)
# Look at the responses in their authored order
responses = sorted(list(problem.responders.values()), key=lambda resp: int(resp.id[resp.id.rindex("_") + 1 :]))
assert not responses[0].has_mask()
assert responses[0].has_shuffle()
assert responses[1].has_shuffle()
assert responses[0].unmask_order() == ["choice_1", "choice_0", "choice_2", "choice_3"]
assert responses[1].unmask_order() == ["choice_2", "choice_0", "choice_3", "choice_1"]
def test_shuffle_not_with_answerpool(self):
"""Raise error if shuffle and answer-pool are both used."""
xml_str = textwrap.dedent(
"""
A
Mid
C
Mid
D
"""
)
with self.assertRaisesRegex(LoncapaProblemError, "shuffle and answer-pool"):
new_loncapa_problem(xml_str)