Implement optional hiding of capa assessment results
This commit is contained in:
committed by
Braden MacDonald
parent
c4f0a89d58
commit
f18a2be837
@@ -24,7 +24,7 @@ from capa.inputtypes import Status
|
||||
from capa.responsetypes import StudentInputError, ResponseError, LoncapaProblemError
|
||||
from capa.util import convert_files_to_filenames, get_inner_html_from_xpath
|
||||
from xblock.fields import Boolean, Dict, Float, Integer, Scope, String, XMLString
|
||||
from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER
|
||||
from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER, SHOW_CORRECTNESS
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from .fields import Date, Timedelta
|
||||
from .progress import Progress
|
||||
@@ -114,6 +114,18 @@ class CapaFields(object):
|
||||
help=_("Amount of time after the due date that submissions will be accepted"),
|
||||
scope=Scope.settings
|
||||
)
|
||||
show_correctness = String(
|
||||
display_name=_("Show Results"),
|
||||
help=_("Defines when to show whether a learner's answer to the problem is correct. "
|
||||
"Configured on the subsection."),
|
||||
scope=Scope.settings,
|
||||
default=SHOW_CORRECTNESS.ALWAYS,
|
||||
values=[
|
||||
{"display_name": _("Always"), "value": SHOW_CORRECTNESS.ALWAYS},
|
||||
{"display_name": _("Never"), "value": SHOW_CORRECTNESS.NEVER},
|
||||
{"display_name": _("Past Due"), "value": SHOW_CORRECTNESS.PAST_DUE},
|
||||
],
|
||||
)
|
||||
showanswer = String(
|
||||
display_name=_("Show Answer"),
|
||||
help=_("Defines when to show the answer to the problem. "
|
||||
@@ -391,12 +403,25 @@ class CapaMixin(CapaFields):
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_display_progress(self):
|
||||
"""
|
||||
Return (score, total) to be displayed to the learner.
|
||||
"""
|
||||
progress = self.get_progress()
|
||||
score, total = (progress.frac() if progress else (0, 0))
|
||||
|
||||
# Withhold the score if hiding correctness
|
||||
if not self.correctness_available():
|
||||
score = None
|
||||
|
||||
return score, total
|
||||
|
||||
def get_html(self):
|
||||
"""
|
||||
Return some html with data about the module
|
||||
"""
|
||||
progress = self.get_progress()
|
||||
curr_score, total_possible = (progress.frac() if progress else (0, 0))
|
||||
curr_score, total_possible = self.get_display_progress()
|
||||
|
||||
return self.runtime.render_template('problem_ajax.html', {
|
||||
'element_id': self.location.html_id(),
|
||||
'id': self.location.to_deprecated_string(),
|
||||
@@ -739,7 +764,11 @@ class CapaMixin(CapaFields):
|
||||
if render_notifications:
|
||||
progress = self.get_progress()
|
||||
id_list = self.lcp.correct_map.keys()
|
||||
if len(id_list) == 1:
|
||||
|
||||
# Show only a generic message if hiding correctness
|
||||
if not self.correctness_available():
|
||||
answer_notification_type = 'submitted'
|
||||
elif len(id_list) == 1:
|
||||
# Only one answer available
|
||||
answer_notification_type = self.lcp.correct_map.get_correctness(id_list[0])
|
||||
elif len(id_list) > 1:
|
||||
@@ -782,6 +811,8 @@ class CapaMixin(CapaFields):
|
||||
).format(progress=str(progress))
|
||||
else:
|
||||
answer_notification_message = _('Partially Correct')
|
||||
elif answer_notification_type == 'submitted':
|
||||
answer_notification_message = _("Answer submitted.")
|
||||
|
||||
return answer_notification_type, answer_notification_message
|
||||
|
||||
@@ -855,7 +886,10 @@ class CapaMixin(CapaFields):
|
||||
"""
|
||||
Is the user allowed to see an answer?
|
||||
"""
|
||||
if self.showanswer == '':
|
||||
if not self.correctness_available():
|
||||
# If correctness is being withheld, then don't show answers either.
|
||||
return False
|
||||
elif self.showanswer == '':
|
||||
return False
|
||||
elif self.showanswer == SHOWANSWER.NEVER:
|
||||
return False
|
||||
@@ -883,6 +917,24 @@ class CapaMixin(CapaFields):
|
||||
|
||||
return False
|
||||
|
||||
def correctness_available(self):
|
||||
"""
|
||||
Is the user allowed to see whether she's answered correctly?
|
||||
|
||||
Limits access to the correct/incorrect flags, messages, and problem score.
|
||||
"""
|
||||
if self.show_correctness == SHOW_CORRECTNESS.NEVER:
|
||||
return False
|
||||
elif self.runtime.user_is_staff:
|
||||
# This is after the 'never' check because admins can see correctness
|
||||
# unless the problem explicitly prevents it
|
||||
return True
|
||||
elif self.show_correctness == SHOW_CORRECTNESS.PAST_DUE:
|
||||
return self.is_past_due()
|
||||
|
||||
# else: self.show_correctness == SHOW_CORRECTNESS.ALWAYS
|
||||
return True
|
||||
|
||||
def update_score(self, data):
|
||||
"""
|
||||
Delivers grading response (e.g. from asynchronous code checking) to
|
||||
@@ -1233,6 +1285,10 @@ class CapaMixin(CapaFields):
|
||||
# render problem into HTML
|
||||
html = self.get_problem_html(encapsulate=False, submit_notification=True)
|
||||
|
||||
# Withhold success indicator if hiding correctness
|
||||
if not self.correctness_available():
|
||||
success = 'submitted'
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'contents': html
|
||||
|
||||
@@ -4,6 +4,15 @@ Constants for capa_base problems
|
||||
"""
|
||||
|
||||
|
||||
class SHOW_CORRECTNESS(object): # pylint: disable=invalid-name
|
||||
"""
|
||||
Constants for when to show correctness
|
||||
"""
|
||||
ALWAYS = "always"
|
||||
PAST_DUE = "past_due"
|
||||
NEVER = "never"
|
||||
|
||||
|
||||
class SHOWANSWER(object):
|
||||
"""
|
||||
Constants for when to show answer
|
||||
|
||||
@@ -120,7 +120,8 @@ class CapaModule(CapaMixin, XModule):
|
||||
after = self.get_progress()
|
||||
after_attempts = self.attempts
|
||||
progress_changed = (after != before) or (after_attempts != before_attempts)
|
||||
curr_score, total_possible = (after.frac() if after else (0, 0))
|
||||
curr_score, total_possible = self.get_display_progress()
|
||||
|
||||
result.update({
|
||||
'progress_changed': progress_changed,
|
||||
'current_score': curr_score,
|
||||
@@ -215,6 +216,7 @@ class CapaDescriptor(CapaFields, RawDescriptor):
|
||||
CapaDescriptor.force_save_button,
|
||||
CapaDescriptor.markdown,
|
||||
CapaDescriptor.use_latex_compiler,
|
||||
CapaDescriptor.show_correctness,
|
||||
])
|
||||
return non_editable_fields
|
||||
|
||||
|
||||
@@ -250,6 +250,15 @@ div.problem {
|
||||
border-color: $incorrect;
|
||||
}
|
||||
}
|
||||
|
||||
&.choicegroup_submitted {
|
||||
border: 2px solid $submitted;
|
||||
|
||||
// keep blue for submitted answers on hover.
|
||||
&:hover {
|
||||
border-color: $submitted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
@@ -325,6 +334,7 @@ div.problem {
|
||||
@include status-icon($incorrect, $cross-icon);
|
||||
}
|
||||
|
||||
&.submitted,
|
||||
&.unsubmitted,
|
||||
&.unanswered {
|
||||
.status-icon {
|
||||
@@ -419,6 +429,12 @@ div.problem {
|
||||
}
|
||||
}
|
||||
|
||||
&.submitted, &.ui-icon-check {
|
||||
input {
|
||||
border-color: $submitted;
|
||||
}
|
||||
}
|
||||
|
||||
p.answer {
|
||||
display: inline-block;
|
||||
margin-top: ($baseline / 2);
|
||||
@@ -790,6 +806,18 @@ div.problem {
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: submitted, correctness withheld
|
||||
> .submitted {
|
||||
|
||||
input {
|
||||
border: 2px solid $submitted;
|
||||
}
|
||||
|
||||
.status {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: unanswered and unsubmitted
|
||||
> .unanswered, > .unsubmitted {
|
||||
|
||||
@@ -824,7 +852,11 @@ div.problem {
|
||||
.indicator-container {
|
||||
display: inline-block;
|
||||
|
||||
.status.correct:after, .status.partially-correct:after, .status.incorrect:after, .status.unanswered:after {
|
||||
.status.correct:after,
|
||||
.status.partially-correct:after,
|
||||
.status.incorrect:after,
|
||||
.status.submitted:after,
|
||||
.status.unanswered:after {
|
||||
@include margin-left(0);
|
||||
}
|
||||
}
|
||||
@@ -1531,6 +1563,10 @@ div.problem {
|
||||
@extend label.choicegroup_incorrect;
|
||||
}
|
||||
|
||||
label.choicetextgroup_submitted, section.choicetextgroup_submitted {
|
||||
@extend label.choicegroup_submitted;
|
||||
}
|
||||
|
||||
label.choicetextgroup_show_correct, section.choicetextgroup_show_correct {
|
||||
&:after {
|
||||
@include margin-left($baseline*.75);
|
||||
@@ -1569,6 +1605,10 @@ div.problem .imageinput.capa_inputtype {
|
||||
.partially-correct {
|
||||
@include status-icon($partially-correct, $asterisk-icon);
|
||||
}
|
||||
|
||||
.submitted {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
// +Problem - Annotation Problem Overrides
|
||||
@@ -1596,4 +1636,8 @@ div.problem .annotation-input {
|
||||
.partially-correct {
|
||||
@include status-icon($partially-correct, $asterisk-icon);
|
||||
}
|
||||
|
||||
.submitted {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,28 @@ describe 'Problem', ->
|
||||
it 'shows 0 points possible for the detail', ->
|
||||
testProgessData(@problem, 0, 0, 1, "False", "0 points possible (ungraded)")
|
||||
|
||||
describe 'with a score of null (show_correctness == false)', ->
|
||||
it 'reports the number of points possible and graded, results hidden', ->
|
||||
testProgessData(@problem, null, 1, 0, "True", "1 point possible (graded, results hidden)")
|
||||
|
||||
it 'reports the number of points possible (plural) and graded, results hidden', ->
|
||||
testProgessData(@problem, null, 2, 0, "True", "2 points possible (graded, results hidden)")
|
||||
|
||||
it 'reports the number of points possible and ungraded, results hidden', ->
|
||||
testProgessData(@problem, null, 1, 0, "False", "1 point possible (ungraded, results hidden)")
|
||||
|
||||
it 'displays ungraded if number of points possible is 0, results hidden', ->
|
||||
testProgessData(@problem, null, 0, 0, "False", "0 points possible (ungraded, results hidden)")
|
||||
|
||||
it 'displays ungraded if number of points possible is 0, even if graded value is True, results hidden', ->
|
||||
testProgessData(@problem, null, 0, 0, "True", "0 points possible (ungraded, results hidden)")
|
||||
|
||||
it 'reports the correct score with status none and >0 attempts, results hidden', ->
|
||||
testProgessData(@problem, null, 1, 1, "True", "1 point possible (graded, results hidden)")
|
||||
|
||||
it 'reports the correct score with >1 weight, status none, and >0 attempts, results hidden', ->
|
||||
testProgessData(@problem, null, 2, 2, "True", "2 points possible (graded, results hidden)")
|
||||
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
|
||||
@@ -214,11 +214,36 @@
|
||||
attemptsUsed = this.el.data('attempts-used');
|
||||
graded = this.el.data('graded');
|
||||
|
||||
// The problem is ungraded if it's explicitly marked as such, or if the total possible score is 0
|
||||
if (graded === 'True' && totalScore !== 0) {
|
||||
graded = true;
|
||||
} else {
|
||||
graded = false;
|
||||
}
|
||||
|
||||
if (curScore === undefined || totalScore === undefined) {
|
||||
progress = '';
|
||||
// Render an empty string.
|
||||
progressTemplate = '';
|
||||
} else if (curScore === null || curScore === 'None') {
|
||||
// Render 'x point(s) possible (un/graded, results hidden)' if no current score provided.
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).;
|
||||
'%(num_points)s point possible (graded, results hidden)',
|
||||
'%(num_points)s points possible (graded, results hidden)',
|
||||
totalScore
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).;
|
||||
'%(num_points)s point possible (ungraded, results hidden)',
|
||||
'%(num_points)s points possible (ungraded, results hidden)',
|
||||
totalScore
|
||||
);
|
||||
}
|
||||
} else if (attemptsUsed === 0 || totalScore === 0) {
|
||||
// Render 'x point(s) possible' if student has not yet attempted question
|
||||
if (graded === 'True' && totalScore !== 0) {
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).;
|
||||
'%(num_points)s point possible (graded)', '%(num_points)s points possible (graded)',
|
||||
@@ -231,10 +256,9 @@
|
||||
totalScore
|
||||
);
|
||||
}
|
||||
progress = interpolate(progressTemplate, {num_points: totalScore}, true);
|
||||
} else {
|
||||
// Render 'x/y point(s)' if student has attempted question
|
||||
if (graded === 'True' && totalScore !== 0) {
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// This comment needs to be on one line to be properly scraped for the translators.
|
||||
// Translators: %(earned)s is the number of points earned. %(possible)s is the total number of points (examples: 0/1, 1/1, 2/3, 5/10). The total number of points will always be at least 1. We pluralize based on the total number of points (example: 0/1 point; 1/2 points);
|
||||
@@ -249,13 +273,14 @@
|
||||
totalScore
|
||||
);
|
||||
}
|
||||
progress = interpolate(
|
||||
progressTemplate, {
|
||||
earned: curScore,
|
||||
possible: totalScore
|
||||
}, true
|
||||
);
|
||||
}
|
||||
progress = interpolate(
|
||||
progressTemplate, {
|
||||
earned: curScore,
|
||||
num_points: totalScore,
|
||||
possible: totalScore
|
||||
}, true
|
||||
);
|
||||
return this.$('.problem-progress').text(progress);
|
||||
};
|
||||
|
||||
@@ -573,6 +598,7 @@
|
||||
complete: this.enableSubmitButtonAfterResponse,
|
||||
success: function(response) {
|
||||
switch (response.success) {
|
||||
case 'submitted':
|
||||
case 'incorrect':
|
||||
case 'correct':
|
||||
that.render(response.contents);
|
||||
@@ -599,6 +625,7 @@
|
||||
Logger.log('problem_check', this.answers);
|
||||
return $.postWithPrefix('' + this.url + '/problem_check', this.answers, function(response) {
|
||||
switch (response.success) {
|
||||
case 'submitted':
|
||||
case 'incorrect':
|
||||
case 'correct':
|
||||
window.SR.readTexts(that.get_sr_status(response.contents));
|
||||
|
||||
@@ -100,6 +100,19 @@ class InheritanceMixin(XBlockMixin):
|
||||
scope=Scope.settings,
|
||||
default="finished",
|
||||
)
|
||||
|
||||
show_correctness = String(
|
||||
display_name=_("Show Results"),
|
||||
help=_(
|
||||
# Translators: DO NOT translate the words in quotes here, they are
|
||||
# specific words for the acceptable values.
|
||||
'Specify when to show answer correctness and score to learners. '
|
||||
'Valid values are "always", "never", and "past_due".'
|
||||
),
|
||||
scope=Scope.settings,
|
||||
default="always",
|
||||
)
|
||||
|
||||
rerandomize = String(
|
||||
display_name=_("Randomization"),
|
||||
help=_(
|
||||
|
||||
@@ -265,6 +265,46 @@ class CapaModuleTest(unittest.TestCase):
|
||||
problem.attempts = 1
|
||||
self.assertTrue(problem.answer_available())
|
||||
|
||||
@ddt.data(
|
||||
# If show_correctness=always, Answer is visible after attempted
|
||||
({
|
||||
'showanswer': 'attempted',
|
||||
'max_attempts': '1',
|
||||
'show_correctness': 'always',
|
||||
}, True),
|
||||
# If show_correctness=never, Answer is never visible
|
||||
({
|
||||
'showanswer': 'attempted',
|
||||
'max_attempts': '1',
|
||||
'show_correctness': 'never',
|
||||
}, False),
|
||||
# If show_correctness=past_due, answer is not visible before due date
|
||||
({
|
||||
'showanswer': 'attempted',
|
||||
'show_correctness': 'past_due',
|
||||
'max_attempts': '1',
|
||||
'due': 'tomorrow_str',
|
||||
}, False),
|
||||
# If show_correctness=past_due, answer is visible after due date
|
||||
({
|
||||
'showanswer': 'attempted',
|
||||
'show_correctness': 'past_due',
|
||||
'max_attempts': '1',
|
||||
'due': 'yesterday_str',
|
||||
}, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_showanswer_hide_correctness(self, problem_data, answer_available):
|
||||
"""
|
||||
Ensure that the answer will not be shown when correctness is being hidden.
|
||||
"""
|
||||
if 'due' in problem_data:
|
||||
problem_data['due'] = getattr(self, problem_data['due'])
|
||||
problem = CapaFactory.create(**problem_data)
|
||||
self.assertFalse(problem.answer_available())
|
||||
problem.attempts = 1
|
||||
self.assertEqual(problem.answer_available(), answer_available)
|
||||
|
||||
def test_showanswer_closed(self):
|
||||
|
||||
# can see after attempts used up, even with due date in the future
|
||||
@@ -414,6 +454,73 @@ class CapaModuleTest(unittest.TestCase):
|
||||
graceperiod=self.two_day_delta_str)
|
||||
self.assertTrue(still_in_grace.answer_available())
|
||||
|
||||
@ddt.data('', 'other-value')
|
||||
def test_show_correctness_other(self, show_correctness):
|
||||
"""
|
||||
Test that correctness is visible if show_correctness is not set to one of the values
|
||||
from SHOW_CORRECTNESS constant.
|
||||
"""
|
||||
problem = CapaFactory.create(show_correctness=show_correctness)
|
||||
self.assertTrue(problem.correctness_available())
|
||||
|
||||
def test_show_correctness_default(self):
|
||||
"""
|
||||
Test that correctness is visible by default.
|
||||
"""
|
||||
problem = CapaFactory.create()
|
||||
self.assertTrue(problem.correctness_available())
|
||||
|
||||
def test_show_correctness_never(self):
|
||||
"""
|
||||
Test that correctness is hidden when show_correctness turned off.
|
||||
"""
|
||||
problem = CapaFactory.create(show_correctness='never')
|
||||
self.assertFalse(problem.correctness_available())
|
||||
|
||||
@ddt.data(
|
||||
# Correctness not visible if due date in the future, even after using up all attempts
|
||||
({
|
||||
'show_correctness': 'past_due',
|
||||
'max_attempts': '1',
|
||||
'attempts': '1',
|
||||
'due': 'tomorrow_str',
|
||||
}, False),
|
||||
# Correctness visible if due date in the past
|
||||
({
|
||||
'show_correctness': 'past_due',
|
||||
'max_attempts': '1',
|
||||
'attempts': '0',
|
||||
'due': 'yesterday_str',
|
||||
}, True),
|
||||
# Correctness not visible if due date in the future
|
||||
({
|
||||
'show_correctness': 'past_due',
|
||||
'max_attempts': '1',
|
||||
'attempts': '0',
|
||||
'due': 'tomorrow_str',
|
||||
}, False),
|
||||
# Correctness not visible because grace period hasn't expired,
|
||||
# even after using up all attempts
|
||||
({
|
||||
'show_correctness': 'past_due',
|
||||
'max_attempts': '1',
|
||||
'attempts': '1',
|
||||
'due': 'yesterday_str',
|
||||
'graceperiod': 'two_day_delta_str',
|
||||
}, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_show_correctness_past_due(self, problem_data, expected_result):
|
||||
"""
|
||||
Test that with show_correctness="past_due", correctness will only be visible
|
||||
after the problem is closed for everyone--e.g. after due date + grace period.
|
||||
"""
|
||||
problem_data['due'] = getattr(self, problem_data['due'])
|
||||
if 'graceperiod' in problem_data:
|
||||
problem_data['graceperiod'] = getattr(self, problem_data['graceperiod'])
|
||||
problem = CapaFactory.create(**problem_data)
|
||||
self.assertEqual(problem.correctness_available(), expected_result)
|
||||
|
||||
def test_closed(self):
|
||||
|
||||
# Attempts < Max attempts --> NOT closed
|
||||
@@ -814,6 +921,36 @@ class CapaModuleTest(unittest.TestCase):
|
||||
# Expect that the number of attempts is NOT incremented
|
||||
self.assertEqual(module.attempts, 1)
|
||||
|
||||
@ddt.data(
|
||||
("never", True, None, 'submitted'),
|
||||
("never", False, None, 'submitted'),
|
||||
("past_due", True, None, 'submitted'),
|
||||
("past_due", False, None, 'submitted'),
|
||||
("always", True, 1, 'correct'),
|
||||
("always", False, 0, 'incorrect'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_handle_ajax_show_correctness(self, show_correctness, is_correct, expected_score, expected_success):
|
||||
module = CapaFactory.create(show_correctness=show_correctness,
|
||||
due=self.tomorrow_str,
|
||||
correct=is_correct)
|
||||
|
||||
# Simulate marking the input correct/incorrect
|
||||
with patch('capa.correctmap.CorrectMap.is_correct') as mock_is_correct:
|
||||
mock_is_correct.return_value = is_correct
|
||||
|
||||
# Check the problem
|
||||
get_request_dict = {CapaFactory.input_key(): '0'}
|
||||
json_result = module.handle_ajax('problem_check', get_request_dict)
|
||||
result = json.loads(json_result)
|
||||
|
||||
# Expect that the AJAX result withholds correctness and score
|
||||
self.assertEqual(result['current_score'], expected_score)
|
||||
self.assertEqual(result['success'], expected_success)
|
||||
|
||||
# Expect that the number of attempts is incremented by 1
|
||||
self.assertEqual(module.attempts, 1)
|
||||
|
||||
def test_reset_problem(self):
|
||||
module = CapaFactory.create(done=True)
|
||||
module.new_lcp = Mock(wraps=module.new_lcp)
|
||||
@@ -1584,6 +1721,27 @@ class CapaModuleTest(unittest.TestCase):
|
||||
other_module.get_progress()
|
||||
mock_progress.assert_called_with(1, 1)
|
||||
|
||||
@ddt.data(
|
||||
("never", True, None),
|
||||
("never", False, None),
|
||||
("past_due", True, None),
|
||||
("past_due", False, None),
|
||||
("always", True, 1),
|
||||
("always", False, 0),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_get_display_progress_show_correctness(self, show_correctness, is_correct, expected_score):
|
||||
"""
|
||||
Check that score and total are calculated correctly for the progress fraction.
|
||||
"""
|
||||
module = CapaFactory.create(correct=is_correct,
|
||||
show_correctness=show_correctness,
|
||||
due=self.tomorrow_str)
|
||||
module.weight = 1
|
||||
score, total = module.get_display_progress()
|
||||
self.assertEqual(score, expected_score)
|
||||
self.assertEqual(total, 1)
|
||||
|
||||
def test_get_html(self):
|
||||
"""
|
||||
Check that get_html() calls get_progress() with no arguments.
|
||||
|
||||
Reference in New Issue
Block a user