Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
61127b61
Commit
61127b61
authored
9 years ago
by
Dmitry Viskov
Committed by
Dmitry Viskov
9 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make it impossible to click "final check" without selecting a choice
parent
47e4804b
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
200 additions
and
27 deletions
+200
-27
common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
+89
-0
common/lib/xmodule/xmodule/js/src/capa/display.coffee
+54
-0
common/test/acceptance/tests/lms/test_problem_types.py
+34
-1
lms/djangoapps/courseware/features/problems.feature
+14
-26
lms/djangoapps/courseware/features/problems.py
+9
-0
No files found.
common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
View file @
61127b61
...
@@ -247,6 +247,95 @@ describe 'Problem', ->
...
@@ -247,6 +247,95 @@ 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
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'
,
->
describe
'reset'
,
->
beforeEach
->
beforeEach
->
@
problem
=
new
Problem
(
$
(
'.xblock-student_view'
))
@
problem
=
new
Problem
(
$
(
'.xblock-student_view'
))
...
...
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/js/src/capa/display.coffee
View file @
61127b61
...
@@ -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
)
=>
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
:
->
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.
...
...
This diff is collapsed.
Click to expand it.
common/test/acceptance/tests/lms/test_problem_types.py
View file @
61127b61
...
@@ -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)'
,
...
...
This diff is collapsed.
Click to expand it.
lms/djangoapps/courseware/features/problems.feature
View file @
61127b61
...
@@ -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
|
This diff is collapsed.
Click to expand it.
lms/djangoapps/courseware/features/problems.py
View file @
61127b61
...
@@ -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
):
'''
'''
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment