Commit 67c055e2 by Diana Huang

Merge pull request #12206 from CredoReference/impossible-to-click-check-without-answer

Make it impossible to click "final check" without selecting a choice
parents fb83cd71 61127b61
......@@ -247,6 +247,95 @@ describe 'Problem', ->
runs ->
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
it 'should become enabled if there are only hidden input fields', ->
html = '''
<input type="text" name="test" id="test" aria-describedby="answer_test" value="" style="display:none;">
'''
$('#input_example_1').replaceWith(html)
@problem.checkAnswersAndCheckButton true
@checkDisabled false
describe 'reset', ->
beforeEach ->
@problem = new Problem($('.xblock-student_view'))
......
......@@ -49,6 +49,8 @@ class @Problem
window.globalTooltipManager.hide()
@bindResetCorrectness()
if @checkButton.length
@checkAnswersAndCheckButton true
# Collapsibles
Collapsible.setCollapsibles(@el)
......@@ -452,6 +454,58 @@ class @Problem
element.CodeMirror.save() if element.CodeMirror.save
@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) =>
if $(text_field).is(':visible')
at_least_one_text_input_found = true
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: ->
# Loop through all input types
# Bind the reset functions at that scope.
......
......@@ -6,6 +6,7 @@ See also lettuce tests in lms/djangoapps/courseware/features/problems.feature
import random
import textwrap
from nose import SkipTest
from abc import ABCMeta, abstractmethod
from nose.plugins.attrib import attr
from selenium.webdriver import ActionChains
......@@ -135,6 +136,8 @@ class ProblemTypeTestMixin(object):
"""
Test cases shared amongst problem types.
"""
can_submit_blank = False
@attr('shard_7')
def test_answer_correctly(self):
"""
......@@ -200,15 +203,34 @@ class ProblemTypeTestMixin(object):
Then my "<ProblemType>" answer is marked "incorrect"
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(
lambda: self.problem_page.problem_name == self.problem_name,
"Make sure the correct problem is on the page"
)
# 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.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')
def test_problem_type_a11y(self):
"""
......@@ -236,6 +258,8 @@ class AnnotationProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
factory = AnnotationResponseXMLFactory()
can_submit_blank = True
factory_kwargs = {
'title': 'Annotation Problem',
'text': 'The text being annotated',
......@@ -686,6 +710,13 @@ class CodeProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
"""
pass
def test_cant_submit_blank_answer(self):
"""
Overridden for script test because the testing grader always responds
with "correct"
"""
pass
class ChoiceTextProbelmTypeTestBase(ProblemTypeTestBase):
"""
......@@ -801,6 +832,8 @@ class ImageProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
factory = ImageResponseXMLFactory()
can_submit_blank = True
factory_kwargs = {
'src': '/static/images/placeholder-image.png',
'rectangle': '(0,0)-(50,50)',
......
......@@ -180,16 +180,22 @@ Feature: LMS.Answer problems
Examples:
| 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 |
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
Given I am viewing a "<ProblemType>" problem
......@@ -234,21 +240,3 @@ Feature: LMS.Answer problems
| multiple choice | incorrect | correct |
| radio | correct | incorrect |
| 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):
# 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_not_present("button.check.is-disabled")
world.css_click("button.check")
# Wait for the problem to finish re-rendering
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')
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