Correct publish titles, code review feedback.
This commit is contained in:
@@ -1209,7 +1209,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
resp = self._show_course_overview(course.id)
|
||||
self.assertContains(
|
||||
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',
|
||||
course_key='MITx/999/Robot_Super_Course',
|
||||
),
|
||||
|
||||
@@ -149,7 +149,7 @@ def course_image_url(course):
|
||||
path = loc.to_deprecated_string()
|
||||
return path
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def is_currently_visible_to_students(xblock):
|
||||
"""
|
||||
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):
|
||||
# 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)
|
||||
|
||||
# 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.
|
||||
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
|
||||
# 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
|
||||
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
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 = {
|
||||
"id": unicode(xblock.location),
|
||||
"display_name": xblock.display_name_with_default,
|
||||
@@ -646,7 +649,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
"release_date": release_date,
|
||||
"release_date_from": _get_release_date_from(xblock) if release_date else None,
|
||||
"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:
|
||||
xblock_info["data"] = data
|
||||
@@ -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)
|
||||
if 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
|
||||
|
||||
|
||||
@@ -680,18 +688,20 @@ class VisibilityState(object):
|
||||
"""
|
||||
live = 'live'
|
||||
ready = 'ready'
|
||||
unscheduled = 'unscheduled' # unscheduled
|
||||
unscheduled = 'unscheduled'
|
||||
needs_attention = 'needs_attention'
|
||||
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
|
||||
"""
|
||||
if xblock.visible_to_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
|
||||
is_unscheduled = xblock.start == DEFAULT_START_DATE
|
||||
is_live = datetime.now(UTC) > xblock.start
|
||||
|
||||
@@ -231,6 +231,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertEqual(json_response['category'], 'course')
|
||||
self.assertEqual(json_response['id'], 'i4x://MITx/999/course/Robot_Super_Course')
|
||||
self.assertEqual(json_response['display_name'], 'Robot Super Course')
|
||||
self.assertTrue(json_response['published'])
|
||||
self.assertIsNone(json_response['visibility_state'])
|
||||
|
||||
# Now verify the first child
|
||||
@@ -240,6 +241,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertEqual(first_child_response['category'], 'chapter')
|
||||
self.assertEqual(first_child_response['id'], 'i4x://MITx/999/chapter/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.assertTrue(len(first_child_response['child_info']['children']) > 0)
|
||||
|
||||
@@ -253,6 +255,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertIsNotNone(json_response['display_name'])
|
||||
self.assertIsNotNone(json_response['id'])
|
||||
self.assertIsNotNone(json_response['category'])
|
||||
self.assertTrue(json_response['published'])
|
||||
if json_response.get('child_info', None):
|
||||
for child_response in json_response['child_info']['children']:
|
||||
self.assert_correct_json_response(child_response)
|
||||
|
||||
@@ -531,8 +531,25 @@ class TestEditItem(ItemTest):
|
||||
self.assertEqual(unit2_usage_key, children[1])
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
""" Test making a private problem public (publishing it). """
|
||||
# When the problem is first created, it is only in draft (because of its category).
|
||||
@@ -541,7 +558,7 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
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):
|
||||
""" Test creating a draft version of a public problem. """
|
||||
@@ -554,17 +571,13 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
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)
|
||||
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.assertFalse(self._is_location_published(self.problem_usage_key))
|
||||
@@ -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(
|
||||
self.problem_usage_key,
|
||||
revision=ModuleStoreEnum.RevisionOption.published_only
|
||||
@@ -616,7 +629,7 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
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)
|
||||
|
||||
# Update the draft version and check that published is different.
|
||||
@@ -651,7 +664,7 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
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)
|
||||
|
||||
# Now make a draft
|
||||
@@ -706,8 +719,8 @@ class TestEditItem(ItemTest):
|
||||
data={'publish': 'make_public'}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(self._is_location_published(unit_usage_key))
|
||||
self.assertTrue(self._is_location_published(html_usage_key))
|
||||
self._verify_published_with_no_draft(unit_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
|
||||
resp = self.client.ajax_post(
|
||||
@@ -718,10 +731,8 @@ class TestEditItem(ItemTest):
|
||||
}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(self._is_location_published(unit_usage_key))
|
||||
self.assertTrue(self._is_location_published(html_usage_key))
|
||||
self.assertTrue(modulestore().get_item(unit_usage_key).has_changes())
|
||||
self.assertTrue(modulestore().get_item(html_usage_key).has_changes())
|
||||
self._verify_published_with_draft(unit_usage_key)
|
||||
self._verify_published_with_draft(html_usage_key)
|
||||
|
||||
|
||||
class TestEditSplitModule(ItemTest):
|
||||
@@ -1143,6 +1154,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['category'], 'course')
|
||||
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/course/Robot_Super_Course')
|
||||
self.assertEqual(xblock_info['display_name'], 'Robot Super Course')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
|
||||
@@ -1154,6 +1166,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['category'], 'chapter')
|
||||
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/chapter/Week_1')
|
||||
self.assertEqual(xblock_info['display_name'], 'Week 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertEqual(xblock_info['edited_by'], 'testuser')
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
@@ -1166,6 +1179,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['category'], 'sequential')
|
||||
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/sequential/Lesson_1')
|
||||
self.assertEqual(xblock_info['display_name'], 'Lesson 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertEqual(xblock_info['edited_by'], 'testuser')
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
@@ -1178,6 +1192,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['category'], 'vertical')
|
||||
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/vertical/Unit_1')
|
||||
self.assertEqual(xblock_info['display_name'], 'Unit 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertEqual(xblock_info['edited_by'], 'testuser')
|
||||
|
||||
# Validate that the correct ancestor info has been included
|
||||
@@ -1199,6 +1214,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['category'], 'video')
|
||||
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/video/My_Video')
|
||||
self.assertEqual(xblock_info['display_name'], 'My Video')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertEqual(xblock_info['edited_by'], 'testuser')
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
@@ -1211,6 +1227,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertIsNotNone(xblock_info['display_name'])
|
||||
self.assertIsNotNone(xblock_info['id'])
|
||||
self.assertIsNotNone(xblock_info['category'])
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertEqual(xblock_info['edited_by'], 'testuser')
|
||||
if has_ancestor_info:
|
||||
self.assertIsNotNone(xblock_info.get('ancestor_info', None))
|
||||
@@ -1243,14 +1260,17 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
SECOND_UNIT_PATH = [0, 1]
|
||||
|
||||
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(
|
||||
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
|
||||
)
|
||||
|
||||
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']
|
||||
self.assertTrue(len(children) > index)
|
||||
@@ -1293,12 +1313,12 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
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
|
||||
then the root item is verified.
|
||||
then the root item will be verified.
|
||||
"""
|
||||
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
|
||||
self._verify_visibility_state(direct_child, expected_state, remaining_path)
|
||||
self._verify_visibility_state(direct_child_xblock_info, expected_state, remaining_path)
|
||||
else:
|
||||
self.assertEqual(xblock_info['visibility_state'], expected_state)
|
||||
|
||||
@@ -1311,10 +1331,13 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
|
||||
self._create_child(chapter, 'sequential', "Empty Sequential")
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self.assertEqual(xblock_info['visibility_state'], VisibilityState.unscheduled)
|
||||
self.assertEqual(self._get_child(xblock_info, 0)['visibility_state'], VisibilityState.unscheduled)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.unscheduled)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.unscheduled, path=self.FIRST_SUBSECTION_PATH)
|
||||
|
||||
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")
|
||||
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
|
||||
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
|
||||
@@ -1327,6 +1350,9 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
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")
|
||||
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
|
||||
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
|
||||
@@ -1339,10 +1365,14 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
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")
|
||||
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
|
||||
unit = self._create_child(sequential, 'vertical', "Published Unit", publish_item=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')
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention)
|
||||
|
||||
@@ -46,7 +46,8 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
|
||||
"published_by": null,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
category: 'vertical',
|
||||
published: false,
|
||||
has_changes: false,
|
||||
visibility_state: 'unscheduled',
|
||||
visibility_state: VisibilityState.unscheduled,
|
||||
edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe",
|
||||
published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako",
|
||||
currently_visible_to_students: false
|
||||
@@ -116,13 +116,15 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
liveClass = "is-live",
|
||||
readyClass = "is-ready",
|
||||
staffOnlyClass = "is-staff-only",
|
||||
scheduledClass = "is-scheduled",
|
||||
unscheduledClass = "",
|
||||
hasWarningsClass = 'has-warnings',
|
||||
publishButtonCss = ".action-publish",
|
||||
discardChangesButtonCss = ".action-discard",
|
||||
lastDraftCss = ".wrapper-last-draft",
|
||||
releaseDateTitleCss = ".wrapper-release .title",
|
||||
releaseDateContentCss = ".wrapper-release .copy",
|
||||
promptSpies, sendDiscardChangesToServer;
|
||||
promptSpies, sendDiscardChangesToServer, verifyPublishingBitUnscheduled;
|
||||
|
||||
sendDiscardChangesToServer = function() {
|
||||
// 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
|
||||
);
|
||||
};
|
||||
|
||||
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() {
|
||||
promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"]);
|
||||
promptSpies.show.andReturn(this.promptSpies);
|
||||
});
|
||||
|
||||
it('renders correctly with unscheduled content', function () {
|
||||
it('renders correctly with private content', function () {
|
||||
var verifyPrivateState = function() {
|
||||
expect(containerPage.$(headerCss).text()).toContain('Draft (Never published)');
|
||||
expect(containerPage.$(publishButtonCss)).not.toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
|
||||
};
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({published: false, has_changes: false});
|
||||
fetch({published: false, has_changes: false, visibility_state: VisibilityState.needsAttention});
|
||||
verifyPrivateState();
|
||||
|
||||
fetch({published: false, has_changes: true});
|
||||
fetch({published: false, has_changes: true, visibility_state: VisibilityState.needsAttention});
|
||||
verifyPrivateState();
|
||||
});
|
||||
|
||||
it('renders correctly with published content', function () {
|
||||
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.$(publishButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
|
||||
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.$(publishButtonCss)).not.toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass(disabledCss);
|
||||
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.$(publishButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
|
||||
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 () {
|
||||
@@ -217,7 +249,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
it('does not refresh if publish fails', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
verifyPublishingBitUnscheduled();
|
||||
|
||||
// Click publish
|
||||
containerPage.$(publishButtonCss).click();
|
||||
@@ -228,8 +260,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
expect(requests.length).toEqual(numRequests);
|
||||
|
||||
// Verify still in draft state.
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
// Verify still in draft (unscheduled) state.
|
||||
verifyPublishingBitUnscheduled();
|
||||
// Verify that the "published" value has been cleared out of the model.
|
||||
expect(containerPage.model.get("publish")).toBeNull();
|
||||
});
|
||||
@@ -273,7 +305,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
it('does not discard changes on cancel', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({published: true, has_changes: true});
|
||||
fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
|
||||
var numRequests = requests.length;
|
||||
|
||||
// Click discard changes
|
||||
@@ -373,7 +405,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
create_sinon.respondWithJson(requests, createXBlockInfo({
|
||||
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
|
||||
expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check');
|
||||
expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff Only');
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass);
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
|
||||
} else {
|
||||
expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check-empty');
|
||||
expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff and Students');
|
||||
@@ -403,18 +437,19 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it("can remove staff only setting", function() {
|
||||
promptSpy = edit_helpers.createPromptSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {
|
||||
visibility_state: VisibilityState.staffOnly
|
||||
visibility_state: VisibilityState.staffOnly,
|
||||
release_date: "Jul 02, 2000 at 14:20 UTC"
|
||||
});
|
||||
requestStaffOnly(false);
|
||||
verifyStaffOnly(false);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
});
|
||||
|
||||
it("does not refresh if removing staff only is canceled", function() {
|
||||
var requestCount;
|
||||
promptSpy = edit_helpers.createPromptSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {
|
||||
visibility_state: VisibilityState.staffOnly
|
||||
visibility_state: VisibilityState.staffOnly,
|
||||
release_date: "Jul 02, 2000 at 14:20 UTC"
|
||||
});
|
||||
requestCount = requests.length;
|
||||
containerPage.$('.action-staff-lock').click();
|
||||
|
||||
@@ -118,6 +118,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
category: 'vertical',
|
||||
studio_url: '/container/mock-unit',
|
||||
is_container: true,
|
||||
has_changes: false,
|
||||
published: true,
|
||||
visibility_state: 'unscheduled',
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser'
|
||||
|
||||
@@ -116,7 +116,6 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
published: this.model.get('published'),
|
||||
publishedOn: this.model.get('published_on'),
|
||||
publishedBy: this.model.get('published_by'),
|
||||
releasedToStudents: this.model.get('released_to_students'),
|
||||
releaseDate: this.model.get('release_date'),
|
||||
releaseDateFrom: this.model.get('release_date_from')
|
||||
}));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/utils/module"],
|
||||
function($, _, gettext, ViewUtils, ModuleUtils) {
|
||||
var addXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState,
|
||||
getXBlockVisibilityClass;
|
||||
getXBlockVisibilityClass, getXBlockListTypeClass;
|
||||
|
||||
/**
|
||||
* Represents the possible visibility states for an xblock:
|
||||
@@ -131,11 +131,24 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/util
|
||||
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 {
|
||||
'VisibilityState': VisibilityState,
|
||||
'addXBlock': addXBlock,
|
||||
'deleteXBlock': deleteXBlock,
|
||||
'updateXBlockField': updateXBlockField,
|
||||
'getXBlockVisibilityClass': getXBlockVisibilityClass
|
||||
'getXBlockVisibilityClass': getXBlockVisibilityClass,
|
||||
'getXBlockListTypeClass': getXBlockListTypeClass
|
||||
};
|
||||
});
|
||||
|
||||
@@ -69,6 +69,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
html = this.template({
|
||||
xblockInfo: xblockInfo,
|
||||
visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')),
|
||||
typeListClass: XBlockViewUtils.getXBlockListTypeClass(xblockType),
|
||||
parentInfo: this.parentInfo,
|
||||
xblockType: xblockType,
|
||||
parentType: parentType,
|
||||
|
||||
@@ -47,7 +47,7 @@ from contentstore.utils import reverse_usage_url
|
||||
<h3 class="sr">${_("Page Actions")}</h3>
|
||||
<ul>
|
||||
<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')}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -2,28 +2,20 @@
|
||||
var category = xblockInfo.get('category');
|
||||
var releasedToStudents = xblockInfo.get('released_to_students');
|
||||
var visibilityState = xblockInfo.get('visibility_state');
|
||||
|
||||
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 published = xblockInfo.get('published');
|
||||
|
||||
var statusMessage = null;
|
||||
var statusType = null;
|
||||
if (visibilityState === 'staff_only') {
|
||||
statusType = 'staff-only';
|
||||
statusMessage = 'Contains staff only content';
|
||||
statusMessage = gettext('Contains staff only content');
|
||||
} else if (visibilityState === 'needs_attention') {
|
||||
if (category === 'vertical') {
|
||||
statusType = 'warning';
|
||||
if (releasedToStudents) {
|
||||
statusMessage = 'Unpublished changes to live content';
|
||||
if (published && releasedToStudents) {
|
||||
statusMessage = gettext('Unpublished changes to live content');
|
||||
} else {
|
||||
statusMessage = 'Unpublished units will not be released';
|
||||
statusMessage = gettext('Unpublished units will not be released');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +67,7 @@ if (statusType === 'warning') {
|
||||
<% if (category !== 'vertical') { %>
|
||||
<div class="status-release">
|
||||
<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">
|
||||
<% if (xblockInfo.get('released_to_students')) { %>
|
||||
<i class="icon icon-check-sign"></i>
|
||||
@@ -115,7 +107,7 @@ if (statusType === 'warning') {
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="outline-content <%= xblockType %>-content">
|
||||
<ol class="<%= listType %> is-sortable">
|
||||
<ol class="<%= typeListClass %> is-sortable">
|
||||
</ol>
|
||||
<% if (childType) { %>
|
||||
<div class="add-<%= childType %> add-item">
|
||||
|
||||
@@ -4,9 +4,9 @@ if (visibilityState === 'staff_only') {
|
||||
title = gettext("Unpublished (Staff only)");
|
||||
} else if (visibilityState === 'live') {
|
||||
title = gettext("Published and Live");
|
||||
} else if (visibilityState === 'ready') {
|
||||
} else if (published && !hasChanges) {
|
||||
title = gettext("Published");
|
||||
} else if (visibilityState === 'needs_attention') {
|
||||
} else if (published && hasChanges) {
|
||||
title = gettext("Draft (Unpublished changes)");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ if (visibilityState === 'live') {
|
||||
|
||||
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>
|
||||
<%= title %>
|
||||
</h3>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
<%
|
||||
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) { %>
|
||||
<li class="outline-item outline-<%= xblockType %> <%= visibilityClass %>"
|
||||
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
|
||||
@@ -21,7 +11,7 @@ if (xblockType === 'course') {
|
||||
<% } %>
|
||||
|
||||
<div class="<%= xblockType %>-content outline-content">
|
||||
<ol class="<%= listType %>">
|
||||
<ol class="<%= typeListClass %>">
|
||||
</ol>
|
||||
<% if (childType) { %>
|
||||
<div class="add-<%= childType %> add-item">
|
||||
|
||||
@@ -18,4 +18,3 @@ class HtmlComponentEditorView(ComponentEditorView):
|
||||
ActionChains(self.browser).click(editor).\
|
||||
send_keys([Keys.CONTROL, 'a']).key_up(Keys.CONTROL).send_keys(content).perform()
|
||||
click_css(self, 'a.action-save')
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
__test__ = True
|
||||
|
||||
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)"
|
||||
LOCKED_STATUS = "Publishing Status\nUnpublished (Staff only)"
|
||||
RELEASE_TITLE_RELEASED = "RELEASED:"
|
||||
@@ -398,6 +398,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
self.courseware = CoursewarePage(self.browser, self.course_id)
|
||||
past_start_date = datetime.datetime(1974, 6, 22)
|
||||
self.past_start_date_text = "Jun 22, 1974 at 00:00 UTC"
|
||||
future_start_date = datetime.datetime(2100, 9, 13)
|
||||
|
||||
course_fixture.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section').add_children(
|
||||
@@ -407,20 +408,29 @@ class UnitPublishingTest(ContainerBase):
|
||||
)
|
||||
)
|
||||
),
|
||||
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', '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)
|
||||
)
|
||||
)
|
||||
)
|
||||
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)
|
||||
)
|
||||
)
|
||||
),
|
||||
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):
|
||||
@@ -658,6 +668,25 @@ class UnitPublishingTest(ContainerBase):
|
||||
self._view_published_version(unit)
|
||||
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):
|
||||
"""
|
||||
Goes to the published version, then waits for the browser to load the page.
|
||||
|
||||
Reference in New Issue
Block a user