Problem content draft.
", - 'publish': 'create_draft' + 'data': "Problem content draft.
" } ) @@ -746,6 +694,9 @@ class TestEditItem(ItemTest): # Both published and draft content should still be different draft = self.get_item_from_modulestore(self.problem_usage_key, verify_is_draft=True) self.assertNotEqual(draft.data, published.data) + # Fetch the published version again to make sure the data is correct. + published = modulestore().get_item(published.location, revision=ModuleStoreEnum.RevisionOption.published_only) + self.assertNotEqual(draft.data, published.data) def test_publish_states_of_nested_xblocks(self): """ Test publishing of a unit page containing a nested xblock """ @@ -759,8 +710,8 @@ class TestEditItem(ItemTest): # The unit and its children should be private initially unit_update_url = reverse_usage_url('xblock_handler', unit_usage_key) - self.verify_publish_state(unit_usage_key, PublishState.private) - self.verify_publish_state(html_usage_key, PublishState.private) + self.assertFalse(self._is_location_published(unit_usage_key)) + self.assertFalse(self._is_location_published(html_usage_key)) # Make the unit public and verify that the problem is also made public resp = self.client.ajax_post( @@ -768,8 +719,8 @@ class TestEditItem(ItemTest): data={'publish': 'make_public'} ) self.assertEqual(resp.status_code, 200) - self.verify_publish_state(unit_usage_key, PublishState.public) - self.verify_publish_state(html_usage_key, PublishState.public) + 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( @@ -777,12 +728,11 @@ class TestEditItem(ItemTest): data={ 'id': unicode(unit_usage_key), 'metadata': {}, - 'publish': 'create_draft' } ) self.assertEqual(resp.status_code, 200) - self.verify_publish_state(unit_usage_key, PublishState.draft) - self.verify_publish_state(html_usage_key, PublishState.draft) + self._verify_published_with_draft(unit_usage_key) + self._verify_published_with_draft(html_usage_key) class TestEditSplitModule(ItemTest): @@ -1132,3 +1082,371 @@ class TestComponentTemplates(CourseTestCase): self.assertIsNotNone(ora_template) self.assertEqual(ora_template.get('category'), 'openassessment') self.assertIsNone(ora_template.get('boilerplate_name', None)) + + +class TestXBlockInfo(ItemTest): + """ + Unit tests for XBlock's outline handling. + """ + def setUp(self): + super(TestXBlockInfo, self).setUp() + user_id = self.user.id + self.chapter = ItemFactory.create( + parent_location=self.course.location, category='chapter', display_name="Week 1", user_id=user_id + ) + self.sequential = ItemFactory.create( + parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", user_id=user_id + ) + self.vertical = ItemFactory.create( + parent_location=self.sequential.location, category='vertical', display_name='Unit 1', user_id=user_id + ) + self.video = ItemFactory.create( + parent_location=self.vertical.location, category='video', display_name='My Video', user_id=user_id + ) + + def test_json_responses(self): + outline_url = reverse_usage_url('xblock_outline_handler', self.usage_key) + resp = self.client.get(outline_url, HTTP_ACCEPT='application/json') + json_response = json.loads(resp.content) + self.validate_course_xblock_info(json_response, course_outline=True) + + def test_chapter_xblock_info(self): + chapter = modulestore().get_item(self.chapter.location) + xblock_info = create_xblock_info( + chapter, + include_child_info=True, + include_children_predicate=ALWAYS, + ) + self.validate_chapter_xblock_info(xblock_info) + + def test_sequential_xblock_info(self): + sequential = modulestore().get_item(self.sequential.location) + xblock_info = create_xblock_info( + sequential, + include_child_info=True, + include_children_predicate=ALWAYS, + ) + self.validate_sequential_xblock_info(xblock_info) + + def test_vertical_xblock_info(self): + vertical = modulestore().get_item(self.vertical.location) + xblock_info = create_xblock_info( + vertical, + include_child_info=True, + include_children_predicate=ALWAYS, + include_ancestor_info=True + ) + self.validate_vertical_xblock_info(xblock_info) + + def test_component_xblock_info(self): + video = modulestore().get_item(self.video.location) + xblock_info = create_xblock_info( + video, + include_child_info=True, + include_children_predicate=ALWAYS + ) + self.validate_component_xblock_info(xblock_info) + + def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False): + """ + Validate that the xblock info is correct for the test course. + """ + 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, course_outline=course_outline) + + def validate_chapter_xblock_info(self, xblock_info, has_child_info=True): + """ + Validate that the xblock info is correct for the test chapter. + """ + 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.assertIsNone(xblock_info.get('edited_by', None)) + self.assertEqual(xblock_info['course_graders'], '["Homework", "Lab", "Midterm Exam", "Final Exam"]') + self.assertEqual(xblock_info['start'], '2030-01-01T00:00:00Z') + self.assertEqual(xblock_info['graded'], False) + self.assertEqual(xblock_info['due'], None) + self.assertEqual(xblock_info['format'], None) + + # Finally, validate the entire response for consistency + self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) + + def validate_sequential_xblock_info(self, xblock_info, has_child_info=True): + """ + Validate that the xblock info is correct for the test sequential. + """ + 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.assertIsNone(xblock_info.get('edited_by', None)) + + # Finally, validate the entire response for consistency + self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) + + def validate_vertical_xblock_info(self, xblock_info): + """ + Validate that the xblock info is correct for the test vertical. + """ + 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 + ancestor_info = xblock_info.get('ancestor_info', None) + self.assertIsNotNone(ancestor_info) + ancestors = ancestor_info['ancestors'] + self.assertEqual(len(ancestors), 3) + self.validate_sequential_xblock_info(ancestors[0], has_child_info=True) + self.validate_chapter_xblock_info(ancestors[1], has_child_info=False) + self.validate_course_xblock_info(ancestors[2], has_child_info=False) + + # Finally, validate the entire response for consistency + self.validate_xblock_info_consistency(xblock_info, has_child_info=True, has_ancestor_info=True) + + def validate_component_xblock_info(self, xblock_info): + """ + Validate that the xblock info is correct for the test component. + """ + 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.assertIsNone(xblock_info.get('edited_by', None)) + + # Finally, validate the entire response for consistency + self.validate_xblock_info_consistency(xblock_info) + + def validate_xblock_info_consistency(self, xblock_info, has_ancestor_info=False, has_child_info=False, + course_outline=False): + """ + Validate that the xblock info is internally consistent. + """ + self.assertIsNotNone(xblock_info['display_name']) + self.assertIsNotNone(xblock_info['id']) + self.assertIsNotNone(xblock_info['category']) + self.assertTrue(xblock_info['published']) + if has_ancestor_info: + self.assertIsNotNone(xblock_info.get('ancestor_info', None)) + ancestors = xblock_info['ancestor_info']['ancestors'] + for ancestor in xblock_info['ancestor_info']['ancestors']: + self.validate_xblock_info_consistency( + ancestor, + has_child_info=(ancestor == ancestors[0]), # Only the direct ancestor includes children + course_outline=course_outline + ) + else: + self.assertIsNone(xblock_info.get('ancestor_info', None)) + if has_child_info: + self.assertIsNotNone(xblock_info.get('child_info', None)) + if xblock_info['child_info'].get('children', None): + for child_response in xblock_info['child_info']['children']: + self.validate_xblock_info_consistency( + child_response, + has_child_info=(not child_response.get('child_info', None) is None), + course_outline=course_outline + ) + else: + self.assertIsNone(xblock_info.get('child_info', None)) + if xblock_info['category'] == 'vertical' and not course_outline: + self.assertEqual(xblock_info['edited_by'], 'testuser') + else: + self.assertIsNone(xblock_info.get('edited_by', None)) + + +class TestXBlockPublishingInfo(ItemTest): + """ + Unit tests for XBlock's outline handling. + """ + FIRST_SUBSECTION_PATH = [0] + FIRST_UNIT_PATH = [0, 0] + 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_xblock_info(self, xblock_info, index): + """ + Returns the child xblock info at the specified index. + """ + children = xblock_info['child_info']['children'] + self.assertTrue(len(children) > index) + return children[index] + + def _get_xblock_info(self, location): + """ + Returns the xblock info for the specified location. + """ + return create_xblock_info( + modulestore().get_item(location), + include_child_info=True, + include_children_predicate=ALWAYS, + ) + + def _set_release_date(self, location, start): + """ + Sets the release date for the specified xblock. + """ + xblock = modulestore().get_item(location) + xblock.start = start + self.store.update_item(xblock, self.user.id) + + def _set_staff_only(self, location, staff_only): + """ + Sets staff only for the specified xblock. + """ + xblock = modulestore().get_item(location) + xblock.visible_to_staff_only = staff_only + self.store.update_item(xblock, self.user.id) + + def _set_display_name(self, location, display_name): + """ + Sets the display name for the specified xblock. + """ + xblock = modulestore().get_item(location) + xblock.display_name = display_name + self.store.update_item(xblock, self.user.id) + + 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 will be verified. + """ + if path: + 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_xblock_info, expected_state, remaining_path) + else: + self.assertEqual(xblock_info['visibility_state'], expected_state) + + def test_empty_chapter(self): + empty_chapter = self._create_child(self.course, 'chapter', "Empty Chapter") + xblock_info = self._get_xblock_info(empty_chapter.location) + self.assertEqual(xblock_info['visibility_state'], VisibilityState.unscheduled) + + def test_empty_sequential(self): + 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._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) + self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) + xblock_info = self._get_xblock_info(chapter.location) + self._verify_visibility_state(xblock_info, VisibilityState.ready) + self._verify_visibility_state(xblock_info, VisibilityState.ready, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.ready, path=self.FIRST_UNIT_PATH) + 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) + self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1)) + xblock_info = self._get_xblock_info(chapter.location) + self._verify_visibility_state(xblock_info, VisibilityState.live) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH) + 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) + self._verify_visibility_state(xblock_info, VisibilityState.needs_attention, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.needs_attention, path=self.FIRST_UNIT_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + + def test_partially_released_section(self): + chapter = self._create_child(self.course, 'chapter', "Test Chapter") + released_sequential = self._create_child(chapter, 'sequential', "Released Sequential") + self._create_child(released_sequential, 'vertical', "Released Unit", publish_item=True) + self._create_child(released_sequential, 'vertical', "Staff Only Unit", staff_only=True) + self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1)) + published_sequential = self._create_child(chapter, 'sequential', "Published Sequential") + self._create_child(published_sequential, 'vertical', "Published Unit", publish_item=True) + self._create_child(published_sequential, 'vertical', "Staff Only Unit", staff_only=True) + self._set_release_date(published_sequential.location, datetime.now(UTC) + timedelta(days=1)) + xblock_info = self._get_xblock_info(chapter.location) + + # Verify the state of the released sequential + self._verify_visibility_state(xblock_info, VisibilityState.live, path=[0]) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=[0, 0]) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[0, 1]) + + # Verify the state of the published sequential + self._verify_visibility_state(xblock_info, VisibilityState.ready, path=[1]) + self._verify_visibility_state(xblock_info, VisibilityState.ready, path=[1, 0]) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[1, 1]) + + # Finally verify the state of the chapter + self._verify_visibility_state(xblock_info, VisibilityState.ready) + + def test_staff_only(self): + 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") + self._set_staff_only(unit.location, True) + xblock_info = self._get_xblock_info(chapter.location) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH) + + def test_unscheduled_section_with_live_subsection(self): + 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) + self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + self._set_release_date(sequential.location, datetime.now(UTC) - timedelta(days=1)) + xblock_info = self._get_xblock_info(chapter.location) + self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) + + def test_unreleased_section_with_live_subsection(self): + 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) + self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True) + self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1)) + self._set_release_date(sequential.location, datetime.now(UTC) - timedelta(days=1)) + xblock_info = self._get_xblock_info(chapter.location) + self._verify_visibility_state(xblock_info, VisibilityState.needs_attention) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH) + self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH) diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index e1c18711bd..ee26693d4e 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -38,7 +38,11 @@ class GetPreviewHtmlTestCase(TestCase): request.session = {} # Call get_preview_fragment directly. - html = get_preview_fragment(request, html, {}).content + context = { + 'reorderable_items': set(), + 'read_only': True + } + html = get_preview_fragment(request, html, context).content # Verify student view html is returned, and the usage ID is as expected. self.assertRegexpMatches( diff --git a/cms/djangoapps/contentstore/views/tests/test_unit_page.py b/cms/djangoapps/contentstore/views/tests/test_unit_page.py index 967d6628aa..f25ddfba6e 100644 --- a/cms/djangoapps/contentstore/views/tests/test_unit_page.py +++ b/cms/djangoapps/contentstore/views/tests/test_unit_page.py @@ -21,35 +21,18 @@ class UnitPageTestCase(StudioPageTestCase): category="video", display_name="My Video") self.store = modulestore() - def test_public_unit_page_html(self): - """ - Verify that an xblock returns the expected HTML for a public unit page. - """ - - html = self.get_page_html(self.vertical) - self.validate_html_for_add_buttons(html) - - def test_draft_unit_page_html(self): - """ - Verify that an xblock returns the expected HTML for a draft unit page. - """ - html = self.get_page_html(self.vertical) - self.validate_html_for_add_buttons(html) - def test_public_component_preview_html(self): """ Verify that a public xblock's preview returns the expected HTML. """ published_video = self.store.publish(self.video.location, self.user.id) - self.validate_preview_html(self.video, STUDENT_VIEW, - can_edit=True, can_reorder=True, can_add=False) + self.validate_preview_html(self.video, STUDENT_VIEW, can_add=False) def test_draft_component_preview_html(self): """ Verify that a draft xblock's preview returns the expected HTML. """ - self.validate_preview_html(self.video, STUDENT_VIEW, - can_edit=True, can_reorder=True, can_add=False) + self.validate_preview_html(self.video, STUDENT_VIEW, can_add=False) def test_public_child_container_preview_html(self): """ @@ -61,8 +44,7 @@ class UnitPageTestCase(StudioPageTestCase): ItemFactory.create(parent_location=child_container.location, category='html', display_name='grandchild') published_child_container = self.store.publish(child_container.location, self.user.id) - self.validate_preview_html(published_child_container, STUDENT_VIEW, - can_reorder=True, can_edit=True, can_add=False) + self.validate_preview_html(published_child_container, STUDENT_VIEW, can_add=False) def test_draft_child_container_preview_html(self): """ @@ -74,5 +56,4 @@ class UnitPageTestCase(StudioPageTestCase): ItemFactory.create(parent_location=child_container.location, category='html', display_name='grandchild') draft_child_container = self.store.get_item(child_container.location) - self.validate_preview_html(draft_child_container, STUDENT_VIEW, - can_reorder=True, can_edit=True, can_add=False) + self.validate_preview_html(draft_child_container, STUDENT_VIEW, can_add=False) diff --git a/cms/djangoapps/contentstore/views/tests/utils.py b/cms/djangoapps/contentstore/views/tests/utils.py index 046465e35a..094a789214 100644 --- a/cms/djangoapps/contentstore/views/tests/utils.py +++ b/cms/djangoapps/contentstore/views/tests/utils.py @@ -41,19 +41,16 @@ class StudioPageTestCase(CourseTestCase): resp_content = json.loads(resp.content) return resp_content['html'] - def validate_preview_html(self, xblock, view_name, can_edit=True, can_reorder=True, can_add=True): + def validate_preview_html(self, xblock, view_name, can_add=True): """ Verify that the specified xblock's preview has the expected HTML elements. """ html = self.get_preview_html(xblock, view_name) - self.validate_html_for_add_buttons(html, can_add=can_add) + self.validate_html_for_add_buttons(html, can_add) - # Verify that there are no drag handles for public blocks + # Verify drag handles always appear. drag_handle_html = '' - if can_reorder: - self.assertIn(drag_handle_html, html) - else: - self.assertNotIn(drag_handle_html, html) + self.assertIn(drag_handle_html, html) # Verify that there are no action buttons for public blocks expected_button_html = [ @@ -62,10 +59,7 @@ class StudioPageTestCase(CourseTestCase): '' ] for button_html in expected_button_html: - if can_edit: - self.assertIn(button_html, html) - else: - self.assertNotIn(button_html, html) + self.assertIn(button_html, html) def validate_html_for_add_buttons(self, html, can_add=True): """ diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee index 46b039956c..2854eabe34 100644 --- a/cms/static/coffee/spec/main.coffee +++ b/cms/static/coffee/spec/main.coffee @@ -202,10 +202,8 @@ define([ "coffee/spec/models/settings_grading_spec", "coffee/spec/models/textbook_spec", "coffee/spec/models/upload_spec", - "coffee/spec/views/section_spec", "coffee/spec/views/course_info_spec", "coffee/spec/views/feedback_spec", "coffee/spec/views/metadata_edit_spec", "coffee/spec/views/module_edit_spec", - "coffee/spec/views/overview_spec", "coffee/spec/views/textbook_spec", "coffee/spec/views/upload_spec", "js/spec/video/transcripts/utils_spec", "js/spec/video/transcripts/editor_spec", @@ -214,23 +212,27 @@ define([ "js/spec/models/component_template_spec", "js/spec/models/explicit_url_spec", + "js/spec/models/xblock_info_spec", "js/spec/utils/drag_and_drop_spec", "js/spec/utils/handle_iframe_binding_spec", "js/spec/utils/module_spec", - "js/spec/views/baseview_spec", "js/spec/views/paging_spec", "js/spec/views/assets_spec", - "js/spec/views/group_configuration_spec", - + "js/spec/views/baseview_spec", "js/spec/views/container_spec", - "js/spec/views/unit_spec", + "js/spec/views/group_configuration_spec", + "js/spec/views/paging_spec", + "js/spec/views/unit_outline_spec", "js/spec/views/xblock_spec", "js/spec/views/xblock_editor_spec", + "js/spec/views/xblock_string_field_editor_spec", "js/spec/views/pages/container_spec", + "js/spec/views/pages/container_subviews_spec", "js/spec/views/pages/group_configurations_spec", + "js/spec/views/pages/course_outline_spec", "js/spec/views/modals/base_modal_spec", "js/spec/views/modals/edit_xblock_spec", diff --git a/cms/static/coffee/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee deleted file mode 100644 index 333e369bc2..0000000000 --- a/cms/static/coffee/spec/views/overview_spec.coffee +++ /dev/null @@ -1,99 +0,0 @@ -define ["js/views/overview", "js/views/feedback_notification", "js/spec_helpers/create_sinon", "js/base", "date", "jquery.timepicker"], -(Overview, Notification, create_sinon) -> - - describe "Course Overview", -> - beforeEach -> - appendSetFixtures """ -+ ${_("Content")} + > ${_("Course Outline")} +
+ + +${_("Loading...")}
+<%= title %>
diff --git a/cms/templates/js/container-message.underscore b/cms/templates/js/container-message.underscore new file mode 100644 index 0000000000..bf70262d38 --- /dev/null +++ b/cms/templates/js/container-message.underscore @@ -0,0 +1,8 @@ +<% if (currentlyVisibleToStudents) { %> + +<% } %> diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore new file mode 100644 index 0000000000..32a2ef6061 --- /dev/null +++ b/cms/templates/js/course-outline.underscore @@ -0,0 +1,167 @@ +<% +var category = xblockInfo.get('category'); +var releasedToStudents = xblockInfo.get('released_to_students'); +var visibilityState = xblockInfo.get('visibility_state'); +var published = xblockInfo.get('published'); + +var statusMessage = null; +var statusType = null; +if (visibilityState === 'staff_only') { + statusType = 'staff-only'; + statusMessage = gettext('Contains staff only content'); +} else if (visibilityState === 'needs_attention') { + if (category === 'vertical') { + statusType = 'warning'; + if (published && releasedToStudents) { + statusMessage = gettext('Unpublished changes to live content'); + } else if (!published) { + statusMessage = gettext('Unpublished units will not be released'); + } else { + statusMessage = gettext('Unpublished changes to content that will release in the future'); + } + } +} + +var statusIconClass = ''; +if (statusType === 'warning') { + statusIconClass = 'icon-file-alt'; +} else if (statusType === 'error') { + statusIconClass = 'icon-warning-sign'; +} else if (statusType === 'staff-only') { + statusIconClass = 'icon-lock'; +} + +var gradingType = gettext('Not Graded'); +if (xblockInfo.get('graded')) { + gradingType = xblockInfo.get('format') +} +%> +<% if (parentInfo) { %> +
+
+ <% } else { %>
+
+ <% } %>
+ <% if (xblockInfo.isVertical()) { %>
+
+ <%= xblockInfo.get('display_name') %>
+
+ <% } else { %>
+ ">
+ <%= xblockInfo.get('display_name') %>
+
+ <% } %>
+
+
+
+
+ <%= gettext('Release Status:') %> + + <% if (xblockInfo.get('released_to_students')) { %> + + <%= gettext('Released:') %> + <% } else if (xblockInfo.get('release_date')) { %> + + <%= gettext('Scheduled:') %> + <% } else { %> + + <%= gettext('Unscheduled') %> + <% } %> + <% if (xblockInfo.get('release_date')) { %> + <%= xblockInfo.get('release_date') %> + <% } %> + +
++ <%= gettext('Graded as:') %> + + <%= gradingType %> + <% if (xblockInfo.get('due_date')) { %> + <%= gettext('Due:') %> <%= xblockInfo.get('due_date') %> + <% } %> +
+<%= gettext("You haven't added any content to this course yet.") %> + + <%= addChildLabel %> + +
+-
+
- + + +
+
This page has no content yet.
-Loading...
${_("Unit Location")}
+Unit Location ID
++ ${unit.location.name} + Tip: ${_("Use this ID when you create links to this unit from other course content. You enter the ID in the URL field.")} +
+-
@@ -35,7 +35,7 @@
- +-
- +diff --git a/cms/templates/js/mock/mock-empty-container-xblock.underscore b/cms/templates/js/mock/mock-empty-container-xblock.underscore index 1ba44ccee7..83ac9e1754 100644 --- a/cms/templates/js/mock/mock-empty-container-xblock.underscore +++ b/cms/templates/js/mock/mock-empty-container-xblock.underscore @@ -13,7 +13,7 @@-
+ + ++ ++ ++ Content + > Course Outline +
+ + +++ ++ ++ + ++++ +++You haven't added any content to this course yet. + + Add Section + +
+++Loading...
+
- +diff --git a/cms/templates/js/mock/mock-unit-page-child-container.underscore b/cms/templates/js/mock/mock-unit-page-child-container.underscore deleted file mode 100644 index 43924f0683..0000000000 --- a/cms/templates/js/mock/mock-unit-page-child-container.underscore +++ /dev/null @@ -1,37 +0,0 @@ -- -- Test Child Container ---
-
- - - - - - -
- - - - Duplicate - - -
- - - - Delete - - -
- -- diff --git a/cms/templates/js/mock/mock-unit-page-xblock.underscore b/cms/templates/js/mock/mock-unit-page-xblock.underscore deleted file mode 100644 index e2ad78a0bf..0000000000 --- a/cms/templates/js/mock/mock-unit-page-xblock.underscore +++ /dev/null @@ -1,27 +0,0 @@ --
-
- - - - - - -
--Mock Component--
-
- - - - - - -
- - - - Duplicate this component - - -
- - - - Delete this component - - -
-diff --git a/cms/templates/js/mock/mock-updated-container-xblock.underscore b/cms/templates/js/mock/mock-updated-container-xblock.underscore index 141daa5526..40f241e210 100644 --- a/cms/templates/js/mock/mock-updated-container-xblock.underscore +++ b/cms/templates/js/mock/mock-updated-container-xblock.underscore @@ -10,7 +10,7 @@Mock Component
-- + data-init="MockXBlock" data-runtime-class="StudioRuntime" tabindex="0"> -
-