1452 lines
63 KiB
Python
1452 lines
63 KiB
Python
"""
|
|
Tests for discussion pages
|
|
"""
|
|
|
|
import datetime
|
|
import time
|
|
from unittest import skip
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from pytz import UTC
|
|
|
|
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
|
|
from common.test.acceptance.fixtures.discussion import (
|
|
Comment,
|
|
Response,
|
|
SearchResult,
|
|
SearchResultFixture,
|
|
SingleThreadViewFixture,
|
|
Thread,
|
|
UserProfileViewFixture
|
|
)
|
|
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
|
from common.test.acceptance.pages.lms.courseware import CoursewarePage
|
|
from common.test.acceptance.pages.lms.discussion import (
|
|
DiscussionSortPreferencePage,
|
|
DiscussionTabHomePage,
|
|
DiscussionTabSingleThreadPage,
|
|
DiscussionUserProfilePage,
|
|
InlineDiscussionPage
|
|
)
|
|
from common.test.acceptance.pages.lms.learner_profile import LearnerProfilePage
|
|
from common.test.acceptance.pages.lms.tab_nav import TabNavPage
|
|
from common.test.acceptance.tests.discussion.helpers import BaseDiscussionMixin, BaseDiscussionTestCase
|
|
from common.test.acceptance.tests.helpers import UniqueCourseTest, get_modal_alert, skip_if_browser
|
|
from openedx.core.lib.tests import attr
|
|
|
|
|
|
THREAD_CONTENT_WITH_LATEX = u"""Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
\n\n----------\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur. (b).\n\n
|
|
**(a)** $H_1(e^{j\\omega}) = \\sum_{n=-\\infty}^{\\infty}h_1[n]e^{-j\\omega n} =
|
|
\\sum_{n=-\\infty} ^{\\infty}h[n]e^{-j\\omega n}+\\delta_2e^{-j\\omega n_0}$
|
|
$= H(e^{j\\omega})+\\delta_2e^{-j\\omega n_0}=A_e (e^{j\\omega}) e^{-j\\omega n_0}
|
|
+\\delta_2e^{-j\\omega n_0}=e^{-j\\omega n_0} (A_e(e^{j\\omega})+\\delta_2)
|
|
$H_3(e^{j\\omega})=A_e(e^{j\\omega})+\\delta_2$. Dummy $A_e(e^{j\\omega})$ dummy post $.
|
|
$A_e(e^{j\\omega}) \\ge -\\delta_2$, it follows that $H_3(e^{j\\omega})$ is real and
|
|
$H_3(e^{j\\omega})\\ge 0$.\n\n**(b)** Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.\n\n
|
|
**Case 1:** If $re^{j\\theta}$ is a Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
\n\n**Case 3:** Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
Lorem $H_3(e^{j\\omega}) = P(cos\\omega)(cos\\omega - cos\\theta)^k$,
|
|
Lorem Lorem Lorem Lorem Lorem Lorem $P(cos\\omega)$ has no
|
|
$(cos\\omega - cos\\theta)$ factor.
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
$P(cos\\theta) \\neq 0$. Since $P(cos\\omega)$ this is a dummy data post $\\omega$,
|
|
dummy $\\delta > 0$ such that for all $\\omega$ dummy $|\\omega - \\theta|
|
|
< \\delta$, $P(cos\\omega)$ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
|
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
|
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit sse cillum dolore
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
|
"""
|
|
|
|
|
|
class DiscussionResponsePaginationTestMixin(BaseDiscussionMixin):
|
|
"""
|
|
A mixin containing tests for response pagination for use by both inline
|
|
discussion and the discussion tab
|
|
"""
|
|
def assert_response_display_correct(self, response_total, displayed_responses):
|
|
"""
|
|
Assert that various aspects of the display of responses are all correct:
|
|
* Text indicating total number of responses
|
|
* Presence of "Add a response" button
|
|
* Number of responses actually displayed
|
|
* Presence and text of indicator of how many responses are shown
|
|
* Presence and text of button to load more responses
|
|
"""
|
|
self.assertEqual(
|
|
self.thread_page.get_response_total_text(),
|
|
str(response_total) + " responses"
|
|
)
|
|
self.assertEqual(self.thread_page.has_add_response_button(), response_total != 0)
|
|
self.assertEqual(self.thread_page.get_num_displayed_responses(), displayed_responses)
|
|
self.assertEqual(
|
|
self.thread_page.get_shown_responses_text(),
|
|
(
|
|
None if response_total == 0 else
|
|
"Showing all responses" if response_total == displayed_responses else
|
|
u"Showing first {} responses".format(displayed_responses)
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
self.thread_page.get_load_responses_button_text(),
|
|
(
|
|
None if response_total == displayed_responses else
|
|
"Load all responses" if response_total - displayed_responses < 100 else
|
|
"Load next 100 responses"
|
|
)
|
|
)
|
|
|
|
def test_pagination_no_responses(self):
|
|
self.setup_thread(0)
|
|
self.assert_response_display_correct(0, 0)
|
|
|
|
def test_pagination_few_responses(self):
|
|
self.setup_thread(5)
|
|
self.assert_response_display_correct(5, 5)
|
|
|
|
def test_pagination_two_response_pages(self):
|
|
self.setup_thread(50)
|
|
self.assert_response_display_correct(50, 25)
|
|
|
|
self.thread_page.load_more_responses()
|
|
self.assert_response_display_correct(50, 50)
|
|
|
|
def test_pagination_exactly_two_response_pages(self):
|
|
self.setup_thread(125)
|
|
self.assert_response_display_correct(125, 25)
|
|
|
|
self.thread_page.load_more_responses()
|
|
self.assert_response_display_correct(125, 125)
|
|
|
|
def test_pagination_three_response_pages(self):
|
|
self.setup_thread(150)
|
|
self.assert_response_display_correct(150, 25)
|
|
|
|
self.thread_page.load_more_responses()
|
|
self.assert_response_display_correct(150, 125)
|
|
|
|
self.thread_page.load_more_responses()
|
|
self.assert_response_display_correct(150, 150)
|
|
|
|
def test_add_response_button(self):
|
|
self.setup_thread(5)
|
|
self.assertTrue(self.thread_page.has_add_response_button())
|
|
self.thread_page.click_add_response_button()
|
|
|
|
def test_add_response_button_closed_thread(self):
|
|
self.setup_thread(5, closed=True)
|
|
self.assertFalse(self.thread_page.has_add_response_button())
|
|
|
|
|
|
@attr(shard=2)
|
|
class DiscussionHomePageTest(BaseDiscussionTestCase):
|
|
"""
|
|
Tests for the discussion home page.
|
|
"""
|
|
|
|
SEARCHED_USERNAME = "gizmo"
|
|
|
|
def setUp(self):
|
|
super(DiscussionHomePageTest, self).setUp()
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
self.page = DiscussionTabHomePage(self.browser, self.course_id)
|
|
self.page.visit()
|
|
|
|
@attr(shard=2)
|
|
def test_new_post_button(self):
|
|
"""
|
|
Scenario: I can create new posts from the Discussion home page.
|
|
Given that I am on the Discussion home page
|
|
When I click on the 'New Post' button
|
|
Then I should be shown the new post form
|
|
"""
|
|
self.assertIsNotNone(self.page.new_post_button)
|
|
self.page.click_new_post_button()
|
|
self.assertIsNotNone(self.page.new_post_form)
|
|
|
|
def test_receive_update_checkbox(self):
|
|
"""
|
|
Scenario: I can save the receive update email notification checkbox
|
|
on Discussion home page.
|
|
Given that I am on the Discussion home page
|
|
When I click on the 'Receive update' checkbox
|
|
Then it should always shown selected.
|
|
"""
|
|
receive_updates_selector = '.email-setting'
|
|
receive_updates_checkbox = self.page.is_element_visible(receive_updates_selector)
|
|
self.assertTrue(receive_updates_checkbox)
|
|
|
|
self.assertFalse(self.page.is_checkbox_selected(receive_updates_selector))
|
|
self.page.click_element(receive_updates_selector)
|
|
|
|
self.assertTrue(self.page.is_checkbox_selected(receive_updates_selector))
|
|
self.page.refresh_and_wait_for_load()
|
|
self.assertTrue(self.page.is_checkbox_selected(receive_updates_selector))
|
|
|
|
@attr('a11y')
|
|
def test_page_accessibility(self):
|
|
self.page.a11y_audit.config.set_rules({
|
|
"ignore": [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
self.page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
|
|
@attr(shard=2)
|
|
class DiscussionNavigationTest(BaseDiscussionTestCase):
|
|
"""
|
|
Tests for breadcrumbs navigation in the Discussions page nav bar
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(DiscussionNavigationTest, self).setUp()
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
|
|
thread_id = "test_thread_{}".format(uuid4().hex)
|
|
thread_fixture = SingleThreadViewFixture(
|
|
Thread(
|
|
id=thread_id,
|
|
body=THREAD_CONTENT_WITH_LATEX,
|
|
commentable_id=self.discussion_id
|
|
)
|
|
)
|
|
thread_fixture.push()
|
|
self.thread_page = DiscussionTabSingleThreadPage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.discussion_id,
|
|
thread_id
|
|
)
|
|
self.thread_page.visit()
|
|
|
|
@skip("andya: 10/19/17: re-enable once the failure on Jenkins is determined")
|
|
def test_breadcrumbs_push_topic(self):
|
|
topic_button = self.thread_page.q(
|
|
css=".forum-nav-browse-menu-item[data-discussion-id='{}']".format(self.discussion_id)
|
|
)
|
|
self.assertTrue(topic_button.visible)
|
|
|
|
topic_button.click()
|
|
|
|
# Verify the thread's topic has been pushed to breadcrumbs
|
|
breadcrumbs = self.thread_page.q(css=".breadcrumbs .nav-item")
|
|
self.assertEqual(len(breadcrumbs), 3)
|
|
self.assertEqual(breadcrumbs[2].text, "Topic-Level Student-Visible Label")
|
|
|
|
@skip("andya: 10/19/17: re-enable once the failure on Jenkins is determined")
|
|
def test_breadcrumbs_back_to_all_topics(self):
|
|
topic_button = self.thread_page.q(
|
|
css=".forum-nav-browse-menu-item[data-discussion-id='{}']".format(self.discussion_id)
|
|
)
|
|
self.assertTrue(topic_button.visible)
|
|
topic_button.click()
|
|
|
|
# Verify clicking the first breadcrumb takes you back to all topics
|
|
self.thread_page.q(css=".breadcrumbs .nav-item")[0].click()
|
|
self.assertEqual(len(self.thread_page.q(css=".breadcrumbs .nav-item")), 1)
|
|
|
|
def test_breadcrumbs_clear_search(self):
|
|
self.thread_page.q(css=".search-input").fill("search text")
|
|
self.thread_page.q(css=".search-button").click()
|
|
|
|
# Verify that clicking the first breadcrumb clears your search
|
|
self.thread_page.q(css=".breadcrumbs .nav-item")[0].click()
|
|
self.assertEqual(self.thread_page.q(css=".search-input").text[0], "")
|
|
|
|
@skip("andya: 10/19/17: re-enable once the failure on Jenkins is determined")
|
|
def test_navigation_and_sorting(self):
|
|
"""
|
|
Test that after adding the post, user sorting preference is changing properly
|
|
and recently added post is shown.
|
|
"""
|
|
topic_button = self.thread_page.q(
|
|
css=".forum-nav-browse-menu-item[data-discussion-id='{}']".format(self.discussion_id)
|
|
)
|
|
self.assertTrue(topic_button.visible)
|
|
topic_button.click()
|
|
sort_page = DiscussionSortPreferencePage(self.browser, self.course_id)
|
|
for sort_type in ["votes", "comments", "activity"]:
|
|
sort_page.change_sort_preference(sort_type)
|
|
# Verify that recently added post titled "dummy thread title" is shown in each sorting preference
|
|
self.assertEqual(self.thread_page.q(css=".forum-nav-thread-title").text[0], 'dummy thread title')
|
|
|
|
|
|
@attr(shard=19)
|
|
class DiscussionTabSingleThreadTest(BaseDiscussionTestCase, DiscussionResponsePaginationTestMixin):
|
|
"""
|
|
Tests for the discussion page displaying a single thread
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(DiscussionTabSingleThreadTest, self).setUp()
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
self.tab_nav = TabNavPage(self.browser)
|
|
|
|
def setup_thread_page(self, thread_id):
|
|
self.thread_page = self.create_single_thread_page(thread_id) # pylint: disable=attribute-defined-outside-init
|
|
self.thread_page.visit()
|
|
|
|
@skip("andya: 10/19/17: re-enable once the failure on Jenkins is determined")
|
|
def test_mathjax_rendering(self):
|
|
thread_id = "test_thread_{}".format(uuid4().hex)
|
|
|
|
thread_fixture = SingleThreadViewFixture(
|
|
Thread(
|
|
id=thread_id,
|
|
body=THREAD_CONTENT_WITH_LATEX,
|
|
commentable_id=self.discussion_id,
|
|
thread_type="discussion"
|
|
)
|
|
)
|
|
thread_fixture.push()
|
|
self.setup_thread_page(thread_id)
|
|
self.assertTrue(self.thread_page.is_discussion_body_visible())
|
|
self.thread_page.verify_mathjax_preview_available()
|
|
self.thread_page.verify_mathjax_rendered()
|
|
|
|
def test_markdown_reference_link(self):
|
|
"""
|
|
Check markdown editor renders reference link correctly
|
|
and colon(:) in reference link is not converted to %3a
|
|
"""
|
|
sample_link = "http://example.com/colon:test"
|
|
thread_content = """[enter link description here][1]\n[1]: http://example.com/colon:test"""
|
|
thread_id = "test_thread_{}".format(uuid4().hex)
|
|
thread_fixture = SingleThreadViewFixture(
|
|
Thread(
|
|
id=thread_id,
|
|
body=thread_content,
|
|
commentable_id=self.discussion_id,
|
|
thread_type="discussion"
|
|
)
|
|
)
|
|
thread_fixture.push()
|
|
self.setup_thread_page(thread_id)
|
|
self.assertEqual(self.thread_page.get_link_href(), sample_link)
|
|
|
|
def test_marked_answer_comments(self):
|
|
thread_id = "test_thread_{}".format(uuid4().hex)
|
|
response_id = "test_response_{}".format(uuid4().hex)
|
|
comment_id = "test_comment_{}".format(uuid4().hex)
|
|
thread_fixture = SingleThreadViewFixture(
|
|
Thread(id=thread_id, commentable_id=self.discussion_id, thread_type="question")
|
|
)
|
|
thread_fixture.addResponse(
|
|
Response(id=response_id, endorsed=True),
|
|
[Comment(id=comment_id)]
|
|
)
|
|
thread_fixture.push()
|
|
self.setup_thread_page(thread_id)
|
|
self.assertFalse(self.thread_page.is_comment_visible(comment_id))
|
|
self.assertFalse(self.thread_page.is_add_comment_visible(response_id))
|
|
self.assertTrue(self.thread_page.is_show_comments_visible(response_id))
|
|
self.thread_page.show_comments(response_id)
|
|
self.assertTrue(self.thread_page.is_comment_visible(comment_id))
|
|
self.assertTrue(self.thread_page.is_add_comment_visible(response_id))
|
|
self.assertFalse(self.thread_page.is_show_comments_visible(response_id))
|
|
|
|
def test_discussion_blackout_period(self):
|
|
"""
|
|
Verify that new discussion can not be started during course blackout period.
|
|
|
|
Blackout period is the period between which students cannot post new or contribute
|
|
to existing discussions.
|
|
"""
|
|
now = datetime.datetime.now(UTC)
|
|
# Update course advance settings with a valid blackout period.
|
|
self.course_fixture.add_advanced_settings(
|
|
{
|
|
u"discussion_blackouts": {
|
|
"value": [
|
|
[
|
|
(now - datetime.timedelta(days=14)).isoformat(),
|
|
(now + datetime.timedelta(days=2)).isoformat()
|
|
]
|
|
]
|
|
}
|
|
}
|
|
)
|
|
self.course_fixture._add_advanced_settings() # pylint: disable=protected-access
|
|
self.browser.refresh()
|
|
thread = Thread(id=uuid4().hex, commentable_id=self.discussion_id)
|
|
thread_fixture = SingleThreadViewFixture(thread)
|
|
thread_fixture.addResponse(
|
|
Response(id="response1"),
|
|
[Comment(id="comment1")])
|
|
thread_fixture.push()
|
|
self.setup_thread_page(thread.get("id"))
|
|
|
|
# Verify that `Add a Post` is not visible on course tab nav.
|
|
self.assertFalse(self.tab_nav.has_new_post_button_visible_on_tab())
|
|
|
|
# Verify that `Add a response` button is not visible.
|
|
self.assertFalse(self.thread_page.has_add_response_button())
|
|
|
|
# Verify user can not add new responses or modify existing responses.
|
|
self.assertFalse(self.thread_page.has_discussion_reply_editor())
|
|
self.assertFalse(self.thread_page.is_response_editable("response1"))
|
|
self.assertFalse(self.thread_page.is_response_deletable("response1"))
|
|
|
|
# Verify that user can not add new comment to a response or modify existing responses.
|
|
self.assertFalse(self.thread_page.is_add_comment_visible("response1"))
|
|
self.assertFalse(self.thread_page.is_comment_editable("comment1"))
|
|
self.assertFalse(self.thread_page.is_comment_deletable("comment1"))
|
|
|
|
|
|
class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase, BaseDiscussionMixin):
|
|
"""
|
|
Tests for the discussion page with multiple threads
|
|
"""
|
|
def setUp(self):
|
|
super(DiscussionTabMultipleThreadTest, self).setUp()
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
self.thread_count = 2
|
|
self.thread_ids = []
|
|
self.setup_multiple_threads(thread_count=self.thread_count)
|
|
|
|
self.thread_page_1 = DiscussionTabSingleThreadPage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.discussion_id,
|
|
self.thread_ids[0]
|
|
)
|
|
self.thread_page_2 = DiscussionTabSingleThreadPage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.discussion_id,
|
|
self.thread_ids[1]
|
|
)
|
|
self.thread_page_1.visit()
|
|
|
|
@attr('a11y')
|
|
def test_page_accessibility(self):
|
|
self.thread_page_1.a11y_audit.config.set_rules({
|
|
"ignore": [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
|
|
self.thread_page_1.a11y_audit.check_for_accessibility_errors()
|
|
|
|
self.thread_page_2.a11y_audit.config.set_rules({
|
|
"ignore": [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
]
|
|
})
|
|
|
|
self.thread_page_2.a11y_audit.check_for_accessibility_errors()
|
|
|
|
|
|
class DiscussionOpenClosedThreadTest(BaseDiscussionTestCase):
|
|
"""
|
|
Tests for checking the display of attributes on open and closed threads
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(DiscussionOpenClosedThreadTest, self).setUp()
|
|
|
|
self.thread_id = "test_thread_{}".format(uuid4().hex)
|
|
|
|
def setup_user(self, roles=[]):
|
|
roles_str = ','.join(roles)
|
|
self.user_id = AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str).visit().get_user_id()
|
|
|
|
def setup_view(self, **thread_kwargs):
|
|
thread_kwargs.update({'commentable_id': self.discussion_id})
|
|
view = SingleThreadViewFixture(
|
|
Thread(id=self.thread_id, **thread_kwargs)
|
|
)
|
|
view.addResponse(Response(id="response1"))
|
|
view.push()
|
|
|
|
def setup_openclosed_thread_page(self, closed=False):
|
|
self.setup_user(roles=['Moderator'])
|
|
if closed:
|
|
self.setup_view(closed=True)
|
|
else:
|
|
self.setup_view()
|
|
page = self.create_single_thread_page(self.thread_id)
|
|
page.visit()
|
|
page.close_open_thread()
|
|
return page
|
|
|
|
@attr(shard=2)
|
|
def test_originally_open_thread_vote_display(self):
|
|
page = self.setup_openclosed_thread_page()
|
|
self.assertFalse(page.is_element_visible('.thread-main-wrapper .action-vote'))
|
|
self.assertTrue(page.is_element_visible('.thread-main-wrapper .display-vote'))
|
|
self.assertFalse(page.is_element_visible('.response_response1 .action-vote'))
|
|
self.assertTrue(page.is_element_visible('.response_response1 .display-vote'))
|
|
|
|
@attr(shard=2)
|
|
def test_originally_closed_thread_vote_display(self):
|
|
page = self.setup_openclosed_thread_page(True)
|
|
self.assertTrue(page.is_element_visible('.thread-main-wrapper .action-vote'))
|
|
self.assertFalse(page.is_element_visible('.thread-main-wrapper .display-vote'))
|
|
self.assertTrue(page.is_element_visible('.response_response1 .action-vote'))
|
|
self.assertFalse(page.is_element_visible('.response_response1 .display-vote'))
|
|
|
|
@attr('a11y')
|
|
def test_page_accessibility(self):
|
|
page = self.setup_openclosed_thread_page()
|
|
page.a11y_audit.config.set_rules({
|
|
'ignore': [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'color-contrast', # Commented out for now because they reproducibly fail on Jenkins but not locally
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
page = self.setup_openclosed_thread_page(True)
|
|
page.a11y_audit.config.set_rules({
|
|
'ignore': [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'color-contrast', # Commented out for now because they reproducibly fail on Jenkins but not locally
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
|
|
@attr(shard=2)
|
|
class DiscussionCommentDeletionTest(BaseDiscussionTestCase):
|
|
"""
|
|
Tests for deleting comments displayed beneath responses in the single thread view.
|
|
"""
|
|
def setup_user(self, roles=[]):
|
|
roles_str = ','.join(roles)
|
|
self.user_id = AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str).visit().get_user_id()
|
|
|
|
def setup_view(self):
|
|
view = SingleThreadViewFixture(Thread(id="comment_deletion_test_thread", commentable_id=self.discussion_id))
|
|
view.addResponse(
|
|
Response(id="response1"), [
|
|
Comment(id="comment_other_author"),
|
|
Comment(id="comment_self_author", user_id=self.user_id, thread_id="comment_deletion_test_thread")
|
|
]
|
|
)
|
|
view.push()
|
|
|
|
def test_comment_deletion_as_student(self):
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_deletion_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_comment_deletable("comment_self_author"))
|
|
self.assertTrue(page.is_comment_visible("comment_other_author"))
|
|
self.assertFalse(page.is_comment_deletable("comment_other_author"))
|
|
page.delete_comment("comment_self_author")
|
|
|
|
def test_comment_deletion_as_moderator(self):
|
|
self.setup_user(roles=['Moderator'])
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_deletion_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_comment_deletable("comment_self_author"))
|
|
self.assertTrue(page.is_comment_deletable("comment_other_author"))
|
|
page.delete_comment("comment_self_author")
|
|
page.delete_comment("comment_other_author")
|
|
|
|
|
|
class DiscussionResponseEditTest(BaseDiscussionTestCase):
|
|
"""
|
|
Tests for editing responses displayed beneath thread in the single thread view.
|
|
"""
|
|
def setup_user(self, roles=[]):
|
|
roles_str = ','.join(roles)
|
|
self.user_id = AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str).visit().get_user_id()
|
|
|
|
def setup_view(self):
|
|
view = SingleThreadViewFixture(Thread(id="response_edit_test_thread", commentable_id=self.discussion_id))
|
|
view.addResponse(
|
|
Response(id="response_other_author", user_id="other", thread_id="response_edit_test_thread"),
|
|
)
|
|
view.addResponse(
|
|
Response(id="response_self_author", user_id=self.user_id, thread_id="response_edit_test_thread"),
|
|
)
|
|
view.push()
|
|
|
|
def edit_response(self, page, response_id):
|
|
self.assertTrue(page.is_response_editable(response_id))
|
|
page.start_response_edit(response_id)
|
|
new_response = "edited body"
|
|
page.set_response_editor_value(response_id, new_response)
|
|
page.submit_response_edit(response_id, new_response)
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_add_link(self):
|
|
"""
|
|
Scenario: User submits valid input to the 'add link' form
|
|
Given I am editing a response on a discussion page
|
|
When I click the 'add link' icon in the editor toolbar
|
|
And enter a valid url to the URL input field
|
|
And enter a valid string in the Description input field
|
|
And click the 'OK' button
|
|
Then the edited response should contain the new link
|
|
"""
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
|
|
response_id = "response_self_author"
|
|
url = "http://example.com"
|
|
description = "example"
|
|
|
|
page.start_response_edit(response_id)
|
|
page.set_response_editor_value(response_id, "")
|
|
page.add_content_via_editor_button(
|
|
"link", response_id, url, description)
|
|
page.submit_response_edit(response_id, description)
|
|
|
|
expected_response_html = (
|
|
u'<p><a href="{}">{}</a></p>'.format(url, description)
|
|
)
|
|
actual_response_html = page.q(
|
|
css=u".response_{} .response-body".format(response_id)
|
|
).html[0]
|
|
self.assertEqual(expected_response_html, actual_response_html)
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_add_image(self):
|
|
"""
|
|
Scenario: User submits valid input to the 'add image' form
|
|
Given I am editing a response on a discussion page
|
|
When I click the 'add image' icon in the editor toolbar
|
|
And enter a valid url to the URL input field
|
|
And enter a valid string in the Description input field
|
|
And click the 'OK' button
|
|
Then the edited response should contain the new image
|
|
"""
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
|
|
response_id = "response_self_author"
|
|
url = "http://www.example.com/something.png"
|
|
description = "image from example.com"
|
|
|
|
page.start_response_edit(response_id)
|
|
page.set_response_editor_value(response_id, "")
|
|
page.add_content_via_editor_button(
|
|
"image", response_id, url, description)
|
|
page.submit_response_edit(response_id, '')
|
|
|
|
expected_response_html = (
|
|
u'<p><img src="{}" alt="{}" title=""></p>'.format(url, description)
|
|
)
|
|
actual_response_html = page.q(
|
|
css=u".response_{} .response-body".format(response_id)
|
|
).html[0]
|
|
self.assertEqual(expected_response_html, actual_response_html)
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_add_image_error_msg(self):
|
|
"""
|
|
Scenario: User submits invalid input to the 'add image' form
|
|
Given I am editing a response on a discussion page
|
|
When I click the 'add image' icon in the editor toolbar
|
|
And enter an invalid url to the URL input field
|
|
And enter an empty string in the Description input field
|
|
And click the 'OK' button
|
|
Then I should be shown 2 error messages
|
|
"""
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
page.start_response_edit("response_self_author")
|
|
page.add_content_via_editor_button(
|
|
"image", "response_self_author", '', '')
|
|
page.verify_link_editor_error_messages_shown()
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_add_decorative_image(self):
|
|
"""
|
|
Scenario: User submits invalid input to the 'add image' form
|
|
Given I am editing a response on a discussion page
|
|
When I click the 'add image' icon in the editor toolbar
|
|
And enter a valid url to the URL input field
|
|
And enter an empty string in the Description input field
|
|
And I check the 'image is decorative' checkbox
|
|
And click the 'OK' button
|
|
Then the edited response should contain the new image
|
|
"""
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
|
|
response_id = "response_self_author"
|
|
url = "http://www.example.com/something.png"
|
|
description = ""
|
|
|
|
page.start_response_edit(response_id)
|
|
page.set_response_editor_value(response_id, "Some content")
|
|
page.add_content_via_editor_button(
|
|
"image", response_id, url, description, is_decorative=True)
|
|
page.submit_response_edit(response_id, "Some content")
|
|
|
|
expected_response_html = (
|
|
u'<p>Some content<img src="{}" alt="{}" title=""></p>'.format(
|
|
url, description)
|
|
)
|
|
actual_response_html = page.q(
|
|
css=u".response_{} .response-body".format(response_id)
|
|
).html[0]
|
|
self.assertEqual(expected_response_html, actual_response_html)
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_add_link_error_msg(self):
|
|
"""
|
|
Scenario: User submits invalid input to the 'add link' form
|
|
Given I am editing a response on a discussion page
|
|
When I click the 'add link' icon in the editor toolbar
|
|
And enter an invalid url to the URL input field
|
|
And enter an empty string in the Description input field
|
|
And click the 'OK' button
|
|
Then I should be shown 2 error messages
|
|
"""
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
page.start_response_edit("response_self_author")
|
|
page.add_content_via_editor_button(
|
|
"link", "response_self_author", '', '')
|
|
page.verify_link_editor_error_messages_shown()
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_as_student(self):
|
|
"""
|
|
Scenario: Students should be able to edit the response they created not responses of other users
|
|
Given that I am on discussion page with student logged in
|
|
When I try to edit the response created by student
|
|
Then the response should be edited and rendered successfully
|
|
And responses from other users should be shown over there
|
|
And the student should be able to edit the response of other people
|
|
"""
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_response_visible("response_other_author"))
|
|
self.assertFalse(page.is_response_editable("response_other_author"))
|
|
self.edit_response(page, "response_self_author")
|
|
|
|
@attr(shard=2)
|
|
def test_edit_response_as_moderator(self):
|
|
"""
|
|
Scenario: Moderator should be able to edit the response they created and responses of other users
|
|
Given that I am on discussion page with moderator logged in
|
|
When I try to edit the response created by moderator
|
|
Then the response should be edited and rendered successfully
|
|
And I try to edit the response created by other users
|
|
Then the response should be edited and rendered successfully
|
|
"""
|
|
self.setup_user(roles=["Moderator"])
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
self.edit_response(page, "response_self_author")
|
|
self.edit_response(page, "response_other_author")
|
|
|
|
@attr(shard=2)
|
|
def test_vote_report_endorse_after_edit(self):
|
|
"""
|
|
Scenario: Moderator should be able to vote, report or endorse after editing the response.
|
|
Given that I am on discussion page with moderator logged in
|
|
When I try to edit the response created by moderator
|
|
Then the response should be edited and rendered successfully
|
|
And I try to edit the response created by other users
|
|
Then the response should be edited and rendered successfully
|
|
And I try to vote the response created by moderator
|
|
Then the response should not be able to be voted
|
|
And I try to vote the response created by other users
|
|
Then the response should be voted successfully
|
|
And I try to report the response created by moderator
|
|
Then the response should not be able to be reported
|
|
And I try to report the response created by other users
|
|
Then the response should be reported successfully
|
|
And I try to endorse the response created by moderator
|
|
Then the response should be endorsed successfully
|
|
And I try to endorse the response created by other users
|
|
Then the response should be endorsed successfully
|
|
"""
|
|
self.setup_user(roles=["Moderator"])
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.visit()
|
|
self.edit_response(page, "response_self_author")
|
|
self.edit_response(page, "response_other_author")
|
|
page.cannot_vote_response('response_self_author')
|
|
page.vote_response('response_other_author')
|
|
page.cannot_report_response('response_self_author')
|
|
page.report_response('response_other_author')
|
|
page.endorse_response('response_self_author')
|
|
page.endorse_response('response_other_author')
|
|
|
|
@attr('a11y')
|
|
def test_page_accessibility(self):
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("response_edit_test_thread")
|
|
page.a11y_audit.config.set_rules({
|
|
'ignore': [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
page.visit()
|
|
page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
|
|
class DiscussionCommentEditTest(BaseDiscussionTestCase):
|
|
"""
|
|
Tests for editing comments displayed beneath responses in the single thread view.
|
|
"""
|
|
def setup_user(self, roles=[]):
|
|
roles_str = ','.join(roles)
|
|
self.user_id = AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str).visit().get_user_id()
|
|
|
|
def setup_view(self):
|
|
view = SingleThreadViewFixture(Thread(id="comment_edit_test_thread", commentable_id=self.discussion_id))
|
|
view.addResponse(
|
|
Response(id="response1"),
|
|
[Comment(id="comment_other_author", user_id="other"), Comment(id="comment_self_author", user_id=self.user_id)])
|
|
view.push()
|
|
|
|
def edit_comment(self, page, comment_id):
|
|
page.start_comment_edit(comment_id)
|
|
new_comment = "edited body"
|
|
page.set_comment_editor_value(comment_id, new_comment)
|
|
page.submit_comment_edit(comment_id, new_comment)
|
|
|
|
@attr(shard=2)
|
|
def test_edit_comment_as_student(self):
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_edit_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_comment_editable("comment_self_author"))
|
|
self.assertTrue(page.is_comment_visible("comment_other_author"))
|
|
self.assertFalse(page.is_comment_editable("comment_other_author"))
|
|
self.edit_comment(page, "comment_self_author")
|
|
|
|
@attr(shard=2)
|
|
def test_edit_comment_as_moderator(self):
|
|
self.setup_user(roles=["Moderator"])
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_edit_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_comment_editable("comment_self_author"))
|
|
self.assertTrue(page.is_comment_editable("comment_other_author"))
|
|
self.edit_comment(page, "comment_self_author")
|
|
self.edit_comment(page, "comment_other_author")
|
|
|
|
@attr(shard=2)
|
|
def test_cancel_comment_edit(self):
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_edit_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_comment_editable("comment_self_author"))
|
|
original_body = page.get_comment_body("comment_self_author")
|
|
page.start_comment_edit("comment_self_author")
|
|
page.set_comment_editor_value("comment_self_author", "edited body")
|
|
page.cancel_comment_edit("comment_self_author", original_body)
|
|
|
|
@attr(shard=2)
|
|
def test_editor_visibility(self):
|
|
"""Only one editor should be visible at a time within a single response"""
|
|
self.setup_user(roles=["Moderator"])
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_edit_test_thread")
|
|
page.visit()
|
|
self.assertTrue(page.is_comment_editable("comment_self_author"))
|
|
self.assertTrue(page.is_comment_editable("comment_other_author"))
|
|
self.assertTrue(page.is_add_comment_visible("response1"))
|
|
original_body = page.get_comment_body("comment_self_author")
|
|
page.start_comment_edit("comment_self_author")
|
|
self.assertFalse(page.is_add_comment_visible("response1"))
|
|
self.assertTrue(page.is_comment_editor_visible("comment_self_author"))
|
|
page.set_comment_editor_value("comment_self_author", "edited body")
|
|
page.start_comment_edit("comment_other_author")
|
|
self.assertFalse(page.is_comment_editor_visible("comment_self_author"))
|
|
self.assertTrue(page.is_comment_editor_visible("comment_other_author"))
|
|
self.assertEqual(page.get_comment_body("comment_self_author"), original_body)
|
|
page.start_response_edit("response1")
|
|
self.assertFalse(page.is_comment_editor_visible("comment_other_author"))
|
|
self.assertTrue(page.is_response_editor_visible("response1"))
|
|
original_body = page.get_comment_body("comment_self_author")
|
|
page.start_comment_edit("comment_self_author")
|
|
self.assertFalse(page.is_response_editor_visible("response1"))
|
|
self.assertTrue(page.is_comment_editor_visible("comment_self_author"))
|
|
page.cancel_comment_edit("comment_self_author", original_body)
|
|
self.assertFalse(page.is_comment_editor_visible("comment_self_author"))
|
|
self.assertTrue(page.is_add_comment_visible("response1"))
|
|
|
|
@attr('a11y')
|
|
def test_page_accessibility(self):
|
|
self.setup_user()
|
|
self.setup_view()
|
|
page = self.create_single_thread_page("comment_edit_test_thread")
|
|
page.visit()
|
|
page.a11y_audit.config.set_rules({
|
|
'ignore': [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
|
|
@attr(shard=2)
|
|
class DiscussionEditorPreviewTest(UniqueCourseTest):
|
|
def setUp(self):
|
|
super(DiscussionEditorPreviewTest, self).setUp()
|
|
CourseFixture(**self.course_info).install()
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
self.page = DiscussionTabHomePage(self.browser, self.course_id)
|
|
self.page.visit()
|
|
self.page.click_new_post_button()
|
|
|
|
# sleep/wait added to allow Major MathJax a11y files to load
|
|
time.sleep(5)
|
|
|
|
def test_text_rendering(self):
|
|
"""When I type plain text into the editor, it should be rendered as plain text in the preview box"""
|
|
self.page.set_new_post_editor_value("Some plain text")
|
|
self.assertEqual(self.page.get_new_post_preview_value(), "<p>Some plain text</p>")
|
|
|
|
def test_markdown_rendering(self):
|
|
"""When I type Markdown into the editor, it should be rendered as formatted Markdown in the preview box"""
|
|
self.page.set_new_post_editor_value(
|
|
"Some markdown\n"
|
|
"\n"
|
|
"- line 1\n"
|
|
"- line 2"
|
|
)
|
|
|
|
self.assertEqual(self.page.get_new_post_preview_value(), (
|
|
"<p>Some markdown</p>\n"
|
|
"\n"
|
|
"<ul>\n"
|
|
"<li>line 1</li>\n"
|
|
"<li>line 2</li>\n"
|
|
"</ul>"
|
|
))
|
|
|
|
def test_mathjax_rendering_in_order(self):
|
|
"""
|
|
Tests that mathjax is rendered in proper order.
|
|
|
|
When user types mathjax expressions into discussion editor, it should render in the proper
|
|
order.
|
|
"""
|
|
self.page.set_new_post_editor_value(
|
|
'Text line 1 \n'
|
|
'$$e[n]=d_1$$ \n'
|
|
'Text line 2 \n'
|
|
'$$e[n]=d_2$$'
|
|
)
|
|
self.assertEqual(self.page.get_new_post_preview_text(), 'Text line 1\nText line 2')
|
|
|
|
def test_inline_mathjax_rendering_in_order(self):
|
|
"""
|
|
Tests the order of Post body content when inline Mathjax is used.
|
|
|
|
With inline mathjax expressions, the text content doesn't break into new lines at the places of
|
|
mathjax expressions.
|
|
"""
|
|
self.page.set_new_post_editor_value(
|
|
'Text line 1 \n'
|
|
'$e[n]=d_1$ \n'
|
|
'Text line 2 \n'
|
|
'$e[n]=d_2$'
|
|
)
|
|
self.assertEqual(self.page.get_new_post_preview_text('.wmd-preview > p'), 'Text line 1 Text line 2')
|
|
|
|
def test_mathjax_not_rendered_after_post_cancel(self):
|
|
"""
|
|
Tests that mathjax is not rendered when we cancel the post
|
|
|
|
When user types the mathjax expression into discussion editor, it will appear in te preview
|
|
box, and when user cancel it and again click the "Add new post" button, mathjax will not
|
|
appear in the preview box
|
|
"""
|
|
self.page.set_new_post_editor_value(
|
|
ur'\begin{equation}'
|
|
ur'\tau_g(\omega) = - \frac{d}{d\omega}\phi(\omega) \hspace{2em} (1) '
|
|
ur'\end{equation}'
|
|
)
|
|
self.assertIsNotNone(self.page.get_new_post_preview_text())
|
|
self.page.click_element(".cancel")
|
|
alert = get_modal_alert(self.browser)
|
|
alert.accept()
|
|
self.assertIsNotNone(self.page.new_post_button)
|
|
self.page.click_new_post_button()
|
|
self.assertEqual(self.page.get_new_post_preview_value('.wmd-preview'), "")
|
|
|
|
|
|
@attr(shard=2)
|
|
class InlineDiscussionTest(UniqueCourseTest):
|
|
"""
|
|
Tests for inline discussions
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(InlineDiscussionTest, self).setUp()
|
|
self.thread_ids = []
|
|
self.discussion_id = "test_discussion_{}".format(uuid4().hex)
|
|
self.additional_discussion_id = "test_discussion_{}".format(uuid4().hex)
|
|
self.course_fix = 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}
|
|
),
|
|
XBlockFixtureDesc(
|
|
"discussion",
|
|
"Test Discussion 1",
|
|
metadata={"discussion_id": self.additional_discussion_id}
|
|
)
|
|
)
|
|
)
|
|
)
|
|
).install()
|
|
|
|
self.user_id = AutoAuthPage(self.browser, course_id=self.course_id).visit().get_user_id()
|
|
|
|
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
|
self.courseware_page.visit()
|
|
self.discussion_page = InlineDiscussionPage(self.browser, self.discussion_id)
|
|
self.additional_discussion_page = InlineDiscussionPage(self.browser, self.additional_discussion_id)
|
|
|
|
@attr('a11y')
|
|
@pytest.mark.skip(reason='This test is too flaky to run at all. TNL-6215')
|
|
def test_inline_a11y(self):
|
|
"""
|
|
Tests Inline Discussion for accessibility issues.
|
|
"""
|
|
self.setup_multiple_threads(thread_count=3)
|
|
|
|
# First test the a11y of the expanded list of threads
|
|
self.discussion_page.expand_discussion()
|
|
self.discussion_page.a11y_audit.config.set_rules({
|
|
'ignore': [
|
|
'section'
|
|
]
|
|
})
|
|
self.discussion_page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
# Now show the first thread and test the a11y again
|
|
self.discussion_page.show_thread(self.thread_ids[0])
|
|
self.discussion_page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
# Finally show the new post form and test its a11y
|
|
self.discussion_page.click_new_post_button()
|
|
self.discussion_page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
def test_add_a_post_is_present_if_can_create_thread_when_expanded(self):
|
|
self.discussion_page.expand_discussion()
|
|
# Add a Post link is present
|
|
self.assertTrue(self.discussion_page.q(css='.new-post-btn').present)
|
|
|
|
def test_add_post_not_present_if_discussion_blackout_period_started(self):
|
|
"""
|
|
If discussion blackout period has started Add a post button should not appear.
|
|
"""
|
|
self.start_discussion_blackout_period()
|
|
self.browser.refresh()
|
|
self.discussion_page.expand_discussion()
|
|
self.assertFalse(self.discussion_page.is_new_post_button_visible())
|
|
|
|
def test_initial_render(self):
|
|
self.assertTrue(self.discussion_page.is_discussion_expanded())
|
|
|
|
def test_discussion_empty(self):
|
|
self.assertEqual(self.discussion_page.get_num_displayed_threads(), 0)
|
|
|
|
def test_dual_discussion_xblock(self):
|
|
"""
|
|
Scenario: Two discussion xblocks in one unit shouldn't override their actions
|
|
Given that I'm on a courseware page where there are two inline discussion
|
|
When I click on the first discussion block's new post button
|
|
Then I should be shown only the new post form for the first block
|
|
When I click on the second discussion block's new post button
|
|
Then I should be shown both new post forms
|
|
When I cancel the first form
|
|
Then I should be shown only the new post form for the second block
|
|
When I cancel the second form
|
|
And I click on the first discussion block's new post button
|
|
Then I should be shown only the new post form for the first block
|
|
When I cancel the first form
|
|
Then I should be shown none of the forms
|
|
"""
|
|
self.discussion_page.wait_for_page()
|
|
self.additional_discussion_page.wait_for_page()
|
|
|
|
# Click to add a post to the first discussion
|
|
self.discussion_page.click_new_post_button()
|
|
|
|
# Verify that only the first discussion's form is shown
|
|
self.assertIsNotNone(self.discussion_page.new_post_form)
|
|
self.assertIsNone(self.additional_discussion_page.new_post_form)
|
|
|
|
# Click to add a post to the second discussion
|
|
self.additional_discussion_page.click_new_post_button()
|
|
|
|
# Verify that both discussion's forms are shown
|
|
self.assertIsNotNone(self.discussion_page.new_post_form)
|
|
self.assertIsNotNone(self.additional_discussion_page.new_post_form)
|
|
|
|
# Cancel the first form
|
|
self.discussion_page.click_cancel_new_post()
|
|
|
|
# Verify that only the second discussion's form is shown
|
|
self.assertIsNone(self.discussion_page.new_post_form)
|
|
self.assertIsNotNone(self.additional_discussion_page.new_post_form)
|
|
|
|
# Cancel the second form and click to show the first one
|
|
self.additional_discussion_page.click_cancel_new_post()
|
|
self.discussion_page.click_new_post_button()
|
|
|
|
# Verify that only the first discussion's form is shown
|
|
self.assertIsNotNone(self.discussion_page.new_post_form)
|
|
self.assertIsNone(self.additional_discussion_page.new_post_form)
|
|
|
|
# Cancel the first form
|
|
self.discussion_page.click_cancel_new_post()
|
|
|
|
# Verify that neither discussion's forms are shwon
|
|
self.assertIsNone(self.discussion_page.new_post_form)
|
|
self.assertIsNone(self.additional_discussion_page.new_post_form)
|
|
|
|
def start_discussion_blackout_period(self):
|
|
"""
|
|
Start discussion blackout period, starting 14 days before now to 2 days ago.
|
|
"""
|
|
now = datetime.datetime.now(UTC)
|
|
self.course_fix.add_advanced_settings(
|
|
{
|
|
u"discussion_blackouts": {
|
|
"value": [
|
|
[
|
|
(now - datetime.timedelta(days=14)).isoformat(),
|
|
(now + datetime.timedelta(days=2)).isoformat()
|
|
]
|
|
]
|
|
}
|
|
}
|
|
)
|
|
self.course_fix._add_advanced_settings() # pylint: disable=protected-access
|
|
|
|
|
|
@attr(shard=2)
|
|
class DiscussionUserProfileTest(UniqueCourseTest):
|
|
"""
|
|
Tests for user profile page in discussion tab.
|
|
"""
|
|
|
|
PAGE_SIZE = 20 # discussion.views.THREADS_PER_PAGE
|
|
PROFILED_USERNAME = "profiled-user"
|
|
|
|
def setUp(self):
|
|
super(DiscussionUserProfileTest, self).setUp()
|
|
self.setup_course()
|
|
# The following line creates a user enrolled in our course, whose
|
|
# threads will be viewed, but not the one who will view the page.
|
|
# It isn't necessary to log them in, but using the AutoAuthPage
|
|
# saves a lot of code.
|
|
self.profiled_user_id = self.setup_user(username=self.PROFILED_USERNAME)
|
|
# now create a second user who will view the profile.
|
|
self.user_id = self.setup_user()
|
|
UserProfileViewFixture([]).push()
|
|
|
|
def setup_course(self):
|
|
"""
|
|
Set up the for the course discussion user-profile tests.
|
|
"""
|
|
return CourseFixture(**self.course_info).install()
|
|
|
|
def setup_user(self, roles=None, **user_info):
|
|
"""
|
|
Helper method to create and authenticate a user.
|
|
"""
|
|
roles_str = ''
|
|
if roles:
|
|
roles_str = ','.join(roles)
|
|
return AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str, **user_info).visit().get_user_id()
|
|
|
|
def test_redirects_to_learner_profile(self):
|
|
"""
|
|
Scenario: Verify that learner-profile link is present on forum discussions page and we can navigate to it.
|
|
|
|
Given that I am on discussion forum user's profile page.
|
|
And I can see a username on the page
|
|
When I click on my username.
|
|
Then I will be navigated to Learner Profile page.
|
|
And I can my username on Learner Profile page
|
|
"""
|
|
learner_profile_page = LearnerProfilePage(self.browser, self.PROFILED_USERNAME)
|
|
|
|
page = DiscussionUserProfilePage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.profiled_user_id,
|
|
self.PROFILED_USERNAME
|
|
)
|
|
page.visit()
|
|
page.click_on_sidebar_username()
|
|
|
|
learner_profile_page.wait_for_page()
|
|
self.assertTrue(learner_profile_page.field_is_visible('username'))
|
|
|
|
def test_learner_profile_roles(self):
|
|
"""
|
|
Test that on the learner profile page user roles are correctly listed according to the course.
|
|
"""
|
|
# Setup a learner with roles in a Course-A.
|
|
expected_student_roles = ['Administrator', 'Community TA', 'Moderator', 'Student']
|
|
self.profiled_user_id = self.setup_user(
|
|
roles=expected_student_roles,
|
|
username=self.PROFILED_USERNAME
|
|
)
|
|
|
|
# Visit the page and verify the roles are listed correctly.
|
|
page = DiscussionUserProfilePage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.profiled_user_id,
|
|
self.PROFILED_USERNAME
|
|
)
|
|
page.visit()
|
|
student_roles = page.get_user_roles()
|
|
self.assertEqual(student_roles, ', '.join(expected_student_roles))
|
|
|
|
# Save the course_id of Course-A before setting up a new course.
|
|
old_course_id = self.course_id
|
|
|
|
# Setup Course-B and set user do not have additional roles and test roles are displayed correctly.
|
|
self.course_info['number'] = self.unique_id
|
|
self.setup_course()
|
|
new_course_id = self.course_id
|
|
|
|
# Set the user to have no extra role in the Course-B and verify the existing
|
|
# user is updated.
|
|
profiled_student_user_id = self.setup_user(roles=None, username=self.PROFILED_USERNAME)
|
|
self.assertEqual(self.profiled_user_id, profiled_student_user_id)
|
|
self.assertNotEqual(old_course_id, new_course_id)
|
|
|
|
# Visit the user profile in course discussion page of Course-B. Make sure the
|
|
# roles are listed correctly.
|
|
page = DiscussionUserProfilePage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.profiled_user_id,
|
|
self.PROFILED_USERNAME
|
|
)
|
|
page.visit()
|
|
self.assertEqual(page.get_user_roles(), u'Student')
|
|
|
|
|
|
class DiscussionSearchAlertTest(UniqueCourseTest):
|
|
"""
|
|
Tests for spawning and dismissing alerts related to user search actions and their results.
|
|
"""
|
|
|
|
SEARCHED_USERNAME = "gizmo"
|
|
|
|
def setUp(self):
|
|
super(DiscussionSearchAlertTest, self).setUp()
|
|
CourseFixture(**self.course_info).install()
|
|
# first auto auth call sets up a user that we will search for in some tests
|
|
self.searched_user_id = AutoAuthPage(
|
|
self.browser,
|
|
username=self.SEARCHED_USERNAME,
|
|
course_id=self.course_id
|
|
).visit().get_user_id()
|
|
# this auto auth call creates the actual session user
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
self.page = DiscussionTabHomePage(self.browser, self.course_id)
|
|
self.page.visit()
|
|
|
|
def setup_corrected_text(self, text):
|
|
SearchResultFixture(SearchResult(corrected_text=text)).push()
|
|
|
|
def check_search_alert_messages(self, expected):
|
|
actual = self.page.get_search_alert_messages()
|
|
self.assertTrue(all(map(lambda msg, sub: msg.lower().find(sub.lower()) >= 0, actual, expected)))
|
|
|
|
@attr(shard=2)
|
|
def test_no_rewrite(self):
|
|
self.setup_corrected_text(None)
|
|
self.page.perform_search()
|
|
self.check_search_alert_messages(["no posts"])
|
|
|
|
@attr(shard=2)
|
|
def test_rewrite_dismiss(self):
|
|
self.page.dismiss_alert_message("There are no posts in this topic yet.")
|
|
self.setup_corrected_text("foo")
|
|
self.page.perform_search()
|
|
self.check_search_alert_messages(["foo"])
|
|
self.page.dismiss_alert_message("foo")
|
|
self.check_search_alert_messages([])
|
|
|
|
@attr(shard=2)
|
|
def test_new_search(self):
|
|
self.page.dismiss_alert_message("There are no posts in this topic yet.")
|
|
self.setup_corrected_text("foo")
|
|
self.page.perform_search()
|
|
self.check_search_alert_messages(["foo"])
|
|
|
|
self.setup_corrected_text("bar")
|
|
self.page.perform_search()
|
|
self.check_search_alert_messages(["bar"])
|
|
|
|
self.setup_corrected_text(None)
|
|
self.page.perform_search()
|
|
self.check_search_alert_messages(["no posts"])
|
|
|
|
@attr(shard=2)
|
|
def test_rewrite_and_user(self):
|
|
self.page.dismiss_alert_message("There are no posts in this topic yet.")
|
|
self.setup_corrected_text("foo")
|
|
self.page.perform_search(self.SEARCHED_USERNAME)
|
|
self.check_search_alert_messages(["foo", self.SEARCHED_USERNAME])
|
|
|
|
@attr(shard=2)
|
|
def test_user_only(self):
|
|
self.setup_corrected_text(None)
|
|
self.page.perform_search(self.SEARCHED_USERNAME)
|
|
self.check_search_alert_messages(["no posts", self.SEARCHED_USERNAME])
|
|
# make sure clicking the link leads to the user profile page
|
|
UserProfileViewFixture([]).push()
|
|
self.page.get_search_alert_links().first.click()
|
|
DiscussionUserProfilePage(
|
|
self.browser,
|
|
self.course_id,
|
|
self.searched_user_id,
|
|
self.SEARCHED_USERNAME
|
|
).wait_for_page()
|
|
|
|
@attr('a11y')
|
|
def test_page_accessibility(self):
|
|
self.page.a11y_audit.config.set_rules({
|
|
'ignore': [
|
|
'section', # TODO: AC-491
|
|
'aria-required-children', # TODO: AC-534
|
|
'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865
|
|
]
|
|
})
|
|
self.page.a11y_audit.check_for_accessibility_errors()
|
|
|
|
|
|
@attr(shard=2)
|
|
class DiscussionSortPreferenceTest(UniqueCourseTest):
|
|
"""
|
|
Tests for the discussion page displaying a single thread.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(DiscussionSortPreferenceTest, self).setUp()
|
|
|
|
# Create a course to register for.
|
|
CourseFixture(**self.course_info).install()
|
|
|
|
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
|
|
|
self.sort_page = DiscussionSortPreferencePage(self.browser, self.course_id)
|
|
self.sort_page.visit()
|
|
self.sort_page.show_all_discussions()
|
|
|
|
def test_default_sort_preference(self):
|
|
"""
|
|
Test to check the default sorting preference of user. (Default = date )
|
|
"""
|
|
selected_sort = self.sort_page.get_selected_sort_preference()
|
|
self.assertEqual(selected_sort, "activity")
|
|
|
|
@skip_if_browser('chrome') # TODO TE-1542 and TE-1543
|
|
def test_change_sort_preference(self):
|
|
"""
|
|
Test that if user sorting preference is changing properly.
|
|
"""
|
|
selected_sort = ""
|
|
for sort_type in ["votes", "comments", "activity"]:
|
|
self.assertNotEqual(selected_sort, sort_type)
|
|
self.sort_page.change_sort_preference(sort_type)
|
|
selected_sort = self.sort_page.get_selected_sort_preference()
|
|
self.assertEqual(selected_sort, sort_type)
|
|
|
|
@skip_if_browser('chrome') # TODO TE-1542 and TE-1543
|
|
def test_last_preference_saved(self):
|
|
"""
|
|
Test that user last preference is saved.
|
|
"""
|
|
selected_sort = ""
|
|
for sort_type in ["votes", "comments", "activity"]:
|
|
self.assertNotEqual(selected_sort, sort_type)
|
|
self.sort_page.change_sort_preference(sort_type)
|
|
selected_sort = self.sort_page.get_selected_sort_preference()
|
|
self.assertEqual(selected_sort, sort_type)
|
|
self.sort_page.refresh_page()
|
|
self.sort_page.show_all_discussions()
|
|
selected_sort = self.sort_page.get_selected_sort_preference()
|
|
self.assertEqual(selected_sort, sort_type)
|