Commit 4c83a2f0 by Dmitry Viskov Committed by Dmitry Viskov

Make it impossible to click "final check" without selecting a choice

parent 91c0517b
...@@ -247,6 +247,87 @@ describe 'Problem', -> ...@@ -247,6 +247,87 @@ describe 'Problem', ->
runs -> runs ->
expect(@problem.checkButtonLabel.text).toHaveBeenCalledWith 'Check' expect(@problem.checkButtonLabel.text).toHaveBeenCalledWith 'Check'
describe 'check button on problems', ->
beforeEach ->
@problem = new Problem($('.xblock-student_view'))
@checkDisabled = (v) -> expect(@problem.checkButton.hasClass('is-disabled')).toBe(v)
describe 'some basic tests for check button', ->
it 'should become enabled after a value is entered into the text box', ->
$('#input_example_1').val('test').trigger('input')
@checkDisabled false
$('#input_example_1').val('').trigger('input')
@checkDisabled true
describe 'some advanced tests for check button', ->
it 'should become enabled after a checkbox is checked', ->
html = '''
<div class="choicegroup">
<label for="input_1_1_1"><input type="checkbox" name="input_1_1" id="input_1_1_1" value="1"> One</label>
<label for="input_1_1_2"><input type="checkbox" name="input_1_1" id="input_1_1_2" value="2"> Two</label>
<label for="input_1_1_3"><input type="checkbox" name="input_1_1" id="input_1_1_3" value="3"> Three</label>
</div>
'''
$('#input_example_1').replaceWith(html)
@problem.checkAnswersAndCheckButton true
@checkDisabled true
$('#input_1_1_1').attr('checked', true).trigger('click')
@checkDisabled false
$('#input_1_1_1').attr('checked', false).trigger('click')
@checkDisabled true
it 'should become enabled after a radiobutton is checked', ->
html = '''
<div class="choicegroup">
<label for="input_1_1_1"><input type="radio" name="input_1_1" id="input_1_1_1" value="1"> One</label>
<label for="input_1_1_2"><input type="radio" name="input_1_1" id="input_1_1_2" value="2"> Two</label>
<label for="input_1_1_3"><input type="radio" name="input_1_1" id="input_1_1_3" value="3"> Three</label>
</div>
'''
$('#input_example_1').replaceWith(html)
@problem.checkAnswersAndCheckButton true
@checkDisabled true
$('#input_1_1_1').attr('checked', true).trigger('click')
@checkDisabled false
$('#input_1_1_1').attr('checked', false).trigger('click')
@checkDisabled true
it 'should become enabled after a value is selected in a selector', ->
html = '''
<div id="problem_sel">
<select>
<option value="val0"></option>
<option value="val1">1</option>
<option value="val2">2</option>
</select>
</div>
'''
$('#input_example_1').replaceWith(html)
@problem.checkAnswersAndCheckButton true
@checkDisabled true
$("#problem_sel select").val("val2").trigger('change')
@checkDisabled false
$("#problem_sel select").val("val0").trigger('change')
@checkDisabled true
it 'should become enabled after a radiobutton is checked and a value is entered into the text box', ->
html = '''
<div class="choicegroup">
<label for="input_1_1_1"><input type="radio" name="input_1_1" id="input_1_1_1" value="1"> One</label>
<label for="input_1_1_2"><input type="radio" name="input_1_1" id="input_1_1_2" value="2"> Two</label>
<label for="input_1_1_3"><input type="radio" name="input_1_1" id="input_1_1_3" value="3"> Three</label>
</div>
'''
$(html).insertAfter('#input_example_1')
@problem.checkAnswersAndCheckButton true
@checkDisabled true
$('#input_1_1_1').attr('checked', true).trigger('click')
@checkDisabled true
$('#input_example_1').val('111').trigger('input')
@checkDisabled false
$('#input_1_1_1').attr('checked', false).trigger('click')
@checkDisabled true
describe 'reset', -> describe 'reset', ->
beforeEach -> beforeEach ->
@problem = new Problem($('.xblock-student_view')) @problem = new Problem($('.xblock-student_view'))
......
...@@ -49,6 +49,8 @@ class @Problem ...@@ -49,6 +49,8 @@ class @Problem
window.globalTooltipManager.hide() window.globalTooltipManager.hide()
@bindResetCorrectness() @bindResetCorrectness()
if @checkButton.length
@checkAnswersAndCheckButton true
# Collapsibles # Collapsibles
Collapsible.setCollapsibles(@el) Collapsible.setCollapsibles(@el)
...@@ -452,6 +454,58 @@ class @Problem ...@@ -452,6 +454,58 @@ class @Problem
element.CodeMirror.save() if element.CodeMirror.save element.CodeMirror.save() if element.CodeMirror.save
@answers = @inputs.serialize() @answers = @inputs.serialize()
checkAnswersAndCheckButton: (bind=false) =>
# Used to check available answers and if something is checked (or the answer is set in some textbox)
# "Check"/"Final check" button becomes enabled. Otherwise it is disabled by default.
# params:
# 'bind' used on the first check to attach event handlers to input fields
# to change "Check"/"Final check" enable status in case of some manipulations with answers
answered = true
at_least_one_text_input_found = false
one_text_input_filled = false
@el.find("input:text").each (i, text_field) =>
at_least_one_text_input_found = true
if $(text_field).is(':visible')
if $(text_field).val() isnt ''
one_text_input_filled = true
if bind
$(text_field).on 'input', (e) =>
@checkAnswersAndCheckButton()
return
return
if at_least_one_text_input_found and not one_text_input_filled
answered = false
@el.find(".choicegroup").each (i, choicegroup_block) =>
checked = false
$(choicegroup_block).find("input[type=checkbox], input[type=radio]").each (j, checkbox_or_radio) =>
if $(checkbox_or_radio).is(':checked')
checked = true
if bind
$(checkbox_or_radio).on 'click', (e) =>
@checkAnswersAndCheckButton()
return
return
if not checked
answered = false
return
@el.find("select").each (i, select_field) =>
selected_option = $(select_field).find("option:selected").text().trim()
if selected_option is ''
answered = false
if bind
$(select_field).on 'change', (e) =>
@checkAnswersAndCheckButton()
return
return
if answered
@enableCheckButton true
else
@enableCheckButton false, false
bindResetCorrectness: -> bindResetCorrectness: ->
# Loop through all input types # Loop through all input types
# Bind the reset functions at that scope. # Bind the reset functions at that scope.
......
...@@ -6,6 +6,7 @@ See also lettuce tests in lms/djangoapps/courseware/features/problems.feature ...@@ -6,6 +6,7 @@ See also lettuce tests in lms/djangoapps/courseware/features/problems.feature
import random import random
import textwrap import textwrap
from nose import SkipTest
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from selenium.webdriver import ActionChains from selenium.webdriver import ActionChains
...@@ -135,6 +136,8 @@ class ProblemTypeTestMixin(object): ...@@ -135,6 +136,8 @@ class ProblemTypeTestMixin(object):
""" """
Test cases shared amongst problem types. Test cases shared amongst problem types.
""" """
can_submit_blank = False
@attr('shard_7') @attr('shard_7')
def test_answer_correctly(self): def test_answer_correctly(self):
""" """
...@@ -200,15 +203,34 @@ class ProblemTypeTestMixin(object): ...@@ -200,15 +203,34 @@ class ProblemTypeTestMixin(object):
Then my "<ProblemType>" answer is marked "incorrect" Then my "<ProblemType>" answer is marked "incorrect"
And The "<ProblemType>" problem displays a "blank" answer And The "<ProblemType>" problem displays a "blank" answer
""" """
if not self.can_submit_blank:
raise SkipTest("Test incompatible with the current problem type")
self.problem_page.wait_for( self.problem_page.wait_for(
lambda: self.problem_page.problem_name == self.problem_name, lambda: self.problem_page.problem_name == self.problem_name,
"Make sure the correct problem is on the page" "Make sure the correct problem is on the page"
) )
# Leave the problem unchanged and click check. # Leave the problem unchanged and click check.
self.assertNotIn('is-disabled', self.problem_page.q(css='div.problem button.check').attrs('class')[0])
self.problem_page.click_check() self.problem_page.click_check()
self.wait_for_status('incorrect') self.wait_for_status('incorrect')
@attr('shard_7')
def test_cant_submit_blank_answer(self):
"""
Scenario: I can't submit a blank answer
When I try to submit blank answer
Then I can't check a problem
"""
if self.can_submit_blank:
raise SkipTest("Test incompatible with the current problem type")
self.problem_page.wait_for(
lambda: self.problem_page.problem_name == self.problem_name,
"Make sure the correct problem is on the page"
)
self.assertIn('is-disabled', self.problem_page.q(css='div.problem button.check').attrs('class')[0])
@attr('a11y') @attr('a11y')
def test_problem_type_a11y(self): def test_problem_type_a11y(self):
""" """
...@@ -236,6 +258,8 @@ class AnnotationProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin): ...@@ -236,6 +258,8 @@ class AnnotationProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
factory = AnnotationResponseXMLFactory() factory = AnnotationResponseXMLFactory()
can_submit_blank = True
factory_kwargs = { factory_kwargs = {
'title': 'Annotation Problem', 'title': 'Annotation Problem',
'text': 'The text being annotated', 'text': 'The text being annotated',
...@@ -686,6 +710,13 @@ class CodeProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin): ...@@ -686,6 +710,13 @@ class CodeProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
""" """
pass pass
def test_cant_submit_blank_answer(self):
"""
Overridden for script test because the testing grader always responds
with "correct"
"""
pass
class ChoiceTextProbelmTypeTestBase(ProblemTypeTestBase): class ChoiceTextProbelmTypeTestBase(ProblemTypeTestBase):
""" """
...@@ -801,6 +832,8 @@ class ImageProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin): ...@@ -801,6 +832,8 @@ class ImageProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
factory = ImageResponseXMLFactory() factory = ImageResponseXMLFactory()
can_submit_blank = True
factory_kwargs = { factory_kwargs = {
'src': '/static/images/placeholder-image.png', 'src': '/static/images/placeholder-image.png',
'rectangle': '(0,0)-(50,50)', 'rectangle': '(0,0)-(50,50)',
......
...@@ -180,16 +180,22 @@ Feature: LMS.Answer problems ...@@ -180,16 +180,22 @@ Feature: LMS.Answer problems
Examples: Examples:
| ProblemType | Points Possible | | ProblemType | Points Possible |
| drop down | 1 point possible |
| multiple choice | 1 point possible |
| checkbox | 1 point possible |
| radio | 1 point possible |
#| string | 1 point possible |
| numerical | 1 point possible |
| formula | 1 point possible |
| script | 2 points possible |
| image | 1 point possible | | image | 1 point possible |
Scenario: I can't submit a blank answer
Given I am viewing a "<ProblemType>" problem
Then I can't check a problem
Examples:
| ProblemType |
| drop down |
| multiple choice |
| checkbox |
| radio |
| string |
| numerical |
| formula |
| script |
Scenario: I can reset the correctness of a problem after changing my answer Scenario: I can reset the correctness of a problem after changing my answer
Given I am viewing a "<ProblemType>" problem Given I am viewing a "<ProblemType>" problem
...@@ -234,21 +240,3 @@ Feature: LMS.Answer problems ...@@ -234,21 +240,3 @@ Feature: LMS.Answer problems
| multiple choice | incorrect | correct | | multiple choice | incorrect | correct |
| radio | correct | incorrect | | radio | correct | incorrect |
| radio | incorrect | correct | | radio | incorrect | correct |
Scenario: I can reset the correctness of a problem after submitting a blank answer
Given I am viewing a "<ProblemType>" problem
When I check a problem
And I input an answer on a "<ProblemType>" problem "correctly"
Then my "<ProblemType>" answer is marked "unanswered"
Examples:
| ProblemType |
| drop down |
| multiple choice |
| checkbox |
| radio |
#| string |
| numerical |
| formula |
| script |
...@@ -92,12 +92,21 @@ def check_problem(step): ...@@ -92,12 +92,21 @@ def check_problem(step):
# first scroll down so the loading mathjax button does not # first scroll down so the loading mathjax button does not
# cover up the Check button # cover up the Check button
world.browser.execute_script("window.scrollTo(0,1024)") world.browser.execute_script("window.scrollTo(0,1024)")
assert world.is_css_not_present("button.check.is-disabled")
world.css_click("button.check") world.css_click("button.check")
# Wait for the problem to finish re-rendering # Wait for the problem to finish re-rendering
world.wait_for_ajax_complete() world.wait_for_ajax_complete()
@step(u"I can't check a problem")
def assert_cant_check_problem(step): # pylint: disable=unused-argument
# first scroll down so the loading mathjax button does not
# cover up the Check button
world.browser.execute_script("window.scrollTo(0,1024)")
assert world.is_css_present("button.check.is-disabled")
@step(u'The "([^"]*)" problem displays a "([^"]*)" answer') @step(u'The "([^"]*)" problem displays a "([^"]*)" answer')
def assert_problem_has_answer(step, problem_type, answer_class): def assert_problem_has_answer(step, problem_type, answer_class):
''' '''
......
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