Improves navigation within Studio for Learning Sequences, speeding up authors who want to see how a learner progresses through content without needing to jump over to the LMS. This adds a dropdown section navigator to the breadcrumbs on the unit page and copies the sequence navigator from LMS to the studio unit page.
1984 lines
85 KiB
Python
1984 lines
85 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Acceptance tests for studio related to the outline page.
|
|
"""
|
|
from __future__ import absolute_import
|
|
|
|
import itertools
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from unittest import skip
|
|
|
|
from pytz import UTC
|
|
import six
|
|
from six.moves import range
|
|
|
|
from common.test.acceptance.fixtures.config import ConfigModelFixture
|
|
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
|
|
from common.test.acceptance.pages.lms.course_home import CourseHomePage
|
|
from common.test.acceptance.pages.lms.courseware import CoursewarePage
|
|
from common.test.acceptance.pages.lms.progress import ProgressPage
|
|
from common.test.acceptance.pages.studio.checklists import CourseChecklistsPage
|
|
from common.test.acceptance.pages.studio.overview import ContainerPage, CourseOutlinePage, ExpandCollapseLinkState
|
|
from common.test.acceptance.pages.studio.settings import SettingsPage
|
|
from common.test.acceptance.pages.studio.settings_advanced import AdvancedSettingsPage
|
|
from common.test.acceptance.pages.studio.settings_group_configurations import GroupConfigurationsPage
|
|
from common.test.acceptance.pages.studio.utils import add_discussion, drag, verify_ordering
|
|
from common.test.acceptance.tests.helpers import disable_animations, load_data_str
|
|
from openedx.core.lib.tests import attr
|
|
|
|
from .base_studio_test import StudioCourseTest
|
|
|
|
SECTION_NAME = 'Test Section'
|
|
SUBSECTION_NAME = 'Test Subsection'
|
|
UNIT_NAME = 'Test Unit'
|
|
|
|
|
|
class CourseOutlineTest(StudioCourseTest):
|
|
"""
|
|
Base class for all course outline tests
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Install a course with no content using a fixture.
|
|
"""
|
|
super(CourseOutlineTest, self).setUp()
|
|
self.course_outline_page = CourseOutlinePage(
|
|
self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']
|
|
)
|
|
self.advanced_settings = AdvancedSettingsPage(
|
|
self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']
|
|
)
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Install a course with sections/problems, tabs, updates, and handouts """
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', SECTION_NAME).add_children(
|
|
XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children(
|
|
XBlockFixtureDesc('vertical', UNIT_NAME).add_children(
|
|
XBlockFixtureDesc('problem', 'Test Problem 1', data=load_data_str('multiple_choice.xml')),
|
|
XBlockFixtureDesc('html', 'Test HTML Component'),
|
|
XBlockFixtureDesc('discussion', 'Test Discussion Component')
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
def do_action_and_verify(self, outline_page, action, expected_ordering):
|
|
"""
|
|
Perform the supplied action and then verify the resulting ordering.
|
|
"""
|
|
if outline_page is None:
|
|
outline_page = self.course_outline_page.visit()
|
|
|
|
action(outline_page)
|
|
verify_ordering(self, outline_page, expected_ordering)
|
|
|
|
# Reload the page and expand all subsections to see that the change was persisted.
|
|
outline_page = self.course_outline_page.visit()
|
|
outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').click()
|
|
verify_ordering(self, outline_page, expected_ordering)
|
|
|
|
|
|
@attr(shard=3)
|
|
class CourseOutlineDragAndDropTest(CourseOutlineTest):
|
|
"""
|
|
Tests of drag and drop within the outline page.
|
|
"""
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
"""
|
|
Create a course with one section, two subsections, and four units
|
|
"""
|
|
# with collapsed outline
|
|
self.chap_1_handle = 0
|
|
self.chap_1_seq_1_handle = 1
|
|
|
|
# with first sequential expanded
|
|
self.seq_1_vert_1_handle = 2
|
|
self.seq_1_vert_2_handle = 3
|
|
self.chap_1_seq_2_handle = 4
|
|
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', "1").add_children(
|
|
XBlockFixtureDesc('sequential', '1.1').add_children(
|
|
XBlockFixtureDesc('vertical', '1.1.1'),
|
|
XBlockFixtureDesc('vertical', '1.1.2')
|
|
),
|
|
XBlockFixtureDesc('sequential', '1.2').add_children(
|
|
XBlockFixtureDesc('vertical', '1.2.1'),
|
|
XBlockFixtureDesc('vertical', '1.2.2')
|
|
)
|
|
)
|
|
)
|
|
|
|
def drag_and_verify(self, source, target, expected_ordering, outline_page=None):
|
|
self.do_action_and_verify(
|
|
outline_page,
|
|
lambda outline: drag(outline, source, target),
|
|
expected_ordering
|
|
)
|
|
|
|
@skip("Fails in Firefox 45 but passes in Chrome")
|
|
def test_drop_unit_in_collapsed_subsection(self):
|
|
"""
|
|
Drag vertical "1.1.2" from subsection "1.1" into collapsed subsection "1.2" which already
|
|
have its own verticals.
|
|
"""
|
|
course_outline_page = self.course_outline_page.visit()
|
|
# expand first subsection
|
|
course_outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').first.click()
|
|
|
|
expected_ordering = [{"1": ["1.1", "1.2"]},
|
|
{"1.1": ["1.1.1"]},
|
|
{"1.2": ["1.1.2", "1.2.1", "1.2.2"]}]
|
|
self.drag_and_verify(self.seq_1_vert_2_handle, self.chap_1_seq_2_handle, expected_ordering, course_outline_page)
|
|
|
|
|
|
@attr(shard=3)
|
|
class WarningMessagesTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Warning messages on sections, subsections, and units
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
STAFF_ONLY_WARNING = 'Contains staff only content'
|
|
LIVE_UNPUBLISHED_WARNING = 'Unpublished changes to live content'
|
|
FUTURE_UNPUBLISHED_WARNING = 'Unpublished changes to content that will release in the future'
|
|
NEVER_PUBLISHED_WARNING = 'Unpublished units will not be released'
|
|
|
|
class PublishState(object):
|
|
"""
|
|
Default values for representing the published state of a unit
|
|
"""
|
|
NEVER_PUBLISHED = 1
|
|
UNPUBLISHED_CHANGES = 2
|
|
PUBLISHED = 3
|
|
VALUES = [NEVER_PUBLISHED, UNPUBLISHED_CHANGES, PUBLISHED]
|
|
|
|
class UnitState(object):
|
|
""" Represents the state of a unit """
|
|
|
|
def __init__(self, is_released, publish_state, is_locked):
|
|
""" Creates a new UnitState with the given properties """
|
|
self.is_released = is_released
|
|
self.publish_state = publish_state
|
|
self.is_locked = is_locked
|
|
|
|
@property
|
|
def name(self):
|
|
""" Returns an appropriate name based on the properties of the unit """
|
|
result = "Released " if self.is_released else "Unreleased "
|
|
if self.publish_state == WarningMessagesTest.PublishState.NEVER_PUBLISHED:
|
|
result += "Never Published "
|
|
elif self.publish_state == WarningMessagesTest.PublishState.UNPUBLISHED_CHANGES:
|
|
result += "Unpublished Changes "
|
|
else:
|
|
result += "Published "
|
|
result += "Locked" if self.is_locked else "Unlocked"
|
|
return result
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Install a course with various configurations that could produce warning messages """
|
|
|
|
# Define the dimensions that map to the UnitState constructor
|
|
features = [
|
|
[True, False], # Possible values for is_released
|
|
self.PublishState.VALUES, # Possible values for publish_state
|
|
[True, False] # Possible values for is_locked
|
|
]
|
|
|
|
# Add a fixture for every state in the product of features
|
|
course_fixture.add_children(*[
|
|
self._build_fixture(self.UnitState(*state)) for state in itertools.product(*features)
|
|
])
|
|
|
|
def _build_fixture(self, unit_state):
|
|
""" Returns an XBlockFixtureDesc with a section, subsection, and possibly unit that has the given state. """
|
|
name = unit_state.name
|
|
start = (datetime(1984, 3, 4) if unit_state.is_released else datetime.now(UTC) + timedelta(1)).isoformat()
|
|
|
|
subsection = XBlockFixtureDesc('sequential', name, metadata={'start': start})
|
|
|
|
# Children of never published subsections will be added on demand via _ensure_unit_present
|
|
return XBlockFixtureDesc('chapter', name).add_children(
|
|
subsection if unit_state.publish_state == self.PublishState.NEVER_PUBLISHED
|
|
else subsection.add_children(
|
|
XBlockFixtureDesc('vertical', name, metadata={
|
|
'visible_to_staff_only': True if unit_state.is_locked else None
|
|
})
|
|
)
|
|
)
|
|
|
|
def test_released_never_published_locked(self):
|
|
""" Tests that released never published locked units display staff only warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=True, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=True),
|
|
self.STAFF_ONLY_WARNING
|
|
)
|
|
|
|
def test_released_never_published_unlocked(self):
|
|
""" Tests that released never published unlocked units display 'Unpublished units will not be released' """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=True, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=False),
|
|
self.NEVER_PUBLISHED_WARNING
|
|
)
|
|
|
|
def test_released_unpublished_changes_locked(self):
|
|
""" Tests that released unpublished changes locked units display staff only warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=True, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=True),
|
|
self.STAFF_ONLY_WARNING
|
|
)
|
|
|
|
def test_released_unpublished_changes_unlocked(self):
|
|
""" Tests that released unpublished changes unlocked units display 'Unpublished changes to live content' """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=True, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=False),
|
|
self.LIVE_UNPUBLISHED_WARNING
|
|
)
|
|
|
|
def test_released_published_locked(self):
|
|
""" Tests that released published locked units display staff only warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=True, publish_state=self.PublishState.PUBLISHED, is_locked=True),
|
|
self.STAFF_ONLY_WARNING
|
|
)
|
|
|
|
def test_released_published_unlocked(self):
|
|
""" Tests that released published unlocked units display no warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=True, publish_state=self.PublishState.PUBLISHED, is_locked=False),
|
|
None
|
|
)
|
|
|
|
def test_unreleased_never_published_locked(self):
|
|
""" Tests that unreleased never published locked units display staff only warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=False, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=True),
|
|
self.STAFF_ONLY_WARNING
|
|
)
|
|
|
|
def test_unreleased_never_published_unlocked(self):
|
|
""" Tests that unreleased never published unlocked units display 'Unpublished units will not be released' """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=False, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=False),
|
|
self.NEVER_PUBLISHED_WARNING
|
|
)
|
|
|
|
def test_unreleased_unpublished_changes_locked(self):
|
|
""" Tests that unreleased unpublished changes locked units display staff only warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=False, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=True),
|
|
self.STAFF_ONLY_WARNING
|
|
)
|
|
|
|
def test_unreleased_unpublished_changes_unlocked(self):
|
|
"""
|
|
Tests that unreleased unpublished changes unlocked units display 'Unpublished changes to content that will
|
|
release in the future'
|
|
"""
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=False, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=False),
|
|
self.FUTURE_UNPUBLISHED_WARNING
|
|
)
|
|
|
|
def test_unreleased_published_locked(self):
|
|
""" Tests that unreleased published locked units display staff only warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=False, publish_state=self.PublishState.PUBLISHED, is_locked=True),
|
|
self.STAFF_ONLY_WARNING
|
|
)
|
|
|
|
def test_unreleased_published_unlocked(self):
|
|
""" Tests that unreleased published unlocked units display no warnings """
|
|
self._verify_unit_warning(
|
|
self.UnitState(is_released=False, publish_state=self.PublishState.PUBLISHED, is_locked=False),
|
|
None
|
|
)
|
|
|
|
def _verify_unit_warning(self, unit_state, expected_status_message):
|
|
"""
|
|
Verifies that the given unit's messages match the expected messages.
|
|
If expected_status_message is None, then the unit status message is expected to not be present.
|
|
"""
|
|
self._ensure_unit_present(unit_state)
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section(unit_state.name)
|
|
subsection = section.subsection_at(0)
|
|
subsection.expand_subsection()
|
|
unit = subsection.unit_at(0)
|
|
if expected_status_message == self.STAFF_ONLY_WARNING:
|
|
self.assertEqual(section.status_message, self.STAFF_ONLY_WARNING)
|
|
self.assertEqual(subsection.status_message, self.STAFF_ONLY_WARNING)
|
|
self.assertEqual(unit.status_message, self.STAFF_ONLY_WARNING)
|
|
else:
|
|
self.assertFalse(section.has_status_message)
|
|
self.assertFalse(subsection.has_status_message)
|
|
if expected_status_message:
|
|
self.assertEqual(unit.status_message, expected_status_message)
|
|
else:
|
|
self.assertFalse(unit.has_status_message)
|
|
|
|
def _ensure_unit_present(self, unit_state):
|
|
""" Ensures that a unit with the given state is present on the course outline """
|
|
if unit_state.publish_state == self.PublishState.PUBLISHED:
|
|
return
|
|
|
|
name = unit_state.name
|
|
self.course_outline_page.visit()
|
|
subsection = self.course_outline_page.section(name).subsection(name)
|
|
subsection.expand_subsection()
|
|
|
|
if unit_state.publish_state == self.PublishState.UNPUBLISHED_CHANGES:
|
|
unit = subsection.unit(name).go_to()
|
|
add_discussion(unit)
|
|
elif unit_state.publish_state == self.PublishState.NEVER_PUBLISHED:
|
|
subsection.add_unit()
|
|
unit = ContainerPage(self.browser, None)
|
|
unit.wait_for_page()
|
|
unit.set_name(name)
|
|
|
|
if unit.is_staff_locked != unit_state.is_locked:
|
|
unit.toggle_staff_lock()
|
|
|
|
|
|
@attr(shard=3)
|
|
class EditingSectionsTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Editing Release date, Due date and grading type.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def test_can_edit_subsection(self):
|
|
"""
|
|
Scenario: I can edit settings of subsection.
|
|
|
|
Given that I have created a subsection
|
|
Then I see release date, due date and grading policy of subsection in course outline
|
|
When I click on the configuration icon
|
|
Then edit modal window is shown
|
|
And release date, due date and grading policy fields present
|
|
And they have correct initial values
|
|
Then I set new values for these fields
|
|
And I click save button on the modal
|
|
Then I see release date, due date and grading policy of subsection in course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
subsection = self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME)
|
|
|
|
# Verify that Release date visible by default
|
|
self.assertTrue(subsection.release_date)
|
|
# Verify that Due date and Policy hidden by default
|
|
self.assertFalse(subsection.due_date)
|
|
self.assertFalse(subsection.policy)
|
|
|
|
modal = subsection.edit()
|
|
|
|
# Verify fields
|
|
self.assertTrue(modal.has_release_date())
|
|
self.assertTrue(modal.has_release_time())
|
|
self.assertTrue(modal.has_due_date())
|
|
self.assertTrue(modal.has_due_time())
|
|
self.assertTrue(modal.has_policy())
|
|
|
|
# Verify initial values
|
|
self.assertEqual(modal.release_date, u'1/1/1970')
|
|
self.assertEqual(modal.release_time, u'00:00')
|
|
self.assertEqual(modal.due_date, u'')
|
|
self.assertEqual(modal.due_time, u'')
|
|
self.assertEqual(modal.policy, u'Not Graded')
|
|
|
|
# Set new values
|
|
modal.release_date = '3/12/1972'
|
|
modal.release_time = '04:01'
|
|
modal.due_date = '7/21/2014'
|
|
modal.due_time = '23:39'
|
|
modal.policy = 'Lab'
|
|
|
|
modal.save()
|
|
self.assertIn(u'Released: Mar 12, 1972', subsection.release_date)
|
|
self.assertIn(u'04:01', subsection.release_date)
|
|
self.assertIn(u'Due: Jul 21, 2014', subsection.due_date)
|
|
self.assertIn(u'23:39', subsection.due_date)
|
|
self.assertIn(u'Lab', subsection.policy)
|
|
|
|
def test_can_edit_section(self):
|
|
"""
|
|
Scenario: I can edit settings of section.
|
|
|
|
Given that I have created a section
|
|
Then I see release date of section in course outline
|
|
When I click on the configuration icon
|
|
Then edit modal window is shown
|
|
And release date field present
|
|
And it has correct initial value
|
|
Then I set new value for this field
|
|
And I click save button on the modal
|
|
Then I see release date of section in course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section(SECTION_NAME)
|
|
|
|
# Verify that Release date visible by default
|
|
self.assertTrue(section.release_date)
|
|
# Verify that Due date and Policy are not present
|
|
self.assertFalse(section.due_date)
|
|
self.assertFalse(section.policy)
|
|
|
|
modal = section.edit()
|
|
# Verify fields
|
|
self.assertTrue(modal.has_release_date())
|
|
self.assertFalse(modal.has_due_date())
|
|
self.assertFalse(modal.has_policy())
|
|
|
|
# Verify initial value
|
|
self.assertEqual(modal.release_date, u'1/1/1970')
|
|
|
|
# Set new value
|
|
modal.release_date = '5/14/1969'
|
|
|
|
modal.save()
|
|
self.assertIn(u'Released: May 14, 1969', section.release_date)
|
|
# Verify that Due date and Policy are not present
|
|
self.assertFalse(section.due_date)
|
|
self.assertFalse(section.policy)
|
|
|
|
def test_subsection_is_graded_in_lms(self):
|
|
"""
|
|
Scenario: I can grade subsection from course outline page.
|
|
|
|
Given I visit progress page
|
|
And I see that problem in subsection has grading type "Practice"
|
|
Then I visit course outline page
|
|
And I click on the configuration icon of subsection
|
|
And I set grading policy to "Lab"
|
|
And I click save button on the modal
|
|
Then I visit progress page
|
|
And I see that problem in subsection has grading type "Problem"
|
|
"""
|
|
progress_page = ProgressPage(self.browser, self.course_id)
|
|
progress_page.visit()
|
|
progress_page.wait_for_page()
|
|
self.assertEqual(u'Practice', progress_page.grading_formats[0])
|
|
self.course_outline_page.visit()
|
|
|
|
subsection = self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME)
|
|
modal = subsection.edit()
|
|
# Set new values
|
|
modal.policy = 'Lab'
|
|
modal.save()
|
|
|
|
progress_page.visit()
|
|
|
|
self.assertEqual(u'Problem', progress_page.grading_formats[0])
|
|
|
|
def test_unchanged_release_date_is_not_saved(self):
|
|
"""
|
|
Scenario: Saving a subsection without changing the release date will not override the release date
|
|
Given that I have created a section with a subsection
|
|
When I open the settings modal for the subsection
|
|
And I pressed save
|
|
And I open the settings modal for the section
|
|
And I change the release date to 07/20/1969
|
|
And I press save
|
|
Then the subsection and the section have the release date 07/20/1969
|
|
"""
|
|
self.course_outline_page.visit()
|
|
|
|
modal = self.course_outline_page.section_at(0).subsection_at(0).edit()
|
|
modal.save()
|
|
|
|
modal = self.course_outline_page.section_at(0).edit()
|
|
modal.release_date = '7/20/1969'
|
|
modal.save()
|
|
|
|
release_text = 'Released: Jul 20, 1969'
|
|
self.assertIn(release_text, self.course_outline_page.section_at(0).release_date)
|
|
self.assertIn(release_text, self.course_outline_page.section_at(0).subsection_at(0).release_date)
|
|
|
|
|
|
@attr(shard=3)
|
|
class UnitAccessTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Units can be restricted and unrestricted to certain groups from the course outline.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def setUp(self):
|
|
super(UnitAccessTest, self).setUp()
|
|
self.group_configurations_page = GroupConfigurationsPage(
|
|
self.browser,
|
|
self.course_info['org'],
|
|
self.course_info['number'],
|
|
self.course_info['run']
|
|
)
|
|
self.content_group_a = "Test Group A"
|
|
self.content_group_b = "Test Group B"
|
|
|
|
self.group_configurations_page.visit()
|
|
self.group_configurations_page.create_first_content_group()
|
|
config_a = self.group_configurations_page.content_groups[0]
|
|
config_a.name = self.content_group_a
|
|
config_a.save()
|
|
self.content_group_a_id = config_a.id
|
|
|
|
self.group_configurations_page.add_content_group()
|
|
config_b = self.group_configurations_page.content_groups[1]
|
|
config_b.name = self.content_group_b
|
|
config_b.save()
|
|
self.content_group_b_id = config_b.id
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
"""
|
|
Create a course with one section, one subsection, and two units
|
|
"""
|
|
# with collapsed outline
|
|
self.chap_1_handle = 0
|
|
self.chap_1_seq_1_handle = 1
|
|
|
|
# with first sequential expanded
|
|
self.seq_1_vert_1_handle = 2
|
|
self.seq_1_vert_2_handle = 3
|
|
self.chap_1_seq_2_handle = 4
|
|
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', "1").add_children(
|
|
XBlockFixtureDesc('sequential', '1.1').add_children(
|
|
XBlockFixtureDesc('vertical', '1.1.1'),
|
|
XBlockFixtureDesc('vertical', '1.1.2')
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
@attr(shard=14)
|
|
class StaffLockTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Sections, subsections, and units can be locked and unlocked from the course outline.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Create a course with one section, two subsections, and four units """
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', '1').add_children(
|
|
XBlockFixtureDesc('sequential', '1.1').add_children(
|
|
XBlockFixtureDesc('vertical', '1.1.1'),
|
|
XBlockFixtureDesc('vertical', '1.1.2')
|
|
),
|
|
XBlockFixtureDesc('sequential', '1.2').add_children(
|
|
XBlockFixtureDesc('vertical', '1.2.1'),
|
|
XBlockFixtureDesc('vertical', '1.2.2')
|
|
)
|
|
)
|
|
)
|
|
|
|
def _verify_descendants_are_staff_only(self, item):
|
|
"""Verifies that all the descendants of item are staff only"""
|
|
self.assertTrue(item.is_staff_only)
|
|
if hasattr(item, 'children'):
|
|
for child in item.children():
|
|
self._verify_descendants_are_staff_only(child)
|
|
|
|
def _remove_staff_lock_and_verify_warning(self, outline_item, expect_warning):
|
|
"""Removes staff lock from a course outline item and checks whether or not a warning appears."""
|
|
modal = outline_item.edit()
|
|
modal.is_explicitly_locked = False
|
|
if expect_warning:
|
|
self.assertTrue(modal.shows_staff_lock_warning())
|
|
else:
|
|
self.assertFalse(modal.shows_staff_lock_warning())
|
|
modal.save()
|
|
|
|
def _toggle_lock_on_unlocked_item(self, outline_item):
|
|
"""Toggles outline_item's staff lock on and then off, verifying the staff lock warning"""
|
|
self.assertFalse(outline_item.has_staff_lock_warning)
|
|
outline_item.set_staff_lock(True)
|
|
self.assertTrue(outline_item.has_staff_lock_warning)
|
|
self._verify_descendants_are_staff_only(outline_item)
|
|
outline_item.set_staff_lock(False)
|
|
self.assertFalse(outline_item.has_staff_lock_warning)
|
|
|
|
def _verify_explicit_staff_lock_remains_after_unlocking_parent(self, child_item, parent_item):
|
|
"""Verifies that child_item's explicit staff lock remains after removing parent_item's staff lock"""
|
|
child_item.set_staff_lock(True)
|
|
parent_item.set_staff_lock(True)
|
|
self.assertTrue(parent_item.has_staff_lock_warning)
|
|
self.assertTrue(child_item.has_staff_lock_warning)
|
|
parent_item.set_staff_lock(False)
|
|
self.assertFalse(parent_item.has_staff_lock_warning)
|
|
self.assertTrue(child_item.has_staff_lock_warning)
|
|
|
|
def test_units_can_be_locked(self):
|
|
"""
|
|
Scenario: Units can be locked and unlocked from the course outline page
|
|
Given I have a course with a unit
|
|
When I click on the configuration icon
|
|
And I enable explicit staff locking
|
|
And I click save
|
|
Then the unit shows a staff lock warning
|
|
And when I click on the configuration icon
|
|
And I disable explicit staff locking
|
|
And I click save
|
|
Then the unit does not show a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0)
|
|
self._toggle_lock_on_unlocked_item(unit)
|
|
|
|
def test_subsections_can_be_locked(self):
|
|
"""
|
|
Scenario: Subsections can be locked and unlocked from the course outline page
|
|
Given I have a course with a subsection
|
|
When I click on the subsection's configuration icon
|
|
And I enable explicit staff locking
|
|
And I click save
|
|
Then the subsection shows a staff lock warning
|
|
And all its descendants are staff locked
|
|
And when I click on the subsection's configuration icon
|
|
And I disable explicit staff locking
|
|
And I click save
|
|
Then the the subsection does not show a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
self._toggle_lock_on_unlocked_item(subsection)
|
|
|
|
def test_sections_can_be_locked(self):
|
|
"""
|
|
Scenario: Sections can be locked and unlocked from the course outline page
|
|
Given I have a course with a section
|
|
When I click on the section's configuration icon
|
|
And I enable explicit staff locking
|
|
And I click save
|
|
Then the section shows a staff lock warning
|
|
And all its descendants are staff locked
|
|
And when I click on the section's configuration icon
|
|
And I disable explicit staff locking
|
|
And I click save
|
|
Then the section does not show a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
section = self.course_outline_page.section_at(0)
|
|
self._toggle_lock_on_unlocked_item(section)
|
|
|
|
def test_explicit_staff_lock_remains_after_unlocking_section(self):
|
|
"""
|
|
Scenario: An explicitly locked unit is still locked after removing an inherited lock from a section
|
|
Given I have a course with sections, subsections, and units
|
|
And I have enabled explicit staff lock on a section and one of its units
|
|
When I click on the section's configuration icon
|
|
And I disable explicit staff locking
|
|
And I click save
|
|
Then the unit still shows a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
section = self.course_outline_page.section_at(0)
|
|
unit = section.subsection_at(0).unit_at(0)
|
|
self._verify_explicit_staff_lock_remains_after_unlocking_parent(unit, section)
|
|
|
|
def test_explicit_staff_lock_remains_after_unlocking_subsection(self):
|
|
"""
|
|
Scenario: An explicitly locked unit is still locked after removing an inherited lock from a subsection
|
|
Given I have a course with sections, subsections, and units
|
|
And I have enabled explicit staff lock on a subsection and one of its units
|
|
When I click on the subsection's configuration icon
|
|
And I disable explicit staff locking
|
|
And I click save
|
|
Then the unit still shows a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
unit = subsection.unit_at(0)
|
|
self._verify_explicit_staff_lock_remains_after_unlocking_parent(unit, subsection)
|
|
|
|
def test_section_displays_lock_when_all_subsections_locked(self):
|
|
"""
|
|
Scenario: All subsections in section are explicitly locked, section should display staff only warning
|
|
Given I have a course one section and two subsections
|
|
When I enable explicit staff lock on all the subsections
|
|
Then the section shows a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section_at(0)
|
|
section.subsection_at(0).set_staff_lock(True)
|
|
section.subsection_at(1).set_staff_lock(True)
|
|
self.assertTrue(section.has_staff_lock_warning)
|
|
|
|
def test_section_displays_lock_when_all_units_locked(self):
|
|
"""
|
|
Scenario: All units in a section are explicitly locked, section should display staff only warning
|
|
Given I have a course with one section, two subsections, and four units
|
|
When I enable explicit staff lock on all the units
|
|
Then the section shows a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
section = self.course_outline_page.section_at(0)
|
|
section.subsection_at(0).unit_at(0).set_staff_lock(True)
|
|
section.subsection_at(0).unit_at(1).set_staff_lock(True)
|
|
section.subsection_at(1).unit_at(0).set_staff_lock(True)
|
|
section.subsection_at(1).unit_at(1).set_staff_lock(True)
|
|
self.assertTrue(section.has_staff_lock_warning)
|
|
|
|
def test_subsection_displays_lock_when_all_units_locked(self):
|
|
"""
|
|
Scenario: All units in subsection are explicitly locked, subsection should display staff only warning
|
|
Given I have a course with one subsection and two units
|
|
When I enable explicit staff lock on all the units
|
|
Then the subsection shows a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
subsection.unit_at(0).set_staff_lock(True)
|
|
subsection.unit_at(1).set_staff_lock(True)
|
|
self.assertTrue(subsection.has_staff_lock_warning)
|
|
|
|
def test_section_does_not_display_lock_when_some_subsections_locked(self):
|
|
"""
|
|
Scenario: Only some subsections in section are explicitly locked, section should NOT display staff only warning
|
|
Given I have a course with one section and two subsections
|
|
When I enable explicit staff lock on one subsection
|
|
Then the section does not show a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section_at(0)
|
|
section.subsection_at(0).set_staff_lock(True)
|
|
self.assertFalse(section.has_staff_lock_warning)
|
|
|
|
def test_section_does_not_display_lock_when_some_units_locked(self):
|
|
"""
|
|
Scenario: Only some units in section are explicitly locked, section should NOT display staff only warning
|
|
Given I have a course with one section, two subsections, and four units
|
|
When I enable explicit staff lock on three units
|
|
Then the section does not show a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
section = self.course_outline_page.section_at(0)
|
|
section.subsection_at(0).unit_at(0).set_staff_lock(True)
|
|
section.subsection_at(0).unit_at(1).set_staff_lock(True)
|
|
section.subsection_at(1).unit_at(1).set_staff_lock(True)
|
|
self.assertFalse(section.has_staff_lock_warning)
|
|
|
|
def test_subsection_does_not_display_lock_when_some_units_locked(self):
|
|
"""
|
|
Scenario: Only some units in subsection are explicitly locked, subsection should NOT display staff only warning
|
|
Given I have a course with one subsection and two units
|
|
When I enable explicit staff lock on one unit
|
|
Then the subsection does not show a staff lock warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
subsection.unit_at(0).set_staff_lock(True)
|
|
self.assertFalse(subsection.has_staff_lock_warning)
|
|
|
|
def test_locked_sections_do_not_appear_in_lms(self):
|
|
"""
|
|
Scenario: A locked section is not visible to students in the LMS
|
|
Given I have a course with two sections
|
|
When I enable explicit staff lock on one section
|
|
And I click the View Live button to switch to staff view
|
|
And I visit the course home with the outline
|
|
Then I see two sections in the outline
|
|
And when I switch the view mode to student view
|
|
Then I see one section in the outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.add_section_from_top_button()
|
|
self.course_outline_page.section_at(1).set_staff_lock(True)
|
|
self.course_outline_page.view_live()
|
|
|
|
course_home_page = CourseHomePage(self.browser, self.course_id)
|
|
course_home_page.visit()
|
|
course_home_page.wait_for_page()
|
|
self.assertEqual(course_home_page.outline.num_sections, 2)
|
|
course_home_page.preview.set_staff_view_mode('Learner')
|
|
course_home_page.wait_for(lambda: course_home_page.outline.num_sections == 1,
|
|
'Only 1 section is visible in the outline')
|
|
|
|
def test_toggling_staff_lock_on_section_does_not_publish_draft_units(self):
|
|
"""
|
|
Scenario: Locking and unlocking a section will not publish its draft units
|
|
Given I have a course with a section and unit
|
|
And the unit has a draft and published version
|
|
When I enable explicit staff lock on the section
|
|
And I disable explicit staff lock on the section
|
|
And I click the View Live button to switch to staff view
|
|
Then I see the published version of the unit
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to()
|
|
add_discussion(unit)
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
section = self.course_outline_page.section_at(0)
|
|
section.set_staff_lock(True)
|
|
section.set_staff_lock(False)
|
|
unit = section.subsection_at(0).unit_at(0).go_to()
|
|
unit.view_published_version()
|
|
courseware = CoursewarePage(self.browser, self.course_id)
|
|
courseware.wait_for_page()
|
|
self.assertEqual(courseware.num_xblock_components, 0)
|
|
|
|
def test_toggling_staff_lock_on_subsection_does_not_publish_draft_units(self):
|
|
"""
|
|
Scenario: Locking and unlocking a subsection will not publish its draft units
|
|
Given I have a course with a subsection and unit
|
|
And the unit has a draft and published version
|
|
When I enable explicit staff lock on the subsection
|
|
And I disable explicit staff lock on the subsection
|
|
And I click the View Live button to switch to staff view
|
|
Then I see the published version of the unit
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to()
|
|
add_discussion(unit)
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
subsection.set_staff_lock(True)
|
|
subsection.set_staff_lock(False)
|
|
unit = subsection.unit_at(0).go_to()
|
|
unit.view_published_version()
|
|
courseware = CoursewarePage(self.browser, self.course_id)
|
|
courseware.wait_for_page()
|
|
self.assertEqual(courseware.num_xblock_components, 0)
|
|
|
|
def test_removing_staff_lock_from_unit_without_inherited_lock_shows_warning(self):
|
|
"""
|
|
Scenario: Removing explicit staff lock from a unit which does not inherit staff lock displays a warning.
|
|
Given I have a course with a subsection and unit
|
|
When I enable explicit staff lock on the unit
|
|
And I disable explicit staff lock on the unit
|
|
Then I see a modal warning.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0)
|
|
unit.set_staff_lock(True)
|
|
self._remove_staff_lock_and_verify_warning(unit, True)
|
|
|
|
def test_removing_staff_lock_from_subsection_without_inherited_lock_shows_warning(self):
|
|
"""
|
|
Scenario: Removing explicit staff lock from a subsection which does not inherit staff lock displays a warning.
|
|
Given I have a course with a section and subsection
|
|
When I enable explicit staff lock on the subsection
|
|
And I disable explicit staff lock on the subsection
|
|
Then I see a modal warning.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
subsection.set_staff_lock(True)
|
|
self._remove_staff_lock_and_verify_warning(subsection, True)
|
|
|
|
def test_removing_staff_lock_from_unit_with_inherited_lock_shows_no_warning(self):
|
|
"""
|
|
Scenario: Removing explicit staff lock from a unit which also inherits staff lock displays no warning.
|
|
Given I have a course with a subsection and unit
|
|
When I enable explicit staff lock on the subsection
|
|
And I enable explicit staff lock on the unit
|
|
When I disable explicit staff lock on the unit
|
|
Then I do not see a modal warning.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
subsection = self.course_outline_page.section_at(0).subsection_at(0)
|
|
unit = subsection.unit_at(0)
|
|
subsection.set_staff_lock(True)
|
|
unit.set_staff_lock(True)
|
|
self._remove_staff_lock_and_verify_warning(unit, False)
|
|
|
|
def test_removing_staff_lock_from_subsection_with_inherited_lock_shows_no_warning(self):
|
|
"""
|
|
Scenario: Removing explicit staff lock from a subsection which also inherits staff lock displays no warning.
|
|
Given I have a course with a section and subsection
|
|
When I enable explicit staff lock on the section
|
|
And I enable explicit staff lock on the subsection
|
|
When I disable explicit staff lock on the subsection
|
|
Then I do not see a modal warning.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.expand_all_subsections()
|
|
section = self.course_outline_page.section_at(0)
|
|
subsection = section.subsection_at(0)
|
|
section.set_staff_lock(True)
|
|
subsection.set_staff_lock(True)
|
|
self._remove_staff_lock_and_verify_warning(subsection, False)
|
|
|
|
|
|
@attr(shard=14)
|
|
class EditNamesTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Click-to-edit section/subsection names
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def set_name_and_verify(self, item, old_name, new_name, expected_name):
|
|
"""
|
|
Changes the display name of item from old_name to new_name, then verifies that its value is expected_name.
|
|
"""
|
|
self.assertEqual(item.name, old_name)
|
|
item.change_name(new_name)
|
|
self.assertFalse(item.in_editable_form())
|
|
self.assertEqual(item.name, expected_name)
|
|
|
|
def test_edit_section_name(self):
|
|
"""
|
|
Scenario: Click-to-edit section name
|
|
Given that I have created a section
|
|
When I click on the name of section
|
|
Then the section name becomes editable
|
|
And given that I have edited the section name
|
|
When I click outside of the edited section name
|
|
Then the section name saves
|
|
And becomes non-editable
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.set_name_and_verify(
|
|
self.course_outline_page.section_at(0),
|
|
'Test Section',
|
|
'Changed',
|
|
'Changed'
|
|
)
|
|
|
|
def test_edit_subsection_name(self):
|
|
"""
|
|
Scenario: Click-to-edit subsection name
|
|
Given that I have created a subsection
|
|
When I click on the name of subsection
|
|
Then the subsection name becomes editable
|
|
And given that I have edited the subsection name
|
|
When I click outside of the edited subsection name
|
|
Then the subsection name saves
|
|
And becomes non-editable
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.set_name_and_verify(
|
|
self.course_outline_page.section_at(0).subsection_at(0),
|
|
'Test Subsection',
|
|
'Changed',
|
|
'Changed'
|
|
)
|
|
|
|
def test_edit_empty_section_name(self):
|
|
"""
|
|
Scenario: Click-to-edit section name, enter empty name
|
|
Given that I have created a section
|
|
And I have clicked to edit the name of the section
|
|
And I have entered an empty section name
|
|
When I click outside of the edited section name
|
|
Then the section name does not change
|
|
And becomes non-editable
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.set_name_and_verify(
|
|
self.course_outline_page.section_at(0),
|
|
'Test Section',
|
|
'',
|
|
'Test Section'
|
|
)
|
|
|
|
def test_edit_empty_subsection_name(self):
|
|
"""
|
|
Scenario: Click-to-edit subsection name, enter empty name
|
|
Given that I have created a subsection
|
|
And I have clicked to edit the name of the subsection
|
|
And I have entered an empty subsection name
|
|
When I click outside of the edited subsection name
|
|
Then the subsection name does not change
|
|
And becomes non-editable
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.set_name_and_verify(
|
|
self.course_outline_page.section_at(0).subsection_at(0),
|
|
'Test Subsection',
|
|
'',
|
|
'Test Subsection'
|
|
)
|
|
|
|
def test_editing_names_does_not_expand_collapse(self):
|
|
"""
|
|
Scenario: A section stays in the same expand/collapse state while its name is edited
|
|
Given that I have created a section
|
|
And the section is collapsed
|
|
When I click on the name of the section
|
|
Then the section is collapsed
|
|
And given that I have entered a new name
|
|
Then the section is collapsed
|
|
And given that I press ENTER to finalize the name
|
|
Then the section is collapsed
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.section_at(0).expand_subsection()
|
|
self.assertFalse(self.course_outline_page.section_at(0).in_editable_form())
|
|
self.assertTrue(self.course_outline_page.section_at(0).is_collapsed)
|
|
self.course_outline_page.section_at(0).edit_name()
|
|
self.assertTrue(self.course_outline_page.section_at(0).in_editable_form())
|
|
self.assertTrue(self.course_outline_page.section_at(0).is_collapsed)
|
|
self.course_outline_page.section_at(0).enter_name('Changed')
|
|
self.assertTrue(self.course_outline_page.section_at(0).is_collapsed)
|
|
self.course_outline_page.section_at(0).finalize_name()
|
|
self.assertTrue(self.course_outline_page.section_at(0).is_collapsed)
|
|
|
|
|
|
@attr(shard=14)
|
|
class CreateSectionsTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Create new sections/subsections/units
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Start with a completely empty course to easily test adding things to it """
|
|
pass
|
|
|
|
def test_create_new_section_from_top_button(self):
|
|
"""
|
|
Scenario: Create new section from button at top of page
|
|
Given that I am on the course outline
|
|
When I click the "+ Add section" button at the top of the page
|
|
Then I see a new section added to the bottom of the page
|
|
And the display name is in its editable form.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.add_section_from_top_button()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.assertTrue(self.course_outline_page.section_at(0).in_editable_form())
|
|
|
|
def test_create_new_section_from_bottom_button(self):
|
|
"""
|
|
Scenario: Create new section from button at bottom of page
|
|
Given that I am on the course outline
|
|
When I click the "+ Add section" button at the bottom of the page
|
|
Then I see a new section added to the bottom of the page
|
|
And the display name is in its editable form.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.add_section_from_bottom_button()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.assertTrue(self.course_outline_page.section_at(0).in_editable_form())
|
|
|
|
def test_create_new_section_from_bottom_button_plus_icon(self):
|
|
"""
|
|
Scenario: Create new section from button plus icon at bottom of page
|
|
Given that I am on the course outline
|
|
When I click the plus icon in "+ Add section" button at the bottom of the page
|
|
Then I see a new section added to the bottom of the page
|
|
And the display name is in its editable form.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.add_section_from_bottom_button(click_child_icon=True)
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.assertTrue(self.course_outline_page.section_at(0).in_editable_form())
|
|
|
|
def test_create_new_subsection(self):
|
|
"""
|
|
Scenario: Create new subsection
|
|
Given that I have created a section
|
|
When I click the "+ Add subsection" button in that section
|
|
Then I see a new subsection added to the bottom of the section
|
|
And the display name is in its editable form.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.add_section_from_top_button()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.course_outline_page.section_at(0).add_subsection()
|
|
subsections = self.course_outline_page.section_at(0).subsections()
|
|
self.assertEqual(len(subsections), 1)
|
|
self.assertTrue(subsections[0].in_editable_form())
|
|
|
|
def test_create_new_unit(self):
|
|
"""
|
|
Scenario: Create new unit
|
|
Given that I have created a section
|
|
And that I have created a subsection within that section
|
|
When I click the "+ Add unit" button in that subsection
|
|
Then I am redirected to a New Unit page
|
|
And the display name is in its editable form.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.add_section_from_top_button()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.course_outline_page.section_at(0).add_subsection()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
|
self.course_outline_page.section_at(0).subsection_at(0).add_unit()
|
|
unit_page = ContainerPage(self.browser, None)
|
|
unit_page.wait_for_page()
|
|
self.assertTrue(unit_page.is_inline_editing_display_name())
|
|
|
|
|
|
@attr(shard=14)
|
|
class DeleteContentTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Deleting sections/subsections/units
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def test_delete_section(self):
|
|
"""
|
|
Scenario: Delete section
|
|
Given that I am on the course outline
|
|
When I click the delete button for a section on the course outline
|
|
Then I should receive a confirmation message, asking me if I really want to delete the section
|
|
When I click "Yes, I want to delete this component"
|
|
Then the confirmation message should close
|
|
And the section should immediately be deleted from the course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.course_outline_page.section_at(0).delete()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 0)
|
|
|
|
def test_cancel_delete_section(self):
|
|
"""
|
|
Scenario: Cancel delete of section
|
|
Given that I clicked the delte button for a section on the course outline
|
|
And I received a confirmation message, asking me if I really want to delete the component
|
|
When I click "Cancel"
|
|
Then the confirmation message should close
|
|
And the section should remain in the course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
self.course_outline_page.section_at(0).delete(cancel=True)
|
|
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
|
|
|
def test_delete_subsection(self):
|
|
"""
|
|
Scenario: Delete subsection
|
|
Given that I am on the course outline
|
|
When I click the delete button for a subsection on the course outline
|
|
Then I should receive a confirmation message, asking me if I really want to delete the subsection
|
|
When I click "Yes, I want to delete this component"
|
|
Then the confiramtion message should close
|
|
And the subsection should immediately be deleted from the course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
|
self.course_outline_page.section_at(0).subsection_at(0).delete()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 0)
|
|
|
|
def test_cancel_delete_subsection(self):
|
|
"""
|
|
Scenario: Cancel delete of subsection
|
|
Given that I clicked the delete button for a subsection on the course outline
|
|
And I received a confirmation message, asking me if I really want to delete the subsection
|
|
When I click "cancel"
|
|
Then the confirmation message should close
|
|
And the subsection should remain in the course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
|
self.course_outline_page.section_at(0).subsection_at(0).delete(cancel=True)
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
|
|
|
def test_delete_unit(self):
|
|
"""
|
|
Scenario: Delete unit
|
|
Given that I am on the course outline
|
|
When I click the delete button for a unit on the course outline
|
|
Then I should receive a confirmation message, asking me if I really want to delete the unit
|
|
When I click "Yes, I want to delete this unit"
|
|
Then the confirmation message should close
|
|
And the unit should immediately be deleted from the course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.section_at(0).subsection_at(0).expand_subsection()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1)
|
|
self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).delete()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 0)
|
|
|
|
def test_cancel_delete_unit(self):
|
|
"""
|
|
Scenario: Cancel delete of unit
|
|
Given that I clicked the delete button for a unit on the course outline
|
|
And I received a confirmation message, asking me if I really want to delete the unit
|
|
When I click "Cancel"
|
|
Then the confirmation message should close
|
|
And the unit should remain in the course outline
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.section_at(0).subsection_at(0).expand_subsection()
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1)
|
|
self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).delete(cancel=True)
|
|
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1)
|
|
|
|
def test_delete_all_no_content_message(self):
|
|
"""
|
|
Scenario: Delete all sections/subsections/units in a course, "no content" message should appear
|
|
Given that I delete all sections, subsections, and units in a course
|
|
When I visit the course outline
|
|
Then I will see a message that says, "You haven't added any content to this course yet"
|
|
Add see a + Add Section button
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertFalse(self.course_outline_page.has_no_content_message)
|
|
self.course_outline_page.section_at(0).delete()
|
|
self.assertEqual(len(self.course_outline_page.sections()), 0)
|
|
self.assertTrue(self.course_outline_page.has_no_content_message)
|
|
|
|
|
|
@attr(shard=14)
|
|
class ExpandCollapseMultipleSectionsTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Courses with multiple sections can expand and collapse all sections.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Start with a course with two sections """
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', 'Test Section').add_children(
|
|
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
|
|
XBlockFixtureDesc('vertical', 'Test Unit')
|
|
)
|
|
),
|
|
XBlockFixtureDesc('chapter', 'Test Section 2').add_children(
|
|
XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children(
|
|
XBlockFixtureDesc('vertical', 'Test Unit 2')
|
|
)
|
|
)
|
|
)
|
|
|
|
def verify_all_sections(self, collapsed):
|
|
"""
|
|
Verifies that all sections are collapsed if collapsed is True, otherwise all expanded.
|
|
"""
|
|
for section in self.course_outline_page.sections():
|
|
self.assertEqual(collapsed, section.is_collapsed)
|
|
|
|
def toggle_all_sections(self):
|
|
"""
|
|
Toggles the expand collapse state of all sections.
|
|
"""
|
|
for section in self.course_outline_page.sections():
|
|
section.expand_subsection()
|
|
|
|
def test_expanded_by_default(self):
|
|
"""
|
|
Scenario: The default layout for the outline page is to show sections in expanded view
|
|
Given I have a course with sections
|
|
When I navigate to the course outline page
|
|
Then I see the "Collapse All Sections" link
|
|
And all sections are expanded
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
|
self.verify_all_sections(collapsed=False)
|
|
|
|
def test_no_expand_link_for_empty_course(self):
|
|
"""
|
|
Scenario: Collapse link is removed after last section of a course is deleted
|
|
Given I have a course with multiple sections
|
|
And I navigate to the course outline page
|
|
When I will confirm all alerts
|
|
And I press the "section" delete icon
|
|
Then I do not see the "Collapse All Sections" link
|
|
And I will see a message that says "You haven't added any content to this course yet"
|
|
"""
|
|
self.course_outline_page.visit()
|
|
for section in self.course_outline_page.sections():
|
|
section.delete()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
|
self.assertTrue(self.course_outline_page.has_no_content_message)
|
|
|
|
def test_collapse_all_when_all_expanded(self):
|
|
"""
|
|
Scenario: Collapse all sections when all sections are expanded
|
|
Given I navigate to the outline page of a course with sections
|
|
And all sections are expanded
|
|
When I click the "Collapse All Sections" link
|
|
Then I see the "Expand All Sections" link
|
|
And all sections are collapsed
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.verify_all_sections(collapsed=False)
|
|
self.course_outline_page.toggle_expand_collapse()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
|
self.verify_all_sections(collapsed=True)
|
|
|
|
def test_collapse_all_when_some_expanded(self):
|
|
"""
|
|
Scenario: Collapsing all sections when 1 or more sections are already collapsed
|
|
Given I navigate to the outline page of a course with sections
|
|
And all sections are expanded
|
|
When I collapse the first section
|
|
And I click the "Collapse All Sections" link
|
|
Then I see the "Expand All Sections" link
|
|
And all sections are collapsed
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.verify_all_sections(collapsed=False)
|
|
self.course_outline_page.section_at(0).expand_subsection()
|
|
self.course_outline_page.toggle_expand_collapse()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
|
self.verify_all_sections(collapsed=True)
|
|
|
|
def test_expand_all_when_all_collapsed(self):
|
|
"""
|
|
Scenario: Expanding all sections when all sections are collapsed
|
|
Given I navigate to the outline page of a course with multiple sections
|
|
And I click the "Collapse All Sections" link
|
|
When I click the "Expand All Sections" link
|
|
Then I see the "Collapse All Sections" link
|
|
And all sections are expanded
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.toggle_expand_collapse()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
|
self.course_outline_page.toggle_expand_collapse()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
|
self.verify_all_sections(collapsed=False)
|
|
|
|
def test_expand_all_when_some_collapsed(self):
|
|
"""
|
|
Scenario: Expanding all sections when 1 or more sections are already expanded
|
|
Given I navigate to the outline page of a course with multiple sections
|
|
And I click the "Collapse All Sections" link
|
|
When I expand the first section
|
|
And I click the "Expand All Sections" link
|
|
Then I see the "Collapse All Sections" link
|
|
And all sections are expanded
|
|
"""
|
|
self.course_outline_page.visit()
|
|
# We have seen unexplainable sporadic failures in this test. Try disabling animations to see
|
|
# if that helps.
|
|
disable_animations(self.course_outline_page)
|
|
self.course_outline_page.toggle_expand_collapse()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
|
self.verify_all_sections(collapsed=True)
|
|
self.course_outline_page.section_at(0).expand_subsection()
|
|
self.course_outline_page.toggle_expand_collapse()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
|
self.verify_all_sections(collapsed=False)
|
|
|
|
|
|
@attr(shard=14)
|
|
class ExpandCollapseSingleSectionTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Courses with a single section can expand and collapse all sections.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def test_no_expand_link_for_empty_course(self):
|
|
"""
|
|
Scenario: Collapse link is removed after last section of a course is deleted
|
|
Given I have a course with one section
|
|
And I navigate to the course outline page
|
|
When I will confirm all alerts
|
|
And I press the "section" delete icon
|
|
Then I do not see the "Collapse All Sections" link
|
|
And I will see a message that says "You haven't added any content to this course yet"
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.section_at(0).delete()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
|
self.assertTrue(self.course_outline_page.has_no_content_message)
|
|
|
|
def test_old_subsection_stays_collapsed_after_creation(self):
|
|
"""
|
|
Scenario: Collapsed subsection stays collapsed after creating a new subsection
|
|
Given I have a course with one section and subsection
|
|
And I navigate to the course outline page
|
|
Then the subsection is collapsed
|
|
And when I create a new subsection
|
|
Then the first subsection is collapsed
|
|
And the second subsection is expanded
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertTrue(self.course_outline_page.section_at(0).subsection_at(0).is_collapsed)
|
|
self.course_outline_page.section_at(0).add_subsection()
|
|
self.assertTrue(self.course_outline_page.section_at(0).subsection_at(0).is_collapsed)
|
|
self.assertFalse(self.course_outline_page.section_at(0).subsection_at(1).is_collapsed)
|
|
|
|
|
|
@attr(shard=14)
|
|
class ExpandCollapseEmptyTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Courses with no sections initially can expand and collapse all sections after addition.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Start with an empty course """
|
|
pass
|
|
|
|
def test_no_expand_link_for_empty_course(self):
|
|
"""
|
|
Scenario: Expand/collapse for a course with no sections
|
|
Given I have a course with no sections
|
|
When I navigate to the course outline page
|
|
Then I do not see the "Collapse All Sections" link
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
|
|
|
def test_link_appears_after_section_creation(self):
|
|
"""
|
|
Scenario: Collapse link appears after creating first section of a course
|
|
Given I have a course with no sections
|
|
When I navigate to the course outline page
|
|
And I add a section
|
|
Then I see the "Collapse All Sections" link
|
|
And all sections are expanded
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
|
self.course_outline_page.add_section_from_top_button()
|
|
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
|
self.assertFalse(self.course_outline_page.section_at(0).is_collapsed)
|
|
|
|
|
|
@attr(shard=14)
|
|
class DefaultStatesEmptyTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Misc course outline default states/actions when starting with an empty course
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
""" Start with an empty course """
|
|
pass
|
|
|
|
def test_empty_course_message(self):
|
|
"""
|
|
Scenario: Empty course state
|
|
Given that I am in a course with no sections, subsections, nor units
|
|
When I visit the course outline
|
|
Then I will see a message that says "You haven't added any content to this course yet"
|
|
And see a + Add Section button
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertTrue(self.course_outline_page.has_no_content_message)
|
|
self.assertTrue(self.course_outline_page.bottom_add_section_button.is_present())
|
|
|
|
|
|
@attr(shard=14)
|
|
class DefaultStatesContentTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Misc course outline default states/actions when starting with a course with content
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def test_view_live(self):
|
|
"""
|
|
Scenario: View Live version from course outline
|
|
Given that I am on the course outline
|
|
When I click the "View Live" button
|
|
Then a new tab will open to the course on the LMS
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.view_live()
|
|
courseware = CoursewarePage(self.browser, self.course_id)
|
|
courseware.wait_for_page()
|
|
self.assertEqual(courseware.num_xblock_components, 3)
|
|
self.assertEqual(courseware.xblock_component_type(0), 'problem')
|
|
self.assertEqual(courseware.xblock_component_type(1), 'html')
|
|
self.assertEqual(courseware.xblock_component_type(2), 'discussion')
|
|
|
|
|
|
@attr(shard=7)
|
|
class UnitNavigationTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Navigate to units
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def test_navigate_to_unit(self):
|
|
"""
|
|
Scenario: Click unit name to navigate to unit page
|
|
Given that I have expanded a section/subsection so I can see unit names
|
|
When I click on a unit name
|
|
Then I will be taken to the appropriate unit page
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.section_at(0).subsection_at(0).expand_subsection()
|
|
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to()
|
|
unit.wait_for_page()
|
|
|
|
|
|
@attr(shard=7)
|
|
class PublishSectionTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Publish sections.
|
|
"""
|
|
|
|
__test__ = True
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
"""
|
|
Sets up a course structure with 2 subsections inside a single section.
|
|
The first subsection has 2 units, and the second subsection has one unit.
|
|
"""
|
|
self.courseware = CoursewarePage(self.browser, self.course_id)
|
|
self.course_home_page = CourseHomePage(self.browser, self.course_id)
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', SECTION_NAME).add_children(
|
|
XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children(
|
|
XBlockFixtureDesc('vertical', UNIT_NAME),
|
|
XBlockFixtureDesc('vertical', 'Test Unit 2'),
|
|
),
|
|
XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children(
|
|
XBlockFixtureDesc('vertical', 'Test Unit 3'),
|
|
),
|
|
),
|
|
)
|
|
|
|
def test_unit_publishing(self):
|
|
"""
|
|
Scenario: Can publish a unit and see published content in LMS
|
|
Given I have a section with 2 subsections and 3 unpublished units
|
|
When I go to the course outline
|
|
Then I see publish button for the first unit, subsection, section
|
|
When I publish the first unit
|
|
Then I see that publish button for the first unit disappears
|
|
And I see publish buttons for subsection, section
|
|
And I see the changed content in LMS
|
|
"""
|
|
self._add_unpublished_content()
|
|
self.course_outline_page.visit()
|
|
section, subsection, unit = self._get_items()
|
|
self.assertTrue(unit.publish_action)
|
|
self.assertTrue(subsection.publish_action)
|
|
self.assertTrue(section.publish_action)
|
|
unit.publish()
|
|
self.assertFalse(unit.publish_action)
|
|
self.assertTrue(subsection.publish_action)
|
|
self.assertTrue(section.publish_action)
|
|
self.courseware.visit()
|
|
self.assertEqual(1, self.courseware.num_xblock_components)
|
|
|
|
def test_subsection_publishing(self):
|
|
"""
|
|
Scenario: Can publish a subsection and see published content in LMS
|
|
Given I have a section with 2 subsections and 3 unpublished units
|
|
When I go to the course outline
|
|
Then I see publish button for the unit, subsection, section
|
|
When I publish the first subsection
|
|
Then I see that publish button for the first subsection disappears
|
|
And I see that publish buttons disappear for the child units of the subsection
|
|
And I see publish button for section
|
|
And I see the changed content in LMS
|
|
"""
|
|
self._add_unpublished_content()
|
|
self.course_outline_page.visit()
|
|
section, subsection, unit = self._get_items()
|
|
self.assertTrue(unit.publish_action)
|
|
self.assertTrue(subsection.publish_action)
|
|
self.assertTrue(section.publish_action)
|
|
self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME).publish()
|
|
self.assertFalse(unit.publish_action)
|
|
self.assertFalse(subsection.publish_action)
|
|
self.assertTrue(section.publish_action)
|
|
self.courseware.visit()
|
|
self.assertEqual(1, self.courseware.num_xblock_components)
|
|
self.courseware.go_to_sequential_position(2)
|
|
self.assertEqual(1, self.courseware.num_xblock_components)
|
|
|
|
def test_section_publishing(self):
|
|
"""
|
|
Scenario: Can publish a section and see published content in LMS
|
|
Given I have a section with 2 subsections and 3 unpublished units
|
|
When I go to the course outline
|
|
Then I see publish button for the unit, subsection, section
|
|
When I publish the section
|
|
Then I see that publish buttons disappears
|
|
And I see the changed content in LMS
|
|
"""
|
|
self._add_unpublished_content()
|
|
self.course_outline_page.visit()
|
|
section, subsection, unit = self._get_items()
|
|
self.assertTrue(subsection.publish_action)
|
|
self.assertTrue(section.publish_action)
|
|
self.assertTrue(unit.publish_action)
|
|
self.course_outline_page.section(SECTION_NAME).publish()
|
|
self.assertFalse(subsection.publish_action)
|
|
self.assertFalse(section.publish_action)
|
|
self.assertFalse(unit.publish_action)
|
|
self.courseware.visit()
|
|
self.assertEqual(1, self.courseware.num_xblock_components)
|
|
self.courseware.go_to_sequential_position(2)
|
|
self.assertEqual(1, self.courseware.num_xblock_components)
|
|
self.course_home_page.visit()
|
|
self.course_home_page.outline.go_to_section(SECTION_NAME, 'Test Subsection 2')
|
|
self.assertEqual(1, self.courseware.num_xblock_components)
|
|
|
|
def _add_unpublished_content(self):
|
|
"""
|
|
Adds unpublished HTML content to first three units in the course.
|
|
"""
|
|
for index in range(3):
|
|
self.course_fixture.create_xblock(
|
|
self.course_fixture.get_nested_xblocks(category="vertical")[index].locator,
|
|
XBlockFixtureDesc('html', 'Unpublished HTML Component ' + str(index)),
|
|
)
|
|
|
|
def _get_items(self):
|
|
"""
|
|
Returns first section, subsection, and unit on the page.
|
|
"""
|
|
section = self.course_outline_page.section(SECTION_NAME)
|
|
subsection = section.subsection(SUBSECTION_NAME)
|
|
unit = subsection.expand_subsection().unit(UNIT_NAME)
|
|
|
|
return (section, subsection, unit)
|
|
|
|
|
|
@attr(shard=7)
|
|
class DeprecationWarningMessageTest(CourseOutlineTest):
|
|
"""
|
|
Feature: Verify deprecation warning message.
|
|
"""
|
|
HEADING_TEXT = 'This course uses features that are no longer supported.'
|
|
COMPONENT_LIST_HEADING = 'You must delete or replace the following components.'
|
|
ADVANCE_MODULES_REMOVE_TEXT = (
|
|
u'To avoid errors, édX strongly recommends that you remove unsupported features '
|
|
u'from the course advanced settings. To do this, go to the Advanced Settings '
|
|
u'page, locate the "Advanced Module List" setting, and then delete the following '
|
|
u'modules from the list.'
|
|
)
|
|
DEFAULT_DISPLAYNAME = "Deprecated Component"
|
|
|
|
def _add_deprecated_advance_modules(self, block_types):
|
|
"""
|
|
Add `block_types` into `Advanced Module List`
|
|
|
|
Arguments:
|
|
block_types (list): list of block types
|
|
"""
|
|
self.advanced_settings.visit()
|
|
self.advanced_settings.set_values({"Advanced Module List": json.dumps(block_types)})
|
|
|
|
def _create_deprecated_components(self):
|
|
"""
|
|
Create deprecated components.
|
|
"""
|
|
parent_vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0]
|
|
|
|
self.course_fixture.create_xblock(
|
|
parent_vertical.locator,
|
|
XBlockFixtureDesc('poll', "Poll", data=load_data_str('poll_markdown.xml'))
|
|
)
|
|
self.course_fixture.create_xblock(parent_vertical.locator, XBlockFixtureDesc('survey', 'Survey'))
|
|
|
|
def _verify_deprecation_warning_info(
|
|
self,
|
|
deprecated_blocks_present,
|
|
components_present,
|
|
components_display_name_list=None,
|
|
deprecated_modules_list=None
|
|
):
|
|
"""
|
|
Verify deprecation warning
|
|
|
|
Arguments:
|
|
deprecated_blocks_present (bool): deprecated blocks remove text and
|
|
is list is visible if True else False
|
|
components_present (bool): components list shown if True else False
|
|
components_display_name_list (list): list of components display name
|
|
deprecated_modules_list (list): list of deprecated advance modules
|
|
"""
|
|
self.assertTrue(self.course_outline_page.deprecated_warning_visible)
|
|
self.assertEqual(self.course_outline_page.warning_heading_text, self.HEADING_TEXT)
|
|
self.assertEqual(self.course_outline_page.modules_remove_text_shown, deprecated_blocks_present)
|
|
if deprecated_blocks_present:
|
|
self.assertEqual(self.course_outline_page.modules_remove_text, self.ADVANCE_MODULES_REMOVE_TEXT)
|
|
self.assertEqual(self.course_outline_page.deprecated_advance_modules, deprecated_modules_list)
|
|
|
|
self.assertEqual(self.course_outline_page.components_visible, components_present)
|
|
if components_present:
|
|
self.assertEqual(self.course_outline_page.components_list_heading, self.COMPONENT_LIST_HEADING)
|
|
six.assertCountEqual(self, self.course_outline_page.components_display_names, components_display_name_list)
|
|
|
|
def test_no_deprecation_warning_message_present(self):
|
|
"""
|
|
Scenario: Verify that deprecation warning message is not shown if no deprecated
|
|
advance modules are not present and also no deprecated component exist in
|
|
course outline.
|
|
|
|
When I goto course outline
|
|
Then I don't see any deprecation warning
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertFalse(self.course_outline_page.deprecated_warning_visible)
|
|
|
|
def test_deprecation_warning_message_present(self):
|
|
"""
|
|
Scenario: Verify deprecation warning message if deprecated modules
|
|
and components are present.
|
|
|
|
Given I have "poll" advance modules present in `Advanced Module List`
|
|
And I have created 2 poll components
|
|
When I go to course outline
|
|
Then I see poll deprecated warning
|
|
And I see correct poll deprecated warning heading text
|
|
And I see correct poll deprecated warning advance modules remove text
|
|
And I see list of poll components with correct display names
|
|
"""
|
|
self._add_deprecated_advance_modules(block_types=['poll', 'survey'])
|
|
self._create_deprecated_components()
|
|
self.course_outline_page.visit()
|
|
self._verify_deprecation_warning_info(
|
|
deprecated_blocks_present=True,
|
|
components_present=True,
|
|
components_display_name_list=['Poll', 'Survey'],
|
|
deprecated_modules_list=['poll', 'survey']
|
|
)
|
|
|
|
def test_deprecation_warning_with_no_displayname(self):
|
|
"""
|
|
Scenario: Verify deprecation warning message if poll components are present.
|
|
|
|
Given I have created 1 poll deprecated component
|
|
When I go to course outline
|
|
Then I see poll deprecated warning
|
|
And I see correct poll deprecated warning heading text
|
|
And I see list of poll components with correct message
|
|
"""
|
|
parent_vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0]
|
|
|
|
# Create a deprecated component with display_name to be empty and make sure
|
|
# the deprecation warning is displayed with
|
|
self.course_fixture.create_xblock(
|
|
parent_vertical.locator,
|
|
XBlockFixtureDesc(category='poll', display_name="", data=load_data_str('poll_markdown.xml'))
|
|
)
|
|
self.course_outline_page.visit()
|
|
|
|
self._verify_deprecation_warning_info(
|
|
deprecated_blocks_present=False,
|
|
components_present=True,
|
|
components_display_name_list=[self.DEFAULT_DISPLAYNAME],
|
|
)
|
|
|
|
def test_warning_with_poll_advance_modules_only(self):
|
|
"""
|
|
Scenario: Verify that deprecation warning message is shown if only
|
|
poll advance modules are present and no poll component exist.
|
|
|
|
Given I have poll advance modules present in `Advanced Module List`
|
|
When I go to course outline
|
|
Then I see poll deprecated warning
|
|
And I see correct poll deprecated warning heading text
|
|
And I see correct poll deprecated warning advance modules remove text
|
|
And I don't see list of poll components
|
|
"""
|
|
self._add_deprecated_advance_modules(block_types=['poll', 'survey'])
|
|
self.course_outline_page.visit()
|
|
self._verify_deprecation_warning_info(
|
|
deprecated_blocks_present=True,
|
|
components_present=False,
|
|
deprecated_modules_list=['poll', 'survey']
|
|
)
|
|
|
|
def test_warning_with_poll_components_only(self):
|
|
"""
|
|
Scenario: Verify that deprecation warning message is shown if only
|
|
poll component exist and no poll advance modules are present.
|
|
|
|
Given I have created two poll components
|
|
When I go to course outline
|
|
Then I see poll deprecated warning
|
|
And I see correct poll deprecated warning heading text
|
|
And I don't see poll deprecated warning advance modules remove text
|
|
And I see list of poll components with correct display names
|
|
"""
|
|
self._create_deprecated_components()
|
|
self.course_outline_page.visit()
|
|
self._verify_deprecation_warning_info(
|
|
deprecated_blocks_present=False,
|
|
components_present=True,
|
|
components_display_name_list=['Poll', 'Survey']
|
|
)
|
|
|
|
|
|
@attr(shard=4)
|
|
class SelfPacedOutlineTest(CourseOutlineTest):
|
|
"""Test the course outline for a self-paced course."""
|
|
|
|
def populate_course_fixture(self, course_fixture):
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', SECTION_NAME).add_children(
|
|
XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children(
|
|
XBlockFixtureDesc('vertical', UNIT_NAME)
|
|
)
|
|
),
|
|
)
|
|
self.course_fixture.add_course_details({
|
|
'self_paced': True,
|
|
'start_date': datetime.now() + timedelta(days=1)
|
|
})
|
|
ConfigModelFixture('/config/self_paced', {'enabled': True}).install()
|
|
|
|
def test_release_dates_not_shown(self):
|
|
"""
|
|
Scenario: Ensure that block release dates are not shown on the
|
|
course outline page of a self-paced course.
|
|
|
|
Given I am the author of a self-paced course
|
|
When I go to the course outline
|
|
Then I should not see release dates for course content
|
|
"""
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section(SECTION_NAME)
|
|
self.assertEqual(section.release_date, '')
|
|
subsection = section.subsection(SUBSECTION_NAME)
|
|
self.assertEqual(subsection.release_date, '')
|
|
|
|
def test_edit_section_and_subsection(self):
|
|
"""
|
|
Scenario: Ensure that block release/due dates are not shown
|
|
in their settings modals.
|
|
|
|
Given I am the author of a self-paced course
|
|
When I go to the course outline
|
|
And I click on settings for a section or subsection
|
|
Then I should not see release or due date settings
|
|
"""
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section(SECTION_NAME)
|
|
modal = section.edit()
|
|
self.assertFalse(modal.has_release_date())
|
|
self.assertFalse(modal.has_due_date())
|
|
modal.cancel()
|
|
subsection = section.subsection(SUBSECTION_NAME)
|
|
modal = subsection.edit()
|
|
self.assertFalse(modal.has_release_date())
|
|
self.assertFalse(modal.has_due_date())
|
|
|
|
|
|
class CourseStatusOutlineTest(CourseOutlineTest):
|
|
"""Test the course outline status section."""
|
|
shard = 6
|
|
|
|
def setUp(self):
|
|
super(CourseStatusOutlineTest, self).setUp()
|
|
|
|
self.schedule_and_details_settings = SettingsPage(
|
|
self.browser,
|
|
self.course_info['org'],
|
|
self.course_info['number'],
|
|
self.course_info['run']
|
|
)
|
|
|
|
self.checklists = CourseChecklistsPage(
|
|
self.browser,
|
|
self.course_info['org'],
|
|
self.course_info['number'],
|
|
self.course_info['run']
|
|
)
|
|
|
|
def test_course_status_section(self):
|
|
"""
|
|
Ensure that the course status section appears in the course outline.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.assertTrue(self.course_outline_page.has_course_status_section)
|
|
|
|
def test_course_status_section_start_date_link(self):
|
|
"""
|
|
Ensure that the course start date link in the course status section in
|
|
the course outline links to the "Schedule and Details" page.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.click_course_status_section_start_date_link()
|
|
self.schedule_and_details_settings.wait_for_page()
|
|
|
|
def test_course_status_section_checklists_link(self):
|
|
"""
|
|
Ensure that the course checklists link in the course status section in
|
|
the course outline links to the "Checklists" page.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
self.course_outline_page.click_course_status_section_checklists_link()
|
|
self.checklists.wait_for_page()
|
|
|
|
|
|
class InstructorPacedToSelfPacedOutlineTest(CourseOutlineTest):
|
|
"""
|
|
Test the course outline when pacing is changed from
|
|
instructor to self paced.
|
|
"""
|
|
def populate_course_fixture(self, course_fixture):
|
|
course_fixture.add_children(
|
|
XBlockFixtureDesc('chapter', SECTION_NAME).add_children(
|
|
XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children(
|
|
XBlockFixtureDesc('vertical', UNIT_NAME)
|
|
)
|
|
),
|
|
)
|
|
self.course_fixture.add_course_details({
|
|
'start_date': datetime.now() + timedelta(days=1),
|
|
})
|
|
self.course_fixture.add_advanced_settings({
|
|
'enable_timed_exams': {
|
|
'value': True
|
|
}
|
|
})
|
|
|
|
def test_due_dates_not_shown(self):
|
|
"""
|
|
Scenario: Ensure that due dates for timed exams
|
|
are not displayed on the course outline page when switched to
|
|
self-paced mode from instructor-paced.
|
|
|
|
Given an instructor paced course, add a due date for a subsection.
|
|
Change the course's pacing to self-paced.
|
|
Make the subsection a timed exam.
|
|
Make sure adding the timed exam doesn't display the due date.
|
|
"""
|
|
self.course_outline_page.visit()
|
|
section = self.course_outline_page.section(SECTION_NAME)
|
|
subsection = section.subsection(SUBSECTION_NAME)
|
|
|
|
modal = subsection.edit()
|
|
modal.due_date = '5/14/2016'
|
|
modal.policy = 'Homework'
|
|
modal.save()
|
|
# Checking if the added due date saved
|
|
self.assertIn('May 14', subsection.due_date)
|
|
# Checking if grading policy added
|
|
self.assertEqual('Homework', subsection.policy)
|
|
|
|
# Updating the course mode to self-paced
|
|
self.course_fixture.add_course_details({
|
|
'self_paced': True
|
|
})
|
|
# Making the subsection a timed exam
|
|
self.course_outline_page.open_subsection_settings_dialog()
|
|
self.course_outline_page.select_advanced_tab()
|
|
self.course_outline_page.make_exam_timed()
|
|
|
|
# configure call to actually update course with new settings
|
|
self.course_fixture.configure_course()
|
|
# Reloading page after the changes
|
|
self.course_outline_page.visit()
|
|
self.assertIsNone(subsection.due_date)
|