Replace unit page with the container page.

STUD-1754
This commit is contained in:
Andy Armstrong
2014-06-16 14:21:42 -04:00
committed by cahrens
parent 3796f8c689
commit b1eccdf2d4
67 changed files with 712 additions and 2454 deletions

View File

@@ -37,11 +37,6 @@ REQUIREJS_WAIT = {
"jquery", "js/base", "js/models/course", "js/models/settings/advanced",
"js/views/settings/advanced", "codemirror"],
# Individual Unit (editing)
re.compile('^Individual Unit \|'): [
"js/base", "coffee/src/views/unit",
"coffee/src/views/module_edit"],
# Content - Outline
# Note that calling your org, course number, or display name, 'course' will mess this up
re.compile('^Course Outline \|'): [

View File

@@ -174,8 +174,7 @@ def add_staff_markup(user, has_instructor_access, block, view, frag, context):
if is_studio_course and is_mongo_course:
# build edit link to unit in CMS. Can't use reverse here as lms doesn't load cms's urls.py
# reverse for contentstore.views.unit_handler
edit_link = "//" + settings.CMS_BASE + '/unit/' + unicode(block.location)
edit_link = "//" + settings.CMS_BASE + '/container/' + unicode(block.location)
# return edit link in rendered HTML for display
return wrap_fragment(frag, render_to_string("edit_unit_link.html", {'frag_content': frag.content, 'edit_link': edit_link}))

View File

@@ -171,7 +171,6 @@ class SplitTestModuleStudioTest(SplitTestModuleTest):
Context for rendering the studio "author_view".
"""
return {
'container_view': True,
'reorderable_items': set(),
'root_xblock': root_xblock,
}

View File

@@ -13,7 +13,6 @@ class StudioEditableModuleTestCase(BaseVerticalModuleTest):
"""
reorderable_items = set()
context = {
'container_view': True,
'reorderable_items': reorderable_items,
'read_only': False,
'root_xblock': self.vertical,

View File

@@ -57,7 +57,7 @@ class VerticalModuleTestCase(BaseVerticalModuleTest):
"""
# Vertical shouldn't render children on the unit page
context = {
'container_view': False,
'is_unit_page': True
}
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content
self.assertNotIn(self.test_html_1, html)
@@ -66,7 +66,7 @@ class VerticalModuleTestCase(BaseVerticalModuleTest):
# Vertical should render reorderable children on the container page
reorderable_items = set()
context = {
'container_view': True,
'is_unit_page': False,
'reorderable_items': reorderable_items,
}
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content

View File

@@ -45,9 +45,13 @@ class VerticalModule(VerticalFields, XModule, StudioEditableModule):
Renders the Studio preview view, which supports drag and drop.
"""
fragment = Fragment()
root_xblock = context.get('root_xblock')
is_root = root_xblock and root_xblock.location == self.location
# For the container page we want the full drag-and-drop, but for unit pages we want
# a more concise version that appears alongside the "View =>" link.
if context.get('container_view'):
# a more concise version that appears alongside the "View =>" link-- unless it is
# the unit page and the vertical being rendered is itself the unit vertical (is_root == True).
if is_root or not context.get('is_unit_page'):
self.render_children(context, fragment, can_reorder=True, can_add=True)
return fragment

View File

@@ -15,7 +15,7 @@ class ContainerPage(PageObject):
"""
Container page in Studio
"""
NAME_SELECTOR = 'a.navigation-current'
NAME_SELECTOR = '.page-header-title'
def __init__(self, browser, locator):
super(ContainerPage, self).__init__(browser)
@@ -126,16 +126,8 @@ class ContainerPage(PageObject):
def edit(self):
"""
Clicks the "edit" button for the first component on the page.
Same as the implementation in unit.py, unit and component pages will be merging.
"""
self.q(css='.edit-button').first.click()
EmptyPromise(
lambda: self.q(css='.xblock-studio_view').present,
'Wait for the Studio editor to be present'
).fulfill()
return self
return _click_edit(self)
def add_missing_groups(self):
"""
@@ -164,7 +156,7 @@ class XBlockWrapper(PageObject):
"""
url = None
BODY_SELECTOR = '.studio-xblock-wrapper'
NAME_SELECTOR = '.header-details'
NAME_SELECTOR = '.xblock-display-name'
def __init__(self, browser, locator):
super(XBlockWrapper, self).__init__(browser)
@@ -210,3 +202,33 @@ class XBlockWrapper(PageObject):
@property
def preview_selector(self):
return self._bounded_selector('.xblock-student_view,.xblock-author_view')
def go_to_container(self):
"""
Open the container page linked to by this xblock, and return
an initialized :class:`.ContainerPage` for that xblock.
"""
return ContainerPage(self.browser, self.locator).visit()
def edit(self):
"""
Clicks the "edit" button for this xblock.
"""
return _click_edit(self, self._bounded_selector)
@property
def editor_selector(self):
return '.xblock-studio_view'
def _click_edit(page_object, bounded_selector=lambda(x): x):
"""
Click on the first edit button found and wait for the Studio editor to be present.
"""
page_object.q(css=bounded_selector('.edit-button')).first.click()
EmptyPromise(
lambda: page_object.q(css='.xblock-studio_view').present,
'Wait for the Studio editor to be present'
).fulfill()
return page_object

View File

@@ -5,7 +5,7 @@ from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise
from .course_page import CoursePage
from .unit import UnitPage
from .container import ContainerPage
class CourseOutlineContainer(object):
@@ -84,10 +84,10 @@ class CourseOutlineUnit(CourseOutlineChild):
def go_to(self):
"""
Open the unit page linked to by this unit link, and return
an initialized :class:`.UnitPage` for that unit.
Open the container page linked to by this unit link, and return
an initialized :class:`.ContainerPage` for that unit.
"""
return UnitPage(self.browser, self.locator).visit()
return ContainerPage(self.browser, self.locator).visit()
def is_browser_on_page(self):
return self.q(css=self.BODY_SELECTOR).present

View File

@@ -1,194 +0,0 @@
"""
Unit page in Studio
"""
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise, Promise
from . import BASE_URL
from .container import ContainerPage
class UnitPage(PageObject):
"""
Unit page in Studio
"""
NAME_SELECTOR = '#unit-display-name-input'
def __init__(self, browser, unit_locator):
super(UnitPage, self).__init__(browser)
self.unit_locator = unit_locator
@property
def url(self):
"""URL to the pages UI in a course."""
return "{}/unit/{}".format(BASE_URL, self.unit_locator)
def is_browser_on_page(self):
def _is_finished_loading():
# Wait until all components have been loaded
number_of_leaf_xblocks = len(self.q(css='{} .xblock-author_view,.xblock-student_view'.format(Component.BODY_SELECTOR)).results)
is_done = len(self.q(css=Component.BODY_SELECTOR).results) == number_of_leaf_xblocks
return (is_done, is_done)
# First make sure that an element with the view-unit class is present on the page,
# and then wait to make sure that the xblocks are all there
return (
self.q(css='body.view-unit').present and
Promise(_is_finished_loading, 'Finished rendering the xblocks in the unit.').fulfill()
)
@property
def name(self):
return self.q(css=self.NAME_SELECTOR).attrs('value')[0]
@property
def components(self):
"""
Return a list of components loaded on the unit page.
"""
return self.q(css=Component.BODY_SELECTOR).map(
lambda el: Component(self.browser, el.get_attribute('data-locator'))).results
def edit_draft(self):
"""
Started editing a draft of this unit.
"""
EmptyPromise(
lambda: self.q(css='.create-draft').present,
'Wait for edit draft link to be present'
).fulfill()
self.q(css='.create-draft').first.click()
EmptyPromise(
lambda: self.q(css='.editing-draft-alert').present,
'Wait for draft mode to be activated'
).fulfill()
def set_unit_visibility(self, visibility):
"""
Set unit visibility state
Arguments:
visibility (str): private or public
"""
self.q(css='select[name="visibility-select"] option[value="{}"]'.format(visibility)).first.click()
self.wait_for_ajax()
selector = '.edit-button'
if visibility == 'private':
check_func = lambda: self.q(css=selector).visible
elif visibility == 'public':
check_func = lambda: not self.q(css=selector).visible
EmptyPromise(check_func, 'Unit Visibility is {}'.format(visibility)).fulfill()
COMPONENT_BUTTONS = {
'advanced_tab': '.editor-tabs li.inner_tab_wrap:nth-child(2) > a',
'save_settings': '.action-save',
}
class Component(PageObject):
"""
A PageObject representing an XBlock child on the Studio UnitPage (including
the editing controls).
"""
url = None
BODY_SELECTOR = '.component'
NAME_SELECTOR = '.component-header'
def __init__(self, browser, locator):
super(Component, self).__init__(browser)
self.locator = locator
def is_browser_on_page(self):
return self.q(css='{}[data-locator="{}"]'.format(self.BODY_SELECTOR, self.locator)).present
def _bounded_selector(self, selector):
"""
Return `selector`, but limited to this particular `CourseOutlineChild` context
"""
return '{}[data-locator="{}"] {}'.format(
self.BODY_SELECTOR,
self.locator,
selector
)
@property
def name(self):
titles = self.q(css=self._bounded_selector(self.NAME_SELECTOR)).text
if titles:
return titles[0]
else:
return None
@property
def preview_selector(self):
return self._bounded_selector('.xblock-author_view,.xblock-student_view')
def edit(self):
"""
Clicks the "edit" button for the first component on the page.
Same as the implementation in unit.py, unit and component pages will be merging.
"""
self.q(css=self._bounded_selector('.edit-button')).first.click()
EmptyPromise(
lambda: self.q(css='.xblock-studio_view').present,
'Wait for the Studio editor to be present'
).fulfill()
return self
@property
def editor_selector(self):
return '.xblock-studio_view'
def go_to_container(self):
"""
Open the container page linked to by this component, and return
an initialized :class:`.ContainerPage` for that xblock.
"""
return ContainerPage(self.browser, self.locator).visit()
def _click_button(self, button_name):
"""
Click on a button as specified by `button_name`
Arguments:
button_name (str): button name
"""
self.q(css=COMPONENT_BUTTONS[button_name]).first.click()
self.wait_for_ajax()
def open_advanced_tab(self):
"""
Click on Advanced Tab.
"""
self._click_button('advanced_tab')
def save_settings(self):
"""
Click on settings Save button.
"""
self._click_button('save_settings')
def go_to_group_configuration_page(self):
"""
Go to the Group Configuration used by the component.
"""
self.q(css=self._bounded_selector('span.message-text a')).first.click()
@property
def group_configuration_link_name(self):
"""
Get Group Configuration name from link.
"""
return self.q(css=self._bounded_selector('span.message-text a')).first.text[0]

View File

@@ -73,7 +73,7 @@ class XBlockAcidBase(WebAppTest):
subsection = self.outline.section('Test Section').subsection('Test Subsection')
unit = subsection.toggle_expand().unit('Test Unit').go_to()
acid_block = AcidView(self.browser, unit.components[0].preview_selector)
acid_block = AcidView(self.browser, unit.xblocks[0].preview_selector)
self.validate_acid_block_preview(acid_block)
def test_acid_block_editor(self):
@@ -85,9 +85,7 @@ class XBlockAcidBase(WebAppTest):
subsection = self.outline.section('Test Section').subsection('Test Subsection')
unit = subsection.toggle_expand().unit('Test Unit').go_to()
unit.edit_draft()
acid_block = AcidView(self.browser, unit.components[0].edit().editor_selector)
acid_block = AcidView(self.browser, unit.xblocks[0].edit().editor_selector)
self.assertTrue(acid_block.init_fn_passed)
self.assertTrue(acid_block.resource_url_passed)
self.assertTrue(acid_block.scope_passed('content'))
@@ -141,15 +139,11 @@ class XBlockAcidParentBase(XBlockAcidBase):
self.outline.visit()
subsection = self.outline.section('Test Section').subsection('Test Subsection')
unit = subsection.toggle_expand().unit('Test Unit').go_to()
container = unit.components[0].go_to_container()
container = unit.xblocks[0].go_to_container()
acid_block = AcidView(self.browser, container.xblocks[0].preview_selector)
self.validate_acid_block_preview(acid_block)
@skip('This will fail until the container page supports editing')
def test_acid_block_editor(self):
super(XBlockAcidParentBase, self).test_acid_block_editor()
class XBlockAcidEmptyParentTest(XBlockAcidParentBase):
"""
@@ -212,7 +206,6 @@ class XBlockAcidChildTest(XBlockAcidParentBase):
self.user = course_fix.user
@skip('This will fail until we fix support of children in pure XBlocks')
def test_acid_block_preview(self):
super(XBlockAcidChildTest, self).test_acid_block_preview()

View File

@@ -34,17 +34,16 @@ class ContainerBase(StudioCourseTest):
self.course_info['run']
)
def go_to_container_page(self, make_draft=False):
def go_to_nested_container_page(self):
"""
Go to the test container page.
If make_draft is true, the unit page (accessed on way to container page) will be put into draft mode.
Go to the nested container page.
"""
unit = self.go_to_unit_page(make_draft)
container = unit.components[0].go_to_container()
unit = self.go_to_unit_page()
# The 0th entry is the unit page itself.
container = unit.xblocks[1].go_to_container()
return container
def go_to_unit_page(self, make_draft=False):
def go_to_unit_page(self):
"""
Go to the test unit page.
@@ -52,10 +51,7 @@ class ContainerBase(StudioCourseTest):
"""
self.outline.visit()
subsection = self.outline.section('Test Section').subsection('Test Subsection')
unit = subsection.toggle_expand().unit('Test Unit').go_to()
if make_draft:
unit.edit_draft()
return unit
return subsection.toggle_expand().unit('Test Unit').go_to()
def verify_ordering(self, container, expected_orderings):
"""
@@ -83,13 +79,13 @@ class ContainerBase(StudioCourseTest):
"""
Perform the supplied action and then verify the resulting ordering.
"""
container = self.go_to_container_page(make_draft=True)
container = self.go_to_nested_container_page()
action(container)
self.verify_ordering(container, expected_ordering)
# Reload the page to see that the change was persisted.
container = self.go_to_container_page()
container = self.go_to_nested_container_page()
self.verify_ordering(container, expected_ordering)
@@ -101,9 +97,9 @@ class NestedVerticalTest(ContainerBase):
Sets up a course structure with nested verticals.
"""
self.container_title = ""
self.group_a = "Expand or Collapse\nGroup A"
self.group_b = "Expand or Collapse\nGroup B"
self.group_empty = "Expand or Collapse\nGroup Empty"
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"
@@ -360,13 +356,13 @@ class EditContainerTest(NestedVerticalTest):
"""
Test the "edit" button on a container appearing on the unit page.
"""
unit = self.go_to_unit_page(make_draft=True)
component = unit.components[0]
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_container_page(make_draft=True)
container = self.go_to_nested_container_page()
self.modify_display_name_and_verify(container)

View File

@@ -57,7 +57,7 @@ class SplitTestMixin(object):
def verify_add_missing_groups_button_not_present(self, container):
"""
Checks that the "add missing gorups" button/link is not present.
Checks that the "add missing groups" button/link is not present.
"""
def missing_groups_button_not_present():
button_present = container.missing_groups_button_present()
@@ -105,9 +105,9 @@ class SplitTest(ContainerBase, SplitTestMixin):
Returns the container page.
"""
unit = self.go_to_unit_page(make_draft=True)
unit = self.go_to_unit_page()
add_advanced_component(unit, 0, 'split_test')
container = self.go_to_container_page()
container = self.go_to_nested_container_page()
container.edit()
component_editor = ComponentEditorView(self.browser, container.locator)
component_editor.set_select_value_and_save('Group Configuration', 'Configuration alpha,beta')
@@ -119,16 +119,16 @@ class SplitTest(ContainerBase, SplitTestMixin):
],
},
})
return self.go_to_container_page()
return self.go_to_nested_container_page()
def test_create_and_select_group_configuration(self):
"""
Tests creating a split test instance on the unit page, and then
assigning the group configuration.
"""
unit = self.go_to_unit_page(make_draft=True)
unit = self.go_to_unit_page()
add_advanced_component(unit, 0, 'split_test')
container = self.go_to_container_page()
container = self.go_to_nested_container_page()
container.edit()
component_editor = ComponentEditorView(self.browser, container.locator)
component_editor.set_select_value_and_save('Group Configuration', 'Configuration alpha,beta')
@@ -136,14 +136,14 @@ class SplitTest(ContainerBase, SplitTestMixin):
# Switch to the other group configuration. Must navigate again to the container page so
# that there is only a single "editor" on the page.
container = self.go_to_container_page()
container = self.go_to_nested_container_page()
container.edit()
component_editor = ComponentEditorView(self.browser, container.locator)
component_editor.set_select_value_and_save('Group Configuration', 'Configuration 0,1,2')
self.verify_groups(container, ['Group 0', 'Group 1', 'Group 2'], ['alpha', 'beta'])
# Reload the page to make sure the groups were persisted.
container = self.go_to_container_page()
container = self.go_to_nested_container_page()
self.verify_groups(container, ['Group 0', 'Group 1', 'Group 2'], ['alpha', 'beta'])
@skip("This fails periodically where it fails to trigger the add missing groups action.Dis")
@@ -161,7 +161,7 @@ class SplitTest(ContainerBase, SplitTestMixin):
self.verify_groups(container, ['alpha', 'gamma'], ['beta'])
# Reload the page to make sure the groups were persisted.
container = self.go_to_container_page()
container = self.go_to_nested_container_page()
self.verify_groups(container, ['alpha', 'gamma'], ['beta'])
@skip("Disabling as this fails intermittently. STUD-2003")