Merge pull request #4606 from edx/anton/remove-group-configurations

Add possibility to remove group configurations.
This commit is contained in:
Anton Stupak
2014-08-01 22:18:35 +03:00
18 changed files with 661 additions and 290 deletions

View File

@@ -1,6 +1,3 @@
$ ->
new TooltipManager
class @TooltipManager
constructor: () ->
@$body = $('body')
@@ -45,3 +42,8 @@ class @TooltipManager
hideTooltip: (e) =>
@$tooltip.hide().css('opacity', 0)
clearTimeout(@tooltipTimer)
# Move initialization at the bottom to make sure that TooltipManager is already
# assigned to the Global object.
$ ->
new TooltipManager

View File

@@ -499,7 +499,6 @@ class CourseFixture(StudioApiFixture):
try:
loc = response.json().get('locator')
xblock_desc.locator = loc
except ValueError:
raise CourseFixtureError("Could not decode JSON from '{0}'".format(response.content))

View File

@@ -3,6 +3,7 @@ Course Group Configurations page.
"""
from .course_page import CoursePage
from .utils import confirm_prompt
class GroupConfigurationsPage(CoursePage):
@@ -53,15 +54,13 @@ class GroupConfiguration(object):
"""
Expand/collapse group configuration.
"""
css = 'a.group-toggle'
self.find_css(css).first.click()
self.find_css('a.group-toggle').first.click()
def add_group(self):
"""
Add new group.
"""
css = 'button.action-add-group'
self.find_css(css).first.click()
self.find_css('button.action-add-group').first.click()
def get_text(self, css):
"""
@@ -73,37 +72,47 @@ class GroupConfiguration(object):
"""
Click on the `Course Outline` link.
"""
css = 'p.group-configuration-usage-text a'
self.find_css(css).first.click()
self.find_css('p.group-configuration-usage-text a').first.click()
def click_unit_anchor(self, index=0):
"""
Click on the link to the unit.
"""
css = 'li.group-configuration-usage-unit a'
self.find_css(css).nth(index).click()
self.find_css('li.group-configuration-usage-unit a').nth(index).click()
def edit(self):
"""
Open editing view for the group configuration.
"""
css = '.action-edit .edit'
self.find_css(css).first.click()
self.find_css('.action-edit .edit').first.click()
@property
def delete_button_is_disabled(self):
return self.find_css('.actions .delete.is-disabled').present
@property
def delete_button_is_absent(self):
return not self.find_css('.actions .delete').present
def delete(self):
"""
Delete the group configuration.
"""
self.find_css('.actions .delete').first.click()
confirm_prompt(self.page)
def save(self):
"""
Save group configuration.
"""
css = '.action-primary'
self.find_css(css).first.click()
self.find_css('.action-primary').first.click()
self.page.wait_for_ajax()
def cancel(self):
"""
Cancel group configuration.
"""
css = '.action-secondary'
self.find_css(css).first.click()
self.find_css('.action-secondary').first.click()
@property
def mode(self):
@@ -149,8 +158,7 @@ class GroupConfiguration(object):
"""
Set group configuration name.
"""
css = '.group-configuration-name-input'
self.find_css(css).first.fill(value)
self.find_css('.group-configuration-name-input').first.fill(value)
@property
def description(self):
@@ -164,20 +172,24 @@ class GroupConfiguration(object):
"""
Set group configuration description.
"""
css = '.group-configuration-description-input'
self.find_css(css).first.fill(value)
self.find_css('.group-configuration-description-input').first.fill(value)
@property
def groups(self):
"""
Return list of groups.
"""
css = '.group'
def group_selector(group_index):
return self.get_selector('.group-{} '.format(group_index))
return [Group(self.page, group_selector(index)) for index, element in enumerate(self.find_css(css))]
return [Group(self.page, group_selector(index)) for index, element in enumerate(self.find_css('.group'))]
@property
def delete_note(self):
"""
Return delete note for the group configuration.
"""
return self.find_css('.wrapper-delete-button').first.attrs('data-tooltip')[0]
def __repr__(self):
return "<{}:{}>".format(self.__class__.__name__, self.name)

View File

@@ -111,3 +111,14 @@ def get_codemirror_value(page, index=0, find_prefix="$"):
return {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror.getValue();
""".format(index=index, find_prefix=find_prefix)
)
def confirm_prompt(page, cancel=False):
"""
Ensures that a modal prompt and confirmation button are visible, then clicks the button. The prompt is canceled iff
cancel is True.
"""
page.wait_for_element_visibility('.prompt', 'Prompt is visible')
confirmation_button_css = '.prompt .action-' + ('secondary' if cancel else 'primary')
page.wait_for_element_visibility(confirmation_button_css, 'Confirmation button is visible')
click_css(page, confirmation_button_css, require_notification=(not cancel))

View File

@@ -375,6 +375,7 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
# Save the configuration
self.assertEqual(config.get_text('.action-primary'), "CREATE")
self.assertTrue(config.delete_button_is_absent)
config.save()
self._assert_fields(
@@ -652,6 +653,7 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
usage = config.usages[0]
config.click_unit_anchor()
unit = UnitPage(self.browser, vertical.locator)
# Waiting for the page load and verify that we've landed on the unit page
EmptyPromise(
lambda: unit.is_browser_on_page(), "loaded page {!r}".format(unit),
@@ -659,3 +661,71 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
).fulfill()
self.assertIn(unit.name, usage)
def test_can_delete_unused_group_configuration(self):
"""
Scenario: Ensure that the user can delete unused group configuration.
Given I have a course with 2 group configurations
And I go to the Group Configuration page
When I delete the Group Configuration with name "Configuration 1"
Then I see that there is one Group Configuration
When I edit the Group Configuration with name "Configuration 2"
And I delete the Group Configuration with name "Configuration 2"
Then I see that the are no Group Configurations
"""
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
UserPartition(0, 'Configuration 1', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')]).to_json(),
UserPartition(1, 'Configuration 2', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]).to_json()
],
},
})
self.page.visit()
self.assertEqual(len(self.page.group_configurations), 2)
config = self.page.group_configurations[1]
# Delete first group configuration via detail view
config.delete()
self.assertEqual(len(self.page.group_configurations), 1)
config = self.page.group_configurations[0]
config.edit()
self.assertFalse(config.delete_button_is_disabled)
# Delete first group configuration via edit view
config.delete()
self.assertEqual(len(self.page.group_configurations), 0)
def test_cannot_delete_used_group_configuration(self):
"""
Scenario: Ensure that the user cannot delete unused group configuration.
Given I have a course with group configuration that is used in the Content Experiment
When I go to the Group Configuration page
Then I do not see delete button and I see a note about that
When I edit the Group Configuration
Then I do not see delete button and I see the note about that
"""
# Create a new group configurations
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
UserPartition(0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")]).to_json()
],
},
})
vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0]
self.course_fixture.create_xblock(
vertical.locator,
XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0})
)
# Go to the Group Configuration Page and click unit anchor
self.page.visit()
config = self.page.group_configurations[0]
self.assertTrue(config.delete_button_is_disabled)
self.assertIn('Cannot delete when in use by an experiment', config.delete_note)
config.edit()
self.assertTrue(config.delete_button_is_disabled)
self.assertIn('Cannot delete when in use by an experiment', config.delete_note)