Commit 6d19a0c8 by Jim

Support and tests for adding a reset button to units

Users may want to start anew when answering a question. This commit decouples reset from randomization while still preserving backward compatibility. Users can clear their input.
Instructors can set course-wide and problem-specific settings for reset button display.
parent 14209c67
...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. the top. Include a label indicating the component affected.
Common: Add configurable reset button to units
LMS: Support adding cohorts from the instructor dashboard. TNL-162 LMS: Support adding cohorts from the instructor dashboard. TNL-162
LMS: Support adding students to a cohort via the instructor dashboard. TNL-163 LMS: Support adding students to a cohort via the instructor dashboard. TNL-163
......
...@@ -13,6 +13,7 @@ MAXIMUM_ATTEMPTS = "Maximum Attempts" ...@@ -13,6 +13,7 @@ MAXIMUM_ATTEMPTS = "Maximum Attempts"
PROBLEM_WEIGHT = "Problem Weight" PROBLEM_WEIGHT = "Problem Weight"
RANDOMIZATION = 'Randomization' RANDOMIZATION = 'Randomization'
SHOW_ANSWER = "Show Answer" SHOW_ANSWER = "Show Answer"
SHOW_RESET_BUTTON = "Show Reset Button"
TIMER_BETWEEN_ATTEMPTS = "Timer Between Attempts" TIMER_BETWEEN_ATTEMPTS = "Timer Between Attempts"
MATLAB_API_KEY = "Matlab API key" MATLAB_API_KEY = "Matlab API key"
...@@ -102,6 +103,7 @@ def i_see_advanced_settings_with_values(step): ...@@ -102,6 +103,7 @@ def i_see_advanced_settings_with_values(step):
[PROBLEM_WEIGHT, "", False], [PROBLEM_WEIGHT, "", False],
[RANDOMIZATION, "Never", False], [RANDOMIZATION, "Never", False],
[SHOW_ANSWER, "Finished", False], [SHOW_ANSWER, "Finished", False],
[SHOW_RESET_BUTTON, "False", False],
[TIMER_BETWEEN_ATTEMPTS, "0", False], [TIMER_BETWEEN_ATTEMPTS, "0", False],
]) ])
......
...@@ -29,6 +29,8 @@ from xblock.fields import Scope, String, Boolean, Dict, Integer, Float ...@@ -29,6 +29,8 @@ from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
from .fields import Timedelta, Date from .fields import Timedelta, Date
from django.utils.timezone import UTC from django.utils.timezone import UTC
from .util.duedate import get_extended_due_date from .util.duedate import get_extended_due_date
from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER
from django.conf import settings
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
...@@ -63,9 +65,9 @@ class Randomization(String): ...@@ -63,9 +65,9 @@ class Randomization(String):
""" """
def from_json(self, value): def from_json(self, value):
if value in ("", "true"): if value in ("", "true"):
return "always" return RANDOMIZATION.ALWAYS
elif value == "false": elif value == "false":
return "per_student" return RANDOMIZATION.PER_STUDENT
return value return value
to_json = from_json to_json = from_json
...@@ -103,15 +105,15 @@ class CapaFields(object): ...@@ -103,15 +105,15 @@ class CapaFields(object):
max_attempts = Integer( max_attempts = Integer(
display_name=_("Maximum Attempts"), display_name=_("Maximum Attempts"),
help=_("Defines the number of times a student can try to answer this problem. " help=_("Defines the number of times a student can try to answer this problem. "
"If the value is not set, infinite attempts are allowed."), "If the value is not set, infinite attempts are allowed."),
values={"min": 0}, scope=Scope.settings values={"min": 0}, scope=Scope.settings
) )
due = Date(help=_("Date that this problem is due by"), scope=Scope.settings) due = Date(help=_("Date that this problem is due by"), scope=Scope.settings)
extended_due = Date( extended_due = Date(
help=_("Date that this problem is due by for a particular student. This " help=_("Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due " "can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due " "date if it is set to a date that is later than the global due "
"date."), "date."),
default=None, default=None,
scope=Scope.user_state, scope=Scope.user_state,
) )
...@@ -122,36 +124,45 @@ class CapaFields(object): ...@@ -122,36 +124,45 @@ class CapaFields(object):
showanswer = String( showanswer = String(
display_name=_("Show Answer"), display_name=_("Show Answer"),
help=_("Defines when to show the answer to the problem. " help=_("Defines when to show the answer to the problem. "
"A default value can be set in Advanced Settings."), "A default value can be set in Advanced Settings."),
scope=Scope.settings, scope=Scope.settings,
default="finished", default=SHOWANSWER.FINISHED,
values=[ values=[
{"display_name": _("Always"), "value": "always"}, {"display_name": _("Always"), "value": SHOWANSWER.ALWAYS},
{"display_name": _("Answered"), "value": "answered"}, {"display_name": _("Answered"), "value": SHOWANSWER.ANSWERED},
{"display_name": _("Attempted"), "value": "attempted"}, {"display_name": _("Attempted"), "value": SHOWANSWER.ATTEMPTED},
{"display_name": _("Closed"), "value": "closed"}, {"display_name": _("Closed"), "value": SHOWANSWER.CLOSED},
{"display_name": _("Finished"), "value": "finished"}, {"display_name": _("Finished"), "value": SHOWANSWER.FINISHED},
{"display_name": _("Correct or Past Due"), "value": "correct_or_past_due"}, {"display_name": _("Correct or Past Due"), "value": SHOWANSWER.CORRECT_OR_PAST_DUE},
{"display_name": _("Past Due"), "value": "past_due"}, {"display_name": _("Past Due"), "value": SHOWANSWER.PAST_DUE},
{"display_name": _("Never"), "value": "never"}] {"display_name": _("Never"), "value": SHOWANSWER.NEVER}]
) )
force_save_button = Boolean( force_save_button = Boolean(
help=_("Whether to force the save button to appear on the page"), help=_("Whether to force the save button to appear on the page"),
scope=Scope.settings, scope=Scope.settings,
default=False default=False
) )
reset_key = "DEFAULT_SHOW_RESET_BUTTON"
default_reset_button = getattr(settings, reset_key) if hasattr(settings, reset_key) else False
show_reset_button = Boolean(
display_name=_("Show Reset Button"),
help=_("Determines whether a 'Reset' button is shown so the user may reset their answer. "
"A default value can be set in Advanced Settings."),
scope=Scope.settings,
default=default_reset_button
)
rerandomize = Randomization( rerandomize = Randomization(
display_name=_("Randomization"), display_name=_("Randomization"),
help=_("Defines how often inputs are randomized when a student loads the problem. " help=_("Defines how often inputs are randomized when a student loads the problem. "
"This setting only applies to problems that can have randomly generated numeric values. " "This setting only applies to problems that can have randomly generated numeric values. "
"A default value can be set in Advanced Settings."), "A default value can be set in Advanced Settings."),
default="never", default=RANDOMIZATION.NEVER,
scope=Scope.settings, scope=Scope.settings,
values=[ values=[
{"display_name": _("Always"), "value": "always"}, {"display_name": _("Always"), "value": RANDOMIZATION.ALWAYS},
{"display_name": _("On Reset"), "value": "onreset"}, {"display_name": _("On Reset"), "value": RANDOMIZATION.ONRESET},
{"display_name": _("Never"), "value": "never"}, {"display_name": _("Never"), "value": RANDOMIZATION.NEVER},
{"display_name": _("Per Student"), "value": "per_student"} {"display_name": _("Per Student"), "value": RANDOMIZATION.PER_STUDENT}
] ]
) )
data = String(help=_("XML data for the problem"), scope=Scope.content, default="<problem></problem>") data = String(help=_("XML data for the problem"), scope=Scope.content, default="<problem></problem>")
...@@ -170,7 +181,7 @@ class CapaFields(object): ...@@ -170,7 +181,7 @@ class CapaFields(object):
weight = Float( weight = Float(
display_name=_("Problem Weight"), display_name=_("Problem Weight"),
help=_("Defines the number of points each problem is worth. " help=_("Defines the number of points each problem is worth. "
"If the value is not set, each response field in the problem is worth one point."), "If the value is not set, each response field in the problem is worth one point."),
values={"min": 0, "step": .1}, values={"min": 0, "step": .1},
scope=Scope.settings scope=Scope.settings
) )
...@@ -254,7 +265,7 @@ class CapaMixin(CapaFields): ...@@ -254,7 +265,7 @@ class CapaMixin(CapaFields):
tb=cgi.escape( tb=cgi.escape(
u''.join(['Traceback (most recent call last):\n'] + u''.join(['Traceback (most recent call last):\n'] +
traceback.format_tb(sys.exc_info()[2]))) traceback.format_tb(sys.exc_info()[2])))
) )
# create a dummy problem with error message instead of failing # create a dummy problem with error message instead of failing
problem_text = (u'<problem><text><span class="inline-error">' problem_text = (u'<problem><text><span class="inline-error">'
u'Problem {url} has an error:</span>{msg}</text></problem>'.format( u'Problem {url} has an error:</span>{msg}</text></problem>'.format(
...@@ -274,9 +285,9 @@ class CapaMixin(CapaFields): ...@@ -274,9 +285,9 @@ class CapaMixin(CapaFields):
""" """
Choose a new seed. Choose a new seed.
""" """
if self.rerandomize == 'never': if self.rerandomize == RANDOMIZATION.NEVER:
self.seed = 1 self.seed = 1
elif self.rerandomize == "per_student" and hasattr(self.runtime, 'seed'): elif self.rerandomize == RANDOMIZATION.PER_STUDENT and hasattr(self.runtime, 'seed'):
# see comment on randomization_bin # see comment on randomization_bin
self.seed = randomization_bin(self.runtime.seed, unicode(self.location).encode('utf-8')) self.seed = randomization_bin(self.runtime.seed, unicode(self.location).encode('utf-8'))
else: else:
...@@ -446,7 +457,7 @@ class CapaMixin(CapaFields): ...@@ -446,7 +457,7 @@ class CapaMixin(CapaFields):
""" """
Return True/False to indicate whether to show the "Check" button. Return True/False to indicate whether to show the "Check" button.
""" """
submitted_without_reset = (self.is_submitted() and self.rerandomize == "always") submitted_without_reset = (self.is_submitted() and self.rerandomize == RANDOMIZATION.ALWAYS)
# If the problem is closed (past due / too many attempts) # If the problem is closed (past due / too many attempts)
# then we do NOT show the "check" button # then we do NOT show the "check" button
...@@ -463,19 +474,20 @@ class CapaMixin(CapaFields): ...@@ -463,19 +474,20 @@ class CapaMixin(CapaFields):
""" """
is_survey_question = (self.max_attempts == 0) is_survey_question = (self.max_attempts == 0)
if self.rerandomize in ["always", "onreset"]: # If the problem is closed (and not a survey question with max_attempts==0),
# then do NOT show the reset button.
if (self.closed() and not is_survey_question):
return False
# If the problem is closed (and not a survey question with max_attempts==0), # Button only shows up for randomized problems if the question has been submitted
# then do NOT show the reset button. if self.rerandomize in [RANDOMIZATION.ALWAYS, RANDOMIZATION.ONRESET] and self.is_submitted():
# If the problem hasn't been submitted yet, then do NOT show return True
# the reset button. else:
if (self.closed() and not is_survey_question) or not self.is_submitted(): # Do NOT show the button if the problem is correct
if self.is_correct():
return False return False
else: else:
return True return self.show_reset_button
# Only randomized problems need a "reset" button
else:
return False
def should_show_save_button(self): def should_show_save_button(self):
""" """
...@@ -489,7 +501,7 @@ class CapaMixin(CapaFields): ...@@ -489,7 +501,7 @@ class CapaMixin(CapaFields):
return not self.closed() return not self.closed()
else: else:
is_survey_question = (self.max_attempts == 0) is_survey_question = (self.max_attempts == 0)
needs_reset = self.is_submitted() and self.rerandomize == "always" needs_reset = self.is_submitted() and self.rerandomize == RANDOMIZATION.ALWAYS
# If the student has unlimited attempts, and their answers # If the student has unlimited attempts, and their answers
# are not randomized, then we do not need a save button # are not randomized, then we do not need a save button
...@@ -503,7 +515,7 @@ class CapaMixin(CapaFields): ...@@ -503,7 +515,7 @@ class CapaMixin(CapaFields):
# In those cases. the if statement below is false, # In those cases. the if statement below is false,
# and the save button can still be displayed. # and the save button can still be displayed.
# #
if self.max_attempts is None and self.rerandomize != "always": if self.max_attempts is None and self.rerandomize != RANDOMIZATION.ALWAYS:
return False return False
# If the problem is closed (and not a survey question with max_attempts==0), # If the problem is closed (and not a survey question with max_attempts==0),
...@@ -697,28 +709,28 @@ class CapaMixin(CapaFields): ...@@ -697,28 +709,28 @@ class CapaMixin(CapaFields):
""" """
if self.showanswer == '': if self.showanswer == '':
return False return False
elif self.showanswer == "never": elif self.showanswer == SHOWANSWER.NEVER:
return False return False
elif self.runtime.user_is_staff: elif self.runtime.user_is_staff:
# This is after the 'never' check because admins can see the answer # This is after the 'never' check because admins can see the answer
# unless the problem explicitly prevents it # unless the problem explicitly prevents it
return True return True
elif self.showanswer == 'attempted': elif self.showanswer == SHOWANSWER.ATTEMPTED:
return self.attempts > 0 return self.attempts > 0
elif self.showanswer == 'answered': elif self.showanswer == SHOWANSWER.ANSWERED:
# NOTE: this is slightly different from 'attempted' -- resetting the problems # NOTE: this is slightly different from 'attempted' -- resetting the problems
# makes lcp.done False, but leaves attempts unchanged. # makes lcp.done False, but leaves attempts unchanged.
return self.lcp.done return self.lcp.done
elif self.showanswer == 'closed': elif self.showanswer == SHOWANSWER.CLOSED:
return self.closed() return self.closed()
elif self.showanswer == 'finished': elif self.showanswer == SHOWANSWER.FINISHED:
return self.closed() or self.is_correct() return self.closed() or self.is_correct()
elif self.showanswer == 'correct_or_past_due': elif self.showanswer == SHOWANSWER.CORRECT_OR_PAST_DUE:
return self.is_correct() or self.is_past_due() return self.is_correct() or self.is_past_due()
elif self.showanswer == 'past_due': elif self.showanswer == SHOWANSWER.PAST_DUE:
return self.is_past_due() return self.is_past_due()
elif self.showanswer == 'always': elif self.showanswer == SHOWANSWER.ALWAYS:
return True return True
return False return False
...@@ -952,7 +964,7 @@ class CapaMixin(CapaFields): ...@@ -952,7 +964,7 @@ class CapaMixin(CapaFields):
raise NotFoundError(_("Problem is closed.")) raise NotFoundError(_("Problem is closed."))
# Problem submitted. Student should reset before checking again # Problem submitted. Student should reset before checking again
if self.done and self.rerandomize == "always": if self.done and self.rerandomize == RANDOMIZATION.ALWAYS:
event_info['failure'] = 'unreset' event_info['failure'] = 'unreset'
self.track_function_unmask('problem_check_fail', event_info) self.track_function_unmask('problem_check_fail', event_info)
if dog_stats_api: if dog_stats_api:
...@@ -1206,7 +1218,7 @@ class CapaMixin(CapaFields): ...@@ -1206,7 +1218,7 @@ class CapaMixin(CapaFields):
# was presented to the user, with values interpolated etc, but that can be done # was presented to the user, with values interpolated etc, but that can be done
# later if necessary. # later if necessary.
variant = '' variant = ''
if self.rerandomize != 'never': if self.rerandomize != RANDOMIZATION.NEVER:
variant = self.seed variant = self.seed
is_correct = correct_map.is_correct(input_id) is_correct = correct_map.is_correct(input_id)
...@@ -1333,7 +1345,7 @@ class CapaMixin(CapaFields): ...@@ -1333,7 +1345,7 @@ class CapaMixin(CapaFields):
# Problem submitted. Student should reset before saving # Problem submitted. Student should reset before saving
# again. # again.
if self.done and self.rerandomize == "always": if self.done and self.rerandomize == RANDOMIZATION.ALWAYS:
event_info['failure'] = 'done' event_info['failure'] = 'done'
self.track_function_unmask('save_problem_fail', event_info) self.track_function_unmask('save_problem_fail', event_info)
return { return {
...@@ -1357,7 +1369,7 @@ class CapaMixin(CapaFields): ...@@ -1357,7 +1369,7 @@ class CapaMixin(CapaFields):
def reset_problem(self, _data): def reset_problem(self, _data):
""" """
Changes problem state to unfinished -- removes student answers, Changes problem state to unfinished -- removes student answers,
and causes problem to rerender itself. Causes problem to rerender itself if randomization is enabled.
Returns a dictionary of the form: Returns a dictionary of the form:
{'success': True/False, {'success': True/False,
...@@ -1380,7 +1392,7 @@ class CapaMixin(CapaFields): ...@@ -1380,7 +1392,7 @@ class CapaMixin(CapaFields):
'error': _("Problem is closed."), 'error': _("Problem is closed."),
} }
if not self.done: if not self.is_submitted():
event_info['failure'] = 'not_done' event_info['failure'] = 'not_done'
self.track_function_unmask('reset_problem_fail', event_info) self.track_function_unmask('reset_problem_fail', event_info)
return { return {
...@@ -1389,7 +1401,7 @@ class CapaMixin(CapaFields): ...@@ -1389,7 +1401,7 @@ class CapaMixin(CapaFields):
'error': _("Refresh the page and make an attempt before resetting."), 'error': _("Refresh the page and make an attempt before resetting."),
} }
if self.rerandomize in ["always", "onreset"]: if self.is_submitted() and self.rerandomize in [RANDOMIZATION.ALWAYS, RANDOMIZATION.ONRESET]:
# Reset random number generator seed. # Reset random number generator seed.
self.choose_new_seed() self.choose_new_seed()
......
# -*- coding: utf-8 -*-
"""
Constants for capa_base problems
"""
class SHOWANSWER:
"""
Constants for when to show answer
"""
ALWAYS = "always"
ANSWERED = "answered"
ATTEMPTED = "attempted"
CLOSED = "closed"
FINISHED = "finished"
CORRECT_OR_PAST_DUE = "correct_or_past_due"
PAST_DUE = "past_due"
NEVER = "never"
class RANDOMIZATION:
"""
Constants for problem randomization
"""
ALWAYS = "always"
ONRESET = "onreset"
NEVER = "never"
PER_STUDENT = "per_student"
""" """
Support for inheritance of fields down an XBlock hierarchy. Support for inheritance of fields down an XBlock hierarchy.
""" """
from __future__ import absolute_import
from datetime import datetime from datetime import datetime
from pytz import UTC from pytz import UTC
from xmodule.partitions.partitions import UserPartition from xmodule.partitions.partitions import UserPartition
from xblock.fields import Scope, Boolean, String, Float, XBlockMixin, Dict, Integer, List from xblock.fields import Scope, Boolean, String, Float, XBlockMixin, Dict, Integer, List
from xblock.runtime import KeyValueStore, KvsFieldData from xblock.runtime import KeyValueStore, KvsFieldData
from xmodule.fields import Date, Timedelta from xmodule.fields import Date, Timedelta
from django.conf import settings
# Make '_' a no-op so we can scrape strings # Make '_' a no-op so we can scrape strings
_ = lambda text: text _ = lambda text: text
...@@ -153,6 +154,16 @@ class InheritanceMixin(XBlockMixin): ...@@ -153,6 +154,16 @@ class InheritanceMixin(XBlockMixin):
scope=Scope.settings scope=Scope.settings
) )
reset_key = "DEFAULT_SHOW_RESET_BUTTON"
default_reset_button = getattr(settings, reset_key) if hasattr(settings, reset_key) else False
show_reset_button = Boolean(
display_name=_("Show Reset Button for Problems"),
help=_("Enter true or false. If true, problems default to displaying a 'Reset' button. This value may be "
"overriden in each problem's settings. Existing problems whose reset setting have not been changed are affected."),
scope=Scope.settings,
default=default_reset_button
)
def compute_inherited_metadata(descriptor): def compute_inherited_metadata(descriptor):
"""Given a descriptor, traverse all of its descendants and do metadata """Given a descriptor, traverse all of its descendants and do metadata
......
...@@ -13,6 +13,7 @@ import random ...@@ -13,6 +13,7 @@ import random
import os import os
import textwrap import textwrap
import unittest import unittest
import ddt
from mock import Mock, patch from mock import Mock, patch
import webob import webob
...@@ -31,6 +32,7 @@ from xblock.fields import ScopeIds ...@@ -31,6 +32,7 @@ from xblock.fields import ScopeIds
from . import get_test_system from . import get_test_system
from pytz import UTC from pytz import UTC
from capa.correctmap import CorrectMap from capa.correctmap import CorrectMap
from ..capa_base_constants import RANDOMIZATION
class CapaFactory(object): class CapaFactory(object):
...@@ -140,7 +142,6 @@ class CapaFactory(object): ...@@ -140,7 +142,6 @@ class CapaFactory(object):
return module return module
class CapaFactoryWithFiles(CapaFactory): class CapaFactoryWithFiles(CapaFactory):
""" """
A factory for creating a Capa problem with files attached. A factory for creating a Capa problem with files attached.
...@@ -182,6 +183,7 @@ if submission[0] == '': ...@@ -182,6 +183,7 @@ if submission[0] == '':
""") """)
@ddt.ddt
class CapaModuleTest(unittest.TestCase): class CapaModuleTest(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -540,39 +542,42 @@ class CapaModuleTest(unittest.TestCase): ...@@ -540,39 +542,42 @@ class CapaModuleTest(unittest.TestCase):
# Expect that number of attempts NOT incremented # Expect that number of attempts NOT incremented
self.assertEqual(module.attempts, 3) self.assertEqual(module.attempts, 3)
def test_check_problem_resubmitted_with_randomize(self): @ddt.data(
rerandomize_values = ['always', 'true'] RANDOMIZATION.ALWAYS,
'true'
for rerandomize in rerandomize_values: )
# Randomize turned on def test_check_problem_resubmitted_with_randomize(self, rerandomize):
module = CapaFactory.create(rerandomize=rerandomize, attempts=0) # Randomize turned on
module = CapaFactory.create(rerandomize=rerandomize, attempts=0)
# Simulate that the problem is completed # Simulate that the problem is completed
module.done = True module.done = True
# Expect that we cannot submit
with self.assertRaises(xmodule.exceptions.NotFoundError):
get_request_dict = {CapaFactory.input_key(): '3.14'}
module.check_problem(get_request_dict)
# Expect that number of attempts NOT incremented # Expect that we cannot submit
self.assertEqual(module.attempts, 0) with self.assertRaises(xmodule.exceptions.NotFoundError):
get_request_dict = {CapaFactory.input_key(): '3.14'}
module.check_problem(get_request_dict)
def test_check_problem_resubmitted_no_randomize(self): # Expect that number of attempts NOT incremented
rerandomize_values = ['never', 'false', 'per_student'] self.assertEqual(module.attempts, 0)
for rerandomize in rerandomize_values: @ddt.data(
# Randomize turned off RANDOMIZATION.NEVER,
module = CapaFactory.create(rerandomize=rerandomize, attempts=0, done=True) 'false',
RANDOMIZATION.PER_STUDENT
)
def test_check_problem_resubmitted_no_randomize(self, rerandomize):
# Randomize turned off
module = CapaFactory.create(rerandomize=rerandomize, attempts=0, done=True)
# Expect that we can submit successfully # Expect that we can submit successfully
get_request_dict = {CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.check_problem(get_request_dict) result = module.check_problem(get_request_dict)
self.assertEqual(result['success'], 'correct') self.assertEqual(result['success'], 'correct')
# Expect that number of attempts IS incremented # Expect that number of attempts IS incremented
self.assertEqual(module.attempts, 1) self.assertEqual(module.attempts, 1)
def test_check_problem_queued(self): def test_check_problem_queued(self):
module = CapaFactory.create(attempts=1) module = CapaFactory.create(attempts=1)
...@@ -813,7 +818,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -813,7 +818,7 @@ class CapaModuleTest(unittest.TestCase):
def test_reset_problem_closed(self): def test_reset_problem_closed(self):
# pre studio default # pre studio default
module = CapaFactory.create(rerandomize="always") module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS)
# Simulate that the problem is closed # Simulate that the problem is closed
with patch('xmodule.capa_module.CapaModule.closed') as mock_closed: with patch('xmodule.capa_module.CapaModule.closed') as mock_closed:
...@@ -944,35 +949,36 @@ class CapaModuleTest(unittest.TestCase): ...@@ -944,35 +949,36 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the result is failure # Expect that the result is failure
self.assertTrue('success' in result and not result['success']) self.assertTrue('success' in result and not result['success'])
def test_save_problem_submitted_with_randomize(self): @ddt.data(
RANDOMIZATION.ALWAYS,
'true'
)
def test_save_problem_submitted_with_randomize(self, rerandomize):
# Capa XModule treats 'always' and 'true' equivalently # Capa XModule treats 'always' and 'true' equivalently
rerandomize_values = ['always', 'true'] module = CapaFactory.create(rerandomize=rerandomize, done=True)
for rerandomize in rerandomize_values:
module = CapaFactory.create(rerandomize=rerandomize, done=True)
# Try to save # Try to save
get_request_dict = {CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.save_problem(get_request_dict) result = module.save_problem(get_request_dict)
# Expect that we cannot save
self.assertTrue('success' in result and not result['success'])
def test_save_problem_submitted_no_randomize(self): # Expect that we cannot save
self.assertTrue('success' in result and not result['success'])
@ddt.data(
RANDOMIZATION.NEVER,
'false',
RANDOMIZATION.PER_STUDENT
)
def test_save_problem_submitted_no_randomize(self, rerandomize):
# Capa XModule treats 'false' and 'per_student' equivalently # Capa XModule treats 'false' and 'per_student' equivalently
rerandomize_values = ['never', 'false', 'per_student'] module = CapaFactory.create(rerandomize=rerandomize, done=True)
for rerandomize in rerandomize_values:
module = CapaFactory.create(rerandomize=rerandomize, done=True)
# Try to save # Try to save
get_request_dict = {CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.save_problem(get_request_dict) result = module.save_problem(get_request_dict)
# Expect that we succeed # Expect that we succeed
self.assertTrue('success' in result and result['success']) self.assertTrue('success' in result and result['success'])
def test_check_button_name(self): def test_check_button_name(self):
...@@ -1066,7 +1072,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1066,7 +1072,7 @@ class CapaModuleTest(unittest.TestCase):
# If user submitted a problem but hasn't reset, # If user submitted a problem but hasn't reset,
# do NOT show the check button # do NOT show the check button
# Note: we can only reset when rerandomize="always" or "true" # Note: we can only reset when rerandomize="always" or "true"
module = CapaFactory.create(rerandomize="always", done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, done=True)
self.assertFalse(module.should_show_check_button()) self.assertFalse(module.should_show_check_button())
module = CapaFactory.create(rerandomize="true", done=True) module = CapaFactory.create(rerandomize="true", done=True)
...@@ -1080,13 +1086,13 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1080,13 +1086,13 @@ class CapaModuleTest(unittest.TestCase):
# and we do NOT have a reset button, then we can show the check button # and we do NOT have a reset button, then we can show the check button
# Setting rerandomize to "never" or "false" ensures that the reset button # Setting rerandomize to "never" or "false" ensures that the reset button
# is not shown # is not shown
module = CapaFactory.create(rerandomize="never", done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.NEVER, done=True)
self.assertTrue(module.should_show_check_button()) self.assertTrue(module.should_show_check_button())
module = CapaFactory.create(rerandomize="false", done=True) module = CapaFactory.create(rerandomize="false", done=True)
self.assertTrue(module.should_show_check_button()) self.assertTrue(module.should_show_check_button())
module = CapaFactory.create(rerandomize="per_student", done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.PER_STUDENT, done=True)
self.assertTrue(module.should_show_check_button()) self.assertTrue(module.should_show_check_button())
def test_should_show_reset_button(self): def test_should_show_reset_button(self):
...@@ -1101,30 +1107,36 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1101,30 +1107,36 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(attempts=attempts, max_attempts=attempts, done=True) module = CapaFactory.create(attempts=attempts, max_attempts=attempts, done=True)
self.assertFalse(module.should_show_reset_button()) self.assertFalse(module.should_show_reset_button())
# If we're NOT randomizing, then do NOT show the reset button # pre studio default value, DO show the reset button
module = CapaFactory.create(rerandomize="never", done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, done=True)
self.assertFalse(module.should_show_reset_button()) self.assertTrue(module.should_show_reset_button())
# If we're NOT randomizing, then do NOT show the reset button # If survey question for capa (max_attempts = 0),
module = CapaFactory.create(rerandomize="per_student", done=True) # DO show the reset button
self.assertFalse(module.should_show_reset_button()) module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, max_attempts=0, done=True)
self.assertTrue(module.should_show_reset_button())
# If we're NOT randomizing, then do NOT show the reset button # If the question is not correct
module = CapaFactory.create(rerandomize="false", done=True) # DO show the reset button
self.assertFalse(module.should_show_reset_button()) module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, max_attempts=0, done=True, correct=False)
self.assertTrue(module.should_show_reset_button())
# If the user hasn't submitted an answer yet, # If the question is correct and randomization is never
# then do NOT show the reset button # DO not show the reset button
module = CapaFactory.create(done=False) module = CapaFactory.create(rerandomize=RANDOMIZATION.NEVER, max_attempts=0, done=True, correct=True)
self.assertFalse(module.should_show_reset_button()) self.assertFalse(module.should_show_reset_button())
# pre studio default value, DO show the reset button # If the question is correct and randomization is always
module = CapaFactory.create(rerandomize="always", done=True) # Show the reset button
module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, max_attempts=0, done=True, correct=True)
self.assertTrue(module.should_show_reset_button()) self.assertTrue(module.should_show_reset_button())
# If survey question for capa (max_attempts = 0), # Don't show reset button if randomization is turned on and the question is not done
# DO show the reset button module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, show_reset_button=False, done=False)
module = CapaFactory.create(rerandomize="always", max_attempts=0, done=True) self.assertFalse(module.should_show_reset_button())
# Show reset button if randomization is turned on and the problem is done
module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, show_reset_button=False, done=True)
self.assertTrue(module.should_show_reset_button()) self.assertTrue(module.should_show_reset_button())
def test_should_show_save_button(self): def test_should_show_save_button(self):
...@@ -1140,7 +1152,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1140,7 +1152,7 @@ class CapaModuleTest(unittest.TestCase):
self.assertFalse(module.should_show_save_button()) self.assertFalse(module.should_show_save_button())
# If user submitted a problem but hasn't reset, do NOT show the save button # If user submitted a problem but hasn't reset, do NOT show the save button
module = CapaFactory.create(rerandomize="always", done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, done=True)
self.assertFalse(module.should_show_save_button()) self.assertFalse(module.should_show_save_button())
module = CapaFactory.create(rerandomize="true", done=True) module = CapaFactory.create(rerandomize="true", done=True)
...@@ -1149,27 +1161,27 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1149,27 +1161,27 @@ class CapaModuleTest(unittest.TestCase):
# If the user has unlimited attempts and we are not randomizing, # If the user has unlimited attempts and we are not randomizing,
# then do NOT show a save button # then do NOT show a save button
# because they can keep using "Check" # because they can keep using "Check"
module = CapaFactory.create(max_attempts=None, rerandomize="never", done=False) module = CapaFactory.create(max_attempts=None, rerandomize=RANDOMIZATION.NEVER, done=False)
self.assertFalse(module.should_show_save_button()) self.assertFalse(module.should_show_save_button())
module = CapaFactory.create(max_attempts=None, rerandomize="false", done=True) module = CapaFactory.create(max_attempts=None, rerandomize="false", done=True)
self.assertFalse(module.should_show_save_button()) self.assertFalse(module.should_show_save_button())
module = CapaFactory.create(max_attempts=None, rerandomize="per_student", done=True) module = CapaFactory.create(max_attempts=None, rerandomize=RANDOMIZATION.PER_STUDENT, done=True)
self.assertFalse(module.should_show_save_button()) self.assertFalse(module.should_show_save_button())
# pre-studio default, DO show the save button # pre-studio default, DO show the save button
module = CapaFactory.create(rerandomize="always", done=False) module = CapaFactory.create(rerandomize=RANDOMIZATION.ALWAYS, done=False)
self.assertTrue(module.should_show_save_button()) self.assertTrue(module.should_show_save_button())
# If we're not randomizing and we have limited attempts, then we can save # If we're not randomizing and we have limited attempts, then we can save
module = CapaFactory.create(rerandomize="never", max_attempts=2, done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.NEVER, max_attempts=2, done=True)
self.assertTrue(module.should_show_save_button()) self.assertTrue(module.should_show_save_button())
module = CapaFactory.create(rerandomize="false", max_attempts=2, done=True) module = CapaFactory.create(rerandomize="false", max_attempts=2, done=True)
self.assertTrue(module.should_show_save_button()) self.assertTrue(module.should_show_save_button())
module = CapaFactory.create(rerandomize="per_student", max_attempts=2, done=True) module = CapaFactory.create(rerandomize=RANDOMIZATION.PER_STUDENT, max_attempts=2, done=True)
self.assertTrue(module.should_show_save_button()) self.assertTrue(module.should_show_save_button())
# If survey question for capa (max_attempts = 0), # If survey question for capa (max_attempts = 0),
...@@ -1197,7 +1209,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1197,7 +1209,7 @@ class CapaModuleTest(unittest.TestCase):
# then show it even if we would ordinarily # then show it even if we would ordinarily
# require a reset first # require a reset first
module = CapaFactory.create(force_save_button="true", module = CapaFactory.create(force_save_button="true",
rerandomize="always", rerandomize=RANDOMIZATION.ALWAYS,
done=True) done=True)
self.assertTrue(module.should_show_save_button()) self.assertTrue(module.should_show_save_button())
...@@ -1331,48 +1343,66 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1331,48 +1343,66 @@ class CapaModuleTest(unittest.TestCase):
context = render_args[1] context = render_args[1]
self.assertTrue(error_msg in context['problem']['html']) self.assertTrue(error_msg in context['problem']['html'])
def test_random_seed_no_change(self): @ddt.data(
'false',
'true',
RANDOMIZATION.NEVER,
RANDOMIZATION.PER_STUDENT,
RANDOMIZATION.ALWAYS,
RANDOMIZATION.ONRESET
)
def test_random_seed_no_change(self, rerandomize):
# Run the test for each possible rerandomize value # Run the test for each possible rerandomize value
for rerandomize in ['false', 'never',
'per_student', 'always',
'true', 'onreset']:
module = CapaFactory.create(rerandomize=rerandomize)
# Get the seed module = CapaFactory.create(rerandomize=rerandomize)
# By this point, the module should have persisted the seed
seed = module.seed
self.assertTrue(seed is not None)
# If we're not rerandomizing, the seed is always set # Get the seed
# to the same value (1) # By this point, the module should have persisted the seed
if rerandomize in ['never']: seed = module.seed
self.assertEqual(seed, 1, self.assertTrue(seed is not None)
msg="Seed should always be 1 when rerandomize='%s'" % rerandomize)
# Check the problem # If we're not rerandomizing, the seed is always set
get_request_dict = {CapaFactory.input_key(): '3.14'} # to the same value (1)
module.check_problem(get_request_dict) if rerandomize == RANDOMIZATION.NEVER:
self.assertEqual(seed, 1,
msg="Seed should always be 1 when rerandomize='%s'" % rerandomize)
# Expect that the seed is the same # Check the problem
self.assertEqual(seed, module.seed) get_request_dict = {CapaFactory.input_key(): '3.14'}
module.check_problem(get_request_dict)
# Save the problem # Expect that the seed is the same
module.save_problem(get_request_dict) self.assertEqual(seed, module.seed)
# Expect that the seed is the same # Save the problem
self.assertEqual(seed, module.seed) module.save_problem(get_request_dict)
# Expect that the seed is the same
self.assertEqual(seed, module.seed)
@ddt.data(
'false',
'true',
RANDOMIZATION.NEVER,
RANDOMIZATION.PER_STUDENT,
RANDOMIZATION.ALWAYS,
RANDOMIZATION.ONRESET
)
def test_random_seed_with_reset(self, rerandomize):
"""
Run the test for each possible rerandomize value
"""
def test_random_seed_with_reset(self):
def _reset_and_get_seed(module): def _reset_and_get_seed(module):
''' """
Reset the XModule and return the module's seed Reset the XModule and return the module's seed
''' """
# Simulate submitting an attempt # Simulate submitting an attempt
# We need to do this, or reset_problem() will # We need to do this, or reset_problem() will
# fail with a complaint that we haven't submitted # fail because it won't re-randomize until the problem has been submitted
# the problem yet. # the problem yet.
module.done = True module.done = True
...@@ -1397,45 +1427,83 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1397,45 +1427,83 @@ class CapaModuleTest(unittest.TestCase):
break break
return success return success
# Run the test for each possible rerandomize value module = CapaFactory.create(rerandomize=rerandomize, done=True)
for rerandomize in ['never', 'false', 'per_student',
'always', 'true', 'onreset']: # Get the seed
module = CapaFactory.create(rerandomize=rerandomize) # By this point, the module should have persisted the seed
seed = module.seed
self.assertTrue(seed is not None)
# We do NOT want the seed to reset if rerandomize
# is set to 'never' -- it should still be 1
# The seed also stays the same if we're randomizing
# 'per_student': the same student should see the same problem
if rerandomize in [RANDOMIZATION.NEVER,
'false',
RANDOMIZATION.PER_STUDENT]:
self.assertEqual(seed, _reset_and_get_seed(module))
# Otherwise, we expect the seed to change
# to another valid seed
else:
# Since there's a small chance we might get the
# same seed again, give it 5 chances
# to generate a different seed
success = _retry_and_check(5, lambda: _reset_and_get_seed(module) != seed)
self.assertTrue(module.seed is not None)
msg = 'Could not get a new seed from reset after 5 tries'
self.assertTrue(success, msg)
@ddt.data(
'false',
'true',
RANDOMIZATION.NEVER,
RANDOMIZATION.PER_STUDENT,
RANDOMIZATION.ALWAYS,
RANDOMIZATION.ONRESET
)
def test_random_seed_with_reset_question_unsubmitted(self, rerandomize):
"""
Run the test for each possible rerandomize value
"""
def _reset_and_get_seed(module):
"""
Reset the XModule and return the module's seed
"""
# Get the seed # Reset the problem
# By this point, the module should have persisted the seed # By default, the problem is instantiated as unsubmitted
seed = module.seed module.reset_problem({})
self.assertTrue(seed is not None)
# We do NOT want the seed to reset if rerandomize # Return the seed
# is set to 'never' -- it should still be 1 return module.seed
# The seed also stays the same if we're randomizing
# 'per_student': the same student should see the same problem
if rerandomize in ['never', 'false', 'per_student']:
self.assertEqual(seed, _reset_and_get_seed(module))
# Otherwise, we expect the seed to change module = CapaFactory.create(rerandomize=rerandomize, done=False)
# to another valid seed
else:
# Since there's a small chance we might get the # Get the seed
# same seed again, give it 5 chances # By this point, the module should have persisted the seed
# to generate a different seed seed = module.seed
success = _retry_and_check(5, lambda: _reset_and_get_seed(module) != seed) self.assertTrue(seed is not None)
self.assertTrue(module.seed is not None) #the seed should never change because the student hasn't finished the problem
msg = 'Could not get a new seed from reset after 5 tries' self.assertEqual(seed, _reset_and_get_seed(module))
self.assertTrue(success, msg)
def test_random_seed_bins(self): @ddt.data(
RANDOMIZATION.ALWAYS,
RANDOMIZATION.PER_STUDENT,
'true',
RANDOMIZATION.ONRESET
)
def test_random_seed_bins(self, rerandomize):
# Assert that we are limiting the number of possible seeds. # Assert that we are limiting the number of possible seeds.
# Get a bunch of seeds, they should all be in 0-999.
# Check the conditions that generate random seeds i = 200
for rerandomize in ['always', 'per_student', 'true', 'onreset']: while i > 0:
# Get a bunch of seeds, they should all be in 0-999. module = CapaFactory.create(rerandomize=rerandomize)
for i in range(200): assert 0 <= module.seed < 1000
module = CapaFactory.create(rerandomize=rerandomize) i -= 1
assert 0 <= module.seed < 1000
@patch('xmodule.capa_base.log') @patch('xmodule.capa_base.log')
@patch('xmodule.capa_base.Progress') @patch('xmodule.capa_base.Progress')
...@@ -1765,7 +1833,7 @@ class TestProblemCheckTracking(unittest.TestCase): ...@@ -1765,7 +1833,7 @@ class TestProblemCheckTracking(unittest.TestCase):
def test_rerandomized_inputs(self): def test_rerandomized_inputs(self):
factory = CapaFactory factory = CapaFactory
module = factory.create(rerandomize='always') module = factory.create(rerandomize=RANDOMIZATION.ALWAYS)
answer_input_dict = { answer_input_dict = {
factory.input_key(2): '3.14' factory.input_key(2): '3.14'
......
...@@ -72,37 +72,77 @@ Feature: LMS.Answer problems ...@@ -72,37 +72,77 @@ Feature: LMS.Answer problems
Scenario: I can reset a problem Scenario: I can reset a problem
Given I am viewing a "<ProblemType>" problem Given I am viewing a randomization "<Randomization>" "<ProblemType>" problem with reset button on
And I answer a "<ProblemType>" problem "<Correctness>ly"
When I reset the problem
Then my "<ProblemType>" answer is marked "unanswered"
And The "<ProblemType>" problem displays a "blank" answer
Examples:
| ProblemType | Correctness | Randomization |
| drop down | correct | always |
| drop down | incorrect | always |
| multiple choice | correct | always |
| multiple choice | incorrect | always |
| checkbox | correct | always |
| checkbox | incorrect | always |
| radio | correct | always |
| radio | incorrect | always |
| string | correct | always |
| string | incorrect | always |
| numerical | correct | always |
| numerical | incorrect | always |
| formula | correct | always |
| formula | incorrect | always |
| script | correct | always |
| script | incorrect | always |
| radio_text | correct | always |
| radio_text | incorrect | always |
| checkbox_text | correct | always |
| checkbox_text | incorrect | always |
| image | correct | always |
| image | incorrect | always |
Scenario: I can reset a non-randomized problem that I answer incorrectly
Given I am viewing a randomization "<Randomization>" "<ProblemType>" problem with reset button on
And I answer a "<ProblemType>" problem "<Correctness>ly" And I answer a "<ProblemType>" problem "<Correctness>ly"
When I reset the problem When I reset the problem
Then my "<ProblemType>" answer is marked "unanswered" Then my "<ProblemType>" answer is marked "unanswered"
And The "<ProblemType>" problem displays a "blank" answer And The "<ProblemType>" problem displays a "blank" answer
Examples: Examples:
| ProblemType | Correctness | | ProblemType | Correctness | Randomization |
| drop down | correct | | drop down | incorrect | never |
| drop down | incorrect | | drop down | incorrect | never |
| multiple choice | correct | | multiple choice | incorrect | never |
| multiple choice | incorrect | | checkbox | incorrect | never |
| checkbox | correct | | radio | incorrect | never |
| checkbox | incorrect | | string | incorrect | never |
| radio | correct | | numerical | incorrect | never |
| radio | incorrect | | formula | incorrect | never |
| string | correct | | script | incorrect | never |
| string | incorrect | | radio_text | incorrect | never |
| numerical | correct | | checkbox_text | incorrect | never |
| numerical | incorrect | | image | incorrect | never |
| formula | correct |
| formula | incorrect | Scenario: The reset button doesn't show up
| script | correct | Given I am viewing a randomization "<Randomization>" "<ProblemType>" problem with reset button on
| script | incorrect | And I answer a "<ProblemType>" problem "<Correctness>ly"
| radio_text | correct | Then The "Reset" button does not appear
| radio_text | incorrect |
| checkbox_text | correct |
| checkbox_text | incorrect |
| image | correct |
| image | incorrect |
Examples:
| ProblemType | Correctness | Randomization |
| drop down | correct | never |
| multiple choice | correct | never |
| checkbox | correct | never |
| radio | correct | never |
| string | correct | never |
| numerical | correct | never |
| formula | correct | never |
| script | correct | never |
| radio_text | correct | never |
| checkbox_text | correct | never |
| image | correct | never |
Scenario: I can answer a problem with one attempt correctly and not reset Scenario: I can answer a problem with one attempt correctly and not reset
Given I am viewing a "multiple choice" problem with "1" attempt Given I am viewing a "multiple choice" problem with "1" attempt
...@@ -115,6 +155,12 @@ Feature: LMS.Answer problems ...@@ -115,6 +155,12 @@ Feature: LMS.Answer problems
When I answer a "multiple choice" problem "correctly" When I answer a "multiple choice" problem "correctly"
Then The "Reset" button does appear Then The "Reset" button does appear
Scenario: I can answer a problem with multiple attempts correctly but cannot reset because randomization is off
Given I am viewing a randomization "never" "multiple choice" problem with "3" attempts with reset
Then I should see "You have used 0 of 3 submissions" somewhere in the page
When I answer a "multiple choice" problem "correctly"
Then The "Reset" button does not appear
Scenario: I can view how many attempts I have left on a problem Scenario: I can view how many attempts I have left on a problem
Given I am viewing a "multiple choice" problem with "3" attempts Given I am viewing a "multiple choice" problem with "3" attempts
Then I should see "You have used 0 of 3 submissions" somewhere in the page Then I should see "You have used 0 of 3 submissions" somewhere in the page
...@@ -165,6 +211,34 @@ Feature: LMS.Answer problems ...@@ -165,6 +211,34 @@ Feature: LMS.Answer problems
| image | correct | 1/1 point | 1 point possible | | image | correct | 1/1 point | 1 point possible |
| image | incorrect | 1 point possible | 1 point possible | | image | incorrect | 1 point possible | 1 point possible |
Scenario: I can see my score on a problem when I answer it and after I reset it
Given I am viewing a "<ProblemType>" problem with randomization "<Randomization>" with reset button on
When I answer a "<ProblemType>" problem "<Correctness>ly"
Then I should see a score of "<Score>"
When I reset the problem
Then I should see a score of "<Points Possible>"
Examples:
| ProblemType | Correctness | Score | Points Possible | Randomization |
| drop down | correct | 1/1 point | 1 point possible | never |
| drop down | incorrect | 1 point possible | 1 point possible | never |
| multiple choice | correct | 1/1 point | 1 point possible | never |
| multiple choice | incorrect | 1 point possible | 1 point possible | never |
| checkbox | correct | 1/1 point | 1 point possible | never |
| checkbox | incorrect | 1 point possible | 1 point possible | never |
| radio | correct | 1/1 point | 1 point possible | never |
| radio | incorrect | 1 point possible | 1 point possible | never |
| string | correct | 1/1 point | 1 point possible | never |
| string | incorrect | 1 point possible | 1 point possible | never |
| numerical | correct | 1/1 point | 1 point possible | never |
| numerical | incorrect | 1 point possible | 1 point possible | never |
| formula | correct | 1/1 point | 1 point possible | never |
| formula | incorrect | 1 point possible | 1 point possible | never |
| script | correct | 2/2 points | 2 points possible | never |
| script | incorrect | 2 points possible | 2 points possible | never |
| image | correct | 1/1 point | 1 point possible | never |
| image | incorrect | 1 point possible | 1 point possible | never |
Scenario: I can see my score on a problem to which I submit a blank answer Scenario: I can see my score on a problem to which I submit a blank answer
Given I am viewing a "<ProblemType>" problem Given I am viewing a "<ProblemType>" problem
When I check a problem When I check a problem
......
...@@ -26,6 +26,13 @@ def view_problem_with_attempts(step, problem_type, attempts): ...@@ -26,6 +26,13 @@ def view_problem_with_attempts(step, problem_type, attempts):
_view_problem(step, problem_type, {'max_attempts': attempts}) _view_problem(step, problem_type, {'max_attempts': attempts})
@step(u'I am viewing a randomization "([^"]*)" "([^"]*)" problem with "([^"]*)" attempts with reset')
def view_problem_attempts_reset(step, randomization, problem_type, attempts, ):
_view_problem(step, problem_type, {'max_attempts': attempts,
'rerandomize': randomization,
'show_reset_button': True})
@step(u'I am viewing a "([^"]*)" that shows the answer "([^"]*)"') @step(u'I am viewing a "([^"]*)" that shows the answer "([^"]*)"')
def view_problem_with_show_answer(step, problem_type, answer): def view_problem_with_show_answer(step, problem_type, answer):
_view_problem(step, problem_type, {'showanswer': answer}) _view_problem(step, problem_type, {'showanswer': answer})
...@@ -36,6 +43,11 @@ def view_problem(step, problem_type): ...@@ -36,6 +43,11 @@ def view_problem(step, problem_type):
_view_problem(step, problem_type) _view_problem(step, problem_type)
@step(u'I am viewing a randomization "([^"]*)" "([^"]*)" problem with reset button on')
def view_random_reset_problem(step, randomization, problem_type):
_view_problem(step, problem_type, {'rerandomize': randomization, 'show_reset_button': True})
@step(u'External graders respond "([^"]*)"') @step(u'External graders respond "([^"]*)"')
def set_external_grader_response(step, correctness): def set_external_grader_response(step, correctness):
assert(correctness in ['correct', 'incorrect']) assert(correctness in ['correct', 'incorrect'])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment