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
3cbbb8f3
Commit
3cbbb8f3
authored
May 25, 2015
by
muzaffaryousaf
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add/Remove Bookmark button to each unit in LMS courseware.
TNL-1957
parent
c9b87aa0
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
654 additions
and
111 deletions
+654
-111
common/lib/xmodule/xmodule/css/sequence/display.scss
+11
-2
common/lib/xmodule/xmodule/js/src/sequence/display.coffee
+16
-1
common/lib/xmodule/xmodule/library_content_module.py
+1
-0
common/lib/xmodule/xmodule/public/js/vertical_student_view.js
+16
-0
common/lib/xmodule/xmodule/seq_module.py
+16
-4
common/lib/xmodule/xmodule/tests/test_vertical.py
+14
-1
common/lib/xmodule/xmodule/vertical_block.py
+7
-0
common/test/acceptance/pages/lms/bookmarks.py
+6
-5
common/test/acceptance/pages/lms/course_nav.py
+2
-2
common/test/acceptance/pages/lms/courseware.py
+27
-0
common/test/acceptance/tests/lms/test_bookmarks.py
+145
-61
common/test/acceptance/tests/lms/test_lms_courseware.py
+35
-2
lms/djangoapps/bookmarks/tests/test_views.py
+2
-2
lms/djangoapps/courseware/module_render.py
+3
-0
lms/djangoapps/courseware/tests/test_module_render.py
+9
-7
lms/djangoapps/courseware/tests/test_split_module.py
+1
-1
lms/djangoapps/courseware/views.py
+3
-1
lms/static/js/bookmarks/collections/bookmarks.js
+5
-2
lms/static/js/bookmarks/main.js
+3
-3
lms/static/js/bookmarks/views/bookmark_button.js
+93
-0
lms/static/js/bookmarks/views/bookmarks_list.js
+3
-1
lms/static/js/bookmarks/views/bookmarks_list_button.js
+0
-2
lms/static/js/fixtures/bookmarks/bookmark_button.html
+13
-0
lms/static/js/fixtures/bookmarks/bookmarks.html
+1
-1
lms/static/js/spec/bookmarks/bookmark_button_view_spec.js
+114
-0
lms/static/js/spec/bookmarks/bookmarks_list_view_spec.js
+5
-5
lms/static/js/spec/main.js
+9
-1
lms/static/sass/course/courseware/_courseware.scss
+4
-0
lms/static/sass/views/_bookmarks.scss
+59
-4
lms/templates/bookmark_button.html
+11
-0
lms/templates/courseware/courseware.html
+10
-1
lms/templates/seq_module.html
+5
-2
lms/templates/vert_module.html
+5
-0
No files found.
common/lib/xmodule/xmodule/css/sequence/display.scss
View file @
3cbbb8f3
$sequence--border-color
:
#C8C8C8
;
$sequence--border-color
:
#C8C8C8
;
$link-color
:
rgb
(
26
,
161
,
222
);
// repeated extends - needed since LMS styling was referenced
// repeated extends - needed since LMS styling was referenced
.block-link
{
.block-link
{
border-left
:
1px
solid
lighten
(
$sequence--border-color
,
10%
);
border-left
:
1px
solid
lighten
(
$sequence--border-color
,
10%
);
...
@@ -36,7 +36,7 @@ $sequence--border-color: #C8C8C8;
...
@@ -36,7 +36,7 @@ $sequence--border-color: #C8C8C8;
// TODO (cpennington): This doesn't work anymore. XModules aren't able to
// TODO (cpennington): This doesn't work anymore. XModules aren't able to
// import from external sources.
// import from external sources.
@extend
.topbar
;
@extend
.topbar
;
margin
:
-4px
0
(
$baseline
*
1
.5
)
;
margin
:
-4px
0
$baseline
;
position
:
relative
;
position
:
relative
;
border-bottom
:
none
;
border-bottom
:
none
;
z-index
:
0
;
z-index
:
0
;
...
@@ -119,6 +119,10 @@ $sequence--border-color: #C8C8C8;
...
@@ -119,6 +119,10 @@ $sequence--border-color: #C8C8C8;
-webkit-font-smoothing
:
antialiased
;
// Clear up the lines on the icons
-webkit-font-smoothing
:
antialiased
;
// Clear up the lines on the icons
}
}
i
.fa-bookmark
{
color
:
$link-color
;
}
&
.inactive
{
&
.inactive
{
.icon
{
.icon
{
...
@@ -142,6 +146,10 @@ $sequence--border-color: #C8C8C8;
...
@@ -142,6 +146,10 @@ $sequence--border-color: #C8C8C8;
.icon
{
.icon
{
color
:
rgb
(
10
,
10
,
10
);
color
:
rgb
(
10
,
10
,
10
);
}
}
i
.fa-bookmark
{
color
:
$link-color
;
}
}
}
}
}
...
@@ -295,3 +303,4 @@ nav.sequence-bottom {
...
@@ -295,3 +303,4 @@ nav.sequence-bottom {
outline
:
none
;
outline
:
none
;
}
}
}
}
common/lib/xmodule/xmodule/js/src/sequence/display.coffee
View file @
3cbbb8f3
...
@@ -18,6 +18,8 @@ class @Sequence
...
@@ -18,6 +18,8 @@ class @Sequence
bind
:
->
bind
:
->
@
$
(
'#sequence-list a'
).
click
@
goto
@
$
(
'#sequence-list a'
).
click
@
goto
@
el
.
on
'bookmark:add'
,
@
addBookmarkIconToActiveNavItem
@
el
.
on
'bookmark:remove'
,
@
removeBookmarkIconFromActiveNavItem
initProgress
:
->
initProgress
:
->
@
progressTable
=
{}
# "#problem_#{id}" -> progress
@
progressTable
=
{}
# "#problem_#{id}" -> progress
...
@@ -102,8 +104,9 @@ class @Sequence
...
@@ -102,8 +104,9 @@ class @Sequence
@
mark_active
new_position
@
mark_active
new_position
current_tab
=
@
contents
.
eq
(
new_position
-
1
)
current_tab
=
@
contents
.
eq
(
new_position
-
1
)
@
content_container
.
html
(
current_tab
.
text
()).
attr
(
"aria-labelledby"
,
current_tab
.
attr
(
"aria-labelledby"
))
bookmarked
=
if
@
el
.
find
(
'.active .bookmark-icon'
).
hasClass
(
'bookmarked'
)
then
true
else
false
@
content_container
.
html
(
current_tab
.
text
()).
attr
(
"aria-labelledby"
,
current_tab
.
attr
(
"aria-labelledby"
)).
data
(
'bookmarked'
,
bookmarked
)
XBlock
.
initializeBlocks
(
@
content_container
,
@
requestToken
)
XBlock
.
initializeBlocks
(
@
content_container
,
@
requestToken
)
window
.
update_schematics
()
# For embedded circuit simulator exercises in 6.002x
window
.
update_schematics
()
# For embedded circuit simulator exercises in 6.002x
...
@@ -116,6 +119,8 @@ class @Sequence
...
@@ -116,6 +119,8 @@ class @Sequence
sequence_links
=
@
content_container
.
find
(
'a.seqnav'
)
sequence_links
=
@
content_container
.
find
(
'a.seqnav'
)
sequence_links
.
click
@
goto
sequence_links
.
click
@
goto
@
el
.
find
(
'.path'
).
html
(
@
el
.
find
(
'.nav-item.active'
).
data
(
'path'
))
@
sr_container
.
focus
();
@
sr_container
.
focus
();
# @$("a.active").blur()
# @$("a.active").blur()
...
@@ -180,3 +185,13 @@ class @Sequence
...
@@ -180,3 +185,13 @@ class @Sequence
element
.
removeClass
(
"inactive"
)
element
.
removeClass
(
"inactive"
)
.
removeClass
(
"visited"
)
.
removeClass
(
"visited"
)
.
addClass
(
"active"
)
.
addClass
(
"active"
)
addBookmarkIconToActiveNavItem
:
(
event
)
=>
event
.
preventDefault
()
@
el
.
find
(
'.nav-item.active .bookmark-icon'
).
removeClass
(
'is-hidden'
).
addClass
(
'bookmarked'
)
@
el
.
find
(
'.nav-item.active .bookmark-icon-sr'
).
text
(
gettext
(
'Bookmarked'
))
removeBookmarkIconFromActiveNavItem
:
(
event
)
=>
event
.
preventDefault
()
@
el
.
find
(
'.nav-item.active .bookmark-icon'
).
removeClass
(
'bookmarked'
).
addClass
(
'is-hidden'
)
@
el
.
find
(
'.nav-item.active .bookmark-icon-sr'
).
text
(
''
)
common/lib/xmodule/xmodule/library_content_module.py
View file @
3cbbb8f3
...
@@ -316,6 +316,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
...
@@ -316,6 +316,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
fragment
.
add_content
(
self
.
system
.
render_template
(
'vert_module.html'
,
{
fragment
.
add_content
(
self
.
system
.
render_template
(
'vert_module.html'
,
{
'items'
:
contents
,
'items'
:
contents
,
'xblock_context'
:
context
,
'xblock_context'
:
context
,
'show_bookmark_button'
:
False
,
}))
}))
return
fragment
return
fragment
...
...
common/lib/xmodule/xmodule/public/js/vertical_student_view.js
0 → 100644
View file @
3cbbb8f3
/* JavaScript for Vertical Student View. */
window
.
VerticalStudentView
=
function
(
runtime
,
element
)
{
RequireJS
.
require
([
'js/bookmarks/views/bookmark_button'
],
function
(
BookmarkButton
)
{
var
$element
=
$
(
element
);
var
$bookmarkButtonElement
=
$element
.
find
(
'.bookmark-button'
);
return
new
BookmarkButton
({
el
:
$bookmarkButtonElement
,
bookmarkId
:
$bookmarkButtonElement
.
data
(
'bookmarkId'
),
usageId
:
$element
.
data
(
'usageId'
),
bookmarked
:
$element
.
parent
(
'#seq_content'
).
data
(
'bookmarked'
),
apiUrl
:
$
(
".courseware-bookmarks-button"
).
data
(
'bookmarksApiUrl'
)
});
});
};
common/lib/xmodule/xmodule/seq_module.py
View file @
3cbbb8f3
...
@@ -55,7 +55,6 @@ class SequenceFields(object):
...
@@ -55,7 +55,6 @@ class SequenceFields(object):
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
)
)
class
ProctoringFields
(
object
):
class
ProctoringFields
(
object
):
"""
"""
Fields that are specific to Proctored or Timed Exams
Fields that are specific to Proctored or Timed Exams
...
@@ -119,9 +118,12 @@ class ProctoringFields(object):
...
@@ -119,9 +118,12 @@ class ProctoringFields(object):
@XBlock.wants
(
'proctoring'
)
@XBlock.wants
(
'proctoring'
)
@XBlock.wants
(
'credit'
)
@XBlock.wants
(
'credit'
)
class
SequenceModule
(
SequenceFields
,
ProctoringFields
,
XModule
):
@XBlock.needs
(
"user"
)
''' Layout module which lays out content in a temporal sequence
@XBlock.needs
(
"bookmarks"
)
'''
class
SequenceModule
(
SequenceFields
,
XModule
):
"""
Layout module which lays out content in a temporal sequence
"""
js
=
{
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/sequence/display.coffee'
)],
'coffee'
:
[
resource_string
(
__name__
,
'js/src/sequence/display.coffee'
)],
'js'
:
[
resource_string
(
__name__
,
'js/src/sequence/display/jquery.sequence.js'
)],
'js'
:
[
resource_string
(
__name__
,
'js/src/sequence/display/jquery.sequence.js'
)],
...
@@ -182,7 +184,12 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
...
@@ -182,7 +184,12 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
contents
=
[]
contents
=
[]
fragment
=
Fragment
()
fragment
=
Fragment
()
context
=
context
or
{}
bookmarks_service
=
self
.
runtime
.
service
(
self
,
"bookmarks"
)
context
[
"username"
]
=
self
.
runtime
.
service
(
self
,
"user"
)
.
get_current_user
()
.
opt_attrs
[
'edx-platform.username'
]
display_names
=
[
self
.
get_parent
()
.
display_name
or
''
,
self
.
display_name
or
''
]
# Is this sequential part of a timed or proctored exam?
# Is this sequential part of a timed or proctored exam?
if
self
.
is_time_limited
:
if
self
.
is_time_limited
:
view_html
=
self
.
_time_limited_student_view
(
context
)
view_html
=
self
.
_time_limited_student_view
(
context
)
...
@@ -194,6 +201,9 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
...
@@ -194,6 +201,9 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
return
fragment
return
fragment
for
child
in
self
.
get_display_items
():
for
child
in
self
.
get_display_items
():
is_bookmarked
=
bookmarks_service
.
is_bookmarked
(
usage_key
=
child
.
scope_ids
.
usage_id
)
context
[
"bookmarked"
]
=
is_bookmarked
progress
=
child
.
get_progress
()
progress
=
child
.
get_progress
()
rendered_child
=
child
.
render
(
STUDENT_VIEW
,
context
)
rendered_child
=
child
.
render
(
STUDENT_VIEW
,
context
)
fragment
.
add_frag_resources
(
rendered_child
)
fragment
.
add_frag_resources
(
rendered_child
)
...
@@ -209,6 +219,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
...
@@ -209,6 +219,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
'progress_detail'
:
Progress
.
to_js_detail_str
(
progress
),
'progress_detail'
:
Progress
.
to_js_detail_str
(
progress
),
'type'
:
child
.
get_icon_class
(),
'type'
:
child
.
get_icon_class
(),
'id'
:
child
.
scope_ids
.
usage_id
.
to_deprecated_string
(),
'id'
:
child
.
scope_ids
.
usage_id
.
to_deprecated_string
(),
'bookmarked'
:
is_bookmarked
,
'path'
:
" > "
.
join
(
display_names
+
[
child
.
display_name
or
''
]),
}
}
if
childinfo
[
'title'
]
==
''
:
if
childinfo
[
'title'
]
==
''
:
childinfo
[
'title'
]
=
child
.
display_name_with_default
childinfo
[
'title'
]
=
child
.
display_name_with_default
...
...
common/lib/xmodule/xmodule/tests/test_vertical.py
View file @
3cbbb8f3
...
@@ -37,18 +37,31 @@ class BaseVerticalBlockTest(XModuleXmlImportTest):
...
@@ -37,18 +37,31 @@ class BaseVerticalBlockTest(XModuleXmlImportTest):
self
.
vertical
=
course_seq
.
get_children
()[
0
]
self
.
vertical
=
course_seq
.
get_children
()[
0
]
self
.
vertical
.
xmodule_runtime
=
self
.
module_system
self
.
vertical
.
xmodule_runtime
=
self
.
module_system
self
.
username
=
"bilbo"
self
.
default_context
=
{
"bookmarked"
:
False
,
"username"
:
self
.
username
}
class
VerticalBlockTestCase
(
BaseVerticalBlockTest
):
class
VerticalBlockTestCase
(
BaseVerticalBlockTest
):
"""
"""
Tests for the VerticalBlock.
Tests for the VerticalBlock.
"""
"""
def
assert_bookmark_info_in
(
self
,
content
):
"""
Assert content has all the bookmark info.
"""
self
.
assertIn
(
'bookmark_id'
,
content
)
self
.
assertIn
(
'{},{}'
.
format
(
self
.
username
,
unicode
(
self
.
vertical
.
location
)),
content
)
self
.
assertIn
(
'bookmarked'
,
content
)
self
.
assertIn
(
'show_bookmark_button'
,
content
)
def
test_render_student_view
(
self
):
def
test_render_student_view
(
self
):
"""
"""
Test the rendering of the student view.
Test the rendering of the student view.
"""
"""
html
=
self
.
module_system
.
render
(
self
.
vertical
,
STUDENT_VIEW
,
{}
)
.
content
html
=
self
.
module_system
.
render
(
self
.
vertical
,
STUDENT_VIEW
,
self
.
default_context
)
.
content
self
.
assertIn
(
self
.
test_html_1
,
html
)
self
.
assertIn
(
self
.
test_html_1
,
html
)
self
.
assertIn
(
self
.
test_html_2
,
html
)
self
.
assertIn
(
self
.
test_html_2
,
html
)
self
.
assert_bookmark_info_in
(
html
)
def
test_render_studio_view
(
self
):
def
test_render_studio_view
(
self
):
"""
"""
...
...
common/lib/xmodule/xmodule/vertical_block.py
View file @
3cbbb8f3
...
@@ -54,7 +54,14 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse
...
@@ -54,7 +54,14 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse
fragment
.
add_content
(
self
.
system
.
render_template
(
'vert_module.html'
,
{
fragment
.
add_content
(
self
.
system
.
render_template
(
'vert_module.html'
,
{
'items'
:
contents
,
'items'
:
contents
,
'xblock_context'
:
context
,
'xblock_context'
:
context
,
'show_bookmark_button'
:
True
,
'bookmarked'
:
child_context
[
'bookmarked'
],
'bookmark_id'
:
"{},{}"
.
format
(
child_context
[
'username'
],
unicode
(
self
.
location
))
}))
}))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/vertical_student_view.js'
))
fragment
.
initialize_js
(
'VerticalStudentView'
)
return
fragment
return
fragment
def
author_view
(
self
,
context
):
def
author_view
(
self
,
context
):
...
...
common/test/acceptance/pages/lms/bookmarks.py
View file @
3cbbb8f3
...
@@ -7,7 +7,7 @@ from .course_page import CoursePage
...
@@ -7,7 +7,7 @@ from .course_page import CoursePage
class
BookmarksPage
(
CoursePage
):
class
BookmarksPage
(
CoursePage
):
"""
"""
Coursware Bookmarks Page.
Cours
e
ware Bookmarks Page.
"""
"""
url
=
None
url
=
None
url_path
=
"courseware/"
url_path
=
"courseware/"
...
@@ -23,10 +23,11 @@ class BookmarksPage(CoursePage):
...
@@ -23,10 +23,11 @@ class BookmarksPage(CoursePage):
""" Check if bookmarks button is visible """
""" Check if bookmarks button is visible """
return
self
.
q
(
css
=
self
.
BOOKMARKS_BUTTON_SELECTOR
)
.
visible
return
self
.
q
(
css
=
self
.
BOOKMARKS_BUTTON_SELECTOR
)
.
visible
def
click_bookmarks_button
(
self
):
def
click_bookmarks_button
(
self
,
wait_for_results
=
True
):
""" Click on Bookmarks button """
""" Click on Bookmarks button """
self
.
q
(
css
=
self
.
BOOKMARKS_BUTTON_SELECTOR
)
.
first
.
click
()
self
.
q
(
css
=
self
.
BOOKMARKS_BUTTON_SELECTOR
)
.
first
.
click
()
EmptyPromise
(
self
.
results_present
,
"Bookmarks results present"
)
.
fulfill
()
if
wait_for_results
:
EmptyPromise
(
self
.
results_present
,
"Bookmarks results present"
)
.
fulfill
()
def
results_present
(
self
):
def
results_present
(
self
):
""" Check if bookmarks results are present """
""" Check if bookmarks results are present """
...
@@ -53,9 +54,9 @@ class BookmarksPage(CoursePage):
...
@@ -53,9 +54,9 @@ class BookmarksPage(CoursePage):
breadcrumbs
=
self
.
q
(
css
=
self
.
BOOKMARKED_BREADCRUMBS
)
.
text
breadcrumbs
=
self
.
q
(
css
=
self
.
BOOKMARKED_BREADCRUMBS
)
.
text
return
[
breadcrumb
.
replace
(
'
\n
'
,
''
)
.
split
(
'-'
)
for
breadcrumb
in
breadcrumbs
]
return
[
breadcrumb
.
replace
(
'
\n
'
,
''
)
.
split
(
'-'
)
for
breadcrumb
in
breadcrumbs
]
def
click_bookmark
(
self
,
index
):
def
click_bookmark
ed_block
(
self
,
index
):
"""
"""
Click on bookmark at index `index`
Click on bookmark
ed block
at index `index`
Arguments:
Arguments:
index (int): bookmark index in the list
index (int): bookmark index in the list
...
...
common/test/acceptance/pages/lms/course_nav.py
View file @
3cbbb8f3
...
@@ -193,13 +193,13 @@ class CourseNavPage(PageObject):
...
@@ -193,13 +193,13 @@ class CourseNavPage(PageObject):
)
)
# Regular expression to remove HTML span tags from a string
# Regular expression to remove HTML span tags from a string
REMOVE_SPAN_TAG_RE
=
re
.
compile
(
r'<
span.+/span>
'
)
REMOVE_SPAN_TAG_RE
=
re
.
compile
(
r'<
/span>(.+)<span
'
)
def
_clean_seq_titles
(
self
,
element
):
def
_clean_seq_titles
(
self
,
element
):
"""
"""
Clean HTML of sequence titles, stripping out span tags and returning the first line.
Clean HTML of sequence titles, stripping out span tags and returning the first line.
"""
"""
return
self
.
REMOVE_SPAN_TAG_RE
.
s
ub
(
''
,
element
.
get_attribute
(
'innerHTML'
))
.
strip
()
.
split
(
'
\n
'
)[
0
]
return
self
.
REMOVE_SPAN_TAG_RE
.
s
earch
(
element
.
get_attribute
(
'innerHTML'
))
.
groups
()[
0
]
.
strip
()
def
go_to_sequential_position
(
self
,
sequential_position
):
def
go_to_sequential_position
(
self
,
sequential_position
):
"""
"""
...
...
common/test/acceptance/pages/lms/courseware.py
View file @
3cbbb8f3
...
@@ -3,6 +3,7 @@ Courseware page.
...
@@ -3,6 +3,7 @@ Courseware page.
"""
"""
from
.course_page
import
CoursePage
from
.course_page
import
CoursePage
from
bok_choy.promise
import
EmptyPromise
from
selenium.webdriver.common.action_chains
import
ActionChains
from
selenium.webdriver.common.action_chains
import
ActionChains
...
@@ -177,6 +178,32 @@ class CoursewarePage(CoursePage):
...
@@ -177,6 +178,32 @@ class CoursewarePage(CoursePage):
attribute_value
=
lambda
el
:
el
.
get_attribute
(
'data-id'
)
attribute_value
=
lambda
el
:
el
.
get_attribute
(
'data-id'
)
return
self
.
q
(
css
=
'#sequence-list a'
)
.
filter
(
get_active
)
.
map
(
attribute_value
)
.
results
[
0
]
return
self
.
q
(
css
=
'#sequence-list a'
)
.
filter
(
get_active
)
.
map
(
attribute_value
)
.
results
[
0
]
@property
def
breadcrumb
(
self
):
""" Return the course tree breadcrumb shown above the sequential bar """
return
[
part
.
strip
()
for
part
in
self
.
q
(
css
=
'.path'
)
.
text
[
0
]
.
split
(
'>'
)]
def
bookmark_button_visible
(
self
):
""" Check if bookmark button is visible """
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.bookmark-button'
)
.
visible
,
"Bookmark button visible"
)
.
fulfill
()
return
True
@property
def
bookmark_button_state
(
self
):
""" Return `bookmarked` if button is in bookmarked state else '' """
return
'bookmarked'
if
self
.
q
(
css
=
'.bookmark-button.bookmarked'
)
.
present
else
''
@property
def
bookmark_icon_visible
(
self
):
""" Check if bookmark icon is visible on active sequence nav item """
return
self
.
q
(
css
=
'.active .bookmark-icon'
)
.
visible
def
click_bookmark_unit_button
(
self
):
""" Bookmark a unit by clicking on Bookmark button """
previous_state
=
self
.
bookmark_button_state
self
.
q
(
css
=
'.bookmark-button'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
bookmark_button_state
!=
previous_state
,
"Bookmark button toggled"
)
.
fulfill
()
class
CoursewareSequentialTabPage
(
CoursePage
):
class
CoursewareSequentialTabPage
(
CoursePage
):
"""
"""
...
...
common/test/acceptance/tests/lms/test_bookmarks.py
View file @
3cbbb8f3
...
@@ -2,17 +2,15 @@
...
@@ -2,17 +2,15 @@
"""
"""
End-to-end tests for the courseware unit bookmarks.
End-to-end tests for the courseware unit bookmarks.
"""
"""
import
json
import
requests
from
...pages.studio.auto_auth
import
AutoAuthPage
from
...pages.studio.auto_auth
import
AutoAuthPage
from
...pages.lms.bookmarks
import
BookmarksPage
from
...pages.lms.bookmarks
import
BookmarksPage
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.course_nav
import
CourseNavPage
from
...pages.studio.overview
import
CourseOutlinePage
from
...pages.studio.overview
import
CourseOutlinePage
from
...pages.common.logout
import
LogoutPage
from
...pages.common.logout
import
LogoutPage
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
...fixtures
import
LMS_BASE_URL
from
..helpers
import
EventsTestMixin
,
UniqueCourseTest
,
is_404_page
from
..helpers
import
EventsTestMixin
,
UniqueCourseTest
,
is_404_page
...
@@ -22,30 +20,29 @@ class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest):
...
@@ -22,30 +20,29 @@ class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest):
"""
"""
USERNAME
=
"STUDENT"
USERNAME
=
"STUDENT"
EMAIL
=
"student@example.com"
EMAIL
=
"student@example.com"
COURSE_TREE_INFO
=
[
[
'TestSection1'
,
'TestSubsection1'
,
'TestProblem1'
],
[
'TestSection2'
,
'TestSubsection2'
,
'TestProblem2'
]
]
def
create_course_fixture
(
self
):
def
create_course_fixture
(
self
,
num_chapters
):
""" Create course fixture """
"""
Create course fixture
Arguments:
num_chapters: number of chapters to create
"""
self
.
course_fixture
=
CourseFixture
(
# pylint: disable=attribute-defined-outside-init
self
.
course_fixture
=
CourseFixture
(
# pylint: disable=attribute-defined-outside-init
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
)
self
.
course_fixture
.
add_children
(
xblocks
=
[]
XBlockFixtureDesc
(
'chapter'
,
self
.
COURSE_TREE_INFO
[
0
][
0
])
.
add_children
(
for
index
in
range
(
num_chapters
):
XBlockFixtureDesc
(
'sequential'
,
self
.
COURSE_TREE_INFO
[
0
][
1
])
.
add_children
(
xblocks
+=
[
XBlockFixtureDesc
(
'problem'
,
self
.
COURSE_TREE_INFO
[
0
][
2
])
XBlockFixtureDesc
(
'chapter'
,
'TestSection{}'
.
format
(
index
))
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'TestSubsection{}'
.
format
(
index
))
.
add_children
(
XBlockFixtureDesc
(
'vertical'
,
'TestVertical{}'
.
format
(
index
))
)
)
)
),
]
XBlockFixtureDesc
(
'chapter'
,
self
.
COURSE_TREE_INFO
[
1
][
0
])
.
add_children
(
self
.
course_fixture
.
add_children
(
*
xblocks
)
.
install
()
XBlockFixtureDesc
(
'sequential'
,
self
.
COURSE_TREE_INFO
[
1
][
1
])
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
self
.
COURSE_TREE_INFO
[
1
][
2
])
)
)
)
.
install
()
class
BookmarksTest
(
BookmarksTestMixin
):
class
BookmarksTest
(
BookmarksTestMixin
):
...
@@ -66,35 +63,64 @@ class BookmarksTest(BookmarksTestMixin):
...
@@ -66,35 +63,64 @@ class BookmarksTest(BookmarksTestMixin):
self
.
course_info
[
'run'
]
self
.
course_info
[
'run'
]
)
)
self
.
create_course_fixture
()
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
self
.
bookmarks_page
=
BookmarksPage
(
self
.
browser
,
self
.
course_id
)
self
.
course_nav
=
CourseNavPage
(
self
.
browser
)
def
_test_setup
(
self
,
num_chapters
=
2
):
"""
Setup test settings.
Arguments:
num_chapters: number of chapters to create in course
"""
self
.
create_course_fixture
(
num_chapters
)
# Auto-auth register for the course.
# Auto-auth register for the course.
AutoAuthPage
(
self
.
browser
,
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
course_id
=
self
.
course_id
)
.
visit
()
AutoAuthPage
(
self
.
browser
,
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
course_id
=
self
.
course_id
)
.
visit
()
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
self
.
courseware_page
.
visit
()
self
.
courseware_page
.
visit
()
self
.
bookmarks
=
BookmarksPage
(
self
.
browser
,
self
.
course_id
)
# Use auto-auth to retrieve the session for a logged in user
def
_bookmark_unit
(
self
,
index
):
self
.
session
=
requests
.
Session
()
"""
response
=
self
.
session
.
get
(
LMS_BASE_URL
+
"/auto_auth?username=STUDENT&email=student@example.com"
)
Bookmark a unit
self
.
assertTrue
(
response
.
ok
,
"Failed to get session info"
)
Arguments:
index: unit index to bookmark
"""
self
.
course_nav
.
go_to_section
(
'TestSection{}'
.
format
(
index
),
'TestSubsection{}'
.
format
(
index
))
self
.
courseware_page
.
click_bookmark_unit_button
()
def
_bookmark_units
(
self
,
num_units
):
"""
Bookmark first `num_units` units by visiting them
def
_bookmark_unit
(
self
,
course_id
,
usage_id
):
Arguments:
""" Bookmark a single unit """
num_units(int): Number of units to bookmarks
csrftoken
=
self
.
session
.
cookies
[
'csrftoken'
]
"""
headers
=
{
'Content-type'
:
'application/json'
,
"X-CSRFToken"
:
csrftoken
}
for
index
in
range
(
num_units
):
url
=
LMS_BASE_URL
+
"/api/bookmarks/v0/bookmarks/?course_id="
+
course_id
+
'&fields=path'
self
.
_bookmark_unit
(
index
)
data
=
json
.
dumps
({
'usage_id'
:
usage_id
})
def
_breadcrumb
(
self
,
num_units
):
"""
Creates breadcrumbs for the first `num_units`
response
=
self
.
session
.
post
(
url
,
data
=
data
,
headers
=
headers
,
cookies
=
self
.
session
.
cookies
)
Arguments:
response
=
json
.
loads
(
response
.
text
)
num_units(int): Number of units for which we want to create breadcrumbs
self
.
assertTrue
(
response
[
'usage_id'
]
==
usage_id
,
"Failed to bookmark unit"
)
def
_bookmarks_blocks
(
self
,
xblocks
):
Returns:
""" Bookmark all units in a course """
list of breadcrumbs
for
xblock
in
xblocks
:
"""
self
.
_bookmark_unit
(
self
.
course_id
,
usage_id
=
xblock
.
locator
)
breadcrumbs
=
[]
for
index
in
range
(
num_units
):
breadcrumbs
.
append
(
[
'TestSection{}'
.
format
(
index
),
'TestSubsection{}'
.
format
(
index
),
'TestVertical{}'
.
format
(
index
)
]
)
return
breadcrumbs
def
_delete_section
(
self
,
index
):
def
_delete_section
(
self
,
index
):
""" Delete a section at index `index` """
""" Delete a section at index `index` """
...
@@ -119,6 +145,39 @@ class BookmarksTest(BookmarksTestMixin):
...
@@ -119,6 +145,39 @@ class BookmarksTest(BookmarksTestMixin):
self
.
courseware_page
.
visit
()
self
.
courseware_page
.
visit
()
self
.
courseware_page
.
wait_for_page
()
self
.
courseware_page
.
wait_for_page
()
def
_toggle_bookmark_and_verify
(
self
,
bookmark_icon_state
,
bookmark_button_state
,
bookmarked_count
):
"""
Bookmark/Un-Bookmark a unit and then verify
"""
self
.
assertTrue
(
self
.
courseware_page
.
bookmark_button_visible
)
self
.
courseware_page
.
click_bookmark_unit_button
()
self
.
assertEqual
(
self
.
courseware_page
.
bookmark_icon_visible
,
bookmark_icon_state
)
self
.
assertEqual
(
self
.
courseware_page
.
bookmark_button_state
,
bookmark_button_state
)
self
.
bookmarks_page
.
click_bookmarks_button
()
self
.
assertEqual
(
self
.
bookmarks_page
.
count
(),
bookmarked_count
)
def
test_bookmark_button
(
self
):
"""
Scenario: Bookmark unit button toggles correctly
Given that I am a registered user
And I visit my courseware page
For first 2 units
I visit the unit
And I can see the Bookmark button
When I click on Bookmark button
Then unit should be bookmarked
Then I click again on the bookmark button
And I should see a unit un-bookmarked
"""
self
.
_test_setup
()
for
index
in
range
(
2
):
self
.
course_nav
.
go_to_section
(
'TestSection{}'
.
format
(
index
),
'TestSubsection{}'
.
format
(
index
))
self
.
_toggle_bookmark_and_verify
(
True
,
'bookmarked'
,
1
)
self
.
bookmarks_page
.
click_bookmarks_button
(
False
)
self
.
_toggle_bookmark_and_verify
(
False
,
''
,
0
)
def
test_empty_bookmarks_list
(
self
):
def
test_empty_bookmarks_list
(
self
):
"""
"""
Scenario: An empty bookmarks list is shown if there are no bookmarked units.
Scenario: An empty bookmarks list is shown if there are no bookmarked units.
...
@@ -130,15 +189,16 @@ class BookmarksTest(BookmarksTestMixin):
...
@@ -130,15 +189,16 @@ class BookmarksTest(BookmarksTestMixin):
Then I should see an empty bookmarks list
Then I should see an empty bookmarks list
And empty bookmarks list content is correct
And empty bookmarks list content is correct
"""
"""
self
.
assertTrue
(
self
.
bookmarks
.
bookmarks_button_visible
())
self
.
_test_setup
()
self
.
bookmarks
.
click_bookmarks_button
()
self
.
assertTrue
(
self
.
bookmarks_page
.
bookmarks_button_visible
())
self
.
assertEqual
(
self
.
bookmarks
.
results_header_text
(),
'MY BOOKMARKS'
)
self
.
bookmarks_page
.
click_bookmarks_button
()
self
.
assertEqual
(
self
.
bookmarks
.
empty_header_text
(),
'You have not bookmarked any courseware pages yet.'
)
self
.
assertEqual
(
self
.
bookmarks_page
.
results_header_text
(),
'MY BOOKMARKS'
)
self
.
assertEqual
(
self
.
bookmarks_page
.
empty_header_text
(),
'You have not bookmarked any courseware pages yet.'
)
empty_list_text
=
(
"Use bookmarks to help you easily return to courseware pages. To bookmark a page, "
empty_list_text
=
(
"Use bookmarks to help you easily return to courseware pages. To bookmark a page, "
"select Bookmark in the upper right corner of that page. To see a list of all your "
"select Bookmark in the upper right corner of that page. To see a list of all your "
"bookmarks, select Bookmarks in the upper left corner of any courseware page."
)
"bookmarks, select Bookmarks in the upper left corner of any courseware page."
)
self
.
assertEqual
(
self
.
bookmarks
.
empty_list_text
(),
empty_list_text
)
self
.
assertEqual
(
self
.
bookmarks
_page
.
empty_list_text
(),
empty_list_text
)
def
test_bookmarks_list
(
self
):
def
test_bookmarks_list
(
self
):
"""
"""
...
@@ -160,27 +220,30 @@ class BookmarksTest(BookmarksTestMixin):
...
@@ -160,27 +220,30 @@ class BookmarksTest(BookmarksTestMixin):
# discarded by the current version of MySQL we are using due to the
# discarded by the current version of MySQL we are using due to the
# lack of support. Due to which order of bookmarked units will be
# lack of support. Due to which order of bookmarked units will be
# incorrect.
# incorrect.
xblocks
=
self
.
course_fixture
.
get_nested_xblocks
(
category
=
"problem"
)
self
.
_test_setup
(
)
self
.
_bookmark
s_blocks
(
xblocks
)
self
.
_bookmark
_units
(
2
)
self
.
bookmarks
.
click_bookmarks_button
()
self
.
bookmarks
_page
.
click_bookmarks_button
()
self
.
assertTrue
(
self
.
bookmarks
.
results_present
())
self
.
assertTrue
(
self
.
bookmarks
_page
.
results_present
())
self
.
assertEqual
(
self
.
bookmarks
.
results_header_text
(),
'MY BOOKMARKS'
)
self
.
assertEqual
(
self
.
bookmarks
_page
.
results_header_text
(),
'MY BOOKMARKS'
)
self
.
assertEqual
(
self
.
bookmarks
.
count
(),
2
)
self
.
assertEqual
(
self
.
bookmarks
_page
.
count
(),
2
)
bookmarked_breadcrumbs
=
self
.
bookmarks
.
breadcrumbs
()
bookmarked_breadcrumbs
=
self
.
bookmarks
_page
.
breadcrumbs
()
# Verify bookmarked breadcrumbs
# Verify bookmarked breadcrumbs
self
.
assertItemsEqual
(
bookmarked_breadcrumbs
,
self
.
COURSE_TREE_INFO
)
breadcrumbs
=
self
.
_breadcrumb
(
2
)
self
.
assertItemsEqual
(
bookmarked_breadcrumbs
,
breadcrumbs
)
# get usage ids for units
xblocks
=
self
.
course_fixture
.
get_nested_xblocks
(
category
=
"vertical"
)
xblock_usage_ids
=
[
xblock
.
locator
for
xblock
in
xblocks
]
xblock_usage_ids
=
[
xblock
.
locator
for
xblock
in
xblocks
]
# Verify link navigation
# Verify link navigation
for
index
in
range
(
2
):
for
index
in
range
(
2
):
self
.
bookmarks
.
click_bookmar
k
(
index
)
self
.
bookmarks
_page
.
click_bookmarked_bloc
k
(
index
)
self
.
courseware_page
.
wait_for_page
()
self
.
courseware_page
.
wait_for_page
()
self
.
assertTrue
(
self
.
courseware_page
.
active_usage_id
()
in
xblock_usage_ids
)
self
.
assertTrue
(
self
.
courseware_page
.
active_usage_id
()
in
xblock_usage_ids
)
self
.
courseware_page
.
visit
()
.
wait_for_page
()
self
.
courseware_page
.
visit
()
.
wait_for_page
()
self
.
bookmarks
.
click_bookmarks_button
()
self
.
bookmarks
_page
.
click_bookmarks_button
()
def
test_unreachable_bookmark
(
self
):
def
test_unreachable_bookmark
(
self
):
"""
"""
...
@@ -195,13 +258,34 @@ class BookmarksTest(BookmarksTestMixin):
...
@@ -195,13 +258,34 @@ class BookmarksTest(BookmarksTestMixin):
When I click on deleted bookmark
When I click on deleted bookmark
Then I should navigated to 404 page
Then I should navigated to 404 page
"""
"""
self
.
_
bookmarks_blocks
(
self
.
course_fixture
.
get_nested_xblocks
(
category
=
"problem"
)
)
self
.
_
test_setup
(
)
self
.
_bookmark_units
(
2
)
self
.
_delete_section
(
0
)
self
.
_delete_section
(
0
)
self
.
bookmarks
.
click_bookmarks_button
()
self
.
bookmarks
_page
.
click_bookmarks_button
()
self
.
assertTrue
(
self
.
bookmarks
.
results_present
())
self
.
assertTrue
(
self
.
bookmarks
_page
.
results_present
())
self
.
assertEqual
(
self
.
bookmarks
.
count
(),
2
)
self
.
assertEqual
(
self
.
bookmarks
_page
.
count
(),
2
)
self
.
bookmarks
.
click_bookmark
(
0
)
self
.
bookmarks
_page
.
click_bookmarked_block
(
1
)
self
.
assertTrue
(
is_404_page
(
self
.
browser
))
self
.
assertTrue
(
is_404_page
(
self
.
browser
))
def
test_page_size_limit
(
self
):
"""
Scenario: We can get more bookmarks if page size is greater than default page size.
Note:
* Current Bookmarks API page_size value is 10.
* page_size value in bookmarks client side is set to 500.
Given that I am a registered user
And I visit my courseware page
And I have bookmarked all the units available
Then I click on Bookmarks button
And I should see a bookmarked list
And bookmark list contains 11 bookmarked items
"""
self
.
_test_setup
(
11
)
self
.
_bookmark_units
(
11
)
self
.
bookmarks_page
.
click_bookmarks_button
()
self
.
assertTrue
(
self
.
bookmarks_page
.
results_present
())
self
.
assertEqual
(
self
.
bookmarks_page
.
count
(),
11
)
common/test/acceptance/tests/lms/test_lms_courseware.py
View file @
3cbbb8f3
...
@@ -29,6 +29,7 @@ class CoursewareTest(UniqueCourseTest):
...
@@ -29,6 +29,7 @@ class CoursewareTest(UniqueCourseTest):
super
(
CoursewareTest
,
self
)
.
setUp
()
super
(
CoursewareTest
,
self
)
.
setUp
()
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
self
.
course_nav
=
CourseNavPage
(
self
.
browser
)
self
.
course_outline
=
CourseOutlinePage
(
self
.
course_outline
=
CourseOutlinePage
(
self
.
browser
,
self
.
browser
,
...
@@ -38,12 +39,12 @@ class CoursewareTest(UniqueCourseTest):
...
@@ -38,12 +39,12 @@ class CoursewareTest(UniqueCourseTest):
)
)
# Install a course with sections/problems, tabs, updates, and handouts
# Install a course with sections/problems, tabs, updates, and handouts
course_fix
=
CourseFixture
(
self
.
course_fix
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
)
course_fix
.
add_children
(
self
.
course_fix
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section 1'
)
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section 1'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 1'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 1'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Problem 1'
)
XBlockFixtureDesc
(
'problem'
,
'Test Problem 1'
)
...
@@ -67,6 +68,18 @@ class CoursewareTest(UniqueCourseTest):
...
@@ -67,6 +68,18 @@ class CoursewareTest(UniqueCourseTest):
self
.
problem_page
=
ProblemPage
(
self
.
browser
)
self
.
problem_page
=
ProblemPage
(
self
.
browser
)
self
.
assertEqual
(
self
.
problem_page
.
problem_name
,
'TEST PROBLEM 1'
)
self
.
assertEqual
(
self
.
problem_page
.
problem_name
,
'TEST PROBLEM 1'
)
def
_change_problem_release_date_in_studio
(
self
):
"""
"""
self
.
course_outline
.
q
(
css
=
".subsection-header-actions .configure-button"
)
.
first
.
click
()
self
.
course_outline
.
q
(
css
=
"#start_date"
)
.
fill
(
"01/01/2030"
)
self
.
course_outline
.
q
(
css
=
".action-save"
)
.
first
.
click
()
def
_create_breadcrumb
(
self
,
index
):
""" Create breadcrumb """
return
[
'Test Section {}'
.
format
(
index
),
'Test Subsection {}'
.
format
(
index
),
'Test Problem {}'
.
format
(
index
)]
def
_auto_auth
(
self
,
username
,
email
,
staff
):
def
_auto_auth
(
self
,
username
,
email
,
staff
):
"""
"""
Logout and login with given credentials.
Logout and login with given credentials.
...
@@ -92,6 +105,9 @@ class CoursewareTest(UniqueCourseTest):
...
@@ -92,6 +105,9 @@ class CoursewareTest(UniqueCourseTest):
# Set release date for subsection in future.
# Set release date for subsection in future.
self
.
course_outline
.
change_problem_release_date_in_studio
()
self
.
course_outline
.
change_problem_release_date_in_studio
()
# Wait for 2 seconds to save new date.
time
.
sleep
(
2
)
# Logout and login as a student.
# Logout and login as a student.
LogoutPage
(
self
.
browser
)
.
visit
()
LogoutPage
(
self
.
browser
)
.
visit
()
self
.
_auto_auth
(
self
.
USERNAME
,
self
.
EMAIL
,
False
)
self
.
_auto_auth
(
self
.
USERNAME
,
self
.
EMAIL
,
False
)
...
@@ -246,6 +262,23 @@ class ProctoredExamTest(UniqueCourseTest):
...
@@ -246,6 +262,23 @@ class ProctoredExamTest(UniqueCourseTest):
self
.
courseware_page
.
start_timed_exam
()
self
.
courseware_page
.
start_timed_exam
()
self
.
assertTrue
(
self
.
courseware_page
.
is_timer_bar_present
)
self
.
assertTrue
(
self
.
courseware_page
.
is_timer_bar_present
)
def
test_course_tree_breadcrumb
(
self
):
"""
Scenario: Correct course tree breadcrumb is shown.
Given that I am a registered user
And I visit my courseware page
Then I should see correct course tree breadcrumb
"""
self
.
courseware_page
.
visit
()
xblocks
=
self
.
course_fix
.
get_nested_xblocks
(
category
=
"problem"
)
for
index
in
range
(
1
,
len
(
xblocks
)
+
1
):
self
.
course_nav
.
go_to_section
(
'Test Section {}'
.
format
(
index
),
'Test Subsection {}'
.
format
(
index
))
courseware_page_breadcrumb
=
self
.
courseware_page
.
breadcrumb
expected_breadcrumb
=
self
.
_create_breadcrumb
(
index
)
self
.
assertEqual
(
courseware_page_breadcrumb
,
expected_breadcrumb
)
def
test_time_allotted_field_is_not_visible_with_none_exam
(
self
):
def
test_time_allotted_field_is_not_visible_with_none_exam
(
self
):
"""
"""
Given that I am a staff member
Given that I am a staff member
...
...
lms/djangoapps/bookmarks/tests/test_views.py
View file @
3cbbb8f3
...
@@ -145,8 +145,8 @@ class BookmarksViewTestsMixin(ModuleStoreTestCase):
...
@@ -145,8 +145,8 @@ class BookmarksViewTestsMixin(ModuleStoreTestCase):
class
BookmarksListViewTests
(
BookmarksViewTestsMixin
):
class
BookmarksListViewTests
(
BookmarksViewTestsMixin
):
"""
"""
This contains the tests for GET & POST methods of bookmark.views.BookmarksListView class
This contains the tests for GET & POST methods of bookmark.views.BookmarksListView class
GET /api/bookmarks/v
0
/bookmarks/?course_id={course_id1}
GET /api/bookmarks/v
1
/bookmarks/?course_id={course_id1}
POST /api/bookmarks/v
0
/bookmarks
POST /api/bookmarks/v
1
/bookmarks
"""
"""
@ddt.data
(
@ddt.data
(
(
'course_id={}'
,
False
),
(
'course_id={}'
,
False
),
...
...
lms/djangoapps/courseware/module_render.py
View file @
3cbbb8f3
...
@@ -42,6 +42,7 @@ from courseware.entrance_exams import (
...
@@ -42,6 +42,7 @@ from courseware.entrance_exams import (
)
)
from
edxmako.shortcuts
import
render_to_string
from
edxmako.shortcuts
import
render_to_string
from
eventtracking
import
tracker
from
eventtracking
import
tracker
from
lms.djangoapps.bookmarks.services
import
BookmarksService
from
lms.djangoapps.lms_xblock.field_data
import
LmsFieldData
from
lms.djangoapps.lms_xblock.field_data
import
LmsFieldData
from
lms.djangoapps.lms_xblock.runtime
import
LmsModuleSystem
,
unquote_slashes
,
quote_slashes
from
lms.djangoapps.lms_xblock.runtime
import
LmsModuleSystem
,
unquote_slashes
,
quote_slashes
from
lms.djangoapps.lms_xblock.models
import
XBlockAsidesConfig
from
lms.djangoapps.lms_xblock.models
import
XBlockAsidesConfig
...
@@ -715,6 +716,8 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
...
@@ -715,6 +716,8 @@ def get_module_system_for_user(user, student_data, # TODO # pylint: disable=to
"reverification"
:
ReverificationService
(),
"reverification"
:
ReverificationService
(),
'proctoring'
:
ProctoringService
(),
'proctoring'
:
ProctoringService
(),
'credit'
:
CreditService
(),
'credit'
:
CreditService
(),
'reverification'
:
ReverificationService
(),
'bookmarks'
:
BookmarksService
(
user
=
user
),
},
},
get_user_role
=
lambda
:
get_user_role
(
user
,
course_id
),
get_user_role
=
lambda
:
get_user_role
(
user
,
course_id
),
descriptor_runtime
=
descriptor
.
_runtime
,
# pylint: disable=protected-access
descriptor_runtime
=
descriptor
.
_runtime
,
# pylint: disable=protected-access
...
...
lms/djangoapps/courseware/tests/test_module_render.py
View file @
3cbbb8f3
...
@@ -73,6 +73,7 @@ TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
...
@@ -73,6 +73,7 @@ TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
@XBlock.needs
(
"i18n"
)
@XBlock.needs
(
"i18n"
)
@XBlock.needs
(
"fs"
)
@XBlock.needs
(
"fs"
)
@XBlock.needs
(
"user"
)
@XBlock.needs
(
"user"
)
@XBlock.needs
(
"bookmarks"
)
class
PureXBlock
(
XBlock
):
class
PureXBlock
(
XBlock
):
"""
"""
Pure XBlock to use in tests.
Pure XBlock to use in tests.
...
@@ -1232,6 +1233,7 @@ class ViewInStudioTest(ModuleStoreTestCase):
...
@@ -1232,6 +1233,7 @@ class ViewInStudioTest(ModuleStoreTestCase):
self
.
request
.
user
=
self
.
staff_user
self
.
request
.
user
=
self
.
staff_user
self
.
request
.
session
=
{}
self
.
request
.
session
=
{}
self
.
module
=
None
self
.
module
=
None
self
.
default_context
=
{
'bookmarked'
:
False
,
'username'
:
self
.
user
.
username
}
def
_get_module
(
self
,
course_id
,
descriptor
,
location
):
def
_get_module
(
self
,
course_id
,
descriptor
,
location
):
"""
"""
...
@@ -1290,14 +1292,14 @@ class MongoViewInStudioTest(ViewInStudioTest):
...
@@ -1290,14 +1292,14 @@ class MongoViewInStudioTest(ViewInStudioTest):
def
test_view_in_studio_link_studio_course
(
self
):
def
test_view_in_studio_link_studio_course
(
self
):
"""Regular Studio courses should see 'View in Studio' links."""
"""Regular Studio courses should see 'View in Studio' links."""
self
.
setup_mongo_course
()
self
.
setup_mongo_course
()
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
,
context
=
self
.
default_context
)
self
.
assertIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
self
.
assertIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
def
test_view_in_studio_link_only_in_top_level_vertical
(
self
):
def
test_view_in_studio_link_only_in_top_level_vertical
(
self
):
"""Regular Studio courses should not see 'View in Studio' for child verticals of verticals."""
"""Regular Studio courses should not see 'View in Studio' for child verticals of verticals."""
self
.
setup_mongo_course
()
self
.
setup_mongo_course
()
# Render the parent vertical, then check that there is only a single "View Unit in Studio" link.
# Render the parent vertical, then check that there is only a single "View Unit in Studio" link.
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
,
context
=
self
.
default_context
)
# The single "View Unit in Studio" link should appear before the first xmodule vertical definition.
# The single "View Unit in Studio" link should appear before the first xmodule vertical definition.
parts
=
result_fragment
.
content
.
split
(
'data-block-type="vertical"'
)
parts
=
result_fragment
.
content
.
split
(
'data-block-type="vertical"'
)
self
.
assertEqual
(
3
,
len
(
parts
),
"Did not find two vertical blocks"
)
self
.
assertEqual
(
3
,
len
(
parts
),
"Did not find two vertical blocks"
)
...
@@ -1308,7 +1310,7 @@ class MongoViewInStudioTest(ViewInStudioTest):
...
@@ -1308,7 +1310,7 @@ class MongoViewInStudioTest(ViewInStudioTest):
def
test_view_in_studio_link_xml_authored
(
self
):
def
test_view_in_studio_link_xml_authored
(
self
):
"""Courses that change 'course_edit_method' setting can hide 'View in Studio' links."""
"""Courses that change 'course_edit_method' setting can hide 'View in Studio' links."""
self
.
setup_mongo_course
(
course_edit_method
=
'XML'
)
self
.
setup_mongo_course
(
course_edit_method
=
'XML'
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
,
context
=
self
.
default_context
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
...
@@ -1321,19 +1323,19 @@ class MixedViewInStudioTest(ViewInStudioTest):
...
@@ -1321,19 +1323,19 @@ class MixedViewInStudioTest(ViewInStudioTest):
def
test_view_in_studio_link_mongo_backed
(
self
):
def
test_view_in_studio_link_mongo_backed
(
self
):
"""Mixed mongo courses that are mongo backed should see 'View in Studio' links."""
"""Mixed mongo courses that are mongo backed should see 'View in Studio' links."""
self
.
setup_mongo_course
()
self
.
setup_mongo_course
()
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
,
context
=
self
.
default_context
)
self
.
assertIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
self
.
assertIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
def
test_view_in_studio_link_xml_authored
(
self
):
def
test_view_in_studio_link_xml_authored
(
self
):
"""Courses that change 'course_edit_method' setting can hide 'View in Studio' links."""
"""Courses that change 'course_edit_method' setting can hide 'View in Studio' links."""
self
.
setup_mongo_course
(
course_edit_method
=
'XML'
)
self
.
setup_mongo_course
(
course_edit_method
=
'XML'
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
,
context
=
self
.
default_context
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
def
test_view_in_studio_link_xml_backed
(
self
):
def
test_view_in_studio_link_xml_backed
(
self
):
"""Course in XML only modulestore should not see 'View in Studio' links."""
"""Course in XML only modulestore should not see 'View in Studio' links."""
self
.
setup_xml_course
()
self
.
setup_xml_course
()
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
)
result_fragment
=
self
.
module
.
render
(
STUDENT_VIEW
,
context
=
self
.
default_context
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
self
.
assertNotIn
(
'View Unit in Studio'
,
result_fragment
.
content
)
...
@@ -1826,7 +1828,7 @@ class LMSXBlockServiceBindingTest(ModuleStoreTestCase):
...
@@ -1826,7 +1828,7 @@ class LMSXBlockServiceBindingTest(ModuleStoreTestCase):
self
.
request_token
=
Mock
()
self
.
request_token
=
Mock
()
@XBlock.register_temp_plugin
(
PureXBlock
,
identifier
=
'pure'
)
@XBlock.register_temp_plugin
(
PureXBlock
,
identifier
=
'pure'
)
@ddt.data
(
"user"
,
"i18n"
,
"fs"
,
"field-data"
)
@ddt.data
(
"user"
,
"i18n"
,
"fs"
,
"field-data"
,
"bookmarks"
)
def
test_expected_services_exist
(
self
,
expected_service
):
def
test_expected_services_exist
(
self
,
expected_service
):
"""
"""
Tests that the 'user', 'i18n', and 'fs' services are provided by the LMS runtime.
Tests that the 'user', 'i18n', and 'fs' services are provided by the LMS runtime.
...
...
lms/djangoapps/courseware/tests/test_split_module.py
View file @
3cbbb8f3
...
@@ -119,7 +119,7 @@ class SplitTestBase(ModuleStoreTestCase):
...
@@ -119,7 +119,7 @@ class SplitTestBase(ModuleStoreTestCase):
content
=
resp
.
content
content
=
resp
.
content
# Assert we see the proper icon in the top display
# Assert we see the proper icon in the top display
self
.
assertIn
(
'<a class="{} inactive progress-0"'
.
format
(
self
.
ICON_CLASSES
[
user_tag
]),
content
)
self
.
assertIn
(
'<a class="{} inactive progress-0
nav-item
"'
.
format
(
self
.
ICON_CLASSES
[
user_tag
]),
content
)
# And proper tooltips
# And proper tooltips
for
tooltip
in
self
.
TOOLTIPS
[
user_tag
]:
for
tooltip
in
self
.
TOOLTIPS
[
user_tag
]:
self
.
assertIn
(
tooltip
,
content
)
self
.
assertIn
(
tooltip
,
content
)
...
...
lms/djangoapps/courseware/views.py
View file @
3cbbb8f3
...
@@ -403,6 +403,8 @@ def _index_bulk_op(request, course_key, chapter, section, position):
...
@@ -403,6 +403,8 @@ def _index_bulk_op(request, course_key, chapter, section, position):
if
survey
.
utils
.
must_answer_survey
(
course
,
user
):
if
survey
.
utils
.
must_answer_survey
(
course
,
user
):
return
redirect
(
reverse
(
'course_survey'
,
args
=
[
unicode
(
course
.
id
)]))
return
redirect
(
reverse
(
'course_survey'
,
args
=
[
unicode
(
course
.
id
)]))
bookmarks_api_url
=
reverse
(
'bookmarks'
)
try
:
try
:
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
course_key
,
user
,
course
,
depth
=
2
)
course_key
,
user
,
course
,
depth
=
2
)
...
@@ -428,7 +430,7 @@ def _index_bulk_op(request, course_key, chapter, section, position):
...
@@ -428,7 +430,7 @@ def _index_bulk_op(request, course_key, chapter, section, position):
'studio_url'
:
studio_url
,
'studio_url'
:
studio_url
,
'masquerade'
:
masquerade
,
'masquerade'
:
masquerade
,
'xqa_server'
:
settings
.
FEATURES
.
get
(
'XQA_SERVER'
,
"http://your_xqa_server.com"
),
'xqa_server'
:
settings
.
FEATURES
.
get
(
'XQA_SERVER'
,
"http://your_xqa_server.com"
),
'
reverifications'
:
fetch_reverify_banner_info
(
request
,
course_key
)
,
'
bookmarks_api_url'
:
bookmarks_api_url
,
'language_preference'
:
language_preference
,
'language_preference'
:
language_preference
,
}
}
...
...
lms/static/js/bookmarks/collections/bookmarks.js
View file @
3cbbb8f3
...
@@ -5,8 +5,11 @@
...
@@ -5,8 +5,11 @@
'use strict'
;
'use strict'
;
return
Backbone
.
Collection
.
extend
({
return
Backbone
.
Collection
.
extend
({
model
:
BookmarkModel
,
model
:
BookmarkModel
,
url
:
'/api/bookmarks/v0/bookmarks/'
,
url
:
function
()
{
return
$
(
".courseware-bookmarks-button"
).
data
(
'bookmarksApiUrl'
);
},
parse
:
function
(
response
)
{
parse
:
function
(
response
)
{
return
response
.
results
;
return
response
.
results
;
...
...
lms/static/js/bookmarks/main.js
View file @
3cbbb8f3
RequireJS
.
require
([
RequireJS
.
require
([
'js/bookmarks/views/bookmarks_button'
'js/bookmarks/views/bookmarks_
list_
button'
],
function
(
BookmarksButton
)
{
],
function
(
Bookmarks
List
Button
)
{
'use strict'
;
'use strict'
;
return
new
BookmarksButton
();
return
new
Bookmarks
List
Button
();
});
});
lms/static/js/bookmarks/views/bookmark_button.js
0 → 100644
View file @
3cbbb8f3
;(
function
(
define
,
undefined
)
{
'use strict'
;
define
([
'gettext'
,
'jquery'
,
'underscore'
,
'backbone'
,
'js/views/message'
],
function
(
gettext
,
$
,
_
,
Backbone
,
MessageView
)
{
return
Backbone
.
View
.
extend
({
errorIcon
:
'<i class="fa fa-fw fa-exclamation-triangle message-error" aria-hidden="true"></i>'
,
errorMessage
:
gettext
(
'An error has occurred. Please try again.'
),
srAddBookmarkText
:
gettext
(
'Click to add'
),
srRemoveBookmarkText
:
gettext
(
'Click to remove'
),
events
:
{
'click'
:
'toggleBookmark'
},
initialize
:
function
(
options
)
{
this
.
apiUrl
=
options
.
apiUrl
;
this
.
bookmarkId
=
options
.
bookmarkId
;
this
.
bookmarked
=
options
.
bookmarked
;
this
.
usageId
=
options
.
usageId
;
this
.
setBookmarkState
(
this
.
bookmarked
);
},
toggleBookmark
:
function
(
event
)
{
event
.
preventDefault
();
if
(
this
.
$el
.
hasClass
(
'bookmarked'
))
{
this
.
removeBookmark
();
}
else
{
this
.
addBookmark
();
}
},
addBookmark
:
function
()
{
var
view
=
this
;
$
.
ajax
({
data
:
{
usage_id
:
view
.
usageId
},
type
:
"POST"
,
url
:
view
.
apiUrl
,
dataType
:
'json'
,
success
:
function
()
{
view
.
$el
.
trigger
(
'bookmark:add'
);
view
.
setBookmarkState
(
true
);
},
error
:
function
()
{
view
.
showError
();
}
});
},
removeBookmark
:
function
()
{
var
view
=
this
;
var
deleteUrl
=
view
.
apiUrl
+
view
.
bookmarkId
+
'/'
;
$
.
ajax
({
type
:
"DELETE"
,
url
:
deleteUrl
,
success
:
function
()
{
view
.
$el
.
trigger
(
'bookmark:remove'
);
view
.
setBookmarkState
(
false
);
},
error
:
function
()
{
view
.
showError
();
}
});
},
setBookmarkState
:
function
(
bookmarked
)
{
if
(
bookmarked
)
{
this
.
$el
.
addClass
(
'bookmarked'
);
this
.
$el
.
attr
(
'aria-pressed'
,
'true'
);
this
.
$el
.
find
(
'.bookmark-sr'
).
text
(
this
.
srRemoveBookmarkText
);
}
else
{
this
.
$el
.
removeClass
(
'bookmarked'
);
this
.
$el
.
attr
(
'aria-pressed'
,
'false'
);
this
.
$el
.
find
(
'.bookmark-sr'
).
text
(
this
.
srAddBookmarkText
);
}
},
showError
:
function
()
{
if
(
!
this
.
messageView
)
{
this
.
messageView
=
new
MessageView
({
el
:
$
(
'.coursewide-message-banner'
),
templateId
:
'#message_banner-tpl'
});
}
this
.
messageView
.
showMessage
(
this
.
errorMessage
,
this
.
errorIcon
);
}
});
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/static/js/bookmarks/views/bookmarks_list.js
View file @
3cbbb8f3
...
@@ -16,6 +16,8 @@
...
@@ -16,6 +16,8 @@
errorMessage
:
gettext
(
'An error has occurred. Please try again.'
),
errorMessage
:
gettext
(
'An error has occurred. Please try again.'
),
loadingMessage
:
gettext
(
'Loading'
),
loadingMessage
:
gettext
(
'Loading'
),
PAGE_SIZE
:
500
,
events
:
{
events
:
{
'click .bookmarks-results-list-item'
:
'visitBookmark'
'click .bookmarks-results-list-item'
:
'visitBookmark'
},
},
...
@@ -48,7 +50,7 @@
...
@@ -48,7 +50,7 @@
this
.
collection
.
fetch
({
this
.
collection
.
fetch
({
reset
:
true
,
reset
:
true
,
data
:
{
course_id
:
this
.
courseId
,
fields
:
'display_name,path'
}
data
:
{
course_id
:
this
.
courseId
,
page_size
:
this
.
PAGE_SIZE
,
fields
:
'display_name,path'
}
}).
done
(
function
()
{
}).
done
(
function
()
{
view
.
hideLoadingMessage
();
view
.
hideLoadingMessage
();
view
.
render
();
view
.
render
();
...
...
lms/static/js/bookmarks/views/bookmarks_button.js
→
lms/static/js/bookmarks/views/bookmarks_
list_
button.js
View file @
3cbbb8f3
...
@@ -16,8 +16,6 @@
...
@@ -16,8 +16,6 @@
},
},
initialize
:
function
()
{
initialize
:
function
()
{
this
.
template
=
_
.
template
(
$
(
'#bookmarks_button-tpl'
).
text
());
this
.
bookmarksListView
=
new
BookmarksListView
({
this
.
bookmarksListView
=
new
BookmarksListView
({
collection
:
new
BookmarksCollection
(),
collection
:
new
BookmarksCollection
(),
loadingMessageView
:
new
MessageView
({
el
:
$
(
this
.
loadingMessageElement
)}),
loadingMessageView
:
new
MessageView
({
el
:
$
(
this
.
loadingMessageElement
)}),
...
...
lms/static/js/fixtures/bookmarks/bookmark_button.html
0 → 100644
View file @
3cbbb8f3
<div
class=
"coursewide-message-banner"
aria-live=
"polite"
></div>
<div
class=
"xblock xblock-student_view xblock-student_view-vertical xblock-initialized"
>
<div
class=
"bookmark-button-wrapper"
>
<button
class=
"btn bookmark-button"
aria-pressed=
"false"
data-bookmark-id=
"bilbo,usage_1"
>
<span
class=
"sr bookmark-sr"
></span>
Bookmark
</button>
</div>
</div>
lms/static/js/fixtures/bookmarks/bookmarks.html
View file @
3cbbb8f3
<div
class=
"courseware-bookmarks-button"
>
<div
class=
"courseware-bookmarks-button"
data-bookmarks-api-url=
"/api/bookmarks/v1/bookmarks/"
>
<button
type=
"button"
class=
"bookmarks-list-button is-inactive"
aria-pressed=
"false"
>
<button
type=
"button"
class=
"bookmarks-list-button is-inactive"
aria-pressed=
"false"
>
Bookmarks
Bookmarks
</button>
</button>
...
...
lms/static/js/spec/bookmarks/bookmark_button_view_spec.js
0 → 100644
View file @
3cbbb8f3
define
([
'backbone'
,
'jquery'
,
'underscore'
,
'js/common_helpers/ajax_helpers'
,
'js/common_helpers/template_helpers'
,
'js/bookmarks/views/bookmark_button'
],
function
(
Backbone
,
$
,
_
,
AjaxHelpers
,
TemplateHelpers
,
BookmarkButtonView
)
{
'use strict'
;
describe
(
"bookmarks.button"
,
function
()
{
var
API_URL
=
'bookmarks/api/v1/bookmarks/'
;
beforeEach
(
function
()
{
loadFixtures
(
'js/fixtures/bookmarks/bookmark_button.html'
);
TemplateHelpers
.
installTemplates
(
[
'templates/fields/message_banner'
]
);
});
var
createBookmarkButtonView
=
function
(
isBookmarked
)
{
return
new
BookmarkButtonView
({
el
:
'.bookmark-button'
,
bookmarked
:
isBookmarked
,
bookmarkId
:
'bilbo,usage_1'
,
usageId
:
'usage_1'
,
apiUrl
:
API_URL
});
};
var
verifyBookmarkButtonState
=
function
(
view
,
bookmarked
)
{
if
(
bookmarked
)
{
expect
(
view
.
$el
).
toHaveAttr
(
'aria-pressed'
,
'true'
);
expect
(
view
.
$el
).
toHaveClass
(
'bookmarked'
);
expect
(
view
.
$el
.
find
(
'.bookmark-sr'
).
text
()).
toBe
(
'Click to remove'
);
}
else
{
expect
(
view
.
$el
).
toHaveAttr
(
'aria-pressed'
,
'false'
);
expect
(
view
.
$el
).
not
.
toHaveClass
(
'bookmarked'
);
expect
(
view
.
$el
.
find
(
'.bookmark-sr'
).
text
()).
toBe
(
'Click to add'
);
}
expect
(
view
.
$el
.
data
(
'bookmarkId'
)).
toBe
(
'bilbo,usage_1'
);
};
it
(
"rendered correctly "
,
function
()
{
var
view
=
createBookmarkButtonView
(
false
);
verifyBookmarkButtonState
(
view
,
false
);
// with bookmarked true
view
=
createBookmarkButtonView
(
true
);
verifyBookmarkButtonState
(
view
,
true
);
});
it
(
"bookmark/un-bookmark the block correctly"
,
function
()
{
var
addBookmarkedData
=
{
bookmarked
:
true
,
handler
:
'removeBookmark'
,
event
:
'bookmark:remove'
,
method
:
'DELETE'
,
url
:
API_URL
+
'bilbo,usage_1/'
,
body
:
null
};
var
removeBookmarkData
=
{
bookmarked
:
false
,
handler
:
'addBookmark'
,
event
:
'bookmark:add'
,
method
:
'POST'
,
url
:
API_URL
,
body
:
'usage_id=usage_1'
};
var
requests
=
AjaxHelpers
.
requests
(
this
);
_
.
each
([[
addBookmarkedData
,
removeBookmarkData
],
[
removeBookmarkData
,
addBookmarkedData
]],
function
(
actionsData
)
{
var
firstActionData
=
actionsData
[
0
];
var
secondActionData
=
actionsData
[
1
];
var
bookmarkButtonView
=
createBookmarkButtonView
(
firstActionData
.
bookmarked
);
verifyBookmarkButtonState
(
bookmarkButtonView
,
firstActionData
.
bookmarked
);
spyOn
(
bookmarkButtonView
,
firstActionData
.
handler
).
andCallThrough
();
spyOnEvent
(
bookmarkButtonView
.
$el
,
firstActionData
.
event
);
bookmarkButtonView
.
$el
.
click
();
AjaxHelpers
.
expectRequest
(
requests
,
firstActionData
.
method
,
firstActionData
.
url
,
firstActionData
.
body
);
expect
(
bookmarkButtonView
[
firstActionData
.
handler
]).
toHaveBeenCalled
();
AjaxHelpers
.
respondWithJson
(
requests
,
{});
expect
(
firstActionData
.
event
).
toHaveBeenTriggeredOn
(
bookmarkButtonView
.
$el
);
bookmarkButtonView
[
firstActionData
.
handler
].
reset
();
verifyBookmarkButtonState
(
bookmarkButtonView
,
secondActionData
.
bookmarked
);
spyOn
(
bookmarkButtonView
,
secondActionData
.
handler
).
andCallThrough
();
spyOnEvent
(
bookmarkButtonView
.
$el
,
secondActionData
.
event
);
bookmarkButtonView
.
$el
.
click
();
AjaxHelpers
.
expectRequest
(
requests
,
secondActionData
.
method
,
secondActionData
.
url
,
secondActionData
.
body
);
expect
(
bookmarkButtonView
[
secondActionData
.
handler
]).
toHaveBeenCalled
();
AjaxHelpers
.
respondWithJson
(
requests
,
{});
expect
(
secondActionData
.
event
).
toHaveBeenTriggeredOn
(
bookmarkButtonView
.
$el
);
verifyBookmarkButtonState
(
bookmarkButtonView
,
firstActionData
.
bookmarked
);
});
});
it
(
"shows an error message for HTTP 500"
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
$messageBanner
=
$
(
'.coursewide-message-banner'
),
bookmarkButtonView
=
createBookmarkButtonView
(
false
);
bookmarkButtonView
.
$el
.
click
();
AjaxHelpers
.
respondWithError
(
requests
);
expect
(
$messageBanner
.
text
().
trim
()).
toBe
(
bookmarkButtonView
.
errorMessage
);
// For bookmarked button.
bookmarkButtonView
=
createBookmarkButtonView
(
true
);
bookmarkButtonView
.
$el
.
click
();
AjaxHelpers
.
respondWithError
(
requests
);
expect
(
$messageBanner
.
text
().
trim
()).
toBe
(
bookmarkButtonView
.
errorMessage
);
});
});
});
lms/static/js/spec/bookmarks/bookmarks_spec.js
→
lms/static/js/spec/bookmarks/bookmarks_
list_view_
spec.js
View file @
3cbbb8f3
define
([
'backbone'
,
'jquery'
,
'underscore'
,
'js/common_helpers/ajax_helpers'
,
'js/common_helpers/template_helpers'
,
define
([
'backbone'
,
'jquery'
,
'underscore'
,
'js/common_helpers/ajax_helpers'
,
'js/common_helpers/template_helpers'
,
'js/bookmarks/views/bookmarks_button'
'js/bookmarks/views/bookmarks_
list_
button'
],
],
function
(
Backbone
,
$
,
_
,
AjaxHelpers
,
TemplateHelpers
,
BookmarksButtonView
)
{
function
(
Backbone
,
$
,
_
,
AjaxHelpers
,
TemplateHelpers
,
Bookmarks
List
ButtonView
)
{
'use strict'
;
'use strict'
;
describe
(
"lms.courseware.bookmarks"
,
function
()
{
describe
(
"lms.courseware.bookmarks"
,
function
()
{
var
bookmarksButtonView
;
var
bookmarksButtonView
;
var
BOOKMARKS_API_URL
=
'/api/bookmarks/v
0
/bookmarks/'
;
var
BOOKMARKS_API_URL
=
'/api/bookmarks/v
1
/bookmarks/'
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
loadFixtures
(
'js/fixtures/bookmarks/bookmarks.html'
);
loadFixtures
(
'js/fixtures/bookmarks/bookmarks.html'
);
...
@@ -18,7 +18,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
...
@@ -18,7 +18,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
]
]
);
);
bookmarksButtonView
=
new
BookmarksButtonView
();
bookmarksButtonView
=
new
Bookmarks
List
ButtonView
();
this
.
addMatchers
({
this
.
addMatchers
({
toHaveBeenCalledWithUrl
:
function
(
expectedUrl
)
{
toHaveBeenCalledWithUrl
:
function
(
expectedUrl
)
{
...
@@ -124,7 +124,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
...
@@ -124,7 +124,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
it
(
"has rendered bookmarked list correctly"
,
function
()
{
it
(
"has rendered bookmarked list correctly"
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
var
requests
=
AjaxHelpers
.
requests
(
this
);
var
url
=
BOOKMARKS_API_URL
+
'?course_id=COURSE_ID&fields=display_name%2Cpath'
;
var
url
=
BOOKMARKS_API_URL
+
'?course_id=COURSE_ID&
page_size=500&
fields=display_name%2Cpath'
;
var
expectedData
=
createBookmarksData
(
3
);
var
expectedData
=
createBookmarksData
(
3
);
spyOn
(
bookmarksButtonView
.
bookmarksListView
,
'courseId'
).
andReturn
(
'COURSE_ID'
);
spyOn
(
bookmarksButtonView
.
bookmarksListView
,
'courseId'
).
andReturn
(
'COURSE_ID'
);
...
...
lms/static/js/spec/main.js
View file @
3cbbb8f3
...
@@ -87,6 +87,13 @@
...
@@ -87,6 +87,13 @@
// Discussion classes loaded explicitly until they are converted to use RequireJS
// Discussion classes loaded explicitly until they are converted to use RequireJS
'DiscussionModuleView'
:
'xmodule_js/common_static/coffee/src/discussion/discussion_module_view'
,
'DiscussionModuleView'
:
'xmodule_js/common_static/coffee/src/discussion/discussion_module_view'
,
'js/bookmarks/collections/bookmarks'
:
'js/bookmarks/collections/bookmarks'
,
'js/bookmarks/models/bookmark'
:
'js/bookmarks/models/bookmark'
,
'js/bookmarks/views/bookmarks_list_button'
:
'js/bookmarks/views/bookmarks_list_button'
,
'js/bookmarks/views/bookmarks_list'
:
'js/bookmarks/views/bookmarks_list'
,
'js/bookmarks/views/bookmark_button'
:
'js/bookmarks/views/bookmark_button'
,
'js/views/message'
:
'js/views/message'
,
// edxnotes
// edxnotes
'annotator_1.2.9'
:
'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min'
,
'annotator_1.2.9'
:
'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min'
,
...
@@ -738,7 +745,8 @@
...
@@ -738,7 +745,8 @@
'lms/include/teams/js/spec/views/team_join_spec.js'
'lms/include/teams/js/spec/views/team_join_spec.js'
'lms/include/js/spec/discovery/discovery_spec.js'
,
'lms/include/js/spec/discovery/discovery_spec.js'
,
'lms/include/js/spec/ccx/schedule_spec.js'
,
'lms/include/js/spec/ccx/schedule_spec.js'
,
'lms/include/js/spec/bookmarks/bookmarks_spec.js'
'lms/include/js/spec/bookmarks/bookmarks_list_view_spec.js'
,
'lms/include/js/spec/bookmarks/bookmark_button_view_spec.js'
]);
]);
}).
call
(
this
,
requirejs
,
define
);
}).
call
(
this
,
requirejs
,
define
);
lms/static/sass/course/courseware/_courseware.scss
View file @
3cbbb8f3
...
@@ -403,6 +403,10 @@ div.course-wrapper {
...
@@ -403,6 +403,10 @@ div.course-wrapper {
}
}
}
}
.sequence
.path
{
margin-bottom
:
(
$baseline
/
2
);
}
div
#seq_content
{
div
#seq_content
{
h1
{
h1
{
background
:
none
;
background
:
none
;
...
...
lms/static/sass/views/_bookmarks.scss
View file @
3cbbb8f3
...
@@ -67,8 +67,8 @@
...
@@ -67,8 +67,8 @@
margin-bottom
:
$baseline
;
margin-bottom
:
$baseline
;
&
:hover
{
&
:hover
{
border-color
:
$
link-color
;
border-color
:
$
m-blue
;
color
:
$
link-color
;
color
:
$
m-blue
;
}
}
}
}
...
@@ -87,6 +87,7 @@
...
@@ -87,6 +87,7 @@
position
:
relative
;
position
:
relative
;
top
:
-7px
;
top
:
-7px
;
font-family
:
FontAwesome
;
font-family
:
FontAwesome
;
color
:
$m-blue
;
}
}
.list-item-content
{
.list-item-content
{
...
@@ -124,4 +125,59 @@
...
@@ -124,4 +125,59 @@
.bookmarks-empty-detail
{
.bookmarks-empty-detail
{
@extend
%t-copy-sub1
;
@extend
%t-copy-sub1
;
}
}
\ No newline at end of file
// Rules for bookmark icon shown on each sequence nav item
i
.bookmarked
{
top
:
-3px
;
position
:
absolute
;
left
:
(
$baseline
/
4
);
}
// Rules for bookmark button's different styles
.bookmark-button-wrapper
{
text-align
:
right
;
margin-bottom
:
10px
;
}
@mixin
base-style
(
$border-color
,
$content-color
)
{
background
:
none
;
border
:
1px
solid
$border-color
;
border-radius
:
(
$baseline
/
4
);
color
:
$content-color
;
&
:focus
,
&
:active
{
box-shadow
:
none
;
}
}
@mixin
icon-style
(
$content
,
$color
)
{
&
:before
{
content
:
$content
;
font-family
:
FontAwesome
;
color
:
$color
;
}
}
@mixin
hover-style
(
$border-color
,
$content-color
,
$icon-content
)
{
&
:hover
{
background
:
none
;
border
:
1px
solid
$border-color
;
color
:
$content-color
;
@include
icon-style
(
$icon-content
,
$content-color
);
}
}
.bookmark-button.bookmarked
{
@include
base-style
(
$m-blue
,
$m-blue
);
@include
icon-style
(
"\f02e"
,
$m-blue
);
@include
hover-style
(
$light-gray
,
$black
,
"\f097"
);
}
.bookmark-button
:not
(
.bookmarked
)
{
@include
base-style
(
$light-gray
,
$black
);
@include
icon-style
(
"\f097"
,
$black
);
@include
hover-style
(
$m-blue
,
$m-blue
,
"\f02e"
);
}
lms/templates/bookmark_button.html
0 → 100644
View file @
3cbbb8f3
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
page
args=
"bookmark_id, is_bookmarked"
/>
<div
class=
"bookmark-button-wrapper"
>
<button
class=
"btn bookmark-button ${"
bookmarked
"
if
is_bookmarked
else
""}"
aria-pressed=
"${"
true
"
if
is_bookmarked
else
"
false
"}"
data-bookmark-id=
"${bookmark_id}"
>
<span
class=
"sr bookmark-sr"
>
${_("Click to remove") if is_bookmarked else _("Click to add")}
</span>
${_("Bookmark")}
</button>
</div>
lms/templates/courseware/courseware.html
View file @
3cbbb8f3
...
@@ -24,6 +24,13 @@ ${page_title_breadcrumbs(course_name())}
...
@@ -24,6 +24,13 @@ ${page_title_breadcrumbs(course_name())}
</title></
%
block>
</title></
%
block>
<
%
block
name=
"header_extras"
>
<
%
block
name=
"header_extras"
>
% for template_name in ["message_banner"]:
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<%
static
:
include
path
=
"fields/${template_name}.underscore"
/>
</script>
% endfor
% for template_name in ["image-modal"]:
% for template_name in ["image-modal"]:
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<%
static
:
include
path
=
"common/templates/${template_name}.underscore"
/>
<%
static
:
include
path
=
"common/templates/${template_name}.underscore"
/>
...
@@ -124,6 +131,8 @@ ${fragment.foot_html()}
...
@@ -124,6 +131,8 @@ ${fragment.foot_html()}
</
%
block>
</
%
block>
<div
class=
"coursewide-message-banner"
aria-live=
"polite"
></div>
% if default_tab:
% if default_tab:
<
%
include
file=
"/courseware/course_navigation.html"
/>
<
%
include
file=
"/courseware/course_navigation.html"
/>
% else:
% else:
...
@@ -149,7 +158,7 @@ ${fragment.foot_html()}
...
@@ -149,7 +158,7 @@ ${fragment.foot_html()}
<div
class=
"wrapper-course-modes"
>
<div
class=
"wrapper-course-modes"
>
<div
class=
"courseware-bookmarks-button"
>
<div
class=
"courseware-bookmarks-button"
data-bookmarks-api-url=
"${bookmarks_api_url}"
>
<button
type=
"button"
class=
"bookmarks-list-button is-inactive"
aria-pressed=
"false"
>
<button
type=
"button"
class=
"bookmarks-list-button is-inactive"
aria-pressed=
"false"
>
${_('Bookmarks')}
${_('Bookmarks')}
</button>
</button>
...
...
lms/templates/seq_module.html
View file @
3cbbb8f3
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
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}"
>
<div
class=
"path"
></div>
<div
class=
"sequence-nav"
>
<div
class=
"sequence-nav"
>
<button
class=
"sequence-nav-button button-previous"
>
<button
class=
"sequence-nav-button button-previous"
>
<span
class=
"icon fa fa-chevron-prev"
aria-hidden=
"true"
></span><span
class=
"sr"
>
${_('Previous')}
</span>
<span
class=
"icon fa fa-chevron-prev"
aria-hidden=
"true"
></span><span
class=
"sr"
>
${_('Previous')}
</span>
...
@@ -13,16 +14,18 @@
...
@@ -13,16 +14,18 @@
## implementation note: will need to figure out how to handle combining detail
## implementation note: will need to figure out how to handle combining detail
## statuses of multiple modules in js.
## statuses of multiple modules in js.
<li>
<li>
<a
class=
"seq_${item['type']} inactive progress-${item['progress_status']}"
<a
class=
"seq_${item['type']} inactive progress-${item['progress_status']}
nav-item
"
data-id=
"${item['id']}"
data-id=
"${item['id']}"
data-element=
"${idx+1}"
data-element=
"${idx+1}"
href=
"javascript:void(0);"
href=
"javascript:void(0);"
data-page-title=
"${item['page_title']|h}"
data-page-title=
"${item['page_title']|h}"
data-path=
"${item['path']}"
aria-controls=
"seq_contents_${idx}"
aria-controls=
"seq_contents_${idx}"
id=
"tab_${idx}"
id=
"tab_${idx}"
tabindex=
"0"
>
tabindex=
"0"
>
<i
class=
"icon fa seq_${item['type']}"
aria-hidden=
"true"
></i>
<i
class=
"icon fa seq_${item['type']}"
aria-hidden=
"true"
></i>
<p><span
class=
"sr"
>
${item['type']}
</span>
${item['title']}
</p>
<i
class=
"fa fa-fw fa-bookmark bookmark-icon ${"
is-hidden
"
if
not
item
['
bookmarked
']
else
"
bookmarked
"}"
aria-hidden=
"true"
></i>
<p><span
class=
"sr"
>
${item['type']}
</span>
${item['title']}
<span
class=
"sr bookmark-icon-sr"
>
${_("Bookmarked") if item['bookmarked'] else ""}
</span></p>
</a>
</a>
</li>
</li>
% endfor
% endfor
...
...
lms/templates/vert_module.html
View file @
3cbbb8f3
% if show_bookmark_button:
<
%
include
file=
'bookmark_button.html'
args=
"bookmark_id=bookmark_id, is_bookmarked=bookmarked"
/>
% endif
<div
class=
"vert-mod"
>
<div
class=
"vert-mod"
>
% for idx, item in enumerate(items):
% for idx, item in enumerate(items):
<div
class=
"vert vert-${idx}"
data-id=
"${item['id']}"
>
<div
class=
"vert vert-${idx}"
data-id=
"${item['id']}"
>
...
...
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