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
8cbf99ac
Commit
8cbf99ac
authored
Feb 25, 2016
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Activate Next and Previous Buttons across sections
MA-2152 MA-2153
parent
a77000a8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
599 additions
and
83 deletions
+599
-83
common/lib/xmodule/xmodule/js/src/sequence/display.coffee
+36
-17
common/lib/xmodule/xmodule/seq_module.py
+105
-12
common/lib/xmodule/xmodule/tests/test_sequence.py
+163
-0
common/lib/xmodule/xmodule/tests/xml/__init__.py
+2
-1
common/lib/xmodule/xmodule/tests/xml/factories.py
+7
-1
common/test/acceptance/pages/lms/course_nav.py
+8
-11
common/test/acceptance/pages/lms/courseware.py
+65
-10
common/test/acceptance/tests/lms/test_lms_courseware.py
+68
-5
common/test/acceptance/tests/lms/test_lms_edxnotes.py
+10
-10
common/test/acceptance/tests/studio/test_studio_outline.py
+2
-2
common/test/acceptance/tests/video/test_video_module.py
+5
-3
lms/djangoapps/courseware/tests/test_views.py
+115
-8
lms/djangoapps/courseware/url_helpers.py
+5
-1
lms/djangoapps/courseware/views.py
+0
-0
lms/envs/bok_choy.py
+3
-0
lms/envs/common.py
+4
-1
lms/templates/seq_module.html
+1
-1
No files found.
common/lib/xmodule/xmodule/js/src/sequence/display.coffee
View file @
8cbf99ac
...
...
@@ -8,6 +8,8 @@ class @Sequence
@
num_contents
=
@
contents
.
length
@
id
=
@
el
.
data
(
'id'
)
@
ajaxUrl
=
@
el
.
data
(
'ajax-url'
)
@
nextUrl
=
@
el
.
data
(
'next-url'
)
@
prevUrl
=
@
el
.
data
(
'prev-url'
)
@
base_page_title
=
" | "
+
document
.
title
@
initProgress
()
@
bind
()
...
...
@@ -73,23 +75,35 @@ class @Sequence
when
'in_progress'
then
element
.
addClass
(
'progress-some'
)
when
'done'
then
element
.
addClass
(
'progress-done'
)
toggleArrows
:
=
>
@
$
(
'.sequence-nav-button'
).
unbind
(
'click'
)
enableButton
:
(
button_class
,
button_action
)
-
>
@
$
(
button_class
).
removeClass
(
'disabled'
).
removeAttr
(
'disabled'
).
click
(
button_action
)
if
@
contents
.
length
==
0
## There are no modules to display, and therefore no nav to build.
@
$
(
'.sequence-nav-button.button-previous'
).
addClass
(
'disabled'
).
attr
(
'disabled'
,
true
)
@
$
(
'.sequence-nav-button.button-next'
).
addClass
(
'disabled'
).
attr
(
'disabled'
,
true
)
return
disableButton
:
(
button_class
)
->
@
$
(
button_class
).
addClass
(
'disabled'
).
attr
(
'disabled'
,
true
)
if
@
position
==
1
## 1 != 0 here. 1 is the first item in the sequence nav.
@
$
(
'.sequence-nav-button.button-previous'
).
addClass
(
'disabled'
).
attr
(
'disabled'
,
true
)
else
@
$
(
'.sequence-nav-button.button-previous'
).
removeClass
(
'disabled'
).
removeAttr
(
'disabled'
).
click
(
@
previous
)
setButtonLabel
:
(
button_class
,
button_label
)
->
@
$
(
button_class
+
' .sr'
).
html
(
button_label
)
if
@
position
==
@
contents
.
length
## If the final position on the nav matches the total contents.
@
$
(
'.sequence-nav-button.button-next'
).
addClass
(
'disabled'
).
attr
(
'disabled'
,
true
)
updateButtonState
:
(
button_class
,
button_action
,
action_label_prefix
,
is_at_boundary
,
boundary_url
)
->
if
is_at_boundary
and
boundary_url
==
'None'
@
disableButton
(
button_class
)
else
@
$
(
'.sequence-nav-button.button-next'
).
removeClass
(
'disabled'
).
removeAttr
(
'disabled'
).
click
(
@
next
)
button_label
=
action_label_prefix
+
(
if
is_at_boundary
then
' Section'
else
' Unit'
)
@
setButtonLabel
(
button_class
,
button_label
)
@
enableButton
(
button_class
,
button_action
)
toggleArrows
:
=>
@
$
(
'.sequence-nav-button'
).
unbind
(
'click'
)
# previous button
first_tab
=
@
position
==
1
previous_button_class
=
'.sequence-nav-button.button-previous'
@
updateButtonState
(
previous_button_class
,
@
previous
,
'Previous'
,
first_tab
,
@
prevUrl
)
# next button
last_tab
=
@
position
>=
@
contents
.
length
# use inequality in case contents.length is 0 and position is 1.
next_button_class
=
'.sequence-nav-button.button-next'
@
updateButtonState
(
next_button_class
,
@
next
,
'Next'
,
last_tab
,
@
nextUrl
)
render
:
(
new_position
)
->
if
@
position
!=
new_position
...
...
@@ -164,10 +178,15 @@ class @Sequence
new
:
new_position
id
:
@
id
# If the bottom nav is used, scroll to the top of the page on change.
if
$
(
event
.
target
).
closest
(
'nav[class="sequence-bottom"]'
).
length
>
0
$
.
scrollTo
0
,
150
@
render
new_position
if
(
direction
==
"seq_next"
)
and
(
@
position
==
@
contents
.
length
)
window
.
location
.
href
=
@
nextUrl
else
if
(
direction
==
"seq_prev"
)
and
(
@
position
==
1
)
window
.
location
.
href
=
@
prevUrl
else
# If the bottom nav is used, scroll to the top of the page on change.
if
$
(
event
.
target
).
closest
(
'nav[class="sequence-bottom"]'
).
length
>
0
$
.
scrollTo
0
,
150
@
render
new_position
link_for
:
(
position
)
->
@
$
(
"#sequence-list a[data-element=
#{
position
}
]"
)
...
...
common/lib/xmodule/xmodule/seq_module.py
View file @
8cbf99ac
...
...
@@ -141,16 +141,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
# If position is specified in system, then use that instead.
position
=
getattr
(
self
.
system
,
'position'
,
None
)
if
position
is
not
None
:
try
:
self
.
position
=
int
(
self
.
system
.
position
)
except
(
ValueError
,
TypeError
):
# Check for https://openedx.atlassian.net/browse/LMS-6496
warnings
.
warn
(
"Sequential position cannot be converted to an integer: {pos!r}"
.
format
(
pos
=
self
.
system
.
position
,
),
RuntimeWarning
,
)
assert
isinstance
(
position
,
int
)
self
.
position
=
self
.
system
.
position
def
get_progress
(
self
):
''' Return the total progress, adding total done and total available.
...
...
@@ -177,9 +169,16 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
raise
NotFoundError
(
'Unexpected dispatch type'
)
def
student_view
(
self
,
context
):
display_items
=
self
.
get_display_items
()
# If we're rendering this sequence, but no position is set yet,
# or exceeds the length of the displayable items,
# default the position to the first element
if
self
.
position
is
None
:
if
context
.
get
(
'requested_child'
)
==
'first'
:
self
.
position
=
1
elif
context
.
get
(
'requested_child'
)
==
'last'
:
self
.
position
=
len
(
display_items
)
or
None
elif
self
.
position
is
None
or
self
.
position
>
len
(
display_items
):
self
.
position
=
1
## Returns a set of all types of all sub-children
...
...
@@ -211,7 +210,6 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
fragment
.
add_content
(
view_html
)
return
fragment
display_items
=
self
.
get_display_items
()
for
child
in
display_items
:
is_bookmarked
=
bookmarks_service
.
is_bookmarked
(
usage_key
=
child
.
scope_ids
.
usage_id
)
context
[
"bookmarked"
]
=
is_bookmarked
...
...
@@ -245,6 +243,16 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
'position'
:
self
.
position
,
'tag'
:
self
.
location
.
category
,
'ajax_url'
:
self
.
system
.
ajax_url
,
'next_url'
:
_compute_next_url
(
self
.
location
,
parent_module
,
context
.
get
(
'redirect_url_func'
),
),
'prev_url'
:
_compute_previous_url
(
self
.
location
,
parent_module
,
context
.
get
(
'redirect_url_func'
),
),
}
fragment
.
add_content
(
self
.
system
.
render_template
(
"seq_module.html"
,
params
))
...
...
@@ -453,3 +461,88 @@ class SequenceDescriptor(SequenceFields, ProctoringFields, MakoModuleDescriptor,
xblock_body
[
"content_type"
]
=
"Sequence"
return
xblock_body
def
_compute_next_url
(
block_location
,
parent_block
,
redirect_url_func
):
"""
Returns the url for the next block after the given block.
"""
def
get_next_block_location
(
parent_block
,
index_in_parent
):
"""
Returns the next block in the parent_block after the block with the given
index_in_parent.
"""
if
index_in_parent
+
1
<
len
(
parent_block
.
children
):
return
parent_block
.
children
[
index_in_parent
+
1
]
else
:
return
None
return
_compute_next_or_prev_url
(
block_location
,
parent_block
,
redirect_url_func
,
get_next_block_location
,
'first'
,
)
def
_compute_previous_url
(
block_location
,
parent_block
,
redirect_url_func
):
"""
Returns the url for the previous block after the given block.
"""
def
get_previous_block_location
(
parent_block
,
index_in_parent
):
"""
Returns the previous block in the parent_block before the block with the given
index_in_parent.
"""
return
parent_block
.
children
[
index_in_parent
-
1
]
if
index_in_parent
else
None
return
_compute_next_or_prev_url
(
block_location
,
parent_block
,
redirect_url_func
,
get_previous_block_location
,
'last'
,
)
def
_compute_next_or_prev_url
(
block_location
,
parent_block
,
redirect_url_func
,
get_next_or_prev_block
,
redirect_url_child_param
,
):
"""
Returns the url for the next or previous block from the given block.
Arguments:
block_location: Location of the block that is being navigated.
parent_block: Parent block of the given block.
redirect_url_func: Function that computes a redirect URL directly to
a block, given the block's location.
get_next_or_prev_block: Function that returns the next or previous
block in the parent, or None if doesn't exist.
redirect_url_child_param: Value to pass for the child parameter to the
redirect_url_func.
"""
if
redirect_url_func
:
index_in_parent
=
parent_block
.
children
.
index
(
block_location
)
next_or_prev_block_location
=
get_next_or_prev_block
(
parent_block
,
index_in_parent
)
if
next_or_prev_block_location
:
return
redirect_url_func
(
block_location
.
course_key
,
next_or_prev_block_location
,
child
=
redirect_url_child_param
,
)
else
:
grandparent
=
parent_block
.
get_parent
()
if
grandparent
:
return
_compute_next_or_prev_url
(
parent_block
.
location
,
grandparent
,
redirect_url_func
,
get_next_or_prev_block
,
redirect_url_child_param
,
)
return
None
common/lib/xmodule/xmodule/tests/test_sequence.py
0 → 100644
View file @
8cbf99ac
"""
Tests for sequence module.
"""
# pylint: disable=no-member
from
mock
import
Mock
from
xblock.reference.user_service
import
XBlockUser
,
UserService
from
xmodule.tests
import
get_test_system
from
xmodule.tests.xml
import
XModuleXmlImportTest
from
xmodule.tests.xml
import
factories
as
xml
from
xmodule.x_module
import
STUDENT_VIEW
from
xmodule.seq_module
import
_compute_next_url
,
_compute_previous_url
,
SequenceModule
class
StubUserService
(
UserService
):
"""
Stub UserService for testing the sequence module.
"""
def
get_current_user
(
self
):
"""
Implements abstract method for getting the current user.
"""
user
=
XBlockUser
()
user
.
opt_attrs
[
'edx-platform.username'
]
=
'test user'
return
user
class
SequenceBlockTestCase
(
XModuleXmlImportTest
):
"""
Tests for the Sequence Module.
"""
@classmethod
def
setUpClass
(
cls
):
super
(
SequenceBlockTestCase
,
cls
)
.
setUpClass
()
course_xml
=
cls
.
_set_up_course_xml
()
cls
.
course
=
cls
.
process_xml
(
course_xml
)
cls
.
_set_up_module_system
(
cls
.
course
)
for
chapter_index
in
range
(
len
(
cls
.
course
.
get_children
())):
chapter
=
cls
.
_set_up_block
(
cls
.
course
,
chapter_index
)
setattr
(
cls
,
'chapter_{}'
.
format
(
chapter_index
+
1
),
chapter
)
for
sequence_index
in
range
(
len
(
chapter
.
get_children
())):
sequence
=
cls
.
_set_up_block
(
chapter
,
sequence_index
)
setattr
(
cls
,
'sequence_{}_{}'
.
format
(
chapter_index
+
1
,
sequence_index
+
1
),
sequence
)
@classmethod
def
_set_up_course_xml
(
cls
):
"""
Sets up and returns XML course structure.
"""
course
=
xml
.
CourseFactory
.
build
()
chapter_1
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 2 child sequences
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 0 child sequences
chapter_3
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 1 child sequence
chapter_4
=
xml
.
ChapterFactory
.
build
(
parent
=
course
)
# has 2 child sequences
xml
.
SequenceFactory
.
build
(
parent
=
chapter_1
)
xml
.
SequenceFactory
.
build
(
parent
=
chapter_1
)
sequence_3_1
=
xml
.
SequenceFactory
.
build
(
parent
=
chapter_3
)
# has 3 verticals
xml
.
SequenceFactory
.
build
(
parent
=
chapter_4
)
xml
.
SequenceFactory
.
build
(
parent
=
chapter_4
)
for
_
in
range
(
3
):
xml
.
VerticalFactory
.
build
(
parent
=
sequence_3_1
)
return
course
@classmethod
def
_set_up_block
(
cls
,
parent
,
index_in_parent
):
"""
Sets up the stub sequence module for testing.
"""
block
=
parent
.
get_children
()[
index_in_parent
]
cls
.
_set_up_module_system
(
block
)
block
.
xmodule_runtime
.
_services
[
'bookmarks'
]
=
Mock
()
# pylint: disable=protected-access
block
.
xmodule_runtime
.
_services
[
'user'
]
=
StubUserService
()
# pylint: disable=protected-access
block
.
xmodule_runtime
.
xmodule_instance
=
getattr
(
block
,
'_xmodule'
,
None
)
# pylint: disable=protected-access
block
.
parent
=
parent
.
location
return
block
@classmethod
def
_set_up_module_system
(
cls
,
block
):
"""
Sets up the test module system for the given block.
"""
module_system
=
get_test_system
()
module_system
.
descriptor_runtime
=
block
.
_runtime
# pylint: disable=protected-access
block
.
xmodule_runtime
=
module_system
def
test_student_view_init
(
self
):
seq_module
=
SequenceModule
(
runtime
=
Mock
(
position
=
2
),
descriptor
=
Mock
(),
scope_ids
=
Mock
())
self
.
assertEquals
(
seq_module
.
position
,
2
)
# matches position set in the runtime
def
test_render_student_view
(
self
):
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
None
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
1
)
self
.
assertIn
(
unicode
(
self
.
sequence_3_1
.
location
),
html
)
self
.
assertIn
(
"'next_url': u'{}'"
.
format
(
unicode
(
self
.
chapter_4
.
location
)),
html
)
self
.
assertIn
(
"'prev_url': u'{}'"
.
format
(
unicode
(
self
.
chapter_2
.
location
)),
html
)
def
test_student_view_first_child
(
self
):
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
'first'
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
1
)
def
test_student_view_last_child
(
self
):
html
=
self
.
_get_rendered_student_view
(
self
.
sequence_3_1
,
requested_child
=
'last'
)
self
.
_assert_view_at_position
(
html
,
expected_position
=
3
)
def
_get_rendered_student_view
(
self
,
sequence
,
requested_child
):
"""
Returns the rendered student view for the given sequence and the
requested_child parameter.
"""
return
sequence
.
xmodule_runtime
.
render
(
sequence
,
STUDENT_VIEW
,
{
'redirect_url_func'
:
lambda
course_key
,
block_location
,
child
:
unicode
(
block_location
),
'requested_child'
:
requested_child
,
},
)
.
content
def
_assert_view_at_position
(
self
,
rendered_html
,
expected_position
):
"""
Verifies that the rendered view contains the expected position.
"""
self
.
assertIn
(
"'position': {}"
.
format
(
expected_position
),
rendered_html
)
def
test_compute_next_url
(
self
):
for
sequence
,
parent
,
expected_next_sequence_location
in
[
(
self
.
sequence_1_1
,
self
.
chapter_1
,
self
.
sequence_1_2
.
location
),
(
self
.
sequence_1_2
,
self
.
chapter_1
,
self
.
chapter_2
.
location
),
(
self
.
sequence_3_1
,
self
.
chapter_3
,
self
.
chapter_4
.
location
),
(
self
.
sequence_4_1
,
self
.
chapter_4
,
self
.
sequence_4_2
.
location
),
(
self
.
sequence_4_2
,
self
.
chapter_4
,
None
),
]:
actual_next_sequence_location
=
_compute_next_url
(
sequence
.
location
,
parent
,
lambda
course_key
,
block_location
,
child
:
block_location
,
)
self
.
assertEquals
(
actual_next_sequence_location
,
expected_next_sequence_location
)
def
test_compute_previous_url
(
self
):
for
sequence
,
parent
,
expected_prev_sequence_location
in
[
(
self
.
sequence_1_1
,
self
.
chapter_1
,
None
),
(
self
.
sequence_1_2
,
self
.
chapter_1
,
self
.
sequence_1_1
.
location
),
(
self
.
sequence_3_1
,
self
.
chapter_3
,
self
.
chapter_2
.
location
),
(
self
.
sequence_4_1
,
self
.
chapter_4
,
self
.
chapter_3
.
location
),
(
self
.
sequence_4_2
,
self
.
chapter_4
,
self
.
sequence_4_1
.
location
),
]:
actual_next_sequence_location
=
_compute_previous_url
(
sequence
.
location
,
parent
,
lambda
course_key
,
block_location
,
child
:
block_location
,
)
self
.
assertEquals
(
actual_next_sequence_location
,
expected_prev_sequence_location
)
common/lib/xmodule/xmodule/tests/xml/__init__.py
View file @
8cbf99ac
...
...
@@ -57,7 +57,8 @@ class InMemorySystem(XMLParsingSystem, MakoDescriptorSystem): # pylint: disable
class
XModuleXmlImportTest
(
TestCase
):
"""Base class for tests that use basic XML parsing"""
def
process_xml
(
self
,
xml_import_data
):
@classmethod
def
process_xml
(
cls
,
xml_import_data
):
"""Use the `xml_import_data` to import an :class:`XBlock` from XML."""
system
=
InMemorySystem
(
xml_import_data
)
return
system
.
process_xml
(
xml_import_data
.
xml_string
)
common/lib/xmodule/xmodule/tests/xml/factories.py
View file @
8cbf99ac
...
...
@@ -8,6 +8,7 @@ from fs.memoryfs import MemoryFS
from
factory
import
Factory
,
lazy_attribute
,
post_generation
,
Sequence
from
lxml
import
etree
from
xblock.mixins
import
HierarchyMixin
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.x_module
import
XModuleMixin
from
xmodule.modulestore
import
only_xmodules
...
...
@@ -68,7 +69,7 @@ class XmlImportFactory(Factory):
model
=
XmlImportData
filesystem
=
MemoryFS
()
xblock_mixins
=
(
InheritanceMixin
,
XModuleMixin
)
xblock_mixins
=
(
InheritanceMixin
,
XModuleMixin
,
HierarchyMixin
)
xblock_select
=
only_xmodules
url_name
=
Sequence
(
str
)
attribs
=
{}
...
...
@@ -142,6 +143,11 @@ class CourseFactory(XmlImportFactory):
static_asset_path
=
'xml_test_course'
class
ChapterFactory
(
XmlImportFactory
):
"""Factory for <chapter> nodes"""
tag
=
'chapter'
class
SequenceFactory
(
XmlImportFactory
):
"""Factory for <sequential> nodes"""
tag
=
'sequential'
...
...
common/test/acceptance/pages/lms/course_nav.py
View file @
8cbf99ac
...
...
@@ -3,7 +3,7 @@ Course navigation page object
"""
import
re
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
,
unguarded
from
bok_choy.promise
import
EmptyPromise
...
...
@@ -165,10 +165,11 @@ class CourseNavPage(PageObject):
"""
desc
=
"currently at section '{0}' and subsection '{1}'"
.
format
(
section_title
,
subsection_title
)
return
EmptyPromise
(
lambda
:
self
.
_
is_on_section
(
section_title
,
subsection_title
),
desc
lambda
:
self
.
is_on_section
(
section_title
,
subsection_title
),
desc
)
def
_is_on_section
(
self
,
section_title
,
subsection_title
):
@unguarded
def
is_on_section
(
self
,
section_title
,
subsection_title
):
"""
Return a boolean indicating whether the user is on the section and subsection
with the specified titles.
...
...
@@ -203,13 +204,9 @@ class CourseNavPage(PageObject):
"""
return
self
.
REMOVE_SPAN_TAG_RE
.
search
(
element
.
get_attribute
(
'innerHTML'
))
.
groups
()[
0
]
.
strip
()
def
go_to_sequential_position
(
self
,
sequential_position
):
@property
def
active_subsection_url
(
self
):
"""
Within a section/subsection navigate to the sequential position specified by `sequential_position`.
Arguments:
sequential_position (int): position in sequential bar
return the url of the active subsection in the left nav
"""
sequential_position_css
=
'#tab_{0}'
.
format
(
sequential_position
-
1
)
self
.
q
(
css
=
sequential_position_css
)
.
first
.
click
()
return
self
.
q
(
css
=
'.chapter-content-container .menu-item.active a'
)
.
attrs
(
'href'
)[
0
]
common/test/acceptance/pages/lms/courseware.py
View file @
8cbf99ac
...
...
@@ -21,6 +21,13 @@ class CoursewarePage(CoursePage):
return
self
.
q
(
css
=
'body.courseware'
)
.
present
@property
def
chapter_count_in_navigation
(
self
):
"""
Returns count of chapters available on LHS navigation.
"""
return
len
(
self
.
q
(
css
=
'nav.course-navigation a.chapter'
))
@property
def
num_sections
(
self
):
"""
Return the number of sections in the sidebar on the page
...
...
@@ -101,11 +108,66 @@ class CoursewarePage(CoursePage):
return
element
.
text
[
0
]
return
None
def
get_active_subsection_url
(
self
):
def
go_to_sequential_position
(
self
,
sequential_position
):
"""
Within a section/subsection navigate to the sequential position specified by `sequential_position`.
Arguments:
sequential_position (int): position in sequential bar
"""
sequential_position_css
=
'#sequence-list #tab_{0}'
.
format
(
sequential_position
-
1
)
self
.
q
(
css
=
sequential_position_css
)
.
first
.
click
()
@property
def
sequential_position
(
self
):
"""
Returns the position of the active tab in the sequence.
"""
tab_id
=
self
.
_active_sequence_tab
.
attrs
(
'id'
)[
0
]
return
int
(
tab_id
.
split
(
'_'
)[
1
])
@property
def
_active_sequence_tab
(
self
):
# pylint: disable=missing-docstring
return
self
.
q
(
css
=
'#sequence-list .nav-item.active'
)
@property
def
is_next_button_enabled
(
self
):
# pylint: disable=missing-docstring
return
not
self
.
q
(
css
=
'.sequence-nav > .sequence-nav-button.button-next.disabled'
)
.
is_present
()
@property
def
is_previous_button_enabled
(
self
):
# pylint: disable=missing-docstring
return
not
self
.
q
(
css
=
'.sequence-nav > .sequence-nav-button.button-previous.disabled'
)
.
is_present
()
def
click_next_button_on_top
(
self
):
# pylint: disable=missing-docstring
self
.
_click_navigation_button
(
'sequence-nav'
,
'button-next'
)
def
click_next_button_on_bottom
(
self
):
# pylint: disable=missing-docstring
self
.
_click_navigation_button
(
'sequence-bottom'
,
'button-next'
)
def
click_previous_button_on_top
(
self
):
# pylint: disable=missing-docstring
self
.
_click_navigation_button
(
'sequence-nav'
,
'button-previous'
)
def
click_previous_button_on_bottom
(
self
):
# pylint: disable=missing-docstring
self
.
_click_navigation_button
(
'sequence-bottom'
,
'button-previous'
)
def
_click_navigation_button
(
self
,
top_or_bottom_class
,
next_or_previous_class
):
"""
return the url of the active subsection in the left nav
Clicks the navigation button, given the respective CSS classes.
"""
return
self
.
q
(
css
=
'.chapter-content-container .menu-item.active a'
)
.
attrs
(
'href'
)[
0
]
previous_tab_id
=
self
.
_active_sequence_tab
.
attrs
(
'data-id'
)[
0
]
def
is_at_new_tab_id
():
"""
Returns whether the active tab has changed. It is defensive
against the case where the page is still being loaded.
"""
active_tab
=
self
.
_active_sequence_tab
return
active_tab
and
previous_tab_id
!=
active_tab
.
attrs
(
'data-id'
)[
0
]
self
.
q
(
css
=
'.{} > .sequence-nav-button.{}'
.
format
(
top_or_bottom_class
,
next_or_previous_class
)
)
.
first
.
click
()
EmptyPromise
(
is_at_new_tab_id
,
"Button navigation fulfilled"
)
.
fulfill
()
@property
def
can_start_proctored_exam
(
self
):
...
...
@@ -161,13 +223,6 @@ class CoursewarePage(CoursePage):
and
"You have passed the entrance exam"
in
self
.
entrance_exam_message_selector
.
text
[
0
]
@property
def
chapter_count_in_navigation
(
self
):
"""
Returns count of chapters available on LHS navigation.
"""
return
len
(
self
.
q
(
css
=
'nav.course-navigation a.chapter'
))
@property
def
is_timer_bar_present
(
self
):
"""
Returns True if the timed/proctored exam timer bar is visible on the courseware.
...
...
common/test/acceptance/tests/lms/test_lms_courseware.py
View file @
8cbf99ac
...
...
@@ -2,7 +2,8 @@
"""
End-to-end tests for the LMS.
"""
import
time
from
nose.plugins.attrib
import
attr
from
unittest
import
skip
from
..helpers
import
UniqueCourseTest
from
...pages.studio.auto_auth
import
AutoAuthPage
...
...
@@ -420,13 +421,20 @@ class CoursewareMultipleVerticalsTest(UniqueCourseTest):
course_fix
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section 1'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 1'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 1
,1
'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Problem 1'
,
data
=
'<problem>problem 1 dummy body</problem>'
),
XBlockFixtureDesc
(
'html'
,
'html 1'
,
data
=
"<html>html 1 dummy body</html>"
),
XBlockFixtureDesc
(
'problem'
,
'Test Problem 2'
,
data
=
"<problem>problem 2 dummy body</problem>"
),
XBlockFixtureDesc
(
'html'
,
'html 2'
,
data
=
"<html>html 2 dummy body</html>"
),
),
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 2'
),
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 1,2'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Problem 3'
,
data
=
'<problem>problem 3 dummy body</problem>'
),
),
),
XBlockFixtureDesc
(
'chapter'
,
'Test Section 2'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 2,1'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Problem 4'
,
data
=
'<problem>problem 4 dummy body</problem>'
),
),
),
)
.
install
()
...
...
@@ -436,10 +444,53 @@ class CoursewareMultipleVerticalsTest(UniqueCourseTest):
self
.
courseware_page
.
visit
()
self
.
course_nav
=
CourseNavPage
(
self
.
browser
)
def
test_navigation_buttons
(
self
):
# start in first section
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,1'
,
0
,
next_enabled
=
True
,
prev_enabled
=
False
)
# next takes us to next tab in sequential
self
.
courseware_page
.
click_next_button_on_top
()
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,1'
,
1
,
next_enabled
=
True
,
prev_enabled
=
True
)
# go to last sequential position
self
.
courseware_page
.
go_to_sequential_position
(
4
)
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,1'
,
3
,
next_enabled
=
True
,
prev_enabled
=
True
)
# next takes us to next sequential
self
.
courseware_page
.
click_next_button_on_bottom
()
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,2'
,
0
,
next_enabled
=
True
,
prev_enabled
=
True
)
# next takes us to next chapter
self
.
courseware_page
.
click_next_button_on_top
()
self
.
assert_navigation_state
(
'Test Section 2'
,
'Test Subsection 2,1'
,
0
,
next_enabled
=
False
,
prev_enabled
=
True
)
# previous takes us to previous chapter
self
.
courseware_page
.
click_previous_button_on_top
()
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,2'
,
0
,
next_enabled
=
True
,
prev_enabled
=
True
)
# previous takes us to last tab in previous sequential
self
.
courseware_page
.
click_previous_button_on_bottom
()
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,1'
,
3
,
next_enabled
=
True
,
prev_enabled
=
True
)
# previous takes us to previous tab in sequential
self
.
courseware_page
.
click_previous_button_on_bottom
()
self
.
assert_navigation_state
(
'Test Section 1'
,
'Test Subsection 1,1'
,
2
,
next_enabled
=
True
,
prev_enabled
=
True
)
def
assert_navigation_state
(
self
,
section_title
,
subsection_title
,
subsection_position
,
next_enabled
,
prev_enabled
):
"""
Verifies that the navigation state is as expected.
"""
self
.
assertTrue
(
self
.
course_nav
.
is_on_section
(
section_title
,
subsection_title
))
self
.
assertEquals
(
self
.
courseware_page
.
sequential_position
,
subsection_position
)
self
.
assertEquals
(
self
.
courseware_page
.
is_next_button_enabled
,
next_enabled
)
self
.
assertEquals
(
self
.
courseware_page
.
is_previous_button_enabled
,
prev_enabled
)
def
test_tab_position
(
self
):
# test that using the position in the url direct to correct tab in courseware
self
.
course_nav
.
go_to_section
(
'Test Section 1'
,
'Test Subsection 1'
)
subsection_url
=
self
.
course
ware_page
.
get_active_subsection_url
()
self
.
course_nav
.
go_to_section
(
'Test Section 1'
,
'Test Subsection 1
,1
'
)
subsection_url
=
self
.
course
_nav
.
active_subsection_url
url_part_list
=
subsection_url
.
split
(
'/'
)
self
.
assertEqual
(
len
(
url_part_list
),
9
)
...
...
@@ -481,3 +532,15 @@ class CoursewareMultipleVerticalsTest(UniqueCourseTest):
position
=
4
)
.
visit
()
self
.
assertIn
(
'html 2 dummy body'
,
html2_page
.
get_selected_tab_content
())
@skip
(
'Fix a11y test errors.'
)
@attr
(
'a11y'
)
def
test_courseware_a11y
(
self
):
"""
Run accessibility audit for the problem type.
"""
self
.
course_nav
.
go_to_section
(
'Test Section 1'
,
'Test Subsection 1,1'
)
# Set the scope to the sequence navigation
self
.
courseware_page
.
a11y_audit
.
config
.
set_scope
(
include
=
[
'div.sequence-nav'
])
self
.
courseware_page
.
a11y_audit
.
check_for_accessibility_errors
()
common/test/acceptance/tests/lms/test_lms_edxnotes.py
View file @
8cbf99ac
...
...
@@ -194,14 +194,14 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
self
.
create_notes
(
components
)
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware_page
.
go_to_sequential_position
(
2
)
components
=
self
.
note_unit_page
.
components
self
.
create_notes
(
components
)
components
=
self
.
note_unit_page
.
refresh
()
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
self
.
course
_nav
.
go_to_sequential_position
(
1
)
self
.
course
ware_page
.
go_to_sequential_position
(
1
)
components
=
self
.
note_unit_page
.
components
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
...
...
@@ -227,7 +227,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
self
.
edit_notes
(
components
)
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware_page
.
go_to_sequential_position
(
2
)
components
=
self
.
note_unit_page
.
components
self
.
edit_notes
(
components
)
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
...
...
@@ -235,7 +235,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
components
=
self
.
note_unit_page
.
refresh
()
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
self
.
course
_nav
.
go_to_sequential_position
(
1
)
self
.
course
ware_page
.
go_to_sequential_position
(
1
)
components
=
self
.
note_unit_page
.
components
self
.
assert_text_in_notes
(
self
.
note_unit_page
.
notes
)
...
...
@@ -261,7 +261,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
self
.
remove_notes
(
components
)
self
.
assert_notes_are_removed
(
components
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware_page
.
go_to_sequential_position
(
2
)
components
=
self
.
note_unit_page
.
components
self
.
remove_notes
(
components
)
self
.
assert_notes_are_removed
(
components
)
...
...
@@ -269,7 +269,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
components
=
self
.
note_unit_page
.
refresh
()
self
.
assert_notes_are_removed
(
components
)
self
.
course
_nav
.
go_to_sequential_position
(
1
)
self
.
course
ware_page
.
go_to_sequential_position
(
1
)
components
=
self
.
note_unit_page
.
components
self
.
assert_notes_are_removed
(
components
)
...
...
@@ -1106,10 +1106,10 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
self
.
assertTrue
(
note
.
is_visible
)
note
=
self
.
note_unit_page
.
notes
[
1
]
self
.
assertFalse
(
note
.
is_visible
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware_page
.
go_to_sequential_position
(
2
)
note
=
self
.
note_unit_page
.
notes
[
0
]
self
.
assertFalse
(
note
.
is_visible
)
self
.
course
_nav
.
go_to_sequential_position
(
1
)
self
.
course
ware_page
.
go_to_sequential_position
(
1
)
note
=
self
.
note_unit_page
.
notes
[
0
]
self
.
assertFalse
(
note
.
is_visible
)
...
...
@@ -1494,7 +1494,7 @@ class EdxNotesToggleNotesTest(EdxNotesTestMixin):
# Disable all notes
self
.
note_unit_page
.
toggle_visibility
()
self
.
assertEqual
(
len
(
self
.
note_unit_page
.
notes
),
0
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware_page
.
go_to_sequential_position
(
2
)
self
.
assertEqual
(
len
(
self
.
note_unit_page
.
notes
),
0
)
self
.
course_nav
.
go_to_section
(
u"Test Section 1"
,
u"Test Subsection 2"
)
self
.
assertEqual
(
len
(
self
.
note_unit_page
.
notes
),
0
)
...
...
@@ -1520,7 +1520,7 @@ class EdxNotesToggleNotesTest(EdxNotesTestMixin):
# the page.
self
.
note_unit_page
.
toggle_visibility
()
self
.
assertGreater
(
len
(
self
.
note_unit_page
.
notes
),
0
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware_page
.
go_to_sequential_position
(
2
)
self
.
assertGreater
(
len
(
self
.
note_unit_page
.
notes
),
0
)
self
.
course_nav
.
go_to_section
(
u"Test Section 1"
,
u"Test Subsection 2"
)
self
.
assertGreater
(
len
(
self
.
note_unit_page
.
notes
),
0
)
common/test/acceptance/tests/studio/test_studio_outline.py
View file @
8cbf99ac
...
...
@@ -1546,7 +1546,7 @@ class PublishSectionTest(CourseOutlineTest):
self
.
assertTrue
(
section
.
publish_action
)
self
.
courseware
.
visit
()
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware
.
go_to_sequential_position
(
2
)
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
def
test_section_publishing
(
self
):
...
...
@@ -1571,7 +1571,7 @@ class PublishSectionTest(CourseOutlineTest):
self
.
assertFalse
(
unit
.
publish_action
)
self
.
courseware
.
visit
()
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware
.
go_to_sequential_position
(
2
)
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
self
.
course_nav
.
go_to_section
(
SECTION_NAME
,
'Test Subsection 2'
)
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
...
...
common/test/acceptance/tests/video/test_video_module.py
View file @
8cbf99ac
...
...
@@ -11,6 +11,7 @@ from unittest import skipIf, skip
from
..helpers
import
UniqueCourseTest
,
is_youtube_available
,
YouTubeStubConfig
from
...pages.lms.video.video
import
VideoPage
from
...pages.lms.tab_nav
import
TabNavPage
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.course_nav
import
CourseNavPage
from
...pages.lms.auto_auth
import
AutoAuthPage
from
...pages.lms.course_info
import
CourseInfoPage
...
...
@@ -49,6 +50,7 @@ class VideoBaseTest(UniqueCourseTest):
self
.
video
=
VideoPage
(
self
.
browser
)
self
.
tab_nav
=
TabNavPage
(
self
.
browser
)
self
.
course_nav
=
CourseNavPage
(
self
.
browser
)
self
.
courseware
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
self
.
course_info_page
=
CourseInfoPage
(
self
.
browser
,
self
.
course_id
)
self
.
auth_page
=
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
...
...
@@ -190,7 +192,7 @@ class VideoBaseTest(UniqueCourseTest):
"""
Navigate to sequential specified by `video_display_name`
"""
self
.
course
_nav
.
go_to_sequential_position
(
position
)
self
.
course
ware
.
go_to_sequential_position
(
position
)
self
.
video
.
wait_for_video_player_render
()
...
...
@@ -916,13 +918,13 @@ class YouTubeVideoTest(VideoBaseTest):
execute_video_steps
([
'A'
])
# go to second sequential position
self
.
course
_nav
.
go_to_sequential_position
(
2
)
self
.
course
ware
.
go_to_sequential_position
(
2
)
execute_video_steps
(
tab2_video_names
)
# go back to first sequential position
# we are again playing tab 1 videos to ensure that switching didn't broke some video functionality.
self
.
course
_nav
.
go_to_sequential_position
(
1
)
self
.
course
ware
.
go_to_sequential_position
(
1
)
execute_video_steps
(
tab1_video_names
)
self
.
video
.
browser
.
refresh
()
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
8cbf99ac
...
...
@@ -37,6 +37,7 @@ from course_modes.tests.factories import CourseModeFactory
from
courseware.model_data
import
set_score
from
courseware.testutils
import
RenderXBlockTestMixin
from
courseware.tests.factories
import
StudentModuleFactory
from
courseware.url_helpers
import
get_redirect_url
from
courseware.user_state_client
import
DjangoXBlockUserStateClient
from
edxmako.tests
import
mako_middleware_process_request
from
lms.djangoapps.commerce.utils
import
EcommerceService
# pylint: disable=import-error
...
...
@@ -192,9 +193,25 @@ class ViewsTestCase(ModuleStoreTestCase):
super
(
ViewsTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
display_name
=
u'teꜱᴛ course'
)
self
.
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
self
.
course
.
location
)
self
.
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
self
.
chapter
.
location
,
due
=
datetime
(
2013
,
9
,
18
,
11
,
30
,
00
))
self
.
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
self
.
chapter
.
location
,
due
=
datetime
(
2013
,
9
,
18
,
11
,
30
,
00
),
)
self
.
vertical
=
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
self
.
section
.
location
)
self
.
component
=
ItemFactory
.
create
(
category
=
'problem'
,
parent_location
=
self
.
vertical
.
location
)
self
.
component
=
ItemFactory
.
create
(
category
=
'problem'
,
parent_location
=
self
.
vertical
.
location
,
display_name
=
'Problem 1'
,
)
self
.
section2
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
self
.
chapter
.
location
)
self
.
vertical2
=
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
self
.
section2
.
location
)
ItemFactory
.
create
(
category
=
'problem'
,
parent_location
=
self
.
vertical2
.
location
,
display_name
=
'Problem 2'
,
)
self
.
course_key
=
self
.
course
.
id
self
.
password
=
'123456'
...
...
@@ -210,6 +227,74 @@ class ViewsTestCase(ModuleStoreTestCase):
self
.
org
=
u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"
self
.
org_html
=
"<p>'+Stark/Industries+'</p>"
def
test_index_success
(
self
):
response
=
self
.
_verify_index_response
()
self
.
assertIn
(
'Problem 2'
,
response
.
content
)
# re-access to the main course page redirects to last accessed view.
url
=
reverse
(
'courseware'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course_key
)})
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
302
)
response
=
self
.
client
.
get
(
response
.
url
)
# pylint: disable=no-member
self
.
assertNotIn
(
'Problem 1'
,
response
.
content
)
self
.
assertIn
(
'Problem 2'
,
response
.
content
)
def
test_index_nonexistent_chapter
(
self
):
self
.
_verify_index_response
(
expected_response_code
=
404
,
chapter_name
=
'non-existent'
)
def
test_index_nonexistent_chapter_masquerade
(
self
):
with
patch
(
'courseware.views.setup_masquerade'
)
as
patch_masquerade
:
masquerade
=
MagicMock
(
role
=
'student'
)
patch_masquerade
.
return_value
=
(
masquerade
,
self
.
user
)
self
.
_verify_index_response
(
expected_response_code
=
302
,
chapter_name
=
'non-existent'
)
def
test_index_nonexistent_section
(
self
):
self
.
_verify_index_response
(
expected_response_code
=
404
,
section_name
=
'non-existent'
)
def
test_index_nonexistent_section_masquerade
(
self
):
with
patch
(
'courseware.views.setup_masquerade'
)
as
patch_masquerade
:
masquerade
=
MagicMock
(
role
=
'student'
)
patch_masquerade
.
return_value
=
(
masquerade
,
self
.
user
)
self
.
_verify_index_response
(
expected_response_code
=
302
,
section_name
=
'non-existent'
)
def
_verify_index_response
(
self
,
expected_response_code
=
200
,
chapter_name
=
None
,
section_name
=
None
):
"""
Verifies the response when the courseware index page is accessed with
the given chapter and section names.
"""
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
password
)
url
=
reverse
(
'courseware_section'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course_key
),
'chapter'
:
unicode
(
self
.
chapter
.
location
.
name
)
if
chapter_name
is
None
else
chapter_name
,
'section'
:
unicode
(
self
.
section2
.
location
.
name
)
if
section_name
is
None
else
section_name
,
}
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
expected_response_code
)
return
response
def
test_index_no_visible_section_in_chapter
(
self
):
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
password
)
# reload the chapter from the store so its children information is updated
self
.
chapter
=
self
.
store
.
get_item
(
self
.
chapter
.
location
)
# disable the visibility of the sections in the chapter
for
section
in
self
.
chapter
.
get_children
():
section
.
visible_to_staff_only
=
True
self
.
store
.
update_item
(
section
,
ModuleStoreEnum
.
UserID
.
test
)
url
=
reverse
(
'courseware_chapter'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
),
'chapter'
:
unicode
(
self
.
chapter
.
location
.
name
)},
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertNotIn
(
'Problem 1'
,
response
.
content
)
self
.
assertNotIn
(
'Problem 2'
,
response
.
content
)
@unittest.skipUnless
(
settings
.
FEATURES
.
get
(
'ENABLE_SHOPPING_CART'
),
"Shopping Cart not enabled in settings"
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_PAID_COURSE_REGISTRATION'
:
True
})
def
test_course_about_in_cart
(
self
):
...
...
@@ -299,15 +384,37 @@ class ViewsTestCase(ModuleStoreTestCase):
self
.
assertEqual
(
views
.
user_groups
(
mock_user
),
[])
def
test_get_current_child
(
self
):
self
.
assertIsNone
(
views
.
get_current_child
(
MagicMock
()))
mock_xmodule
=
MagicMock
()
self
.
assertIsNone
(
views
.
get_current_child
(
mock_xmodule
))
mock_xmodule
.
position
=
-
1
mock_xmodule
.
get_display_items
.
return_value
=
[
'one'
,
'two'
]
mock_xmodule
.
get_display_items
.
return_value
=
[
'one'
,
'two'
,
'three'
]
self
.
assertEqual
(
views
.
get_current_child
(
mock_xmodule
),
'one'
)
mock_xmodule_2
=
MagicMock
()
mock_xmodule_2
.
position
=
3
mock_xmodule_2
.
get_display_items
.
return_value
=
[]
self
.
assertIsNone
(
views
.
get_current_child
(
mock_xmodule_2
))
mock_xmodule
.
position
=
2
self
.
assertEqual
(
views
.
get_current_child
(
mock_xmodule
),
'two'
)
self
.
assertEqual
(
views
.
get_current_child
(
mock_xmodule
,
requested_child
=
'first'
),
'one'
)
self
.
assertEqual
(
views
.
get_current_child
(
mock_xmodule
,
requested_child
=
'last'
),
'three'
)
mock_xmodule
.
position
=
3
mock_xmodule
.
get_display_items
.
return_value
=
[]
self
.
assertIsNone
(
views
.
get_current_child
(
mock_xmodule
))
def
test_get_redirect_url
(
self
):
self
.
assertIn
(
'activate_block_id'
,
get_redirect_url
(
self
.
course_key
,
self
.
section
.
location
),
)
self
.
assertIn
(
'child=first'
,
get_redirect_url
(
self
.
course_key
,
self
.
section
.
location
,
child
=
'first'
),
)
self
.
assertIn
(
'child=last'
,
get_redirect_url
(
self
.
course_key
,
self
.
section
.
location
,
child
=
'last'
),
)
def
test_redirect_to_course_position
(
self
):
mock_module
=
MagicMock
()
...
...
lms/djangoapps/courseware/url_helpers.py
View file @
8cbf99ac
...
...
@@ -7,12 +7,13 @@ from xmodule.modulestore.django import modulestore
from
django.core.urlresolvers
import
reverse
def
get_redirect_url
(
course_key
,
usage_key
):
def
get_redirect_url
(
course_key
,
usage_key
,
child
=
None
):
""" Returns the redirect url back to courseware
Args:
course_id(str): Course Id string
location(str): The location id of course component
child(str): Optional child parameter to pass to the URL
Raises:
ItemNotFoundError if no data at the location or NoPathToItem if location not in any class
...
...
@@ -50,4 +51,7 @@ def get_redirect_url(course_key, usage_key):
redirect_url
+=
"?{}"
.
format
(
urlencode
({
'activate_block_id'
:
unicode
(
final_target_id
)}))
if
child
:
redirect_url
+=
"&child={}"
.
format
(
child
)
return
redirect_url
lms/djangoapps/courseware/views.py
View file @
8cbf99ac
This diff is collapsed.
Click to expand it.
lms/envs/bok_choy.py
View file @
8cbf99ac
...
...
@@ -162,6 +162,9 @@ FEATURES['ENABLE_COURSEWARE_SEARCH'] = True
# Enable dashboard search for tests
FEATURES
[
'ENABLE_DASHBOARD_SEARCH'
]
=
True
# Enable cross-section Next button for tests
FEATURES
[
'ENABLE_NEXT_BUTTON_ACROSS_SECTIONS'
]
=
True
# Use MockSearchEngine as the search engine for test scenario
SEARCH_ENGINE
=
"search.tests.mock_search_engine.MockSearchEngine"
# Path at which to store the mock index
...
...
lms/envs/common.py
View file @
8cbf99ac
...
...
@@ -371,7 +371,10 @@ FEATURES = {
# This is the default, but can be disabled if all history
# lives in the Extended table, saving the frontend from
# making multiple queries.
'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES'
:
True
'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES'
:
True
,
# Enable Next Button to jump sequences in Sequence Navigation bar.
'ENABLE_NEXT_BUTTON_ACROSS_SECTIONS'
:
False
,
}
# Ignore static asset files on import which match this pattern
...
...
lms/templates/seq_module.html
View file @
8cbf99ac
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<div
id=
"sequence_${element_id}"
class=
"sequence"
data-id=
"${item_id}"
data-position=
"${position}"
data-ajax-url=
"${ajax_url}"
>
<div
id=
"sequence_${element_id}"
class=
"sequence"
data-id=
"${item_id}"
data-position=
"${position}"
data-ajax-url=
"${ajax_url}"
data-next-url=
"${next_url}"
data-prev-url=
"${prev_url}"
>
<div
class=
"path"
></div>
<div
class=
"sequence-nav"
>
<button
class=
"sequence-nav-button button-previous"
>
...
...
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