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):
: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
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
:graderType: change how this unit is graded
: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):
prereq_usage_key
=
request
.
json
.
get
(
'prereqUsageKey'
),
prereq_min_score
=
request
.
json
.
get
(
'prereqMinScore'
),
publish
=
request
.
json
.
get
(
'publish'
),
fields
=
request
.
json
.
get
(
'fields'
),
)
elif
request
.
method
in
(
'PUT'
,
'POST'
):
if
'duplicate_source_locator'
in
request
.
json
:
...
...
@@ -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
,
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.
nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
to default).
"""
store
=
modulestore
()
# 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,
else
:
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
:
children
=
[]
for
child_string
in
children_strings
:
...
...
@@ -610,7 +620,7 @@ def _create_item(request):
user
=
request
.
user
,
category
=
category
,
display_name
=
request
.
json
.
get
(
'display_name'
),
boilerplate
=
request
.
json
.
get
(
'boilerplate'
)
boilerplate
=
request
.
json
.
get
(
'boilerplate'
)
,
)
return
JsonResponse
(
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
2c12decc
...
...
@@ -805,6 +805,22 @@ class TestEditItem(TestEditItemSetup):
self
.
assertEqual
(
sequential
.
due
,
datetime
(
2010
,
11
,
22
,
4
,
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
):
"""
Test deleting a child.
...
...
common/test/acceptance/fixtures/course.py
View file @
2c12decc
...
...
@@ -22,7 +22,8 @@ class XBlockFixtureDesc(object):
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.
These arguments have the same meaning as in the Studio REST API:
...
...
@@ -41,6 +42,7 @@ class XBlockFixtureDesc(object):
self
.
publish
=
publish
self
.
children
=
[]
self
.
locator
=
None
self
.
fields
=
kwargs
def
add_children
(
self
,
*
args
):
"""
...
...
@@ -59,13 +61,15 @@ class XBlockFixtureDesc(object):
XBlocks are always set to public visibility.
"""
return
json
.
dumps
(
{
return
ed_data
=
{
'display_name'
:
self
.
display_name
,
'data'
:
self
.
data
,
'metadata'
:
self
.
metadata
,
'graderType'
:
self
.
grader_type
,
'publish'
:
self
.
publish
})
'publish'
:
self
.
publish
,
'fields'
:
self
.
fields
,
}
return
json
.
dumps
(
returned_data
)
def
__str__
(
self
):
"""
...
...
@@ -354,7 +358,7 @@ class CourseFixture(XBlockContainerFixture):
'children'
:
None
,
'data'
:
handouts_html
,
'id'
:
self
.
_handouts_loc
,
'metadata'
:
dict
()
'metadata'
:
dict
()
,
})
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