Skip to content

Commit

Permalink
fix: The XBlock should work even when max_attempts is null
Browse files Browse the repository at this point in the history
This change allows the XBlock to continue working in cases where max_attempts is
set to null. Currently Studio and LMS will throw an error in such a case due
to a number of places where max_attempts is compared to count_attempts without
testing for None first.
  • Loading branch information
xitij2000 committed Jan 24, 2024
1 parent 92e4316 commit 7452d76
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 17 deletions.
7 changes: 6 additions & 1 deletion freetextresponse/tests/submitdisplay_class.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
53 changes: 45 additions & 8 deletions freetextresponse/tests/test_all.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Module To Test FreeTextResponse XBlock
"""
from datetime import datetime, timedelta
from os import path
import json
import unittest
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -497,12 +499,29 @@ 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')
Expand Down Expand Up @@ -543,13 +562,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')
Expand Down Expand Up @@ -587,3 +620,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 ''
)
4 changes: 2 additions & 2 deletions freetextresponse/tests/validate_field_data.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"weight_attemps_negative": {
"weight_attempts_negative": {
"weight": -1,
"max_attempts": 1,
"max_word_count": 1,
"min_word_count": 1,
"submitted_message": "s",
"result": "Weight Attempts cannot be negative"
},
"max_attemps_negative": {
"max_attempts_negative": {
"weight": 0,
"max_attempts": -1,
"max_word_count": 1,
Expand Down
12 changes: 6 additions & 6 deletions freetextresponse/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 7452d76

Please sign in to comment.