Merge pull request #15037 from edx/christina/ed11-simplification

Feature branch: dividing discussions by enrollment tracks
This commit is contained in:
Albert (AJ) St. Aubin
2017-06-07 13:22:13 -04:00
committed by GitHub
79 changed files with 3371 additions and 2013 deletions

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import openedx.core.djangoapps.xmodule_django.models
class Migration(migrations.Migration):
dependencies = [
('django_comment_common', '0004_auto_20161117_1209'),
]
operations = [
migrations.CreateModel(
name='CourseDiscussionSettings',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('course_id', openedx.core.djangoapps.xmodule_django.models.CourseKeyField(help_text=b'Which course are these settings associated with?', unique=True, max_length=255, db_index=True)),
('always_divide_inline_discussions', models.BooleanField(default=False)),
('_divided_discussions', models.TextField(null=True, db_column=b'divided_discussions', blank=True)),
('division_scheme', models.CharField(default=b'none', max_length=20, choices=[(b'none', b'None'), (b'cohort', b'Cohort'), (b'enrollment_track', b'Enrollment Track')])),
],
),
]

View File

@@ -1,3 +1,4 @@
import json
import logging
from config_models.models import ConfigurationModel
@@ -162,3 +163,30 @@ class ForumsConfig(ConfigurationModel):
def __unicode__(self):
"""Simple representation so the admin screen looks less ugly."""
return u"ForumsConfig: timeout={}".format(self.connection_timeout)
class CourseDiscussionSettings(models.Model):
course_id = CourseKeyField(
unique=True,
max_length=255,
db_index=True,
help_text="Which course are these settings associated with?",
)
always_divide_inline_discussions = models.BooleanField(default=False)
_divided_discussions = models.TextField(db_column='divided_discussions', null=True, blank=True) # JSON list
COHORT = 'cohort'
ENROLLMENT_TRACK = 'enrollment_track'
NONE = 'none'
ASSIGNMENT_TYPE_CHOICES = ((NONE, 'None'), (COHORT, 'Cohort'), (ENROLLMENT_TRACK, 'Enrollment Track'))
division_scheme = models.CharField(max_length=20, choices=ASSIGNMENT_TYPE_CHOICES, default=NONE)
@property
def divided_discussions(self):
"""Jsonify the divided_discussions"""
return json.loads(self._divided_discussions)
@divided_discussions.setter
def divided_discussions(self, value):
"""Un-Jsonify the divided_discussions"""
self._divided_discussions = json.dumps(value)

View File

@@ -1,10 +1,19 @@
from nose.plugins.attrib import attr
from django.test import TestCase
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from django_comment_common.models import Role
from models import CourseDiscussionSettings
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from openedx.core.djangoapps.course_groups.cohorts import CourseCohortsSettings
from student.models import CourseEnrollment, User
from utils import get_course_discussion_settings, set_course_discussion_settings
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@attr(shard=1)
class RoleAssignmentTest(TestCase):
"""
Basic checks to make sure our Roles get assigned and unassigned as students
@@ -55,3 +64,73 @@ class RoleAssignmentTest(TestCase):
# )
# self.assertNotIn(student_role, self.student_user.roles.all())
# self.assertIn(student_role, another_student.roles.all())
@attr(shard=1)
class CourseDiscussionSettingsTest(ModuleStoreTestCase):
def setUp(self):
super(CourseDiscussionSettingsTest, self).setUp()
self.course = CourseFactory.create()
def test_get_course_discussion_settings(self):
discussion_settings = get_course_discussion_settings(self.course.id)
self.assertEqual(CourseDiscussionSettings.NONE, discussion_settings.division_scheme)
self.assertEqual([], discussion_settings.divided_discussions)
self.assertFalse(discussion_settings.always_divide_inline_discussions)
def test_get_course_discussion_settings_legacy_settings(self):
self.course.cohort_config = {
'cohorted': True,
'always_cohort_inline_discussions': True,
'cohorted_discussions': ['foo']
}
modulestore().update_item(self.course, ModuleStoreEnum.UserID.system)
discussion_settings = get_course_discussion_settings(self.course.id)
self.assertEqual(CourseDiscussionSettings.COHORT, discussion_settings.division_scheme)
self.assertEqual(['foo'], discussion_settings.divided_discussions)
self.assertTrue(discussion_settings.always_divide_inline_discussions)
def test_get_course_discussion_settings_cohort_settings(self):
CourseCohortsSettings.objects.get_or_create(
course_id=self.course.id,
defaults={
'is_cohorted': True,
'always_cohort_inline_discussions': True,
'cohorted_discussions': ['foo', 'bar']
}
)
discussion_settings = get_course_discussion_settings(self.course.id)
self.assertEqual(CourseDiscussionSettings.COHORT, discussion_settings.division_scheme)
self.assertEqual(['foo', 'bar'], discussion_settings.divided_discussions)
self.assertTrue(discussion_settings.always_divide_inline_discussions)
def test_set_course_discussion_settings(self):
set_course_discussion_settings(
course_key=self.course.id,
divided_discussions=['cohorted_topic'],
division_scheme=CourseDiscussionSettings.ENROLLMENT_TRACK,
always_divide_inline_discussions=True,
)
discussion_settings = get_course_discussion_settings(self.course.id)
self.assertEqual(CourseDiscussionSettings.ENROLLMENT_TRACK, discussion_settings.division_scheme)
self.assertEqual(['cohorted_topic'], discussion_settings.divided_discussions)
self.assertTrue(discussion_settings.always_divide_inline_discussions)
def test_invalid_data_types(self):
exception_msg_template = "Incorrect field type for `{}`. Type must be `{}`"
fields = [
{'name': 'division_scheme', 'type': basestring},
{'name': 'always_divide_inline_discussions', 'type': bool},
{'name': 'divided_discussions', 'type': list}
]
invalid_value = 3.14
for field in fields:
with self.assertRaises(ValueError) as value_error:
set_course_discussion_settings(self.course.id, **{field['name']: invalid_value})
self.assertEqual(
value_error.exception.message,
exception_msg_template.format(field['name'], field['type'].__name__)
)

View File

@@ -9,6 +9,10 @@ from django_comment_common.models import (
FORUM_ROLE_STUDENT,
Role
)
from openedx.core.djangoapps.course_groups.cohorts import get_legacy_discussion_settings
from request_cache.middleware import request_cached
from .models import CourseDiscussionSettings
class ThreadContext(object):
@@ -91,3 +95,48 @@ def are_permissions_roles_seeded(course_id):
return False
return True
@request_cached
def get_course_discussion_settings(course_key):
try:
course_discussion_settings = CourseDiscussionSettings.objects.get(course_id=course_key)
except CourseDiscussionSettings.DoesNotExist:
legacy_discussion_settings = get_legacy_discussion_settings(course_key)
course_discussion_settings, _ = CourseDiscussionSettings.objects.get_or_create(
course_id=course_key,
defaults={
'always_divide_inline_discussions': legacy_discussion_settings['always_cohort_inline_discussions'],
'divided_discussions': legacy_discussion_settings['cohorted_discussions'],
'division_scheme': CourseDiscussionSettings.COHORT if legacy_discussion_settings['is_cohorted']
else CourseDiscussionSettings.NONE
}
)
return course_discussion_settings
def set_course_discussion_settings(course_key, **kwargs):
"""
Set discussion settings for a course.
Arguments:
course_key: CourseKey
always_divide_inline_discussions (bool): If inline discussions should always be divided.
divided_discussions (list): List of discussion ids.
division_scheme (str): `CourseDiscussionSettings.NONE`, `CourseDiscussionSettings.COHORT`,
or `CourseDiscussionSettings.ENROLLMENT_TRACK`
Returns:
A CourseDiscussionSettings object.
"""
fields = {'division_scheme': basestring, 'always_divide_inline_discussions': bool, 'divided_discussions': list}
course_discussion_settings = get_course_discussion_settings(course_key)
for field, field_type in fields.items():
if field in kwargs:
if not isinstance(kwargs[field], field_type):
raise ValueError("Incorrect field type for `{}`. Type must be `{}`".format(field, field_type.__name__))
setattr(course_discussion_settings, field, kwargs[field])
course_discussion_settings.save()
return course_discussion_settings

View File

@@ -133,7 +133,7 @@ class PartitionService(object):
if self._cache and (cache_key in self._cache):
return self._cache[cache_key]
user_partition = self._get_user_partition(user_partition_id)
user_partition = self.get_user_partition(user_partition_id)
if user_partition is None:
raise ValueError(
"Configuration problem! No user_partition with id {0} "
@@ -148,7 +148,7 @@ class PartitionService(object):
return group_id
def _get_user_partition(self, user_partition_id):
def get_user_partition(self, user_partition_id):
"""
Look for a user partition with a matching id in the course's partitions.
Note that this method can return an inactive user partition.

View File

@@ -32,8 +32,8 @@
this.retrieveFollowed = function() {
return DiscussionThreadListView.prototype.retrieveFollowed.apply(self, arguments);
};
this.chooseCohort = function() {
return DiscussionThreadListView.prototype.chooseCohort.apply(self, arguments);
this.chooseGroup = function() {
return DiscussionThreadListView.prototype.chooseGroup.apply(self, arguments);
};
this.chooseFilter = function() {
return DiscussionThreadListView.prototype.chooseFilter.apply(self, arguments);
@@ -85,7 +85,7 @@
'click .forum-nav-thread-link': 'threadSelected',
'click .forum-nav-load-more-link': 'loadMorePages',
'change .forum-nav-filter-main-control': 'chooseFilter',
'change .forum-nav-filter-cohort-control': 'chooseCohort'
'change .forum-nav-filter-cohort-control': 'chooseGroup'
};
DiscussionThreadListView.prototype.initialize = function(options) {
@@ -194,7 +194,7 @@
edx.HtmlUtils.append(
this.$el,
this.template({
isCohorted: this.courseSettings.get('is_cohorted'),
isDiscussionDivisionEnabled: this.courseSettings.get('is_discussion_division_enabled'),
isPrivilegedUser: DiscussionUtil.isPrivilegedUser()
})
);
@@ -404,7 +404,7 @@
return $(elem).data('discussion-id');
}).get();
this.retrieveDiscussions(discussionIds);
return this.$('.forum-nav-filter-cohort').toggle($item.data('cohorted') === true);
return this.$('.forum-nav-filter-cohort').toggle($item.data('divided') === true);
}
};
@@ -413,7 +413,7 @@
return this.retrieveFirstPage();
};
DiscussionThreadListView.prototype.chooseCohort = function() {
DiscussionThreadListView.prototype.chooseGroup = function() {
this.group_id = this.$('.forum-nav-filter-cohort-control :selected').val();
return this.retrieveFirstPage();
};

View File

@@ -35,7 +35,7 @@
'[data-discussion-id="' + this.getCurrentTopicId() + '"]'
));
} else if ($general.length > 0) {
this.setTopic($general);
this.setTopic($general.first());
} else {
this.setTopic(this.$('.post-topic option').first());
}

View File

@@ -51,7 +51,7 @@
threadTypeTemplate;
context = _.clone(this.course_settings.attributes);
_.extend(context, {
cohort_options: this.getCohortOptions(),
group_options: this.getGroupOptions(),
is_commentable_divided: this.is_commentable_divided,
mode: this.mode,
startHeader: this.startHeader,
@@ -84,15 +84,15 @@
return this.mode === 'tab';
};
NewPostView.prototype.getCohortOptions = function() {
NewPostView.prototype.getGroupOptions = function() {
var userGroupId;
if (this.course_settings.get('is_cohorted') && DiscussionUtil.isPrivilegedUser()) {
if (this.course_settings.get('is_discussion_division_enabled') && DiscussionUtil.isPrivilegedUser()) {
userGroupId = $('#discussion-container').data('user-group-id');
return _.map(this.course_settings.get('cohorts'), function(cohort) {
return _.map(this.course_settings.get('groups'), function(group) {
return {
value: cohort.id,
text: cohort.name,
selected: cohort.id === userGroupId
value: group.id,
text: group.name,
selected: group.id === userGroupId
};
});
} else {
@@ -112,7 +112,7 @@
};
NewPostView.prototype.toggleGroupDropdown = function($target) {
if ($target.data('cohorted')) {
if ($target.data('divided')) {
$('.js-group-select').prop('disabled', false);
return $('.group-selector-wrapper').removeClass('disabled');
} else {

View File

@@ -62,7 +62,7 @@
' <li' +
' class="forum-nav-browse-menu-item"' +
' data-discussion-id="child"' +
' data-cohorted="false"' +
' data-divided="false"' +
' >' +
' <a href="#" class="forum-nav-browse-title">Child</a>' +
' </li>' +
@@ -70,7 +70,7 @@
' <li' +
' class="forum-nav-browse-menu-item"' +
' data-discussion-id="sibling"' +
' data-cohorted="false"' +
' data-divided="false"' +
' >' +
' <a href="#" class="forum-nav-browse-title">Sibling</a>' +
' </li>' +
@@ -79,7 +79,7 @@
' <li' +
' class="forum-nav-browse-menu-item"' +
' data-discussion-id="other"' +
' data-cohorted="true"' +
' data-divided="true"' +
' >' +
' <a href="#" class="forum-nav-browse-title">Other Category</a>' +
' </li>' +
@@ -95,11 +95,11 @@
' <option value="flagged">Flagged</option>' +
' </select>' +
' </label>' +
' <% if (isCohorted && isPrivilegedUser) { %>' +
' <% if (isDiscussionDivisionEnabled && isPrivilegedUser) { %>' +
' <label class="forum-nav-filter-cohort">' +
' <span class="sr">Cohort:</span>' +
' <span class="sr">Group:</span>' +
' <select class="forum-nav-filter-cohort-control">' +
' <option value="">in all cohorts</option>' +
' <option value="">in all groups</option>' +
' <option value="1">Cohort1</option>' +
' <option value="2">Cohort2</option>' +
' </select>' +
@@ -164,7 +164,7 @@
collection: this.discussion,
el: $('#fixture-element'),
courseSettings: new DiscussionCourseSettings({
is_cohorted: true
is_discussion_division_enabled: true
})
});
return this.view.render();
@@ -199,7 +199,7 @@
collection: discussion,
showThreadPreview: true,
courseSettings: new DiscussionCourseSettings({
is_cohorted: true
is_discussion_division_enabled: true
})
},
props
@@ -233,7 +233,7 @@
});
});
describe('cohort selector', function() {
describe('group selector', function() {
it('should not be visible to students', function() {
return expect(this.view.$('.forum-nav-filter-cohort-control:visible')).not.toExist();
});

View File

@@ -31,7 +31,7 @@
return expect(group_disabled).toEqual(true);
}
};
describe('cohort selector', function() {
describe('group selector', function() {
beforeEach(function() {
this.course_settings = new DiscussionCourseSettings({
category_map: {
@@ -53,8 +53,8 @@
},
allow_anonymous: false,
allow_anonymous_to_peers: false,
is_cohorted: true,
cohorts: [
is_discussion_division_enabled: true,
groups: [
{
id: 1,
name: 'Cohort1'
@@ -75,15 +75,15 @@
it('is not visible to students', function() {
return checkVisibility(this.view, false, false, true);
});
it('allows TAs to see the cohort selector when the topic is cohorted', function() {
it('allows TAs to see the group selector when the topic is divided', function() {
DiscussionSpecHelper.makeTA();
return checkVisibility(this.view, true, false, true);
});
it('allows moderators to see the cohort selector when the topic is cohorted', function() {
it('allows moderators to see the group selector when the topic is divided', function() {
DiscussionSpecHelper.makeModerator();
return checkVisibility(this.view, true, false, true);
});
it('only enables the cohort selector when applicable', function() {
it('only enables the group selector when applicable', function() {
DiscussionSpecHelper.makeModerator();
checkVisibility(this.view, true, false, true);
@@ -95,7 +95,7 @@
$('.post-topic').trigger('change');
return checkVisibility(this.view, true, false, false);
});
it('allows the user to make a cohort selection', function() {
it('allows the user to make a group selection', function() {
var expectedGroupId,
self = this;
DiscussionSpecHelper.makeModerator();
@@ -116,23 +116,23 @@
});
});
});
describe('always cohort inline discussions ', function() {
describe('always divide inline discussions ', function() {
beforeEach(function() {
this.course_settings = new DiscussionCourseSettings({
'category_map': {
'children': [],
'entries': {}
category_map: {
children: [],
entries: {}
},
'allow_anonymous': false,
'allow_anonymous_to_peers': false,
'is_cohorted': true,
'cohorts': [
allow_anonymous: false,
allow_anonymous_to_peers: false,
is_discussion_division_enabled: true,
groups: [
{
'id': 1,
'name': 'Cohort1'
id: 1,
name: 'Cohort1'
}, {
'id': 2,
'name': 'Cohort2'
id: 2,
name: 'Cohort2'
}
]
});
@@ -143,12 +143,12 @@
mode: 'tab'
});
});
it('disables the cohort menu if it is set false', function() {
it('disables the group menu if it is set false', function() {
DiscussionSpecHelper.makeModerator();
this.view.is_commentable_divided = false;
return checkVisibility(this.view, true, true, true);
});
it('enables the cohort menu if it is set true', function() {
it('enables the group menu if it is set true', function() {
DiscussionSpecHelper.makeModerator();
this.view.is_commentable_divided = true;
return checkVisibility(this.view, true, false, true);

View File

@@ -67,7 +67,7 @@
}
}
},
is_cohorted: true,
is_discussion_division_enabled: true,
allow_anonymous: false,
allow_anonymous_to_peers: false
},
@@ -170,7 +170,7 @@
' <li' +
' class="forum-nav-browse-menu-item"' +
' data-discussion-id="child"' +
' data-cohorted="false"' +
' data-divided="false"' +
' >' +
' <a href="#" class="forum-nav-browse-title">Child</a>' +
' </li>' +
@@ -178,7 +178,7 @@
' <li' +
' class="forum-nav-browse-menu-item"' +
' data-discussion-id="sibling"' +
' data-cohorted="false"' +
' data-divided="false"' +
' >' +
' <a href="#" class="forum-nav-browse-title">Sibling</a>' +
' </li>' +
@@ -187,7 +187,7 @@
' <li' +
' class="forum-nav-browse-menu-item"' +
' data-discussion-id="other"' +
' data-cohorted="true"' +
' data-divided="true"' +
' >' +
' <a href="#" class="forum-nav-browse-title">Other Category</a>' +
' </li>' +
@@ -203,11 +203,11 @@
' <option value="flagged">Flagged</option>' +
' </select>' +
' </label>' +
' <% if (isCohorted && isPrivilegedUser) { %>' +
' <% if (isDiscussionDivisionEnabled && isPrivilegedUser) { %>' +
' <label class="forum-nav-filter-cohort">' +
' <span class="sr">Cohort:</span>' +
' <span class="sr">Group:</span>' +
' <select class="forum-nav-filter-cohort-control">' +
' <option value="">in all cohorts</option>' +
' <option value="">in all groups</option>' +
' <option value="1">Cohort1</option>' +
' <option value="2">Cohort2</option>' +
' </select>' +

View File

@@ -1 +1 @@
<option class="topic-title" data-discussion-id="<%- id %>" data-cohorted="<%- is_divided %>"><%- text %></option>
<option class="topic-title" data-discussion-id="<%- id %>" data-divided="<%- is_divided %>"><%- text %></option>

View File

@@ -9,7 +9,7 @@
<% } %>
<ul class="post-errors" style="display: none"></ul>
<div class="forum-new-post-form-wrapper"></div>
<% if (cohort_options) { %>
<% if (group_options) { %>
<div class="post-field group-selector-wrapper <% if (!is_commentable_divided) { print('disabled'); } %>">
<label class="field-label">
<span class="field-label-text">
@@ -17,12 +17,12 @@
<%- gettext("Visible to") %>
</span>
<div class="field-help" id="field_help_visible_to">
<%- gettext("Discussion admins, moderators, and TAs can make their posts visible to all students or specify a single cohort.") %>
<%- gettext("Discussion admins, moderators, and TAs can make their posts visible to all students or specify a single group.") %>
</div>
<div class="field-input">
<select aria-describedby="field_help_visible_to" class="post-topic field-input js-group-select" name="group_id" <% if (!is_commentable_divided) { print("disabled"); } %>>
<option value=""><%- gettext("All Groups") %></option>
<% _.each(cohort_options, function(opt) { %>
<% _.each(group_options, function(opt) { %>
<option value="<%- opt.value %>" <% if (opt.selected) { print("selected"); } %>><%- opt.text %></option>
<% }); %>
</select>

View File

@@ -47,6 +47,21 @@ class InstructorDashboardPage(CoursePage):
cohort_management_section.wait_for_page()
return cohort_management_section
def select_discussion_management(self):
"""
Selects the Discussion tab and returns the DiscussionmanagementSection
"""
self.q(css='[data-section="discussions_management"').first.click()
discussion_management_section = DiscussionManagementSection(self.browser)
discussion_management_section.wait_for_page()
return discussion_management_section
def is_discussion_management_visible(self):
"""
Is the Discussion tab visible
"""
return self.q(css='[data-section="discussions_management"').visible
def select_data_download(self):
"""
Selects the data download tab and returns a DataDownloadPage.
@@ -254,10 +269,6 @@ class CohortManagementSection(PageObject):
no_content_group_button_css = '.cohort-management-details-association-course input.radio-no'
select_content_group_button_css = '.cohort-management-details-association-course input.radio-yes'
assignment_type_buttons_css = '.cohort-management-assignment-type-settings input'
discussion_form_selectors = {
'course-wide': '.cohort-course-wide-discussions-form',
'inline': '.cohort-inline-discussions-form'
}
def get_cohort_help_element_and_click_help(self):
"""
@@ -666,61 +677,36 @@ class CohortManagementSection(PageObject):
self.q(css=self._bounded_selector('.cohorts-state')).first.click()
self.wait_for_ajax()
def toggles_showing_of_discussion_topics(self):
def cohort_management_controls_visible(self):
"""
Shows the discussion topics.
Return the visibility status of cohort management controls(cohort selector section etc).
"""
self.q(css=self._bounded_selector(".toggle-cohort-management-discussions")).first.click()
self.wait_for_element_visibility("#cohort-discussions-management", "Waiting for discussions to appear")
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
def discussion_topics_visible(self):
"""
Returns the visibility status of cohort discussion controls.
"""
EmptyPromise(
lambda: self.q(css=self._bounded_selector('.cohort-discussions-nav')).results != 0,
"Waiting for discussion section to show"
).fulfill()
return (self.q(css=self._bounded_selector('.cohort-course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.cohort-inline-discussions-nav')).visible)
class DiscussionManagementSection(PageObject):
def select_discussion_topic(self, key):
"""
Selects discussion topic checkbox by clicking on it.
"""
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
url = None
def select_always_inline_discussion(self):
"""
Selects the always_cohort_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
discussion_form_selectors = {
'course-wide': '.cohort-course-wide-discussions-form',
'inline': '.cohort-inline-discussions-form',
'scheme': '.division-scheme-container',
}
def always_inline_discussion_selected(self):
"""
Returns true if always_cohort_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
NOT_DIVIDED_SCHEME = "none"
COHORT_SCHEME = "cohort"
ENROLLMENT_TRACK_SCHEME = "enrollment_track"
def cohort_some_inline_discussion_selected(self):
"""
Returns true if some_cohort_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
def is_browser_on_page(self):
return self.q(css=self.discussion_form_selectors['course-wide']).present
def select_cohort_some_inline_discussion(self):
def _bounded_selector(self, selector):
"""
Selects the cohort_some_inline_discussions radio button.
Return `selector`, but limited to the divided discussion management context.
"""
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
def inline_discussion_topics_disabled(self):
"""
Returns the status of inline discussion topics, enabled or disabled.
"""
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
return '.discussions-management {}'.format(selector)
def is_save_button_disabled(self, key):
"""
@@ -730,18 +716,36 @@ class CohortManagementSection(PageObject):
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
return disabled[0] == 'true'
def is_category_selected(self):
def discussion_topics_visible(self):
"""
Returns the status for category checkboxes.
Returns the visibility status of divide discussion controls.
"""
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
return (self.q(css=self._bounded_selector('.course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.inline-discussions-nav')).visible)
def get_cohorted_topics_count(self, key):
def divided_discussion_heading_is_visible(self, key):
"""
Returns the count for cohorted topics.
Returns the text of discussion topic headings if it exists, otherwise return False.
"""
cohorted_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
return len(cohorted_topics.results)
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def select_always_inline_discussion(self):
"""
Selects the always_divide_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
def inline_discussion_topics_disabled(self):
"""
Returns the status of inline discussion topics, enabled or disabled.
"""
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
def save_discussion_topics(self, key):
"""
@@ -750,7 +754,38 @@ class CohortManagementSection(PageObject):
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
self.q(css=self._bounded_selector(save_button_css)).first.click()
def get_cohort_discussions_message(self, key, msg_type="confirmation"):
def always_inline_discussion_selected(self):
"""
Returns true if always_divide_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
def divide_some_inline_discussion_selected(self):
"""
Returns true if divide_some_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
def select_divide_some_inline_discussion(self):
"""
Selects the divide_some_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
def get_divided_topics_count(self, key):
"""
Returns the count for divided topics.
"""
divided_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
return len(divided_topics.results)
def select_discussion_topic(self, key):
"""
Selects discussion topic checkbox by clicking on it.
"""
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
def get_divide_discussions_message(self, key, msg_type="confirmation"):
"""
Returns the message related to modifying discussion topics.
"""
@@ -767,23 +802,30 @@ class CohortManagementSection(PageObject):
return ''
return message_title.first.text[0]
def cohort_discussion_heading_is_visible(self, key):
def is_category_selected(self):
"""
Returns the visibility of discussion topic headings.
Returns the status for category checkboxes.
"""
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def get_selected_scheme(self):
"""
Returns the ID of the selected discussion division scheme
("NOT_DIVIDED_SCHEME", "COHORT_SCHEME", or "ENROLLMENT_TRACK_SCHEME)".
"""
return self.q(css=self._bounded_selector('.division-scheme:checked')).first.attrs('value')[0]
def cohort_management_controls_visible(self):
def select_division_scheme(self, scheme):
"""
Return the visibility status of cohort management controls(cohort selector section etc).
Selects the radio button associated with the specified division scheme.
"""
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
self.q(css=self._bounded_selector("input.%s" % scheme)).first.click()
def division_scheme_visible(self, scheme):
"""
Returns whether or not the specified scheme is visible as an option.
"""
return self.q(css=self._bounded_selector("input.%s" % scheme)).visible
class MembershipPageAutoEnrollSection(PageObject):

View File

@@ -76,17 +76,26 @@ class CohortTestMixin(object):
def enable_cohorting(self, course_fixture):
"""
enables cohorting for the current course fixture.
Enables cohorting for the specified course fixture.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
data = json.dumps({'always_cohort_inline_discussions': True})
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings'
data = json.dumps({'is_cohorted': True})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to enable cohorts")
def enable_always_divide_inline_discussions(self, course_fixture):
"""
Enables "always_divide_inline_discussions" (but does not enabling cohorting).
"""
discussions_url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/discussions/settings'
discussions_data = json.dumps({'always_divide_inline_discussions': True})
course_fixture.session.patch(discussions_url, data=discussions_data, headers=course_fixture.headers)
def disable_cohorting(self, course_fixture):
"""
Disables cohorting for the current course fixture.
Disables cohorting for the specified course fixture.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings'
data = json.dumps({'is_cohorted': False})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to disable cohorts")

View File

@@ -693,267 +693,6 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
self.cohort_management_page.a11y_audit.check_for_accessibility_errors()
@attr(shard=6)
class CohortDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
"""
Tests for cohorting the inline and course-wide discussion topics.
"""
def setUp(self):
"""
Set up a discussion topics
"""
super(CohortDiscussionTopicsTest, self).setUp()
self.discussion_id = "test_discussion_{}".format(uuid.uuid4().hex)
self.course_fixture = CourseFixture(**self.course_info).add_children(
XBlockFixtureDesc("chapter", "Test Section").add_children(
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
XBlockFixtureDesc("vertical", "Test Unit").add_children(
XBlockFixtureDesc(
"discussion",
"Test Discussion",
metadata={"discussion_id": self.discussion_id}
)
)
)
)
).install()
# create course with single cohort and two content groups (user_partition of type "cohort")
self.cohort_name = "OnlyCohort"
self.setup_cohort_config(self.course_fixture)
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
# login as an instructor
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
self.instructor_dashboard_page.visit()
self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management()
self.cohort_management_page.wait_for_page()
self.course_wide_key = 'course-wide'
self.inline_key = 'inline'
def cohort_discussion_topics_are_visible(self):
"""
Assert that discussion topics are visible with appropriate content.
"""
self.cohort_management_page.toggles_showing_of_discussion_topics()
self.assertTrue(self.cohort_management_page.discussion_topics_visible())
self.assertEqual(
"Course-Wide Discussion Topics",
self.cohort_management_page.cohort_discussion_heading_is_visible(self.course_wide_key)
)
self.assertTrue(self.cohort_management_page.is_save_button_disabled(self.course_wide_key))
self.assertEqual(
"Content-Specific Discussion Topics",
self.cohort_management_page.cohort_discussion_heading_is_visible(self.inline_key)
)
self.assertTrue(self.cohort_management_page.is_save_button_disabled(self.inline_key))
def save_and_verify_discussion_topics(self, key):
"""
Saves the discussion topics and the verify the changes.
"""
# click on the inline save button.
self.cohort_management_page.save_discussion_topics(key)
# verifies that changes saved successfully.
confirmation_message = self.cohort_management_page.get_cohort_discussions_message(key=key)
self.assertEqual("Your changes have been saved.", confirmation_message)
# save button disabled again.
self.assertTrue(self.cohort_management_page.is_save_button_disabled(key))
def reload_page(self):
"""
Refresh the page.
"""
self.browser.refresh()
self.cohort_management_page.wait_for_page()
self.instructor_dashboard_page.select_cohort_management()
self.cohort_management_page.wait_for_page()
self.cohort_discussion_topics_are_visible()
def verify_discussion_topics_after_reload(self, key, cohorted_topics):
"""
Verifies the changed topics.
"""
self.reload_page()
self.assertEqual(self.cohort_management_page.get_cohorted_topics_count(key), cohorted_topics)
def test_cohort_course_wide_discussion_topic(self):
"""
Scenario: cohort a course-wide discussion topic.
Given I have a course with a cohort defined,
And a course-wide discussion with disabled Save button.
When I click on the course-wide discussion topic
Then I see the enabled save button
When I click on save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.cohort_discussion_topics_are_visible()
cohorted_topics_before = self.cohort_management_page.get_cohorted_topics_count(self.course_wide_key)
self.cohort_management_page.select_discussion_topic(self.course_wide_key)
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.course_wide_key))
self.save_and_verify_discussion_topics(key=self.course_wide_key)
cohorted_topics_after = self.cohort_management_page.get_cohorted_topics_count(self.course_wide_key)
self.assertNotEqual(cohorted_topics_before, cohorted_topics_after)
self.verify_discussion_topics_after_reload(self.course_wide_key, cohorted_topics_after)
def test_always_cohort_inline_topic_enabled(self):
"""
Scenario: Select the always_cohort_inline_topics radio button
Given I have a course with a cohort defined,
And an inline discussion topic with disabled Save button.
When I click on always_cohort_inline_topics
Then I see enabled save button
And I see disabled inline discussion topics
When I save the change
And I reload the page
Then I see the always_cohort_inline_topics option enabled
"""
self.cohort_discussion_topics_are_visible()
# enable always inline discussion topics and save the change
self.cohort_management_page.select_always_inline_discussion()
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
self.assertTrue(self.cohort_management_page.inline_discussion_topics_disabled())
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.cohort_management_page.always_inline_discussion_selected())
def test_cohort_some_inline_topics_enabled(self):
"""
Scenario: Select the cohort_some_inline_topics radio button
Given I have a course with a cohort defined and always_cohort_inline_topics set to True
And an inline discussion topic with disabled Save button.
When I click on cohort_some_inline_topics
Then I see enabled save button
And I see enabled inline discussion topics
When I save the change
And I reload the page
Then I see the cohort_some_inline_topics option enabled
"""
self.cohort_discussion_topics_are_visible()
# By default always inline discussion topics is False. Enable it (and reload the page).
self.assertFalse(self.cohort_management_page.always_inline_discussion_selected())
self.cohort_management_page.select_always_inline_discussion()
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertFalse(self.cohort_management_page.cohort_some_inline_discussion_selected())
# enable some inline discussion topic radio button.
self.cohort_management_page.select_cohort_some_inline_discussion()
# I see that save button is enabled
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
# I see that inline discussion topics are enabled
self.assertFalse(self.cohort_management_page.inline_discussion_topics_disabled())
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.cohort_management_page.cohort_some_inline_discussion_selected())
def test_cohort_inline_discussion_topic(self):
"""
Scenario: cohort inline discussion topic.
Given I have a course with a cohort defined,
And a inline discussion topic with disabled Save button
And When I click on inline discussion topic
And I see enabled save button
And When i click save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.cohort_discussion_topics_are_visible()
cohorted_topics_before = self.cohort_management_page.get_cohorted_topics_count(self.inline_key)
# check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# Save button enabled.
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
# verifies that changes saved successfully.
self.save_and_verify_discussion_topics(key=self.inline_key)
cohorted_topics_after = self.cohort_management_page.get_cohorted_topics_count(self.inline_key)
self.assertNotEqual(cohorted_topics_before, cohorted_topics_after)
self.verify_discussion_topics_after_reload(self.inline_key, cohorted_topics_after)
def test_verify_that_selecting_the_final_child_selects_category(self):
"""
Scenario: Category should be selected on selecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on child topics
Then I see enabled saved button
Then I see parent category to be checked.
"""
self.cohort_discussion_topics_are_visible()
# category should not be selected.
self.assertFalse(self.cohort_management_page.is_category_selected())
# check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.cohort_management_page.is_category_selected())
def test_verify_that_deselecting_the_final_child_deselects_category(self):
"""
Scenario: Category should be deselected on deselecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on final child topics
Then I see enabled saved button
Then I see parent category to be deselected.
"""
self.cohort_discussion_topics_are_visible()
# category should not be selected.
self.assertFalse(self.cohort_management_page.is_category_selected())
# check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.cohort_management_page.is_category_selected())
# un-check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# category should not be selected.
self.assertFalse(self.cohort_management_page.is_category_selected())
@attr(shard=6)
class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
"""

View File

@@ -47,6 +47,7 @@ class CohortedDiscussionTestMixin(BaseDiscussionMixin, CohortTestMixin):
# Enable cohorts and verify that the post shows to cohort only.
self.enable_cohorting(self.course_fixture)
self.enable_always_divide_inline_discussions(self.course_fixture)
self.refresh_thread_page(self.thread_id)
self.assertEquals(
self.thread_page.get_group_visibility_label(),

View File

@@ -0,0 +1,495 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests related to the divided discussion management on the LMS Instructor Dashboard
"""
import uuid
from nose.plugins.attrib import attr
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
from common.test.acceptance.pages.common.utils import add_enrollment_course_modes
from common.test.acceptance.pages.lms.discussion import DiscussionTabSingleThreadPage
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
from common.test.acceptance.tests.discussion.helpers import BaseDiscussionMixin, CohortTestMixin
from common.test.acceptance.tests.helpers import UniqueCourseTest
class BaseDividedDiscussionTest(UniqueCourseTest, CohortTestMixin):
"""
Base class for tests related to divided discussions.
"""
def setUp(self):
"""
Set up a discussion topic
"""
super(BaseDividedDiscussionTest, self).setUp()
self.discussion_id = "test_discussion_{}".format(uuid.uuid4().hex)
self.course_fixture = CourseFixture(**self.course_info).add_children(
XBlockFixtureDesc("chapter", "Test Section").add_children(
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
XBlockFixtureDesc("vertical", "Test Unit").add_children(
XBlockFixtureDesc(
"discussion",
"Test Discussion",
metadata={"discussion_id": self.discussion_id}
)
)
)
)
).install()
# create course with single cohort and two content groups (user_partition of type "cohort")
self.cohort_name = "OnlyCohort"
self.setup_cohort_config(self.course_fixture)
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
# login as an instructor
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
self.instructor_dashboard_page.visit()
self.discussion_management_page = self.instructor_dashboard_page.select_discussion_management()
self.discussion_management_page.wait_for_page()
self.course_wide_key = 'course-wide'
self.inline_key = 'inline'
self.scheme_key = 'scheme'
def check_discussion_topic_visibility(self, visible=True):
"""
Assert that discussion topics are visible with appropriate content.
"""
self.assertEqual(visible, self.discussion_management_page.discussion_topics_visible())
if visible:
self.assertEqual(
"Course-Wide Discussion Topics",
self.discussion_management_page.divided_discussion_heading_is_visible(self.course_wide_key)
)
self.assertTrue(self.discussion_management_page.is_save_button_disabled(self.course_wide_key))
self.assertEqual(
"Content-Specific Discussion Topics",
self.discussion_management_page.divided_discussion_heading_is_visible(self.inline_key)
)
self.assertTrue(self.discussion_management_page.is_save_button_disabled(self.inline_key))
def reload_page(self, topics_visible=True):
"""
Refresh the page, then verify if the discussion topics are visible on the discussion
management instructor dashboard tab.
"""
self.browser.refresh()
self.discussion_management_page.wait_for_page()
self.instructor_dashboard_page.select_discussion_management()
self.discussion_management_page.wait_for_page()
self.check_discussion_topic_visibility(topics_visible)
def verify_save_confirmation_message(self, key):
"""
Verify that the save confirmation message for the specified portion of the page is visible.
"""
confirmation_message = self.discussion_management_page.get_divide_discussions_message(key=key)
self.assertIn("Your changes have been saved.", confirmation_message)
@attr(shard=6)
class DividedDiscussionTopicsTest(BaseDividedDiscussionTest):
"""
Tests for dividing the inline and course-wide discussion topics.
"""
def save_and_verify_discussion_topics(self, key):
"""
Saves the discussion topics and the verify the changes.
"""
# click on the inline save button.
self.discussion_management_page.save_discussion_topics(key)
# verifies that changes saved successfully.
self.verify_save_confirmation_message(key)
# save button disabled again.
self.assertTrue(self.discussion_management_page.is_save_button_disabled(key))
def verify_discussion_topics_after_reload(self, key, divided_topics):
"""
Verifies the changed topics.
"""
self.reload_page()
self.assertEqual(self.discussion_management_page.get_divided_topics_count(key), divided_topics)
def test_divide_course_wide_discussion_topic(self):
"""
Scenario: divide a course-wide discussion topic.
Given I have a course with a divide defined,
And a course-wide discussion with disabled Save button.
When I click on the course-wide discussion topic
Then I see the enabled save button
When I click on save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.check_discussion_topic_visibility()
divided_topics_before = self.discussion_management_page.get_divided_topics_count(self.course_wide_key)
self.discussion_management_page.select_discussion_topic(self.course_wide_key)
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.course_wide_key))
self.save_and_verify_discussion_topics(key=self.course_wide_key)
divided_topics_after = self.discussion_management_page.get_divided_topics_count(self.course_wide_key)
self.assertNotEqual(divided_topics_before, divided_topics_after)
self.verify_discussion_topics_after_reload(self.course_wide_key, divided_topics_after)
def test_always_divide_inline_topic_enabled(self):
"""
Scenario: Select the always_divide_inline_topics radio button
Given I have a course with a cohort defined,
And an inline discussion topic with disabled Save button.
When I click on always_divide_inline_topics
Then I see enabled save button
And I see disabled inline discussion topics
When I save the change
And I reload the page
Then I see the always_divide_inline_topics option enabled
"""
self.check_discussion_topic_visibility()
# enable always inline discussion topics and save the change
self.discussion_management_page.select_always_inline_discussion()
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
self.assertTrue(self.discussion_management_page.inline_discussion_topics_disabled())
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.discussion_management_page.always_inline_discussion_selected())
def test_divide_some_inline_topics_enabled(self):
"""
Scenario: Select the divide_some_inline_topics radio button
Given I have a course with a divide defined and always_divide_inline_topics set to True
And an inline discussion topic with disabled Save button.
When I click on divide_some_inline_topics
Then I see enabled save button
And I see enabled inline discussion topics
When I save the change
And I reload the page
Then I see the divide_some_inline_topics option enabled
"""
self.check_discussion_topic_visibility()
# By default always inline discussion topics is False. Enable it (and reload the page).
self.assertFalse(self.discussion_management_page.always_inline_discussion_selected())
self.discussion_management_page.select_always_inline_discussion()
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertFalse(self.discussion_management_page.divide_some_inline_discussion_selected())
# enable some inline discussion topic radio button.
self.discussion_management_page.select_divide_some_inline_discussion()
# I see that save button is enabled
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
# I see that inline discussion topics are enabled
self.assertFalse(self.discussion_management_page.inline_discussion_topics_disabled())
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.discussion_management_page.divide_some_inline_discussion_selected())
def test_divide_inline_discussion_topic(self):
"""
Scenario: divide inline discussion topic.
Given I have a course with a divide defined,
And a inline discussion topic with disabled Save button
And When I click on inline discussion topic
And I see enabled save button
And When i click save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.check_discussion_topic_visibility()
divided_topics_before = self.discussion_management_page.get_divided_topics_count(self.inline_key)
# check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# Save button enabled.
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
# verifies that changes saved successfully.
self.save_and_verify_discussion_topics(key=self.inline_key)
divided_topics_after = self.discussion_management_page.get_divided_topics_count(self.inline_key)
self.assertNotEqual(divided_topics_before, divided_topics_after)
self.verify_discussion_topics_after_reload(self.inline_key, divided_topics_after)
def test_verify_that_selecting_the_final_child_selects_category(self):
"""
Scenario: Category should be selected on selecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on child topics
Then I see enabled saved button
Then I see parent category to be checked.
"""
self.check_discussion_topic_visibility()
# category should not be selected.
self.assertFalse(self.discussion_management_page.is_category_selected())
# check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.discussion_management_page.is_category_selected())
def test_verify_that_deselecting_the_final_child_deselects_category(self):
"""
Scenario: Category should be deselected on deselecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on final child topics
Then I see enabled saved button
Then I see parent category to be deselected.
"""
self.check_discussion_topic_visibility()
# category should not be selected.
self.assertFalse(self.discussion_management_page.is_category_selected())
# check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.discussion_management_page.is_category_selected())
# un-check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# category should not be selected.
self.assertFalse(self.discussion_management_page.is_category_selected())
@attr(shard=6)
class DivisionSchemeTest(BaseDividedDiscussionTest, BaseDiscussionMixin):
"""
Tests for changing the division scheme for Discussions.
"""
def add_modes_and_view_discussion_mgmt_page(self, modes):
"""
Adds enrollment modes to the course, and then goes to the
discussion tab on the instructor dashboard.
"""
add_enrollment_course_modes(self.browser, self.course_id, modes)
self.view_discussion_management_page()
def view_discussion_management_page(self):
"""
Go to the discussion tab on the instructor dashboard.
"""
self.instructor_dashboard_page.visit()
self.assertTrue(self.instructor_dashboard_page.is_discussion_management_visible())
self.instructor_dashboard_page.select_discussion_management()
self.discussion_management_page.wait_for_page()
def setup_thread_page(self, thread_id):
"""
This is called by BaseDiscussionMixin.setup_thread.
"""
self.thread_page = DiscussionTabSingleThreadPage(
self.browser, self.course_id, self.discussion_id, thread_id
)
self.thread_page.visit()
def test_not_divided_hides_discussion_topics(self):
"""
Tests that discussion topics are hidden iff discussion division is disabled.
"""
# Initially "Cohort" is the selected scheme.
self.assertTrue(
self.discussion_management_page.division_scheme_visible(self.discussion_management_page.COHORT_SCHEME)
)
self.assertEqual(
self.discussion_management_page.COHORT_SCHEME,
self.discussion_management_page.get_selected_scheme()
)
self.check_discussion_topic_visibility(visible=True)
self.discussion_management_page.select_division_scheme(self.discussion_management_page.NOT_DIVIDED_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
self.check_discussion_topic_visibility(visible=False)
# Reload the page and make sure that the change was persisted
self.reload_page(topics_visible=False)
self.assertTrue(self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.COHORT_SCHEME)
)
self.assertEqual(
self.discussion_management_page.NOT_DIVIDED_SCHEME,
self.discussion_management_page.get_selected_scheme()
)
# Select "cohort" again and make sure that the discussion topics appear.
self.discussion_management_page.select_division_scheme(self.discussion_management_page.COHORT_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
self.check_discussion_topic_visibility(visible=True)
def test_disabling_cohorts(self):
"""
Test that the discussions management tab hides when there is <= 1 enrollment track, the Cohort division scheme
is not selected, and cohorts are disabled.
(even without reloading the page).
"""
self.disable_cohorting(self.course_fixture)
self.instructor_dashboard_page.visit()
self.assertFalse(self.instructor_dashboard_page.is_discussion_management_visible())
def test_disabling_cohorts_while_selected(self):
"""
Test that disabling cohorts does not hide the discussion tab when there is more than one enrollment track.
Also that the division scheme for cohorts is visible iff it was selected.
(even without reloading the page).
"""
add_enrollment_course_modes(self.browser, self.course_id, ['audit', 'verified'])
# Verify that the tab is visible, the cohort scheme is selected by default for divided discussions
self.disable_cohorting(self.course_fixture)
# Go to Discussions tab and ensure that the correct scheme options are visible
self.view_discussion_management_page()
self.assertTrue(
self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.COHORT_SCHEME
)
)
def test_disabling_cohorts_while_not_selected(self):
"""
Test that disabling cohorts does not hide the discussion tab when there is more than one enrollment track.
Also that the division scheme for cohorts is not visible when cohorts are disabled and another scheme is
selected for division.
(even without reloading the page).
"""
add_enrollment_course_modes(self.browser, self.course_id, ['audit', 'verified'])
# Verify that the tab is visible
self.view_discussion_management_page()
self.discussion_management_page.select_division_scheme(self.discussion_management_page.ENROLLMENT_TRACK_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
self.disable_cohorting(self.course_fixture)
# Go to Discussions tab and ensure that the correct scheme options are visible
self.view_discussion_management_page()
self.assertFalse(
self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.COHORT_SCHEME
)
)
def test_single_enrollment_mode(self):
"""
Test that the enrollment track scheme is not visible if there is a single enrollment mode.
"""
self.add_modes_and_view_discussion_mgmt_page(['audit'])
self.assertFalse(
self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.ENROLLMENT_TRACK_SCHEME
)
)
def test_radio_buttons_with_multiple_enrollment_modes(self):
"""
Test that the enrollment track scheme is visible if there are multiple enrollment tracks,
and that the selection can be persisted.
Also verifies that the cohort division scheme is not presented if cohorts are disabled and cohorts
are not the selected division scheme.
"""
self.add_modes_and_view_discussion_mgmt_page(['audit', 'verified'])
self.assertTrue(
self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.ENROLLMENT_TRACK_SCHEME
)
)
# And the cohort scheme is initially visible because it is selected (and cohorts are enabled).
self.assertTrue(
self.discussion_management_page.division_scheme_visible(self.discussion_management_page.COHORT_SCHEME)
)
self.discussion_management_page.select_division_scheme(self.discussion_management_page.ENROLLMENT_TRACK_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
self.check_discussion_topic_visibility(visible=True)
# Also disable cohorts so we can verify that the cohort scheme choice goes away.
self.disable_cohorting(self.course_fixture)
self.reload_page(topics_visible=True)
self.assertEqual(
self.discussion_management_page.ENROLLMENT_TRACK_SCHEME,
self.discussion_management_page.get_selected_scheme()
)
# Verify that the cohort scheme is no longer visible as cohorts are disabled.
self.assertFalse(
self.discussion_management_page.division_scheme_visible(self.discussion_management_page.COHORT_SCHEME)
)
def test_enrollment_track_discussion_visibility_label(self):
"""
If enrollment tracks are the division scheme, verifies that discussion visibility labels
correctly render.
Note that there are similar tests for cohorts in test_cohorts.py.
"""
def refresh_thread_page():
self.browser.refresh()
self.thread_page.wait_for_page()
# Make moderator for viewing all groups in discussions.
AutoAuthPage(self.browser, course_id=self.course_id, roles="Moderator", staff=True).visit()
self.add_modes_and_view_discussion_mgmt_page(['audit', 'verified'])
self.discussion_management_page.select_division_scheme(self.discussion_management_page.ENROLLMENT_TRACK_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
# Set "always divide" as the thread we will be creating will be an inline thread,
# and this way the thread does not need to be explicitly divided.
self.enable_always_divide_inline_discussions(self.course_fixture)
# Create a thread with group_id corresponding to the Audit enrollment mode.
# The Audit group ID is 1, and for the comment service group_id we negate it.
self.setup_thread(1, group_id=-1)
refresh_thread_page()
self.assertEquals(
self.thread_page.get_group_visibility_label(),
"This post is visible only to {}.".format("Audit")
)
# Disable dividing discussions and verify that the post now shows as visible to everyone.
self.view_discussion_management_page()
self.discussion_management_page.select_division_scheme(self.discussion_management_page.NOT_DIVIDED_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
self.thread_page.visit()
self.assertEquals(self.thread_page.get_group_visibility_label(), "This post is visible to everyone.")

View File

@@ -7,7 +7,6 @@ import uuid
from nose.plugins.attrib import attr
from common.test.acceptance.fixtures import LMS_BASE_URL
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
from common.test.acceptance.pages.common.logout import LogoutPage
@@ -17,12 +16,13 @@ from common.test.acceptance.pages.lms.staff_view import StaffCoursewarePage
from common.test.acceptance.pages.studio.component_editor import ComponentVisibilityEditorView
from common.test.acceptance.pages.studio.overview import CourseOutlinePage as StudioCourseOutlinePage
from common.test.acceptance.pages.studio.settings_group_configurations import GroupConfigurationsPage
from common.test.acceptance.tests.discussion.helpers import CohortTestMixin
from common.test.acceptance.tests.helpers import remove_file
from common.test.acceptance.tests.studio.base_studio_test import ContainerBase
@attr(shard=1)
class CoursewareSearchCohortTest(ContainerBase):
class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
"""
Test courseware search.
"""
@@ -132,15 +132,6 @@ class CoursewareSearchCohortTest(ContainerBase):
)
)
def enable_cohorting(self, course_fixture):
"""
Enables cohorting for the current course.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
data = json.dumps({'is_cohorted': True})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to enable cohorts")
def create_content_groups(self):
"""
Creates two content groups in Studio Group Configurations Settings.

View File

@@ -2,17 +2,15 @@
Test Help links in LMS
"""
import json
from common.test.acceptance.fixtures import LMS_BASE_URL
from common.test.acceptance.fixtures.course import CourseFixture
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
from common.test.acceptance.tests.discussion.helpers import CohortTestMixin
from common.test.acceptance.tests.helpers import assert_opened_help_link_is_correct, url_for_help
from common.test.acceptance.tests.lms.test_lms_instructor_dashboard import BaseInstructorDashboardTest
from common.test.acceptance.tests.studio.base_studio_test import ContainerBase
class TestCohortHelp(ContainerBase):
class TestCohortHelp(ContainerBase, CohortTestMixin):
"""
Tests help links in Cohort page
"""
@@ -74,15 +72,6 @@ class TestCohortHelp(ContainerBase):
)
self.verify_help_link(href)
def enable_cohorting(self, course_fixture):
"""
Enables cohorting for the current course.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
data = json.dumps({'is_cohorted': True})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to enable cohorts")
class InstructorDashboardHelp(BaseInstructorDashboardTest):
"""

View File

@@ -2,12 +2,9 @@
End-to-end test for cohorted courseware. This uses both Studio and LMS.
"""
import json
from bok_choy.page_object import XSS_INJECTION
from nose.plugins.attrib import attr
from common.test.acceptance.fixtures import LMS_BASE_URL
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
from common.test.acceptance.pages.common.utils import add_enrollment_course_modes, enroll_user_track
@@ -15,6 +12,7 @@ from common.test.acceptance.pages.lms.courseware import CoursewarePage
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
from common.test.acceptance.pages.studio.component_editor import ComponentVisibilityEditorView
from common.test.acceptance.pages.studio.settings_group_configurations import GroupConfigurationsPage
from common.test.acceptance.tests.discussion.helpers import CohortTestMixin
from common.test.acceptance.tests.lms.test_lms_user_preview import verify_expected_problem_visibility
from studio.base_studio_test import ContainerBase
@@ -23,7 +21,7 @@ VERIFIED_TRACK = "Verified"
@attr(shard=5)
class EndToEndCohortedCoursewareTest(ContainerBase):
class EndToEndCohortedCoursewareTest(ContainerBase, CohortTestMixin):
"""
End-to-end of cohorted courseware.
"""
@@ -113,15 +111,6 @@ class EndToEndCohortedCoursewareTest(ContainerBase):
)
)
def enable_cohorting(self, course_fixture):
"""
Enables cohorting for the current course.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
data = json.dumps({'is_cohorted': True})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
self.assertTrue(response.ok, "Failed to enable cohorts")
def create_content_groups(self):
"""
Creates two content groups in Studio Group Configurations Settings.