diff --git a/common/lib/capa/capa/verifiers/draganddrop.py b/common/lib/capa/capa/verifiers/draganddrop.py index 44e50828f5..7ad1ce35eb 100644 --- a/common/lib/capa/capa/verifiers/draganddrop.py +++ b/common/lib/capa/capa/verifiers/draganddrop.py @@ -119,9 +119,24 @@ class DragAndDrop(object): if not self.excess_draggables[draggable]: return False # user answer has more draggables than correct answer - # Number of draggables in user_groups may be smaller that in - # correct_groups, that is incorrect. + # Number of draggables in user_groups may be differ that in + # correct_groups, that is incorrect, except special case with 'number' for groupname, draggable_ids in self.correct_groups.items(): + + # 'number' rule special case + # for reusable draggables we may get in self.user_groups + # {'1': [u'2', u'2', u'2'], '0': [u'1', u'1'], '2': [u'3']} + # if +number in rule - do not remove duplicates but clean rule + current_rule = self.correct_positions[groupname].keys()[0] + if 'number' in current_rule: + rule_values = self.correct_positions[groupname][current_rule] + #clean rule, do not do clean dublicate items + self.correct_positions[groupname].pop(current_rule, None) + parsed_rule = current_rule.replace('+', '').replace('number', '') + self.correct_positions[groupname][parsed_rule] = rule_values + else: # remove dublicates + self.user_groups[groupname] = list(set(self.user_groups[groupname])) + if sorted(draggable_ids) != sorted(self.user_groups[groupname]): return False @@ -129,8 +144,9 @@ class DragAndDrop(object): # every element are equal with correct positions for groupname in self.correct_groups: rules_executed = 0 - for rule in ('exact', 'anyof'): # every group has only one rule - if self.correct_positions[groupname].get(rule, []): + for rule in ('exact', 'anyof', 'unorderly_equal'): + # every group has only one rule + if self.correct_positions[groupname].get(rule, None): rules_executed += 1 if not self.compare_positions( self.correct_positions[groupname][rule], @@ -174,6 +190,8 @@ class DragAndDrop(object): - draggables can be placed in any order: user ['1','2','3','4'] is 'anyof' equal to ['4', '2', '1', 3'] + 'unorderly_equal' is same as 'exact' but disregards on order + Equality functions: Equality functon depends on type of element. They declared in @@ -193,14 +211,24 @@ class DragAndDrop(object): return False if flag == 'anyof': - count = 0 for u_el in user: for c_el in correct: if PositionsCompare(u_el) == PositionsCompare(c_el): - count += 1 break - if count != len(user): + else: + return False + + if flag == 'unorderly_equal': + if len(correct) != len(user): return False + temp = correct[:] + for u_el in user: + for c_el in temp: + if PositionsCompare(u_el) == PositionsCompare(c_el): + temp.remove(c_el) + break + else: + return False return True diff --git a/common/lib/capa/capa/verifiers/tests.py b/common/lib/capa/capa/verifiers/tests_draganddrop.py similarity index 54% rename from common/lib/capa/capa/verifiers/tests.py rename to common/lib/capa/capa/verifiers/tests_draganddrop.py index c744734995..257d404679 100644 --- a/common/lib/capa/capa/verifiers/tests.py +++ b/common/lib/capa/capa/verifiers/tests_draganddrop.py @@ -54,21 +54,25 @@ class Test_DragAndDrop_Grade(unittest.TestCase): def test_multiple_images_per_target_true(self): user_input = '{\ - "draggables": [{"1": "t1"}, {"name_with_icon": "t1"}]}' - correct_answer = {'1': 't1', 'name_with_icon': 't1'} + "draggables": [{"1": "t1"}, {"name_with_icon": "t2"}, \ + {"2": "t1"}]}' + correct_answer = {'1': 't1', 'name_with_icon': 't2', + '2': 't1'} self.assertTrue(draganddrop.grade(user_input, correct_answer)) def test_multiple_images_per_target_false(self): user_input = '{\ - "draggables": [{"1": "t1"}, {"name_with_icon": "t1"}]}' - correct_answer = {'1': 't2', 'name_with_icon': 't1'} + "draggables": [{"1": "t1"}, {"name_with_icon": "t2"}, \ + {"2": "t1"}]}' + correct_answer = {'1': 't2', 'name_with_icon': 't2', + '2': 't1'} self.assertFalse(draganddrop.grade(user_input, correct_answer)) def test_targets_and_positions(self): user_input = '{"draggables": [{"1": [10,10]}, \ {"name_with_icon": [[10,10],4]}]}' correct_answer = {'1': [10, 10], 'name_with_icon': [[10, 10], 4]} - self.assertFalse(draganddrop.grade(user_input, correct_answer)) + self.assertTrue(draganddrop.grade(user_input, correct_answer)) def test_position_and_targets(self): user_input = '{"draggables": [{"1": "t1"}, {"name_with_icon": "t2"}]}' @@ -117,6 +121,7 @@ class Test_DragAndDrop_Grade(unittest.TestCase): right upper corner.""" user_input = '{"draggables": \ [{"ant":[610.5,57.449951171875]},{"grass":[322.5,199.449951171875]}]}' + correct_answer = {'grass': [[300, 200], 200], 'ant': [[500, 0], 200]} self.assertTrue(draganddrop.grade(user_input, correct_answer)) @@ -198,6 +203,255 @@ class Test_DragAndDrop_Grade(unittest.TestCase): self.assertFalse(draganddrop.grade(user_input, correct_answer)) + def test_reuse_draggable_no_mupliples(self): + """Test reusable draggables (no mupltiple draggables per target)""" + user_input = '{"draggables":[{"1":"target1"}, \ + {"2":"target2"},{"1":"target3"},{"2":"target4"},{"2":"target5"}, \ + {"3":"target6"}]}' + correct_answer = [ + { + 'draggables': ['1'], + 'targets': ['target1', 'target3'], + 'rule': 'anyof' + }, + { + 'draggables': ['2'], + 'targets': ['target2', 'target4', 'target5'], + 'rule': 'anyof' + }, + { + 'draggables': ['3'], + 'targets': ['target6'], + 'rule': 'anyof' + }] + self.assertTrue(draganddrop.grade(user_input, correct_answer)) + + def test_reuse_draggable_with_mupliples(self): + """Test reusable draggables with mupltiple draggables per target""" + user_input = '{"draggables":[{"1":"target1"}, \ + {"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \ + {"3":"target6"}]}' + correct_answer = [ + { + 'draggables': ['1'], + 'targets': ['target1', 'target3'], + 'rule': 'anyof' + }, + { + 'draggables': ['2'], + 'targets': ['target2', 'target4'], + 'rule': 'anyof' + }, + { + 'draggables': ['3'], + 'targets': ['target6'], + 'rule': 'anyof' + }] + self.assertTrue(draganddrop.grade(user_input, correct_answer)) + + def test_reuse_many_draggable_with_mupliples(self): + """Test reusable draggables with mupltiple draggables per target""" + user_input = '{"draggables":[{"1":"target1"}, \ + {"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \ + {"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \ + {"5": "target5"}, {"6": "target2"}]}' + correct_answer = [ + { + 'draggables': ['1', '4'], + 'targets': ['target1', 'target3'], + 'rule': 'anyof' + }, + { + 'draggables': ['2', '6'], + 'targets': ['target2', 'target4'], + 'rule': 'anyof' + }, + { + 'draggables': ['5'], + 'targets': ['target4', 'target5'], + 'rule': 'anyof' + }, + { + 'draggables': ['3'], + 'targets': ['target6'], + 'rule': 'anyof' + }] + self.assertTrue(draganddrop.grade(user_input, correct_answer)) + + def test_reuse_many_draggable_with_mupliples_wrong(self): + """Test reusable draggables with mupltiple draggables per target""" + user_input = '{"draggables":[{"1":"target1"}, \ + {"2":"target2"},{"1":"target1"}, \ + {"2":"target3"}, \ + {"2":"target4"}, \ + {"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \ + {"5": "target5"}, {"6": "target2"}]}' + correct_answer = [ + { + 'draggables': ['1', '4'], + 'targets': ['target1', 'target3'], + 'rule': 'anyof' + }, + { + 'draggables': ['2', '6'], + 'targets': ['target2', 'target4'], + 'rule': 'anyof' + }, + { + 'draggables': ['5'], + 'targets': ['target4', 'target5'], + 'rule': 'anyof' + }, + { + 'draggables': ['3'], + 'targets': ['target6'], + 'rule': 'anyof' + }] + self.assertFalse(draganddrop.grade(user_input, correct_answer)) + + def test_label_10_targets_with_a_b_c_false(self): + """Test reusable draggables (no mupltiple draggables per target)""" + user_input = '{"draggables":[{"a":"target1"}, \ + {"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \ + {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ + {"a":"target1"}]}' + correct_answer = [ + { + 'draggables': ['a'], + 'targets': ['target1', 'target4', 'target7', 'target10'], + 'rule': 'unorderly_equal' + }, + { + 'draggables': ['b'], + 'targets': ['target2', 'target5', 'target8'], + 'rule': 'unorderly_equal' + }, + { + 'draggables': ['c'], + 'targets': ['target3', 'target6', 'target9'], + 'rule': 'unorderly_equal' + }] + self.assertFalse(draganddrop.grade(user_input, correct_answer)) + + def test_label_10_targets_with_a_b_c_(self): + """Test reusable draggables (no mupltiple draggables per target)""" + user_input = '{"draggables":[{"a":"target1"}, \ + {"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \ + {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ + {"a":"target10"}]}' + correct_answer = [ + { + 'draggables': ['a'], + 'targets': ['target1', 'target4', 'target7', 'target10'], + 'rule': 'unorderly_equal' + }, + { + 'draggables': ['b'], + 'targets': ['target2', 'target5', 'target8'], + 'rule': 'unorderly_equal' + }, + { + 'draggables': ['c'], + 'targets': ['target3', 'target6', 'target9'], + 'rule': 'unorderly_equal' + }] + self.assertTrue(draganddrop.grade(user_input, correct_answer)) + + def test_label_10_targets_with_a_b_c_multiple(self): + """Test reusable draggables (mupltiple draggables per target)""" + user_input = '{"draggables":[{"a":"target1"}, \ + {"b":"target2"},{"c":"target3"},{"b":"target5"}, \ + {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ + {"a":"target1"}]}' + correct_answer = [ + { + 'draggables': ['a', 'a', 'a'], + 'targets': ['target1', 'target4', 'target7', 'target10'], + 'rule': 'anyof+number' + }, + { + 'draggables': ['b', 'b', 'b'], + 'targets': ['target2', 'target5', 'target8'], + 'rule': 'anyof+number' + }, + { + 'draggables': ['c', 'c', 'c'], + 'targets': ['target3', 'target6', 'target9'], + 'rule': 'anyof+number' + }] + self.assertTrue(draganddrop.grade(user_input, correct_answer)) + + def test_label_10_targets_with_a_b_c_multiple_false(self): + """Test reusable draggables (mupltiple draggables per target)""" + user_input = '{"draggables":[{"a":"target1"}, \ + {"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \ + {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ + {"a":"target1"}]}' + correct_answer = [ + { + 'draggables': ['a', 'a', 'a'], + 'targets': ['target1', 'target4', 'target7', 'target10'], + 'rule': 'anyof+number' + }, + { + 'draggables': ['b', 'b', 'b'], + 'targets': ['target2', 'target5', 'target8'], + 'rule': 'anyof+number' + }, + { + 'draggables': ['c', 'c', 'c'], + 'targets': ['target3', 'target6', 'target9'], + 'rule': 'anyof+number' + }] + self.assertFalse(draganddrop.grade(user_input, correct_answer)) + + def test_label_10_targets_with_a_b_c_reused(self): + """Test reusable draggables (no mupltiple draggables per target)""" + user_input = '{"draggables":[{"a":"target1"}, \ + {"b":"target2"},{"c":"target3"},{"b":"target5"}, \ + {"c":"target6"}, {"b":"target8"},{"c":"target9"}, \ + {"a":"target10"}]}' + correct_answer = [ + { + 'draggables': ['a', 'a'], + 'targets': ['target1', 'target10'], + 'rule': 'unorderly_equal+number' + }, + { + 'draggables': ['b', 'b', 'b'], + 'targets': ['target2', 'target5', 'target8'], + 'rule': 'unorderly_equal+number' + }, + { + 'draggables': ['c', 'c', 'c'], + 'targets': ['target3', 'target6', 'target9'], + 'rule': 'unorderly_equal+number' + }] + self.assertTrue(draganddrop.grade(user_input, correct_answer)) + + def test_label_10_targets_with_a_b_c_reused_false(self): + """Test reusable draggables (no mupltiple draggables per target)""" + user_input = '{"draggables":[{"a":"target1"}, \ + {"b":"target2"},{"c":"target3"},{"b":"target5"}, {"a":"target8"},\ + {"c":"target6"}, {"b":"target8"},{"c":"target9"}, \ + {"a":"target10"}]}' + correct_answer = [ + { + 'draggables': ['a', 'a'], + 'targets': ['target1', 'target10'], + 'rule': 'unorderly_equal+number' + }, + { + 'draggables': ['b', 'b', 'b'], + 'targets': ['target2', 'target5', 'target8'], + 'rule': 'unorderly_equal+number' + }, + { + 'draggables': ['c', 'c', 'c'], + 'targets': ['target3', 'target6', 'target9'], + 'rule': 'unorderly_equal+number' + }] + self.assertFalse(draganddrop.grade(user_input, correct_answer)) class Test_DragAndDrop_Populate(unittest.TestCase): @@ -273,9 +527,10 @@ class Test_DraAndDrop_Compare_Positions(unittest.TestCase): def suite(): testcases = [Test_PositionsCompare, - Test_DragAndDrop_Populate, - Test_DragAndDrop_Grade, - Test_DraAndDrop_Compare_Positions] + Test_DragAndDrop_Populate, + Test_DragAndDrop_Grade, + Test_DraAndDrop_Compare_Positions + ] suites = [] for testcase in testcases: suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase))