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
2c12decc
Commit
2c12decc
authored
May 06, 2016
by
Diana Huang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12124 from edx/diana/conditional-transaction
Convert conditional module test to bok choy
parents
61ef49b9
07573c51
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
206 additions
and
155 deletions
+206
-155
cms/djangoapps/contentstore/views/item.py
+12
-2
cms/djangoapps/contentstore/views/tests/test_item.py
+16
-0
common/test/acceptance/fixtures/course.py
+9
-5
common/test/acceptance/pages/lms/conditional.py
+42
-0
common/test/acceptance/tests/lms/test_conditional.py
+127
-0
lms/djangoapps/courseware/features/conditional.feature
+0
-22
lms/djangoapps/courseware/features/conditional.py
+0
-126
No files found.
cms/djangoapps/contentstore/views/item.py
View file @
2c12decc
...
@@ -106,6 +106,9 @@ def xblock_handler(request, usage_key_string):
...
@@ -106,6 +106,9 @@ def xblock_handler(request, usage_key_string):
:children: the unicode representation of the UsageKeys of children for this xblock.
:children: the unicode representation of the UsageKeys of children for this xblock.
:metadata: new values for the metadata fields. Any whose values are None will be deleted not set
:metadata: new values for the metadata fields. Any whose values are None will be deleted not set
to None! Absent ones will be left alone.
to None! Absent ones will be left alone.
:fields: any other xblock fields to be set. Only supported by update.
This is represented as a dictionary:
{'field_name': 'field_value'}
:nullout: which metadata fields to set to None
:nullout: which metadata fields to set to None
:graderType: change how this unit is graded
:graderType: change how this unit is graded
:isPrereq: Set this xblock as a prerequisite which can be used to limit access to other xblocks
:isPrereq: Set this xblock as a prerequisite which can be used to limit access to other xblocks
...
@@ -169,6 +172,7 @@ def xblock_handler(request, usage_key_string):
...
@@ -169,6 +172,7 @@ def xblock_handler(request, usage_key_string):
prereq_usage_key
=
request
.
json
.
get
(
'prereqUsageKey'
),
prereq_usage_key
=
request
.
json
.
get
(
'prereqUsageKey'
),
prereq_min_score
=
request
.
json
.
get
(
'prereqMinScore'
),
prereq_min_score
=
request
.
json
.
get
(
'prereqMinScore'
),
publish
=
request
.
json
.
get
(
'publish'
),
publish
=
request
.
json
.
get
(
'publish'
),
fields
=
request
.
json
.
get
(
'fields'
),
)
)
elif
request
.
method
in
(
'PUT'
,
'POST'
):
elif
request
.
method
in
(
'PUT'
,
'POST'
):
if
'duplicate_source_locator'
in
request
.
json
:
if
'duplicate_source_locator'
in
request
.
json
:
...
@@ -431,11 +435,13 @@ def _update_with_callback(xblock, user, old_metadata=None, old_content=None):
...
@@ -431,11 +435,13 @@ def _update_with_callback(xblock, user, old_metadata=None, old_content=None):
def
_save_xblock
(
user
,
xblock
,
data
=
None
,
children_strings
=
None
,
metadata
=
None
,
nullout
=
None
,
def
_save_xblock
(
user
,
xblock
,
data
=
None
,
children_strings
=
None
,
metadata
=
None
,
nullout
=
None
,
grader_type
=
None
,
is_prereq
=
None
,
prereq_usage_key
=
None
,
prereq_min_score
=
None
,
publish
=
None
):
grader_type
=
None
,
is_prereq
=
None
,
prereq_usage_key
=
None
,
prereq_min_score
=
None
,
publish
=
None
,
fields
=
None
):
"""
"""
Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
to default).
to default).
"""
"""
store
=
modulestore
()
store
=
modulestore
()
# Perform all xblock changes within a (single-versioned) transaction
# Perform all xblock changes within a (single-versioned) transaction
...
@@ -457,6 +463,10 @@ def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None,
...
@@ -457,6 +463,10 @@ def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None,
else
:
else
:
data
=
old_content
[
'data'
]
if
'data'
in
old_content
else
None
data
=
old_content
[
'data'
]
if
'data'
in
old_content
else
None
if
fields
:
for
field_name
in
fields
:
setattr
(
xblock
,
field_name
,
fields
[
field_name
])
if
children_strings
is
not
None
:
if
children_strings
is
not
None
:
children
=
[]
children
=
[]
for
child_string
in
children_strings
:
for
child_string
in
children_strings
:
...
@@ -610,7 +620,7 @@ def _create_item(request):
...
@@ -610,7 +620,7 @@ def _create_item(request):
user
=
request
.
user
,
user
=
request
.
user
,
category
=
category
,
category
=
category
,
display_name
=
request
.
json
.
get
(
'display_name'
),
display_name
=
request
.
json
.
get
(
'display_name'
),
boilerplate
=
request
.
json
.
get
(
'boilerplate'
)
boilerplate
=
request
.
json
.
get
(
'boilerplate'
)
,
)
)
return
JsonResponse
(
return
JsonResponse
(
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
2c12decc
...
@@ -805,6 +805,22 @@ class TestEditItem(TestEditItemSetup):
...
@@ -805,6 +805,22 @@ class TestEditItem(TestEditItemSetup):
self
.
assertEqual
(
sequential
.
due
,
datetime
(
2010
,
11
,
22
,
4
,
0
,
tzinfo
=
UTC
))
self
.
assertEqual
(
sequential
.
due
,
datetime
(
2010
,
11
,
22
,
4
,
0
,
tzinfo
=
UTC
))
self
.
assertEqual
(
sequential
.
start
,
datetime
(
2010
,
9
,
12
,
14
,
0
,
tzinfo
=
UTC
))
self
.
assertEqual
(
sequential
.
start
,
datetime
(
2010
,
9
,
12
,
14
,
0
,
tzinfo
=
UTC
))
def
test_update_generic_fields
(
self
):
new_display_name
=
'New Display Name'
new_max_attempts
=
2
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
data
=
{
'fields'
:
{
'display_name'
:
new_display_name
,
'max_attempts'
:
new_max_attempts
,
}
}
)
problem
=
self
.
get_item_from_modulestore
(
self
.
problem_usage_key
,
verify_is_draft
=
True
)
self
.
assertEqual
(
problem
.
display_name
,
new_display_name
)
self
.
assertEqual
(
problem
.
max_attempts
,
new_max_attempts
)
def
test_delete_child
(
self
):
def
test_delete_child
(
self
):
"""
"""
Test deleting a child.
Test deleting a child.
...
...
common/test/acceptance/fixtures/course.py
View file @
2c12decc
...
@@ -22,7 +22,8 @@ class XBlockFixtureDesc(object):
...
@@ -22,7 +22,8 @@ class XBlockFixtureDesc(object):
Description of an XBlock, used to configure a course fixture.
Description of an XBlock, used to configure a course fixture.
"""
"""
def
__init__
(
self
,
category
,
display_name
,
data
=
None
,
metadata
=
None
,
grader_type
=
None
,
publish
=
'make_public'
):
def
__init__
(
self
,
category
,
display_name
,
data
=
None
,
metadata
=
None
,
grader_type
=
None
,
publish
=
'make_public'
,
**
kwargs
):
"""
"""
Configure the XBlock to be created by the fixture.
Configure the XBlock to be created by the fixture.
These arguments have the same meaning as in the Studio REST API:
These arguments have the same meaning as in the Studio REST API:
...
@@ -41,6 +42,7 @@ class XBlockFixtureDesc(object):
...
@@ -41,6 +42,7 @@ class XBlockFixtureDesc(object):
self
.
publish
=
publish
self
.
publish
=
publish
self
.
children
=
[]
self
.
children
=
[]
self
.
locator
=
None
self
.
locator
=
None
self
.
fields
=
kwargs
def
add_children
(
self
,
*
args
):
def
add_children
(
self
,
*
args
):
"""
"""
...
@@ -59,13 +61,15 @@ class XBlockFixtureDesc(object):
...
@@ -59,13 +61,15 @@ class XBlockFixtureDesc(object):
XBlocks are always set to public visibility.
XBlocks are always set to public visibility.
"""
"""
return
json
.
dumps
(
{
return
ed_data
=
{
'display_name'
:
self
.
display_name
,
'display_name'
:
self
.
display_name
,
'data'
:
self
.
data
,
'data'
:
self
.
data
,
'metadata'
:
self
.
metadata
,
'metadata'
:
self
.
metadata
,
'graderType'
:
self
.
grader_type
,
'graderType'
:
self
.
grader_type
,
'publish'
:
self
.
publish
'publish'
:
self
.
publish
,
})
'fields'
:
self
.
fields
,
}
return
json
.
dumps
(
returned_data
)
def
__str__
(
self
):
def
__str__
(
self
):
"""
"""
...
@@ -354,7 +358,7 @@ class CourseFixture(XBlockContainerFixture):
...
@@ -354,7 +358,7 @@ class CourseFixture(XBlockContainerFixture):
'children'
:
None
,
'children'
:
None
,
'data'
:
handouts_html
,
'data'
:
handouts_html
,
'id'
:
self
.
_handouts_loc
,
'id'
:
self
.
_handouts_loc
,
'metadata'
:
dict
()
'metadata'
:
dict
()
,
})
})
response
=
self
.
session
.
post
(
url
,
data
=
payload
,
headers
=
self
.
headers
)
response
=
self
.
session
.
post
(
url
,
data
=
payload
,
headers
=
self
.
headers
)
...
...
common/test/acceptance/pages/lms/conditional.py
0 → 100644
View file @
2c12decc
"""
Conditional Pages
"""
from
bok_choy.page_object
import
PageObject
POLL_ANSWER
=
'Yes, of course'
class
ConditionalPage
(
PageObject
):
"""
View of conditional page.
"""
url
=
None
def
is_browser_on_page
(
self
):
"""
Returns True if the browser is currently on the right page.
"""
return
self
.
q
(
css
=
'.conditional-wrapper'
)
.
visible
def
is_content_visible
(
self
):
"""
Returns True if the conditional's content has been revealed,
False otherwise
"""
return
self
.
q
(
css
=
'.hidden-contents'
)
.
visible
def
fill_in_poll
(
self
):
"""
Fills in a poll on the same page as the conditional
with the answer that matches POLL_ANSWER
"""
text_selector
=
'.poll_answer .text'
text_options
=
self
.
q
(
css
=
text_selector
)
.
text
# Out of the possible poll answers, we want
# to select the one that matches POLL_ANSWER and click it.
for
idx
,
text
in
enumerate
(
text_options
):
if
text
==
POLL_ANSWER
:
self
.
q
(
css
=
text_selector
)
.
nth
(
idx
)
.
click
()
common/test/acceptance/tests/lms/test_conditional.py
0 → 100644
View file @
2c12decc
"""
Bok choy acceptance tests for conditionals in the LMS
"""
from
capa.tests.response_xml_factory
import
StringResponseXMLFactory
from
..helpers
import
UniqueCourseTest
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.conditional
import
ConditionalPage
,
POLL_ANSWER
from
...pages.lms.problem
import
ProblemPage
from
...pages.studio.auto_auth
import
AutoAuthPage
class
ConditionalTest
(
UniqueCourseTest
):
"""
Test the conditional module in the lms.
"""
def
setUp
(
self
):
super
(
ConditionalTest
,
self
)
.
setUp
()
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
,
staff
=
False
)
.
visit
()
def
install_course_fixture
(
self
,
block_type
=
'problem'
):
"""
Install a course fixture
"""
course_fixture
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
],
)
vertical
=
XBlockFixtureDesc
(
'vertical'
,
'Test Unit'
)
# populate the course fixture with the right conditional modules
course_fixture
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection'
)
.
add_children
(
vertical
)
)
)
course_fixture
.
install
()
# Construct conditional block
conditional_metadata
=
{}
source_block
=
None
if
block_type
==
'problem'
:
problem_factory
=
StringResponseXMLFactory
()
problem_xml
=
problem_factory
.
build_xml
(
question_text
=
'The answer is "correct string"'
,
case_sensitive
=
False
,
answer
=
'correct string'
,
),
problem
=
XBlockFixtureDesc
(
'problem'
,
'Test Problem'
,
data
=
problem_xml
[
0
])
conditional_metadata
=
{
'xml_attributes'
:
{
'attempted'
:
'True'
}
}
source_block
=
problem
elif
block_type
==
'poll'
:
poll
=
XBlockFixtureDesc
(
'poll_question'
,
'Conditional Poll'
,
question
=
'Is this a good poll?'
,
answers
=
[
{
'id'
:
'yes'
,
'text'
:
POLL_ANSWER
},
{
'id'
:
'no'
,
'text'
:
'Of course not!'
}
],
)
conditional_metadata
=
{
'xml_attributes'
:
{
'poll_answer'
:
'yes'
}
}
source_block
=
poll
else
:
raise
NotImplementedError
()
course_fixture
.
create_xblock
(
vertical
.
locator
,
source_block
)
# create conditional
conditional
=
XBlockFixtureDesc
(
'conditional'
,
'Test Conditional'
,
metadata
=
conditional_metadata
,
sources_list
=
[
source_block
.
locator
],
)
result_block
=
XBlockFixtureDesc
(
'html'
,
'Conditional Contents'
,
data
=
'<html><div class="hidden-contents">Hidden Contents</p></html>'
)
course_fixture
.
create_xblock
(
vertical
.
locator
,
conditional
)
course_fixture
.
create_xblock
(
conditional
.
locator
,
result_block
)
def
test_conditional_hides_content
(
self
):
self
.
install_course_fixture
()
self
.
courseware_page
.
visit
()
conditional_page
=
ConditionalPage
(
self
.
browser
)
self
.
assertFalse
(
conditional_page
.
is_content_visible
())
def
test_conditional_displays_content
(
self
):
self
.
install_course_fixture
()
self
.
courseware_page
.
visit
()
# Answer the problem
problem_page
=
ProblemPage
(
self
.
browser
)
problem_page
.
fill_answer
(
'correct string'
)
problem_page
.
click_check
()
# The conditional does not update on its own, so we need to reload the page.
self
.
courseware_page
.
visit
()
# Verify that we can see the content.
conditional_page
=
ConditionalPage
(
self
.
browser
)
self
.
assertTrue
(
conditional_page
.
is_content_visible
())
def
test_conditional_handles_polls
(
self
):
self
.
install_course_fixture
(
block_type
=
'poll'
)
self
.
courseware_page
.
visit
()
# Fill in the conditional page poll
conditional_page
=
ConditionalPage
(
self
.
browser
)
conditional_page
.
fill_in_poll
()
# The conditional does not update on its own, so we need to reload the page.
self
.
courseware_page
.
visit
()
self
.
assertTrue
(
conditional_page
.
is_content_visible
())
lms/djangoapps/courseware/features/conditional.feature
deleted
100644 → 0
View file @
61ef49b9
@shard_2
Feature
:
LMS.Conditional Module
As a student, I want to view a Conditional component in the LMS
Scenario
:
A
Conditional hides content when conditions aren't satisfied
Given
that a course has a Conditional conditioned on problem attempted=True
And
that the conditioned problem has not been attempted
When
I view the conditional
Then
the conditional contents are hidden
Scenario
:
A
Conditional shows content when conditions are satisfied
Given
that a course has a Conditional conditioned on problem attempted=True
And
that the conditioned problem has been attempted
When
I view the conditional
Then
the conditional contents are visible
Scenario
:
A
Conditional containing a Poll is updated when the poll is answered
Given
that a course has a Conditional conditioned on poll poll_answer=yes
When
I view the conditional
Then
the conditional contents are hidden
When
I answer the conditioned poll
"yes"
Then
the conditional contents are visible
lms/djangoapps/courseware/features/conditional.py
deleted
100644 → 0
View file @
61ef49b9
# pylint: disable=missing-docstring
from
lettuce
import
world
,
steps
from
nose.tools
import
assert_in
,
assert_true
from
common
import
i_am_registered_for_the_course
,
visit_scenario_item
from
problems_setup
import
add_problem_to_course
,
answer_problem
@steps
class
ConditionalSteps
(
object
):
COURSE_NUM
=
'test_course'
def
setup_conditional
(
self
,
step
,
condition_type
,
condition
,
cond_value
):
r'that a course has a Conditional conditioned on (?P<condition_type>\w+) (?P<condition>\w+)=(?P<cond_value>\w+)$'
i_am_registered_for_the_course
(
step
,
self
.
COURSE_NUM
)
world
.
scenario_dict
[
'VERTICAL'
]
=
world
.
ItemFactory
(
parent_location
=
world
.
scenario_dict
[
'SECTION'
]
.
location
,
category
=
'vertical'
,
display_name
=
"Test Vertical"
,
)
world
.
scenario_dict
[
'WRAPPER'
]
=
world
.
ItemFactory
(
parent_location
=
world
.
scenario_dict
[
'VERTICAL'
]
.
location
,
category
=
'wrapper'
,
display_name
=
"Test Poll Wrapper"
)
if
condition_type
==
'problem'
:
world
.
scenario_dict
[
'CONDITION_SOURCE'
]
=
add_problem_to_course
(
self
.
COURSE_NUM
,
'string'
)
elif
condition_type
==
'poll'
:
world
.
scenario_dict
[
'CONDITION_SOURCE'
]
=
world
.
ItemFactory
(
parent_location
=
world
.
scenario_dict
[
'WRAPPER'
]
.
location
,
category
=
'poll_question'
,
display_name
=
'Conditional Poll'
,
data
=
{
'question'
:
'Is this a good poll?'
,
'answers'
:
[
{
'id'
:
'yes'
,
'text'
:
'Yes, of course'
},
{
'id'
:
'no'
,
'text'
:
'Of course not!'
}
],
}
)
else
:
raise
Exception
(
"Unknown condition type: {!r}"
.
format
(
condition_type
))
metadata
=
{
'xml_attributes'
:
{
condition
:
cond_value
}
}
world
.
scenario_dict
[
'CONDITIONAL'
]
=
world
.
ItemFactory
(
parent_location
=
world
.
scenario_dict
[
'WRAPPER'
]
.
location
,
category
=
'conditional'
,
display_name
=
"Test Conditional"
,
metadata
=
metadata
,
sources_list
=
[
world
.
scenario_dict
[
'CONDITION_SOURCE'
]
.
location
],
)
world
.
ItemFactory
(
parent_location
=
world
.
scenario_dict
[
'CONDITIONAL'
]
.
location
,
category
=
'html'
,
display_name
=
'Conditional Contents'
,
data
=
'<html><div class="hidden-contents">Hidden Contents</p></html>'
)
def
setup_problem_attempts
(
self
,
step
,
not_attempted
=
None
):
r'that the conditioned problem has (?P<not_attempted>not )?been attempted$'
visit_scenario_item
(
'CONDITION_SOURCE'
)
if
not_attempted
is
None
:
answer_problem
(
self
.
COURSE_NUM
,
'string'
,
True
)
world
.
css_click
(
"button.check"
)
def
when_i_view_the_conditional
(
self
,
step
):
r'I view the conditional$'
visit_scenario_item
(
'CONDITIONAL'
)
world
.
wait_for_js_variable_truthy
(
'$(".xblock-student_view[data-type=Conditional]").data("initialized")'
)
def
check_visibility
(
self
,
step
,
visible
):
r'the conditional contents are (?P<visible>\w+)$'
world
.
wait_for_ajax_complete
()
assert_in
(
visible
,
(
'visible'
,
'hidden'
))
if
visible
==
'visible'
:
world
.
wait_for_visible
(
'.hidden-contents'
)
assert_true
(
world
.
css_visible
(
'.hidden-contents'
))
else
:
assert_true
(
world
.
is_css_not_present
(
'.hidden-contents'
))
assert_true
(
world
.
css_contains_text
(
'.conditional-message'
,
'must be attempted before this will become visible.'
)
)
def
answer_poll
(
self
,
step
,
answer
):
r' I answer the conditioned poll "([^"]*)"$'
visit_scenario_item
(
'CONDITION_SOURCE'
)
world
.
wait_for_js_variable_truthy
(
'$(".xblock-student_view[data-type=Poll]").data("initialized")'
)
world
.
wait_for_ajax_complete
()
answer_text
=
[
poll_answer
[
'text'
]
for
poll_answer
in
world
.
scenario_dict
[
'CONDITION_SOURCE'
]
.
answers
if
poll_answer
[
'id'
]
==
answer
][
0
]
text_selector
=
'.poll_answer .text'
poll_texts
=
world
.
retry_on_exception
(
lambda
:
[
elem
.
text
for
elem
in
world
.
css_find
(
text_selector
)]
)
for
idx
,
poll_text
in
enumerate
(
poll_texts
):
if
poll_text
==
answer_text
:
world
.
css_click
(
text_selector
,
index
=
idx
)
return
ConditionalSteps
()
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