Commit 6d82cf09 by cahrens

Correct publish titles, code review feedback.

parent ef581e11
...@@ -1209,7 +1209,7 @@ class ContentStoreTest(ContentStoreTestCase): ...@@ -1209,7 +1209,7 @@ class ContentStoreTest(ContentStoreTestCase):
resp = self._show_course_overview(course.id) resp = self._show_course_overview(course.id)
self.assertContains( self.assertContains(
resp, resp,
'<article class="outline" data-locator="{locator}" data-course-key="{course_key}">'.format( '<article class="outline outline-course" data-locator="{locator}" data-course-key="{course_key}">'.format(
locator='i4x://MITx/999/course/Robot_Super_Course', locator='i4x://MITx/999/course/Robot_Super_Course',
course_key='MITx/999/Robot_Super_Course', course_key='MITx/999/Robot_Super_Course',
), ),
......
...@@ -149,7 +149,7 @@ def course_image_url(course): ...@@ -149,7 +149,7 @@ def course_image_url(course):
path = loc.to_deprecated_string() path = loc.to_deprecated_string()
return path return path
# pylint: disable=invalid-name
def is_currently_visible_to_students(xblock): def is_currently_visible_to_students(xblock):
""" """
Returns true if there is a published version of the xblock that is currently visible to students. Returns true if there is a published version of the xblock that is currently visible to students.
......
...@@ -178,10 +178,6 @@ def container_handler(request, usage_key_string): ...@@ -178,10 +178,6 @@ def container_handler(request, usage_key_string):
# about the block's ancestors and siblings for use by the Unit Outline. # about the block's ancestors and siblings for use by the Unit Outline.
xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page) xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page)
# On the unit page only, add 'has_changes' to indicate when there are changes that can be discarded.
if is_unit_page:
xblock_info['has_changes'] = modulestore().has_changes(xblock.location)
# Create the link for preview. # Create the link for preview.
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE') preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
# need to figure out where this item is in the list of children as the # need to figure out where this item is in the list of children as the
......
...@@ -632,6 +632,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -632,6 +632,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only) published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
currently_visible_to_students = is_currently_visible_to_students(xblock) currently_visible_to_students = is_currently_visible_to_students(xblock)
is_xblock_unit = is_unit(xblock)
is_unit_with_changes = is_xblock_unit and modulestore().has_changes(xblock.location)
xblock_info = { xblock_info = {
"id": unicode(xblock.location), "id": unicode(xblock.location),
"display_name": xblock.display_name_with_default, "display_name": xblock.display_name_with_default,
...@@ -646,7 +649,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -646,7 +649,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"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,
"currently_visible_to_students": currently_visible_to_students, "currently_visible_to_students": currently_visible_to_students,
"visibility_state": _compute_visibility_state(xblock, child_info) if not xblock.category == 'course' else None "visibility_state": _compute_visibility_state(xblock, child_info, is_unit_with_changes) if not xblock.category == 'course' else None
} }
if data is not None: if data is not None:
xblock_info["data"] = data xblock_info["data"] = data
...@@ -656,6 +659,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -656,6 +659,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock) xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock)
if child_info: if child_info:
xblock_info['child_info'] = child_info xblock_info['child_info'] = child_info
# On the unit page only, add 'has_changes' to indicate when there are changes that can be discarded.
# We don't add it in general because it is an expensive operation.
if is_xblock_unit:
xblock_info['has_changes'] = is_unit_with_changes
return xblock_info return xblock_info
...@@ -680,18 +688,20 @@ class VisibilityState(object): ...@@ -680,18 +688,20 @@ class VisibilityState(object):
""" """
live = 'live' live = 'live'
ready = 'ready' ready = 'ready'
unscheduled = 'unscheduled' # unscheduled unscheduled = 'unscheduled'
needs_attention = 'needs_attention' needs_attention = 'needs_attention'
staff_only = 'staff_only' staff_only = 'staff_only'
def _compute_visibility_state(xblock, child_info): def _compute_visibility_state(xblock, child_info, is_unit_with_changes):
""" """
Returns the current publish state for the specified xblock and its children Returns the current publish state for the specified xblock and its children
""" """
if xblock.visible_to_staff_only: if xblock.visible_to_staff_only:
return VisibilityState.staff_only return VisibilityState.staff_only
elif is_unit(xblock) and modulestore().has_changes(xblock.location): elif is_unit_with_changes:
# Note that a unit that has never been published will fall into this category,
# as well as previously published units with draft content.
return VisibilityState.needs_attention return VisibilityState.needs_attention
is_unscheduled = xblock.start == DEFAULT_START_DATE is_unscheduled = xblock.start == DEFAULT_START_DATE
is_live = datetime.now(UTC) > xblock.start is_live = datetime.now(UTC) > xblock.start
......
...@@ -231,6 +231,7 @@ class TestCourseOutline(CourseTestCase): ...@@ -231,6 +231,7 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(json_response['category'], 'course') self.assertEqual(json_response['category'], 'course')
self.assertEqual(json_response['id'], 'i4x://MITx/999/course/Robot_Super_Course') self.assertEqual(json_response['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertEqual(json_response['display_name'], 'Robot Super Course') self.assertEqual(json_response['display_name'], 'Robot Super Course')
self.assertTrue(json_response['published'])
self.assertIsNone(json_response['visibility_state']) self.assertIsNone(json_response['visibility_state'])
# Now verify the first child # Now verify the first child
...@@ -240,6 +241,7 @@ class TestCourseOutline(CourseTestCase): ...@@ -240,6 +241,7 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(first_child_response['category'], 'chapter') self.assertEqual(first_child_response['category'], 'chapter')
self.assertEqual(first_child_response['id'], 'i4x://MITx/999/chapter/Week_1') self.assertEqual(first_child_response['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(first_child_response['display_name'], 'Week 1') self.assertEqual(first_child_response['display_name'], 'Week 1')
self.assertTrue(json_response['published'])
self.assertEqual(first_child_response['visibility_state'], VisibilityState.unscheduled) self.assertEqual(first_child_response['visibility_state'], VisibilityState.unscheduled)
self.assertTrue(len(first_child_response['child_info']['children']) > 0) self.assertTrue(len(first_child_response['child_info']['children']) > 0)
...@@ -253,6 +255,7 @@ class TestCourseOutline(CourseTestCase): ...@@ -253,6 +255,7 @@ class TestCourseOutline(CourseTestCase):
self.assertIsNotNone(json_response['display_name']) self.assertIsNotNone(json_response['display_name'])
self.assertIsNotNone(json_response['id']) self.assertIsNotNone(json_response['id'])
self.assertIsNotNone(json_response['category']) self.assertIsNotNone(json_response['category'])
self.assertTrue(json_response['published'])
if json_response.get('child_info', None): if json_response.get('child_info', None):
for child_response in json_response['child_info']['children']: for child_response in json_response['child_info']['children']:
self.assert_correct_json_response(child_response) self.assert_correct_json_response(child_response)
......
...@@ -531,8 +531,25 @@ class TestEditItem(ItemTest): ...@@ -531,8 +531,25 @@ class TestEditItem(ItemTest):
self.assertEqual(unit2_usage_key, children[1]) self.assertEqual(unit2_usage_key, children[1])
def _is_location_published(self, location): def _is_location_published(self, location):
"""
Returns whether or not the item with given location has a published version.
"""
return modulestore().has_item(location, revision=ModuleStoreEnum.RevisionOption.published_only) return modulestore().has_item(location, revision=ModuleStoreEnum.RevisionOption.published_only)
def _verify_published_with_no_draft(self, location):
"""
Verifies the item with given location has a published version and no draft (unpublished changes).
"""
self.assertTrue(self._is_location_published(location))
self.assertFalse(modulestore().has_changes(location))
def _verify_published_with_draft(self, location):
"""
Verifies the item with given location has a published version and also a draft version (unpublished changes).
"""
self.assertTrue(self._is_location_published(location))
self.assertTrue(modulestore().has_changes(location))
def test_make_public(self): def test_make_public(self):
""" Test making a private problem public (publishing it). """ """ Test making a private problem public (publishing it). """
# When the problem is first created, it is only in draft (because of its category). # When the problem is first created, it is only in draft (because of its category).
...@@ -541,7 +558,7 @@ class TestEditItem(ItemTest): ...@@ -541,7 +558,7 @@ class TestEditItem(ItemTest):
self.problem_update_url, self.problem_update_url,
data={'publish': 'make_public'} data={'publish': 'make_public'}
) )
self.assertTrue(self._is_location_published(self.problem_usage_key)) self._verify_published_with_no_draft(self.problem_usage_key)
def test_make_draft(self): def test_make_draft(self):
""" Test creating a draft version of a public problem. """ """ Test creating a draft version of a public problem. """
...@@ -554,17 +571,13 @@ class TestEditItem(ItemTest): ...@@ -554,17 +571,13 @@ class TestEditItem(ItemTest):
self.problem_update_url, self.problem_update_url,
data={'publish': 'discard_changes'} data={'publish': 'discard_changes'}
) )
self.assertTrue(self._is_location_published(self.problem_usage_key)) self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only) published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
self.assertIsNone(published.due) self.assertIsNone(published.due)
def test_republish(self): def test_republish(self):
""" Test republishing an item. """ """ Test republishing an item. """
new_display_name = 'New Display Name' 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). # When the problem is first created, it is only in draft (because of its category).
self.assertFalse(self._is_location_published(self.problem_usage_key)) self.assertFalse(self._is_location_published(self.problem_usage_key))
...@@ -600,7 +613,7 @@ class TestEditItem(ItemTest): ...@@ -600,7 +613,7 @@ class TestEditItem(ItemTest):
} }
} }
) )
self.assertTrue(self._is_location_published(self.problem_usage_key)) self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item( published = modulestore().get_item(
self.problem_usage_key, self.problem_usage_key,
revision=ModuleStoreEnum.RevisionOption.published_only revision=ModuleStoreEnum.RevisionOption.published_only
...@@ -616,7 +629,7 @@ class TestEditItem(ItemTest): ...@@ -616,7 +629,7 @@ class TestEditItem(ItemTest):
self.problem_update_url, self.problem_update_url,
data={'publish': 'make_public'} data={'publish': 'make_public'}
) )
self.assertTrue(self._is_location_published(self.problem_usage_key)) self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only) published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
# Update the draft version and check that published is different. # Update the draft version and check that published is different.
...@@ -651,7 +664,7 @@ class TestEditItem(ItemTest): ...@@ -651,7 +664,7 @@ class TestEditItem(ItemTest):
self.problem_update_url, self.problem_update_url,
data={'publish': 'make_public'} data={'publish': 'make_public'}
) )
self.assertTrue(self._is_location_published(self.problem_usage_key)) self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only) published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
# Now make a draft # Now make a draft
...@@ -706,8 +719,8 @@ class TestEditItem(ItemTest): ...@@ -706,8 +719,8 @@ class TestEditItem(ItemTest):
data={'publish': 'make_public'} data={'publish': 'make_public'}
) )
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertTrue(self._is_location_published(unit_usage_key)) self._verify_published_with_no_draft(unit_usage_key)
self.assertTrue(self._is_location_published(html_usage_key)) self._verify_published_with_no_draft(html_usage_key)
# Make a draft for the unit and verify that the problem also has a draft # Make a draft for the unit and verify that the problem also has a draft
resp = self.client.ajax_post( resp = self.client.ajax_post(
...@@ -718,10 +731,8 @@ class TestEditItem(ItemTest): ...@@ -718,10 +731,8 @@ class TestEditItem(ItemTest):
} }
) )
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertTrue(self._is_location_published(unit_usage_key)) self._verify_published_with_draft(unit_usage_key)
self.assertTrue(self._is_location_published(html_usage_key)) self._verify_published_with_draft(html_usage_key)
self.assertTrue(modulestore().get_item(unit_usage_key).has_changes())
self.assertTrue(modulestore().get_item(html_usage_key).has_changes())
class TestEditSplitModule(ItemTest): class TestEditSplitModule(ItemTest):
...@@ -1143,6 +1154,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1143,6 +1154,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'course') self.assertEqual(xblock_info['category'], 'course')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/course/Robot_Super_Course') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertEqual(xblock_info['display_name'], 'Robot Super Course') self.assertEqual(xblock_info['display_name'], 'Robot Super Course')
self.assertTrue(xblock_info['published'])
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
...@@ -1154,6 +1166,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1154,6 +1166,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'chapter') self.assertEqual(xblock_info['category'], 'chapter')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/chapter/Week_1') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(xblock_info['display_name'], 'Week 1') self.assertEqual(xblock_info['display_name'], 'Week 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser') self.assertEqual(xblock_info['edited_by'], 'testuser')
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
...@@ -1166,6 +1179,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1166,6 +1179,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'sequential') self.assertEqual(xblock_info['category'], 'sequential')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/sequential/Lesson_1') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/sequential/Lesson_1')
self.assertEqual(xblock_info['display_name'], 'Lesson 1') self.assertEqual(xblock_info['display_name'], 'Lesson 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser') self.assertEqual(xblock_info['edited_by'], 'testuser')
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
...@@ -1178,6 +1192,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1178,6 +1192,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'vertical') self.assertEqual(xblock_info['category'], 'vertical')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/vertical/Unit_1') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/vertical/Unit_1')
self.assertEqual(xblock_info['display_name'], 'Unit 1') self.assertEqual(xblock_info['display_name'], 'Unit 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser') self.assertEqual(xblock_info['edited_by'], 'testuser')
# Validate that the correct ancestor info has been included # Validate that the correct ancestor info has been included
...@@ -1199,6 +1214,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1199,6 +1214,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'video') self.assertEqual(xblock_info['category'], 'video')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/video/My_Video') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/video/My_Video')
self.assertEqual(xblock_info['display_name'], 'My Video') self.assertEqual(xblock_info['display_name'], 'My Video')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser') self.assertEqual(xblock_info['edited_by'], 'testuser')
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
...@@ -1211,6 +1227,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1211,6 +1227,7 @@ class TestXBlockInfo(ItemTest):
self.assertIsNotNone(xblock_info['display_name']) self.assertIsNotNone(xblock_info['display_name'])
self.assertIsNotNone(xblock_info['id']) self.assertIsNotNone(xblock_info['id'])
self.assertIsNotNone(xblock_info['category']) self.assertIsNotNone(xblock_info['category'])
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser') self.assertEqual(xblock_info['edited_by'], 'testuser')
if has_ancestor_info: if has_ancestor_info:
self.assertIsNotNone(xblock_info.get('ancestor_info', None)) self.assertIsNotNone(xblock_info.get('ancestor_info', None))
...@@ -1243,14 +1260,17 @@ class TestXBlockPublishingInfo(ItemTest): ...@@ -1243,14 +1260,17 @@ class TestXBlockPublishingInfo(ItemTest):
SECOND_UNIT_PATH = [0, 1] SECOND_UNIT_PATH = [0, 1]
def _create_child(self, parent, category, display_name, publish_item=False, staff_only=False): def _create_child(self, parent, category, display_name, publish_item=False, staff_only=False):
"""
Creates a child xblock for the given parent.
"""
return ItemFactory.create( return ItemFactory.create(
parent_location=parent.location, category=category, display_name=display_name, parent_location=parent.location, category=category, display_name=display_name,
user_id=self.user.id, publish_item=publish_item, visible_to_staff_only=staff_only user_id=self.user.id, publish_item=publish_item, visible_to_staff_only=staff_only
) )
def _get_child(self, xblock_info, index): def _get_child_xblock_info(self, xblock_info, index):
""" """
Returns the child at the specified index. Returns the child xblock info at the specified index.
""" """
children = xblock_info['child_info']['children'] children = xblock_info['child_info']['children']
self.assertTrue(len(children) > index) self.assertTrue(len(children) > index)
...@@ -1293,12 +1313,12 @@ class TestXBlockPublishingInfo(ItemTest): ...@@ -1293,12 +1313,12 @@ class TestXBlockPublishingInfo(ItemTest):
def _verify_visibility_state(self, xblock_info, expected_state, path=None): def _verify_visibility_state(self, xblock_info, expected_state, path=None):
""" """
Verify the publish state of an item in the xblock_info. If no path is provided Verify the publish state of an item in the xblock_info. If no path is provided
then the root item is verified. then the root item will be verified.
""" """
if path: if path:
direct_child = self._get_child(xblock_info, path[0]) direct_child_xblock_info = self._get_child_xblock_info(xblock_info, path[0])
remaining_path = path[1:] if len(path) > 1 else None remaining_path = path[1:] if len(path) > 1 else None
self._verify_visibility_state(direct_child, expected_state, remaining_path) self._verify_visibility_state(direct_child_xblock_info, expected_state, remaining_path)
else: else:
self.assertEqual(xblock_info['visibility_state'], expected_state) self.assertEqual(xblock_info['visibility_state'], expected_state)
...@@ -1311,10 +1331,13 @@ class TestXBlockPublishingInfo(ItemTest): ...@@ -1311,10 +1331,13 @@ class TestXBlockPublishingInfo(ItemTest):
chapter = self._create_child(self.course, 'chapter', "Test Chapter") chapter = self._create_child(self.course, 'chapter', "Test Chapter")
self._create_child(chapter, 'sequential', "Empty Sequential") self._create_child(chapter, 'sequential', "Empty Sequential")
xblock_info = self._get_xblock_info(chapter.location) xblock_info = self._get_xblock_info(chapter.location)
self.assertEqual(xblock_info['visibility_state'], VisibilityState.unscheduled) self._verify_visibility_state(xblock_info, VisibilityState.unscheduled)
self.assertEqual(self._get_child(xblock_info, 0)['visibility_state'], VisibilityState.unscheduled) self._verify_visibility_state(xblock_info, VisibilityState.unscheduled, path=self.FIRST_SUBSECTION_PATH)
def test_published_unit(self): def test_published_unit(self):
"""
Tests the visibility state of a published unit with release date in the future.
"""
chapter = self._create_child(self.course, 'chapter', "Test Chapter") chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential") sequential = self._create_child(chapter, 'sequential', "Test Sequential")
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
...@@ -1327,6 +1350,9 @@ class TestXBlockPublishingInfo(ItemTest): ...@@ -1327,6 +1350,9 @@ class TestXBlockPublishingInfo(ItemTest):
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
def test_released_unit(self): def test_released_unit(self):
"""
Tests the visibility state of a published unit with release date in the past.
"""
chapter = self._create_child(self.course, 'chapter', "Test Chapter") chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential") sequential = self._create_child(chapter, 'sequential', "Test Sequential")
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
...@@ -1339,10 +1365,14 @@ class TestXBlockPublishingInfo(ItemTest): ...@@ -1339,10 +1365,14 @@ class TestXBlockPublishingInfo(ItemTest):
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
def test_unpublished_changes(self): def test_unpublished_changes(self):
"""
Tests the visibility state of a published unit with draft (unpublished) changes.
"""
chapter = self._create_child(self.course, 'chapter', "Test Chapter") chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential") sequential = self._create_child(chapter, 'sequential', "Test Sequential")
unit = self._create_child(sequential, 'vertical', "Published Unit", publish_item=True) unit = self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
# Setting the display name creates a draft version of unit.
self._set_display_name(unit.location, 'Updated Unit') self._set_display_name(unit.location, 'Updated Unit')
xblock_info = self._get_xblock_info(chapter.location) xblock_info = self._get_xblock_info(chapter.location)
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) self._verify_visibility_state(xblock_info, VisibilityState.needs_attention)
......
...@@ -46,7 +46,8 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu ...@@ -46,7 +46,8 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
"published_by": null, "published_by": null,
/** /**
* True if the xblock has changes. * True if the xblock has changes.
* Note: this is not always provided as a performance optimization. * Note: this is not always provided as a performance optimization. It is only provided for
* verticals functioning as units.
*/ */
"has_changes": null, "has_changes": null,
/** /**
......
...@@ -27,7 +27,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -27,7 +27,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
category: 'vertical', category: 'vertical',
published: false, published: false,
has_changes: false, has_changes: false,
visibility_state: 'unscheduled', visibility_state: VisibilityState.unscheduled,
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", published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako",
currently_visible_to_students: false currently_visible_to_students: false
...@@ -116,13 +116,15 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -116,13 +116,15 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
liveClass = "is-live", liveClass = "is-live",
readyClass = "is-ready", readyClass = "is-ready",
staffOnlyClass = "is-staff-only", staffOnlyClass = "is-staff-only",
scheduledClass = "is-scheduled",
unscheduledClass = "",
hasWarningsClass = 'has-warnings', hasWarningsClass = 'has-warnings',
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",
promptSpies, sendDiscardChangesToServer; promptSpies, sendDiscardChangesToServer, verifyPublishingBitUnscheduled;
sendDiscardChangesToServer = function() { 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.
...@@ -143,45 +145,75 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -143,45 +145,75 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
); );
}; };
verifyPublishingBitUnscheduled = function() {
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(liveClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(hasWarningsClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(unscheduledClass);
};
beforeEach(function() { beforeEach(function() {
promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"]); promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"]);
promptSpies.show.andReturn(this.promptSpies); promptSpies.show.andReturn(this.promptSpies);
}); });
it('renders correctly with unscheduled content', function () { it('renders correctly with private content', function () {
var verifyPrivateState = function() { var verifyPrivateState = function() {
expect(containerPage.$(headerCss).text()).toContain('Draft (Never published)'); expect(containerPage.$(headerCss).text()).toContain('Draft (Never published)');
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)).not.toHaveClass(readyClass); expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
}; };
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
fetch({published: false, has_changes: false}); fetch({published: false, has_changes: false, visibility_state: VisibilityState.needsAttention});
verifyPrivateState(); verifyPrivateState();
fetch({published: false, has_changes: true}); fetch({published: false, has_changes: true, visibility_state: VisibilityState.needsAttention});
verifyPrivateState(); verifyPrivateState();
}); });
it('renders correctly with published content', function () { it('renders correctly with published content', function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
fetch({published: true, has_changes: false, visibility_state: VisibilityState.ready}); fetch({
published: true, has_changes: false, visibility_state: VisibilityState.ready,
release_date: "Jul 02, 2030 at 14:20 UTC"
});
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(readyClass); expect(containerPage.$(bitPublishingCss)).toHaveClass(readyClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention}); fetch({
published: true, has_changes: true, visibility_state: VisibilityState.needsAttention,
release_date: "Jul 02, 2030 at 14:20 UTC"
});
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);
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass); expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
fetch({published: true, has_changes: false, visibility_state: VisibilityState.live}); fetch({published: true, has_changes: false, visibility_state: VisibilityState.live,
release_date: "Jul 02, 1990 at 14:20 UTC"
});
expect(containerPage.$(headerCss).text()).toContain('Published and Live'); expect(containerPage.$(headerCss).text()).toContain('Published and Live');
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss); expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss); expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(bitPublishingCss)).toHaveClass(liveClass); expect(containerPage.$(bitPublishingCss)).toHaveClass(liveClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
fetch({published: true, has_changes: false, visibility_state: VisibilityState.unscheduled,
release_date: null
});
expect(containerPage.$(headerCss).text()).toContain('Published');
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
verifyPublishingBitUnscheduled();
}); });
it('can publish private content', function () { it('can publish private content', function () {
...@@ -217,7 +249,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -217,7 +249,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it('does not refresh if publish fails', function () { it('does not refresh if publish fails', function () {
renderContainerPage(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass); verifyPublishingBitUnscheduled();
// Click publish // Click publish
containerPage.$(publishButtonCss).click(); containerPage.$(publishButtonCss).click();
...@@ -228,8 +260,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -228,8 +260,8 @@ 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 (unscheduled) state.
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass); verifyPublishingBitUnscheduled();
// 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();
}); });
...@@ -273,7 +305,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -273,7 +305,7 @@ 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(this, mockContainerXBlockHtml); renderContainerPage(this, mockContainerXBlockHtml);
fetch({published: true, has_changes: true}); fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
var numRequests = requests.length; var numRequests = requests.length;
// Click discard changes // Click discard changes
...@@ -373,7 +405,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -373,7 +405,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/locator-container'); create_sinon.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
create_sinon.respondWithJson(requests, createXBlockInfo({ create_sinon.respondWithJson(requests, createXBlockInfo({
published: containerPage.model.get('published'), published: containerPage.model.get('published'),
visibility_state: isStaffOnly ? VisibilityState.staffOnly : VisibilityState.live visibility_state: isStaffOnly ? VisibilityState.staffOnly : VisibilityState.live,
release_date: "Jul 02, 2000 at 14:20 UTC"
})); }));
}; };
...@@ -382,6 +415,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -382,6 +415,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check'); expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check');
expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff Only'); expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff Only');
expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass); expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
} else { } else {
expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check-empty'); expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check-empty');
expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff and Students'); expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff and Students');
...@@ -403,18 +437,19 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin ...@@ -403,18 +437,19 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it("can remove staff only setting", function() { it("can remove staff only setting", function() {
promptSpy = edit_helpers.createPromptSpy(); promptSpy = edit_helpers.createPromptSpy();
renderContainerPage(this, mockContainerXBlockHtml, { renderContainerPage(this, mockContainerXBlockHtml, {
visibility_state: VisibilityState.staffOnly visibility_state: VisibilityState.staffOnly,
release_date: "Jul 02, 2000 at 14:20 UTC"
}); });
requestStaffOnly(false); requestStaffOnly(false);
verifyStaffOnly(false); verifyStaffOnly(false);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
}); });
it("does not refresh if removing staff only is canceled", function() { it("does not refresh if removing staff only is canceled", function() {
var requestCount; var requestCount;
promptSpy = edit_helpers.createPromptSpy(); promptSpy = edit_helpers.createPromptSpy();
renderContainerPage(this, mockContainerXBlockHtml, { renderContainerPage(this, mockContainerXBlockHtml, {
visibility_state: VisibilityState.staffOnly visibility_state: VisibilityState.staffOnly,
release_date: "Jul 02, 2000 at 14:20 UTC"
}); });
requestCount = requests.length; requestCount = requests.length;
containerPage.$('.action-staff-lock').click(); containerPage.$('.action-staff-lock').click();
......
...@@ -118,6 +118,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" ...@@ -118,6 +118,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
category: 'vertical', category: 'vertical',
studio_url: '/container/mock-unit', studio_url: '/container/mock-unit',
is_container: true, is_container: true,
has_changes: false,
published: true,
visibility_state: 'unscheduled', visibility_state: 'unscheduled',
edited_on: 'Jul 02, 2014 at 20:56 UTC', edited_on: 'Jul 02, 2014 at 20:56 UTC',
edited_by: 'MockUser' edited_by: 'MockUser'
......
...@@ -116,7 +116,6 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/ ...@@ -116,7 +116,6 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
published: this.model.get('published'), published: this.model.get('published'),
publishedOn: this.model.get('published_on'), publishedOn: this.model.get('published_on'),
publishedBy: this.model.get('published_by'), publishedBy: this.model.get('published_by'),
releasedToStudents: this.model.get('released_to_students'),
releaseDate: this.model.get('release_date'), releaseDate: this.model.get('release_date'),
releaseDateFrom: this.model.get('release_date_from') releaseDateFrom: this.model.get('release_date_from')
})); }));
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/utils/module"], define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/utils/module"],
function($, _, gettext, ViewUtils, ModuleUtils) { function($, _, gettext, ViewUtils, ModuleUtils) {
var addXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState, var addXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState,
getXBlockVisibilityClass; getXBlockVisibilityClass, getXBlockListTypeClass;
/** /**
* Represents the possible visibility states for an xblock: * Represents the possible visibility states for an xblock:
...@@ -131,11 +131,24 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/util ...@@ -131,11 +131,24 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/util
return ''; return '';
}; };
getXBlockListTypeClass = function (xblockType) {
var listType = 'list-unknown';
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
return listType;
};
return { return {
'VisibilityState': VisibilityState, 'VisibilityState': VisibilityState,
'addXBlock': addXBlock, 'addXBlock': addXBlock,
'deleteXBlock': deleteXBlock, 'deleteXBlock': deleteXBlock,
'updateXBlockField': updateXBlockField, 'updateXBlockField': updateXBlockField,
'getXBlockVisibilityClass': getXBlockVisibilityClass 'getXBlockVisibilityClass': getXBlockVisibilityClass,
'getXBlockListTypeClass': getXBlockListTypeClass
}; };
}); });
...@@ -69,6 +69,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/ ...@@ -69,6 +69,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
html = this.template({ html = this.template({
xblockInfo: xblockInfo, xblockInfo: xblockInfo,
visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')), visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')),
typeListClass: XBlockViewUtils.getXBlockListTypeClass(xblockType),
parentInfo: this.parentInfo, parentInfo: this.parentInfo,
xblockType: xblockType, xblockType: xblockType,
parentType: parentType, parentType: parentType,
......
...@@ -47,7 +47,7 @@ from contentstore.utils import reverse_usage_url ...@@ -47,7 +47,7 @@ from contentstore.utils import reverse_usage_url
<h3 class="sr">${_("Page Actions")}</h3> <h3 class="sr">${_("Page Actions")}</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button button-new" data-category="chapter" data-parent="${context_course.location}" data-default-name="Section"> <a href="#" class="button button-new" data-category="chapter" data-parent="${context_course.location}" data-default-name="${_('Section')}">
<i class="icon-plus"></i>${_('New Section')} <i class="icon-plus"></i>${_('New Section')}
</a> </a>
</li> </li>
......
...@@ -2,28 +2,20 @@ ...@@ -2,28 +2,20 @@
var category = xblockInfo.get('category'); var category = xblockInfo.get('category');
var releasedToStudents = xblockInfo.get('released_to_students'); var releasedToStudents = xblockInfo.get('released_to_students');
var visibilityState = xblockInfo.get('visibility_state'); var visibilityState = xblockInfo.get('visibility_state');
var published = xblockInfo.get('published');
var listType = 'list-unknown';
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
var statusMessage = null; var statusMessage = null;
var statusType = null; var statusType = null;
if (visibilityState === 'staff_only') { if (visibilityState === 'staff_only') {
statusType = 'staff-only'; statusType = 'staff-only';
statusMessage = 'Contains staff only content'; statusMessage = gettext('Contains staff only content');
} else if (visibilityState === 'needs_attention') { } else if (visibilityState === 'needs_attention') {
if (category === 'vertical') { if (category === 'vertical') {
statusType = 'warning'; statusType = 'warning';
if (releasedToStudents) { if (published && releasedToStudents) {
statusMessage = 'Unpublished changes to live content'; statusMessage = gettext('Unpublished changes to live content');
} else { } else {
statusMessage = 'Unpublished units will not be released'; statusMessage = gettext('Unpublished units will not be released');
} }
} }
} }
...@@ -75,7 +67,7 @@ if (statusType === 'warning') { ...@@ -75,7 +67,7 @@ if (statusType === 'warning') {
<% if (category !== 'vertical') { %> <% if (category !== 'vertical') { %>
<div class="status-release"> <div class="status-release">
<p> <p>
<span class="sr status-release-label">Release Status:</span> <span class="sr status-release-label"><%= gettext('Release Status:') %></span>
<span class="status-release-value"> <span class="status-release-value">
<% if (xblockInfo.get('released_to_students')) { %> <% if (xblockInfo.get('released_to_students')) { %>
<i class="icon icon-check-sign"></i> <i class="icon icon-check-sign"></i>
...@@ -115,7 +107,7 @@ if (statusType === 'warning') { ...@@ -115,7 +107,7 @@ if (statusType === 'warning') {
</div> </div>
<% } else { %> <% } else { %>
<div class="outline-content <%= xblockType %>-content"> <div class="outline-content <%= xblockType %>-content">
<ol class="<%= listType %> is-sortable"> <ol class="<%= typeListClass %> is-sortable">
</ol> </ol>
<% if (childType) { %> <% if (childType) { %>
<div class="add-<%= childType %> add-item"> <div class="add-<%= childType %> add-item">
......
...@@ -4,9 +4,9 @@ if (visibilityState === 'staff_only') { ...@@ -4,9 +4,9 @@ if (visibilityState === 'staff_only') {
title = gettext("Unpublished (Staff only)"); title = gettext("Unpublished (Staff only)");
} else if (visibilityState === 'live') { } else if (visibilityState === 'live') {
title = gettext("Published and Live"); title = gettext("Published and Live");
} else if (visibilityState === 'ready') { } else if (published && !hasChanges) {
title = gettext("Published"); title = gettext("Published");
} else if (visibilityState === 'needs_attention') { } else if (published && hasChanges) {
title = gettext("Draft (Unpublished changes)"); title = gettext("Draft (Unpublished changes)");
} }
...@@ -19,7 +19,7 @@ if (visibilityState === 'live') { ...@@ -19,7 +19,7 @@ if (visibilityState === 'live') {
var visibleToStaffOnly = visibilityState === 'staff_only'; var visibleToStaffOnly = visibilityState === 'staff_only';
%> %>
<div class="bit-publishing <%= visibilityClass %>"> <div class="bit-publishing <%= visibilityClass %> <% if (releaseDate) { %>is-scheduled<% } %>">
<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>
<%= title %> <%= title %>
</h3> </h3>
......
<%
var listType = 'list-for-' + xblockType;
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
%>
<% if (parentInfo) { %> <% if (parentInfo) { %>
<li class="outline-item outline-<%= xblockType %> <%= visibilityClass %>" <li class="outline-item outline-<%= xblockType %> <%= visibilityClass %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>"> data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
...@@ -21,7 +11,7 @@ if (xblockType === 'course') { ...@@ -21,7 +11,7 @@ if (xblockType === 'course') {
<% } %> <% } %>
<div class="<%= xblockType %>-content outline-content"> <div class="<%= xblockType %>-content outline-content">
<ol class="<%= listType %>"> <ol class="<%= typeListClass %>">
</ol> </ol>
<% if (childType) { %> <% if (childType) { %>
<div class="add-<%= childType %> add-item"> <div class="add-<%= childType %> add-item">
......
...@@ -18,4 +18,3 @@ class HtmlComponentEditorView(ComponentEditorView): ...@@ -18,4 +18,3 @@ class HtmlComponentEditorView(ComponentEditorView):
ActionChains(self.browser).click(editor).\ ActionChains(self.browser).click(editor).\
send_keys([Keys.CONTROL, 'a']).key_up(Keys.CONTROL).send_keys(content).perform() send_keys([Keys.CONTROL, 'a']).key_up(Keys.CONTROL).send_keys(content).perform()
click_css(self, 'a.action-save') click_css(self, 'a.action-save')
...@@ -380,7 +380,7 @@ class UnitPublishingTest(ContainerBase): ...@@ -380,7 +380,7 @@ class UnitPublishingTest(ContainerBase):
__test__ = True __test__ = True
PUBLISHED_STATUS = "Publishing Status\nPublished" PUBLISHED_STATUS = "Publishing Status\nPublished"
PUBLISHED_LIVE_STATUS ="Publishing Status\nPublished and Live" PUBLISHED_LIVE_STATUS = "Publishing Status\nPublished and Live"
DRAFT_STATUS = "Publishing Status\nDraft (Unpublished changes)" DRAFT_STATUS = "Publishing Status\nDraft (Unpublished changes)"
LOCKED_STATUS = "Publishing Status\nUnpublished (Staff only)" LOCKED_STATUS = "Publishing Status\nUnpublished (Staff only)"
RELEASE_TITLE_RELEASED = "RELEASED:" RELEASE_TITLE_RELEASED = "RELEASED:"
...@@ -398,6 +398,7 @@ class UnitPublishingTest(ContainerBase): ...@@ -398,6 +398,7 @@ class UnitPublishingTest(ContainerBase):
self.courseware = CoursewarePage(self.browser, self.course_id) self.courseware = CoursewarePage(self.browser, self.course_id)
past_start_date = datetime.datetime(1974, 6, 22) past_start_date = datetime.datetime(1974, 6, 22)
self.past_start_date_text = "Jun 22, 1974 at 00:00 UTC" self.past_start_date_text = "Jun 22, 1974 at 00:00 UTC"
future_start_date = datetime.datetime(2100, 9, 13)
course_fixture.add_children( course_fixture.add_children(
XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children(
...@@ -407,20 +408,29 @@ class UnitPublishingTest(ContainerBase): ...@@ -407,20 +408,29 @@ class UnitPublishingTest(ContainerBase):
) )
) )
), ),
XBlockFixtureDesc('chapter', 'Unlocked Section', metadata={'start': past_start_date.isoformat()}).add_children( XBlockFixtureDesc('chapter', 'Unlocked Section',
XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children( metadata={'start': past_start_date.isoformat()}).add_children(
XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children( XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children(
XBlockFixtureDesc('problem', '<problem></problem>', data=self.html_content) XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children(
) XBlockFixtureDesc('problem', '<problem></problem>', data=self.html_content)
) )
)
), ),
XBlockFixtureDesc('chapter', 'Section With Locked Unit').add_children( XBlockFixtureDesc('chapter', 'Section With Locked Unit').add_children(
XBlockFixtureDesc('sequential', 'Subsection With Locked Unit', metadata={'start': past_start_date.isoformat()}).add_children( XBlockFixtureDesc('sequential', 'Subsection With Locked Unit',
XBlockFixtureDesc('vertical', 'Locked Unit', metadata={'visible_to_staff_only': True}).add_children( metadata={'start': past_start_date.isoformat()}).add_children(
XBlockFixtureDesc('discussion', '', data=self.html_content) XBlockFixtureDesc('vertical', 'Locked Unit',
) metadata={'visible_to_staff_only': True}).add_children(
) XBlockFixtureDesc('discussion', '', data=self.html_content)
) )
)
),
XBlockFixtureDesc('chapter', 'Unreleased Section',
metadata={'start': future_start_date.isoformat()}).add_children(
XBlockFixtureDesc('sequential', 'Unreleased Subsection').add_children(
XBlockFixtureDesc('vertical', 'Unreleased Unit')
)
)
) )
def test_publishing(self): def test_publishing(self):
...@@ -658,6 +668,25 @@ class UnitPublishingTest(ContainerBase): ...@@ -658,6 +668,25 @@ class UnitPublishingTest(ContainerBase):
self._view_published_version(unit) self._view_published_version(unit)
self.assertEqual(0, self.courseware.num_xblock_components) self.assertEqual(0, self.courseware.num_xblock_components)
def test_published_not_live(self):
"""
Scenario: The publish title displays correctly for units that are not live
Given I have a published unit with no unpublished changes that releases in the future
When I go to the unit page in Studio
Then the title in the Publish information box is "Published"
And when I add a component to the unit
Then the title in the Publish information box is "Draft (Unpublished changes)"
And when I click the Publish button
Then the title in the Publish information box is "Published"
"""
unit = self.go_to_unit_page('Unreleased Section', 'Unreleased Subsection', 'Unreleased Unit')
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
add_discussion(unit)
self._verify_publish_title(unit, self.DRAFT_STATUS)
unit.publish_action.click()
unit.wait_for_ajax()
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
def _view_published_version(self, unit): def _view_published_version(self, unit):
""" """
Goes to the published version, then waits for the browser to load the page. Goes to the published version, then waits for the browser to load the page.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment