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
d447c075
Commit
d447c075
authored
Jul 10, 2014
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support staff locking on the unit page
STUD-1873
parent
cf70eb6e
Show whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
893 additions
and
391 deletions
+893
-391
cms/djangoapps/contentstore/tests/test_utils.py
+3
-3
cms/djangoapps/contentstore/utils.py
+3
-2
cms/djangoapps/contentstore/views/component.py
+1
-2
cms/djangoapps/contentstore/views/item.py
+61
-52
cms/djangoapps/contentstore/views/tests/test_container_page.py
+0
-36
cms/djangoapps/contentstore/views/tests/test_item.py
+49
-0
cms/static/js/models/xblock_info.js
+7
-2
cms/static/js/spec/views/pages/container_spec.js
+26
-26
cms/static/js/spec/views/pages/container_subviews_spec.js
+227
-81
cms/static/js/views/pages/container.js
+5
-0
cms/static/js/views/pages/container_subviews.js
+93
-13
cms/static/js/views/utils/view_utils.js
+4
-1
cms/static/sass/_developer.scss
+1
-2
cms/static/sass/views/_container.scss
+9
-106
cms/templates/container.html
+2
-11
cms/templates/js/container-message.underscore
+8
-0
cms/templates/js/course-outline.underscore
+2
-2
cms/templates/js/mock/mock-container-page.underscore
+1
-0
cms/templates/js/publish-xblock.underscore
+53
-30
common/test/acceptance/pages/lms/staff_view.py
+10
-2
common/test/acceptance/pages/studio/container.py
+43
-0
common/test/acceptance/tests/test_lms.py
+85
-0
common/test/acceptance/tests/test_studio_container.py
+200
-20
No files found.
cms/djangoapps/contentstore/tests/test_utils.py
View file @
d447c075
...
@@ -231,7 +231,7 @@ class XBlockVisibilityTestCase(TestCase):
...
@@ -231,7 +231,7 @@ class XBlockVisibilityTestCase(TestCase):
vertical
.
start
=
self
.
future
vertical
.
start
=
self
.
future
modulestore
()
.
update_item
(
vertical
,
self
.
dummy_user
)
modulestore
()
.
update_item
(
vertical
,
self
.
dummy_user
)
self
.
assertTrue
(
utils
.
is_
xblock
_visible_to_students
(
vertical
))
self
.
assertTrue
(
utils
.
is_
currently
_visible_to_students
(
vertical
))
def
_test_visible_to_students
(
self
,
expected_visible_without_lock
,
name
,
start_date
,
publish
=
False
):
def
_test_visible_to_students
(
self
,
expected_visible_without_lock
,
name
,
start_date
,
publish
=
False
):
"""
"""
...
@@ -239,13 +239,13 @@ class XBlockVisibilityTestCase(TestCase):
...
@@ -239,13 +239,13 @@ class XBlockVisibilityTestCase(TestCase):
with and without visible_to_staff_only set.
with and without visible_to_staff_only set.
"""
"""
no_staff_lock
=
self
.
_create_xblock_with_start_date
(
name
,
start_date
,
publish
,
visible_to_staff_only
=
False
)
no_staff_lock
=
self
.
_create_xblock_with_start_date
(
name
,
start_date
,
publish
,
visible_to_staff_only
=
False
)
self
.
assertEqual
(
expected_visible_without_lock
,
utils
.
is_
xblock
_visible_to_students
(
no_staff_lock
))
self
.
assertEqual
(
expected_visible_without_lock
,
utils
.
is_
currently
_visible_to_students
(
no_staff_lock
))
# any xblock with visible_to_staff_only set to True should not be visible to students.
# any xblock with visible_to_staff_only set to True should not be visible to students.
staff_lock
=
self
.
_create_xblock_with_start_date
(
staff_lock
=
self
.
_create_xblock_with_start_date
(
name
+
"_locked"
,
start_date
,
publish
,
visible_to_staff_only
=
True
name
+
"_locked"
,
start_date
,
publish
,
visible_to_staff_only
=
True
)
)
self
.
assertFalse
(
utils
.
is_
xblock
_visible_to_students
(
staff_lock
))
self
.
assertFalse
(
utils
.
is_
currently
_visible_to_students
(
staff_lock
))
def
_create_xblock_with_start_date
(
self
,
name
,
start_date
,
publish
=
False
,
visible_to_staff_only
=
False
):
def
_create_xblock_with_start_date
(
self
,
name
,
start_date
,
publish
=
False
,
visible_to_staff_only
=
False
):
"""Helper to create an xblock with a start date, optionally publishing it"""
"""Helper to create an xblock with a start date, optionally publishing it"""
...
...
cms/djangoapps/contentstore/utils.py
View file @
d447c075
...
@@ -164,9 +164,10 @@ def compute_publish_state(xblock):
...
@@ -164,9 +164,10 @@ def compute_publish_state(xblock):
return
modulestore
()
.
compute_publish_state
(
xblock
)
return
modulestore
()
.
compute_publish_state
(
xblock
)
def
is_
xblock
_visible_to_students
(
xblock
):
def
is_
currently
_visible_to_students
(
xblock
):
"""
"""
Returns true if there is a published version of the xblock that has been released.
Returns true if there is a published version of the xblock that is currently visible to students.
This means that it has a release date in the past, and the xblock has not been set to staff only.
"""
"""
try
:
try
:
...
...
cms/djangoapps/contentstore/views/component.py
View file @
d447c075
...
@@ -21,7 +21,7 @@ from xblock.fields import Scope
...
@@ -21,7 +21,7 @@ from xblock.fields import Scope
from
xblock.plugin
import
PluginMissingError
from
xblock.plugin
import
PluginMissingError
from
xblock.runtime
import
Mixologist
from
xblock.runtime
import
Mixologist
from
contentstore.utils
import
get_lms_link_for_item
,
compute_publish_state
,
is_xblock_visible_to_students
from
contentstore.utils
import
get_lms_link_for_item
,
compute_publish_state
from
contentstore.views.helpers
import
get_parent_xblock
,
is_unit
,
xblock_type_display_name
from
contentstore.views.helpers
import
get_parent_xblock
,
is_unit
,
xblock_type_display_name
from
contentstore.views.item
import
create_xblock_info
from
contentstore.views.item
import
create_xblock_info
...
@@ -207,7 +207,6 @@ def container_handler(request, usage_key_string):
...
@@ -207,7 +207,6 @@ def container_handler(request, usage_key_string):
'xblock_locator'
:
xblock
.
location
,
'xblock_locator'
:
xblock
.
location
,
'unit'
:
unit
,
'unit'
:
unit
,
'is_unit_page'
:
is_unit_page
,
'is_unit_page'
:
is_unit_page
,
'is_visible_to_students'
:
is_xblock_visible_to_students
(
xblock
),
'subsection'
:
subsection
,
'subsection'
:
subsection
,
'section'
:
section
,
'section'
:
section
,
'new_unit_category'
:
'vertical'
,
'new_unit_category'
:
'vertical'
,
...
...
cms/djangoapps/contentstore/views/item.py
View file @
d447c075
...
@@ -37,6 +37,7 @@ from util.date_utils import get_default_time_display
...
@@ -37,6 +37,7 @@ from util.date_utils import get_default_time_display
from
util.json_request
import
expect_json
,
JsonResponse
from
util.json_request
import
expect_json
,
JsonResponse
from
.access
import
has_course_access
from
.access
import
has_course_access
from
contentstore.utils
import
is_currently_visible_to_students
from
contentstore.views.helpers
import
is_unit
,
xblock_studio_url
,
xblock_primary_child_category
,
\
from
contentstore.views.helpers
import
is_unit
,
xblock_studio_url
,
xblock_primary_child_category
,
\
xblock_type_display_name
,
get_parent_xblock
xblock_type_display_name
,
get_parent_xblock
from
contentstore.views.preview
import
get_preview_fragment
from
contentstore.views.preview
import
get_preview_fragment
...
@@ -104,10 +105,12 @@ def xblock_handler(request, usage_key_string):
...
@@ -104,10 +105,12 @@ def xblock_handler(request, usage_key_string):
to None! Absent ones will be left alone.
to None! Absent ones will be left alone.
:nullout: which metadata fields to set to None
:nullout: which metadata fields to set to None
:graderType: change how this unit is graded
:graderType: change how this unit is graded
:publish: can be either -- 'make_public' (which publishes the content) or 'discard_changes'
:publish: can be:
(which reverts to the last published version). If 'discard_changes', the other fields
'make_public': publish the content
will not be used; that is, it is not possible to update and discard changes
'republish': publish this item *only* if it was previously published
in a single operation.
'discard_changes' - reverts to the last published version
Note: If 'discard_changes', the other fields will not be used; that is, it is not possible
to update and discard changes in a single operation.
The JSON representation on the updated xblock (minus children) is returned.
The JSON representation on the updated xblock (minus children) is returned.
if usage_key_string is not specified, create a new xblock instance, either by duplicating
if usage_key_string is not specified, create a new xblock instance, either by duplicating
...
@@ -136,7 +139,7 @@ def xblock_handler(request, usage_key_string):
...
@@ -136,7 +139,7 @@ def xblock_handler(request, usage_key_string):
# right now can't combine output of this w/ output of _get_module_info, but worthy goal
# right now can't combine output of this w/ output of _get_module_info, but worthy goal
return
JsonResponse
(
CourseGradingModel
.
get_section_grader_type
(
usage_key
))
return
JsonResponse
(
CourseGradingModel
.
get_section_grader_type
(
usage_key
))
# TODO: pass fields to _get_module_info and only return those
# TODO: pass fields to _get_module_info and only return those
rsp
=
_get_module_info
(
usage_key
,
request
.
user
)
rsp
=
_get_module_info
(
_get_xblock
(
usage_key
,
request
.
user
)
)
return
JsonResponse
(
rsp
)
return
JsonResponse
(
rsp
)
else
:
else
:
return
HttpResponse
(
status
=
406
)
return
HttpResponse
(
status
=
406
)
...
@@ -145,9 +148,9 @@ def xblock_handler(request, usage_key_string):
...
@@ -145,9 +148,9 @@ def xblock_handler(request, usage_key_string):
_delete_item
(
usage_key
,
request
.
user
)
_delete_item
(
usage_key
,
request
.
user
)
return
JsonResponse
()
return
JsonResponse
()
else
:
# Since we have a usage_key, we are updating an existing xblock.
else
:
# Since we have a usage_key, we are updating an existing xblock.
return
_save_
item
(
return
_save_
xblock
(
request
.
user
,
request
.
user
,
usage_key
,
_get_xblock
(
usage_key
,
request
.
user
)
,
data
=
request
.
json
.
get
(
'data'
),
data
=
request
.
json
.
get
(
'data'
),
children
=
request
.
json
.
get
(
'children'
),
children
=
request
.
json
.
get
(
'children'
),
metadata
=
request
.
json
.
get
(
'metadata'
),
metadata
=
request
.
json
.
get
(
'metadata'
),
...
@@ -289,7 +292,7 @@ def xblock_outline_handler(request, usage_key_string):
...
@@ -289,7 +292,7 @@ def xblock_outline_handler(request, usage_key_string):
return
Http404
return
Http404
def
_save_
item
(
user
,
usage_key
,
data
=
None
,
children
=
None
,
metadata
=
None
,
nullout
=
None
,
def
_save_
xblock
(
user
,
xblock
,
data
=
None
,
children
=
None
,
metadata
=
None
,
nullout
=
None
,
grader_type
=
None
,
publish
=
None
):
grader_type
=
None
,
publish
=
None
):
"""
"""
Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
...
@@ -298,32 +301,19 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
...
@@ -298,32 +301,19 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
"""
"""
store
=
modulestore
()
store
=
modulestore
()
try
:
existing_item
=
store
.
get_item
(
usage_key
)
except
ItemNotFoundError
:
if
usage_key
.
category
in
CREATE_IF_NOT_FOUND
:
# New module at this location, for pages that are not pre-created.
# Used for course info handouts.
existing_item
=
store
.
create_item
(
user
.
id
,
usage_key
.
course_key
,
usage_key
.
block_type
,
usage_key
.
block_id
)
else
:
raise
except
InvalidLocationError
:
log
.
error
(
"Can't find item by location."
)
return
JsonResponse
({
"error"
:
"Can't find item by location: "
+
unicode
(
usage_key
)},
404
)
# Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI).
# Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI).
if
publish
==
"discard_changes"
:
if
publish
==
"discard_changes"
:
store
.
revert_to_published
(
usage_key
,
user
.
id
)
store
.
revert_to_published
(
xblock
.
location
,
user
.
id
)
# Returning the same sort of result that we do for other save operations. In the future,
# Returning the same sort of result that we do for other save operations. In the future,
# we may want to return the full XBlockInfo.
# we may want to return the full XBlockInfo.
return
JsonResponse
({
'id'
:
unicode
(
usage_key
)})
return
JsonResponse
({
'id'
:
unicode
(
xblock
.
location
)})
old_metadata
=
own_metadata
(
existing_item
)
old_metadata
=
own_metadata
(
xblock
)
old_content
=
existing_item
.
get_explicitly_set_fields_by_scope
(
Scope
.
content
)
old_content
=
xblock
.
get_explicitly_set_fields_by_scope
(
Scope
.
content
)
if
data
:
if
data
:
# TODO Allow any scope.content fields not just "data" (exactly like the get below this)
# TODO Allow any scope.content fields not just "data" (exactly like the get below this)
existing_item
.
data
=
data
xblock
.
data
=
data
else
:
else
:
data
=
old_content
[
'data'
]
if
'data'
in
old_content
else
None
data
=
old_content
[
'data'
]
if
'data'
in
old_content
else
None
...
@@ -332,7 +322,7 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
...
@@ -332,7 +322,7 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
for
child
in
children
:
for
child
in
children
:
child_usage_key
=
usage_key_with_run
(
child
)
child_usage_key
=
usage_key_with_run
(
child
)
children_usage_keys
.
append
(
child_usage_key
)
children_usage_keys
.
append
(
child_usage_key
)
existing_item
.
children
=
children_usage_keys
xblock
.
children
=
children_usage_keys
# also commit any metadata which might have been passed along
# also commit any metadata which might have been passed along
if
nullout
is
not
None
or
metadata
is
not
None
:
if
nullout
is
not
None
or
metadata
is
not
None
:
...
@@ -341,53 +331,61 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
...
@@ -341,53 +331,61 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
# 'apply' the submitted metadata, so we don't end up deleting system metadata.
# 'apply' the submitted metadata, so we don't end up deleting system metadata.
if
nullout
is
not
None
:
if
nullout
is
not
None
:
for
metadata_key
in
nullout
:
for
metadata_key
in
nullout
:
setattr
(
existing_item
,
metadata_key
,
None
)
setattr
(
xblock
,
metadata_key
,
None
)
# update existing metadata with submitted metadata (which can be partial)
# update existing metadata with submitted metadata (which can be partial)
# IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
# IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
# the intent is to make it None, use the nullout field
# the intent is to make it None, use the nullout field
if
metadata
is
not
None
:
if
metadata
is
not
None
:
for
metadata_key
,
value
in
metadata
.
items
():
for
metadata_key
,
value
in
metadata
.
items
():
field
=
existing_item
.
fields
[
metadata_key
]
field
=
xblock
.
fields
[
metadata_key
]
if
value
is
None
:
if
value
is
None
:
field
.
delete_from
(
existing_item
)
field
.
delete_from
(
xblock
)
else
:
else
:
try
:
try
:
value
=
field
.
from_json
(
value
)
value
=
field
.
from_json
(
value
)
except
ValueError
:
except
ValueError
:
return
JsonResponse
({
"error"
:
"Invalid data"
},
400
)
return
JsonResponse
({
"error"
:
"Invalid data"
},
400
)
field
.
write_to
(
existing_item
,
value
)
field
.
write_to
(
xblock
,
value
)
if
callable
(
getattr
(
existing_item
,
"editor_saved"
,
None
)):
if
callable
(
getattr
(
xblock
,
"editor_saved"
,
None
)):
existing_item
.
editor_saved
(
user
,
old_metadata
,
old_content
)
xblock
.
editor_saved
(
user
,
old_metadata
,
old_content
)
# commit to datastore
# commit to datastore
store
.
update_item
(
existing_item
,
user
.
id
)
store
.
update_item
(
xblock
,
user
.
id
)
# for static tabs, their containing course also records their display name
# for static tabs, their containing course also records their display name
if
usage_key
.
category
==
'static_tab'
:
if
xblock
.
location
.
category
==
'static_tab'
:
course
=
store
.
get_course
(
usage_key
.
course_key
)
course
=
store
.
get_course
(
xblock
.
location
.
course_key
)
# find the course's reference to this tab and update the name.
# find the course's reference to this tab and update the name.
static_tab
=
CourseTabList
.
get_tab_by_slug
(
course
.
tabs
,
usage_key
.
name
)
static_tab
=
CourseTabList
.
get_tab_by_slug
(
course
.
tabs
,
xblock
.
location
.
name
)
# only update if changed
# only update if changed
if
static_tab
and
static_tab
[
'name'
]
!=
existing_item
.
display_name
:
if
static_tab
and
static_tab
[
'name'
]
!=
xblock
.
display_name
:
static_tab
[
'name'
]
=
existing_item
.
display_name
static_tab
[
'name'
]
=
xblock
.
display_name
store
.
update_item
(
course
,
user
.
id
)
store
.
update_item
(
course
,
user
.
id
)
result
=
{
result
=
{
'id'
:
unicode
(
usage_key
),
'id'
:
unicode
(
xblock
.
location
),
'data'
:
data
,
'data'
:
data
,
'metadata'
:
own_metadata
(
existing_item
)
'metadata'
:
own_metadata
(
xblock
)
}
}
if
grader_type
is
not
None
:
if
grader_type
is
not
None
:
result
.
update
(
CourseGradingModel
.
update_section_grader_type
(
existing_item
,
grader_type
,
user
))
result
.
update
(
CourseGradingModel
.
update_section_grader_type
(
xblock
,
grader_type
,
user
))
# If publish is set to 'republish' and this item has previously been published, then this
# new item should be republished. This is used by staff locking to ensure that changing the draft
# value of the staff lock will also update the published version.
if
publish
==
'republish'
:
published
=
modulestore
()
.
has_item
(
xblock
.
location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
if
published
:
publish
=
'make_public'
# Make public after updating the xblock, in case the caller asked for both an update and a publish.
# Make public after updating the xblock, in case the caller asked for both an update and a publish.
#
Although not supported in the UI, Bok Choy tests use thi
s.
#
Used by Bok Choy tests and by republishing of staff lock
s.
if
publish
==
'make_public'
:
if
publish
==
'make_public'
:
modulestore
()
.
publish
(
existing_item
.
location
,
user
.
id
)
modulestore
()
.
publish
(
xblock
.
location
,
user
.
id
)
# Note that children aren't being returned until we have a use case.
# Note that children aren't being returned until we have a use case.
return
JsonResponse
(
result
)
return
JsonResponse
(
result
)
...
@@ -552,31 +550,40 @@ def orphan_handler(request, course_key_string):
...
@@ -552,31 +550,40 @@ def orphan_handler(request, course_key_string):
raise
PermissionDenied
()
raise
PermissionDenied
()
def
_get_
module_info
(
usage_key
,
user
,
rewrite_static_links
=
True
):
def
_get_
xblock
(
usage_key
,
user
):
"""
"""
metadata, data, id representation of a leaf module fetcher.
Returns the xblock for the specified usage key. Note: if failing to find a key with a category
:param usage_key: A UsageKey
in the CREATE_IF_NOT_FOUND list, an xblock will be created and saved automatically.
"""
"""
store
=
modulestore
()
store
=
modulestore
()
try
:
try
:
module
=
store
.
get_item
(
usage_key
)
return
store
.
get_item
(
usage_key
)
except
ItemNotFoundError
:
except
ItemNotFoundError
:
if
usage_key
.
category
in
CREATE_IF_NOT_FOUND
:
if
usage_key
.
category
in
CREATE_IF_NOT_FOUND
:
# Create a new one for certain categories only. Used for course info handouts.
# Create a new one for certain categories only. Used for course info handouts.
module
=
store
.
create_item
(
user
.
id
,
usage_key
.
course_key
,
usage_key
.
block_type
,
block_id
=
usage_key
.
block_id
)
return
store
.
create_item
(
user
.
id
,
usage_key
.
course_key
,
usage_key
.
block_type
,
block_id
=
usage_key
.
block_id
)
else
:
else
:
raise
raise
except
InvalidLocationError
:
log
.
error
(
"Can't find item by location."
)
return
JsonResponse
({
"error"
:
"Can't find item by location: "
+
unicode
(
usage_key
)},
404
)
data
=
getattr
(
module
,
'data'
,
''
)
def
_get_module_info
(
xblock
,
rewrite_static_links
=
True
):
"""
metadata, data, id representation of a leaf module fetcher.
:param usage_key: A UsageKey
"""
data
=
getattr
(
xblock
,
'data'
,
''
)
if
rewrite_static_links
:
if
rewrite_static_links
:
data
=
replace_static_urls
(
data
=
replace_static_urls
(
data
,
data
,
None
,
None
,
course_id
=
module
.
location
.
course_key
course_id
=
xblock
.
location
.
course_key
)
)
# Note that children aren't being returned until we have a use case.
# Note that children aren't being returned until we have a use case.
return
create_xblock_info
(
module
,
data
=
data
,
metadata
=
own_metadata
(
module
),
include_ancestor_info
=
True
)
return
create_xblock_info
(
xblock
,
data
=
data
,
metadata
=
own_metadata
(
xblock
),
include_ancestor_info
=
True
)
def
create_xblock_info
(
xblock
,
data
=
None
,
metadata
=
None
,
include_ancestor_info
=
False
,
include_child_info
=
False
,
def
create_xblock_info
(
xblock
,
data
=
None
,
metadata
=
None
,
include_ancestor_info
=
False
,
include_child_info
=
False
,
...
@@ -630,6 +637,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
...
@@ -630,6 +637,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"released_to_students"
:
datetime
.
now
(
UTC
)
>
xblock
.
start
,
"released_to_students"
:
datetime
.
now
(
UTC
)
>
xblock
.
start
,
"release_date"
:
release_date
,
"release_date"
:
release_date
,
"release_date_from"
:
_get_release_date_from
(
xblock
)
if
release_date
else
None
,
"release_date_from"
:
_get_release_date_from
(
xblock
)
if
release_date
else
None
,
"visible_to_staff_only"
:
xblock
.
visible_to_staff_only
,
"currently_visible_to_students"
:
is_currently_visible_to_students
(
xblock
),
}
}
if
data
is
not
None
:
if
data
is
not
None
:
xblock_info
[
"data"
]
=
data
xblock_info
[
"data"
]
=
data
...
...
cms/djangoapps/contentstore/views/tests/test_container_page.py
View file @
d447c075
...
@@ -157,39 +157,3 @@ class ContainerPageTestCase(StudioPageTestCase):
...
@@ -157,39 +157,3 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
"""
empty_child_container
=
self
.
_create_item
(
self
.
vertical
.
location
,
'split_test'
,
'Split Test'
)
empty_child_container
=
self
.
_create_item
(
self
.
vertical
.
location
,
'split_test'
,
'Split Test'
)
self
.
validate_preview_html
(
empty_child_container
,
self
.
reorderable_child_view
,
can_add
=
False
)
self
.
validate_preview_html
(
empty_child_container
,
self
.
reorderable_child_view
,
can_add
=
False
)
def
test_unreleased_private_container_messages
(
self
):
"""
Verify that an unreleased private container does not display messages.
"""
self
.
validate_html_for_messages
(
self
.
unreleased_private_vertical
,
False
)
def
test_unreleased_public_container_messages
(
self
):
"""
Verify that an unreleased public container does not display messages.
"""
self
.
validate_html_for_messages
(
self
.
unreleased_public_vertical
,
False
)
def
test_released_private_container_message
(
self
):
"""
Verify that a released private container does not display messages.
"""
self
.
validate_html_for_messages
(
self
.
released_private_vertical
,
False
)
def
test_released_public_container_message
(
self
):
"""
Verify that a released public container does display messages.
"""
self
.
validate_html_for_messages
(
self
.
released_public_vertical
,
True
)
def
validate_html_for_messages
(
self
,
xblock
,
has_messages
):
"""
Validate that the specified HTML has the appropriate messages for the current student visibility state.
"""
# Verify that there are no warning messages for blocks that are not visible to students
html
=
self
.
get_page_html
(
xblock
)
messages_html
=
'<div class="container-message wrapper-message">'
if
has_messages
:
self
.
assertIn
(
messages_html
,
html
)
else
:
self
.
assertNotIn
(
messages_html
,
html
)
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
d447c075
...
@@ -567,6 +567,55 @@ class TestEditItem(ItemTest):
...
@@ -567,6 +567,55 @@ class TestEditItem(ItemTest):
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
self
.
assertIsNone
(
published
.
due
)
self
.
assertIsNone
(
published
.
due
)
def
test_republish
(
self
):
""" Test republishing an item. """
new_display_name
=
'New Display Name'
republish_data
=
{
'publish'
:
'republish'
,
'display_name'
:
new_display_name
}
# When the problem is first created, it is only in draft (because of its category).
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
private
)
# Republishing when only in draft will update the draft but not cause a public item to be created.
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
data
=
{
'publish'
:
'republish'
,
'metadata'
:
{
'display_name'
:
new_display_name
}
}
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
private
)
draft
=
self
.
get_item_from_modulestore
(
self
.
problem_usage_key
,
verify_is_draft
=
True
)
self
.
assertEqual
(
draft
.
display_name
,
new_display_name
)
# Publish the item
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
data
=
{
'publish'
:
'make_public'
}
)
# Now republishing should update the published version
new_display_name_2
=
'New Display Name 2'
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
data
=
{
'publish'
:
'republish'
,
'metadata'
:
{
'display_name'
:
new_display_name_2
}
}
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
published
=
modulestore
()
.
get_item
(
self
.
problem_usage_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
self
.
assertEqual
(
published
.
display_name
,
new_display_name_2
)
def
_make_draft_content_different_from_published
(
self
):
def
_make_draft_content_different_from_published
(
self
):
"""
"""
Helper method to create different draft and published versions of a problem.
Helper method to create different draft and published versions of a problem.
...
...
cms/static/js/models/xblock_info.js
View file @
d447c075
...
@@ -38,7 +38,7 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
...
@@ -38,7 +38,7 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
* If true, only course staff can see the xblock regardless of publish status or
* If true, only course staff can see the xblock regardless of publish status or
* release date status.
* release date status.
*/
*/
"
locked
"
:
null
,
"
visible_to_staff_only
"
:
null
,
/**
/**
* Date of the last edit to this xblock or any of its descendants.
* Date of the last edit to this xblock or any of its descendants.
*/
*/
...
@@ -69,7 +69,12 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
...
@@ -69,7 +69,12 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
* this will either be the parent subsection or the grandparent section.
* this will either be the parent subsection or the grandparent section.
* This can be null if the release date is unscheduled.
* This can be null if the release date is unscheduled.
*/
*/
"release_date_from"
:
null
"release_date_from"
:
null
,
/**
* True if this xblock is currently visible to students. This is computed server-side
* so that the logic isn't duplicated on the client.
*/
"currently_visible_to_students"
:
null
},
},
parse
:
function
(
response
)
{
parse
:
function
(
response
)
{
...
...
cms/static/js/spec/views/pages/container_spec.js
View file @
d447c075
...
@@ -48,7 +48,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -48,7 +48,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
);
);
};
};
renderContainerPage
=
function
(
html
,
test
,
options
)
{
renderContainerPage
=
function
(
test
,
html
,
options
)
{
requests
=
create_sinon
.
requests
(
test
);
requests
=
create_sinon
.
requests
(
test
);
containerPage
=
new
ContainerPage
(
_
.
extend
(
options
||
{},
{
containerPage
=
new
ContainerPage
(
_
.
extend
(
options
||
{},
{
model
:
model
,
model
:
model
,
...
@@ -70,7 +70,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -70,7 +70,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
describe
(
"Initial display"
,
function
()
{
describe
(
"Initial display"
,
function
()
{
it
(
'can render itself'
,
function
()
{
it
(
'can render itself'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
});
});
...
@@ -84,7 +84,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -84,7 +84,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
it
(
'inline edits the display name when performing a new action'
,
function
()
{
it
(
'inline edits the display name when performing a new action'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
,
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
action
:
'new'
action
:
'new'
});
});
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
...
@@ -106,8 +106,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -106,8 +106,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
};
};
expectEditCanceled
=
function
(
test
,
options
)
{
expectEditCanceled
=
function
(
test
,
options
)
{
var
initialRequests
,
displayNameWrapper
;
var
initialRequests
,
displayNameWrapper
,
displayNameInput
;
renderContainerPage
(
mockContainerXBlockHtml
,
test
);
renderContainerPage
(
test
,
mockContainerXBlockHtml
);
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameWrapper
=
getDisplayNameWrapper
();
initialRequests
=
requests
.
length
;
initialRequests
=
requests
.
length
;
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
options
.
newTitle
);
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
options
.
newTitle
);
...
@@ -125,7 +125,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -125,7 +125,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'can edit itself'
,
function
()
{
it
(
'can edit itself'
,
function
()
{
var
editButtons
,
displayNameElement
;
var
editButtons
,
displayNameElement
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameElement
=
containerPage
.
$
(
'.page-header-title'
);
displayNameElement
=
containerPage
.
$
(
'.page-header-title'
);
// Click the root edit button
// Click the root edit button
...
@@ -162,7 +162,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -162,7 +162,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'can inline edit the display name'
,
function
()
{
it
(
'can inline edit the display name'
,
function
()
{
var
displayNameInput
,
displayNameWrapper
;
var
displayNameInput
,
displayNameWrapper
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
displayNameInput
.
change
();
displayNameInput
.
change
();
...
@@ -176,7 +176,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -176,7 +176,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'does not change the title when a display name update fails'
,
function
()
{
it
(
'does not change the title when a display name update fails'
,
function
()
{
var
initialRequests
,
displayNameInput
,
displayNameWrapper
;
var
initialRequests
,
displayNameInput
,
displayNameWrapper
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
initialRequests
=
requests
.
length
;
initialRequests
=
requests
.
length
;
...
@@ -190,7 +190,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -190,7 +190,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'trims whitespace from the display name'
,
function
()
{
it
(
'trims whitespace from the display name'
,
function
()
{
var
displayNameInput
,
displayNameWrapper
;
var
displayNameInput
,
displayNameWrapper
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
+
' '
);
displayNameInput
=
edit_helpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
+
' '
);
displayNameInput
.
change
();
displayNameInput
.
change
();
...
@@ -222,7 +222,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -222,7 +222,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'can show an edit modal for a child xblock'
,
function
()
{
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
var
editButtons
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
// The container should have rendered six mock xblocks
// The container should have rendered six mock xblocks
expect
(
editButtons
.
length
).
toBe
(
6
);
expect
(
editButtons
.
length
).
toBe
(
6
);
...
@@ -258,7 +258,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -258,7 +258,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'can save changes to settings'
,
function
()
{
it
(
'can save changes to settings'
,
function
()
{
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
// The container should have rendered six mock xblocks
// The container should have rendered six mock xblocks
expect
(
editButtons
.
length
).
toBe
(
6
);
expect
(
editButtons
.
length
).
toBe
(
6
);
...
@@ -346,24 +346,24 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -346,24 +346,24 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
};
};
it
(
"can delete the first xblock"
,
function
()
{
it
(
"can delete the first xblock"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
0
);
deleteComponentWithSuccess
(
0
);
});
});
it
(
"can delete a middle xblock"
,
function
()
{
it
(
"can delete a middle xblock"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
1
);
deleteComponentWithSuccess
(
1
);
});
});
it
(
"can delete the last xblock"
,
function
()
{
it
(
"can delete the last xblock"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
deleteComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
});
it
(
'does not delete when clicking No in prompt'
,
function
()
{
it
(
'does not delete when clicking No in prompt'
,
function
()
{
var
numRequests
;
var
numRequests
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
numRequests
=
requests
.
length
;
numRequests
=
requests
.
length
;
// click delete on the first component but press no
// click delete on the first component but press no
...
@@ -378,7 +378,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -378,7 +378,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'shows a notification during the delete operation'
,
function
()
{
it
(
'shows a notification during the delete operation'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDelete
(
0
);
clickDelete
(
0
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
create_sinon
.
respondWithJson
(
requests
,
{});
create_sinon
.
respondWithJson
(
requests
,
{});
...
@@ -387,7 +387,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -387,7 +387,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'does not delete an xblock upon failure'
,
function
()
{
it
(
'does not delete an xblock upon failure'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDelete
(
0
);
clickDelete
(
0
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
create_sinon
.
respondWithError
(
requests
);
create_sinon
.
respondWithError
(
requests
);
...
@@ -431,23 +431,23 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -431,23 +431,23 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
};
};
it
(
"can duplicate the first xblock"
,
function
()
{
it
(
"can duplicate the first xblock"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
0
);
duplicateComponentWithSuccess
(
0
);
});
});
it
(
"can duplicate a middle xblock"
,
function
()
{
it
(
"can duplicate a middle xblock"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
1
);
duplicateComponentWithSuccess
(
1
);
});
});
it
(
"can duplicate the last xblock"
,
function
()
{
it
(
"can duplicate the last xblock"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
duplicateComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
});
it
(
'shows a notification when duplicating'
,
function
()
{
it
(
'shows a notification when duplicating'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDuplicate
(
0
);
clickDuplicate
(
0
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
create_sinon
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
create_sinon
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
...
@@ -456,7 +456,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -456,7 +456,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'does not duplicate an xblock upon failure'
,
function
()
{
it
(
'does not duplicate an xblock upon failure'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
clickDuplicate
(
0
);
clickDuplicate
(
0
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
...
@@ -475,7 +475,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -475,7 +475,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
};
};
it
(
'sends the correct JSON to the server'
,
function
()
{
it
(
'sends the correct JSON to the server'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
clickNewComponent
(
0
);
edit_helpers
.
verifyXBlockRequest
(
requests
,
{
edit_helpers
.
verifyXBlockRequest
(
requests
,
{
"category"
:
"discussion"
,
"category"
:
"discussion"
,
...
@@ -486,7 +486,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -486,7 +486,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'shows a notification while creating'
,
function
()
{
it
(
'shows a notification while creating'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
clickNewComponent
(
0
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Adding/
);
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Adding/
);
create_sinon
.
respondWithJson
(
requests
,
{
});
create_sinon
.
respondWithJson
(
requests
,
{
});
...
@@ -495,7 +495,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -495,7 +495,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'does not insert component upon failure'
,
function
()
{
it
(
'does not insert component upon failure'
,
function
()
{
var
requestCount
;
var
requestCount
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
clickNewComponent
(
0
);
requestCount
=
requests
.
length
;
requestCount
=
requests
.
length
;
create_sinon
.
respondWithError
(
requests
);
create_sinon
.
respondWithError
(
requests
);
...
@@ -514,7 +514,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -514,7 +514,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
verifyCreateHtmlComponent
=
function
(
test
,
templateIndex
,
expectedRequest
)
{
verifyCreateHtmlComponent
=
function
(
test
,
templateIndex
,
expectedRequest
)
{
var
xblockCount
;
var
xblockCount
;
renderContainerPage
(
mockContainerXBlockHtml
,
test
);
renderContainerPage
(
test
,
mockContainerXBlockHtml
);
showTemplatePicker
();
showTemplatePicker
();
xblockCount
=
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
;
xblockCount
=
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
;
containerPage
.
$
(
'.new-component-html a'
)[
templateIndex
].
click
();
containerPage
.
$
(
'.new-component-html a'
)[
templateIndex
].
click
();
...
...
cms/static/js/spec/views/pages/container_subviews_spec.js
View file @
d447c075
...
@@ -4,8 +4,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -4,8 +4,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
function
(
$
,
_
,
str
,
create_sinon
,
edit_helpers
,
Prompt
,
ContainerPage
,
ContainerSubviews
,
XBlockInfo
)
{
function
(
$
,
_
,
str
,
create_sinon
,
edit_helpers
,
Prompt
,
ContainerPage
,
ContainerSubviews
,
XBlockInfo
)
{
describe
(
"Container Subviews"
,
function
()
{
describe
(
"Container Subviews"
,
function
()
{
var
model
,
containerPage
,
requests
,
renderContainerPage
,
respondWithHtml
,
respondWithJson
,
fetch
,
var
model
,
containerPage
,
requests
,
createContainerPage
,
renderContainerPage
,
disabledCss
=
"is-disabled"
,
respondWithHtml
,
respondWithJson
,
fetch
,
disabledCss
=
"is-disabled"
,
defaultXBlockInfo
,
createXBlockInfo
,
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-empty-container-xblock.underscore'
);
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-empty-container-xblock.underscore'
);
...
@@ -14,27 +15,39 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -14,27 +15,39 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
edit_helpers
.
installTemplate
(
'publish-xblock'
);
edit_helpers
.
installTemplate
(
'publish-xblock'
);
edit_helpers
.
installTemplate
(
'publish-history'
);
edit_helpers
.
installTemplate
(
'publish-history'
);
edit_helpers
.
installTemplate
(
'unit-outline'
);
edit_helpers
.
installTemplate
(
'unit-outline'
);
edit_helpers
.
installTemplate
(
'container-message'
);
appendSetFixtures
(
mockContainerPage
);
appendSetFixtures
(
mockContainerPage
);
});
model
=
new
XBlockInfo
(
{
defaultXBlockInfo
=
{
id
:
'locator-container'
,
id
:
'locator-container'
,
display_name
:
'Test Container'
,
display_name
:
'Test Container'
,
category
:
'vertical'
,
category
:
'vertical'
,
published
:
false
,
published
:
false
,
has_changes
:
false
has_changes
:
false
,
},
{
edited_on
:
"Jul 02, 2014 at 14:20 UTC"
,
edited_by
:
"joe"
,
parse
:
true
published_on
:
"Jul 01, 2014 at 12:45 UTC"
,
published_by
:
"amako"
,
});
visible_to_staff_only
:
false
,
currently_visible_to_students
:
false
};
createXBlockInfo
=
function
(
options
)
{
return
_
.
extend
(
_
.
extend
({},
defaultXBlockInfo
),
options
||
{});
};
createContainerPage
=
function
(
test
,
options
)
{
requests
=
create_sinon
.
requests
(
test
);
model
=
new
XBlockInfo
(
createXBlockInfo
(
options
),
{
parse
:
true
});
containerPage
=
new
ContainerPage
({
containerPage
=
new
ContainerPage
({
model
:
model
,
model
:
model
,
templates
:
edit_helpers
.
mockComponentTemplates
,
templates
:
edit_helpers
.
mockComponentTemplates
,
el
:
$
(
'#content'
),
el
:
$
(
'#content'
),
isUnitPage
:
true
isUnitPage
:
true
});
});
}
)
;
};
renderContainerPage
=
function
(
html
,
that
)
{
renderContainerPage
=
function
(
test
,
html
,
options
)
{
requests
=
create_sinon
.
requests
(
that
);
createContainerPage
(
test
,
options
);
containerPage
.
render
();
containerPage
.
render
();
respondWithHtml
(
html
);
respondWithHtml
(
html
);
};
};
...
@@ -57,6 +70,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -57,6 +70,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
};
};
fetch
=
function
(
json
)
{
fetch
=
function
(
json
)
{
json
=
createXBlockInfo
(
json
);
model
.
fetch
();
model
.
fetch
();
respondWithJson
(
json
);
respondWithJson
(
json
);
};
};
...
@@ -66,30 +80,30 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -66,30 +80,30 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
previewCss
=
'.button-preview'
;
previewCss
=
'.button-preview'
;
it
(
'renders correctly for private unit'
,
function
()
{
it
(
'renders correctly for private unit'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
viewPublishedCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
viewPublishedCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
});
});
it
(
'updates when published attribute changes'
,
function
()
{
it
(
'updates when published attribute changes'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
true
});
fetch
({
"published"
:
true
});
expect
(
containerPage
.
$
(
viewPublishedCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
viewPublishedCss
)).
not
.
toHaveClass
(
disabledCss
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
false
});
fetch
({
"published"
:
false
});
expect
(
containerPage
.
$
(
viewPublishedCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
viewPublishedCss
)).
toHaveClass
(
disabledCss
);
});
});
it
(
'updates when has_changes attribute changes'
,
function
()
{
it
(
'updates when has_changes attribute changes'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"
id"
:
"locator-container"
,
"
has_changes"
:
true
});
fetch
({
"has_changes"
:
true
});
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
true
,
"has_changes"
:
false
});
fetch
({
"published"
:
true
,
"has_changes"
:
false
});
expect
(
containerPage
.
$
(
previewCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
previewCss
)).
toHaveClass
(
disabledCss
);
// If published is false, preview is always enabled.
// If published is false, preview is always enabled.
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
false
,
"has_changes"
:
false
});
fetch
({
"published"
:
false
,
"has_changes"
:
false
});
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
});
});
});
});
...
@@ -97,21 +111,21 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -97,21 +111,21 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
describe
(
"Publisher"
,
function
()
{
describe
(
"Publisher"
,
function
()
{
var
headerCss
=
'.pub-status'
,
var
headerCss
=
'.pub-status'
,
bitPublishingCss
=
"div.bit-publishing"
,
bitPublishingCss
=
"div.bit-publishing"
,
publishedBit
=
"published"
,
publishedBit
=
"is-published"
,
draftBit
=
"draft"
,
draftBit
=
"is-draft"
,
staffOnlyBit
=
"is-staff-only"
,
publishButtonCss
=
".action-publish"
,
publishButtonCss
=
".action-publish"
,
discardChangesButtonCss
=
".action-discard"
,
discardChangesButtonCss
=
".action-discard"
,
lastDraftCss
=
".wrapper-last-draft"
,
lastDraftCss
=
".wrapper-last-draft"
,
releaseDateTitleCss
=
".wrapper-release .title"
,
releaseDateTitleCss
=
".wrapper-release .title"
,
releaseDateContentCss
=
".wrapper-release .copy"
,
releaseDateContentCss
=
".wrapper-release .copy"
,
lastRequest
,
promptSpies
,
sendDiscardChangesToServer
;
promptSpies
,
sendDiscardChangesToServer
;
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
];
};
sendDiscardChangesToServer
=
function
(
test
)
{
sendDiscardChangesToServer
=
function
()
{
// Helper function to do the discard operation, up until the server response.
// Helper function to do the discard operation, up until the server response.
renderContainerPage
(
mockContainerXBlockHtml
,
test
);
containerPage
.
render
();
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"has_changes"
:
true
});
respondWithHtml
(
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"has_changes"
:
true
});
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
'is-disabled'
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
'is-disabled'
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draftBit
);
// Click discard changes
// Click discard changes
...
@@ -132,30 +146,30 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -132,30 +146,30 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
it
(
'renders correctly with private content'
,
function
()
{
it
(
'renders correctly with private content'
,
function
()
{
var
verifyPrivateState
=
function
(){
var
verifyPrivateState
=
function
()
{
// State is the same regardless of "has_changes" value.
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Draft (Never published)'
);
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Draft (Unpublished changes)'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
};
};
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
false
,
"has_changes"
:
false
});
fetch
({
"published"
:
false
,
"has_changes"
:
false
});
verifyPrivateState
();
verifyPrivateState
();
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
false
,
"has_changes"
:
true
});
fetch
({
"published"
:
false
,
"has_changes"
:
true
});
verifyPrivateState
();
verifyPrivateState
();
});
});
it
(
'renders correctly with public content'
,
function
()
{
it
(
'renders correctly with public content'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
true
,
"has_changes"
:
false
});
fetch
({
"published"
:
true
,
"has_changes"
:
false
});
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Published'
);
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Published'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
publishedBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
publishedBit
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
true
,
"has_changes"
:
true
});
fetch
({
"published"
:
true
,
"has_changes"
:
true
});
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Draft (Unpublished changes)'
);
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Draft (Unpublished changes)'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
...
@@ -164,9 +178,10 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -164,9 +178,10 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'can publish private content'
,
function
()
{
it
(
'can publish private content'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
false
,
"has_changes"
:
false
});
fetch
({
"published"
:
false
,
"has_changes"
:
false
});
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
// Click publish
// Click publish
containerPage
.
$
(
publishButtonCss
).
click
();
containerPage
.
$
(
publishButtonCss
).
click
();
...
@@ -191,9 +206,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -191,9 +206,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
it
(
'can does not fetch if publish fails'
,
function
()
{
it
(
'can does not fetch if publish fails'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"
id"
:
"locator-container"
,
"published"
:
false
,
"has_changes
"
:
false
});
fetch
({
"
published
"
:
false
});
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draft
Bit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
published
Bit
);
// Click publish
// Click publish
containerPage
.
$
(
publishButtonCss
).
click
();
containerPage
.
$
(
publishButtonCss
).
click
();
...
@@ -205,17 +220,18 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -205,17 +220,18 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect
(
requests
.
length
).
toEqual
(
numRequests
);
expect
(
requests
.
length
).
toEqual
(
numRequests
);
// Verify still in draft state.
// Verify still in draft state.
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draft
Bit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
published
Bit
);
// Verify that the "published" value has been cleared out of the model.
// Verify that the "published" value has been cleared out of the model.
expect
(
containerPage
.
model
.
get
(
"publish"
)).
toBeNull
();
expect
(
containerPage
.
model
.
get
(
"publish"
)).
toBeNull
();
});
});
it
(
'can discard changes'
,
function
()
{
it
(
'can discard changes'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
(),
var
notificationSpy
,
renderPageSpy
,
numRequests
;
renderPageSpy
=
spyOn
(
containerPage
.
xblockPublisher
,
'renderPage'
).
andCallThrough
(),
createContainerPage
(
this
);
numRequests
;
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderPageSpy
=
spyOn
(
containerPage
.
xblockPublisher
,
'renderPage'
).
andCallThrough
();
sendDiscardChangesToServer
(
this
);
sendDiscardChangesToServer
();
numRequests
=
requests
.
length
;
numRequests
=
requests
.
length
;
// Respond with success.
// Respond with success.
...
@@ -230,10 +246,11 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -230,10 +246,11 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
it
(
'does not fetch if discard changes fails'
,
function
()
{
it
(
'does not fetch if discard changes fails'
,
function
()
{
var
renderPageSpy
=
spyOn
(
containerPage
.
xblockPublisher
,
'renderPage'
).
andCallThrough
(),
var
renderPageSpy
,
numRequests
;
numRequests
;
createContainerPage
(
this
);
renderPageSpy
=
spyOn
(
containerPage
.
xblockPublisher
,
'renderPage'
).
andCallThrough
();
sendDiscardChangesToServer
(
this
);
sendDiscardChangesToServer
();
numRequests
=
requests
.
length
;
numRequests
=
requests
.
length
;
// Respond with failure
// Respond with failure
...
@@ -246,8 +263,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -246,8 +263,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
it
(
'does not discard changes on cancel'
,
function
()
{
it
(
'does not discard changes on cancel'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"
id"
:
"locator-container"
,
"
published"
:
true
,
"has_changes"
:
true
});
fetch
({
"published"
:
true
,
"has_changes"
:
true
});
var
numRequests
=
requests
.
length
;
var
numRequests
=
requests
.
length
;
// Click discard changes
// Click discard changes
...
@@ -262,52 +279,49 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -262,52 +279,49 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
it
(
'renders the last published date and user when there are no changes'
,
function
()
{
it
(
'renders the last published date and user when there are no changes'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"has_changes"
:
false
,
fetch
({
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
"edited_on"
:
"Jun 30, 2014 at 14:20 UTC"
,
"edited_by"
:
"joe"
,
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
expect
(
containerPage
.
$
(
lastDraftCss
).
text
()).
expect
(
containerPage
.
$
(
lastDraftCss
).
text
()).
toContain
(
"Last published Jul 01, 2014 at 12:45 UTC by amako"
);
toContain
(
"Last published Jul 01, 2014 at 12:45 UTC by amako"
);
});
});
it
(
'renders the last saved date and user when there are changes'
,
function
()
{
it
(
'renders the last saved date and user when there are changes'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"has_changes"
:
true
,
fetch
({
"has_changes"
:
true
,
"edited_on"
:
"Jul 02, 2014 at 14:20 UTC"
,
"edited_by"
:
"joe"
});
"edited_on"
:
"Jul 02, 2014 at 14:20 UTC"
,
"edited_by"
:
"joe"
,
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
expect
(
containerPage
.
$
(
lastDraftCss
).
text
()).
expect
(
containerPage
.
$
(
lastDraftCss
).
text
()).
toContain
(
"Draft saved on Jul 02, 2014 at 14:20 UTC by joe"
);
toContain
(
"Draft saved on Jul 02, 2014 at 14:20 UTC by joe"
);
});
});
it
(
'renders the release date correctly when unreleased'
,
function
()
{
describe
(
"Release Date"
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
it
(
'renders correctly when unreleased'
,
function
()
{
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"released_to_students"
:
false
,
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"released_to_students"
:
false
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Scheduled:"
);
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Scheduled:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
});
});
it
(
'renders the release date
correctly when released'
,
function
()
{
it
(
'renders
correctly when released'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"released_to_students"
:
true
,
fetch
({
"published"
:
true
,
"released_to_students"
:
true
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Released:"
);
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Released:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
});
});
it
(
'renders the release date
correctly when the release date is not set'
,
function
()
{
it
(
'renders
correctly when the release date is not set'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"released_to_students"
:
false
,
fetch
({
"published"
:
true
,
"released_to_students"
:
false
,
"release_date"
:
null
,
"release_date_from"
:
null
});
"release_date"
:
null
,
"release_date_from"
:
null
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Release:"
);
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Release:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
"Unscheduled"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
"Unscheduled"
);
});
});
it
(
'renders the release date
correctly when the unit is not published'
,
function
()
{
it
(
'renders
correctly when the unit is not published'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
false
,
"released_to_students"
:
true
,
fetch
({
"published"
:
false
,
"released_to_students"
:
true
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
// Force a render because none of the fetched fields will trigger a render
// Force a render because none of the fetched fields will trigger a render
containerPage
.
xblockPublisher
.
render
();
containerPage
.
xblockPublisher
.
render
();
...
@@ -317,29 +331,161 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
...
@@ -317,29 +331,161 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
});
});
});
describe
(
"Content Visibility"
,
function
()
{
var
requestStaffOnly
,
verifyStaffOnly
,
promptSpy
;
requestStaffOnly
=
function
(
isStaffOnly
)
{
containerPage
.
$
(
'.action-staff-lock'
).
click
();
// If removing the staff lock, click 'Yes' to confirm
if
(
!
isStaffOnly
)
{
edit_helpers
.
confirmPrompt
(
promptSpy
);
}
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/locator-container'
,
{
publish
:
'republish'
,
metadata
:
{
visible_to_staff_only
:
isStaffOnly
}
});
create_sinon
.
respondWithJson
(
requests
,
{
data
:
null
,
id
:
"locator-container"
,
metadata
:
{
visible_to_staff_only
:
isStaffOnly
}
});
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
create_sinon
.
respondWithJson
(
requests
,
createXBlockInfo
({
published
:
containerPage
.
model
.
get
(
'published'
),
visible_to_staff_only
:
isStaffOnly
}));
};
verifyStaffOnly
=
function
(
isStaffOnly
)
{
if
(
isStaffOnly
)
{
expect
(
containerPage
.
$
(
'.action-staff-lock i'
)).
toHaveClass
(
'icon-check'
);
expect
(
containerPage
.
$
(
'.wrapper-visibility .copy'
).
text
()).
toBe
(
'Staff Only'
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
staffOnlyBit
);
}
else
{
expect
(
containerPage
.
$
(
'.action-staff-lock i'
)).
toHaveClass
(
'icon-check-empty'
);
expect
(
containerPage
.
$
(
'.wrapper-visibility .copy'
).
text
()).
toBe
(
'Staff and Students'
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
staffOnlyBit
);
}
};
it
(
"is initially shown to all"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
verifyStaffOnly
(
false
);
});
it
(
"can be set to staff only"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
containerPage
.
$
(
'.action-staff-lock'
).
click
();
requestStaffOnly
(
true
);
verifyStaffOnly
(
true
);
});
it
(
"can remove staff only setting"
,
function
()
{
promptSpy
=
edit_helpers
.
createPromptSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
requestStaffOnly
(
true
);
requestStaffOnly
(
false
);
verifyStaffOnly
(
false
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
});
it
(
"can remove staff only setting from published unit"
,
function
()
{
promptSpy
=
edit_helpers
.
createPromptSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
published
:
true
});
requestStaffOnly
(
true
);
requestStaffOnly
(
false
);
verifyStaffOnly
(
false
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
publishedBit
);
});
it
(
"does not refresh if removing staff only is canceled"
,
function
()
{
var
requestCount
;
promptSpy
=
edit_helpers
.
createPromptSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
requestStaffOnly
(
true
);
requestCount
=
requests
.
length
;
containerPage
.
$
(
'.action-staff-lock'
).
click
();
edit_helpers
.
confirmPrompt
(
promptSpy
,
true
);
// Click 'No' to cancel
expect
(
requests
.
length
).
toBe
(
requestCount
);
verifyStaffOnly
(
true
);
});
it
(
"does not refresh when failing to set staff only"
,
function
()
{
var
requestCount
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
containerPage
.
$
(
'.lock-checkbox'
).
click
();
requestCount
=
requests
.
length
;
create_sinon
.
respondWithError
(
requests
);
expect
(
requests
.
length
).
toBe
(
requestCount
);
verifyStaffOnly
(
false
);
});
});
});
describe
(
"PublishHistory"
,
function
()
{
describe
(
"PublishHistory"
,
function
()
{
var
lastPublishCss
=
".wrapper-last-publish"
;
var
lastPublishCss
=
".wrapper-last-publish"
;
it
(
'renders the last published date and user when the block is published'
,
function
()
{
it
(
'renders the last published date and user when the block is published'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
fetch
({
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
"published"
:
true
,
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Last published Jul 01, 2014 at 12:45 UTC by amako"
);
toContain
(
"Last published Jul 01, 2014 at 12:45 UTC by amako"
);
});
});
it
(
'renders never published when the block is unpublished'
,
function
()
{
it
(
'renders never published when the block is unpublished'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
false
,
fetch
({
"published"
:
false
});
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Never published"
);
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Never published"
);
});
});
it
(
'renders correctly when the block is published without publish info'
,
function
()
{
it
(
'renders correctly when the block is published without publish info'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"published_on"
:
null
,
"published_by"
:
null
});
fetch
({
"published"
:
true
,
"published_on"
:
null
,
"published_by"
:
null
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Previously published"
);
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Previously published"
);
});
});
});
});
describe
(
"Message Area"
,
function
()
{
var
messageSelector
=
'.container-message .warning'
,
warningMessage
=
'This content is live for students. Edit with caution.'
;
it
(
'is empty for a unit that is not currently visible to students'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
currently_visible_to_students
:
false
});
expect
(
containerPage
.
$
(
messageSelector
).
text
().
trim
()).
toBe
(
''
);
});
it
(
'shows a message for a unit that is currently visible to students'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
currently_visible_to_students
:
true
});
expect
(
containerPage
.
$
(
messageSelector
).
text
().
trim
()).
toBe
(
warningMessage
);
});
it
(
'hides the message when the unit is hidden from students'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
currently_visible_to_students
:
true
});
fetch
({
currently_visible_to_students
:
false
});
expect
(
containerPage
.
$
(
messageSelector
).
text
().
trim
()).
toBe
(
''
);
});
it
(
'shows a message when a unit is made visible'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
currently_visible_to_students
:
false
});
fetch
({
currently_visible_to_students
:
true
});
expect
(
containerPage
.
$
(
messageSelector
).
text
().
trim
()).
toBe
(
warningMessage
);
});
});
});
});
});
});
cms/static/js/views/pages/container.js
View file @
d447c075
...
@@ -29,6 +29,11 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -29,6 +29,11 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
model
:
this
.
model
,
model
:
this
.
model
,
view
:
this
.
view
view
:
this
.
view
});
});
this
.
messageView
=
new
ContainerSubviews
.
MessageView
({
el
:
this
.
$
(
'.container-message'
),
model
:
this
.
model
});
this
.
messageView
.
render
();
this
.
isUnitPage
=
this
.
options
.
isUnitPage
;
this
.
isUnitPage
=
this
.
options
.
isUnitPage
;
if
(
this
.
isUnitPage
)
{
if
(
this
.
isUnitPage
)
{
this
.
xblockPublisher
=
new
ContainerSubviews
.
Publisher
({
this
.
xblockPublisher
=
new
ContainerSubviews
.
Publisher
({
...
...
cms/static/js/views/pages/container_subviews.js
View file @
d447c075
...
@@ -10,7 +10,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
...
@@ -10,7 +10,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
* A view that calls render when "has_changes" or "published" values in XBlockInfo have changed
* A view that calls render when "has_changes" or "published" values in XBlockInfo have changed
* after a server sync operation.
* after a server sync operation.
*/
*/
var
UnitStateListenerView
=
BaseView
.
extend
({
var
ContainerStateListenerView
=
BaseView
.
extend
({
// takes XBlockInfo as a model
// takes XBlockInfo as a model
initialize
:
function
()
{
initialize
:
function
()
{
...
@@ -18,18 +18,43 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
...
@@ -18,18 +18,43 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
},
onSync
:
function
(
model
)
{
onSync
:
function
(
model
)
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'has_changes'
,
'published'
]
))
{
if
(
this
.
shouldRefresh
(
model
))
{
this
.
render
();
this
.
render
();
}
}
},
},
shouldRefresh
:
function
(
model
)
{
return
false
;
},
render
:
function
()
{}
render
:
function
()
{}
});
});
var
MessageView
=
ContainerStateListenerView
.
extend
({
initialize
:
function
()
{
ContainerStateListenerView
.
prototype
.
initialize
.
call
(
this
);
this
.
template
=
this
.
loadTemplate
(
'container-message'
);
},
shouldRefresh
:
function
(
model
)
{
return
ViewUtils
.
hasChangedAttributes
(
model
,
[
'currently_visible_to_students'
]);
},
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
({
currentlyVisibleToStudents
:
this
.
model
.
get
(
'currently_visible_to_students'
)
}));
return
this
;
}
});
/**
/**
* A controller for updating the "View Live" and "Preview" buttons.
* A controller for updating the "View Live" and "Preview" buttons.
*/
*/
var
PreviewActionController
=
UnitStateListenerView
.
extend
({
var
PreviewActionController
=
ContainerStateListenerView
.
extend
({
shouldRefresh
:
function
(
model
)
{
return
ViewUtils
.
hasChangedAttributes
(
model
,
[
'has_changes'
,
'published'
]);
},
render
:
function
()
{
render
:
function
()
{
var
previewAction
=
this
.
$el
.
find
(
'.button-preview'
),
var
previewAction
=
this
.
$el
.
find
(
'.button-preview'
),
...
@@ -59,7 +84,8 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
...
@@ -59,7 +84,8 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
var
Publisher
=
BaseView
.
extend
({
var
Publisher
=
BaseView
.
extend
({
events
:
{
events
:
{
'click .action-publish'
:
'publish'
,
'click .action-publish'
:
'publish'
,
'click .action-discard'
:
'discardChanges'
'click .action-discard'
:
'discardChanges'
,
'click .action-staff-lock'
:
'toggleStaffLock'
},
},
// takes XBlockInfo as a model
// takes XBlockInfo as a model
...
@@ -72,22 +98,25 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
...
@@ -72,22 +98,25 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
},
onSync
:
function
(
model
)
{
onSync
:
function
(
model
)
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'has_changes'
,
'published'
,
'edited_on'
,
'edited_by'
]))
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'has_changes'
,
'published'
,
'edited_on'
,
'edited_by'
,
'visible_to_staff_only'
]))
{
this
.
render
();
this
.
render
();
}
}
},
},
render
:
function
()
{
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
({
this
.
$el
.
html
(
this
.
template
({
has
_c
hanges
:
this
.
model
.
get
(
'has_changes'
),
has
C
hanges
:
this
.
model
.
get
(
'has_changes'
),
published
:
this
.
model
.
get
(
'published'
),
published
:
this
.
model
.
get
(
'published'
),
edited_on
:
this
.
model
.
get
(
'edited_on'
),
editedOn
:
this
.
model
.
get
(
'edited_on'
),
edited_by
:
this
.
model
.
get
(
'edited_by'
),
editedBy
:
this
.
model
.
get
(
'edited_by'
),
published_on
:
this
.
model
.
get
(
'published_on'
),
publishedOn
:
this
.
model
.
get
(
'published_on'
),
published_by
:
this
.
model
.
get
(
'published_by'
),
publishedBy
:
this
.
model
.
get
(
'published_by'
),
released_to_students
:
this
.
model
.
get
(
'released_to_students'
),
releasedToStudents
:
this
.
model
.
get
(
'released_to_students'
),
release_date
:
this
.
model
.
get
(
'release_date'
),
releaseDate
:
this
.
model
.
get
(
'release_date'
),
release_date_from
:
this
.
model
.
get
(
'release_date_from'
)
releaseDateFrom
:
this
.
model
.
get
(
'release_date_from'
),
visibleToStaffOnly
:
this
.
model
.
get
(
'visible_to_staff_only'
)
}));
}));
return
this
;
return
this
;
...
@@ -127,9 +156,59 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
...
@@ -127,9 +156,59 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
});
});
}
}
);
);
},
toggleStaffLock
:
function
(
e
)
{
var
xblockInfo
=
this
.
model
,
self
=
this
,
enableStaffLock
,
saveAndPublishStaffLock
,
revertCheckBox
;
if
(
e
&&
e
.
preventDefault
)
{
e
.
preventDefault
();
}
}
enableStaffLock
=
!
xblockInfo
.
get
(
'visible_to_staff_only'
);
revertCheckBox
=
function
()
{
self
.
checkStaffLock
(
!
enableStaffLock
);
};
saveAndPublishStaffLock
=
function
()
{
return
xblockInfo
.
save
({
publish
:
'republish'
,
metadata
:
{
visible_to_staff_only
:
enableStaffLock
}},
{
patch
:
true
}
).
always
(
function
()
{
xblockInfo
.
set
(
"publish"
,
null
);
}).
done
(
function
()
{
xblockInfo
.
fetch
();
}).
fail
(
function
()
{
revertCheckBox
();
});
});
};
this
.
checkStaffLock
(
enableStaffLock
);
if
(
enableStaffLock
)
{
ViewUtils
.
runOperationShowingMessage
(
gettext
(
'Setting Staff Lock…'
),
_
.
bind
(
saveAndPublishStaffLock
,
self
));
}
else
{
ViewUtils
.
confirmThenRunOperation
(
gettext
(
"Remove Staff Lock"
),
gettext
(
"Are you sure you want to remove the staff lock? Once you publish this unit, it will be released to students on the release date."
),
gettext
(
"Remove Staff Lock"
),
function
()
{
ViewUtils
.
runOperationShowingMessage
(
gettext
(
'Removing Staff Lock…'
),
_
.
bind
(
saveAndPublishStaffLock
,
self
));
},
function
()
{
// On cancel, revert the check in the check box
revertCheckBox
();
}
);
}
},
checkStaffLock
:
function
(
check
)
{
this
.
$
(
'.action-staff-lock i'
).
removeClass
(
'icon-check icon-check-empty'
);
this
.
$
(
'.action-staff-lock i'
).
addClass
(
check
?
'icon-check'
:
'icon-check-empty'
);
}
});
/**
/**
* PublishHistory displays when and by whom the xblock was last published, if it ever was.
* PublishHistory displays when and by whom the xblock was last published, if it ever was.
...
@@ -161,6 +240,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
...
@@ -161,6 +240,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
});
});
return
{
return
{
'MessageView'
:
MessageView
,
'PreviewActionController'
:
PreviewActionController
,
'PreviewActionController'
:
PreviewActionController
,
'Publisher'
:
Publisher
,
'Publisher'
:
Publisher
,
'PublishHistory'
:
PublishHistory
'PublishHistory'
:
PublishHistory
...
...
cms/static/js/views/utils/view_utils.js
View file @
d447c075
...
@@ -33,7 +33,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
...
@@ -33,7 +33,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
/**
/**
* Confirms with the user whether to run an operation or not, and then runs it if desired.
* Confirms with the user whether to run an operation or not, and then runs it if desired.
*/
*/
confirmThenRunOperation
=
function
(
title
,
message
,
actionLabel
,
operation
)
{
confirmThenRunOperation
=
function
(
title
,
message
,
actionLabel
,
operation
,
onCancelCallback
)
{
return
new
PromptView
.
Warning
({
return
new
PromptView
.
Warning
({
title
:
title
,
title
:
title
,
message
:
message
,
message
:
message
,
...
@@ -48,6 +48,9 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
...
@@ -48,6 +48,9 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
secondary
:
{
secondary
:
{
text
:
gettext
(
'Cancel'
),
text
:
gettext
(
'Cancel'
),
click
:
function
(
prompt
)
{
click
:
function
(
prompt
)
{
if
(
onCancelCallback
)
{
onCancelCallback
();
}
return
prompt
.
hide
();
return
prompt
.
hide
();
}
}
}
}
...
...
cms/static/sass/_developer.scss
View file @
d447c075
...
@@ -11,8 +11,7 @@
...
@@ -11,8 +11,7 @@
//.wrapper-xblock-header {
//.wrapper-xblock-header {
.view-outline
,
.view-outline
{
.view-container
{
.add-xblock-component
{
.add-xblock-component
{
text-align
:
center
;
text-align
:
center
;
...
...
cms/static/sass/views/_container.scss
View file @
d447c075
...
@@ -111,11 +111,15 @@
...
@@ -111,11 +111,15 @@
&
.staff-only
,
&
.staff-only
,
&
.is-staff-only
{
&
.is-staff-only
{
@extend
%bar-module-black
;
@extend
%bar-module-black
;
&
.is-scheduled
.wrapper-release
.copy
{
text-decoration
:
line-through
;
}
}
}
.bar-mod-content
{
.bar-mod-content
{
border
:
0
;
border
:
0
;
padding
:
(
$baseline
/
2
)
(
$baseline
*.
75
)
(
$baseline
*.
75
)
(
$baseline
*.
75
);
padding
:
(
$baseline
/
2
)
(
$baseline
*.
75
)
(
$baseline
/
4
)
(
$baseline
*.
75
);
.title
{
.title
{
margin-bottom
:
(
$baseline
/
10
);
margin-bottom
:
(
$baseline
/
10
);
...
@@ -123,7 +127,6 @@
...
@@ -123,7 +127,6 @@
}
}
.wrapper-last-draft
{
.wrapper-last-draft
{
padding
:
(
$baseline
*.
75
)
(
$baseline
*.
75
)
(
$baseline
/
4
)
(
$baseline
*.
75
);
.date
,
.date
,
.user
{
.user
{
...
@@ -145,9 +148,9 @@
...
@@ -145,9 +148,9 @@
font-weight
:
600
;
font-weight
:
600
;
}
}
.action-inline
[
class
^=
"icon-"
]
{
[
class
^=
"icon-"
]
{
margin
:
0
(
$baseline
/
4
);
margin
-left
:
(
$baseline
/
4
);
color
:
$gray-d1
;
}
}
}
}
...
@@ -215,107 +218,7 @@
...
@@ -215,107 +218,7 @@
}
}
.wrapper-unit-tree-location
{
.wrapper-unit-tree-location
{
// tree location-specific styles should go here
.draggable-drop-indicator
{
display
:
none
;
}
// need to explicitly set this since the html structure is different than the others
.section-name
:hover
{
background
:
$blue-l5
;
color
:
$blue
;
}
.subsection
,
.courseware-unit
{
margin
:
(
$baseline
/
4
)
0
0
(
$baseline
*.
75
);
}
.courseware-unit
.section-item
{
background-color
:
transparent
;
}
.section-item
{
@include
transition
(
background
$tmg-avg
ease-in-out
0
);
@include
box-sizing
(
border-box
);
@extend
%t-copy-sub2
;
width
:
100%
;
display
:
inline-block
;
vertical-align
:
top
;
overflow
:
hidden
;
padding
:
6px
8px
8px
16px
;
background
:
$gray-l5
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
color
:
$gray
;
&
:hover
{
background
:
$blue-l5
;
color
:
$blue
;
}
&
.editing
{
background-color
:
$orange-l3
;
}
// TODO: update these once we have different pub states
.public-item
{
color
:
$black
;
}
.private-item
{
color
:
$gray-l1
;
}
.draft-item
{
color
:
$yellow-d1
;
}
.public-item
:hover
,
.private-item
:hover
,
.draft-item
:hover
{
color
:
$blue
;
}
.draft-item
:after
,
.public-item
:after
,
.private-item
:after
{
@include
font-size
(
9
);
margin-left
:
3px
;
font-weight
:
600
;
text-transform
:
uppercase
;
}
.draft-item
:after
{
content
:
"- draft"
;
}
.private-item
:after
{
content
:
"- private"
;
}
}
.subsection
>
.section-item
:hover
{
background-color
:
$gray-l5
;
color
:
inherit
;
}
.new-unit-item
{
@extend
%ui-btn-flat-outline
;
@extend
%t-action4
;
width
:
90%
;
margin
:
0
0
(
$baseline
/
2
)
(
$baseline
/
4
);
border
:
1px
solid
transparent
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
font-weight
:
normal
;
color
:
$gray-l2
;
text-align
:
left
;
&
:hover
{
box-shadow
:
none
;
background-image
:
none
;
}
}
}
}
}
}
}
}
...
...
cms/templates/container.html
View file @
d447c075
...
@@ -25,7 +25,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
...
@@ -25,7 +25,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
"
editor-mode-button
",
"
upload-dialog
",
"
image-modal
",
"
editor-mode-button
",
"
upload-dialog
",
"
image-modal
",
"
add-xblock-component
",
"
add-xblock-component-button
",
"
add-xblock-component-menu
",
"
add-xblock-component
",
"
add-xblock-component-button
",
"
add-xblock-component-menu
",
"
add-xblock-component-menu-problem
",
"
xblock-string-field-editor
",
"
publish-xblock
",
"
publish-history
",
"
add-xblock-component-menu-problem
",
"
xblock-string-field-editor
",
"
publish-xblock
",
"
publish-history
",
"
unit-outline
"]
"
unit-outline
"
,
"
container-message
"
]
%
>
%
>
<
%
block
name=
"header_extras"
>
<
%
block
name=
"header_extras"
>
% for template_name in templates:
% for template_name in templates:
...
@@ -116,16 +116,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
...
@@ -116,16 +116,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
<section
class=
"content-area"
>
<section
class=
"content-area"
>
<article
class=
"content-primary"
>
<article
class=
"content-primary"
>
% if is_visible_to_students:
<div
class=
"container-message wrapper-message"
></div>
<div
class=
"container-message wrapper-message"
>
<div
class=
"message has-warnings"
>
<p
class=
"warning"
>
<i
class=
"icon-warning-sign"
></i>
${_("This content is live for students. Edit with caution.")}
</p>
</div>
</div>
% endif
<section
class=
"wrapper-xblock level-page is-hidden studio-xblock-wrapper"
data-locator=
"${xblock_locator}"
data-course-key=
"${xblock_locator.course_key}"
>
<section
class=
"wrapper-xblock level-page is-hidden studio-xblock-wrapper"
data-locator=
"${xblock_locator}"
data-course-key=
"${xblock_locator.course_key}"
>
</section>
</section>
<div
class=
"ui-loading"
>
<div
class=
"ui-loading"
>
...
...
cms/templates/js/container-message.underscore
0 → 100644
View file @
d447c075
<% if (currentlyVisibleToStudents) { %>
<div class="message has-warnings">
<p class="warning">
<i class="icon-warning-sign"></i>
<%= gettext("This content is live for students. Edit with caution.") %>
</p>
</div>
<% } %>
cms/templates/js/course-outline.underscore
View file @
d447c075
...
@@ -14,8 +14,8 @@
...
@@ -14,8 +14,8 @@
<% if (xblockInfo.get('category') === 'vertical') { %>
<% if (xblockInfo.get('category') === 'vertical') { %>
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<% } else { %>
<% } else { %>
<span class="wrapper-xblock-field is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="wrapper-xblock-field i
ncontext-editor i
s-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="xblock-field-value"><%= xblockInfo.get('display_name') %></span>
<span class="xblock-field-value
incontext-editor-value
"><%= xblockInfo.get('display_name') %></span>
</span>
</span>
<% } %>
<% } %>
</h3>
</h3>
...
...
cms/templates/js/mock/mock-container-page.underscore
View file @
d447c075
...
@@ -43,6 +43,7 @@
...
@@ -43,6 +43,7 @@
<section class="content-area">
<section class="content-area">
<article class="content-primary window">
<article class="content-primary window">
<div class="container-message wrapper-message"></div>
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="locator-container">
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="locator-container">
</section>
</section>
<div class="ui-loading is-hidden">
<div class="ui-loading is-hidden">
...
...
cms/templates/js/publish-xblock.underscore
View file @
d447c075
<div class="bit-publishing <% if (published && !has_changes) { %>published<% } else { %>draft<%} %>">
<%
var publishClasses = "";
var title = gettext("Draft (Never published)");
if (published) {
if (published && hasChanges) {
publishClasses = publishClasses + " is-draft";
title = gettext("Draft (Unpublished changes)");
} else {
publishClasses = publishClasses + " is-published";
title = gettext("Published");
}
}
if (releaseDate) {
publishClasses = publishClasses + " is-scheduled";
}
if (visibleToStaffOnly) {
publishClasses = publishClasses + " is-staff-only";
title = gettext("Unpublished (Staff only)");
}
%>
<div class="bit-publishing <%= publishClasses %>">
<h3 class="bar-mod-title pub-status"><span class="sr"><%= gettext("Publishing Status") %></span>
<h3 class="bar-mod-title pub-status"><span class="sr"><%= gettext("Publishing Status") %></span>
<% if (published && !has_changes) { %>
<%= title %>
<%= gettext("Published") %>
<% } else { %>
<%= gettext("Draft (Unpublished changes)") %>
<% } %>
</h3>
</h3>
<div class="wrapper-last-draft bar-mod-content">
<div class="wrapper-last-draft bar-mod-content">
<p class="copy meta">
<p class="copy meta">
<% if (has
_changes && edited_on && edited_b
y) {
<% if (has
Changes && editedOn && editedB
y) {
var message = gettext("Draft saved on %(last_saved_date)s by %(edit_username)s") %>
var message = gettext("Draft saved on %(last_saved_date)s by %(edit_username)s") %>
<%= interpolate(message, {
<%= interpolate(message, {
last_saved_date: '<span class="date">' + edited
_o
n + '</span>',
last_saved_date: '<span class="date">' + edited
O
n + '</span>',
edit_username: '<span class="user">' + edited
_b
y + '</span>' }, true) %>
edit_username: '<span class="user">' + edited
B
y + '</span>' }, true) %>
<% } else if (published
_on && published_b
y) {
<% } else if (published
On && publishedB
y) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
<%= interpolate(message, {
<%= interpolate(message, {
last_published_date: '<span class="date">' + published
_o
n + '</span>',
last_published_date: '<span class="date">' + published
O
n + '</span>',
publish_username: '<span class="user">' + published
_b
y + '</span>' }, true) %>
publish_username: '<span class="user">' + published
B
y + '</span>' }, true) %>
<% } else { %>
<% } else { %>
<%= gettext("Previously published") %>
<%= gettext("Previously published") %>
<% } %>
<% } %>
</p>
</p>
</div>
</div>
<!--TODO this needs strikeout styles once staff lock exists-->
<div class="wrapper-release bar-mod-content">
<div class="wrapper-release bar-mod-content">
<h5 class="title">
<h5 class="title">
<% if (published && release
_d
ate) {
<% if (published && release
D
ate) {
if (released
_to_s
tudents) { %>
if (released
ToS
tudents) { %>
<%= gettext("Released:") %>
<%= gettext("Released:") %>
<% } else { %>
<% } else { %>
<%= gettext("Scheduled:") %>
<%= gettext("Scheduled:") %>
...
@@ -39,37 +54,45 @@
...
@@ -39,37 +54,45 @@
<% } %>
<% } %>
</h5>
</h5>
<p class="copy">
<p class="copy">
<% if (release
_d
ate) { %>
<% if (release
D
ate) { %>
<% var message = gettext("%(release_date)s with %(section_or_subsection)s") %>
<% var message = gettext("%(release_date)s with %(section_or_subsection)s") %>
<%= interpolate(message, {
<%= interpolate(message, {
release_date: '<span class="release-date">' + release
_d
ate + '</span>',
release_date: '<span class="release-date">' + release
D
ate + '</span>',
section_or_subsection: '<span class="release-with">' + release
_date_f
rom + '</span>' }, true) %>
section_or_subsection: '<span class="release-with">' + release
DateF
rom + '</span>' }, true) %>
<% } else { %>
<% } else { %>
<%= gettext("Unscheduled") %>
<%= gettext("Unscheduled") %>
<% } %>
<% } %>
</p>
</p>
</div>
</div>
<!--To be added in STUD-1830-->
<div class="wrapper-visibility bar-mod-content">
<!--<div class="wrapper-visibility bar-mod-content">-->
<h5 class="title"><%= gettext("Will Be Visible To:") %></h5>
<!--<h5 class="title">Will be Visible to:</h5>-->
<% if (visibleToStaffOnly) { %>
<!--<p class="copy">Staff and Students</p>-->
<p class="copy"><%= gettext("Staff Only") %></p>
<!--<p class="action-inline">-->
<% } else { %>
<!--<a href="">-->
<p class="copy"><%= gettext("Staff and Students") %></p>
<!--<i class="icon-unlock is-disabled"></i> Hide from Students-->
<% } %>
<!--</a>-->
<p class="action-inline">
<!--</p>-->
<a href="" class="action-staff-lock" role="button" aria-pressed="<%= visibleToStaffOnly %>">
<!--</div>-->
<% if (visibleToStaffOnly) { %>
<i class="icon-check"></i>
<% } else { %>
<i class="icon-check-empty"></i>
<% } %>
<%= gettext('Hide from students') %>
</a>
</p>
</div>
<div class="wrapper-pub-actions bar-mod-actions">
<div class="wrapper-pub-actions bar-mod-actions">
<ul class="action-list">
<ul class="action-list">
<li class="action-item">
<li class="action-item">
<a class="action-publish action-primary <% if (published && !has
_c
hanges) { %>is-disabled<% } %>"
<a class="action-publish action-primary <% if (published && !has
C
hanges) { %>is-disabled<% } %>"
href=""><%= gettext("Publish") %>
href=""><%= gettext("Publish") %>
</a>
</a>
</li>
</li>
<li class="action-item">
<li class="action-item">
<a class="action-discard action-secondary <% if (!published || !has
_c
hanges) { %>is-disabled<% } %>"
<a class="action-discard action-secondary <% if (!published || !has
C
hanges) { %>is-disabled<% } %>"
href=""><%= gettext("Discard Changes") %>
href=""><%= gettext("Discard Changes") %>
</a>
</a>
</li>
</li>
...
...
common/test/acceptance/pages/lms/staff_view.py
View file @
d447c075
...
@@ -10,16 +10,24 @@ class StaffPage(PageObject):
...
@@ -10,16 +10,24 @@ class StaffPage(PageObject):
"""
"""
url
=
None
url
=
None
STAFF_STATUS_CSS
=
'#staffstatus'
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
q
(
css
=
'#staffstatus'
)
.
present
return
self
.
q
(
css
=
self
.
STAFF_STATUS_CSS
)
.
present
@property
@property
def
staff_status
(
self
):
def
staff_status
(
self
):
"""
"""
Return the current status, either Staff view or Student view
Return the current status, either Staff view or Student view
"""
"""
return
self
.
q
(
css
=
'#staffstatus'
)
.
text
[
0
]
return
self
.
q
(
css
=
self
.
STAFF_STATUS_CSS
)
.
text
[
0
]
def
toggle_staff_view
(
self
):
"""
Toggle between staff view and student view.
"""
self
.
q
(
css
=
self
.
STAFF_STATUS_CSS
)
.
first
.
click
()
self
.
wait_for_ajax
()
def
open_staff_debug_info
(
self
):
def
open_staff_debug_info
(
self
):
"""
"""
...
...
common/test/acceptance/pages/studio/container.py
View file @
d447c075
...
@@ -82,6 +82,33 @@ class ContainerPage(PageObject):
...
@@ -82,6 +82,33 @@ class ContainerPage(PageObject):
return
self
.
q
(
css
=
'.pub-status'
)
.
first
.
text
[
0
]
return
self
.
q
(
css
=
'.pub-status'
)
.
first
.
text
[
0
]
@property
@property
def
release_title
(
self
):
"""
Returns the title before the release date in the publishing sidebar component.
"""
return
self
.
q
(
css
=
'.wrapper-release .title'
)
.
first
.
text
[
0
]
@property
def
release_date
(
self
):
"""
Returns the release date of the unit (with ancestor inherited from), as displayed
in the publishing sidebar component.
"""
return
self
.
q
(
css
=
'.wrapper-release .copy'
)
.
first
.
text
[
0
]
@property
def
currently_visible_to_students
(
self
):
"""
Returns True if the unit is marked as currently visible to students
(meaning that a warning is being displayed).
"""
warnings
=
self
.
q
(
css
=
'.container-message .warning'
)
if
not
warnings
.
is_present
():
return
False
warning_text
=
warnings
.
first
.
text
[
0
]
return
warning_text
==
"This content is live for students. Edit with caution."
@property
def
publish_action
(
self
):
def
publish_action
(
self
):
"""
"""
Returns the link for publishing a unit.
Returns the link for publishing a unit.
...
@@ -96,6 +123,22 @@ class ContainerPage(PageObject):
...
@@ -96,6 +123,22 @@ class ContainerPage(PageObject):
self
.
q
(
css
=
'a.button.action-primary'
)
.
first
.
click
()
self
.
q
(
css
=
'a.button.action-primary'
)
.
first
.
click
()
self
.
wait_for_ajax
()
self
.
wait_for_ajax
()
def
toggle_staff_lock
(
self
):
"""
Toggles "hide from students" which enables or disables a staff-only lock.
Returns True if the lock is now enabled, else False.
"""
class_attribute_values
=
self
.
q
(
css
=
'a.action-staff-lock>i'
)
.
attrs
(
'class'
)
was_locked_initially
=
'icon-check'
in
class_attribute_values
if
not
was_locked_initially
:
self
.
q
(
css
=
'a.action-staff-lock'
)
.
first
.
click
()
else
:
click_css
(
self
,
'a.action-staff-lock'
,
0
,
require_notification
=
False
)
self
.
q
(
css
=
'a.button.action-primary'
)
.
first
.
click
()
self
.
wait_for_ajax
()
return
not
was_locked_initially
def
view_published_version
(
self
):
def
view_published_version
(
self
):
"""
"""
Clicks "View Published Version", which will open the published version of the unit page in the LMS.
Clicks "View Published Version", which will open the published version of the unit page in the LMS.
...
...
common/test/acceptance/tests/test_lms.py
View file @
d447c075
...
@@ -16,6 +16,7 @@ from ..pages.lms.progress import ProgressPage
...
@@ -16,6 +16,7 @@ from ..pages.lms.progress import ProgressPage
from
..pages.lms.dashboard
import
DashboardPage
from
..pages.lms.dashboard
import
DashboardPage
from
..pages.lms.video.video
import
VideoPage
from
..pages.lms.video.video
import
VideoPage
from
..pages.xblock.acid
import
AcidView
from
..pages.xblock.acid
import
AcidView
from
..pages.lms.courseware
import
CoursewarePage
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
,
CourseUpdateDesc
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
,
CourseUpdateDesc
...
@@ -421,3 +422,87 @@ class XBlockAcidChildTest(XBlockAcidBase):
...
@@ -421,3 +422,87 @@ class XBlockAcidChildTest(XBlockAcidBase):
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
def
test_acid_block
(
self
):
def
test_acid_block
(
self
):
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block
()
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block
()
class
VisibleToStaffOnlyTest
(
UniqueCourseTest
):
"""
Tests that content with visible_to_staff_only set to True cannot be viewed by students.
"""
def
setUp
(
self
):
super
(
VisibleToStaffOnlyTest
,
self
)
.
setUp
()
course_fix
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
course_fix
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Subsection With Locked Unit'
)
.
add_children
(
XBlockFixtureDesc
(
'vertical'
,
'Locked Unit'
,
metadata
=
{
'visible_to_staff_only'
:
True
})
.
add_children
(
XBlockFixtureDesc
(
'html'
,
'Html Child in locked unit'
,
data
=
"<html>Visible only to staff</html>"
),
),
XBlockFixtureDesc
(
'vertical'
,
'Unlocked Unit'
)
.
add_children
(
XBlockFixtureDesc
(
'html'
,
'Html Child in unlocked unit'
,
data
=
"<html>Visible only to all</html>"
),
)
),
XBlockFixtureDesc
(
'sequential'
,
'Unlocked Subsection'
)
.
add_children
(
XBlockFixtureDesc
(
'vertical'
,
'Test Unit'
)
.
add_children
(
XBlockFixtureDesc
(
'html'
,
'Html Child in visible unit'
,
data
=
"<html>Visible to all</html>"
),
)
),
XBlockFixtureDesc
(
'sequential'
,
'Locked Subsection'
,
metadata
=
{
'visible_to_staff_only'
:
True
})
.
add_children
(
XBlockFixtureDesc
(
'vertical'
,
'Test Unit'
)
.
add_children
(
XBlockFixtureDesc
(
'html'
,
'Html Child in locked subsection'
,
data
=
"<html>Visible only to staff</html>"
)
)
)
)
)
.
install
()
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
self
.
course_nav
=
CourseNavPage
(
self
.
browser
)
def
test_visible_to_staff
(
self
):
"""
Scenario: All content is visible for a user marked is_staff (different from course staff)
Given some of the course content has been marked 'visible_to_staff_only'
And I am logged on with an account marked 'is_staff'
Then I can see all course content
"""
AutoAuthPage
(
self
.
browser
,
username
=
"STAFF_TESTER"
,
email
=
"johndoe_staff@example.com"
,
course_id
=
self
.
course_id
,
staff
=
True
)
.
visit
()
self
.
courseware_page
.
visit
()
self
.
assertEqual
(
3
,
len
(
self
.
course_nav
.
sections
[
'Test Section'
]))
self
.
course_nav
.
go_to_section
(
"Test Section"
,
"Subsection With Locked Unit"
)
self
.
assertEqual
([
"Html Child in locked unit"
,
"Html Child in unlocked unit"
],
self
.
course_nav
.
sequence_items
)
self
.
course_nav
.
go_to_section
(
"Test Section"
,
"Unlocked Subsection"
)
self
.
assertEqual
([
"Html Child in visible unit"
],
self
.
course_nav
.
sequence_items
)
self
.
course_nav
.
go_to_section
(
"Test Section"
,
"Locked Subsection"
)
self
.
assertEqual
([
"Html Child in locked subsection"
],
self
.
course_nav
.
sequence_items
)
def
test_visible_to_student
(
self
):
"""
Scenario: Content marked 'visible_to_staff_only' is not visible for students in the course
Given some of the course content has been marked 'visible_to_staff_only'
And I am logged on with an authorized student account
Then I can only see content without 'visible_to_staff_only' set to True
"""
AutoAuthPage
(
self
.
browser
,
username
=
"STUDENT_TESTER"
,
email
=
"johndoe_student@example.com"
,
course_id
=
self
.
course_id
,
staff
=
False
)
.
visit
()
self
.
courseware_page
.
visit
()
self
.
assertEqual
(
2
,
len
(
self
.
course_nav
.
sections
[
'Test Section'
]))
self
.
course_nav
.
go_to_section
(
"Test Section"
,
"Subsection With Locked Unit"
)
self
.
assertEqual
([
"Html Child in unlocked unit"
],
self
.
course_nav
.
sequence_items
)
self
.
course_nav
.
go_to_section
(
"Test Section"
,
"Unlocked Subsection"
)
self
.
assertEqual
([
"Html Child in visible unit"
],
self
.
course_nav
.
sequence_items
)
common/test/acceptance/tests/test_studio_container.py
View file @
d447c075
...
@@ -11,9 +11,12 @@ from ..fixtures.course import XBlockFixtureDesc
...
@@ -11,9 +11,12 @@ from ..fixtures.course import XBlockFixtureDesc
from
..pages.studio.component_editor
import
ComponentEditorView
from
..pages.studio.component_editor
import
ComponentEditorView
from
..pages.studio.utils
import
add_discussion
from
..pages.studio.utils
import
add_discussion
from
..pages.lms.courseware
import
CoursewarePage
from
..pages.lms.courseware
import
CoursewarePage
from
..pages.lms.staff_view
import
StaffPage
from
unittest
import
skip
from
unittest
import
skip
from
acceptance.tests.base_studio_test
import
StudioCourseTest
from
acceptance.tests.base_studio_test
import
StudioCourseTest
import
datetime
from
bok_choy.promise
import
Promise
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
...
@@ -46,15 +49,15 @@ class ContainerBase(StudioCourseTest):
...
@@ -46,15 +49,15 @@ class ContainerBase(StudioCourseTest):
container
=
unit
.
xblocks
[
1
]
.
go_to_container
()
container
=
unit
.
xblocks
[
1
]
.
go_to_container
()
return
container
return
container
def
go_to_unit_page
(
self
):
def
go_to_unit_page
(
self
,
section_name
=
'Test Section'
,
subsection_name
=
'Test Subsection'
,
unit_name
=
'Test Unit'
):
"""
"""
Go to the test unit page.
Go to the test unit page.
If make_draft is true, the unit page will be put into draft mode.
If make_draft is true, the unit page will be put into draft mode.
"""
"""
self
.
outline
.
visit
()
self
.
outline
.
visit
()
subsection
=
self
.
outline
.
section
(
'Test Section'
)
.
subsection
(
'Test Subsection'
)
subsection
=
self
.
outline
.
section
(
section_name
)
.
subsection
(
subsection_name
)
return
subsection
.
toggle_expand
()
.
unit
(
'Test Unit'
)
.
go_to
()
return
subsection
.
toggle_expand
()
.
unit
(
unit_name
)
.
go_to
()
def
verify_ordering
(
self
,
container
,
expected_orderings
):
def
verify_ordering
(
self
,
container
,
expected_orderings
):
"""
"""
...
@@ -379,6 +382,8 @@ class UnitPublishingTest(ContainerBase):
...
@@ -379,6 +382,8 @@ class UnitPublishingTest(ContainerBase):
PUBLISHED_STATUS
=
"Publishing Status
\n
Published"
PUBLISHED_STATUS
=
"Publishing Status
\n
Published"
DRAFT_STATUS
=
"Publishing Status
\n
Draft (Unpublished changes)"
DRAFT_STATUS
=
"Publishing Status
\n
Draft (Unpublished changes)"
LOCKED_STATUS
=
"Publishing Status
\n
Unpublished (Staff only)"
RELEASE_TITLE_RELEASED
=
"RELEASED:"
def
setup_fixtures
(
self
):
def
setup_fixtures
(
self
):
"""
"""
...
@@ -393,6 +398,8 @@ class UnitPublishingTest(ContainerBase):
...
@@ -393,6 +398,8 @@ class UnitPublishingTest(ContainerBase):
self
.
course_info
[
'run'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
self
.
course_info
[
'display_name'
]
)
)
past_start_date
=
datetime
.
datetime
(
1974
,
6
,
22
)
self
.
past_start_date_text
=
"Jun 22, 1974 at 00:00 UTC"
course_fix
.
add_children
(
course_fix
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
...
@@ -401,6 +408,20 @@ class UnitPublishingTest(ContainerBase):
...
@@ -401,6 +408,20 @@ class UnitPublishingTest(ContainerBase):
XBlockFixtureDesc
(
'html'
,
'Test html'
,
data
=
self
.
html_content
)
XBlockFixtureDesc
(
'html'
,
'Test html'
,
data
=
self
.
html_content
)
)
)
)
)
),
XBlockFixtureDesc
(
'chapter'
,
'Unlocked Section'
,
metadata
=
{
'start'
:
past_start_date
.
isoformat
()})
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Unlocked Subsection'
)
.
add_children
(
XBlockFixtureDesc
(
'vertical'
,
'Unlocked Unit'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'<problem></problem>'
,
data
=
self
.
html_content
)
)
)
),
XBlockFixtureDesc
(
'chapter'
,
'Section With Locked Unit'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Subsection With Locked Unit'
,
metadata
=
{
'start'
:
past_start_date
.
isoformat
()})
.
add_children
(
XBlockFixtureDesc
(
'vertical'
,
'Locked Unit'
,
metadata
=
{
'visible_to_staff_only'
:
True
})
.
add_children
(
XBlockFixtureDesc
(
'discussion'
,
''
,
data
=
self
.
html_content
)
)
)
)
)
)
.
install
()
)
.
install
()
...
@@ -408,61 +429,220 @@ class UnitPublishingTest(ContainerBase):
...
@@ -408,61 +429,220 @@ class UnitPublishingTest(ContainerBase):
def
test_publishing
(
self
):
def
test_publishing
(
self
):
"""
"""
Test the state changes when a published unit has draft changes.
Scenario: The publish title changes based on whether or not draft content exists
Given I have a published unit with no unpublished changes
When I go to the unit page in Studio
Then the title in the Publish information box is "Published"
And the Publish button is disabled
And when I add a component to the unit
Then the title in the Publish information box is "Draft (Unpublished changes)"
And the Publish button is enabled
And when I click the Publish button
Then the title in the Publish information box is "Published"
"""
"""
unit
=
self
.
go_to_unit_page
()
unit
=
self
.
go_to_unit_page
()
self
.
assertEqual
(
self
.
PUBLISHED_STATUS
,
unit
.
publish_title
)
self
.
_verify_publish_title
(
unit
,
self
.
PUBLISHED_STATUS
)
# Start date set in course fixture to 1970.
self
.
_verify_release_date_info
(
unit
,
self
.
RELEASE_TITLE_RELEASED
,
'Jan 01, 1970 at 00:00 UTC with Section "Test Section"'
)
# Should not be able to click on Publish action -- but I don't know how to test that it is not clickable.
# Should not be able to click on Publish action -- but I don't know how to test that it is not clickable.
# TODO: continue discussion with Muhammad and Jay about this.
# TODO: continue discussion with Muhammad and Jay about this.
# Add a component to the page so it will have unpublished changes.
# Add a component to the page so it will have unpublished changes.
add_discussion
(
unit
)
add_discussion
(
unit
)
self
.
assertEqual
(
self
.
DRAFT_STATUS
,
unit
.
publish_title
)
self
.
_verify_publish_title
(
unit
,
self
.
DRAFT_STATUS
)
unit
.
publish_action
.
click
()
unit
.
publish_action
.
click
()
unit
.
wait_for_ajax
()
unit
.
wait_for_ajax
()
self
.
assertEqual
(
self
.
PUBLISHED_STATUS
,
unit
.
publish_title
)
self
.
_verify_publish_title
(
unit
,
self
.
PUBLISHED_STATUS
)
def
test_discard_changes
(
self
):
def
test_discard_changes
(
self
):
"""
"""
Test the state after discard changes.
Scenario: The publish title changes after "Discard Changes" is clicked
Given I have a published unit with no unpublished changes
When I go to the unit page in Studio
Then the Discard Changes button is disabled
And I add a component to the unit
Then the title in the Publish information box is "Draft (Unpublished changes)"
And the Discard Changes button is enabled
And when I click the Discard Changes button
Then the title in the Publish information box is "Published"
"""
"""
unit
=
self
.
go_to_unit_page
()
unit
=
self
.
go_to_unit_page
()
add_discussion
(
unit
)
add_discussion
(
unit
)
self
.
assertEqual
(
self
.
DRAFT_STATUS
,
unit
.
publish_title
)
self
.
_verify_publish_title
(
unit
,
self
.
DRAFT_STATUS
)
unit
.
discard_changes
()
unit
.
discard_changes
()
self
.
assertEqual
(
self
.
PUBLISHED_STATUS
,
unit
.
publish_title
)
self
.
_verify_publish_title
(
unit
,
self
.
PUBLISHED_STATUS
)
def
test_view_live_no_changes
(
self
):
def
test_view_live_no_changes
(
self
):
"""
"""
Tests viewing of live with initial published content.
Scenario: "View Live" shows published content in LMS
Given I have a published unit with no unpublished changes
When I go to the unit page in Studio
Then the View Live button is enabled
And when I click on the View Live button
Then I see the published content in LMS
"""
"""
unit
=
self
.
go_to_unit_page
()
unit
=
self
.
go_to_unit_page
()
unit
.
view_published_version
()
unit
.
view_published_version
()
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
self
.
_verify_components_visible
([
'html'
])
self
.
assertEqual
(
'html'
,
self
.
courseware
.
xblock_component_type
(
0
))
def
test_view_live_changes
(
self
):
def
test_view_live_changes
(
self
):
"""
"""
Tests that viewing of live with draft content does not show the draft content.
Scenario: "View Live" does not show draft content in LMS
Given I have a published unit with no unpublished changes
When I go to the unit page in Studio
And when I add a component to the unit
And when I click on the View Live button
Then I see the published content in LMS
And I do not see the unpublished component
"""
"""
unit
=
self
.
go_to_unit_page
()
unit
=
self
.
go_to_unit_page
()
add_discussion
(
unit
)
add_discussion
(
unit
)
unit
.
view_published_version
()
unit
.
view_published_version
()
self
.
assertEqual
(
1
,
self
.
courseware
.
num_xblock_components
)
self
.
_verify_components_visible
([
'html'
])
self
.
assertEqual
(
'html'
,
self
.
courseware
.
xblock_component_type
(
0
))
self
.
assertEqual
(
self
.
html_content
,
self
.
courseware
.
xblock_component_html_content
(
0
))
self
.
assertEqual
(
self
.
html_content
,
self
.
courseware
.
xblock_component_html_content
(
0
))
def
test_view_live_after_publish
(
self
):
def
test_view_live_after_publish
(
self
):
"""
"""
Tests viewing of live after creating draft and publishing it.
Scenario: "View Live" shows newly published content
Given I have a published unit with no unpublished changes
When I go to the unit page in Studio
And when I add a component to the unit
And when I click the Publish button
And when I click on the View Live button
Then I see the newly published component
"""
"""
unit
=
self
.
go_to_unit_page
()
unit
=
self
.
go_to_unit_page
()
add_discussion
(
unit
)
add_discussion
(
unit
)
unit
.
publish_action
.
click
()
unit
.
publish_action
.
click
()
unit
.
view_published_version
()
unit
.
view_published_version
()
self
.
assertEqual
(
2
,
self
.
courseware
.
num_xblock_components
)
self
.
_verify_components_visible
([
'html'
,
'discussion'
])
self
.
assertEqual
(
'html'
,
self
.
courseware
.
xblock_component_type
(
0
))
self
.
assertEqual
(
'discussion'
,
self
.
courseware
.
xblock_component_type
(
1
))
def
test_initially_unlocked_visible_to_students
(
self
):
"""
Scenario: An unlocked unit with release date in the past is visible to students
Given I have a published unlocked unit with release date in the past
When I go to the unit page in Studio
Then the unit has a warning that it is visible to students
And it is marked as "RELEASED" with release date in the past visible
And when I click on the View Live Button
And when I view the course as a student
Then I see the content in the unit
"""
unit
=
self
.
go_to_unit_page
(
"Unlocked Section"
,
"Unlocked Subsection"
,
"Unlocked Unit"
)
self
.
_verify_publish_title
(
unit
,
self
.
PUBLISHED_STATUS
)
self
.
assertTrue
(
unit
.
currently_visible_to_students
)
self
.
_verify_release_date_info
(
unit
,
self
.
RELEASE_TITLE_RELEASED
,
self
.
past_start_date_text
+
' with Section "Unlocked Section"'
)
unit
.
view_published_version
()
self
.
_verify_student_view_visible
([
'problem'
])
def
test_locked_visible_to_staff_only
(
self
):
"""
Scenario: After locking a unit with release date in the past, it is only visible to staff
Given I have a published unlocked unit with release date in the past
When I go to the unit page in Studio
And when I select "Hide from students"
Then the unit does not have a warning that it is visible to students
And when I click on the View Live Button
Then I see the content in the unit when logged in as staff
And when I view the course as a student
Then I do not see any content in the unit
"""
unit
=
self
.
go_to_unit_page
(
"Unlocked Section"
,
"Unlocked Subsection"
,
"Unlocked Unit"
)
checked
=
unit
.
toggle_staff_lock
()
self
.
assertTrue
(
checked
)
self
.
assertFalse
(
unit
.
currently_visible_to_students
)
self
.
_verify_publish_title
(
unit
,
self
.
LOCKED_STATUS
)
unit
.
view_published_version
()
# Will initially be in staff view, locked component should be visible.
self
.
_verify_components_visible
([
'problem'
])
# Switch to student view and verify not visible
self
.
_verify_student_view_locked
()
def
test_initially_locked_not_visible_to_students
(
self
):
"""
Scenario: A locked unit with release date in the past is not visible to students
Given I have a published locked unit with release date in the past
When I go to the unit page in Studio
Then the unit does not have a warning that it is visible to students
And it is marked as "RELEASED" with release date in the past visible
And when I click on the View Live Button
And when I view the course as a student
Then I do not see any content in the unit
"""
unit
=
self
.
go_to_unit_page
(
"Section With Locked Unit"
,
"Subsection With Locked Unit"
,
"Locked Unit"
)
self
.
_verify_publish_title
(
unit
,
self
.
LOCKED_STATUS
)
self
.
assertFalse
(
unit
.
currently_visible_to_students
)
self
.
_verify_release_date_info
(
unit
,
self
.
RELEASE_TITLE_RELEASED
,
self
.
past_start_date_text
+
' with Subsection "Subsection With Locked Unit"'
)
unit
.
view_published_version
()
self
.
_verify_student_view_locked
()
def
test_unlocked_visible_to_all
(
self
):
"""
Scenario: After unlocking a unit with release date in the past, it is visible to both students and staff
Given I have a published unlocked unit with release date in the past
When I go to the unit page in Studio
And when I deselect "Hide from students"
Then the unit does have a warning that it is visible to students
And when I click on the View Live Button
Then I see the content in the unit when logged in as staff
And when I view the course as a student
Then I see the content in the unit
"""
unit
=
self
.
go_to_unit_page
(
"Section With Locked Unit"
,
"Subsection With Locked Unit"
,
"Locked Unit"
)
checked
=
unit
.
toggle_staff_lock
()
self
.
assertFalse
(
checked
)
self
.
_verify_publish_title
(
unit
,
self
.
PUBLISHED_STATUS
)
self
.
assertTrue
(
unit
.
currently_visible_to_students
)
unit
.
view_published_version
()
# Will initially be in staff view, components always visible.
self
.
_verify_components_visible
([
'discussion'
])
# Switch to student view and verify visible.
self
.
_verify_student_view_visible
([
'discussion'
])
def
_verify_student_view_locked
(
self
):
"""
Verifies no component is visible when viewing as a student.
"""
StaffPage
(
self
.
browser
)
.
toggle_staff_view
()
self
.
assertEqual
(
0
,
self
.
courseware
.
num_xblock_components
)
def
_verify_student_view_visible
(
self
,
expected_components
):
"""
Verifies expected components are visible when viewing as a student.
"""
StaffPage
(
self
.
browser
)
.
toggle_staff_view
()
self
.
_verify_components_visible
(
expected_components
)
def
_verify_components_visible
(
self
,
expected_components
):
"""
Verifies the expected components are visible (and there are no extras).
"""
self
.
assertEqual
(
len
(
expected_components
),
self
.
courseware
.
num_xblock_components
)
for
index
,
component
in
enumerate
(
expected_components
):
self
.
assertEqual
(
component
,
self
.
courseware
.
xblock_component_type
(
index
))
def
_verify_release_date_info
(
self
,
unit
,
expected_title
,
expected_date
):
"""
Verifies how the release date is displayed in the publishing sidebar.
"""
self
.
assertEqual
(
expected_title
,
unit
.
release_title
)
self
.
assertEqual
(
expected_date
,
unit
.
release_date
)
def
_verify_publish_title
(
self
,
unit
,
expected_title
):
"""
Waits for the publish title to change to the expected value.
"""
def
wait_for_title_change
():
return
(
unit
.
publish_title
==
expected_title
,
unit
.
publish_title
)
Promise
(
wait_for_title_change
,
"Publish title incorrect. Found '"
+
unit
.
publish_title
+
"'"
)
.
fulfill
()
# TODO: need to work with Jay/Christine to get testing of "Preview" working.
# TODO: need to work with Jay/Christine to get testing of "Preview" working.
# def test_preview(self):
# def test_preview(self):
...
...
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