Commit 4aec2abf by Peter Fogg

Merge pull request #9450 from edx/peter-fogg/sort-topic-page

Add sorting controls on topics page.
parents dc2e1c0b 382909b7
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
setPage: function (page) { setPage: function (page) {
var oldPage = this.currentPage, var oldPage = this.currentPage,
self = this; self = this;
this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then( return this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then(
function () { function () {
self.trigger('page_changed'); self.trigger('page_changed');
}, },
......
...@@ -9,12 +9,16 @@ ...@@ -9,12 +9,16 @@
var PagingHeader = Backbone.View.extend({ var PagingHeader = Backbone.View.extend({
initialize: function (options) { initialize: function (options) {
this.srInfo = options.srInfo; this.srInfo = options.srInfo;
this.collections = options.collection; this.showSortControls = options.showSortControls;
this.collection.bind('add', _.bind(this.render, this)); this.collection.bind('add', _.bind(this.render, this));
this.collection.bind('remove', _.bind(this.render, this)); this.collection.bind('remove', _.bind(this.render, this));
this.collection.bind('reset', _.bind(this.render, this)); this.collection.bind('reset', _.bind(this.render, this));
}, },
events: {
'change #paging-header-select': 'sortCollection'
},
render: function () { render: function () {
var message, var message,
start = _.isUndefined(this.collection.start) ? 0 : this.collection.start, start = _.isUndefined(this.collection.start) ? 0 : this.collection.start,
...@@ -31,9 +35,18 @@ ...@@ -31,9 +35,18 @@
} }
this.$el.html(_.template(headerTemplate, { this.$el.html(_.template(headerTemplate, {
message: message, message: message,
srInfo: this.srInfo srInfo: this.srInfo,
sortableFields: this.collection.sortableFields,
sortOrder: this.sortOrder,
showSortControls: this.showSortControls
})); }));
return this; return this;
},
sortCollection: function () {
var selected = this.$('#paging-header-select option:selected');
this.sortOrder = selected.attr('value');
this.collection.setSortField(this.sortOrder);
} }
}); });
return PagingHeader; return PagingHeader;
......
...@@ -8,7 +8,7 @@ define([ ...@@ -8,7 +8,7 @@ define([
var pagingHeader, var pagingHeader,
newCollection = function (size, perPage) { newCollection = function (size, perPage) {
var pageSize = 5, var pageSize = 5,
results = _.map(_.range(size), function () { return {}; }); results = _.map(_.range(size), function (i) { return {foo: i}; });
var collection = new PagingCollection( var collection = new PagingCollection(
{ {
count: results.length, count: results.length,
...@@ -22,6 +22,14 @@ define([ ...@@ -22,6 +22,14 @@ define([
collection.start = 0; collection.start = 0;
collection.totalCount = results.length; collection.totalCount = results.length;
return collection; return collection;
},
sortableHeader = function (sortable) {
var collection = newCollection(5, 4);
collection.registerSortableField('foo', 'Display Name');
return new PagingHeader({
collection: collection,
showSortControls: _.isUndefined(sortable) ? true : sortable
});
}; };
it('correctly displays which items are being viewed', function () { it('correctly displays which items are being viewed', function () {
...@@ -47,5 +55,16 @@ define([ ...@@ -47,5 +55,16 @@ define([
expect(pagingHeader.$el.find('.search-count').text()) expect(pagingHeader.$el.find('.search-count').text())
.toContain('Showing 1 out of 1 total'); .toContain('Showing 1 out of 1 total');
}); });
it('optionally shows sorting controls', function () {
pagingHeader = sortableHeader().render();
expect(pagingHeader.$el.find('.listing-sort').text())
.toMatch(/Sorted by\s+Display Name/);
});
it('does not show sorting controls if the `showSortControls` option is not passed', function () {
pagingHeader = sortableHeader(false).render();
expect(pagingHeader.$el.text()).not.toContain('Sorted by');
});
}); });
}); });
<% if (!_.isUndefined(srInfo)) { %> <% if (!_.isUndefined(srInfo)) { %>
<h2 class="sr" id="<%= srInfo.id %>"><%- srInfo.text %></h2> <h2 class="sr" id="<%= srInfo.id %>"><%- srInfo.text %></h2>
<% } %> <% } %>
<div class="search-tools"> <div class="search-tools listing-tools">
<span class="search-count"> <span class="search-count listing-count">
<%= message %> <%= message %>
</span> </span>
<% if (showSortControls) { %>
|
<span class="field listing-sort">
<label class="field-label" for="paging-header-select"><%- gettext("Sorted by") %></label>
<select id="paging-header-select" name="paging-header-select" class="field-input input-select listing-sort-select">
<% _.each(sortableFields, function (option, key) { %>
<option value="<%= key %>" <% if (key === sortOrder) { %> selected="true" <% } %>>
<%- option.displayName %>
</option>
<% }) %>
</select>
</span>
<% } %>
</div> </div>
...@@ -11,6 +11,7 @@ from .fields import FieldsMixin ...@@ -11,6 +11,7 @@ from .fields import FieldsMixin
TOPIC_CARD_CSS = 'div.wrapper-card-core' TOPIC_CARD_CSS = 'div.wrapper-card-core'
CARD_TITLE_CSS = 'h3.card-title'
MY_TEAMS_BUTTON_CSS = 'a.nav-item[data-index="0"]' MY_TEAMS_BUTTON_CSS = 'a.nav-item[data-index="0"]'
BROWSE_BUTTON_CSS = 'a.nav-item[data-index="1"]' BROWSE_BUTTON_CSS = 'a.nav-item[data-index="1"]'
TEAMS_LINK_CSS = '.action-view' TEAMS_LINK_CSS = '.action-view'
...@@ -121,6 +122,11 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin): ...@@ -121,6 +122,11 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
"""Return a list of the topic cards present on the page.""" """Return a list of the topic cards present on the page."""
return self.q(css=TOPIC_CARD_CSS).results return self.q(css=TOPIC_CARD_CSS).results
@property
def topic_names(self):
"""Return a list of the topic names present on the page."""
return self.q(css=CARD_TITLE_CSS).map(lambda e: e.text).results
def browse_teams_for_topic(self, topic_name): def browse_teams_for_topic(self, topic_name):
""" """
Show the teams list for `topic_name`. Show the teams list for `topic_name`.
...@@ -130,6 +136,13 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin): ...@@ -130,6 +136,13 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
)[0].click() )[0].click()
self.wait_for_ajax() self.wait_for_ajax()
def sort_topics_by(self, sort_order):
"""Sort the list of topics by the given `sort_order`."""
self.q(
css='#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
).click()
self.wait_for_ajax()
class BrowseTeamsPage(CoursePage, PaginatedUIMixin): class BrowseTeamsPage(CoursePage, PaginatedUIMixin):
""" """
...@@ -388,3 +401,8 @@ class TeamPage(CoursePage, PaginatedUIMixin): ...@@ -388,3 +401,8 @@ class TeamPage(CoursePage, PaginatedUIMixin):
def new_post_button_present(self): def new_post_button_present(self):
""" Returns True if New Post button is present else False """ """ Returns True if New Post button is present else False """
return self.q(css='.discussion-module .new-post-btn').present return self.q(css='.discussion-module .new-post-btn').present
def click_all_topics_breadcrumb(self):
"""Navigate to the 'All Topics' page."""
self.q(css='.breadcrumbs a').results[0].click()
self.wait_for_ajax()
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Acceptance tests for the teams feature. Acceptance tests for the teams feature.
""" """
import json import json
import random
import ddt import ddt
from flaky import flaky from flaky import flaky
...@@ -22,6 +23,9 @@ from ...pages.lms.tab_nav import TabNavPage ...@@ -22,6 +23,9 @@ from ...pages.lms.tab_nav import TabNavPage
from ...pages.lms.teams import TeamsPage, MyTeamsPage, BrowseTopicsPage, BrowseTeamsPage, CreateTeamPage, TeamPage from ...pages.lms.teams import TeamsPage, MyTeamsPage, BrowseTopicsPage, BrowseTeamsPage, CreateTeamPage, TeamPage
TOPICS_PER_PAGE = 12
class TeamsTabBase(UniqueCourseTest): class TeamsTabBase(UniqueCourseTest):
"""Base class for Teams Tab tests""" """Base class for Teams Tab tests"""
def setUp(self): def setUp(self):
...@@ -274,6 +278,7 @@ class MyTeamsTest(TeamsTabBase): ...@@ -274,6 +278,7 @@ class MyTeamsTest(TeamsTabBase):
@attr('shard_5') @attr('shard_5')
@ddt.ddt
class BrowseTopicsTest(TeamsTabBase): class BrowseTopicsTest(TeamsTabBase):
""" """
Tests for the Browse tab of the Teams page. Tests for the Browse tab of the Teams page.
...@@ -283,6 +288,66 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -283,6 +288,66 @@ class BrowseTopicsTest(TeamsTabBase):
super(BrowseTopicsTest, self).setUp() super(BrowseTopicsTest, self).setUp()
self.topics_page = BrowseTopicsPage(self.browser, self.course_id) self.topics_page = BrowseTopicsPage(self.browser, self.course_id)
@ddt.data(('name', False), ('team_count', True))
@ddt.unpack
def test_sort_topics(self, sort_order, reverse):
"""
Scenario: the user should be able to sort the list of topics by name or team count
Given I am enrolled in a course with team configuration and topics
When I visit the Teams page
And I browse topics
Then I should see a list of topics for the course
When I choose a sort order
Then I should see the paginated list of topics in that order
"""
topics = self.create_topics(TOPICS_PER_PAGE + 1)
self.set_team_configuration({u"max_team_size": 100, u"topics": topics})
for i, topic in enumerate(random.sample(topics, len(topics))):
self.create_teams(topic, i)
topic['team_count'] = i
self.topics_page.visit()
self.topics_page.sort_topics_by(sort_order)
topic_names = self.topics_page.topic_names
self.assertEqual(len(topic_names), TOPICS_PER_PAGE)
self.assertEqual(
topic_names,
[t['name'] for t in sorted(topics, key=lambda t: t[sort_order], reverse=reverse)][:TOPICS_PER_PAGE]
)
def test_sort_topics_update(self):
"""
Scenario: the list of topics should remain sorted after updates
Given I am enrolled in a course with team configuration and topics
When I visit the Teams page
And I browse topics and choose a sort order
Then I should see the paginated list of topics in that order
When I create a team in one of those topics
And I return to the topics list
Then I should see the topics in the correct sorted order
"""
topics = self.create_topics(3)
self.set_team_configuration({u"max_team_size": 100, u"topics": topics})
self.topics_page.visit()
self.topics_page.sort_topics_by('team_count')
topic_name = self.topics_page.topic_names[-1]
topic = [t for t in topics if t['name'] == topic_name][0]
self.topics_page.browse_teams_for_topic(topic_name)
browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic)
self.assertTrue(browse_teams_page.is_browser_on_page())
browse_teams_page.click_create_team_link()
create_team_page = CreateTeamPage(self.browser, self.course_id, topic)
create_team_page.value_for_text_field(field_id='name', value='Team Name', press_enter=False)
create_team_page.value_for_textarea_field(
field_id='description',
value='Team description.'
)
create_team_page.submit_form()
team_page = TeamPage(self.browser, self.course_id)
self.assertTrue(team_page.is_browser_on_page)
team_page.click_all_topics_breadcrumb()
self.assertTrue(self.topics_page.is_browser_on_page())
self.assertEqual(topic_name, self.topics_page.topic_names[0])
def test_list_topics(self): def test_list_topics(self):
""" """
Scenario: a list of topics should be visible in the "Browse" tab Scenario: a list of topics should be visible in the "Browse" tab
...@@ -294,7 +359,7 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -294,7 +359,7 @@ class BrowseTopicsTest(TeamsTabBase):
self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(2)}) self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(2)})
self.topics_page.visit() self.topics_page.visit()
self.assertEqual(len(self.topics_page.topic_cards), 2) self.assertEqual(len(self.topics_page.topic_cards), 2)
self.assertEqual(self.topics_page.get_pagination_header_text(), 'Showing 1-2 out of 2 total') self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-2 out of 2 total'))
self.assertFalse(self.topics_page.pagination_controls_visible()) self.assertFalse(self.topics_page.pagination_controls_visible())
self.assertFalse(self.topics_page.is_previous_page_button_enabled()) self.assertFalse(self.topics_page.is_previous_page_button_enabled())
self.assertFalse(self.topics_page.is_next_page_button_enabled()) self.assertFalse(self.topics_page.is_next_page_button_enabled())
...@@ -309,8 +374,8 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -309,8 +374,8 @@ class BrowseTopicsTest(TeamsTabBase):
""" """
self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(20)}) self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(20)})
self.topics_page.visit() self.topics_page.visit()
self.assertEqual(len(self.topics_page.topic_cards), 12) self.assertEqual(len(self.topics_page.topic_cards), TOPICS_PER_PAGE)
self.assertEqual(self.topics_page.get_pagination_header_text(), 'Showing 1-12 out of 20 total') self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-12 out of 20 total'))
self.assertTrue(self.topics_page.pagination_controls_visible()) self.assertTrue(self.topics_page.pagination_controls_visible())
self.assertFalse(self.topics_page.is_previous_page_button_enabled()) self.assertFalse(self.topics_page.is_previous_page_button_enabled())
self.assertTrue(self.topics_page.is_next_page_button_enabled()) self.assertTrue(self.topics_page.is_next_page_button_enabled())
...@@ -360,10 +425,10 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -360,10 +425,10 @@ class BrowseTopicsTest(TeamsTabBase):
self.topics_page.visit() self.topics_page.visit()
self.topics_page.press_next_page_button() self.topics_page.press_next_page_button()
self.assertEqual(len(self.topics_page.topic_cards), 1) self.assertEqual(len(self.topics_page.topic_cards), 1)
self.assertEqual(self.topics_page.get_pagination_header_text(), 'Showing 13-13 out of 13 total') self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 13-13 out of 13 total'))
self.topics_page.press_previous_page_button() self.topics_page.press_previous_page_button()
self.assertEqual(len(self.topics_page.topic_cards), 12) self.assertEqual(len(self.topics_page.topic_cards), TOPICS_PER_PAGE)
self.assertEqual(self.topics_page.get_pagination_header_text(), 'Showing 1-12 out of 13 total') self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-12 out of 13 total'))
def test_topic_description_truncation(self): def test_topic_description_truncation(self):
""" """
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
var self = this, var self = this,
deferred = $.Deferred(); deferred = $.Deferred();
if (force || this.isStale) { if (force || this.isStale) {
this.fetch() this.setPage(1)
.done(function() { .done(function() {
self.isStale = false; self.isStale = false;
deferred.resolve(); deferred.resolve();
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
define([ define([
'gettext', 'gettext',
'teams/js/views/topic_card', 'teams/js/views/topic_card',
'common/js/components/views/paging_header',
'common/js/components/views/paginated_view' 'common/js/components/views/paginated_view'
], function (gettext, TopicCardView, PaginatedView) { ], function (gettext, TopicCardView, PagingHeader, PaginatedView) {
var TopicsView = PaginatedView.extend({ var TopicsView = PaginatedView.extend({
type: 'topics', type: 'topics',
...@@ -21,11 +22,18 @@ ...@@ -21,11 +22,18 @@
PaginatedView.prototype.initialize.call(this); PaginatedView.prototype.initialize.call(this);
}, },
createHeaderView: function () {
return new PagingHeader({
collection: this.options.collection,
srInfo: this.srInfo,
showSortControls: true
});
},
render: function() { render: function() {
var self = this; var self = this;
this.collection.refresh() this.collection.refresh()
.done(function() { .done(function() {
self.collection.isStale = false;
PaginatedView.prototype.render.call(self); PaginatedView.prototype.render.call(self);
}); });
return this; return this;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment