diff --git a/freetextresponse/__init__.py b/freetextresponse/__init__.py index 653038d8..218c11c3 100644 --- a/freetextresponse/__init__.py +++ b/freetextresponse/__init__.py @@ -4,4 +4,4 @@ present in order for the student to receive credit. """ -__version__ = "4.1.0" +__version__ = "4.1.1" \ No newline at end of file diff --git a/freetextresponse/tests/submitdisplay_class.json b/freetextresponse/tests/submitdisplay_class.json index 621d38e9..86b7d888 100644 --- a/freetextresponse/tests/submitdisplay_class.json +++ b/freetextresponse/tests/submitdisplay_class.json @@ -1,7 +1,12 @@ { + "empty_null_max": { + "max_attempts": null, + "count_attempts": 1, + "result": "" + }, "empty_zero_max": { "max_attempts": 0, - "count_attempts": 1, + "count_attempts": 1, "result": "" }, "empty_under_max": { diff --git a/freetextresponse/tests/test_all.py b/freetextresponse/tests/test_all.py index 39d8aca4..4d972367 100644 --- a/freetextresponse/tests/test_all.py +++ b/freetextresponse/tests/test_all.py @@ -1,6 +1,7 @@ """ Module To Test FreeTextResponse XBlock """ +from datetime import datetime, timedelta from os import path import json import unittest @@ -88,7 +89,7 @@ def test_generate_validation_message(self): def test_validate_field_data(self, **test_dict): """ Checks classmethod validate_field_data - tests the instuctor values set in edit + tests the instructor values set in edit """ test_data = TestData() test_data.weight = test_dict['weight'] @@ -324,6 +325,7 @@ def test_word_count_valid(self, **test_data): # Messages @ddt.data( # max_attempts, count_attempts, result + (None, 4, ''), (0, 4, ''), (1, 0, 'You have used 0 of 1 submission'), (3, 2, 'You have used 2 of 3 submissions'), @@ -412,7 +414,7 @@ def test_get_submitted_message( Tests _get_submitted_message Returns a message to display to the user after they submit a - resopnse + response """ self.xblock._word_count_valid = MagicMock( return_value=word_count_valid @@ -497,12 +499,53 @@ def test_get_submitdisplay_class(self, **test_data): self.xblock._get_nodisplay_class() ) - def test_submit(self): + @ddt.data( + ({'max_attempts': None, 'count_attempts': 1}, True), + ({'max_attempts': None, 'count_attempts': 3}, True), + ({'max_attempts': 0, 'count_attempts': 3}, True), + ({'max_attempts': 3, 'count_attempts': 2}, True), + ({'max_attempts': 3, 'count_attempts': 3}, False), + ({'due': None, 'graceperiod': None}, True), + ({'due': None, 'graceperiod': timedelta(hours=1)}, True), + ( + { + 'due': datetime.utcnow() + timedelta(hours=1), + 'graceperiod': None, + }, + True, + ), + ( + { + 'due': datetime.utcnow() - timedelta(hours=1), + 'graceperiod': None, + }, + False, + ), + ( + { + 'due': datetime.utcnow() - timedelta(hours=1), + 'graceperiod': timedelta(hours=2), + }, + True, + ), + ( + { + 'due': datetime.utcnow() - timedelta(hours=5), + 'graceperiod': timedelta(hours=2), + }, + False, + ), + ) + @ddt.unpack + def test_submit(self, xblock_config, can_submit): # pylint: disable=protected-access """ - Tests save_reponse results + Tests save_response results """ - data = json.dumps({'student_answer': 'asdf'}) + student_answer = 'asdf' + for key, value in xblock_config.items(): + setattr(self.xblock, key, value) + data = json.dumps({'student_answer': student_answer}) request = TestRequest() request.method = 'POST' request.body = data.encode('utf-8') @@ -543,13 +586,27 @@ def test_submit(self): response.json_body['visibility_class'], self.xblock._get_indicator_visibility_class() ) + self.assertEqual( + self.xblock.student_answer, + student_answer if can_submit else '' + ) - def test_save_reponse(self): + @ddt.data( + ({'max_attempts': None, 'count_attempts': 1}, True), + ({'max_attempts': None, 'count_attempts': 3}, True), + ({'max_attempts': 0, 'count_attempts': 3}, True), + ({'max_attempts': 3, 'count_attempts': 3}, False) + ) + @ddt.unpack + def test_save_response(self, xblock_config, can_save): # pylint: disable=protected-access """ - Tests save_reponse results + Tests save_response results """ - data = json.dumps({'student_answer': 'asdf'}) + student_answer = 'asdf' + for key, value in xblock_config.items(): + setattr(self.xblock, key, value) + data = json.dumps({'student_answer': student_answer}) request = TestRequest() request.method = 'POST' request.body = data.encode('utf-8') @@ -587,3 +644,7 @@ def test_save_reponse(self): response.json_body['visibility_class'], self.xblock._get_indicator_visibility_class() ) + self.assertEqual( + self.xblock.student_answer, + student_answer if can_save else '' + ) diff --git a/freetextresponse/tests/validate_field_data.json b/freetextresponse/tests/validate_field_data.json index 3510c75c..935626d8 100644 --- a/freetextresponse/tests/validate_field_data.json +++ b/freetextresponse/tests/validate_field_data.json @@ -1,5 +1,5 @@ { - "weight_attemps_negative": { + "weight_attempts_negative": { "weight": -1, "max_attempts": 1, "max_word_count": 1, @@ -7,7 +7,7 @@ "submitted_message": "s", "result": "Weight Attempts cannot be negative" }, - "max_attemps_negative": { + "max_attempts_negative": { "weight": 0, "max_attempts": -1, "max_word_count": 1, @@ -38,6 +38,14 @@ "min_word_count": 2, "submitted_message": "", "result": "Submission Received Message cannot be blank" + }, + "submission_message_blank_null_max_attempts": { + "weight": 0, + "max_attempts": null, + "max_word_count": 3, + "min_word_count": 2, + "submitted_message": "", + "result": "Submission Received Message cannot be blank" } } diff --git a/freetextresponse/views.py b/freetextresponse/views.py index fa6e4ab9..bb409448 100644 --- a/freetextresponse/views.py +++ b/freetextresponse/views.py @@ -7,7 +7,7 @@ try: from xblock.utils.resources import ResourceLoader from xblock.utils.studio_editable import StudioEditableXBlockMixin -except ModuleNotFoundError: +except ModuleNotFoundError: # pragma: no cover # For backward compatibility with releases older than Quince. from xblockutils.resources import ResourceLoader from xblockutils.studio_editable import StudioEditableXBlockMixin @@ -74,7 +74,7 @@ def _get_nodisplay_class(self): Returns the css class for the submit button """ result = '' - if self.max_attempts > 0 and self.count_attempts >= self.max_attempts: + if self.max_attempts and 0 < self.max_attempts <= self.count_attempts: result = 'nodisplay' return result @@ -142,7 +142,7 @@ def _get_used_attempts_feedback(self): they have used if applicable """ result = '' - if self.max_attempts > 0: + if self.max_attempts and self.max_attempts > 0: result = self.ngettext( 'You have used {count_attempts} of {max_attempts} submission', 'You have used {count_attempts} of {max_attempts} submissions', @@ -206,7 +206,7 @@ def submit(self, data, suffix=''): Processes the user's submission """ # Fails if the UI submit/save buttons were shut - # down on the previous sumbisson + # down on the previous submission if self._can_submit(): self.student_answer = data['student_answer'] # Counting the attempts and publishing a score @@ -239,8 +239,8 @@ def save_reponse(self, data, suffix=''): Processes the user's save """ # Fails if the UI submit/save buttons were shut - # down on the previous sumbisson - if self.max_attempts == 0 or self.count_attempts < self.max_attempts: + # down on the previous submission + if not self.max_attempts or self.count_attempts < self.max_attempts: self.student_answer = data['student_answer'] result = { 'status': 'success', @@ -295,7 +295,7 @@ def _can_submit(self): """ if self.is_past_due(): return False - if self.max_attempts == 0: + if not self.max_attempts: return True if self.count_attempts < self.max_attempts: return True @@ -321,7 +321,7 @@ def validate_field_data(self, validation, data): 'Weight Attempts cannot be negative' ) validation.add(msg) - if data.max_attempts < 0: + if data.max_attempts and data.max_attempts < 0: msg = self._generate_validation_message( 'Maximum Attempts cannot be negative' )