""" Acceptance tests for Studio related to the container page. The container page is used both for displaying units, and for displaying containers within units. """ from nose.plugins.attrib import attr from unittest import skip from ...fixtures.course import XBlockFixtureDesc from ...pages.studio.component_editor import ComponentEditorView, ComponentVisibilityEditorView from ...pages.studio.container import ContainerPage from ...pages.studio.html_component_editor import HtmlComponentEditorView from ...pages.studio.utils import add_discussion, drag from ...pages.lms.courseware import CoursewarePage from ...pages.lms.staff_view import StaffPage from ...tests.helpers import create_user_partition_json import datetime from bok_choy.promise import Promise, EmptyPromise from base_studio_test import ContainerBase from xmodule.partitions.partitions import Group class NestedVerticalTest(ContainerBase): def populate_course_fixture(self, course_fixture): """ Sets up a course structure with nested verticals. """ self.container_title = "" self.group_a = "Group A" self.group_b = "Group B" self.group_empty = "Group Empty" self.group_a_item_1 = "Group A Item 1" self.group_a_item_2 = "Group A Item 2" self.group_b_item_1 = "Group B Item 1" self.group_b_item_2 = "Group B Item 2" self.group_a_handle = 0 self.group_a_item_1_handle = 1 self.group_a_item_2_handle = 2 self.group_empty_handle = 3 self.group_b_handle = 4 self.group_b_item_1_handle = 5 self.group_b_item_2_handle = 6 self.group_a_item_1_action_index = 0 self.group_a_item_2_action_index = 1 self.duplicate_label = "Duplicate of '{0}'" self.discussion_label = "Discussion" course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('vertical', 'Test Container').add_children( XBlockFixtureDesc('vertical', 'Group A').add_children( XBlockFixtureDesc('html', self.group_a_item_1), XBlockFixtureDesc('html', self.group_a_item_2) ), XBlockFixtureDesc('vertical', 'Group Empty'), XBlockFixtureDesc('vertical', 'Group B').add_children( XBlockFixtureDesc('html', self.group_b_item_1), XBlockFixtureDesc('html', self.group_b_item_2) ) ) ) ) ) ) @skip("Flaky: 01/16/2015") @attr('shard_1') class DragAndDropTest(NestedVerticalTest): """ Tests of reordering within the container page. """ def drag_and_verify(self, source, target, expected_ordering): self.do_action_and_verify( lambda (container): drag(container, source, target, 40), expected_ordering ) def test_reorder_in_group(self): """ Drag Group A Item 2 before Group A Item 1. """ expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_2, self.group_a_item_1]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.drag_and_verify(self.group_a_item_2_handle, self.group_a_item_1_handle, expected_ordering) def test_drag_to_top(self): """ Drag Group A Item 1 to top level (outside of Group A). """ expected_ordering = [{self.container_title: [self.group_a_item_1, self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.drag_and_verify(self.group_a_item_1_handle, self.group_a_handle, expected_ordering) def test_drag_into_different_group(self): """ Drag Group B Item 1 into Group A (first element). """ expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_b_item_1, self.group_a_item_1, self.group_a_item_2]}, {self.group_b: [self.group_b_item_2]}, {self.group_empty: []}] self.drag_and_verify(self.group_b_item_1_handle, self.group_a_item_1_handle, expected_ordering) def test_drag_group_into_group(self): """ Drag Group B into Group A (first element). """ expected_ordering = [{self.container_title: [self.group_a, self.group_empty]}, {self.group_a: [self.group_b, self.group_a_item_1, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.drag_and_verify(self.group_b_handle, self.group_a_item_1_handle, expected_ordering) def test_drag_after_addition(self): """ Add some components and then verify that drag and drop still works. """ group_a_menu = 0 def add_new_components_and_rearrange(container): # Add a video component to Group 1 add_discussion(container, group_a_menu) # Duplicate the first item in Group A container.duplicate(self.group_a_item_1_action_index) first_handle = self.group_a_item_1_handle # Drag newly added video component to top. drag(container, first_handle + 3, first_handle, 40) # Drag duplicated component to top. drag(container, first_handle + 2, first_handle, 40) duplicate_label = self.duplicate_label.format(self.group_a_item_1) expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [duplicate_label, self.discussion_label, self.group_a_item_1, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.do_action_and_verify(add_new_components_and_rearrange, expected_ordering) @attr('shard_1') class AddComponentTest(NestedVerticalTest): """ Tests of adding a component to the container page. """ def add_and_verify(self, menu_index, expected_ordering): self.do_action_and_verify( lambda (container): add_discussion(container, menu_index), expected_ordering ) def test_add_component_in_group(self): group_b_menu = 2 expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_1, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2, self.discussion_label]}, {self.group_empty: []}] self.add_and_verify(group_b_menu, expected_ordering) def test_add_component_in_empty_group(self): group_empty_menu = 1 expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_1, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: [self.discussion_label]}] self.add_and_verify(group_empty_menu, expected_ordering) def test_add_component_in_container(self): container_menu = 3 expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b, self.discussion_label]}, {self.group_a: [self.group_a_item_1, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.add_and_verify(container_menu, expected_ordering) @attr('shard_1') class DuplicateComponentTest(NestedVerticalTest): """ Tests of duplicating a component on the container page. """ def duplicate_and_verify(self, source_index, expected_ordering): self.do_action_and_verify( lambda (container): container.duplicate(source_index), expected_ordering ) def test_duplicate_first_in_group(self): duplicate_label = self.duplicate_label.format(self.group_a_item_1) expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_1, duplicate_label, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.duplicate_and_verify(self.group_a_item_1_action_index, expected_ordering) def test_duplicate_second_in_group(self): duplicate_label = self.duplicate_label.format(self.group_a_item_2) expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_1, self.group_a_item_2, duplicate_label]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] self.duplicate_and_verify(self.group_a_item_2_action_index, expected_ordering) def test_duplicate_the_duplicate(self): first_duplicate_label = self.duplicate_label.format(self.group_a_item_1) second_duplicate_label = self.duplicate_label.format(first_duplicate_label) expected_ordering = [ {self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_1, first_duplicate_label, second_duplicate_label, self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []} ] def duplicate_twice(container): container.duplicate(self.group_a_item_1_action_index) container.duplicate(self.group_a_item_1_action_index + 1) self.do_action_and_verify(duplicate_twice, expected_ordering) @attr('shard_1') class DeleteComponentTest(NestedVerticalTest): """ Tests of deleting a component from the container page. """ def delete_and_verify(self, source_index, expected_ordering): self.do_action_and_verify( lambda (container): container.delete(source_index), expected_ordering ) def test_delete_first_in_group(self): expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]}, {self.group_a: [self.group_a_item_2]}, {self.group_b: [self.group_b_item_1, self.group_b_item_2]}, {self.group_empty: []}] # Group A itself has a delete icon now, so item_1 is index 1 instead of 0. group_a_item_1_delete_index = 1 self.delete_and_verify(group_a_item_1_delete_index, expected_ordering) @attr('shard_1') class EditContainerTest(NestedVerticalTest): """ Tests of editing a container. """ def modify_display_name_and_verify(self, component): """ Helper method for changing a display name. """ modified_name = 'modified' self.assertNotEqual(component.name, modified_name) component.edit() component_editor = ComponentEditorView(self.browser, component.locator) component_editor.set_field_value_and_save('Display Name', modified_name) self.assertEqual(component.name, modified_name) def test_edit_container_on_unit_page(self): """ Test the "edit" button on a container appearing on the unit page. """ unit = self.go_to_unit_page() component = unit.xblocks[1] self.modify_display_name_and_verify(component) def test_edit_container_on_container_page(self): """ Test the "edit" button on a container appearing on the container page. """ container = self.go_to_nested_container_page() self.modify_display_name_and_verify(container) def test_edit_raw_html(self): """ Test the raw html editing functionality. """ modified_content = "
modified content
" #navigate to and open the component for editing unit = self.go_to_unit_page() container = unit.xblocks[1].go_to_container() component = container.xblocks[1].children[0] component.edit() html_editor = HtmlComponentEditorView(self.browser, component.locator) html_editor.set_content_and_save(modified_content, raw=True) #note we're expecting thetags to have been removed self.assertEqual(component.student_content, "modified content") @attr('shard_3') class EditVisibilityModalTest(ContainerBase): """ Tests of the visibility settings modal for components on the unit page. """ VISIBILITY_LABEL_ALL = 'All Students and Staff' VISIBILITY_LABEL_SPECIFIC = 'Specific Content Groups' MISSING_GROUP_LABEL = 'Deleted Content Group\nContent group no longer exists. Please choose another or allow access to All Students and staff' VALIDATION_ERROR_LABEL = 'This component has validation issues.' VALIDATION_ERROR_MESSAGE = 'Error:\nThis component refers to deleted or invalid content groups.' GROUP_VISIBILITY_MESSAGE = 'Some content in this unit is visible only to particular content groups' def setUp(self): super(EditVisibilityModalTest, self).setUp() # Set up a cohort-schemed user partition self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Configuration Dogs, Cats', 'Content Group Partition', [Group("0", 'Dogs'), Group("1", 'Cats')], scheme="cohort" ) ], }, }) self.container_page = self.go_to_unit_page() self.html_component = self.container_page.xblocks[1] def populate_course_fixture(self, course_fixture): """ Populate a simple course a section, subsection, and unit, and HTML component. """ course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('html', 'Html Component') ) ) ) ) def edit_component_visibility(self, component): """ Edit the visibility of an xblock on the container page. """ component.edit_visibility() return ComponentVisibilityEditorView(self.browser, component.locator) def verify_selected_labels(self, visibility_editor, expected_labels): """ Verify that a visibility editor's selected labels match the expected ones. """ # If anything other than 'All Students and Staff', is selected, # 'Specific Content Groups' should be selected as well. if expected_labels != [self.VISIBILITY_LABEL_ALL]: expected_labels.append(self.VISIBILITY_LABEL_SPECIFIC) self.assertItemsEqual(expected_labels, [option.text for option in visibility_editor.selected_options]) def select_and_verify_saved(self, component, labels, expected_labels=None): """ Edit the visibility of an xblock on the container page and verify that the edit persists. If provided, verify that `expected_labels` are selected after save, otherwise expect that `labels` are selected after save. Note that `labels` are labels which should be clicked, but not necessarily checked. """ if expected_labels is None: expected_labels = labels # Make initial edit(s) and save visibility_editor = self.edit_component_visibility(component) for label in labels: visibility_editor.select_option(label, save=False) visibility_editor.save() # Re-open the modal and inspect its selected inputs visibility_editor = self.edit_component_visibility(component) self.verify_selected_labels(visibility_editor, expected_labels) visibility_editor.save() def verify_component_validation_error(self, component): """ Verify that we see validation errors for the given component. """ self.assertTrue(component.has_validation_error) self.assertEqual(component.validation_error_text, self.VALIDATION_ERROR_LABEL) self.assertEqual([self.VALIDATION_ERROR_MESSAGE], component.validation_error_messages) def verify_visibility_set(self, component, is_set): """ Verify that the container page shows that component visibility settings have been edited if `is_set` is True; otherwise verify that the container page shows no such information. """ if is_set: self.assertIn(self.GROUP_VISIBILITY_MESSAGE, self.container_page.sidebar_visibility_message) self.assertTrue(component.has_group_visibility_set) else: self.assertNotIn(self.GROUP_VISIBILITY_MESSAGE, self.container_page.sidebar_visibility_message) self.assertFalse(component.has_group_visibility_set) def update_component(self, component, metadata): """ Update a component's metadata and refresh the page. """ self.course_fixture._update_xblock(component.locator, {'metadata': metadata}) self.browser.refresh() self.container_page.wait_for_page() def remove_missing_groups(self, visibility_editor, component): """ Deselect the missing groups for a component. After save, verify that there are no missing group messages in the modal and that there is no validation error on the component. """ for option in visibility_editor.selected_options: if option.text == self.MISSING_GROUP_LABEL: option.click() visibility_editor.save() visibility_editor = self.edit_component_visibility(component) self.assertNotIn(self.MISSING_GROUP_LABEL, [item.text for item in visibility_editor.all_options]) visibility_editor.cancel() self.assertFalse(component.has_validation_error) def test_default_selection(self): """ Scenario: The component visibility modal selects visible to all by default. Given I have a unit with one component When I go to the container page for that unit And I open the visibility editor modal for that unit's component Then the default visibility selection should be 'All Students and Staff' And the container page should not display the content visibility warning """ self.verify_selected_labels(self.edit_component_visibility(self.html_component), [self.VISIBILITY_LABEL_ALL]) self.verify_visibility_set(self.html_component, False) def test_reset_to_all_students_and_staff(self): """ Scenario: The component visibility modal can be set to be visible to all students and staff. Given I have a unit with one component When I go to the container page for that unit And I open the visibility editor modal for that unit's component And I select 'Dogs' And I save the modal Then the container page should display the content visibility warning And I re-open the visibility editor modal for that unit's component And I select 'All Students and Staff' And I save the modal Then the visibility selection should be 'All Students and Staff' And the container page should not display the content visibility warning """ self.select_and_verify_saved(self.html_component, ['Dogs']) self.verify_visibility_set(self.html_component, True) self.select_and_verify_saved(self.html_component, [self.VISIBILITY_LABEL_ALL]) self.verify_visibility_set(self.html_component, False) def test_select_single_content_group(self): """ Scenario: The component visibility modal can be set to be visible to one content group. Given I have a unit with one component When I go to the container page for that unit And I open the visibility editor modal for that unit's component And I select 'Dogs' And I save the modal Then the visibility selection should be 'Dogs' and 'Specific Content Groups' And the container page should display the content visibility warning """ self.select_and_verify_saved(self.html_component, ['Dogs']) self.verify_visibility_set(self.html_component, True) def test_select_multiple_content_groups(self): """ Scenario: The component visibility modal can be set to be visible to multiple content groups. Given I have a unit with one component When I go to the container page for that unit And I open the visibility editor modal for that unit's component And I select 'Dogs' and 'Cats' And I save the modal Then the visibility selection should be 'Dogs', 'Cats', and 'Specific Content Groups' And the container page should display the content visibility warning """ self.select_and_verify_saved(self.html_component, ['Dogs', 'Cats']) self.verify_visibility_set(self.html_component, True) def test_select_zero_content_groups(self): """ Scenario: The component visibility modal can not be set to be visible to 'Specific Content Groups' without selecting those specific groups. Given I have a unit with one component When I go to the container page for that unit And I open the visibility editor modal for that unit's component And I select 'Specific Content Groups' And I save the modal Then the visibility selection should be 'All Students and Staff' And the container page should not display the content visibility warning """ self.select_and_verify_saved( self.html_component, [self.VISIBILITY_LABEL_SPECIFIC], expected_labels=[self.VISIBILITY_LABEL_ALL] ) self.verify_visibility_set(self.html_component, False) def test_missing_groups(self): """ Scenario: The component visibility modal shows a validation error when visibility is set to multiple unknown group ids. Given I have a unit with one component And that component's group access specifies multiple invalid group ids When I go to the container page for that unit Then I should see a validation error message on that unit's component And I open the visibility editor modal for that unit's component Then I should see that I have selected multiple deleted groups And the container page should display the content visibility warning And I de-select the missing groups And I save the modal Then the visibility selection should be 'All Students and Staff' And I should not see any validation errors on the component And the container page should not display the content visibility warning """ self.update_component(self.html_component, {'group_access': {0: [2, 3]}}) self.verify_component_validation_error(self.html_component) visibility_editor = self.edit_component_visibility(self.html_component) self.verify_selected_labels(visibility_editor, [self.MISSING_GROUP_LABEL] * 2) self.remove_missing_groups(visibility_editor, self.html_component) self.verify_visibility_set(self.html_component, False) def test_found_and_missing_groups(self): """ Scenario: The component visibility modal shows a validation error when visibility is set to multiple unknown group ids and multiple known group ids. Given I have a unit with one component And that component's group access specifies multiple invalid and valid group ids When I go to the container page for that unit Then I should see a validation error message on that unit's component And I open the visibility editor modal for that unit's component Then I should see that I have selected multiple deleted groups And the container page should display the content visibility warning And I de-select the missing groups And I save the modal Then the visibility selection should be the names of the valid groups. And I should not see any validation errors on the component And the container page should display the content visibility warning """ self.update_component(self.html_component, {'group_access': {0: [0, 1, 2, 3]}}) self.verify_component_validation_error(self.html_component) visibility_editor = self.edit_component_visibility(self.html_component) self.verify_selected_labels(visibility_editor, ['Dogs', 'Cats'] + [self.MISSING_GROUP_LABEL] * 2) self.remove_missing_groups(visibility_editor, self.html_component) visibility_editor = self.edit_component_visibility(self.html_component) self.verify_selected_labels(visibility_editor, ['Dogs', 'Cats']) self.verify_visibility_set(self.html_component, True) @attr('shard_1') class UnitPublishingTest(ContainerBase): """ Tests of the publishing control and related widgets on the Unit page. """ PUBLISHED_STATUS = "Publishing Status\nPublished (not yet released)" PUBLISHED_LIVE_STATUS = "Publishing Status\nPublished and Live" DRAFT_STATUS = "Publishing Status\nDraft (Unpublished changes)" LOCKED_STATUS = "Publishing Status\nVisible to Staff Only" RELEASE_TITLE_RELEASED = "RELEASED:" RELEASE_TITLE_RELEASE = "RELEASE:" LAST_PUBLISHED = 'Last published' LAST_SAVED = 'Draft saved on' def populate_course_fixture(self, course_fixture): """ Sets up a course structure with a unit and a single HTML child. """ self.html_content = '
Body of HTML Unit.
' 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( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('html', 'Test html', data=self.html_content) ) ) ), XBlockFixtureDesc( 'chapter', 'Unlocked Section', metadata={'start': past_start_date.isoformat()} ).add_children( XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children( XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children( XBlockFixtureDesc('problem', '