Skip to content

Commit

Permalink
Merge pull request #2504 from edx/gprice/forum-pagination-acceptance
Browse files Browse the repository at this point in the history
Add acceptance tests for forum response pagination
  • Loading branch information
Greg Price committed Feb 7, 2014
2 parents ff91002 + 8e3a77f commit b1e5bbb
Show file tree
Hide file tree
Showing 23 changed files with 264 additions and 20 deletions.
79 changes: 79 additions & 0 deletions common/djangoapps/terrain/stubs/comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Stub implementation of cs_comments_service for acceptance tests
"""

from datetime import datetime
import re
import urlparse
from .http import StubHttpRequestHandler, StubHttpService


class StubCommentsServiceHandler(StubHttpRequestHandler):
def do_GET(self):
pattern_handlers = {
"/api/v1/users/(?P<user_id>\\d+)$": self.do_user,
"/api/v1/threads$": self.do_threads,
"/api/v1/threads/(?P<thread_id>\\w+)$": self.do_thread,
}
path = urlparse.urlparse(self.path).path
for pattern in pattern_handlers:
match = re.match(pattern, path)
if match:
pattern_handlers[pattern](**match.groupdict())
return

self.send_response(404, content="404 Not Found")

def do_PUT(self):
self.send_response(204, "")

def do_user(self, user_id):
self.send_json_response({
"id": user_id,
"upvoted_ids": [],
"downvoted_ids": [],
"subscribed_thread_ids": [],
})

def do_thread(self, thread_id):
match = re.search("(?P<num>\\d+)_responses", thread_id)
resp_total = int(match.group("num")) if match else 0
thread = {
"id": thread_id,
"commentable_id": "dummy",
"type": "thread",
"title": "Thread title",
"body": "Thread body",
"created_at": datetime.utcnow().isoformat(),
"unread_comments_count": 0,
"comments_count": resp_total,
"votes": {"up_count": 0},
"abuse_flaggers": [],
"closed": "closed" in thread_id,
}
params = urlparse.parse_qs(urlparse.urlparse(self.path).query)
if "recursive" in params and params["recursive"][0] == "True":
thread["resp_total"] = resp_total
thread["children"] = []
resp_skip = int(params.get("resp_skip", ["0"])[0])
resp_limit = int(params.get("resp_limit", ["10000"])[0])
num_responses = min(resp_limit, resp_total - resp_skip)
self.log_message("Generating {} children; resp_limit={} resp_total={} resp_skip={}".format(num_responses, resp_limit, resp_total, resp_skip))
for i in range(num_responses):
response_id = str(resp_skip + i)
thread["children"].append({
"id": str(response_id),
"type": "comment",
"body": response_id,
"created_at": datetime.utcnow().isoformat(),
"votes": {"up_count": 0},
"abuse_flaggers": [],
})
self.send_json_response(thread)

def do_threads(self):
self.send_json_response({"collection": [], "page": 1, "num_pages": 1})


class StubCommentsService(StubHttpService):
HANDLER_CLASS = StubCommentsServiceHandler
7 changes: 7 additions & 0 deletions common/djangoapps/terrain/stubs/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ def send_response(self, status_code, content=None, headers=None):
if content is not None:
self.wfile.write(content)

def send_json_response(self, content):
"""
Send a response with status code 200, the given content serialized as
JSON, and the Content-Type header set appropriately
"""
self.send_response(200, json.dumps(content), {"Content-Type": "application/json"})

def _format_msg(self, format_str, *args):
"""
Format message for logging.
Expand Down
4 changes: 3 additions & 1 deletion common/djangoapps/terrain/stubs/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import time
import logging
from .comments import StubCommentsService
from .xqueue import StubXQueueService
from .youtube import StubYouTubeService
from .ora import StubOraService
Expand All @@ -14,7 +15,8 @@
SERVICES = {
'xqueue': StubXQueueService,
'youtube': StubYouTubeService,
'ora': StubOraService
'ora': StubOraService,
'comments': StubCommentsService,
}

# Log to stdout, including debug messages
Expand Down
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/lms/course_about.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CourseAboutPage(CoursePage):
Course about page (with registration button)
"""

URL_PATH = "about"
url_path = "about"

def is_browser_on_page(self):
return self.is_css_present('section.course-info')
Expand Down
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/lms/course_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CourseInfoPage(CoursePage):
Course info.
"""

URL_PATH = "info"
url_path = "info"

def is_browser_on_page(self):
return self.is_css_present('section.updates')
Expand Down
4 changes: 2 additions & 2 deletions common/test/acceptance/pages/lms/course_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CoursePage(PageObject):

# Overridden by subclasses to provide the relative path within the course
# Paths should not include the leading forward slash.
URL_PATH = ""
url_path = ""

def __init__(self, browser, course_id):
"""
Expand All @@ -28,4 +28,4 @@ def url(self):
"""
Construct a URL to the page within the course.
"""
return BASE_URL + "/courses/" + self.course_id + "/" + self.URL_PATH
return BASE_URL + "/courses/" + self.course_id + "/" + self.url_path
70 changes: 70 additions & 0 deletions common/test/acceptance/pages/lms/discussion_single_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from bok_choy.page_object import unguarded
from bok_choy.promise import EmptyPromise, fulfill

from .course_page import CoursePage


class DiscussionSingleThreadPage(CoursePage):
def __init__(self, browser, course_id, thread_id):
super(DiscussionSingleThreadPage, self).__init__(browser, course_id)
self.thread_id = thread_id

def is_browser_on_page(self):
return self.is_css_present(
"body.discussion .discussion-article[data-id='{thread_id}']".format(thread_id=self.thread_id)
)

@property
@unguarded
def url_path(self):
return "discussion/forum/dummy/threads/" + self.thread_id

def _get_element_text(self, selector):
"""
Returns the text of the first element matching the given selector, or
None if no such element exists
"""
text_list = self.css_text(selector)
return text_list[0] if text_list else None

def get_response_total_text(self):
"""Returns the response count text, or None if not present"""
return self._get_element_text(".response-count")

def get_num_displayed_responses(self):
"""Returns the number of responses actually rendered"""
return self.css_count(".discussion-response")

def get_shown_responses_text(self):
"""Returns the shown response count text, or None if not present"""
return self._get_element_text(".response-display-count")

def get_load_responses_button_text(self):
"""Returns the load more responses button text, or None if not present"""
return self._get_element_text(".load-response-button")

def load_more_responses(self):
"""Clicks the laod more responses button and waits for responses to load"""
self.css_click(".load-response-button")
fulfill(EmptyPromise(
lambda: not self.is_css_present(".loading"),
"Loading more responses completed"
))

def has_add_response_button(self):
"""Returns true if the add response button is visible, false otherwise"""
return (
self.is_css_present(".add-response-btn") and
self.css_map(".add-response-btn", lambda el: el.visible)[0]
)

def click_add_response_button(self):
"""
Clicks the add response button and ensures that the response text
field receives focus
"""
self.css_click(".add-response-btn")
fulfill(EmptyPromise(
lambda: self.is_css_present("#wmd-input-reply-body-{thread_id}:focus".format(thread_id=self.thread_id)),
"Response field received focus"
))
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/lms/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ProgressPage(CoursePage):
Student progress page.
"""

URL_PATH = "progress"
url_path = "progress"

def is_browser_on_page(self):
has_course_info = self.is_css_present('div.course-info')
Expand Down
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/asset_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AssetIndexPage(CoursePage):
The Files and Uploads page for a course in Studio
"""

URL_PATH = "assets"
url_path = "assets"

def is_browser_on_page(self):
return self.is_css_present('body.view-uploads')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/checklists.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ChecklistsPage(CoursePage):
Course Checklists page.
"""

URL_PATH = "checklists"
url_path = "checklists"

def is_browser_on_page(self):
return self.is_css_present('body.view-checklists')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/course_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ImportPage(CoursePage):
Course Import page.
"""

URL_PATH = "import"
url_path = "import"

def is_browser_on_page(self):
return self.is_css_present('body.view-import')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/course_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CourseUpdatesPage(CoursePage):
Course Updates page.
"""

URL_PATH = "course_info"
url_path = "course_info"

def is_browser_on_page(self):
return self.is_css_present('body.view-updates')
4 changes: 2 additions & 2 deletions common/test/acceptance/pages/studio/course_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CoursePage(PageObject):

# Overridden by subclasses to provide the relative path within the course
# Does not need to include the leading forward or trailing slash
URL_PATH = ""
url_path = ""

def __init__(self, browser, course_org, course_num, course_run):
"""
Expand All @@ -35,7 +35,7 @@ def url(self):
Construct a URL to the page within the course.
"""
return "/".join([
BASE_URL, self.URL_PATH,
BASE_URL, self.url_path,
"{course_org}.{course_num}.{course_run}".format(**self.course_info),
"branch", "draft", "block", self.course_info['course_run']
])
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/edit_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class StaticPagesPage(CoursePage):
Static Pages page for a course.
"""

URL_PATH = "tabs"
url_path = "tabs"

def is_browser_on_page(self):
return self.is_css_present('body.view-static-pages')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ExportPage(CoursePage):
Course Export page.
"""

URL_PATH = "export"
url_path = "export"

def is_browser_on_page(self):
return self.is_css_present('body.view-export')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/manage_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CourseTeamPage(CoursePage):
Course Team page in Studio.
"""

URL_PATH = "course_team"
url_path = "course_team"

def is_browser_on_page(self):
return self.is_css_present('body.view-team')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/overview.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CourseOutlinePage(CoursePage):
Course Outline page in Studio.
"""

URL_PATH = "course"
url_path = "course"

def is_browser_on_page(self):
return self.is_css_present('body.view-outline')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class SettingsPage(CoursePage):
Course Schedule and Details Settings page.
"""

URL_PATH = "settings/details"
url_path = "settings/details"

def is_browser_on_page(self):
return self.is_css_present('body.view-settings')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/settings_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AdvancedSettingsPage(CoursePage):
Course Advanced Settings page.
"""

URL_PATH = "settings/advanced"
url_path = "settings/advanced"

def is_browser_on_page(self):
return self.is_css_present('body.advanced')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/settings_graders.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class GradingPage(CoursePage):
Course Grading Settings page.
"""

URL_PATH = "settings/grading"
url_path = "settings/grading"

def is_browser_on_page(self):
return self.is_css_present('body.grading')
2 changes: 1 addition & 1 deletion common/test/acceptance/pages/studio/textbooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TextbooksPage(CoursePage):
Course Textbooks page.
"""

URL_PATH = "textbooks"
url_path = "textbooks"

def is_browser_on_page(self):
return self.is_css_present('body.view-textbooks')
Loading

0 comments on commit b1e5bbb

Please sign in to comment.