diff --git a/common/test/acceptance/pages/lms/instructor_dashboard.py b/common/test/acceptance/pages/lms/instructor_dashboard.py index b6000e0e3f..077c7d02f2 100644 --- a/common/test/acceptance/pages/lms/instructor_dashboard.py +++ b/common/test/acceptance/pages/lms/instructor_dashboard.py @@ -47,6 +47,15 @@ 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 select_data_download(self): """ Selects the data download tab and returns a DataDownloadPage. @@ -666,61 +675,31 @@ 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' + } - 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 + def is_browser_on_page(self): + return self.q(css=self.discussion_form_selectors['course-wide']).present - def cohort_some_inline_discussion_selected(self): + def _bounded_selector(self, selector): """ - Returns true if some_cohort_inline_discussions radio button is selected. + Return `selector`, but limited to the divided discussion management context. """ - return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0 - - def select_cohort_some_inline_discussion(self): - """ - Selects the cohort_some_inline_discussions radio button. - """ - 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 +709,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 +747,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 +795,11 @@ 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)) - - if len(discussion_heading) == 0: - return False - return discussion_heading.first.text[0] - - def cohort_management_controls_visible(self): - """ - Return the visibility status of cohort management controls(cohort selector section etc). - """ - return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and - self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible) + return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present() class MembershipPageAutoEnrollSection(PageObject): diff --git a/common/test/acceptance/tests/discussion/helpers.py b/common/test/acceptance/tests/discussion/helpers.py index 95c4543c91..3e216dd4a4 100644 --- a/common/test/acceptance/tests/discussion/helpers.py +++ b/common/test/acceptance/tests/discussion/helpers.py @@ -76,11 +76,16 @@ class CohortTestMixin(object): def enable_cohorting(self, course_fixture): """ - enables cohorting for the current course fixture. + enables cohorts and always_divide_inline_discussions for the current 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}) + discussions_url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/discussions/settings' # pylint: disable=protected-access + + data = json.dumps({'is_cohorted': True}) + discussions_data = json.dumps({'always_divide_inline_discussions': True}) + response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers) + course_fixture.session.patch(discussions_url, data=discussions_data, headers=course_fixture.headers) def disable_cohorting(self, course_fixture): """ diff --git a/common/test/acceptance/tests/discussion/test_cohort_management.py b/common/test/acceptance/tests/discussion/test_cohort_management.py index c3546f8a60..815971b732 100644 --- a/common/test/acceptance/tests/discussion/test_cohort_management.py +++ b/common/test/acceptance/tests/discussion/test_cohort_management.py @@ -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): """ diff --git a/common/test/acceptance/tests/discussion/test_discussion_management.py b/common/test/acceptance/tests/discussion/test_discussion_management.py new file mode 100644 index 0000000000..f49109bc8f --- /dev/null +++ b/common/test/acceptance/tests/discussion/test_discussion_management.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +""" +End-to-end tests related to the divided discussion management on the LMS Instructor Dashboard +""" + +from nose.plugins.attrib import attr +from common.test.acceptance.tests.discussion.helpers import CohortTestMixin +from common.test.acceptance.tests.helpers import UniqueCourseTest +from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc +from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage +from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage + +import uuid + + +@attr(shard=6) +class DividedDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin): + """ + Tests for dividing the inline and course-wide discussion topics. + """ + def setUp(self): + """ + Set up a discussion topic + """ + super(DividedDiscussionTopicsTest, 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' + + def divided_discussion_topics_are_visible(self): + """ + Assert that discussion topics are visible with appropriate content. + """ + self.assertTrue(self.discussion_management_page.discussion_topics_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 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. + confirmation_message = self.discussion_management_page.get_divide_discussions_message(key=key) + self.assertEqual("Your changes have been saved.", confirmation_message) + + # save button disabled again. + self.assertTrue(self.discussion_management_page.is_save_button_disabled(key)) + + def reload_page(self): + """ + Refresh the page. + """ + 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.divided_discussion_topics_are_visible() + + 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.divided_discussion_topics_are_visible() + + 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.divided_discussion_topics_are_visible() + + # 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.divided_discussion_topics_are_visible() + # 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.divided_discussion_topics_are_visible() + + 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.divided_discussion_topics_are_visible() + + # 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.divided_discussion_topics_are_visible() + + # 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()) diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index d1a57384de..26ce771cba 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -26,7 +26,7 @@ from courseware.tests.factories import InstructorFactory from courseware.tabs import get_course_tab_list from openedx.core.djangoapps.course_groups import cohorts from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted -from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, topic_name_to_id, CohortFactory +from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, config_course_discussions, topic_name_to_id, CohortFactory from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory from openedx.core.djangoapps.content.course_structures.models import CourseStructure from openedx.core.djangoapps.util.testing import ContentGroupTestCase @@ -478,7 +478,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): } ) - def test_inline_with_always_cohort_inline_discussion_flag(self): + def test_inline_with_always_divide_inline_discussion_flag(self): self.create_discussion("Chapter", "Discussion") set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True) @@ -502,7 +502,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): } ) - def test_inline_without_always_cohort_inline_discussion_flag(self): + def test_inline_without_always_divide_inline_discussion_flag(self): self.create_discussion("Chapter", "Discussion") set_discussion_division_settings(self.course.id, enable_cohorts=True) @@ -1301,15 +1301,16 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase): ) # not cohorted - config_course_cohorts(course, is_cohorted=False, discussion_topics=["General", "Feedback"]) - + config_course_cohorts(course, is_cohorted=False) + config_course_discussions(course, discussion_topics=["General", "Feedback"]) self.assertFalse( utils.is_commentable_divided(course.id, to_id("General")), "Course isn't cohorted" ) # cohorted, but top level topics aren't - config_course_cohorts(course, is_cohorted=True, discussion_topics=["General", "Feedback"]) + config_course_cohorts(course, is_cohorted=True) + config_course_discussions(course, discussion_topics=["General", "Feedback"]) self.assertTrue(cohorts.is_course_cohorted(course.id)) self.assertFalse( @@ -1320,10 +1321,9 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase): # cohorted, including "Feedback" top-level topics aren't config_course_cohorts( course, - is_cohorted=True, - discussion_topics=["General", "Feedback"], - divided_discussions=["Feedback"] + is_cohorted=True ) + config_course_discussions(course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback"]) self.assertTrue(cohorts.is_course_cohorted(course.id)) self.assertFalse( @@ -1345,42 +1345,52 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase): config_course_cohorts( course, is_cohorted=True, + ) + config_course_discussions( + course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback", "random_inline"] ) + self.assertFalse( utils.is_commentable_divided(course.id, to_id("random")), "By default, Non-top-level discussions are not cohorted in a cohorted courses." ) - # if always_cohort_inline_discussions is set to False, non-top-level discussion are always - # non cohorted unless they are explicitly set in cohorted_discussions + # if always_divide_inline_discussions is set to False, non-top-level discussion are always + # not divided unless they are explicitly set in divided_discussions config_course_cohorts( course, is_cohorted=True, + ) + config_course_discussions( + course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback", "random_inline"], always_divide_inline_discussions=False ) + self.assertFalse( utils.is_commentable_divided(course.id, to_id("random")), - "Non-top-level discussion is not cohorted if always_cohort_inline_discussions is False." + "Non-top-level discussion is not cohorted if always_divide_inline_discussions is False." ) self.assertTrue( utils.is_commentable_divided(course.id, to_id("random_inline")), - "If always_cohort_inline_discussions set to False, Non-top-level discussion is " + "If always_divide_inline_discussions set to False, Non-top-level discussion is " "cohorted if explicitly set in cohorted_discussions." ) self.assertTrue( utils.is_commentable_divided(course.id, to_id("Feedback")), - "If always_cohort_inline_discussions set to False, top-level discussion are not affected." + "If always_divide_inline_discussions set to False, top-level discussion are not affected." ) def test_is_commentable_divided_team(self): course = modulestore().get_course(self.toy_course_key) self.assertFalse(cohorts.is_course_cohorted(course.id)) - config_course_cohorts(course, is_cohorted=True, always_divide_inline_discussions=True) + config_course_cohorts(course, is_cohorted=True) + config_course_discussions(course, always_divide_inline_discussions=True) + team = CourseTeamFactory(course_id=course.id) # Verify that team discussions are not cohorted, but other discussions are diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 5aecf20927..3250e335e6 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -125,6 +125,7 @@ def instructor_dashboard_2(request, course_id): _section_course_info(course, access), _section_membership(course, access, is_white_label), _section_cohort_management(course, access), + _section_discussions_management(course, access), _section_student_admin(course, access), _section_data_download(course, access), ] @@ -513,7 +514,6 @@ def _section_cohort_management(course, access): ), 'cohorts_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}), 'upload_cohorts_csv_url': reverse('add_users_to_cohorts', kwargs={'course_id': unicode(course_key)}), - 'discussion_topics_url': reverse('cohort_discussion_topics', kwargs={'course_key_string': unicode(course_key)}), 'verified_track_cohorting_url': reverse( 'verified_track_cohorting', kwargs={'course_key_string': unicode(course_key)} ), @@ -521,6 +521,21 @@ def _section_cohort_management(course, access): return section_data +def _section_discussions_management(course, access): + """ Provide data for the corresponding discussion management section """ + course_key = course.id + section_data = { + 'section_key': 'discussions_management', + 'section_display_name': _('Discussions'), + 'discussion_topics_url': reverse('discussion_topics', kwargs={'course_key_string': unicode(course_key)}), + 'course_discussion_settings': reverse( + 'course_discussions_settings', + kwargs={'course_key_string': unicode(course_key)} + ), + } + return section_data + + def _is_small_course(course_key): """ Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """ is_small_course = False diff --git a/lms/envs/common.py b/lms/envs/common.py index bb4656451e..13b86f4a20 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1759,6 +1759,7 @@ REQUIRE_JS_PATH_OVERRIDES = { 'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js', 'js/courseware/courseware_factory': 'js/courseware/courseware_factory.js', 'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory.js', + 'js/groups/discussions_management/discussions_dashboard_factory': 'js/discussions_management/views/discussions_dashboard_factory.js', 'draggabilly': 'js/vendor/draggabilly.js', 'hls': 'common/js/vendor/hls.js' } diff --git a/lms/static/js/groups/models/cohort_discussions.js b/lms/static/js/discussions_management/models/course_discussions_detail.js similarity index 68% rename from lms/static/js/groups/models/cohort_discussions.js rename to lms/static/js/discussions_management/models/course_discussions_detail.js index 8a309cb96b..13df59db79 100644 --- a/lms/static/js/groups/models/cohort_discussions.js +++ b/lms/static/js/discussions_management/models/course_discussions_detail.js @@ -1,12 +1,12 @@ (function(define) { 'use strict'; define(['backbone'], function(Backbone) { - var DiscussionTopicsSettingsModel = Backbone.Model.extend({ + var CourseDiscussionTopicDetailsModel = Backbone.Model.extend({ defaults: { course_wide_discussions: {}, inline_discussions: {} } }); - return DiscussionTopicsSettingsModel; + return CourseDiscussionTopicDetailsModel; }); }).call(this, define || RequireJS.define); diff --git a/lms/static/js/discussions_management/models/course_discussions_settings.js b/lms/static/js/discussions_management/models/course_discussions_settings.js new file mode 100644 index 0000000000..6eb50f0d5f --- /dev/null +++ b/lms/static/js/discussions_management/models/course_discussions_settings.js @@ -0,0 +1,14 @@ +(function(define) { + 'use strict'; + define(['backbone'], function(Backbone) { + var CourseDiscussionsSettingsModel = Backbone.Model.extend({ + idAttribute: 'id', + defaults: { + divided_inline_discussions: [], + divided_course_wide_discussions: [], + always_divide_inline_discussions: false + } + }); + return CourseDiscussionsSettingsModel; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/discussions_management/views/discussions.js b/lms/static/js/discussions_management/views/discussions.js new file mode 100644 index 0000000000..a784ee74ac --- /dev/null +++ b/lms/static/js/discussions_management/views/discussions.js @@ -0,0 +1,49 @@ +(function(define) { + 'use strict'; + define(['jquery', 'underscore', 'backbone', 'gettext', + 'js/discussions_management/views/divided_discussions_inline', + 'js/discussions_management/views/divided_discussions_course_wide', + 'edx-ui-toolkit/js/utils/html-utils' + ], + + function($, _, Backbone, gettext, InlineDiscussionsView, CourseWideDiscussionsView, HtmlUtils) { + var DiscussionsView = Backbone.View.extend({ + + initialize: function(options) { + this.template = HtmlUtils.template($('#discussions-tpl').text()); + this.context = options.context; + this.discussionSettings = options.discussionSettings; + }, + + render: function() { + HtmlUtils.setHtml(this.$el, this.template({})); + this.showDiscussionTopics(); + return this; + }, + + getSectionCss: function(section) { + return ".instructor-nav .nav-item [data-section='" + section + "']"; + }, + + showDiscussionTopics: function() { + var dividedDiscussionsElement = this.$('.discussions-nav'); + if (!this.CourseWideDiscussionsView) { + this.CourseWideDiscussionsView = new CourseWideDiscussionsView({ + el: dividedDiscussionsElement, + model: this.context.courseDiscussionTopicDetailsModel, + discussionSettings: this.discussionSettings + }).render(); + } + + if (!this.InlineDiscussionsView) { + this.InlineDiscussionsView = new InlineDiscussionsView({ + el: dividedDiscussionsElement, + model: this.context.courseDiscussionTopicDetailsModel, + discussionSettings: this.discussionSettings + }).render(); + } + } + }); + return DiscussionsView; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/discussions_management/views/discussions_dashboard_factory.js b/lms/static/js/discussions_management/views/discussions_dashboard_factory.js new file mode 100644 index 0000000000..c2f0cdf8d5 --- /dev/null +++ b/lms/static/js/discussions_management/views/discussions_dashboard_factory.js @@ -0,0 +1,33 @@ +(function(define) { + 'use strict'; + define('js/discussions_management/views/discussions_dashboard_factory', + ['jquery', 'js/discussions_management/views/discussions', + 'js/discussions_management/models/course_discussions_detail', + 'js/discussions_management/models/course_discussions_settings'], + function($, DiscussionsView, CourseDiscussionTopicDetailsModel, CourseDiscussionsSettingsModel) { + return function() { + var courseDiscussionSettings = new CourseDiscussionsSettingsModel(), + discussionTopicsSettings = new CourseDiscussionTopicDetailsModel(), + $discussionsManagementElement = $('.discussions-management'), + discussionsView; + + courseDiscussionSettings.url = $discussionsManagementElement.data('course-discussion-settings-url'); + discussionTopicsSettings.url = $discussionsManagementElement.data('discussion-topics-url'); + + discussionsView = new DiscussionsView({ + el: $discussionsManagementElement, + discussionSettings: courseDiscussionSettings, + context: { + courseDiscussionTopicDetailsModel: discussionTopicsSettings + } + }); + + courseDiscussionSettings.fetch().done(function() { + discussionTopicsSettings.fetch().done(function() { + discussionsView.render(); + }); + }); + }; + }); +}).call(this, define || RequireJS.define); + diff --git a/lms/static/js/groups/views/cohort_discussions.js b/lms/static/js/discussions_management/views/divided_discussions.js similarity index 74% rename from lms/static/js/groups/views/cohort_discussions.js rename to lms/static/js/discussions_management/views/divided_discussions.js index 8b13aa5056..be498d6196 100644 --- a/lms/static/js/groups/views/cohort_discussions.js +++ b/lms/static/js/discussions_management/views/divided_discussions.js @@ -1,8 +1,9 @@ (function(define) { 'use strict'; define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'], - function($, _, Backbone) { - var CohortDiscussionConfigurationView = Backbone.View.extend({ + function($, _, Backbone, gettext) { + /* global NotificationModel, NotificationView */ + var DividedDiscussionConfigurationView = Backbone.View.extend({ /** * Add/Remove the disabled attribute on given element. @@ -14,53 +15,53 @@ }, /** - * Returns the cohorted discussions list. + * Returns the divided discussions list. * @param {string} selector - To select the discussion elements whose ids to return. - * @returns {Array} - Cohorted discussions. + * @returns {Array} - Divided discussions. */ - getCohortedDiscussions: function(selector) { + getDividedDiscussions: function(selector) { var self = this, - cohortedDiscussions = []; + dividedDiscussions = []; _.each(self.$(selector), function(topic) { - cohortedDiscussions.push($(topic).data('id')); + dividedDiscussions.push($(topic).data('id')); }); - return cohortedDiscussions; + return dividedDiscussions; }, /** - * Save the cohortSettings' changed attributes to the server via PATCH method. + * Save the discussionSettings' changed attributes to the server via PATCH method. * It shows the error message(s) if any. * @param {object} $element - Messages would be shown before this element. * @param {object} fieldData - Data to update on the server. */ saveForm: function($element, fieldData) { var self = this, - cohortSettingsModel = this.cohortSettings, + discussionSettingsModel = this.discussionSettings, saveOperation = $.Deferred(), showErrorMessage; - - showErrorMessage = function(message, $element) { + showErrorMessage = function(message) { self.showMessage(message, $element, 'error'); }; this.removeNotification(); - cohortSettingsModel.save( + discussionSettingsModel.save( fieldData, {patch: true, wait: true} ).done(function() { saveOperation.resolve(); }).fail(function(result) { - var errorMessage = null; + var errorMessage = null, + jsonResponse; try { - var jsonResponse = JSON.parse(result.responseText); + jsonResponse = JSON.parse(result.responseText); errorMessage = jsonResponse.error; } catch (e) { // Ignore the exception and show the default error message instead. } if (!errorMessage) { - errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); + errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len } - showErrorMessage(errorMessage, $element); + showErrorMessage(errorMessage); saveOperation.reject(); }); return saveOperation.promise(); @@ -92,6 +93,6 @@ } }); - return CohortDiscussionConfigurationView; + return DividedDiscussionConfigurationView; }); }).call(this, define || RequireJS.define); diff --git a/lms/static/js/groups/views/cohort_discussions_course_wide.js b/lms/static/js/discussions_management/views/divided_discussions_course_wide.js similarity index 77% rename from lms/static/js/groups/views/cohort_discussions_course_wide.js rename to lms/static/js/discussions_management/views/divided_discussions_course_wide.js index 71fdcd750e..774f734077 100644 --- a/lms/static/js/groups/views/cohort_discussions_course_wide.js +++ b/lms/static/js/discussions_management/views/divided_discussions_course_wide.js @@ -1,21 +1,21 @@ (function(define) { 'use strict'; - define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions', + define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions', 'edx-ui-toolkit/js/utils/html-utils'], - function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) { - var CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({ + function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) { + var CourseWideDiscussionsView = DividedDiscussionConfigurationView.extend({ events: { 'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged', 'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm' }, initialize: function(options) { - this.template = HtmlUtils.template($('#cohort-discussions-course-wide-tpl').text()); - this.cohortSettings = options.cohortSettings; + this.template = HtmlUtils.template($('#divided-discussions-course-wide-tpl').text()); + this.discussionSettings = options.discussionSettings; }, render: function() { - HtmlUtils.setHtml(this.$('.cohort-course-wide-discussions-nav'), this.template({ + HtmlUtils.setHtml(this.$('.course-wide-discussions-nav'), this.template({ courseWideTopicsHtml: this.getCourseWideDiscussionsHtml( this.model.get('course_wide_discussions') ) @@ -56,25 +56,27 @@ }, /** - * Sends the cohorted_course_wide_discussions to the server and renders the view. + * Sends the courseWideDividedDiscussions to the server and renders the view. */ saveCourseWideDiscussionsForm: function(event) { - event.preventDefault(); - var self = this, - courseWideCohortedDiscussions = self.getCohortedDiscussions( + courseWideDividedDiscussions = self.getDividedDiscussions( '.check-discussion-subcategory-course-wide:checked' ), - fieldData = {cohorted_course_wide_discussions: courseWideCohortedDiscussions}; + fieldData = {divided_course_wide_discussions: courseWideDividedDiscussions}; + + event.preventDefault(); self.saveForm(self.$('.course-wide-discussion-topics'), fieldData) .done(function() { self.model.fetch() .done(function() { self.render(); - self.showMessage(gettext('Your changes have been saved.'), self.$('.course-wide-discussion-topics')); + self.showMessage(gettext('Your changes have been saved.'), + self.$('.course-wide-discussion-topics') + ); }).fail(function() { - var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); + var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error'); }); }); diff --git a/lms/static/js/groups/views/cohort_discussions_inline.js b/lms/static/js/discussions_management/views/divided_discussions_inline.js similarity index 75% rename from lms/static/js/groups/views/cohort_discussions_inline.js rename to lms/static/js/discussions_management/views/divided_discussions_inline.js index 817b07d58f..a965b81ec9 100644 --- a/lms/static/js/groups/views/cohort_discussions_inline.js +++ b/lms/static/js/discussions_management/views/divided_discussions_inline.js @@ -1,9 +1,9 @@ (function(define) { 'use strict'; - define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions', + define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions', 'edx-ui-toolkit/js/utils/html-utils', 'js/vendor/jquery.qubit'], - function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) { - var InlineDiscussionsView = CohortDiscussionConfigurationView.extend({ + function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) { + var InlineDiscussionsView = DividedDiscussionConfigurationView.extend({ events: { 'change .check-discussion-category': 'setSaveButton', 'change .check-discussion-subcategory-inline': 'setSaveButton', @@ -13,17 +13,19 @@ }, initialize: function(options) { - this.template = HtmlUtils.template($('#cohort-discussions-inline-tpl').text()); - this.cohortSettings = options.cohortSettings; + this.template = HtmlUtils.template($('#divided-discussions-inline-tpl').text()); + this.discussionSettings = options.discussionSettings; }, render: function() { - var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions'), - inline_discussions = this.model.get('inline_discussions'); + var inlineDiscussions = this.model.get('inline_discussions'), + alwaysDivideInlineDiscussions = this.discussionSettings.get( + 'always_divide_inline_discussions' + ); - HtmlUtils.setHtml(this.$('.cohort-inline-discussions-nav'), this.template({ - inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inline_discussions), - alwaysCohortInlineDiscussions: alwaysCohortInlineDiscussions + HtmlUtils.setHtml(this.$('.inline-discussions-nav'), this.template({ + inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inlineDiscussions), + alwaysDivideInlineDiscussions: alwaysDivideInlineDiscussions })); // Provides the semantics for a nested list of tri-state checkboxes. @@ -32,7 +34,7 @@ // based on the checked values of any checkboxes in child elements of the DOM. this.$('ul.inline-topics').qubit(); - this.setElementsEnabled(alwaysCohortInlineDiscussions, true); + this.setElementsEnabled(alwaysDivideInlineDiscussions, true); }, /** @@ -99,45 +101,48 @@ * * Enable/Disable the category and sub-category checkboxes. * Enable/Disable the save button. - * @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes. - * @param {bool} enable_save_button - The flag to enable/disable the save button. + * @param {bool} enableCheckboxes - The flag to enable/disable the checkboxes. + * @param {bool} enableSaveButton - The flag to enable/disable the save button. */ - setElementsEnabled: function(enable_checkboxes, enable_save_button) { - this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes); - this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes); - this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button); + setElementsEnabled: function(enableCheckboxes, enableSaveButton) { + this.setDisabled(this.$('.check-discussion-category'), enableCheckboxes); + this.setDisabled(this.$('.check-discussion-subcategory-inline'), enableCheckboxes); + this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enableSaveButton); }, /** * Enables the save button for inline discussions. */ - setSaveButton: function(event) { + setSaveButton: function() { this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false); }, /** - * Sends the cohorted_inline_discussions to the server and renders the view. + * Sends the dividedInlineDiscussions to the server and renders the view. */ saveInlineDiscussionsForm: function(event) { - event.preventDefault(); - var self = this, - cohortedInlineDiscussions = self.getCohortedDiscussions( + dividedInlineDiscussions = self.getDividedDiscussions( '.check-discussion-subcategory-inline:checked' ), fieldData = { - cohorted_inline_discussions: cohortedInlineDiscussions, - always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked') + divided_inline_discussions: dividedInlineDiscussions, + always_divide_inline_discussions: self.$( + '.check-all-inline-discussions' + ).prop('checked') }; + event.preventDefault(); + self.saveForm(self.$('.inline-discussion-topics'), fieldData) .done(function() { self.model.fetch() .done(function() { self.render(); - self.showMessage(gettext('Your changes have been saved.'), self.$('.inline-discussion-topics')); + self.showMessage(gettext('Your changes have been saved.'), + self.$('.inline-discussion-topics')); }).fail(function() { - var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); + var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error'); }); }); diff --git a/lms/static/js/groups/models/course_cohort_settings.js b/lms/static/js/groups/models/course_cohort_settings.js index 213a14ec97..1c2a9c9d14 100644 --- a/lms/static/js/groups/models/course_cohort_settings.js +++ b/lms/static/js/groups/models/course_cohort_settings.js @@ -4,10 +4,7 @@ var CourseCohortSettingsModel = Backbone.Model.extend({ idAttribute: 'id', defaults: { - is_cohorted: false, - cohorted_inline_discussions: [], - cohorted_course_wide_discussions: [], - always_cohort_inline_discussions: false + is_cohorted: false } }); return CourseCohortSettingsModel; diff --git a/lms/static/js/groups/views/cohorts.js b/lms/static/js/groups/views/cohorts.js index 8c9614c680..44d2c0cdda 100644 --- a/lms/static/js/groups/views/cohorts.js +++ b/lms/static/js/groups/views/cohorts.js @@ -4,13 +4,11 @@ 'js/groups/models/verified_track_settings', 'js/groups/views/cohort_editor', 'js/groups/views/cohort_form', 'js/groups/views/course_cohort_settings_notification', - 'js/groups/views/cohort_discussions_inline', 'js/groups/views/cohort_discussions_course_wide', 'js/groups/views/verified_track_settings_notification', 'edx-ui-toolkit/js/utils/html-utils', 'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'], function($, _, Backbone, gettext, CohortModel, VerifiedTrackSettingsModel, CohortEditorView, CohortFormView, - CourseCohortSettingsNotificationView, InlineDiscussionsView, CourseWideDiscussionsView, - VerifiedTrackSettingsNotificationView, HtmlUtils) { + CourseCohortSettingsNotificationView, VerifiedTrackSettingsNotificationView, HtmlUtils) { var hiddenClass = 'is-hidden', disabledClass = 'is-disabled', enableCohortsSelector = '.cohorts-state'; @@ -25,7 +23,6 @@ 'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm', 'click .link-cross-reference': 'showSection', 'click .toggle-cohort-management-secondary': 'showCsvUpload', - 'click .toggle-cohort-management-discussions': 'showDiscussionTopics' }, initialize: function(options) { @@ -306,27 +303,6 @@ }).render(); } }, - showDiscussionTopics: function(event) { - event.preventDefault(); - - $(event.currentTarget).addClass(hiddenClass); - var cohortDiscussionsElement = this.$('.cohort-discussions-nav').removeClass(hiddenClass); - - if (!this.CourseWideDiscussionsView) { - this.CourseWideDiscussionsView = new CourseWideDiscussionsView({ - el: cohortDiscussionsElement, - model: this.context.discussionTopicsSettingsModel, - cohortSettings: this.cohortSettings - }).render(); - } - if (!this.InlineDiscussionsView) { - this.InlineDiscussionsView = new InlineDiscussionsView({ - el: cohortDiscussionsElement, - model: this.context.discussionTopicsSettingsModel, - cohortSettings: this.cohortSettings - }).render(); - } - }, getSectionCss: function(section) { return ".instructor-nav .nav-item [data-section='" + section + "']"; diff --git a/lms/static/js/groups/views/cohorts_dashboard_factory.js b/lms/static/js/groups/views/cohorts_dashboard_factory.js index e85ade7bdf..02e0a73b07 100644 --- a/lms/static/js/groups/views/cohorts_dashboard_factory.js +++ b/lms/static/js/groups/views/cohorts_dashboard_factory.js @@ -1,8 +1,8 @@ (function(define, undefined) { 'use strict'; define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings', - 'js/groups/models/cohort_discussions', 'js/groups/models/content_group'], - function($, CohortsView, CohortCollection, CourseCohortSettingsModel, DiscussionTopicsSettingsModel, ContentGroupModel) { + 'js/groups/models/content_group'], + function($, CohortsView, CohortCollection, CourseCohortSettingsModel, ContentGroupModel) { return function(contentGroups, studioGroupConfigurationsUrl) { var contentGroupModels = $.map(contentGroups, function(group) { return new ContentGroupModel({ @@ -14,33 +14,27 @@ var cohorts = new CohortCollection(), courseCohortSettings = new CourseCohortSettingsModel(), - discussionTopicsSettings = new DiscussionTopicsSettingsModel(); + $cohortManagementElement = $('.cohort-management'); - var cohortManagementElement = $('.cohort-management'); - - cohorts.url = cohortManagementElement.data('cohorts_url'); - courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url'); - discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url'); + cohorts.url = $cohortManagementElement.data('cohorts_url'); + courseCohortSettings.url = $cohortManagementElement.data('course_cohort_settings_url'); var cohortsView = new CohortsView({ - el: cohortManagementElement, + el: $cohortManagementElement, model: cohorts, contentGroups: contentGroupModels, cohortSettings: courseCohortSettings, context: { - discussionTopicsSettingsModel: discussionTopicsSettings, - uploadCohortsCsvUrl: cohortManagementElement.data('upload_cohorts_csv_url'), - verifiedTrackCohortingUrl: cohortManagementElement.data('verified_track_cohorting_url'), + uploadCohortsCsvUrl: $cohortManagementElement.data('upload_cohorts_csv_url'), + verifiedTrackCohortingUrl: $cohortManagementElement.data('verified_track_cohorting_url'), studioGroupConfigurationsUrl: studioGroupConfigurationsUrl, - isCcxEnabled: cohortManagementElement.data('is_ccx_enabled') + isCcxEnabled: $cohortManagementElement.data('is_ccx_enabled') } }); cohorts.fetch().done(function() { courseCohortSettings.fetch().done(function() { - discussionTopicsSettings.fetch().done(function() { - cohortsView.render(); - }); + cohortsView.render(); }); }); }; diff --git a/lms/static/js/instructor_dashboard/discussions_management.js b/lms/static/js/instructor_dashboard/discussions_management.js new file mode 100644 index 0000000000..1e883b708b --- /dev/null +++ b/lms/static/js/instructor_dashboard/discussions_management.js @@ -0,0 +1,12 @@ +(function() { + 'use strict'; + + function DiscussionsManagement($section) { + this.$section = $section; + this.$section.data('wrapper', this); + } + + DiscussionsManagement.prototype.onClickTitle = function() {}; + + window.InstructorDashboard.sections.DiscussionsManagement = DiscussionsManagement; +}).call(this); diff --git a/lms/static/js/instructor_dashboard/instructor_dashboard.js b/lms/static/js/instructor_dashboard/instructor_dashboard.js index 272e241855..d5352c7b72 100644 --- a/lms/static/js/instructor_dashboard/instructor_dashboard.js +++ b/lms/static/js/instructor_dashboard/instructor_dashboard.js @@ -187,6 +187,9 @@ such that the value can be defined later than this assignment (file load order). }, { constructor: window.InstructorDashboard.sections.CohortManagement, $element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management') + }, { + constructor: window.InstructorDashboard.sections.DiscussionsManagement, + $element: idashContent.find('.' + CSS_IDASH_SECTION + '#discussions_management') }, { constructor: window.InstructorDashboard.sections.Certificates, $element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates') diff --git a/lms/static/js/spec/groups/views/cohorts_spec.js b/lms/static/js/spec/groups/views/cohorts_spec.js index ba29285340..96ae5576de 100644 --- a/lms/static/js/spec/groups/views/cohorts_spec.js +++ b/lms/static/js/spec/groups/views/cohorts_spec.js @@ -1,14 +1,11 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', - 'common/js/spec_helpers/template_helpers', - 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group', - 'js/groups/models/course_cohort_settings', 'js/utils/animation', 'js/vendor/jquery.qubit', - 'js/groups/views/course_cohort_settings_notification', 'js/groups/models/cohort_discussions', - 'js/groups/views/cohort_discussions', 'js/groups/views/cohort_discussions_course_wide', - 'js/groups/views/cohort_discussions_inline' - ], + 'common/js/spec_helpers/template_helpers', + 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group', + 'js/groups/models/course_cohort_settings', 'js/utils/animation', 'js/vendor/jquery.qubit', + 'js/groups/views/course_cohort_settings_notification' +], function(Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel, - CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView, DiscussionTopicsSettingsModel, - CohortDiscussionsView, CohortCourseWideDiscussionsView, CohortInlineDiscussionsView) { + CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView) { 'use strict'; describe('Cohorts View', function() { @@ -20,16 +17,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers getAddModal, selectContentGroup, clearContentGroup, saveFormAndExpectErrors, createMockCohortSettings, MOCK_COHORTED_USER_PARTITION_ID, MOCK_UPLOAD_COHORTS_CSV_URL, MOCK_STUDIO_ADVANCED_SETTINGS_URL, MOCK_STUDIO_GROUP_CONFIGURATIONS_URL, - MOCK_VERIFIED_TRACK_COHORTING_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT, - createMockCohortDiscussionsJson, createMockCohortDiscussions, showAndAssertDiscussionTopics; - - // Selectors - var discussionsToggle = '.toggle-cohort-management-discussions', - inlineDiscussionsFormCss = '.cohort-inline-discussions-form', - courseWideDiscussionsFormCss = '.cohort-course-wide-discussions-form', - courseWideDiscussionsSaveButtonCss = '.cohort-course-wide-discussions-form .action-save', - inlineDiscussionsSaveButtonCss = '.cohort-inline-discussions-form .action-save', - inlineDiscussionsForm, courseWideDiscussionsForm; + MOCK_VERIFIED_TRACK_COHORTING_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT; MOCK_MANUAL_ASSIGNMENT = 'manual'; MOCK_RANDOM_ASSIGNMENT = 'random'; @@ -70,66 +58,16 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ]; }; - createMockCohortSettingsJson = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) { + createMockCohortSettingsJson = function(isCohorted) { return { id: 0, - is_cohorted: isCohorted || false, - cohorted_inline_discussions: cohortedInlineDiscussions || [], - cohorted_course_wide_discussions: cohortedCourseWideDiscussions || [], - always_cohort_inline_discussions: alwaysCohortInlineDiscussions || false + is_cohorted: isCohorted || false }; }; - createMockCohortSettings = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) { + createMockCohortSettings = function(isCohorted) { return new CourseCohortSettingsModel( - createMockCohortSettingsJson(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) - ); - }; - - createMockCohortDiscussionsJson = function(allCohorted) { - return { - course_wide_discussions: { - children: [['Topic_C_1', 'entry'], ['Topic_C_2', 'entry']], - entries: { - Topic_C_1: { - sort_key: null, - is_divided: true, - id: 'Topic_C_1' - }, - Topic_C_2: { - sort_key: null, - is_divided: false, - id: 'Topic_C_2' - } - } - }, - inline_discussions: { - subcategories: { - Topic_I_1: { - subcategories: {}, - children: [['Inline_Discussion_1', 'entry'], ['Inline_Discussion_2', 'entry']], - entries: { - Inline_Discussion_1: { - sort_key: null, - is_divided: true, - id: 'Inline_Discussion_1' - }, - Inline_Discussion_2: { - sort_key: null, - is_divided: allCohorted || false, - id: 'Inline_Discussion_2' - } - } - } - }, - children: [['Topic_I_1', 'subcategory']] - } - }; - }; - - createMockCohortDiscussions = function(allCohorted) { - return new DiscussionTopicsSettingsModel( - createMockCohortDiscussionsJson(allCohorted) + createMockCohortSettingsJson(isCohorted) ); }; @@ -146,7 +84,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers }; createCohortsView = function(test, options) { - var cohortsJson, cohorts, contentGroups, cohortSettings, cohortDiscussions; + var cohortsJson, cohorts, contentGroups, cohortSettings; options = options || {}; cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts(); cohorts = new CohortCollection(cohortsJson, {parse: true}); @@ -155,16 +93,12 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers cohortSettings.url = '/mock_service/cohorts/settings'; cohorts.url = '/mock_service/cohorts'; - cohortDiscussions = options.cohortDiscussions || createMockCohortDiscussions(); - cohortDiscussions.url = '/mock_service/cohorts/discussion/topics'; - requests = AjaxHelpers.requests(test); cohortsView = new CohortsView({ model: cohorts, contentGroups: contentGroups, cohortSettings: cohortSettings, context: { - discussionTopicsSettingsModel: cohortDiscussions, uploadCohortsCsvUrl: MOCK_UPLOAD_COHORTS_CSV_URL, studioAdvancedSettingsUrl: MOCK_STUDIO_ADVANCED_SETTINGS_URL, studioGroupConfigurationsUrl: MOCK_STUDIO_GROUP_CONFIGURATIONS_URL, @@ -314,40 +248,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers verifyDetailedMessage(expectedTitle, 'error', errors); }; - showAndAssertDiscussionTopics = function(that) { - createCohortsView(that); - - // Should see the control to toggle cohort discussions. - expect(cohortsView.$(discussionsToggle)).not.toHaveClass('is-hidden'); - // But discussions form should not be visible until toggle is clicked. - expect(cohortsView.$(inlineDiscussionsFormCss).length).toBe(0); - expect(cohortsView.$(courseWideDiscussionsFormCss).length).toBe(0); - - expect(cohortsView.$(discussionsToggle).text()). - toContain('Specify whether discussion topics are divided by cohort'); - - cohortsView.$(discussionsToggle).click(); - // After toggle is clicked, it should be hidden. - expect(cohortsView.$(discussionsToggle)).toHaveClass('is-hidden'); - - // Should see the course wide discussions form and its content - courseWideDiscussionsForm = cohortsView.$(courseWideDiscussionsFormCss); - expect(courseWideDiscussionsForm.length).toBe(1); - - expect(courseWideDiscussionsForm.text()). - toContain('Course-Wide Discussion Topics'); - expect(courseWideDiscussionsForm.text()). - toContain('Select the course-wide discussion topics that you want to divide by cohort.'); - - // Should see the inline discussions form and its content - inlineDiscussionsForm = cohortsView.$(inlineDiscussionsFormCss); - expect(inlineDiscussionsForm.length).toBe(1); - expect(inlineDiscussionsForm.text()). - toContain('Content-Specific Discussion Topics'); - expect(inlineDiscussionsForm.text()). - toContain('Specify whether content-specific discussion topics are divided by cohort.'); - }; - unknownUserMessage = function(name) { return 'Unknown user: ' + name; }; @@ -364,10 +264,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-group-header'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification'); TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-state'); - TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-category'); - TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-subcategory'); - TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-course-wide'); - TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-inline'); TemplateHelpers.installTemplate('templates/file-upload'); }); @@ -381,8 +277,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers // If no cohorts have been created, can't upload a CSV file. expect(cohortsView.$('.wrapper-cohort-supplemental')).toHaveClass('is-hidden'); - // if no cohorts have been created, can't show the link to discussion topics. - expect(cohortsView.$('.cohort-discussions-nav')).toHaveClass('is-hidden'); }); it('syncs data when membership tab is clicked', function() { @@ -422,10 +316,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers .toBe("Your file 'upload_file.txt' has been uploaded. Allow a few minutes for processing."); }); - it('can show discussion topics if cohort exists', function() { - showAndAssertDiscussionTopics(this); - }); - describe('Cohort Selector', function() { it('has no initial selection', function() { createCohortsView(this); @@ -1172,375 +1062,5 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers }); }); }); - - describe('Discussion Topics', function() { - var createCourseWideView, createInlineView, - inlineView, courseWideView, assertCohortedTopics; - - createCourseWideView = function(that) { - createCohortsView(that); - - courseWideView = new CohortCourseWideDiscussionsView({ - el: cohortsView.$('.cohort-discussions-nav').removeClass('is-hidden'), - model: cohortsView.context.discussionTopicsSettingsModel, - cohortSettings: cohortsView.cohortSettings - }); - courseWideView.render(); - }; - - createInlineView = function(that, discussionTopicsSettingsModel) { - createCohortsView(that); - - inlineView = new CohortInlineDiscussionsView({ - el: cohortsView.$('.cohort-discussions-nav').removeClass('is-hidden'), - model: discussionTopicsSettingsModel || cohortsView.context.discussionTopicsSettingsModel, - cohortSettings: cohortsView.cohortSettings - }); - inlineView.render(); - }; - - assertCohortedTopics = function(view, type) { - expect(view.$('.check-discussion-subcategory-' + type).length).toBe(2); - expect(view.$('.check-discussion-subcategory-' + type + ':checked').length).toBe(1); - }; - - it('renders the view properly', function() { - showAndAssertDiscussionTopics(this); - }); - - describe('Course Wide', function() { - it('shows the "Save" button as disabled initially', function() { - createCourseWideView(this); - expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy(); - }); - - it('has one cohorted and one non-cohorted topic', function() { - createCourseWideView(this); - - assertCohortedTopics(courseWideView, 'course-wide'); - - expect(courseWideView.$('.cohorted-text').length).toBe(2); - expect(courseWideView.$('.cohorted-text.hidden').length).toBe(1); - }); - - it('enables the "Save" button after changing checkbox', function() { - createCourseWideView(this); - - // save button is disabled. - expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy(); - - $(courseWideView.$('.check-discussion-subcategory-course-wide')[0]).prop('checked', false).change(); - - // save button is enabled. - expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy(); - }); - - it('saves the topic successfully', function() { - createCourseWideView(this); - - $(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change(); - expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy(); - - // Save the updated settings - courseWideView.$('.action-save').click(); - - // fake requests for cohort settings with PATCH method. - AjaxHelpers.expectJsonRequest( - requests, 'PATCH', '/mock_service/cohorts/settings', - {cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']} - ); - AjaxHelpers.respondWithJson( - requests, - {cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']} - ); - - // fake request for discussion/topics with GET method. - AjaxHelpers.expectJsonRequest( - requests, 'GET', '/mock_service/cohorts/discussion/topics' - ); - AjaxHelpers.respondWithJson( - requests, - createMockCohortDiscussions() - ); - - // verify the success message. - expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy(); - verifyMessage('Your changes have been saved.', 'confirmation'); - }); - - it('shows an appropriate message when subsequent "GET" returns HTTP500', function() { - createCourseWideView(this); - - $(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change(); - expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy(); - - // Save the updated settings - courseWideView.$('.action-save').click(); - - // fake requests for cohort settings with PATCH method. - AjaxHelpers.expectJsonRequest( - requests, 'PATCH', '/mock_service/cohorts/settings', - {cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']} - ); - AjaxHelpers.respondWithJson( - requests, - {cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']} - ); - - // fake request for discussion/topics with GET method. - AjaxHelpers.expectJsonRequest( - requests, 'GET', '/mock_service/cohorts/discussion/topics' - ); - AjaxHelpers.respondWithError(requests, 500); - - var expectedTitle = "We've encountered an error. Refresh your browser and then try again."; - expect(courseWideView.$('.message-title').text().trim()).toBe(expectedTitle); - }); - - it('shows an appropriate error message for HTTP500', function() { - createCourseWideView(this); - - $(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change(); - courseWideView.$('.action-save').click(); - - AjaxHelpers.respondWithError(requests, 500); - var expectedTitle = "We've encountered an error. Refresh your browser and then try again."; - expect(courseWideView.$('.message-title').text().trim()).toBe(expectedTitle); - }); - }); - - describe('Inline', function() { - var enableSaveButton, mockGetRequest, verifySuccess, mockPatchRequest; - - enableSaveButton = function() { - // enable the inline discussion topics. - inlineView.$('.check-cohort-inline-discussions').prop('checked', 'checked').change(); - - $(inlineView.$('.check-discussion-subcategory-inline')[0]).prop('checked', 'checked').change(); - - expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy(); - }; - - verifySuccess = function() { - // verify the success message. - expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy(); - verifyMessage('Your changes have been saved.', 'confirmation'); - }; - - mockPatchRequest = function(cohortedInlineDiscussions) { - AjaxHelpers.expectJsonRequest( - requests, 'PATCH', '/mock_service/cohorts/settings', - { - cohorted_inline_discussions: cohortedInlineDiscussions, - always_cohort_inline_discussions: false - } - ); - AjaxHelpers.respondWithJson( - requests, - { - cohorted_inline_discussions: cohortedInlineDiscussions, - always_cohort_inline_discussions: false - } - ); - }; - - mockGetRequest = function(allCohorted) { - // fake request for discussion/topics with GET method. - AjaxHelpers.expectJsonRequest( - requests, 'GET', '/mock_service/cohorts/discussion/topics' - ); - AjaxHelpers.respondWithJson( - requests, - createMockCohortDiscussions(allCohorted) - ); - }; - - it('shows the "Save" button as disabled initially', function() { - createInlineView(this); - expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy(); - }); - - it('shows always cohort radio button as selected', function() { - createInlineView(this); - inlineView.$('.check-all-inline-discussions').prop('checked', 'checked').change(); - - // verify always cohort inline discussions is being selected. - expect(inlineView.$('.check-all-inline-discussions').prop('checked')).toBeTruthy(); - - // verify that inline topics are disabled - expect(inlineView.$('.check-discussion-subcategory-inline').prop('disabled')).toBeTruthy(); - expect(inlineView.$('.check-discussion-category').prop('disabled')).toBeTruthy(); - - // verify that cohort some topics are not being selected. - expect(inlineView.$('.check-cohort-inline-discussions').prop('checked')).toBeFalsy(); - }); - - it('shows cohort some topics radio button as selected', function() { - createInlineView(this); - inlineView.$('.check-cohort-inline-discussions').prop('checked', 'checked').change(); - - // verify some cohort inline discussions radio is being selected. - expect(inlineView.$('.check-cohort-inline-discussions').prop('checked')).toBeTruthy(); - - // verify always cohort radio is not selected. - expect(inlineView.$('.check-all-inline-discussions').prop('checked')).toBeFalsy(); - - // verify that inline topics are enabled - expect(inlineView.$('.check-discussion-subcategory-inline').prop('disabled')).toBeFalsy(); - expect(inlineView.$('.check-discussion-category').prop('disabled')).toBeFalsy(); - }); - - it('has cohorted and non-cohorted topics', function() { - createInlineView(this); - enableSaveButton(); - assertCohortedTopics(inlineView, 'inline'); - }); - - it('enables "Save" button after changing from always inline option', function() { - createInlineView(this); - enableSaveButton(); - }); - - it('saves the topic', function() { - createInlineView(this); - enableSaveButton(); - - // Save the updated settings - inlineView.$('.action-save').click(); - - mockPatchRequest(['Inline_Discussion_1']); - mockGetRequest(); - - verifySuccess(); - }); - - it('selects the parent category when all children are selected', function() { - createInlineView(this); - enableSaveButton(); - - // parent category should be indeterminate. - expect(inlineView.$('.check-discussion-category:checked').length).toBe(0); - expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1); - - inlineView.$('.check-discussion-subcategory-inline').prop('checked', 'checked').change(); - // parent should be checked as we checked all children - expect(inlineView.$('.check-discussion-category:checked').length).toBe(1); - }); - - it('selects/deselects all children when a parent category is selected/deselected', function() { - createInlineView(this); - enableSaveButton(); - - expect(inlineView.$('.check-discussion-category:checked').length).toBe(0); - - inlineView.$('.check-discussion-category').prop('checked', 'checked').change(); - - expect(inlineView.$('.check-discussion-category:checked').length).toBe(1); - expect(inlineView.$('.check-discussion-subcategory-inline:checked').length).toBe(2); - - // un-check the parent, all children should be unchecd. - inlineView.$('.check-discussion-category').prop('checked', false).change(); - expect(inlineView.$('.check-discussion-category:checked').length).toBe(0); - expect(inlineView.$('.check-discussion-subcategory-inline:checked').length).toBe(0); - }); - - it('saves correctly when a subset of topics are selected within a category', function() { - createInlineView(this); - enableSaveButton(); - - // parent category should be indeterminate. - expect(inlineView.$('.check-discussion-category:checked').length).toBe(0); - expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1); - - // Save the updated settings - inlineView.$('.action-save').click(); - - mockPatchRequest(['Inline_Discussion_1']); - mockGetRequest(); - - verifySuccess(); - // parent category should be indeterminate. - expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1); - }); - - it('saves correctly when all child topics are selected within a category', function() { - createInlineView(this); - enableSaveButton(); - - // parent category should be indeterminate. - expect(inlineView.$('.check-discussion-category:checked').length).toBe(0); - expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1); - - inlineView.$('.check-discussion-subcategory-inline').prop('checked', 'checked').change(); - // Save the updated settings - inlineView.$('.action-save').click(); - - mockPatchRequest(['Inline_Discussion_1', 'Inline_Discussion_2']); - mockGetRequest(true); - - verifySuccess(); - // parent category should be checked. - expect(inlineView.$('.check-discussion-category:checked').length).toBe(1); - }); - - it('shows an appropriate message when no inline topics exist', function() { - var topicsJson, discussionTopicsSettingsModel; - - topicsJson = { - course_wide_discussions: { - children: [['Topic_C_1', 'entry']], - entries: { - Topic_C_1: { - sort_key: null, - is_divided: true, - id: 'Topic_C_1' - } - } - }, - inline_discussions: { - subcategories: {}, - children: [] - } - }; - discussionTopicsSettingsModel = new DiscussionTopicsSettingsModel(topicsJson); - - createInlineView(this, discussionTopicsSettingsModel); - - var expectedTitle = 'No content-specific discussion topics exist.'; - expect(inlineView.$('.no-topics').text().trim()).toBe(expectedTitle); - }); - - it('shows an appropriate message when subsequent "GET" returns HTTP500', function() { - createInlineView(this); - enableSaveButton(); - - // Save the updated settings - inlineView.$('.action-save').click(); - - mockPatchRequest(['Inline_Discussion_1']); - - // fake request for discussion/topics with GET method. - AjaxHelpers.expectJsonRequest( - requests, 'GET', '/mock_service/cohorts/discussion/topics' - ); - AjaxHelpers.respondWithError(requests, 500); - - var expectedTitle = "We've encountered an error. Refresh your browser and then try again."; - expect(inlineView.$('.message-title').text().trim()).toBe(expectedTitle); - }); - - it('shows an appropriate error message for HTTP500', function() { - createInlineView(this); - enableSaveButton(); - - $(inlineView.$('.check-discussion-subcategory-inline')[1]).prop('checked', 'checked').change(); - inlineView.$('.action-save').click(); - - AjaxHelpers.respondWithError(requests, 500); - var expectedTitle = "We've encountered an error. Refresh your browser and then try again."; - expect(inlineView.$('.message-title').text().trim()).toBe(expectedTitle); - }); - }); - }); }); }); diff --git a/lms/static/js/spec/groups/views/discussions_spec.js b/lms/static/js/spec/groups/views/discussions_spec.js new file mode 100644 index 0000000000..6a11910755 --- /dev/null +++ b/lms/static/js/spec/groups/views/discussions_spec.js @@ -0,0 +1,512 @@ +define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', + 'common/js/spec_helpers/template_helpers', + 'js/discussions_management/views/discussions', 'js/discussions_management/models/course_discussions_detail', + 'js/discussions_management/models/course_discussions_settings' +], + function(Backbone, $, AjaxHelpers, TemplateHelpers, DiscussionsView, CourseDiscussionTopicDetailsModel, + CourseDiscussionsSettingsModel) { + 'use strict'; + + describe('Discussions View', function() { + var createMockDiscussionsSettingsJson, createDiscussionsView, discussionsView, requests, verifyMessage, + createMockDiscussionsSettings, createMockDiscussionsJson, createMockDiscussions, + showAndAssertDiscussionTopics; + + // Selectors + var inlineDiscussionsFormCss = '.cohort-inline-discussions-form', + courseWideDiscussionsFormCss = '.cohort-course-wide-discussions-form', + courseWideDiscussionsSaveButtonCss = '.cohort-course-wide-discussions-form .action-save', + inlineDiscussionsSaveButtonCss = '.cohort-inline-discussions-form .action-save'; + + createMockDiscussionsSettingsJson = function(dividedInlineDiscussions, + dividedCourseWideDiscussions, + alwaysDivideInlineDiscussions) { + return { + id: 0, + divided_inline_discussions: dividedInlineDiscussions || [], + divided_course_wide_discussions: dividedCourseWideDiscussions || [], + always_divide_inline_discussions: alwaysDivideInlineDiscussions || false + }; + }; + + createMockDiscussionsSettings = function(dividedInlineDiscussions, + dividedCourseWideDiscussions, + alwaysDivideInlineDiscussions) { + return new CourseDiscussionsSettingsModel( + createMockDiscussionsSettingsJson(dividedInlineDiscussions, + dividedCourseWideDiscussions, + alwaysDivideInlineDiscussions) + ); + }; + + createMockDiscussionsJson = function(allDivided) { + return { + course_wide_discussions: { + children: [['Topic_C_1', 'entry'], ['Topic_C_2', 'entry']], + entries: { + Topic_C_1: { + sort_key: null, + is_divided: true, + id: 'Topic_C_1' + }, + Topic_C_2: { + sort_key: null, + is_divided: false, + id: 'Topic_C_2' + } + } + }, + inline_discussions: { + subcategories: { + Topic_I_1: { + subcategories: {}, + children: [['Inline_Discussion_1', 'entry'], ['Inline_Discussion_2', 'entry']], + entries: { + Inline_Discussion_1: { + sort_key: null, + is_divided: true, + id: 'Inline_Discussion_1' + }, + Inline_Discussion_2: { + sort_key: null, + is_divided: allDivided || false, + id: 'Inline_Discussion_2' + } + } + } + }, + children: [['Topic_I_1', 'subcategory']] + } + }; + }; + + createMockDiscussions = function(allDivided) { + return new CourseDiscussionTopicDetailsModel( + createMockDiscussionsJson(allDivided) + ); + }; + + verifyMessage = function(expectedTitle, expectedMessageType, expectedAction, hasDetails) { + expect(discussionsView.$('.message-title').text().trim()).toBe(expectedTitle); + expect(discussionsView.$('div.message')).toHaveClass('message-' + expectedMessageType); + if (expectedAction) { + expect(discussionsView.$('.message-actions .action-primary').text().trim()).toBe(expectedAction); + } else { + expect(discussionsView.$('.message-actions .action-primary').length).toBe(0); + } if (!hasDetails) { + expect(discussionsView.$('.summary-items').length).toBe(0); + } + }; + + createDiscussionsView = function(test, options) { + var discussionSettings, dividedDiscussions, discussionOptions; + discussionOptions = options || {}; + discussionSettings = discussionOptions.cohortSettings || createMockDiscussionsSettings(); + discussionSettings.url = '/mock_service/discussions/settings'; + + dividedDiscussions = discussionOptions.dividedDiscussions || createMockDiscussions(); + dividedDiscussions.url = '/mock_service/discussion/topics'; + + requests = AjaxHelpers.requests(test); + discussionsView = new DiscussionsView({ + el: $('.discussions-management'), + discussionSettings: discussionSettings, + context: { + courseDiscussionTopicDetailsModel: dividedDiscussions + } + }); + discussionsView.render(); + }; + + showAndAssertDiscussionTopics = function() { + var $courseWideDiscussionsForm, + $inlineDiscussionsForm; + + $courseWideDiscussionsForm = discussionsView.$(courseWideDiscussionsFormCss); + $inlineDiscussionsForm = discussionsView.$(inlineDiscussionsFormCss); + + // Discussions form should not be visible. + expect($inlineDiscussionsForm.length).toBe(1); + expect($courseWideDiscussionsForm.length).toBe(1); + + expect($courseWideDiscussionsForm.text()). + toContain('Course-Wide Discussion Topics'); + expect($courseWideDiscussionsForm.text()). + toContain('Select the course-wide discussion topics that you want to divide by cohort.'); + + // Should see the inline discussions form and its content + expect($inlineDiscussionsForm.length).toBe(1); + expect($inlineDiscussionsForm.text()). + toContain('Content-Specific Discussion Topics'); + expect($inlineDiscussionsForm.text()). + toContain('Specify whether content-specific discussion topics are divided by cohort.'); + }; + + beforeEach(function() { + setFixtures('