Commit 4be74937 by Andy Armstrong

Add searching to the teams view

TNL-1935
parent 9fd1d907
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
isZeroIndexed: false, isZeroIndexed: false,
perPage: 10, perPage: 10,
isStale: false,
sortField: '', sortField: '',
sortDirection: 'descending', sortDirection: 'descending',
sortableFields: {}, sortableFields: {},
...@@ -37,6 +39,8 @@ ...@@ -37,6 +39,8 @@
filterField: '', filterField: '',
filterableFields: {}, filterableFields: {},
searchString: null,
paginator_core: { paginator_core: {
type: 'GET', type: 'GET',
dataType: 'json', dataType: 'json',
...@@ -51,9 +55,10 @@ ...@@ -51,9 +55,10 @@
}, },
server_api: { server_api: {
'page': function () { return this.currentPage; }, page: function () { return this.currentPage; },
'page_size': function () { return this.perPage; }, page_size: function () { return this.perPage; },
'sort_order': function () { return this.sortField; } text_search: function () { return this.searchString ? this.searchString : ''; },
sort_order: function () { return this.sortField; }
}, },
parse: function (response) { parse: function (response) {
...@@ -61,7 +66,11 @@ ...@@ -61,7 +66,11 @@
this.currentPage = response.current_page; this.currentPage = response.current_page;
this.totalPages = response.num_pages; this.totalPages = response.num_pages;
this.start = response.start; this.start = response.start;
// Note: sort_order is not returned when performing a search
if (response.sort_order) {
this.sortField = response.sort_order; this.sortField = response.sort_order;
}
return response.results; return response.results;
}, },
...@@ -84,6 +93,7 @@ ...@@ -84,6 +93,7 @@
self = this; self = this;
return this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then( return this.goTo(page - (this.isZeroIndexed ? 1 : 0), {reset: true}).then(
function () { function () {
self.isStale = false;
self.trigger('page_changed'); self.trigger('page_changed');
}, },
function () { function () {
...@@ -92,6 +102,24 @@ ...@@ -92,6 +102,24 @@
); );
}, },
/**
* Refreshes the collection if it has been marked as stale.
* @returns {promise} Returns a promise representing the refresh.
*/
refresh: function() {
var deferred = $.Deferred();
if (this.isStale) {
this.setPage(1)
.done(function() {
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise();
},
/** /**
* Returns true if the collection has a next page, false otherwise. * Returns true if the collection has a next page, false otherwise.
*/ */
...@@ -183,7 +211,7 @@ ...@@ -183,7 +211,7 @@
} }
} }
this.sortField = fieldName; this.sortField = fieldName;
this.setPage(1); this.isStale = true;
}, },
/** /**
...@@ -193,7 +221,7 @@ ...@@ -193,7 +221,7 @@
*/ */
setSortDirection: function (direction) { setSortDirection: function (direction) {
this.sortDirection = direction; this.sortDirection = direction;
this.setPage(1); this.isStale = true;
}, },
/** /**
...@@ -203,7 +231,19 @@ ...@@ -203,7 +231,19 @@
*/ */
setFilterField: function (fieldName) { setFilterField: function (fieldName) {
this.filterField = fieldName; this.filterField = fieldName;
this.setPage(1); this.isStale = true;
},
/**
* Sets the string to use for a text search. If no string is specified then
* the search is cleared.
* @param searchString A string to search on, or null if no search is to be applied.
*/
setSearchString: function(searchString) {
if (searchString !== this.searchString) {
this.searchString = searchString;
this.isStale = true;
}
} }
}, { }, {
SortDirection: { SortDirection: {
......
...@@ -43,10 +43,16 @@ ...@@ -43,10 +43,16 @@
return this; return this;
}, },
/**
* Updates the collection's sort order, and fetches an updated set of
* results.
* @returns {*} A promise for the collection being updated
*/
sortCollection: function () { sortCollection: function () {
var selected = this.$('#paging-header-select option:selected'); var selected = this.$('#paging-header-select option:selected');
this.sortOrder = selected.attr('value'); this.sortOrder = selected.attr('value');
this.collection.setSortField(this.sortOrder); this.collection.setSortField(this.sortOrder);
return this.collection.refresh();
} }
}); });
return PagingHeader; return PagingHeader;
......
/**
* A search field that works in concert with a paginated collection. When the user
* performs a search, the collection's search string will be updated and then the
* collection will be refreshed to show the first page of results.
*/
;(function (define) {
'use strict';
define(['backbone', 'jquery', 'underscore', 'text!common/templates/components/search-field.underscore'],
function (Backbone, $, _, searchFieldTemplate) {
return Backbone.View.extend({
events: {
'submit .search-form': 'performSearch',
'blur .search-form': 'onFocusOut',
'keyup .search-field': 'refreshState',
'click .action-clear': 'clearSearch'
},
initialize: function(options) {
this.type = options.type;
this.label = options.label;
},
refreshState: function() {
var searchField = this.$('.search-field'),
clearButton = this.$('.action-clear'),
searchString = $.trim(searchField.val());
if (searchString) {
clearButton.removeClass('is-hidden');
} else {
clearButton.addClass('is-hidden');
}
},
render: function() {
this.$el.html(_.template(searchFieldTemplate, {
type: this.type,
searchString: this.collection.searchString,
searchLabel: this.label
}));
this.refreshState();
return this;
},
onFocusOut: function(event) {
// If the focus is going anywhere but the clear search
// button then treat it as a request to search.
if (!$(event.relatedTarget).hasClass('action-clear')) {
this.performSearch(event);
}
},
performSearch: function(event) {
var searchField = this.$('.search-field'),
searchString = $.trim(searchField.val());
event.preventDefault();
this.collection.setSearchString(searchString);
return this.collection.refresh();
},
clearSearch: function(event) {
event.preventDefault();
this.$('.search-field').val('');
this.collection.setSearchString('');
this.refreshState();
return this.collection.refresh();
}
});
});
}).call(this, define || RequireJS.define);
...@@ -10,11 +10,11 @@ define(['jquery', ...@@ -10,11 +10,11 @@ define(['jquery',
'use strict'; 'use strict';
describe('PagingCollection', function () { describe('PagingCollection', function () {
var collection, requests, server, assertQueryParams; var collection;
server = { var server = {
isZeroIndexed: false, isZeroIndexed: false,
count: 43, count: 43,
respond: function () { respond: function (requests) {
var params = (new URI(requests[requests.length - 1].url)).query(true), var params = (new URI(requests[requests.length - 1].url)).query(true),
page = parseInt(params['page'], 10), page = parseInt(params['page'], 10),
page_size = parseInt(params['page_size'], 10), page_size = parseInt(params['page_size'], 10),
...@@ -35,7 +35,7 @@ define(['jquery', ...@@ -35,7 +35,7 @@ define(['jquery',
} }
} }
}; };
assertQueryParams = function (params) { var assertQueryParams = function (requests, params) {
var urlParams = (new URI(requests[requests.length - 1].url)).query(true); var urlParams = (new URI(requests[requests.length - 1].url)).query(true);
_.each(params, function (value, key) { _.each(params, function (value, key) {
expect(urlParams[key]).toBe(value); expect(urlParams[key]).toBe(value);
...@@ -45,7 +45,6 @@ define(['jquery', ...@@ -45,7 +45,6 @@ define(['jquery',
beforeEach(function () { beforeEach(function () {
collection = new PagingCollection(); collection = new PagingCollection();
collection.perPage = 10; collection.perPage = 10;
requests = AjaxHelpers.requests(this);
server.isZeroIndexed = false; server.isZeroIndexed = false;
server.count = 43; server.count = 43;
}); });
...@@ -69,10 +68,11 @@ define(['jquery', ...@@ -69,10 +68,11 @@ define(['jquery',
}); });
it('can set the sort field', function () { it('can set the sort field', function () {
var requests = AjaxHelpers.requests(this);
collection.registerSortableField('test_field', 'Test Field'); collection.registerSortableField('test_field', 'Test Field');
collection.setSortField('test_field', false); collection.setSortField('test_field', false);
expect(requests.length).toBe(1); collection.refresh();
assertQueryParams({'sort_order': 'test_field'}); assertQueryParams(requests, {'sort_order': 'test_field'});
expect(collection.sortField).toBe('test_field'); expect(collection.sortField).toBe('test_field');
expect(collection.sortDisplayName()).toBe('Test Field'); expect(collection.sortDisplayName()).toBe('Test Field');
}); });
...@@ -80,7 +80,7 @@ define(['jquery', ...@@ -80,7 +80,7 @@ define(['jquery',
it('can set the filter field', function () { it('can set the filter field', function () {
collection.registerFilterableField('test_field', 'Test Field'); collection.registerFilterableField('test_field', 'Test Field');
collection.setFilterField('test_field'); collection.setFilterField('test_field');
expect(requests.length).toBe(1); collection.refresh();
// The default implementation does not send any query params for filtering // The default implementation does not send any query params for filtering
expect(collection.filterField).toBe('test_field'); expect(collection.filterField).toBe('test_field');
expect(collection.filterDisplayName()).toBe('Test Field'); expect(collection.filterDisplayName()).toBe('Test Field');
...@@ -88,11 +88,9 @@ define(['jquery', ...@@ -88,11 +88,9 @@ define(['jquery',
it('can set the sort direction', function () { it('can set the sort direction', function () {
collection.setSortDirection(PagingCollection.SortDirection.ASCENDING); collection.setSortDirection(PagingCollection.SortDirection.ASCENDING);
expect(requests.length).toBe(1);
// The default implementation does not send any query params for sort direction // The default implementation does not send any query params for sort direction
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.ASCENDING); expect(collection.sortDirection).toBe(PagingCollection.SortDirection.ASCENDING);
collection.setSortDirection(PagingCollection.SortDirection.DESCENDING); collection.setSortDirection(PagingCollection.SortDirection.DESCENDING);
expect(requests.length).toBe(2);
expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING); expect(collection.sortDirection).toBe(PagingCollection.SortDirection.DESCENDING);
}); });
...@@ -113,11 +111,12 @@ define(['jquery', ...@@ -113,11 +111,12 @@ define(['jquery',
'queries with page, page_size, and sort_order parameters when zero indexed': [true, 2], 'queries with page, page_size, and sort_order parameters when zero indexed': [true, 2],
'queries with page, page_size, and sort_order parameters when one indexed': [false, 3], 'queries with page, page_size, and sort_order parameters when one indexed': [false, 3],
}, function (isZeroIndexed, page) { }, function (isZeroIndexed, page) {
var requests = AjaxHelpers.requests(this);
collection.isZeroIndexed = isZeroIndexed; collection.isZeroIndexed = isZeroIndexed;
collection.perPage = 5; collection.perPage = 5;
collection.sortField = 'test_field'; collection.sortField = 'test_field';
collection.setPage(3); collection.setPage(3);
assertQueryParams({'page': page.toString(), 'page_size': '5', 'sort_order': 'test_field'}); assertQueryParams(requests, {'page': page.toString(), 'page_size': '5', 'sort_order': 'test_field'});
}); });
SpecHelpers.withConfiguration({ SpecHelpers.withConfiguration({
...@@ -129,27 +128,30 @@ define(['jquery', ...@@ -129,27 +128,30 @@ define(['jquery',
}, function () { }, function () {
describe('setPage', function() { describe('setPage', function() {
it('triggers a reset event when the page changes successfully', function () { it('triggers a reset event when the page changes successfully', function () {
var resetTriggered = false; var requests = AjaxHelpers.requests(this),
resetTriggered = false;
collection.on('reset', function () { resetTriggered = true; }); collection.on('reset', function () { resetTriggered = true; });
collection.setPage(3); collection.setPage(3);
server.respond(); server.respond(requests);
expect(resetTriggered).toBe(true); expect(resetTriggered).toBe(true);
}); });
it('triggers an error event when the requested page is out of range', function () { it('triggers an error event when the requested page is out of range', function () {
var errorTriggered = false; var requests = AjaxHelpers.requests(this),
errorTriggered = false;
collection.on('error', function () { errorTriggered = true; }); collection.on('error', function () { errorTriggered = true; });
collection.setPage(17); collection.setPage(17);
server.respond(); server.respond(requests);
expect(errorTriggered).toBe(true); expect(errorTriggered).toBe(true);
}); });
it('triggers an error event if the server responds with a 500', function () { it('triggers an error event if the server responds with a 500', function () {
var errorTriggered = false; var requests = AjaxHelpers.requests(this),
errorTriggered = false;
collection.on('error', function () { errorTriggered = true; }); collection.on('error', function () { errorTriggered = true; });
collection.setPage(2); collection.setPage(2);
expect(collection.getPage()).toBe(2); expect(collection.getPage()).toBe(2);
server.respond(); server.respond(requests);
collection.setPage(3); collection.setPage(3);
AjaxHelpers.respondWithError(requests, 500, {}, requests.length - 1); AjaxHelpers.respondWithError(requests, 500, {}, requests.length - 1);
expect(errorTriggered).toBe(true); expect(errorTriggered).toBe(true);
...@@ -159,11 +161,12 @@ define(['jquery', ...@@ -159,11 +161,12 @@ define(['jquery',
describe('getPage', function () { describe('getPage', function () {
it('returns the correct page', function () { it('returns the correct page', function () {
var requests = AjaxHelpers.requests(this);
collection.setPage(1); collection.setPage(1);
server.respond(); server.respond(requests);
expect(collection.getPage()).toBe(1); expect(collection.getPage()).toBe(1);
collection.setPage(3); collection.setPage(3);
server.respond(); server.respond(requests);
expect(collection.getPage()).toBe(3); expect(collection.getPage()).toBe(3);
}); });
}); });
...@@ -177,9 +180,10 @@ define(['jquery', ...@@ -177,9 +180,10 @@ define(['jquery',
'returns false on the last page': [5, 43, false] 'returns false on the last page': [5, 43, false]
}, },
function (page, count, result) { function (page, count, result) {
var requests = AjaxHelpers.requests(this);
server.count = count; server.count = count;
collection.setPage(page); collection.setPage(page);
server.respond(); server.respond(requests);
expect(collection.hasNextPage()).toBe(result); expect(collection.hasNextPage()).toBe(result);
} }
); );
...@@ -194,9 +198,10 @@ define(['jquery', ...@@ -194,9 +198,10 @@ define(['jquery',
'returns false on the first page': [1, 43, false] 'returns false on the first page': [1, 43, false]
}, },
function (page, count, result) { function (page, count, result) {
var requests = AjaxHelpers.requests(this);
server.count = count; server.count = count;
collection.setPage(page); collection.setPage(page);
server.respond(); server.respond(requests);
expect(collection.hasPreviousPage()).toBe(result); expect(collection.hasPreviousPage()).toBe(result);
} }
); );
...@@ -209,13 +214,14 @@ define(['jquery', ...@@ -209,13 +214,14 @@ define(['jquery',
'silently fails on the last page': [5, 43, 5] 'silently fails on the last page': [5, 43, 5]
}, },
function (page, count, newPage) { function (page, count, newPage) {
var requests = AjaxHelpers.requests(this);
server.count = count; server.count = count;
collection.setPage(page); collection.setPage(page);
server.respond(); server.respond(requests);
expect(collection.getPage()).toBe(page); expect(collection.getPage()).toBe(page);
collection.nextPage(); collection.nextPage();
if (requests.length > 1) { if (requests.length > 1) {
server.respond(); server.respond(requests);
} }
expect(collection.getPage()).toBe(newPage); expect(collection.getPage()).toBe(newPage);
} }
...@@ -229,13 +235,14 @@ define(['jquery', ...@@ -229,13 +235,14 @@ define(['jquery',
'silently fails on the first page': [1, 43, 1] 'silently fails on the first page': [1, 43, 1]
}, },
function (page, count, newPage) { function (page, count, newPage) {
var requests = AjaxHelpers.requests(this);
server.count = count; server.count = count;
collection.setPage(page); collection.setPage(page);
server.respond(); server.respond(requests);
expect(collection.getPage()).toBe(page); expect(collection.getPage()).toBe(page);
collection.previousPage(); collection.previousPage();
if (requests.length > 1) { if (requests.length > 1) {
server.respond(); server.respond(requests);
} }
expect(collection.getPage()).toBe(newPage); expect(collection.getPage()).toBe(newPage);
} }
......
define([
'underscore',
'common/js/components/views/search_field',
'common/js/components/collections/paging_collection',
'common/js/spec_helpers/ajax_helpers'
], function (_, SearchFieldView, PagingCollection, AjaxHelpers) {
'use strict';
describe('SearchFieldView', function () {
var searchFieldView,
mockUrl = '/api/mock_collection';
var newCollection = function (size, perPage) {
var pageSize = 5,
results = _.map(_.range(size), function (i) { return {foo: i}; });
var collection = new PagingCollection(
[],
{
url: mockUrl,
count: results.length,
num_pages: results.length / pageSize,
current_page: 1,
start: 0,
results: _.first(results, perPage)
},
{parse: true}
);
collection.start = 0;
collection.totalCount = results.length;
return collection;
};
var createSearchFieldView = function (options) {
options = _.extend(
{
type: 'test',
collection: newCollection(5, 4),
el: $('.test-search')
},
options || {}
);
return new SearchFieldView(options);
};
beforeEach(function() {
setFixtures('<section class="test-search"></section>');
});
it('correctly displays itself', function () {
searchFieldView = createSearchFieldView().render();
expect(searchFieldView.$('.search-field').val(), '');
expect(searchFieldView.$('.action-clear')).toHaveClass('is-hidden');
});
it('can display with an initial search string', function () {
searchFieldView = createSearchFieldView({
searchString: 'foo'
}).render();
expect(searchFieldView.$('.search-field').val(), 'foo');
});
it('refreshes the collection when performing a search', function () {
var requests = AjaxHelpers.requests(this);
searchFieldView = createSearchFieldView().render();
searchFieldView.$('.search-field').val('foo');
searchFieldView.$('.action-search').click();
AjaxHelpers.expectRequestURL(requests, mockUrl, {
page: '1',
page_size: '10',
sort_order: '',
text_search: 'foo'
});
AjaxHelpers.respondWithJson(requests, {
count: 10,
current_page: 1,
num_pages: 1,
start: 0,
results: []
});
expect(searchFieldView.$('.search-field').val(), 'foo');
});
it('can clear the search', function () {
var requests = AjaxHelpers.requests(this);
searchFieldView = createSearchFieldView({
searchString: 'foo'
}).render();
searchFieldView.$('.action-clear').click();
AjaxHelpers.expectRequestURL(requests, mockUrl, {
page: '1',
page_size: '10',
sort_order: '',
text_search: ''
});
AjaxHelpers.respondWithJson(requests, {
count: 10,
current_page: 1,
num_pages: 1,
start: 0,
results: []
});
expect(searchFieldView.$('.search-field').val(), '');
expect(searchFieldView.$('.action-clear')).toHaveClass('is-hidden');
});
});
});
define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) { define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
'use strict'; 'use strict';
var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, expectJsonRequestURL, var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, expectRequestURL,
respondWithJson, respondWithError, respondWithTextError, respondWithNoContent; respondWithJson, respondWithError, respondWithTextError, respondWithNoContent;
/* These utility methods are used by Jasmine tests to create a mock server or /* These utility methods are used by Jasmine tests to create a mock server or
...@@ -77,7 +77,7 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) { ...@@ -77,7 +77,7 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
* @param expectedParameters An object representing the URL parameters * @param expectedParameters An object representing the URL parameters
* @param requestIndex An optional index for the request (by default, the last request is used) * @param requestIndex An optional index for the request (by default, the last request is used)
*/ */
expectJsonRequestURL = function(requests, expectedUrl, expectedParameters, requestIndex) { expectRequestURL = function(requests, expectedUrl, expectedParameters, requestIndex) {
var request, parameters; var request, parameters;
if (_.isUndefined(requestIndex)) { if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1; requestIndex = requests.length - 1;
...@@ -153,15 +153,15 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) { ...@@ -153,15 +153,15 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
}; };
return { return {
'server': fakeServer, server: fakeServer,
'requests': fakeRequests, requests: fakeRequests,
'expectRequest': expectRequest, expectRequest: expectRequest,
'expectJsonRequest': expectJsonRequest, expectJsonRequest: expectJsonRequest,
'expectJsonRequestURL': expectJsonRequestURL, expectPostRequest: expectPostRequest,
'expectPostRequest': expectPostRequest, expectRequestURL: expectRequestURL,
'respondWithJson': respondWithJson, respondWithJson: respondWithJson,
'respondWithError': respondWithError, respondWithError: respondWithError,
'respondWithTextError': respondWithTextError, respondWithTextError: respondWithTextError,
'respondWithNoContent': respondWithNoContent, respondWithNoContent: respondWithNoContent
}; };
}); });
<div class="page-header-search wrapper-search-<%= type %>">
<form class="search-form">
<div class="wrapper-search-input">
<label for="search-<%= type %>" class="search-label">><%- searchLabel %></label>
<input id="search-<%= type %>" class="search-field" type="text" value="<%- searchString %>" placeholder="<%- searchLabel %>" />
<button type="button" class="action action-clear <%= searchLabel ? '' : 'is-hidden' %>" aria-label="<%- gettext('Clear search') %>">
<i class="icon fa fa-times-circle" aria-hidden="true"></i><span class="sr"><%- gettext('Search') %></span>
</button>
</div>
<button type="submit" class="action action-search"><span class="icon fa-search" aria-hidden="true"></span><span class="sr"><%- gettext('Search') %></span></button>
</form>
</div>
...@@ -155,13 +155,14 @@ ...@@ -155,13 +155,14 @@
define([ define([
// Run the common tests that use RequireJS. // Run the common tests that use RequireJS.
'common-requirejs/include/common/js/spec/components/feedback_spec.js',
'common-requirejs/include/common/js/spec/components/list_spec.js', 'common-requirejs/include/common/js/spec/components/list_spec.js',
'common-requirejs/include/common/js/spec/components/paginated_view_spec.js', 'common-requirejs/include/common/js/spec/components/paginated_view_spec.js',
'common-requirejs/include/common/js/spec/components/paging_collection_spec.js', 'common-requirejs/include/common/js/spec/components/paging_collection_spec.js',
'common-requirejs/include/common/js/spec/components/paging_header_spec.js', 'common-requirejs/include/common/js/spec/components/paging_header_spec.js',
'common-requirejs/include/common/js/spec/components/paging_footer_spec.js', 'common-requirejs/include/common/js/spec/components/paging_footer_spec.js',
'common-requirejs/include/common/js/spec/components/view_utils_spec.js', 'common-requirejs/include/common/js/spec/components/search_field_spec.js',
'common-requirejs/include/common/js/spec/components/feedback_spec.js' 'common-requirejs/include/common/js/spec/components/view_utils_spec.js'
]); ]);
}).call(this, requirejs, define); }).call(this, requirejs, define);
...@@ -142,6 +142,11 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin): ...@@ -142,6 +142,11 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
"""Return a list of the topic names present on the page.""" """Return a list of the topic names present on the page."""
return self.q(css=CARD_TITLE_CSS).map(lambda e: e.text).results return self.q(css=CARD_TITLE_CSS).map(lambda e: e.text).results
@property
def topic_descriptions(self):
"""Return a list of the topic descriptions present on the page."""
return self.q(css='p.card-description').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`.
...@@ -159,36 +164,32 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin): ...@@ -159,36 +164,32 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
self.wait_for_ajax() self.wait_for_ajax()
class BrowseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin): class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
""" """
The paginated UI for browsing teams within a Topic on the Teams The paginated UI for browsing teams within a Topic on the Teams
page. page.
""" """
def __init__(self, browser, course_id, topic): def __init__(self, browser, course_id, topic):
""" """
Set up `self.url_path` on instantiation, since it dynamically Note that `topic` is a dict representation of a topic following
reflects the current topic. Note that `topic` is a dict the same convention as a course module's topic.
representation of a topic following the same convention as a
course module's topic.
""" """
super(BrowseTeamsPage, self).__init__(browser, course_id) super(BaseTeamsPage, self).__init__(browser, course_id)
self.topic = topic self.topic = topic
self.url_path = "teams/#topics/{topic_id}".format(topic_id=self.topic['id'])
def is_browser_on_page(self): def is_browser_on_page(self):
"""Check if we're on the teams list page for a particular topic.""" """Check if we're on a teams list page for a particular topic."""
self.wait_for_element_presence('.team-actions', 'Wait for the bottom links to be present')
has_correct_url = self.url.endswith(self.url_path) has_correct_url = self.url.endswith(self.url_path)
teams_list_view_present = self.q(css='.teams-main').present teams_list_view_present = self.q(css='.teams-main').present
return has_correct_url and teams_list_view_present return has_correct_url and teams_list_view_present
@property @property
def header_topic_name(self): def header_name(self):
"""Get the topic name displayed by the page header""" """Get the topic name displayed by the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-title')[0].text return self.q(css=TEAMS_HEADER_CSS + ' .page-title')[0].text
@property @property
def header_topic_description(self): def header_description(self):
"""Get the topic description displayed by the page header""" """Get the topic description displayed by the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-description')[0].text return self.q(css=TEAMS_HEADER_CSS + ' .page-description')[0].text
...@@ -229,6 +230,48 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin): ...@@ -229,6 +230,48 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
).click() ).click()
self.wait_for_ajax() self.wait_for_ajax()
@property
def _showing_search_results(self):
"""
Returns true if showing search results.
"""
return self.header_description.startswith(u"Showing results for")
def search(self, string):
"""
Searches for the specified string, and returns a SearchTeamsPage
representing the search results page.
"""
self.q(css='.search-field').first.fill(string)
self.q(css='.action-search').first.click()
self.wait_for(
lambda: self._showing_search_results,
description="Showing search results"
)
page = SearchTeamsPage(self.browser, self.course_id, self.topic)
page.wait_for_page()
return page
class BrowseTeamsPage(BaseTeamsPage):
"""
The paginated UI for browsing teams within a Topic on the Teams
page.
"""
def __init__(self, browser, course_id, topic):
super(BrowseTeamsPage, self).__init__(browser, course_id, topic)
self.url_path = "teams/#topics/{topic_id}".format(topic_id=self.topic['id'])
class SearchTeamsPage(BaseTeamsPage):
"""
The paginated UI for showing team search results.
page.
"""
def __init__(self, browser, course_id, topic):
super(SearchTeamsPage, self).__init__(browser, course_id, topic)
self.url_path = "teams/#topics/{topic_id}/search".format(topic_id=self.topic['id'])
class CreateOrEditTeamPage(CoursePage, FieldsMixin): class CreateOrEditTeamPage(CoursePage, FieldsMixin):
""" """
......
...@@ -444,7 +444,7 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -444,7 +444,7 @@ class BrowseTopicsTest(TeamsTabBase):
{u"max_team_size": 1, u"topics": [{"name": "", "id": "", "description": initial_description}]} {u"max_team_size": 1, u"topics": [{"name": "", "id": "", "description": initial_description}]}
) )
self.topics_page.visit() self.topics_page.visit()
truncated_description = self.topics_page.topic_cards[0].text truncated_description = self.topics_page.topic_descriptions[0]
self.assertLess(len(truncated_description), len(initial_description)) self.assertLess(len(truncated_description), len(initial_description))
self.assertTrue(truncated_description.endswith('...')) self.assertTrue(truncated_description.endswith('...'))
self.assertIn(truncated_description.split('...')[0], initial_description) self.assertIn(truncated_description.split('...')[0], initial_description)
...@@ -467,8 +467,8 @@ class BrowseTopicsTest(TeamsTabBase): ...@@ -467,8 +467,8 @@ class BrowseTopicsTest(TeamsTabBase):
self.topics_page.browse_teams_for_topic('Example Topic') self.topics_page.browse_teams_for_topic('Example Topic')
browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic) browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic)
self.assertTrue(browse_teams_page.is_browser_on_page()) self.assertTrue(browse_teams_page.is_browser_on_page())
self.assertEqual(browse_teams_page.header_topic_name, 'Example Topic') self.assertEqual(browse_teams_page.header_name, 'Example Topic')
self.assertEqual(browse_teams_page.header_topic_description, 'Description') self.assertEqual(browse_teams_page.header_description, 'Description')
@attr('shard_5') @attr('shard_5')
...@@ -503,15 +503,24 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -503,15 +503,24 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
def verify_page_header(self): def verify_page_header(self):
"""Verify that the page header correctly reflects the current topic's name and description.""" """Verify that the page header correctly reflects the current topic's name and description."""
self.assertEqual(self.browse_teams_page.header_topic_name, self.topic['name']) self.assertEqual(self.browse_teams_page.header_name, self.topic['name'])
self.assertEqual(self.browse_teams_page.header_topic_description, self.topic['description']) self.assertEqual(self.browse_teams_page.header_description, self.topic['description'])
def verify_on_page(self, page_num, total_teams, pagination_header_text, footer_visible): def verify_search_header(self, search_results_page, search_query):
"""Verify that the page header correctly reflects the current topic's name and description."""
self.assertEqual(search_results_page.header_name, 'Team Search')
self.assertEqual(
search_results_page.header_description,
'Showing results for "{search_query}"'.format(search_query=search_query)
)
def verify_on_page(self, teams_page, page_num, total_teams, pagination_header_text, footer_visible):
""" """
Verify that we are on the correct team list page. Verify that we are on the correct team list page.
Arguments: Arguments:
page_num (int): The one-indexed page we expect to be on teams_page (BaseTeamsPage): The teams page object that should be the current page.
page_num (int): The one-indexed page number that we expect to be on
total_teams (list): An unsorted list of all the teams for the total_teams (list): An unsorted list of all the teams for the
current topic current topic
pagination_header_text (str): Text we expect to see in the pagination_header_text (str): Text we expect to see in the
...@@ -520,13 +529,13 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -520,13 +529,13 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
footer controls. footer controls.
""" """
sorted_teams = self.teams_with_default_sort_order(total_teams) sorted_teams = self.teams_with_default_sort_order(total_teams)
self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith(pagination_header_text)) self.assertTrue(teams_page.get_pagination_header_text().startswith(pagination_header_text))
self.verify_teams( self.verify_teams(
self.browse_teams_page, teams_page,
sorted_teams[(page_num - 1) * self.TEAMS_PAGE_SIZE:page_num * self.TEAMS_PAGE_SIZE] sorted_teams[(page_num - 1) * self.TEAMS_PAGE_SIZE:page_num * self.TEAMS_PAGE_SIZE]
) )
self.assertEqual( self.assertEqual(
self.browse_teams_page.pagination_controls_visible(), teams_page.pagination_controls_visible(),
footer_visible, footer_visible,
msg='Expected paging footer to be ' + 'visible' if footer_visible else 'invisible' msg='Expected paging footer to be ' + 'visible' if footer_visible else 'invisible'
) )
...@@ -648,11 +657,11 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -648,11 +657,11 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 1, time_between_creation=1) teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 1, time_between_creation=1)
self.browse_teams_page.visit() self.browse_teams_page.visit()
self.verify_page_header() self.verify_page_header()
self.verify_on_page(1, teams, 'Showing 1-10 out of 11 total', True) self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 11 total', True)
self.browse_teams_page.press_next_page_button() self.browse_teams_page.press_next_page_button()
self.verify_on_page(2, teams, 'Showing 11-11 out of 11 total', True) self.verify_on_page(self.browse_teams_page, 2, teams, 'Showing 11-11 out of 11 total', True)
self.browse_teams_page.press_previous_page_button() self.browse_teams_page.press_previous_page_button()
self.verify_on_page(1, teams, 'Showing 1-10 out of 11 total', True) self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 11 total', True)
def test_teams_page_input(self): def test_teams_page_input(self):
""" """
...@@ -670,25 +679,21 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -670,25 +679,21 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 10, time_between_creation=1) teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 10, time_between_creation=1)
self.browse_teams_page.visit() self.browse_teams_page.visit()
self.verify_page_header() self.verify_page_header()
self.verify_on_page(1, teams, 'Showing 1-10 out of 20 total', True) self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 20 total', True)
self.browse_teams_page.go_to_page(2) self.browse_teams_page.go_to_page(2)
self.verify_on_page(2, teams, 'Showing 11-20 out of 20 total', True) self.verify_on_page(self.browse_teams_page, 2, teams, 'Showing 11-20 out of 20 total', True)
self.browse_teams_page.go_to_page(1) self.browse_teams_page.go_to_page(1)
self.verify_on_page(1, teams, 'Showing 1-10 out of 20 total', True) self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 20 total', True)
def test_navigation_links(self): def test_browse_team_topics(self):
""" """
Scenario: User should be able to navigate to "browse all teams" and "search team description" links. Scenario: User should be able to navigate to "browse all teams" and "search team description" links.
Given I am enrolled in a course with a team configuration and a topic Given I am enrolled in a course with teams enabled
containing one team When I visit the Teams page for a topic
When I visit the Teams page for that topic
Then I should see the correct page header Then I should see the correct page header
And I should see the link to "browse all team" And I should see the link to "browse teams in other topics"
And I should navigate to that link When I should navigate to that link
And I see the relevant page loaded Then I should see the topic browse page
And I should see the link to "search teams"
And I should navigate to that link
And I see the relevant page loaded
""" """
self.browse_teams_page.visit() self.browse_teams_page.visit()
self.verify_page_header() self.verify_page_header()
...@@ -696,10 +701,23 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -696,10 +701,23 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
self.browse_teams_page.click_browse_all_teams_link() self.browse_teams_page.click_browse_all_teams_link()
self.assertTrue(self.topics_page.is_browser_on_page()) self.assertTrue(self.topics_page.is_browser_on_page())
def test_search(self):
"""
Scenario: User should be able to search for a team
Given I am enrolled in a course with teams enabled
When I visit the Teams page for that topic
And I search for 'banana'
Then I should see the search result page
And the search header should be shown
And 0 results should be shown
"""
# Note: all searches will return 0 results with the mock search server
# used by Bok Choy.
self.create_teams(self.topic, 5)
self.browse_teams_page.visit() self.browse_teams_page.visit()
self.verify_page_header() search_results_page = self.browse_teams_page.search('banana')
self.browse_teams_page.click_search_team_link() self.verify_search_header(search_results_page, 'banana')
# TODO Add search page expectation once that implemented. self.assertTrue(search_results_page.get_pagination_header_text().startswith('Showing 0 out of 0 total'))
@attr('shard_5') @attr('shard_5')
...@@ -726,8 +744,8 @@ class TeamFormActions(TeamsTabBase): ...@@ -726,8 +744,8 @@ class TeamFormActions(TeamsTabBase):
self.browse_teams_page.click_create_team_link() self.browse_teams_page.click_create_team_link()
self.verify_page_header( self.verify_page_header(
title='Create a New Team', title='Create a New Team',
description='Create a new team if you can\'t find existing teams to ' description='Create a new team if you can\'t find an existing team to join, '
'join, or if you would like to learn with friends you know.', 'or if you would like to learn with friends you know.',
breadcrumbs='All Topics {topic_name}'.format(topic_name=self.topic['name']) breadcrumbs='All Topics {topic_name}'.format(topic_name=self.topic['name'])
) )
......
...@@ -53,8 +53,8 @@ class Command(BaseCommand): ...@@ -53,8 +53,8 @@ class Command(BaseCommand):
if len(args) == 0 and not options.get('all', False): if len(args) == 0 and not options.get('all', False):
raise CommandError(u"reindex_course_team requires one or more arguments: <course_team_id>") raise CommandError(u"reindex_course_team requires one or more arguments: <course_team_id>")
elif not settings.FEATURES.get('ENABLE_TEAMS_SEARCH', False): elif not settings.FEATURES.get('ENABLE_TEAMS', False):
raise CommandError(u"ENABLE_TEAMS_SEARCH must be enabled") raise CommandError(u"ENABLE_TEAMS must be enabled to use course team indexing")
if options.get('all', False): if options.get('all', False):
course_teams = CourseTeam.objects.all() course_teams = CourseTeam.objects.all()
......
...@@ -39,9 +39,9 @@ class ReindexCourseTeamTest(SharedModuleStoreTestCase): ...@@ -39,9 +39,9 @@ class ReindexCourseTeamTest(SharedModuleStoreTestCase):
def test_teams_search_flag_disabled_raises_command_error(self): def test_teams_search_flag_disabled_raises_command_error(self):
""" Test that raises CommandError for disabled feature flag. """ """ Test that raises CommandError for disabled feature flag. """
with mock.patch('django.conf.settings.FEATURES') as features: with mock.patch('django.conf.settings.FEATURES') as features:
features.return_value = {"ENABLE_TEAMS_SEARCH": False} features.return_value = {"ENABLE_TEAMS": False}
with self.assertRaises(SystemExit), nostderr(): with self.assertRaises(SystemExit), nostderr():
with self.assertRaisesRegexp(CommandError, ".* ENABLE_TEAMS_SEARCH must be enabled .*"): with self.assertRaisesRegexp(CommandError, ".* ENABLE_TEAMS must be enabled .*"):
call_command('reindex_course_team') call_command('reindex_course_team')
def test_given_invalid_team_id_raises_command_error(self): def test_given_invalid_team_id_raises_command_error(self):
......
...@@ -15,7 +15,7 @@ class CourseTeamIndexer(object): ...@@ -15,7 +15,7 @@ class CourseTeamIndexer(object):
""" """
INDEX_NAME = "course_team_index" INDEX_NAME = "course_team_index"
DOCUMENT_TYPE_NAME = "course_team" DOCUMENT_TYPE_NAME = "course_team"
ENABLE_SEARCH_KEY = "ENABLE_TEAMS_SEARCH" ENABLE_SEARCH_KEY = "ENABLE_TEAMS"
def __init__(self, course_team): def __init__(self, course_team):
self.course_team = course_team self.course_team = course_team
......
...@@ -11,31 +11,11 @@ ...@@ -11,31 +11,11 @@
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
this.teamEvents.bind('teams:update', this.onUpdate, this); this.teamEvents.bind('teams:update', this.onUpdate, this);
this.isStale = false;
}, },
onUpdate: function(event) { onUpdate: function(event) {
// Mark the collection as stale so that it knows to refresh when needed.
this.isStale = true; this.isStale = true;
},
/**
* Refreshes the collection if it has been marked as stale.
* @param force If true, it will always refresh.
* @returns {promise} Returns a promise representing the refresh
*/
refresh: function(force) {
var self = this,
deferred = $.Deferred();
if (force || this.isStale) {
this.setPage(1)
.done(function() {
self.isStale = false;
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise();
} }
}); });
return BaseCollection; return BaseCollection;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
topic_id: this.topic_id = options.topic_id, topic_id: this.topic_id = options.topic_id,
expand: 'user', expand: 'user',
course_id: function () { return encodeURIComponent(self.course_id); }, course_id: function () { return encodeURIComponent(self.course_id); },
order_by: function () { return this.sortField; } order_by: function () { return self.searchString ? '' : this.sortField; }
}, },
BaseCollection.prototype.server_api BaseCollection.prototype.server_api
); );
......
...@@ -25,7 +25,9 @@ ...@@ -25,7 +25,9 @@
}, },
onUpdate: function(event) { onUpdate: function(event) {
this.isStale = this.isStale || event.action === 'create'; if (event.action === 'create') {
this.isStale = true;
}
}, },
model: TopicModel model: TopicModel
......
...@@ -28,7 +28,7 @@ define(['backbone', 'URI', 'underscore', 'common/js/spec_helpers/ajax_helpers', ...@@ -28,7 +28,7 @@ define(['backbone', 'URI', 'underscore', 'common/js/spec_helpers/ajax_helpers',
}); });
it('passes a course_id to the server', function () { it('passes a course_id to the server', function () {
testRequestParam(this, 'course_id', 'my/course/id'); testRequestParam(this, 'course_id', TeamSpecHelpers.testCourseID);
}); });
it('URL encodes its course_id ', function () { it('URL encodes its course_id ', function () {
......
define(["jquery", "backbone", "teams/js/teams_tab_factory"], define(['jquery', 'backbone', 'teams/js/teams_tab_factory',
function($, Backbone, TeamsTabFactory) { 'teams/js/spec_helpers/team_spec_helpers'],
function($, Backbone, TeamsTabFactory, TeamSpecHelpers) {
'use strict'; 'use strict';
describe("Teams Tab Factory", function() { describe("Teams Tab Factory", function() {
var teamsTab; var teamsTab;
var initializeTeamsTabFactory = function() { var initializeTeamsTabFactory = function() {
TeamsTabFactory({ TeamsTabFactory(TeamSpecHelpers.createMockContext());
topics: {results: []},
topicsUrl: '',
teamsUrl: '',
maxTeamSize: 9999,
courseID: 'edX/DemoX/Demo_Course',
userInfo: {
username: 'test-user',
privileged: false,
staff: false,
team_memberships_data: null
}
});
}; };
beforeEach(function() { beforeEach(function() {
......
...@@ -13,21 +13,21 @@ define([ ...@@ -13,21 +13,21 @@ define([
var teamsUrl = '/api/team/v0/teams/', var teamsUrl = '/api/team/v0/teams/',
createTeamData = { createTeamData = {
id: null, id: null,
name: "TeamName", name: 'TeamName',
course_id: "a/b/c", course_id: TeamSpecHelpers.testCourseID,
topic_id: "awesomeness", topic_id: TeamSpecHelpers.testTopicID,
date_created: "", date_created: '',
description: "TeamDescription", description: 'TeamDescription',
country: "US", country: 'US',
language: "en", language: 'en',
membership: [], membership: [],
last_activity_at: '' last_activity_at: ''
}, },
editTeamData = { editTeamData = {
name: "UpdatedAvengers", name: 'UpdatedAvengers',
description: "We do not discuss about avengers.", description: 'We do not discuss about avengers.',
country: "US", country: 'US',
language: "en" language: 'en'
}, },
verifyValidation = function (requests, teamEditView, fieldsData) { verifyValidation = function (requests, teamEditView, fieldsData) {
_.each(fieldsData, function (fieldData) { _.each(fieldsData, function (fieldData) {
...@@ -38,17 +38,19 @@ define([ ...@@ -38,17 +38,19 @@ define([
var message = teamEditView.$('.wrapper-msg'); var message = teamEditView.$('.wrapper-msg');
expect(message.hasClass('is-hidden')).toBeFalsy(); expect(message.hasClass('is-hidden')).toBeFalsy();
var actionMessage = (teamAction === 'create' ? 'Your team could not be created.' : 'Your team could not be updated.'); var actionMessage = (
teamAction === 'create' ? 'Your team could not be created.' : 'Your team could not be updated.'
);
expect(message.find('.title').text().trim()).toBe(actionMessage); expect(message.find('.title').text().trim()).toBe(actionMessage);
expect(message.find('.copy').text().trim()).toBe( expect(message.find('.copy').text().trim()).toBe(
"Check the highlighted fields below and try again." 'Check the highlighted fields below and try again.'
); );
_.each(fieldsData, function (fieldData) { _.each(fieldsData, function (fieldData) {
if (fieldData[2] === 'error') { if (fieldData[2] === 'error') {
expect(teamEditView.$(fieldData[0].split(" ")[0] + '.error').length).toBe(1); expect(teamEditView.$(fieldData[0].split(' ')[0] + '.error').length).toBe(1);
} else if (fieldData[2] === 'success') { } else if (fieldData[2] === 'success') {
expect(teamEditView.$(fieldData[0].split(" ")[0] + '.error').length).toBe(0); expect(teamEditView.$(fieldData[0].split(' ')[0] + '.error').length).toBe(0);
} }
}); });
...@@ -58,9 +60,9 @@ define([ ...@@ -58,9 +60,9 @@ define([
teamAction; teamAction;
var createEditTeamView = function () { var createEditTeamView = function () {
var teamModel = {}; var testTeam = {};
if (teamAction === 'edit') { if (teamAction === 'edit') {
teamModel = new TeamModel( testTeam = new TeamModel(
{ {
id: editTeamID, id: editTeamID,
name: 'Avengers', name: 'Avengers',
...@@ -80,16 +82,9 @@ define([ ...@@ -80,16 +82,9 @@ define([
teamEvents: TeamSpecHelpers.teamEvents, teamEvents: TeamSpecHelpers.teamEvents,
el: $('.teams-content'), el: $('.teams-content'),
action: teamAction, action: teamAction,
model: teamModel, model: testTeam,
teamParams: { topic: TeamSpecHelpers.createMockTopic(),
teamsUrl: teamsUrl, context: TeamSpecHelpers.testContext
courseID: "a/b/c",
topicID: 'awesomeness',
topicName: 'Awesomeness',
languages: [['aa', 'Afar'], ['fr', 'French'], ['en', 'English']],
countries: [['af', 'Afghanistan'], ['CA', 'Canada'], ['US', 'United States']],
teamsDetailUrl: teamModel.url
}
}).render(); }).render();
}; };
...@@ -133,13 +128,13 @@ define([ ...@@ -133,13 +128,13 @@ define([
teamEditView.$('.u-field-name input').val(teamsData.name); teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description); teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
teamEditView.$('.u-field-language select').val(teamsData.language).attr("selected", "selected"); teamEditView.$('.u-field-language select').val(teamsData.language).attr('selected', 'selected');
teamEditView.$('.u-field-country select').val(teamsData.country).attr("selected", "selected"); teamEditView.$('.u-field-country select').val(teamsData.country).attr('selected', 'selected');
teamEditView.$('.create-team.form-actions .action-primary').click(); teamEditView.$('.create-team.form-actions .action-primary').click();
AjaxHelpers.expectJsonRequest(requests, requestMethod(), teamsUrl, teamsData); AjaxHelpers.expectJsonRequest(requests, requestMethod(), teamsUrl, teamsData);
AjaxHelpers.respondWithJson(requests, _.extend(_.extend({}, teamsData), teamAction === 'create' ? {id: '123'} : {})); AjaxHelpers.respondWithJson(requests, _.extend({}, teamsData, teamAction === 'create' ? {id: '123'} : {}));
expect(teamEditView.$('.create-team.wrapper-msg .copy').text().trim().length).toBe(0); expect(teamEditView.$('.create-team.wrapper-msg .copy').text().trim().length).toBe(0);
expect(Backbone.history.navigate.calls[0].args).toContain(expectedUrl); expect(Backbone.history.navigate.calls[0].args).toContain(expectedUrl);
...@@ -209,10 +204,10 @@ define([ ...@@ -209,10 +204,10 @@ define([
errorCode, errorCode,
{'user_message': 'User message', 'developer_message': 'Developer message'} {'user_message': 'User message', 'developer_message': 'Developer message'}
); );
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("User message"); expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe('User message');
} else { } else {
AjaxHelpers.respondWithError(requests); AjaxHelpers.respondWithError(requests);
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("An error occurred. Please try again."); expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe('An error occurred. Please try again.');
} }
}; };
...@@ -233,7 +228,9 @@ define([ ...@@ -233,7 +228,9 @@ define([
}); });
it('can create a team', function () { it('can create a team', function () {
assertTeamCreateUpdateInfo(this, createTeamData, teamsUrl, 'teams/awesomeness/123'); assertTeamCreateUpdateInfo(
this, createTeamData, teamsUrl, 'teams/' + TeamSpecHelpers.testTopicID + '/123'
);
}); });
it('shows validation error message when field is empty', function () { it('shows validation error message when field is empty', function () {
...@@ -244,16 +241,16 @@ define([ ...@@ -244,16 +241,16 @@ define([
assertValidationMessagesWhenInvalidData(this); assertValidationMessagesWhenInvalidData(this);
}); });
it("shows an error message for HTTP 500", function () { it('shows an error message for HTTP 500', function () {
assertShowMessageOnError(this, createTeamData, teamsUrl, 500); assertShowMessageOnError(this, createTeamData, teamsUrl, 500);
}); });
it("shows correct error message when server returns an error", function () { it('shows correct error message when server returns an error', function () {
assertShowMessageOnError(this, createTeamData, teamsUrl, 400); assertShowMessageOnError(this, createTeamData, teamsUrl, 400);
}); });
it("changes route on cancel click", function () { it('changes route on cancel click', function () {
assertRedirectsToCorrectUrlOnCancel('topics/awesomeness'); assertRedirectsToCorrectUrlOnCancel('topics/' + TeamSpecHelpers.testTopicID);
}); });
}); });
...@@ -272,7 +269,10 @@ define([ ...@@ -272,7 +269,10 @@ define([
copyTeamsData.country = 'CA'; copyTeamsData.country = 'CA';
copyTeamsData.language = 'fr'; copyTeamsData.language = 'fr';
assertTeamCreateUpdateInfo(this, copyTeamsData, teamsUrl + editTeamID + '?expand=user', 'teams/awesomeness/' + editTeamID); assertTeamCreateUpdateInfo(
this, copyTeamsData, teamsUrl + editTeamID + '?expand=user',
'teams/' + TeamSpecHelpers.testTopicID + '/' + editTeamID
);
}); });
it('shows validation error message when field is empty', function () { it('shows validation error message when field is empty', function () {
...@@ -283,16 +283,16 @@ define([ ...@@ -283,16 +283,16 @@ define([
assertValidationMessagesWhenInvalidData(this); assertValidationMessagesWhenInvalidData(this);
}); });
it("shows an error message for HTTP 500", function () { it('shows an error message for HTTP 500', function () {
assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 500); assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 500);
}); });
it("shows correct error message when server returns an error", function () { it('shows correct error message when server returns an error', function () {
assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 400); assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 400);
}); });
it("changes route on cancel click", function () { it('changes route on cancel click', function () {
assertRedirectsToCorrectUrlOnCancel('teams/awesomeness/' + editTeamID); assertRedirectsToCorrectUrlOnCancel('teams/' + TeamSpecHelpers.testTopicID + '/' + editTeamID);
}); });
}); });
}); });
......
...@@ -13,17 +13,16 @@ define([ ...@@ -13,17 +13,16 @@ define([
}); });
var createMyTeamsView = function(options) { var createMyTeamsView = function(options) {
return new MyTeamsView({ return new MyTeamsView(_.extend(
{
el: '.teams-container', el: '.teams-container',
collection: options.teams || TeamSpecHelpers.createMockTeams(), collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(), teamMemberships: TeamSpecHelpers.createMockTeamMemberships(),
showActions: true, showActions: true,
teamParams: { context: TeamSpecHelpers.testContext
topicID: 'test-topic', },
countries: TeamSpecHelpers.testCountries, options
languages: TeamSpecHelpers.testLanguages )).render();
}
}).render();
}; };
it('can render itself', function () { it('can render itself', function () {
...@@ -62,15 +61,16 @@ define([ ...@@ -62,15 +61,16 @@ define([
expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.'); expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.');
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' }); teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
myTeamsView.render(); myTeamsView.render();
AjaxHelpers.expectJsonRequestURL( AjaxHelpers.expectRequestURL(
requests, requests,
'api/teams/team_memberships', TeamSpecHelpers.testContext.teamMembershipsUrl,
{ {
expand : 'team', expand : 'team',
username : 'testUser', username : TeamSpecHelpers.testContext.userInfo.username,
course_id : 'my/course/id', course_id : TeamSpecHelpers.testContext.courseID,
page : '1', page : '1',
page_size : '10' page_size : '10',
text_search: ''
} }
); );
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
......
...@@ -10,12 +10,10 @@ define([ ...@@ -10,12 +10,10 @@ define([
createMembershipData, createMembershipData,
createHeaderActionsView, createHeaderActionsView,
verifyErrorMessage, verifyErrorMessage,
ACCOUNTS_API_URL = '/api/user/v1/accounts/', ACCOUNTS_API_URL = '/api/user/v1/accounts/';
TEAMS_URL = '/api/team/v0/teams/',
TEAMS_MEMBERSHIP_URL = '/api/team/v0/team_membership/';
createTeamsUrl = function (teamId) { createTeamsUrl = function (teamId) {
return TEAMS_URL + teamId + '?expand=user'; return TeamSpecHelpers.testContext.teamsUrl + teamId + '?expand=user';
}; };
createTeamModelData = function (teamId, teamName, membership) { createTeamModelData = function (teamId, teamName, membership) {
...@@ -27,21 +25,22 @@ define([ ...@@ -27,21 +25,22 @@ define([
}; };
}; };
createHeaderActionsView = function(maxTeamSize, currentUsername, teamModelData, showEditButton) { createHeaderActionsView = function(requests, maxTeamSize, currentUsername, teamModelData, showEditButton) {
var teamId = 'teamA'; var model = new TeamModel(teamModelData, { parse: true }),
context = TeamSpecHelpers.createMockContext({
var model = new TeamModel(teamModelData, { parse: true }); maxTeamSize: maxTeamSize,
userInfo: TeamSpecHelpers.createMockUserInfo({
username: currentUsername
})
});
return new TeamProfileHeaderActionsView( return new TeamProfileHeaderActionsView(
{ {
courseID: TeamSpecHelpers.testCourseID, courseID: TeamSpecHelpers.testCourseID,
teamEvents: TeamSpecHelpers.teamEvents, teamEvents: TeamSpecHelpers.teamEvents,
context: context,
model: model, model: model,
teamsUrl: createTeamsUrl(teamId), topic: TeamSpecHelpers.createMockTopic(),
maxTeamSize: maxTeamSize,
currentUsername: currentUsername,
teamMembershipsUrl: TEAMS_MEMBERSHIP_URL,
topicID: '',
showEditButton: showEditButton showEditButton: showEditButton
} }
).render(); ).render();
...@@ -67,7 +66,7 @@ define([ ...@@ -67,7 +66,7 @@ define([
}); });
verifyErrorMessage = function (requests, errorMessage, expectedMessage, joinTeam) { verifyErrorMessage = function (requests, errorMessage, expectedMessage, joinTeam) {
var view = createHeaderActionsView(1, 'ma', createTeamModelData('teamA', 'teamAlpha', [])); var view = createHeaderActionsView(requests, 1, 'ma', createTeamModelData('teamA', 'teamAlpha', []));
if (joinTeam) { if (joinTeam) {
// if we want the error to return when user try to join team, respond with no membership // if we want the error to return when user try to join team, respond with no membership
AjaxHelpers.respondWithJson(requests, {"count": 0}); AjaxHelpers.respondWithJson(requests, {"count": 0});
...@@ -78,8 +77,9 @@ define([ ...@@ -78,8 +77,9 @@ define([
}; };
it('can render itself', function () { it('can render itself', function () {
var requests = AjaxHelpers.requests(this);
var teamModelData = createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma')); var teamModelData = createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'));
var view = createHeaderActionsView(1, 'ma', teamModelData); var view = createHeaderActionsView(requests, 1, 'ma', teamModelData);
expect(view.$('.join-team').length).toEqual(1); expect(view.$('.join-team').length).toEqual(1);
}); });
...@@ -90,14 +90,14 @@ define([ ...@@ -90,14 +90,14 @@ define([
var teamId = 'teamA'; var teamId = 'teamA';
var teamName = 'teamAlpha'; var teamName = 'teamAlpha';
var teamModelData = createTeamModelData(teamId, teamName, []); var teamModelData = createTeamModelData(teamId, teamName, []);
var view = createHeaderActionsView(1, currentUsername, teamModelData); var view = createHeaderActionsView(requests, 1, currentUsername, teamModelData);
// a get request will be sent to get user membership info // a get request will be sent to get user membership info
// because current user is not member of current team // because current user is not member of current team
AjaxHelpers.expectRequest( AjaxHelpers.expectRequest(
requests, requests,
'GET', 'GET',
TEAMS_MEMBERSHIP_URL + '?' + $.param({ TeamSpecHelpers.testContext.teamMembershipsUrl + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID 'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
}) })
); );
...@@ -111,7 +111,7 @@ define([ ...@@ -111,7 +111,7 @@ define([
AjaxHelpers.expectRequest( AjaxHelpers.expectRequest(
requests, requests,
'POST', 'POST',
TEAMS_MEMBERSHIP_URL, TeamSpecHelpers.testContext.teamMembershipsUrl,
$.param({'username': currentUsername, 'team_id': teamId}) $.param({'username': currentUsername, 'team_id': teamId})
); );
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
...@@ -135,14 +135,14 @@ define([ ...@@ -135,14 +135,14 @@ define([
it('shows already member message', function () { it('shows already member message', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var currentUsername = 'ma1'; var currentUsername = 'ma1';
var view = createHeaderActionsView(1, currentUsername, createTeamModelData('teamA', 'teamAlpha', [])); var view = createHeaderActionsView(requests, 1, currentUsername, createTeamModelData('teamA', 'teamAlpha', []));
// a get request will be sent to get user membership info // a get request will be sent to get user membership info
// because current user is not member of current team // because current user is not member of current team
AjaxHelpers.expectRequest( AjaxHelpers.expectRequest(
requests, requests,
'GET', 'GET',
TEAMS_MEMBERSHIP_URL + '?' + $.param({ TeamSpecHelpers.testContext.teamMembershipsUrl + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID 'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
}) })
); );
...@@ -156,6 +156,7 @@ define([ ...@@ -156,6 +156,7 @@ define([
it('shows team full message', function () { it('shows team full message', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var view = createHeaderActionsView( var view = createHeaderActionsView(
requests,
1, 1,
'ma1', 'ma1',
createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma')) createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'))
...@@ -199,7 +200,6 @@ define([ ...@@ -199,7 +200,6 @@ define([
}); });
it('shows correct error message if initializing the view fails', function () { it('shows correct error message if initializing the view fails', function () {
// Rendering the view sometimes require fetching user's memberships. This may fail.
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
// verify user_message // verify user_message
...@@ -225,23 +225,26 @@ define([ ...@@ -225,23 +225,26 @@ define([
view, view,
createAndAssertView; createAndAssertView;
createAndAssertView = function(showEditButton) { createAndAssertView = function(requests, showEditButton) {
teamModelData = createTeamModelData('aveA', 'avengers', createMembershipData('ma')); teamModelData = createTeamModelData('aveA', 'avengers', createMembershipData('ma'));
view = createHeaderActionsView(1, 'ma', teamModelData, showEditButton); view = createHeaderActionsView(requests, 1, 'ma', teamModelData, showEditButton);
expect(view.$('.action-edit-team').length).toEqual(showEditButton ? 1 : 0); expect(view.$('.action-edit-team').length).toEqual(showEditButton ? 1 : 0);
}; };
it('renders when option showEditButton is true', function () { it('renders when option showEditButton is true', function () {
createAndAssertView(true); var requests = AjaxHelpers.requests(this);
createAndAssertView(requests, true);
}); });
it('does not render when option showEditButton is false', function () { it('does not render when option showEditButton is false', function () {
createAndAssertView(false); var requests = AjaxHelpers.requests(this);
createAndAssertView(requests, false);
}); });
it("can navigate to correct url", function () { it("can navigate to correct url", function () {
var requests = AjaxHelpers.requests(this);
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
createAndAssertView(true); createAndAssertView(requests, true);
var editButton = view.$('.action-edit-team'); var editButton = view.$('.action-edit-team');
expect(editButton.length).toEqual(1); expect(editButton.length).toEqual(1);
......
...@@ -11,7 +11,7 @@ define([ ...@@ -11,7 +11,7 @@ define([
DEFAULT_MEMBERSHIP = [ DEFAULT_MEMBERSHIP = [
{ {
'user': { 'user': {
'username': 'bilbo', 'username': TeamSpecHelpers.testUser,
'profile_image': { 'profile_image': {
'has_image': true, 'has_image': true,
'image_url_medium': '/image-url' 'image_url_medium': '/image-url'
...@@ -42,20 +42,8 @@ define([ ...@@ -42,20 +42,8 @@ define([
profileView = new TeamProfileView({ profileView = new TeamProfileView({
teamEvents: TeamSpecHelpers.teamEvents, teamEvents: TeamSpecHelpers.teamEvents,
courseID: TeamSpecHelpers.testCourseID, courseID: TeamSpecHelpers.testCourseID,
context: TeamSpecHelpers.testContext,
model: teamModel, model: teamModel,
maxTeamSize: options.maxTeamSize || 3,
requestUsername: 'bilbo',
countries : [
['', ''],
['US', 'United States'],
['CA', 'Canada']
],
languages : [
['', ''],
['en', 'English'],
['fr', 'French']
],
teamMembershipDetailUrl: 'api/team/v0/team_membership/team_id,bilbo',
setFocusToHeaderFunc: function() { setFocusToHeaderFunc: function() {
$('.teams-content').focus(); $('.teams-content').focus();
} }
...@@ -88,7 +76,9 @@ define([ ...@@ -88,7 +76,9 @@ define([
$('.prompt.warning .action-primary').click(); $('.prompt.warning .action-primary').click();
// expect a request to DELETE the team membership // expect a request to DELETE the team membership
AjaxHelpers.expectJsonRequest(requests, 'DELETE', 'api/team/v0/team_membership/test-team,bilbo'); AjaxHelpers.expectJsonRequest(
requests, 'DELETE', '/api/team/v0/team_membership/test-team,' + TeamSpecHelpers.testUser
);
AjaxHelpers.respondWithNoContent(requests); AjaxHelpers.respondWithNoContent(requests);
// expect a request to refetch the user's team memberships // expect a request to refetch the user's team memberships
...@@ -135,7 +125,7 @@ define([ ...@@ -135,7 +125,7 @@ define([
expect(view.$('.team-detail-header').text()).toBe('Team Details'); expect(view.$('.team-detail-header').text()).toBe('Team Details');
expect(view.$('.team-country').text()).toContain('United States'); expect(view.$('.team-country').text()).toContain('United States');
expect(view.$('.team-language').text()).toContain('English'); expect(view.$('.team-language').text()).toContain('English');
expect(view.$('.team-capacity').text()).toContain(members + ' / 3 Members'); expect(view.$('.team-capacity').text()).toContain(members + ' / 6 Members');
expect(view.$('.team-member').length).toBe(members); expect(view.$('.team-member').length).toBe(members);
expect(Boolean(view.$('.leave-team-link').length)).toBe(memberOfTeam); expect(Boolean(view.$('.leave-team-link').length)).toBe(memberOfTeam);
}; };
...@@ -176,9 +166,9 @@ define([ ...@@ -176,9 +166,9 @@ define([
expect(view.$('.team-user-membership-status').text().trim()).toBe('You are a member of this team.'); expect(view.$('.team-user-membership-status').text().trim()).toBe('You are a member of this team.');
// assert tooltip text. // assert tooltip text.
expect(view.$('.member-profile p').text()).toBe('bilbo'); expect(view.$('.member-profile p').text()).toBe(TeamSpecHelpers.testUser);
// assert user profile page url. // assert user profile page url.
expect(view.$('.member-profile').attr('href')).toBe('/u/bilbo'); expect(view.$('.member-profile').attr('href')).toBe('/u/' + TeamSpecHelpers.testUser);
//Verify that the leave team link is present //Verify that the leave team link is present
expect(view.$(leaveTeamLinkSelector).text()).toContain('Leave Team'); expect(view.$(leaveTeamLinkSelector).text()).toContain('Leave Team');
......
...@@ -17,11 +17,7 @@ define([ ...@@ -17,11 +17,7 @@ define([
collection: options.teams || TeamSpecHelpers.createMockTeams(), collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(), teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(),
showActions: true, showActions: true,
teamParams: { context: TeamSpecHelpers.testContext
topicID: 'test-topic',
countries: TeamSpecHelpers.testCountries,
languages: TeamSpecHelpers.testLanguages
}
}).render(); }).render();
}; };
......
...@@ -8,14 +8,6 @@ define([ ...@@ -8,14 +8,6 @@ define([
'use strict'; 'use strict';
describe('TeamsTab', function () { describe('TeamsTab', function () {
var expectContent = function (teamsTabView, text) {
expect(teamsTabView.$('.page-content-main').text()).toContain(text);
};
var expectHeader = function (teamsTabView, text) {
expect(teamsTabView.$('.teams-header').text()).toContain(text);
};
var expectError = function (teamsTabView, text) { var expectError = function (teamsTabView, text) {
expect(teamsTabView.$('.warning').text()).toContain(text); expect(teamsTabView.$('.warning').text()).toContain(text);
}; };
...@@ -26,30 +18,17 @@ define([ ...@@ -26,30 +18,17 @@ define([
var createTeamsTabView = function(options) { var createTeamsTabView = function(options) {
var defaultTopics = { var defaultTopics = {
count: 1, count: 5,
num_pages: 1, num_pages: 1,
current_page: 1, current_page: 1,
start: 0, start: 0,
results: [{ results: TeamSpecHelpers.createMockTopicData(1, 5)
description: 'test description',
name: 'test topic',
id: 'test_topic',
team_count: 0
}]
}, },
teamsTabView = new TeamsTabView( teamsTabView = new TeamsTabView(
_.extend(
{ {
el: $('.teams-content'), el: $('.teams-content'),
topics: defaultTopics, context: TeamSpecHelpers.createMockContext(options)
userInfo: TeamSpecHelpers.createMockUserInfo(), }
topicsUrl: 'api/topics/',
topicUrl: 'api/topics/topic_id,test/course/id',
teamsUrl: 'api/teams/',
courseID: 'test/course/id'
},
options || {}
)
); );
teamsTabView.start(); teamsTabView.start();
return teamsTabView; return teamsTabView;
...@@ -82,7 +61,7 @@ define([ ...@@ -82,7 +61,7 @@ define([
var requests = AjaxHelpers.requests(this), var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView(); teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('topics/no_such_topic', {trigger: true}); teamsTabView.router.navigate('topics/no_such_topic', {trigger: true});
AjaxHelpers.expectRequest(requests, 'GET', 'api/topics/no_such_topic,test/course/id', null); AjaxHelpers.expectRequest(requests, 'GET', '/api/team/v0/topics/no_such_topic,course/1', null);
AjaxHelpers.respondWithError(requests, 404); AjaxHelpers.respondWithError(requests, 404);
expectError(teamsTabView, 'The topic "no_such_topic" could not be found.'); expectError(teamsTabView, 'The topic "no_such_topic" could not be found.');
expectFocus(teamsTabView.$('.warning')); expectFocus(teamsTabView.$('.warning'));
...@@ -91,8 +70,8 @@ define([ ...@@ -91,8 +70,8 @@ define([
it('displays and focuses an error message when trying to navigate to a nonexistent team', function () { it('displays and focuses an error message when trying to navigate to a nonexistent team', function () {
var requests = AjaxHelpers.requests(this), var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView(); teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('teams/test_topic/no_such_team', {trigger: true}); teamsTabView.router.navigate('teams/' + TeamSpecHelpers.testTopicID + '/no_such_team', {trigger: true});
AjaxHelpers.expectRequest(requests, 'GET', 'api/teams/no_such_team?expand=user', null); AjaxHelpers.expectRequest(requests, 'GET', '/api/team/v0/teams/no_such_team?expand=user', null);
AjaxHelpers.respondWithError(requests, 404); AjaxHelpers.respondWithError(requests, 404);
expectError(teamsTabView, 'The team "no_such_team" could not be found.'); expectError(teamsTabView, 'The team "no_such_team" could not be found.');
expectFocus(teamsTabView.$('.warning')); expectFocus(teamsTabView.$('.warning'));
...@@ -113,7 +92,7 @@ define([ ...@@ -113,7 +92,7 @@ define([
it('allows access to a team which an unprivileged user is a member of', function () { it('allows access to a team which an unprivileged user is a member of', function () {
var teamsTabView = createTeamsTabView({ var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({ userInfo: TeamSpecHelpers.createMockUserInfo({
username: 'test-user', username: TeamSpecHelpers.testUser,
privileged: false privileged: false
}) })
}); });
...@@ -121,7 +100,7 @@ define([ ...@@ -121,7 +100,7 @@ define([
attributes: { attributes: {
membership: [{ membership: [{
user: { user: {
username: 'test-user' username: TeamSpecHelpers.testUser
} }
}] }]
} }
...@@ -137,5 +116,103 @@ define([ ...@@ -137,5 +116,103 @@ define([
})).toBe(true); })).toBe(true);
}); });
}); });
describe('Search', function () {
var verifyTeamsRequest = function(requests, options) {
AjaxHelpers.expectRequestURL(requests, TeamSpecHelpers.testContext.teamsUrl,
_.extend(
{
topic_id: TeamSpecHelpers.testTopicID,
expand: 'user',
course_id: TeamSpecHelpers.testCourseID,
order_by: '',
page: '1',
page_size: '10',
text_search: ''
},
options
));
};
it('can search teams', function () {
var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView();
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
verifyTeamsRequest(requests, {
order_by: 'last_activity_at',
text_search: ''
});
AjaxHelpers.respondWithJson(requests, {});
teamsTabView.$('.search-field').val('foo');
teamsTabView.$('.action-search').click();
verifyTeamsRequest(requests, {
order_by: '',
text_search: 'foo'
});
AjaxHelpers.respondWithJson(requests, {});
expect(teamsTabView.$('.page-title').text()).toBe('Team Search');
expect(teamsTabView.$('.page-description').text()).toBe('Showing results for "foo"');
});
it('can clear a search', function () {
var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView();
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
AjaxHelpers.respondWithJson(requests, {});
// Perform a search
teamsTabView.$('.search-field').val('foo');
teamsTabView.$('.action-search').click();
AjaxHelpers.respondWithJson(requests, {});
// Clear the search and submit it again
teamsTabView.$('.search-field').val('');
teamsTabView.$('.action-search').click();
verifyTeamsRequest(requests, {
order_by: 'last_activity_at',
text_search: ''
});
AjaxHelpers.respondWithJson(requests, {});
expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
});
it('clears the search when navigating away and then back', function () {
var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView();
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
AjaxHelpers.respondWithJson(requests, {});
// Perform a search
teamsTabView.$('.search-field').val('foo');
teamsTabView.$('.action-search').click();
AjaxHelpers.respondWithJson(requests, {});
// Navigate back to the teams list
teamsTabView.$('.breadcrumbs a').last().click();
verifyTeamsRequest(requests, {
order_by: 'last_activity_at',
text_search: ''
});
AjaxHelpers.respondWithJson(requests, {});
expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
});
it('does not switch to showing results when the search returns an error', function () {
var requests = AjaxHelpers.requests(this),
teamsTabView = createTeamsTabView();
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
AjaxHelpers.respondWithJson(requests, {});
// Perform a search
teamsTabView.$('.search-field').val('foo');
teamsTabView.$('.action-search').click();
AjaxHelpers.respondWithError(requests);
expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
expect(teamsTabView.$('.search-field').val(), 'foo');
});
});
}); });
}); });
...@@ -11,14 +11,11 @@ define([ ...@@ -11,14 +11,11 @@ define([
var createTopicTeamsView = function(options) { var createTopicTeamsView = function(options) {
return new TopicTeamsView({ return new TopicTeamsView({
el: '.teams-container', el: '.teams-container',
model: TeamSpecHelpers.createMockTopic(),
collection: options.teams || TeamSpecHelpers.createMockTeams(), collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(), teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(),
showActions: true, showActions: true,
teamParams: { context: TeamSpecHelpers.testContext
topicID: 'test-topic',
countries: TeamSpecHelpers.testCountries,
languages: TeamSpecHelpers.testLanguages
}
}).render(); }).render();
}; };
...@@ -27,8 +24,8 @@ define([ ...@@ -27,8 +24,8 @@ define([
options = {showActions: true}; options = {showActions: true};
} }
var expectedTitle = 'Are you having trouble finding a team to join?', var expectedTitle = 'Are you having trouble finding a team to join?',
expectedMessage = 'Try browsing all teams or searching team descriptions. If you ' + expectedMessage = 'Browse teams in other topics or search teams in this topic. ' +
'still can\'t find a team to join, create a new team in this topic.', 'If you still can\'t find a team to join, create a new team in this topic.',
title = teamsView.$('.title').text().trim(), title = teamsView.$('.title').text().trim(),
message = teamsView.$('.copy').text().trim(); message = teamsView.$('.copy').text().trim();
if (options.showActions) { if (options.showActions) {
...@@ -65,17 +62,16 @@ define([ ...@@ -65,17 +62,16 @@ define([
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership }); teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
teamsView.$('a.browse-teams').click(); teamsView.$('.browse-teams').click();
expect(Backbone.history.navigate.calls[0].args).toContain('browse'); expect(Backbone.history.navigate.calls[0].args).toContain('browse');
}); });
it('can search teams', function () { it('gives the search field focus when clicking on the search teams link', function () {
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership }); teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate'); spyOn($.fn, 'focus').andCallThrough();
teamsView.$('a.search-teams').click(); teamsView.$('.search-teams').click();
// TODO! Should be updated once team description search feature is available expect(teamsView.$('.search-field').first().focus).toHaveBeenCalled();
expect(Backbone.history.navigate.calls[0].args).toContain('browse');
}); });
it('can show the create team modal', function () { it('can show the create team modal', function () {
...@@ -83,7 +79,9 @@ define([ ...@@ -83,7 +79,9 @@ define([
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership }); teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
teamsView.$('a.create-team').click(); teamsView.$('a.create-team').click();
expect(Backbone.history.navigate.calls[0].args).toContain('topics/test-topic/create-team'); expect(Backbone.history.navigate.calls[0].args).toContain(
'topics/' + TeamSpecHelpers.testTopicID + '/create-team'
);
}); });
it('does not show actions for a user already in a team', function () { it('does not show actions for a user already in a team', function () {
...@@ -118,13 +116,13 @@ define([ ...@@ -118,13 +116,13 @@ define([
verifyActions(teamsView, {showActions: true}); verifyActions(teamsView, {showActions: true});
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' }); teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
teamsView.render(); teamsView.render();
AjaxHelpers.expectJsonRequestURL( AjaxHelpers.expectRequestURL(
requests, requests,
'foo', 'foo',
{ {
expand : 'team', expand : 'team',
username : 'testUser', username : 'testUser',
course_id : 'my/course/id', course_id : TeamSpecHelpers.testCourseID,
page : '1', page : '1',
page_size : '10' page_size : '10'
} }
......
...@@ -10,7 +10,8 @@ define([ ...@@ -10,7 +10,8 @@ define([
return new TopicsView({ return new TopicsView({
teamEvents: TeamSpecHelpers.teamEvents, teamEvents: TeamSpecHelpers.teamEvents,
el: '.topics-container', el: '.topics-container',
collection: topicCollection collection: topicCollection,
context: TeamSpecHelpers.createMockContext()
}).render(); }).render();
}; };
...@@ -48,14 +49,15 @@ define([ ...@@ -48,14 +49,15 @@ define([
topicsView = createTopicsView(); topicsView = createTopicsView();
triggerUpdateEvent(topicsView); triggerUpdateEvent(topicsView);
AjaxHelpers.expectJsonRequestURL( AjaxHelpers.expectRequestURL(
requests, requests,
'api/teams/topics', TeamSpecHelpers.testContext.topicUrl,
{ {
course_id : 'my/course/id', course_id: TeamSpecHelpers.testCourseID,
page : '1', page: '1',
page_size : '5', // currently the page size is determined by the size of the collection page_size: '5', // currently the page size is determined by the size of the collection
order_by : 'name' order_by: 'name',
text_search: ''
} }
); );
}); });
...@@ -66,14 +68,15 @@ define([ ...@@ -66,14 +68,15 @@ define([
// Staff are not immediately added to the team, but may choose to join after the create event. // Staff are not immediately added to the team, but may choose to join after the create event.
triggerUpdateEvent(topicsView, true); triggerUpdateEvent(topicsView, true);
AjaxHelpers.expectJsonRequestURL( AjaxHelpers.expectRequestURL(
requests, requests,
'api/teams/topics', TeamSpecHelpers.testContext.topicUrl,
{ {
course_id : 'my/course/id', course_id: TeamSpecHelpers.testCourseID,
page : '1', page: '1',
page_size : '5', // currently the page size is determined by the size of the collection page_size: '5', // currently the page size is determined by the size of the collection
order_by : 'name' order_by: 'name',
text_search: ''
} }
); );
}); });
......
...@@ -3,13 +3,15 @@ define([ ...@@ -3,13 +3,15 @@ define([
'underscore', 'underscore',
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership', 'teams/js/collections/team_membership',
'teams/js/collections/topic' 'teams/js/collections/topic',
], function (Backbone, _, TeamCollection, TeamMembershipCollection, TopicCollection) { 'teams/js/models/topic'
], function (Backbone, _, TeamCollection, TeamMembershipCollection, TopicCollection, TopicModel) {
'use strict'; 'use strict';
var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse, var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse,
createMockTopicData, createMockTopicCollection, createMockTopicData, createMockTopicCollection, createMockTopic,
testCourseID = 'course/1', testCourseID = 'course/1',
testUser = 'testUser', testUser = 'testUser',
testTopicID = 'test-topic-1',
testTeamDiscussionID = "12345", testTeamDiscussionID = "12345",
teamEvents = _.clone(Backbone.Events), teamEvents = _.clone(Backbone.Events),
testCountries = [ testCountries = [
...@@ -52,7 +54,7 @@ define([ ...@@ -52,7 +54,7 @@ define([
}, },
{ {
teamEvents: teamEvents, teamEvents: teamEvents,
course_id: 'my/course/id', course_id: testCourseID,
parse: true parse: true
} }
); );
...@@ -81,18 +83,22 @@ define([ ...@@ -81,18 +83,22 @@ define([
num_pages: 3, num_pages: 3,
current_page: 1, current_page: 1,
start: 0, start: 0,
sort_order: 'last_activity_at',
results: teamMembershipData results: teamMembershipData
}, },
_.extend(_.extend({}, { _.extend(
{},
{
teamEvents: teamEvents, teamEvents: teamEvents,
course_id: 'my/course/id', course_id: testCourseID,
parse: true, parse: true,
url: 'api/teams/team_memberships', url: testContext.teamMembershipsUrl,
username: testUser, username: testUser,
privileged: false, privileged: false,
staff: false staff: false
}), },
options) options
)
); );
}; };
...@@ -144,7 +150,7 @@ define([ ...@@ -144,7 +150,7 @@ define([
group_id: 1, group_id: 1,
endorsed: false endorsed: false
}, },
options || {} options
); );
}; };
...@@ -228,21 +234,56 @@ define([ ...@@ -228,21 +234,56 @@ define([
context: "standalone", context: "standalone",
endorsed: false endorsed: false
}, },
options || {} options
); );
}; };
createMockTopicData = function (startIndex, stopIndex) { createMockTopicData = function (startIndex, stopIndex) {
return _.map(_.range(startIndex, stopIndex + 1), function (i) { return _.map(_.range(startIndex, stopIndex + 1), function (i) {
return { return {
"description": "description " + i, "description": "Test description " + i,
"name": "topic " + i, "name": "Test Topic " + i,
"id": "id " + i, "id": "test-topic-" + i,
"team_count": 0 "team_count": 0
}; };
}); });
}; };
createMockTopic = function(options) {
return new TopicModel(_.extend(
{
id: testTopicID,
name: 'Test Topic 1',
description: 'Test description 1'
},
options
));
};
var testContext = {
courseID: testCourseID,
topics: {
count: 5,
num_pages: 1,
current_page: 1,
start: 0,
results: createMockTopicData(1, 5)
},
maxTeamSize: 6,
languages: testLanguages,
countries: testCountries,
topicUrl: '/api/team/v0/topics/topic_id,' + testCourseID,
teamsUrl: '/api/team/v0/teams/',
teamsDetailUrl: '/api/team/v0/teams/team_id',
teamMembershipsUrl: '/api/team/v0/team_memberships/',
teamMembershipDetailUrl: '/api/team/v0/team_membership/team_id,' + testUser,
userInfo: createMockUserInfo()
};
var createMockContext = function(options) {
return _.extend({}, testContext, options);
};
createMockTopicCollection = function (topicData) { createMockTopicCollection = function (topicData) {
topicData = topicData !== undefined ? topicData : createMockTopicData(1, 5); topicData = topicData !== undefined ? topicData : createMockTopicData(1, 5);
...@@ -253,13 +294,13 @@ define([ ...@@ -253,13 +294,13 @@ define([
num_pages: 2, num_pages: 2,
start: 0, start: 0,
results: topicData, results: topicData,
sort_order: "name" sort_order: 'name'
}, },
{ {
teamEvents: teamEvents, teamEvents: teamEvents,
course_id: 'my/course/id', course_id: testCourseID,
parse: true, parse: true,
url: 'api/teams/topics' url: testContext.topicUrl
} }
); );
}; };
...@@ -268,14 +309,18 @@ define([ ...@@ -268,14 +309,18 @@ define([
teamEvents: teamEvents, teamEvents: teamEvents,
testCourseID: testCourseID, testCourseID: testCourseID,
testUser: testUser, testUser: testUser,
testTopicID: testTopicID,
testCountries: testCountries, testCountries: testCountries,
testLanguages: testLanguages, testLanguages: testLanguages,
testTeamDiscussionID: testTeamDiscussionID, testTeamDiscussionID: testTeamDiscussionID,
testContext: testContext,
createMockTeamData: createMockTeamData, createMockTeamData: createMockTeamData,
createMockTeams: createMockTeams, createMockTeams: createMockTeams,
createMockTeamMembershipsData: createMockTeamMembershipsData, createMockTeamMembershipsData: createMockTeamMembershipsData,
createMockTeamMemberships: createMockTeamMemberships, createMockTeamMemberships: createMockTeamMemberships,
createMockUserInfo: createMockUserInfo, createMockUserInfo: createMockUserInfo,
createMockContext: createMockContext,
createMockTopic: createMockTopic,
createMockPostResponse: createMockPostResponse, createMockPostResponse: createMockPostResponse,
createMockDiscussionResponse: createMockDiscussionResponse, createMockDiscussionResponse: createMockDiscussionResponse,
createAnnotatedContentInfo: createAnnotatedContentInfo, createAnnotatedContentInfo: createAnnotatedContentInfo,
......
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
define(['jquery', 'underscore', 'backbone', 'teams/js/views/teams_tab'], define(['jquery', 'underscore', 'backbone', 'teams/js/views/teams_tab'],
function ($, _, Backbone, TeamsTabView) { function ($, _, Backbone, TeamsTabView) {
return function (options) { return function (options) {
var teamsTab = new TeamsTabView(_.extend(options, {el: $('.teams-content')})); var teamsTab = new TeamsTabView({
el: $('.teams-content'),
context: options
});
teamsTab.start(); teamsTab.start();
}; };
}); });
......
...@@ -21,22 +21,19 @@ ...@@ -21,22 +21,19 @@
initialize: function(options) { initialize: function(options) {
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
this.courseID = options.teamParams.courseID; this.context = options.context;
this.topicID = options.teamParams.topicID; this.topic = options.topic;
this.collection = options.collection; this.collection = options.collection;
this.teamsUrl = options.teamParams.teamsUrl;
this.languages = options.teamParams.languages;
this.countries = options.teamParams.countries;
this.teamsDetailUrl = options.teamParams.teamsDetailUrl;
this.action = options.action; this.action = options.action;
if (this.action === 'create') { if (this.action === 'create') {
this.teamModel = new TeamModel({}); this.teamModel = new TeamModel({});
this.teamModel.url = this.teamsUrl; this.teamModel.url = this.context.teamsUrl;
this.primaryButtonTitle = gettext("Create"); this.primaryButtonTitle = gettext("Create");
} else if(this.action === 'edit' ) { } else if(this.action === 'edit' ) {
this.teamModel = options.model; this.teamModel = options.model;
this.teamModel.url = this.teamsDetailUrl.replace('team_id', options.model.get('id')) + '?expand=user'; this.teamModel.url = this.context.teamsDetailUrl.replace('team_id', options.model.get('id')) +
'?expand=user';
this.primaryButtonTitle = gettext("Update"); this.primaryButtonTitle = gettext("Update");
} }
...@@ -63,7 +60,7 @@ ...@@ -63,7 +60,7 @@
required: false, required: false,
showMessages: false, showMessages: false,
titleIconName: 'fa-comment-o', titleIconName: 'fa-comment-o',
options: this.languages, options: this.context.languages,
helpMessage: gettext('The language that team members primarily use to communicate with each other.') helpMessage: gettext('The language that team members primarily use to communicate with each other.')
}); });
...@@ -74,7 +71,7 @@ ...@@ -74,7 +71,7 @@
required: false, required: false,
showMessages: false, showMessages: false,
titleIconName: 'fa-globe', titleIconName: 'fa-globe',
options: this.countries, options: this.context.countries,
helpMessage: gettext('The country that team members primarily identify with.') helpMessage: gettext('The country that team members primarily identify with.')
}); });
}, },
...@@ -117,8 +114,8 @@ ...@@ -117,8 +114,8 @@
}; };
if (this.action === 'create') { if (this.action === 'create') {
data.course_id = this.courseID; data.course_id = this.context.courseID;
data.topic_id = this.topicID; data.topic_id = this.topic.id;
} else if (this.action === 'edit' ) { } else if (this.action === 'edit' ) {
saveOptions.patch = true; saveOptions.patch = true;
saveOptions.contentType = 'application/merge-patch+json'; saveOptions.contentType = 'application/merge-patch+json';
...@@ -137,7 +134,7 @@ ...@@ -137,7 +134,7 @@
team: result team: result
}); });
Backbone.history.navigate( Backbone.history.navigate(
'teams/' + view.topicID + '/' + view.teamModel.id, 'teams/' + view.topic.id + '/' + view.teamModel.id,
{trigger: true} {trigger: true}
); );
}) })
...@@ -208,9 +205,9 @@ ...@@ -208,9 +205,9 @@
event.preventDefault(); event.preventDefault();
var url; var url;
if (this.action === 'create') { if (this.action === 'create') {
url = 'topics/' + this.topicID; url = 'topics/' + this.topic.id;
} else if (this.action === 'edit' ) { } else if (this.action === 'edit' ) {
url = 'teams/' + this.topicID + '/' + this.teamModel.get('id'); url = 'teams/' + this.topic.id + '/' + this.teamModel.get('id');
} }
Backbone.history.navigate(url, {trigger: true}); Backbone.history.navigate(url, {trigger: true});
} }
......
...@@ -18,15 +18,11 @@ ...@@ -18,15 +18,11 @@
}, },
initialize: function (options) { initialize: function (options) {
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
this.courseID = options.courseID; this.context = options.context;
this.maxTeamSize = options.maxTeamSize;
this.requestUsername = options.requestUsername;
this.isPrivileged = options.isPrivileged;
this.teamMembershipDetailUrl = options.teamMembershipDetailUrl;
this.setFocusToHeaderFunc = options.setFocusToHeaderFunc; this.setFocusToHeaderFunc = options.setFocusToHeaderFunc;
this.countries = TeamUtils.selectorOptionsArrayToHashWithBlank(options.countries); this.countries = TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries);
this.languages = TeamUtils.selectorOptionsArrayToHashWithBlank(options.languages); this.languages = TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.languages);
this.listenTo(this.model, "change", this.render); this.listenTo(this.model, "change", this.render);
}, },
...@@ -34,18 +30,17 @@ ...@@ -34,18 +30,17 @@
render: function () { render: function () {
var memberships = this.model.get('membership'), var memberships = this.model.get('membership'),
discussionTopicID = this.model.get('discussion_topic_id'), discussionTopicID = this.model.get('discussion_topic_id'),
isMember = TeamUtils.isUserMemberOfTeam(memberships, this.requestUsername); isMember = TeamUtils.isUserMemberOfTeam(memberships, this.context.userInfo.username);
this.$el.html(_.template(teamTemplate, { this.$el.html(_.template(teamTemplate, {
courseID: this.courseID, courseID: this.context.courseID,
discussionTopicID: discussionTopicID, discussionTopicID: discussionTopicID,
readOnly: !(this.isPrivileged || isMember), readOnly: !(this.context.userInfo.privileged || isMember),
country: this.countries[this.model.get('country')], country: this.countries[this.model.get('country')],
language: this.languages[this.model.get('language')], language: this.languages[this.model.get('language')],
membershipText: TeamUtils.teamCapacityText(memberships.length, this.maxTeamSize), membershipText: TeamUtils.teamCapacityText(memberships.length, this.context.maxTeamSize),
isMember: isMember, isMember: isMember,
hasCapacity: memberships.length < this.maxTeamSize, hasCapacity: memberships.length < this.context.maxTeamSize,
hasMembers: memberships.length >= 1 hasMembers: memberships.length >= 1
})); }));
this.discussionView = new TeamDiscussionView({ this.discussionView = new TeamDiscussionView({
el: this.$('.discussion-module') el: this.$('.discussion-module')
...@@ -84,7 +79,7 @@ ...@@ -84,7 +79,7 @@
function() { function() {
$.ajax({ $.ajax({
type: 'DELETE', type: 'DELETE',
url: view.teamMembershipDetailUrl.replace('team_id', view.model.get('id')) url: view.context.teamMembershipDetailUrl.replace('team_id', view.model.get('id'))
}).done(function (data) { }).done(function (data) {
view.model.fetch() view.model.fetch()
.done(function() { .done(function() {
......
...@@ -21,21 +21,19 @@ ...@@ -21,21 +21,19 @@
initialize: function(options) { initialize: function(options) {
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
this.template = _.template(teamProfileHeaderActionsTemplate); this.template = _.template(teamProfileHeaderActionsTemplate);
this.courseID = options.courseID; this.context = options.context;
this.maxTeamSize = options.maxTeamSize;
this.currentUsername = options.currentUsername;
this.teamMembershipsUrl = options.teamMembershipsUrl;
this.showEditButton = options.showEditButton; this.showEditButton = options.showEditButton;
this.topicID = options.topicID; this.topic = options.topic;
this.listenTo(this.model, "change", this.render); this.listenTo(this.model, "change", this.render);
}, },
render: function() { render: function() {
var view = this, var view = this,
username = this.context.userInfo.username,
message, message,
showJoinButton, showJoinButton,
teamHasSpace; teamHasSpace;
this.getUserTeamInfo(this.currentUsername, view.maxTeamSize).done(function (info) { this.getUserTeamInfo(username, this.context.maxTeamSize).done(function (info) {
teamHasSpace = info.teamHasSpace; teamHasSpace = info.teamHasSpace;
// if user is the member of current team then we wouldn't show anything // if user is the member of current team then we wouldn't show anything
...@@ -62,8 +60,8 @@ ...@@ -62,8 +60,8 @@
var view = this; var view = this;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: view.teamMembershipsUrl, url: view.context.teamMembershipsUrl,
data: {'username': view.currentUsername, 'team_id': view.model.get('id')} data: {'username': view.context.userInfo.username, 'team_id': view.model.get('id')}
}).done(function (data) { }).done(function (data) {
view.model.fetch() view.model.fetch()
.done(function() { .done(function() {
...@@ -97,8 +95,8 @@ ...@@ -97,8 +95,8 @@
var view = this; var view = this;
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: view.teamMembershipsUrl, url: view.context.teamMembershipsUrl,
data: {'username': username, 'course_id': view.courseID} data: {'username': username, 'course_id': view.context.courseID}
}).done(function (data) { }).done(function (data) {
info.alreadyMember = (data.count > 0); info.alreadyMember = (data.count > 0);
info.memberOfCurrentTeam = false; info.memberOfCurrentTeam = false;
...@@ -115,9 +113,13 @@ ...@@ -115,9 +113,13 @@
return deferred.promise(); return deferred.promise();
}, },
editTeam: function (event) { editTeam: function (event) {
event.preventDefault(); event.preventDefault();
Backbone.history.navigate('topics/' + this.topicID + '/' + this.model.get('id') +'/edit-team', {trigger: true}); Backbone.history.navigate(
'topics/' + this.topic.id + '/' + this.model.get('id') +'/edit-team',
{trigger: true}
);
} }
}); });
}); });
......
...@@ -18,14 +18,14 @@ ...@@ -18,14 +18,14 @@
initialize: function (options) { initialize: function (options) {
this.topic = options.topic; this.topic = options.topic;
this.teamMemberships = options.teamMemberships; this.teamMemberships = options.teamMemberships;
this.teamParams = options.teamParams; this.context = options.context;
this.itemViewClass = TeamCardView.extend({ this.itemViewClass = TeamCardView.extend({
router: options.router, router: options.router,
topic: options.topic, topic: options.topic,
maxTeamSize: options.maxTeamSize, maxTeamSize: this.context.maxTeamSize,
srInfo: this.srInfo, srInfo: this.srInfo,
countries: TeamUtils.selectorOptionsArrayToHashWithBlank(options.teamParams.countries), countries: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries),
languages: TeamUtils.selectorOptionsArrayToHashWithBlank(options.teamParams.languages) languages: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.languages)
}); });
PaginatedView.prototype.initialize.call(this); PaginatedView.prototype.initialize.call(this);
} }
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
configuration: 'square_card', configuration: 'square_card',
cardClass: 'topic-card', cardClass: 'topic-card',
pennant: gettext('Topic'),
title: function () { return this.model.get('name'); }, title: function () { return this.model.get('name'); },
description: function () { return this.model.get('description'); }, description: function () { return this.model.get('description'); },
details: function () { return this.detailViews; }, details: function () { return this.detailViews; },
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
}, },
initialize: function(options) { initialize: function(options) {
this.showSortControls = options.showSortControls;
TeamsView.prototype.initialize.call(this, options); TeamsView.prototype.initialize.call(this, options);
}, },
...@@ -28,7 +29,15 @@ ...@@ -28,7 +29,15 @@
if (self.teamMemberships.canUserCreateTeam()) { if (self.teamMemberships.canUserCreateTeam()) {
var message = interpolate_text( var message = interpolate_text(
_.escape(gettext("Try {browse_span_start}browsing all teams{span_end} or {search_span_start}searching team descriptions{span_end}. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")), // Translators: this string is shown at the bottom of the teams page
// to find a team to join or else to create a new one. There are three
// links that need to be included in the message:
// 1. Browse teams in other topics
// 2. search teams
// 3. create a new team
// Be careful to start each link with the appropriate start indicator
// (e.g. {browse_span_start} for #1) and finish it with {span_end}.
_.escape(gettext("{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")),
{ {
'browse_span_start': '<a class="browse-teams" href="">', 'browse_span_start': '<a class="browse-teams" href="">',
'search_span_start': '<a class="search-teams" href="">', 'search_span_start': '<a class="search-teams" href="">',
...@@ -48,21 +57,25 @@ ...@@ -48,21 +57,25 @@
}, },
searchTeams: function (event) { searchTeams: function (event) {
var searchField = $('.page-header-search .search-field');
event.preventDefault(); event.preventDefault();
// TODO! Will navigate to correct place once required functionality is available searchField.focus();
Backbone.history.navigate('browse', {trigger: true}); searchField.select();
$('html, body').animate({
scrollTop: 0
}, 500);
}, },
showCreateTeamForm: function (event) { showCreateTeamForm: function (event) {
event.preventDefault(); event.preventDefault();
Backbone.history.navigate('topics/' + this.teamParams.topicID + '/create-team', {trigger: true}); Backbone.history.navigate('topics/' + this.model.id + '/create-team', {trigger: true});
}, },
createHeaderView: function () { createHeaderView: function () {
return new PagingHeader({ return new PagingHeader({
collection: this.options.collection, collection: this.options.collection,
srInfo: this.srInfo, srInfo: this.srInfo,
showSortControls: true showSortControls: this.showSortControls
}); });
} }
}); });
......
...@@ -175,7 +175,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -175,7 +175,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* text_search: Searches for full word matches on the name, description, * text_search: Searches for full word matches on the name, description,
country, and language fields. NOTES: Search is on full names for countries country, and language fields. NOTES: Search is on full names for countries
and languages, not the ISO codes. Text_search cannot be requested along with and languages, not the ISO codes. Text_search cannot be requested along with
with order_by. Searching relies on the ENABLE_TEAMS_SEARCH flag being set to True. with order_by.
* order_by: Cannot be called along with with text_search. Must be one of the following: * order_by: Cannot be called along with with text_search. Must be one of the following:
...@@ -311,7 +311,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -311,7 +311,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
status=status.HTTP_400_BAD_REQUEST status=status.HTTP_400_BAD_REQUEST
) )
if 'text_search' in request.QUERY_PARAMS and 'order_by' in request.QUERY_PARAMS: text_search = request.QUERY_PARAMS.get('text_search', None)
if text_search and request.QUERY_PARAMS.get('order_by', None):
return Response( return Response(
build_api_error(ugettext_noop("text_search and order_by cannot be provided together")), build_api_error(ugettext_noop("text_search and order_by cannot be provided together")),
status=status.HTTP_400_BAD_REQUEST status=status.HTTP_400_BAD_REQUEST
...@@ -327,13 +328,12 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -327,13 +328,12 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
return Response(error, status=status.HTTP_400_BAD_REQUEST) return Response(error, status=status.HTTP_400_BAD_REQUEST)
result_filter.update({'topic_id': request.QUERY_PARAMS['topic_id']}) result_filter.update({'topic_id': request.QUERY_PARAMS['topic_id']})
if 'text_search' in request.QUERY_PARAMS and CourseTeamIndexer.search_is_enabled(): if text_search and CourseTeamIndexer.search_is_enabled():
search_engine = CourseTeamIndexer.engine() search_engine = CourseTeamIndexer.engine()
text_search = request.QUERY_PARAMS['text_search'].encode('utf-8')
result_filter.update({'course_id': course_id_string}) result_filter.update({'course_id': course_id_string})
search_results = search_engine.search( search_results = search_engine.search(
query_string=text_search, query_string=text_search.encode('utf-8'),
field_dictionary=result_filter, field_dictionary=result_filter,
size=MAXIMUM_SEARCH_SIZE, size=MAXIMUM_SEARCH_SIZE,
) )
......
...@@ -631,7 +631,7 @@ PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = ENV_TOKENS.get( ...@@ -631,7 +631,7 @@ PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = ENV_TOKENS.get(
if FEATURES.get('ENABLE_COURSEWARE_SEARCH') or \ if FEATURES.get('ENABLE_COURSEWARE_SEARCH') or \
FEATURES.get('ENABLE_DASHBOARD_SEARCH') or \ FEATURES.get('ENABLE_DASHBOARD_SEARCH') or \
FEATURES.get('ENABLE_COURSE_DISCOVERY') or \ FEATURES.get('ENABLE_COURSE_DISCOVERY') or \
FEATURES.get('ENABLE_TEAMS_SEARCH'): FEATURES.get('ENABLE_TEAMS'):
# Use ElasticSearch as the search engine herein # Use ElasticSearch as the search engine herein
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine" SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
......
...@@ -401,9 +401,6 @@ FEATURES = { ...@@ -401,9 +401,6 @@ FEATURES = {
# Teams feature # Teams feature
'ENABLE_TEAMS': True, 'ENABLE_TEAMS': True,
# Enable indexing teams for search
'ENABLE_TEAMS_SEARCH': False,
# Show video bumper in LMS # Show video bumper in LMS
'ENABLE_VIDEO_BUMPER': False, 'ENABLE_VIDEO_BUMPER': False,
......
...@@ -485,9 +485,6 @@ FEATURES['ENABLE_EDXNOTES'] = True ...@@ -485,9 +485,6 @@ FEATURES['ENABLE_EDXNOTES'] = True
# Enable teams feature for tests. # Enable teams feature for tests.
FEATURES['ENABLE_TEAMS'] = True FEATURES['ENABLE_TEAMS'] = True
# Enable indexing teams for search
FEATURES['ENABLE_TEAMS_SEARCH'] = True
# Add milestones to Installed apps for testing # Add milestones to Installed apps for testing
INSTALLED_APPS += ('milestones', 'openedx.core.djangoapps.call_stack_manager') INSTALLED_APPS += ('milestones', 'openedx.core.djangoapps.call_stack_manager')
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
var json = this.model.attributes; var json = this.model.attributes;
this.$el.html(this.template(json)); this.$el.html(this.template(json));
if (this.headerActionsView) { if (this.headerActionsView) {
this.headerActionsView.setElement(this.$('.header-action-view')).render(); this.headerActionsView.setElement(this.$('.page-header-secondary')).render();
} }
return this; return this;
} }
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
display: inline-block; display: inline-block;
width: flex-grid(4,12); width: flex-grid(4,12);
@include text-align(right); @include text-align(right);
vertical-align: text-bottom;
} }
} }
...@@ -104,8 +105,10 @@ ...@@ -104,8 +105,10 @@
.action-search { .action-search {
@extend %button-reset; @extend %button-reset;
padding: ($baseline/4) ($baseline/2); padding: ($baseline/5) ($baseline/2);
vertical-align: middle;
background-color: $gray-l3;
text-shadow: none;
.icon { .icon {
color: $gray-l3; color: $gray-l3;
......
...@@ -12,5 +12,5 @@ ...@@ -12,5 +12,5 @@
<h2 class="page-title"><%- title %></h2> <h2 class="page-title"><%- title %></h2>
<p class="page-description"><%- description %></p> <p class="page-description"><%- description %></p>
</div> </div>
<div class="header-action-view"></div> <div class="page-header-secondary"></div>
</header> </header>
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