Merge pull request #3458 from edx/jsa/user-profile-pagination-acceptance
add acceptance tests for user profile pagination.
This commit is contained in:
@@ -8,8 +8,14 @@ from .http import StubHttpRequestHandler, StubHttpService
|
||||
|
||||
|
||||
class StubCommentsServiceHandler(StubHttpRequestHandler):
|
||||
|
||||
@property
|
||||
def _params(self):
|
||||
return urlparse.parse_qs(urlparse.urlparse(self.path).query)
|
||||
|
||||
def do_GET(self):
|
||||
pattern_handlers = {
|
||||
"/api/v1/users/(?P<user_id>\\d+)/active_threads$": self.do_user_profile,
|
||||
"/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,
|
||||
@@ -34,12 +40,34 @@ class StubCommentsServiceHandler(StubHttpRequestHandler):
|
||||
self.send_json_response({})
|
||||
|
||||
def do_user(self, user_id):
|
||||
self.send_json_response({
|
||||
response = {
|
||||
"id": user_id,
|
||||
"upvoted_ids": [],
|
||||
"downvoted_ids": [],
|
||||
"subscribed_thread_ids": [],
|
||||
})
|
||||
}
|
||||
if 'course_id' in self._params:
|
||||
response.update({
|
||||
"threads_count": 1,
|
||||
"comments_count": 2
|
||||
})
|
||||
self.send_json_response(response)
|
||||
|
||||
def do_user_profile(self, user_id):
|
||||
if 'active_threads' in self.server.config:
|
||||
user_threads = self.server.config['active_threads'][:]
|
||||
params = self._params
|
||||
page = int(params.get("page", ["1"])[0])
|
||||
per_page = int(params.get("per_page", ["20"])[0])
|
||||
num_pages = max(len(user_threads) - 1, 1) / per_page + 1
|
||||
user_threads = user_threads[(page - 1) * per_page:page * per_page]
|
||||
self.send_json_response({
|
||||
"collection": user_threads,
|
||||
"page": page,
|
||||
"num_pages": num_pages
|
||||
})
|
||||
else:
|
||||
self.send_response(404, content="404 Not Found")
|
||||
|
||||
def do_thread(self, thread_id):
|
||||
if thread_id in self.server.config.get('threads', {}):
|
||||
|
||||
@@ -87,3 +87,19 @@ class SingleThreadViewFixture(object):
|
||||
"comments": json.dumps(self._get_comment_map())
|
||||
}
|
||||
)
|
||||
|
||||
class UserProfileViewFixture(object):
|
||||
|
||||
def __init__(self, threads):
|
||||
self.threads = threads
|
||||
|
||||
def push(self):
|
||||
"""
|
||||
Push the data to the stub comments service.
|
||||
"""
|
||||
requests.put(
|
||||
'{}/set_config'.format(COMMENTS_STUB_URL),
|
||||
data={
|
||||
"active_threads": json.dumps(self.threads),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -231,3 +231,76 @@ class InlineDiscussionThreadPage(DiscussionThreadPage):
|
||||
"Thread expanded"
|
||||
).fulfill()
|
||||
|
||||
|
||||
class DiscussionUserProfilePage(CoursePage):
|
||||
|
||||
TEXT_NEXT = u'Next >'
|
||||
TEXT_PREV = u'< Previous'
|
||||
PAGING_SELECTOR = "a.discussion-pagination[data-page-number]"
|
||||
|
||||
def __init__(self, browser, course_id, user_id, username, page=1):
|
||||
super(DiscussionUserProfilePage, self).__init__(browser, course_id)
|
||||
self.url_path = "discussion/forum/dummy/users/{}?page={}".format(user_id, page)
|
||||
self.username = username
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return (
|
||||
self.q(css='section.discussion-user-threads[data-course-id="{}"]'.format(self.course_id)).present
|
||||
and
|
||||
self.q(css='section.user-profile div.sidebar-username').present
|
||||
and
|
||||
self.q(css='section.user-profile div.sidebar-username').text[0] == self.username
|
||||
)
|
||||
|
||||
def get_shown_thread_ids(self):
|
||||
elems = self.q(css="article.discussion-thread")
|
||||
return [elem.get_attribute("id")[7:] for elem in elems]
|
||||
|
||||
def get_current_page(self):
|
||||
return int(self.q(css="nav.discussion-paginator li.current-page").text[0])
|
||||
|
||||
def _check_pager(self, text, page_number=None):
|
||||
"""
|
||||
returns True if 'text' matches the text in any of the pagination elements. If
|
||||
page_number is provided, only return True if the element points to that result
|
||||
page.
|
||||
"""
|
||||
elems = self.q(css=self.PAGING_SELECTOR).filter(lambda elem: elem.text == text)
|
||||
if page_number:
|
||||
elems = elems.filter(lambda elem: int(elem.get_attribute('data-page-number')) == page_number)
|
||||
return elems.present
|
||||
|
||||
def get_clickable_pages(self):
|
||||
return sorted([
|
||||
int(elem.get_attribute('data-page-number'))
|
||||
for elem in self.q(css=self.PAGING_SELECTOR)
|
||||
if str(elem.text).isdigit()
|
||||
])
|
||||
|
||||
def is_prev_button_shown(self, page_number=None):
|
||||
return self._check_pager(self.TEXT_PREV, page_number)
|
||||
|
||||
def is_next_button_shown(self, page_number=None):
|
||||
return self._check_pager(self.TEXT_NEXT, page_number)
|
||||
|
||||
def _click_pager_with_text(self, text, page_number):
|
||||
"""
|
||||
click the first pagination element with whose text is `text` and ensure
|
||||
the resulting page number matches `page_number`.
|
||||
"""
|
||||
targets = [elem for elem in self.q(css=self.PAGING_SELECTOR) if elem.text == text]
|
||||
targets[0].click()
|
||||
EmptyPromise(
|
||||
lambda: self.get_current_page() == page_number,
|
||||
"navigated to desired page"
|
||||
).fulfill()
|
||||
|
||||
def click_prev_page(self):
|
||||
self._click_pager_with_text(self.TEXT_PREV, self.get_current_page() - 1)
|
||||
|
||||
def click_next_page(self):
|
||||
self._click_pager_with_text(self.TEXT_NEXT, self.get_current_page() + 1)
|
||||
|
||||
def click_on_page(self, page_number):
|
||||
self._click_pager_with_text(unicode(page_number), page_number)
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ from ..pages.lms.courseware import CoursewarePage
|
||||
from ..pages.lms.discussion import (
|
||||
DiscussionTabSingleThreadPage,
|
||||
InlineDiscussionPage,
|
||||
InlineDiscussionThreadPage
|
||||
InlineDiscussionThreadPage,
|
||||
DiscussionUserProfilePage
|
||||
)
|
||||
from ..fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ..fixtures.discussion import SingleThreadViewFixture, Thread, Response, Comment
|
||||
from ..fixtures.discussion import SingleThreadViewFixture, UserProfileViewFixture, Thread, Response, Comment
|
||||
|
||||
|
||||
class DiscussionResponsePaginationTestMixin(object):
|
||||
@@ -301,3 +302,106 @@ class InlineDiscussionTest(UniqueCourseTest, DiscussionResponsePaginationTestMix
|
||||
def test_expand_discussion_empty(self):
|
||||
self.discussion_page.expand_discussion()
|
||||
self.assertEqual(self.discussion_page.get_num_displayed_threads(), 0)
|
||||
|
||||
|
||||
class DiscussionUserProfileTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests for user profile page in discussion tab.
|
||||
"""
|
||||
|
||||
PAGE_SIZE = 20 # django_comment_client.forum.views.THREADS_PER_PAGE
|
||||
PROFILED_USERNAME = "profiled-user"
|
||||
|
||||
def setUp(self):
|
||||
super(DiscussionUserProfileTest, self).setUp()
|
||||
CourseFixture(**self.course_info).install()
|
||||
# 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 = AutoAuthPage(
|
||||
self.browser,
|
||||
username=self.PROFILED_USERNAME,
|
||||
course_id=self.course_id
|
||||
).visit().get_user_id()
|
||||
# now create a second user who will view the profile.
|
||||
self.user_id = AutoAuthPage(
|
||||
self.browser,
|
||||
course_id=self.course_id
|
||||
).visit().get_user_id()
|
||||
|
||||
def check_pages(self, num_threads):
|
||||
# set up the stub server to return the desired amount of thread results
|
||||
threads = [Thread(id=uuid4().hex) for _ in range(num_threads)]
|
||||
UserProfileViewFixture(threads).push()
|
||||
# navigate to default view (page 1)
|
||||
page = DiscussionUserProfilePage(
|
||||
self.browser,
|
||||
self.course_id,
|
||||
self.profiled_user_id,
|
||||
self.PROFILED_USERNAME
|
||||
)
|
||||
page.visit()
|
||||
|
||||
current_page = 1
|
||||
total_pages = max(num_threads - 1, 1) / self.PAGE_SIZE + 1
|
||||
all_pages = range(1, total_pages + 1)
|
||||
|
||||
def _check_page():
|
||||
# ensure the page being displayed as "current" is the expected one
|
||||
self.assertEqual(page.get_current_page(), current_page)
|
||||
# ensure the expected threads are being shown in the right order
|
||||
threads_expected = threads[(current_page - 1) * self.PAGE_SIZE:current_page * self.PAGE_SIZE]
|
||||
self.assertEqual(page.get_shown_thread_ids(), [t["id"] for t in threads_expected])
|
||||
# ensure the clickable page numbers are the expected ones
|
||||
self.assertEqual(page.get_clickable_pages(), [
|
||||
p for p in all_pages
|
||||
if p != current_page
|
||||
and p - 2 <= current_page <= p + 2
|
||||
or (current_page > 2 and p == 1)
|
||||
or (current_page < total_pages and p == total_pages)
|
||||
])
|
||||
# ensure the previous button is shown, but only if it should be.
|
||||
# when it is shown, make sure it works.
|
||||
if current_page > 1:
|
||||
self.assertTrue(page.is_prev_button_shown(current_page - 1))
|
||||
page.click_prev_page()
|
||||
self.assertEqual(page.get_current_page(), current_page - 1)
|
||||
page.click_next_page()
|
||||
self.assertEqual(page.get_current_page(), current_page)
|
||||
else:
|
||||
self.assertFalse(page.is_prev_button_shown())
|
||||
# ensure the next button is shown, but only if it should be.
|
||||
if current_page < total_pages:
|
||||
self.assertTrue(page.is_next_button_shown(current_page + 1))
|
||||
else:
|
||||
self.assertFalse(page.is_next_button_shown())
|
||||
|
||||
# click all the way up through each page
|
||||
for i in range(current_page, total_pages):
|
||||
_check_page()
|
||||
if current_page < total_pages:
|
||||
page.click_on_page(current_page + 1)
|
||||
current_page += 1
|
||||
|
||||
# click all the way back down
|
||||
for i in range(current_page, 0, -1):
|
||||
_check_page()
|
||||
if current_page > 1:
|
||||
page.click_on_page(current_page - 1)
|
||||
current_page -= 1
|
||||
|
||||
def test_0_threads(self):
|
||||
self.check_pages(0)
|
||||
|
||||
def test_1_thread(self):
|
||||
self.check_pages(1)
|
||||
|
||||
def test_20_threads(self):
|
||||
self.check_pages(20)
|
||||
|
||||
def test_21_threads(self):
|
||||
self.check_pages(21)
|
||||
|
||||
def test_151_threads(self):
|
||||
self.check_pages(151)
|
||||
|
||||
Reference in New Issue
Block a user