Commit 1265d540 by Andy Armstrong

Merge pull request #9302 from edx/andya/team-refresh-handling

Implement model refreshing for Teams tab
parents 752226df c84abfab
define(['sinon', 'underscore'], function(sinon, _) { define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, 'use strict';
var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, expectJsonRequestURL,
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
...@@ -69,6 +71,24 @@ define(['sinon', 'underscore'], function(sinon, _) { ...@@ -69,6 +71,24 @@ define(['sinon', 'underscore'], function(sinon, _) {
}; };
/** /**
* Expect that a JSON request be made with the given URL and parameters.
* @param requests The collected requests
* @param expectedUrl The expected URL excluding the parameters
* @param expectedParameters An object representing the URL parameters
* @param requestIndex An optional index for the request (by default, the last request is used)
*/
expectJsonRequestURL = function(requests, expectedUrl, expectedParameters, requestIndex) {
var request, parameters;
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
request = requests[requestIndex];
parameters = new URI(request.url).query(true);
delete parameters._; // Ignore the cache-busting argument
expect(parameters).toEqual(expectedParameters);
};
/**
* Intended for use with POST requests using application/x-www-form-urlencoded. * Intended for use with POST requests using application/x-www-form-urlencoded.
*/ */
expectPostRequest = function(requests, url, body, requestIndex) { expectPostRequest = function(requests, url, body, requestIndex) {
...@@ -136,6 +156,7 @@ define(['sinon', 'underscore'], function(sinon, _) { ...@@ -136,6 +156,7 @@ define(['sinon', 'underscore'], function(sinon, _) {
'requests': fakeRequests, 'requests': fakeRequests,
'expectRequest': expectRequest, 'expectRequest': expectRequest,
'expectJsonRequest': expectJsonRequest, 'expectJsonRequest': expectJsonRequest,
'expectJsonRequestURL': expectJsonRequestURL,
'expectPostRequest': expectPostRequest, 'expectPostRequest': expectPostRequest,
'respondWithJson': respondWithJson, 'respondWithJson': respondWithJson,
'respondWithError': respondWithError, 'respondWithError': respondWithError,
......
...@@ -45,6 +45,42 @@ class TeamsPage(CoursePage): ...@@ -45,6 +45,42 @@ class TeamsPage(CoursePage):
""" View the Browse tab of the Teams page. """ """ View the Browse tab of the Teams page. """
self.q(css=BROWSE_BUTTON_CSS).click() self.q(css=BROWSE_BUTTON_CSS).click()
def verify_team_count_in_first_topic(self, expected_count):
"""
Verify that the team count on the first topic card in the topic list is correct
(browse topics page).
"""
self.wait_for(
lambda: self.q(css='.team-count')[0].text == "0 Teams" if expected_count == 0 else "1 Team",
description="Team count text on topic is wrong"
)
def verify_topic_team_count(self, expected_count):
""" Verify the number of teams listed on the topic page (browse teams within topic). """
self.wait_for(
lambda: len(self.q(css='.team-card')) == expected_count,
description="Expected number of teams is wrong"
)
def verify_my_team_count(self, expected_count):
""" Verify the number of teams on 'My Team'. """
# Click to "My Team" and verify that it contains the expected number of teams.
self.q(css=MY_TEAMS_BUTTON_CSS).click()
self.wait_for(
lambda: len(self.q(css='.team-card')) == expected_count,
description="Expected number of teams is wrong"
)
def click_all_topics(self):
""" Click on the "All Topics" breadcrumb """
self.q(css='a.nav-item').filter(text='All Topics')[0].click()
def click_specific_topic(self, topic):
""" Click on the breadcrumb for a specific topic """
self.q(css='a.nav-item').filter(text=topic)[0].click()
class MyTeamsPage(CoursePage, PaginatedUIMixin): class MyTeamsPage(CoursePage, PaginatedUIMixin):
""" """
...@@ -284,10 +320,14 @@ class TeamPage(CoursePage, PaginatedUIMixin): ...@@ -284,10 +320,14 @@ class TeamPage(CoursePage, PaginatedUIMixin):
"""Verifies that team leave link is present""" """Verifies that team leave link is present"""
return self.q(css='.leave-team-link').present return self.q(css='.leave-team-link').present
def click_leave_team_link(self): def click_leave_team_link(self, remaining_members=0):
""" Click on Leave Team link""" """ Click on Leave Team link"""
self.q(css='.leave-team-link').first.click() self.q(css='.leave-team-link').first.click()
self.wait_for_ajax() self.wait_for(
lambda: self.join_team_button_present,
description="Join Team button did not become present"
)
self.wait_for_capacity_text(remaining_members)
@property @property
def team_members(self): def team_members(self):
...@@ -303,10 +343,29 @@ class TeamPage(CoursePage, PaginatedUIMixin): ...@@ -303,10 +343,29 @@ class TeamPage(CoursePage, PaginatedUIMixin):
"""Returns the username of team member""" """Returns the username of team member"""
return self.q(css='.page-content-secondary .tooltip-custom').text[0] return self.q(css='.page-content-secondary .tooltip-custom').text[0]
def click_join_team_button(self): def click_join_team_button(self, total_members=1):
""" Click on Join Team button""" """ Click on Join Team button"""
self.q(css='.join-team .action-primary').first.click() self.q(css='.join-team .action-primary').first.click()
self.wait_for_ajax() self.wait_for(
lambda: not self.join_team_button_present,
description="Join Team button did not go away"
)
self.wait_for_capacity_text(total_members)
def wait_for_capacity_text(self, num_members, max_size=10):
""" Wait for the team capacity text to be correct. """
self.wait_for(
lambda: self.team_capacity_text == self.format_capacity_text(num_members, max_size),
description="Team capacity text is not correct"
)
def format_capacity_text(self, num_members, max_size):
""" Helper method to format the expected team capacity text. """
return '{num_members} / {max_size} {members_text}'.format(
num_members=num_members,
max_size=max_size,
members_text='Member' if num_members == max_size else 'Members'
)
@property @property
def join_team_message(self): def join_team_message(self):
...@@ -317,7 +376,6 @@ class TeamPage(CoursePage, PaginatedUIMixin): ...@@ -317,7 +376,6 @@ class TeamPage(CoursePage, PaginatedUIMixin):
@property @property
def join_team_button_present(self): def join_team_button_present(self):
""" Returns True if Join Team button is present else False """ """ Returns True if Join Team button is present else False """
self.wait_for_ajax()
return self.q(css='.join-team .action-primary').present return self.q(css='.join-team .action-primary').present
@property @property
......
...@@ -113,6 +113,12 @@ class TeamsTabBase(UniqueCourseTest): ...@@ -113,6 +113,12 @@ class TeamsTabBase(UniqueCourseTest):
] ]
map(assert_team_equal, expected_teams, team_card_names, team_card_descriptions) map(assert_team_equal, expected_teams, team_card_names, team_card_descriptions)
def verify_my_team_count(self, expected_number_of_teams):
""" Verify the number of teams shown on "My Team". """
# We are doing these operations on this top-level page object to avoid reloading the page.
self.teams_page.verify_my_team_count(expected_number_of_teams)
@ddt.ddt @ddt.ddt
@attr('shard_5') @attr('shard_5')
...@@ -248,7 +254,7 @@ class MyTeamsTest(TeamsTabBase): ...@@ -248,7 +254,7 @@ class MyTeamsTest(TeamsTabBase):
self.assertEqual(len(self.my_teams_page.team_cards), 0, msg='Expected to see no team cards') self.assertEqual(len(self.my_teams_page.team_cards), 0, msg='Expected to see no team cards')
self.assertEqual( self.assertEqual(
self.my_teams_page.q(css='.page-content-main').text, self.my_teams_page.q(css='.page-content-main').text,
[u'You are not currently a member of any teams.'] [u'You are not currently a member of any team.']
) )
def test_member_of_a_team(self): def test_member_of_a_team(self):
...@@ -530,30 +536,6 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase): ...@@ -530,30 +536,6 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
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(1, teams, 'Showing 1-10 out of 20 total', True)
def test_teams_membership(self):
"""
Scenario: Team cards correctly reflect membership of the team.
Given I am enrolled in a course with a team configuration and a topic
containing one team
And I add myself to the team
When I visit the Teams page for that topic
Then I should see the correct page header
And I should see the team for that topic
And I should see that the team card shows my membership
"""
teams = self.create_teams(self.topic, 1)
self.browse_teams_page.visit()
self.verify_page_header()
self.verify_teams(self.browse_teams_page, teams)
self.create_membership(self.user_info['username'], teams[0]['id'])
self.browser.refresh()
self.browse_teams_page.wait_for_ajax()
## TODO: fix this!
# self.assertEqual(
# self.browse_teams_page.team_cards[0].find_element_by_css_selector('.member-count').text,
# '1 / 10 Members'
# )
def test_navigation_links(self): def test_navigation_links(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.
...@@ -715,6 +697,9 @@ class CreateTeamTest(TeamsTabBase): ...@@ -715,6 +697,9 @@ class CreateTeamTest(TeamsTabBase):
And I click Create button And I click Create button
Then I should see the page for my team Then I should see the page for my team
And I should see the message that says "You are member of this team" And I should see the message that says "You are member of this team"
And the new team should be added to the list of teams within the topic
And the number of teams should be updated on the topic card
And if I switch to "My Team", the newly created team is displayed
""" """
self.verify_and_navigate_to_create_team_page() self.verify_and_navigate_to_create_team_page()
...@@ -728,6 +713,16 @@ class CreateTeamTest(TeamsTabBase): ...@@ -728,6 +713,16 @@ class CreateTeamTest(TeamsTabBase):
self.assertEqual(team_page.team_description, 'The Avengers are a fictional team of superheroes.') self.assertEqual(team_page.team_description, 'The Avengers are a fictional team of superheroes.')
self.assertEqual(team_page.team_user_membership_text, 'You are a member of this team.') self.assertEqual(team_page.team_user_membership_text, 'You are a member of this team.')
# Verify the new team was added to the topic list
self.teams_page.click_specific_topic("Example Topic")
self.teams_page.verify_topic_team_count(1)
self.teams_page.click_all_topics()
self.teams_page.verify_team_count_in_first_topic(1)
# Verify that if one switches to "My Team" without reloading the page, the newly created team is shown.
self.verify_my_team_count(1)
def test_user_can_cancel_the_team_creation(self): def test_user_can_cancel_the_team_creation(self):
""" """
Scenario: The user should be able to cancel the creation of new team. Scenario: The user should be able to cancel the creation of new team.
...@@ -736,6 +731,7 @@ class CreateTeamTest(TeamsTabBase): ...@@ -736,6 +731,7 @@ class CreateTeamTest(TeamsTabBase):
Then I should see the Create Team header and form Then I should see the Create Team header and form
When I click Cancel button When I click Cancel button
Then I should see teams list page without any new team. Then I should see teams list page without any new team.
And if I switch to "My Team", it shows no teams
""" """
self.assertEqual(self.browse_teams_page.get_pagination_header_text(), 'Showing 0 out of 0 total') self.assertEqual(self.browse_teams_page.get_pagination_header_text(), 'Showing 0 out of 0 total')
...@@ -745,6 +741,11 @@ class CreateTeamTest(TeamsTabBase): ...@@ -745,6 +741,11 @@ class CreateTeamTest(TeamsTabBase):
self.assertTrue(self.browse_teams_page.is_browser_on_page()) self.assertTrue(self.browse_teams_page.is_browser_on_page())
self.assertEqual(self.browse_teams_page.get_pagination_header_text(), 'Showing 0 out of 0 total') self.assertEqual(self.browse_teams_page.get_pagination_header_text(), 'Showing 0 out of 0 total')
self.teams_page.click_all_topics()
self.teams_page.verify_team_count_in_first_topic(0)
self.verify_my_team_count(0)
@attr('shard_5') @attr('shard_5')
@ddt.ddt @ddt.ddt
...@@ -882,11 +883,7 @@ class TeamPageTest(TeamsTabBase): ...@@ -882,11 +883,7 @@ class TeamPageTest(TeamsTabBase):
""" """
self.assertEqual( self.assertEqual(
self.team_page.team_capacity_text, self.team_page.team_capacity_text,
'{num_members} / {max_size} {members_text}'.format( self.team_page.format_capacity_text(num_members, max_size)
num_members=num_members,
max_size=max_size,
members_text='Member' if num_members == max_size else 'Members'
)
) )
self.assertEqual(self.team_page.team_location, 'Afghanistan') self.assertEqual(self.team_page.team_location, 'Afghanistan')
self.assertEqual(self.team_page.team_language, 'Afar') self.assertEqual(self.team_page.team_language, 'Afar')
...@@ -975,6 +972,7 @@ class TeamPageTest(TeamsTabBase): ...@@ -975,6 +972,7 @@ class TeamPageTest(TeamsTabBase):
Then there should be no Join Team button and no message Then there should be no Join Team button and no message
And I should see the updated information under Team Details And I should see the updated information under Team Details
And I should see New Post button And I should see New Post button
And if I switch to "My Team", the team I have joined is displayed
""" """
self._set_team_configuration_and_membership(create_membership=False) self._set_team_configuration_and_membership(create_membership=False)
self.team_page.visit() self.team_page.visit()
...@@ -984,6 +982,10 @@ class TeamPageTest(TeamsTabBase): ...@@ -984,6 +982,10 @@ class TeamPageTest(TeamsTabBase):
self.assertFalse(self.team_page.join_team_message_present) self.assertFalse(self.team_page.join_team_message_present)
self.assert_team_details(num_members=1, is_member=True) self.assert_team_details(num_members=1, is_member=True)
# Verify that if one switches to "My Team" without reloading the page, the newly created team is shown.
self.teams_page.click_all_topics()
self.verify_my_team_count(1)
def test_already_member_message(self): def test_already_member_message(self):
""" """
Scenario: User should see `You are already in a team` if user is a Scenario: User should see `You are already in a team` if user is a
...@@ -1037,6 +1039,7 @@ class TeamPageTest(TeamsTabBase): ...@@ -1037,6 +1039,7 @@ class TeamPageTest(TeamsTabBase):
Then user should be removed from team Then user should be removed from team
And I should see Join Team button And I should see Join Team button
And I should not see New Post button And I should not see New Post button
And if I switch to "My Team", the team I have left is not displayed
""" """
self._set_team_configuration_and_membership() self._set_team_configuration_and_membership()
self.team_page.visit() self.team_page.visit()
...@@ -1045,3 +1048,7 @@ class TeamPageTest(TeamsTabBase): ...@@ -1045,3 +1048,7 @@ class TeamPageTest(TeamsTabBase):
self.team_page.click_leave_team_link() self.team_page.click_leave_team_link()
self.assert_team_details(num_members=0, is_member=False) self.assert_team_details(num_members=0, is_member=False)
self.assertTrue(self.team_page.join_team_button_present) self.assertTrue(self.team_page.join_team_button_present)
# Verify that if one switches to "My Team" without reloading the page, the old team no longer shows.
self.teams_page.click_all_topics()
self.verify_my_team_count(0)
;(function (define) {
'use strict';
define(['common/js/components/collections/paging_collection'],
function(PagingCollection) {
var BaseCollection = PagingCollection.extend({
initialize: function(options) {
PagingCollection.prototype.initialize.call(this);
this.course_id = options.course_id;
this.perPage = options.per_page;
this.teamEvents = options.teamEvents;
this.teamEvents.bind('teams:update', this.onUpdate, this);
this.isStale = false;
},
onUpdate: function(event) {
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.fetch()
.done(function() {
self.isStale = false;
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise();
}
});
return BaseCollection;
});
}).call(this, define || RequireJS.define);
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['common/js/components/collections/paging_collection', 'teams/js/models/team', 'gettext'], define(['teams/js/collections/base', 'teams/js/models/team', 'gettext'],
function(PagingCollection, TeamModel, gettext) { function(BaseCollection, TeamModel, gettext) {
var TeamCollection = PagingCollection.extend({ var TeamCollection = BaseCollection.extend({
initialize: function(teams, options) { initialize: function(teams, options) {
PagingCollection.prototype.initialize.call(this); var self = this;
BaseCollection.prototype.initialize.call(this, options);
this.course_id = options.course_id; this.server_api = _.extend(
this.server_api['topic_id'] = this.topic_id = options.topic_id; {
this.server_api['expand'] = 'user'; topic_id: this.topic_id = options.topic_id,
this.perPage = options.per_page; expand: 'user',
this.server_api['course_id'] = function () { return encodeURIComponent(this.course_id); }; course_id: function () { return encodeURIComponent(self.course_id); },
this.server_api['order_by'] = function () { return 'name'; }; // TODO surface sort order in UI order_by: function () { return 'name'; } // TODO surface sort order in UI
delete this.server_api['sort_order']; // Sort order is not specified for the Team API },
BaseCollection.prototype.server_api
);
delete this.server_api.sort_order; // Sort order is not specified for the Team API
this.registerSortableField('name', gettext('name')); this.registerSortableField('name', gettext('name'));
this.registerSortableField('open_slots', gettext('open_slots')); this.registerSortableField('open_slots', gettext('open_slots'));
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['common/js/components/collections/paging_collection', 'teams/js/models/team_membership'], define(['teams/js/collections/base', 'teams/js/models/team_membership'],
function(PagingCollection, TeamMembershipModel) { function(BaseCollection, TeamMembershipModel) {
var TeamMembershipCollection = PagingCollection.extend({ var TeamMembershipCollection = BaseCollection.extend({
initialize: function(team_memberships, options) { initialize: function(team_memberships, options) {
PagingCollection.prototype.initialize.call(this); var self = this;
BaseCollection.prototype.initialize.call(this, options);
this.course_id = options.course_id; this.perPage = options.per_page || 10;
this.username = options.username; this.username = options.username;
this.privileged = options.privileged; this.privileged = options.privileged;
this.perPage = options.per_page || 10;
this.server_api['expand'] = 'team'; this.server_api = _.extend(
this.server_api['course_id'] = function () { return encodeURIComponent(options.course_id); }; {
this.server_api['username'] = this.username; expand: 'team',
username: this.username,
course_id: function () { return encodeURIComponent(self.course_id); }
},
BaseCollection.prototype.server_api
);
delete this.server_api['sort_order']; // Sort order is not specified for the TeamMembership API delete this.server_api['sort_order']; // Sort order is not specified for the TeamMembership API
delete this.server_api['order_by']; // Order by is not specified for the TeamMembership API delete this.server_api['order_by']; // Order by is not specified for the TeamMembership API
}, },
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['common/js/components/collections/paging_collection', 'teams/js/models/topic', 'gettext'], define(['teams/js/collections/base', 'teams/js/models/topic', 'gettext'],
function(PagingCollection, TopicModel, gettext) { function(BaseCollection, TopicModel, gettext) {
var TopicCollection = PagingCollection.extend({ var TopicCollection = BaseCollection.extend({
initialize: function(topics, options) { initialize: function(topics, options) {
PagingCollection.prototype.initialize.call(this); var self = this;
BaseCollection.prototype.initialize.call(this, options);
this.course_id = options.course_id;
this.perPage = topics.results.length; this.perPage = topics.results.length;
this.server_api['course_id'] = function () { return encodeURIComponent(this.course_id); };
this.server_api['order_by'] = function () { return this.sortField; }; this.server_api = _.extend(
{
course_id: function () { return encodeURIComponent(self.course_id); },
order_by: function () { return this.sortField; }
},
BaseCollection.prototype.server_api
);
delete this.server_api['sort_order']; // Sort order is not specified for the Team API delete this.server_api['sort_order']; // Sort order is not specified for the Team API
this.registerSortableField('name', gettext('name')); this.registerSortableField('name', gettext('name'));
...@@ -17,6 +24,10 @@ ...@@ -17,6 +24,10 @@
this.registerSortableField('team_count', gettext('team count')); this.registerSortableField('team_count', gettext('team count'));
}, },
onUpdate: function(event) {
this.isStale = event.action === 'create';
},
model: TopicModel model: TopicModel
}); });
return TopicCollection; return TopicCollection;
......
define(['URI', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/collections/topic'], define(['backbone', 'URI', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/collections/topic'],
function (URI, _, AjaxHelpers, TopicCollection) { function (Backbone, URI, _, AjaxHelpers, TopicCollection) {
'use strict'; 'use strict';
describe('TopicCollection', function () { describe('TopicCollection', function () {
var topicCollection; var topicCollection;
...@@ -39,7 +39,11 @@ define(['URI', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/co ...@@ -39,7 +39,11 @@ define(['URI', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/co
], ],
"sort_order": "name" "sort_order": "name"
}, },
{course_id: 'my/course/id', parse: true}); {
teamEvents:_.clone(Backbone.Events),
course_id: 'my/course/id',
parse: true
});
}); });
var testRequestParam = function (self, param, value) { var testRequestParam = function (self, param, value) {
......
...@@ -3,13 +3,13 @@ define([ ...@@ -3,13 +3,13 @@ define([
'underscore', 'underscore',
'backbone', 'backbone',
'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/ajax_helpers',
'teams/js/views/edit_team' 'teams/js/views/edit_team',
], function ($, _, Backbone, AjaxHelpers, TeamEditView) { 'teams/js/spec_helpers/team_spec_helpers'
], function ($, _, Backbone, AjaxHelpers, TeamEditView, TeamSpecHelpers) {
'use strict'; 'use strict';
describe('EditTeam', function () { describe('EditTeam', function () {
var teamEditView, var teamsUrl = '/api/team/v0/teams/',
teamsUrl = '/api/team/v0/teams/',
teamsData = { teamsData = {
id: null, id: null,
name: "TeamName", name: "TeamName",
...@@ -22,7 +22,7 @@ define([ ...@@ -22,7 +22,7 @@ define([
language: "a", language: "a",
membership: [] membership: []
}, },
verifyValidation = function (requests, fieldsData) { verifyValidation = function (requests, teamEditView, fieldsData) {
_.each(fieldsData, function (fieldData) { _.each(fieldsData, function (fieldData) {
teamEditView.$(fieldData[0]).val(fieldData[1]); teamEditView.$(fieldData[0]).val(fieldData[1]);
}); });
...@@ -45,24 +45,11 @@ define([ ...@@ -45,24 +45,11 @@ define([
}); });
expect(requests.length).toBe(0); expect(requests.length).toBe(0);
},
expectContent = function (selector, text) {
expect(teamEditView.$(selector).text().trim()).toBe(text);
},
verifyDropdownData = function (selector, expectedItems) {
var options = teamEditView.$(selector)[0].options;
var renderedItems = $.map(options, function( elem ) {
return [[elem.value, elem.text]];
});
for (var i = 0; i < expectedItems.length; i++) {
expect(renderedItems).toContain(expectedItems[i]);
}
}; };
beforeEach(function () { var createTeamEditView = function() {
setFixtures('<div class="teams-content"></div>'); return new TeamEditView({
spyOn(Backbone.history, 'navigate'); teamEvents: TeamSpecHelpers.teamEvents,
teamEditView = new TeamEditView({
el: $('.teams-content'), el: $('.teams-content'),
teamParams: { teamParams: {
teamsUrl: teamsUrl, teamsUrl: teamsUrl,
...@@ -73,6 +60,11 @@ define([ ...@@ -73,6 +60,11 @@ define([
countries: [['c', 'ccc'], ['d', 'ddd']] countries: [['c', 'ccc'], ['d', 'ddd']]
} }
}).render(); }).render();
};
beforeEach(function () {
setFixtures('<div class="teams-content"></div>');
spyOn(Backbone.history, 'navigate');
}); });
it('can render itself correctly', function () { it('can render itself correctly', function () {
...@@ -82,7 +74,8 @@ define([ ...@@ -82,7 +74,8 @@ define([
'.u-field-optional_description', '.u-field-optional_description',
'.u-field-language', '.u-field-language',
'.u-field-country' '.u-field-country'
]; ],
teamEditView = createTeamEditView();
_.each(fieldClasses, function (fieldClass) { _.each(fieldClasses, function (fieldClass) {
expect(teamEditView.$el.find(fieldClass).length).toBe(1); expect(teamEditView.$el.find(fieldClass).length).toBe(1);
...@@ -93,7 +86,8 @@ define([ ...@@ -93,7 +86,8 @@ define([
}); });
it('can create a team', function () { it('can create a team', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this),
teamEditView = createTeamEditView();
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);
...@@ -109,46 +103,49 @@ define([ ...@@ -109,46 +103,49 @@ define([
}); });
it('shows validation error message when field is empty', function () { it('shows validation error message when field is empty', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this),
verifyValidation(requests, [ teamEditView = createTeamEditView();
verifyValidation(requests, teamEditView, [
['.u-field-name input', 'Name', 'success'], ['.u-field-name input', 'Name', 'success'],
['.u-field-textarea textarea', '', 'error'] ['.u-field-textarea textarea', '', 'error']
]); ]);
teamEditView.render(); teamEditView.render();
verifyValidation(requests, [ verifyValidation(requests, teamEditView, [
['.u-field-name input', '', 'error'], ['.u-field-name input', '', 'error'],
['.u-field-textarea textarea', 'description', 'success'] ['.u-field-textarea textarea', 'description', 'success']
]); ]);
teamEditView.render(); teamEditView.render();
verifyValidation(requests, [ verifyValidation(requests, teamEditView, [
['.u-field-name input', '', 'error'], ['.u-field-name input', '', 'error'],
['.u-field-textarea textarea', '', 'error'] ['.u-field-textarea textarea', '', 'error']
]); ]);
}); });
it('shows validation error message when field value length exceeded the limit', function () { it('shows validation error message when field value length exceeded the limit', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this),
var teamName = new Array(500 + 1).join( '$' ); teamEditView = createTeamEditView(),
var teamDescription = new Array(500 + 1).join( '$' ); teamName = new Array(500 + 1).join( '$'),
teamDescription = new Array(500 + 1).join( '$' );
verifyValidation(requests, [ verifyValidation(requests, teamEditView, [
['.u-field-name input', teamName, 'error'], ['.u-field-name input', teamName, 'error'],
['.u-field-textarea textarea', 'description', 'success'] ['.u-field-textarea textarea', 'description', 'success']
]); ]);
teamEditView.render(); teamEditView.render();
verifyValidation(requests, [ verifyValidation(requests, teamEditView, [
['.u-field-name input', 'name', 'success'], ['.u-field-name input', 'name', 'success'],
['.u-field-textarea textarea', teamDescription, 'error'] ['.u-field-textarea textarea', teamDescription, 'error']
]); ]);
teamEditView.render(); teamEditView.render();
verifyValidation(requests, [ verifyValidation(requests, teamEditView, [
['.u-field-name input', teamName, 'error'], ['.u-field-name input', teamName, 'error'],
['.u-field-textarea textarea', teamDescription, 'error'] ['.u-field-textarea textarea', teamDescription, 'error']
]); ]);
}); });
it("shows an error message for HTTP 500", function () { it("shows an error message for HTTP 500", function () {
var requests = AjaxHelpers.requests(this); var teamEditView = createTeamEditView(),
requests = AjaxHelpers.requests(this);
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);
...@@ -182,6 +179,7 @@ define([ ...@@ -182,6 +179,7 @@ define([
}); });
it("changes route on cancel click", function () { it("changes route on cancel click", function () {
var teamEditView = createTeamEditView();
teamEditView.$('.create-team.form-actions .action-cancel').click(); teamEditView.$('.create-team.form-actions .action-cancel').click();
expect(Backbone.history.navigate.calls[0].args).toContain('topics/awesomeness'); expect(Backbone.history.navigate.calls[0].args).toContain('topics/awesomeness');
}); });
......
...@@ -3,8 +3,9 @@ define([ ...@@ -3,8 +3,9 @@ define([
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership', 'teams/js/collections/team_membership',
'teams/js/views/my_teams', 'teams/js/views/my_teams',
'teams/js/spec_helpers/team_spec_helpers' 'teams/js/spec_helpers/team_spec_helpers',
], function (Backbone, TeamCollection, TeamMembershipCollection, MyTeamsView, TeamSpecHelpers) { 'common/js/spec_helpers/ajax_helpers'
], function (Backbone, TeamCollection, TeamMembershipCollection, MyTeamsView, TeamSpecHelpers, AjaxHelpers) {
'use strict'; 'use strict';
describe('My Teams View', function () { describe('My Teams View', function () {
beforeEach(function () { beforeEach(function () {
...@@ -28,26 +29,51 @@ define([ ...@@ -28,26 +29,51 @@ define([
it('can render itself', function () { it('can render itself', function () {
var teamMembershipsData = TeamSpecHelpers.createMockTeamMembershipsData(1, 5), var teamMembershipsData = TeamSpecHelpers.createMockTeamMembershipsData(1, 5),
teamMemberships = TeamSpecHelpers.createMockTeamMemberships(teamMembershipsData), teamMemberships = TeamSpecHelpers.createMockTeamMemberships(teamMembershipsData),
teamsView = createMyTeamsView({ myTeamsView = createMyTeamsView({
teams: teamMemberships, teams: teamMemberships,
teamMemberships: teamMemberships teamMemberships: teamMemberships
}); });
TeamSpecHelpers.verifyCards(teamsView, teamMembershipsData); TeamSpecHelpers.verifyCards(myTeamsView, teamMembershipsData);
// Verify that there is no header or footer // Verify that there is no header or footer
expect(teamsView.$('.teams-paging-header').text().trim()).toBe(''); expect(myTeamsView.$('.teams-paging-header').text().trim()).toBe('');
expect(teamsView.$('.teams-paging-footer').text().trim()).toBe(''); expect(myTeamsView.$('.teams-paging-footer').text().trim()).toBe('');
}); });
it('shows a message when the user is not a member of any teams', function () { it('shows a message when the user is not a member of any teams', function () {
var teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]), var teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createMyTeamsView({ myTeamsView = createMyTeamsView({
teams: teamMemberships, teams: teamMemberships,
teamMemberships: teamMemberships teamMemberships: teamMemberships
}); });
TeamSpecHelpers.verifyCards(teamsView, []); TeamSpecHelpers.verifyCards(myTeamsView, []);
expect(teamsView.$el.text().trim()).toBe('You are not currently a member of any teams.'); expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.');
});
it('refreshes a stale membership collection when rendering', function() {
var requests = AjaxHelpers.requests(this),
teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]),
myTeamsView = createMyTeamsView({
teams: teamMemberships,
teamMemberships: teamMemberships
});
TeamSpecHelpers.verifyCards(myTeamsView, []);
expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.');
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
myTeamsView.render();
AjaxHelpers.expectJsonRequestURL(
requests,
'foo',
{
expand : 'team',
username : 'testUser',
course_id : 'my/course/id',
page : '1',
page_size : '10'
}
);
AjaxHelpers.respondWithJson(requests, {});
}); });
}); });
}); });
define([ define([
'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/models/team', 'backbone', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/models/team',
'teams/js/spec_helpers/team_spec_helpers', 'teams/js/views/team_join', 'teams/js/spec_helpers/team_spec_helpers'
'teams/js/views/team_join' ], function (Backbone, _, AjaxHelpers, TeamModel, TeamJoinView, TeamSpecHelpers) {
], function (_, AjaxHelpers, TeamModel, TeamSpecHelpers, TeamJoinView) {
'use strict'; 'use strict';
describe('TeamJoinView', function () { describe('TeamJoinView', function () {
var createTeamsUrl, var createTeamsUrl,
...@@ -63,6 +62,7 @@ define([ ...@@ -63,6 +62,7 @@ define([
var teamJoinView = new TeamJoinView( var teamJoinView = new TeamJoinView(
{ {
courseID: TeamSpecHelpers.testCourseID, courseID: TeamSpecHelpers.testCourseID,
teamEvents: TeamSpecHelpers.teamEvents,
model: model, model: model,
teamsUrl: createTeamsUrl(teamId), teamsUrl: createTeamsUrl(teamId),
maxTeamSize: maxTeamSize, maxTeamSize: maxTeamSize,
......
...@@ -5,7 +5,9 @@ define([ ...@@ -5,7 +5,9 @@ define([
], function (_, AjaxHelpers, TeamModel, TeamProfileView, TeamSpecHelpers, DiscussionSpecHelper) { ], function (_, AjaxHelpers, TeamModel, TeamProfileView, TeamSpecHelpers, DiscussionSpecHelper) {
'use strict'; 'use strict';
describe('TeamProfileView', function () { describe('TeamProfileView', function () {
var profileView, createTeamProfileView, createTeamModelData, teamModel, var profileView, createTeamProfileView, createTeamModelData, clickLeaveTeam,
teamModel,
leaveTeamLinkSelector = '.leave-team-link',
DEFAULT_MEMBERSHIP = [ DEFAULT_MEMBERSHIP = [
{ {
'user': { 'user': {
...@@ -38,6 +40,7 @@ define([ ...@@ -38,6 +40,7 @@ define([
createTeamProfileView = function(requests, options) { createTeamProfileView = function(requests, options) {
teamModel = new TeamModel(createTeamModelData(options), { parse: true }); teamModel = new TeamModel(createTeamModelData(options), { parse: true });
profileView = new TeamProfileView({ profileView = new TeamProfileView({
teamEvents: TeamSpecHelpers.teamEvents,
courseID: TeamSpecHelpers.testCourseID, courseID: TeamSpecHelpers.testCourseID,
model: teamModel, model: teamModel,
maxTeamSize: options.maxTeamSize || 3, maxTeamSize: options.maxTeamSize || 3,
...@@ -71,6 +74,21 @@ define([ ...@@ -71,6 +74,21 @@ define([
return profileView; return profileView;
}; };
clickLeaveTeam = function(requests, view) {
expect(view.$(leaveTeamLinkSelector).length).toBe(1);
// click on Leave Team link under Team Details
view.$(leaveTeamLinkSelector).click();
// expect a request to DELETE the team membership
AjaxHelpers.expectJsonRequest(requests, 'DELETE', 'api/team/v0/team_membership/test-team,bilbo');
AjaxHelpers.respondWithNoContent(requests);
// expect a request to refetch the user's team memberships
AjaxHelpers.expectJsonRequest(requests, 'GET', '/api/team/v0/teams/test-team');
AjaxHelpers.respondWithJson(requests, createTeamModelData({country: 'US', language: 'en'}));
};
describe('DiscussionsView', function() { describe('DiscussionsView', function() {
it('can render itself', function () { it('can render itself', function () {
var requests = AjaxHelpers.requests(this), var requests = AjaxHelpers.requests(this),
...@@ -84,6 +102,7 @@ define([ ...@@ -84,6 +102,7 @@ define([
expect(view.$('.new-post-btn').length).toEqual(0); expect(view.$('.new-post-btn').length).toEqual(0);
teamModel.set('membership', DEFAULT_MEMBERSHIP); // This should re-render the view. teamModel.set('membership', DEFAULT_MEMBERSHIP); // This should re-render the view.
view.render();
expect(view.$('.new-post-btn').length).toEqual(1); expect(view.$('.new-post-btn').length).toEqual(1);
}); });
...@@ -92,7 +111,7 @@ define([ ...@@ -92,7 +111,7 @@ define([
view = createTeamProfileView(requests, {membership: DEFAULT_MEMBERSHIP}); view = createTeamProfileView(requests, {membership: DEFAULT_MEMBERSHIP});
expect(view.$('.new-post-btn').length).toEqual(1); expect(view.$('.new-post-btn').length).toEqual(1);
teamModel.set('membership', []); clickLeaveTeam(requests, view);
expect(view.$('.new-post-btn').length).toEqual(0); expect(view.$('.new-post-btn').length).toEqual(0);
}); });
}); });
...@@ -119,7 +138,10 @@ define([ ...@@ -119,7 +138,10 @@ define([
assertTeamDetails(view, 0, false); assertTeamDetails(view, 0, false);
expect(view.$('.team-user-membership-status').length).toBe(0); expect(view.$('.team-user-membership-status').length).toBe(0);
// Verify that the leave team link is not present.
expect(view.$(leaveTeamLinkSelector).length).toBe(0);
}); });
it('cannot see the country & language if empty', function() { it('cannot see the country & language if empty', function() {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var view = createTeamProfileView(requests, {}); var view = createTeamProfileView(requests, {});
...@@ -145,29 +167,21 @@ define([ ...@@ -145,29 +167,21 @@ define([
// 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/bilbo');
//Verify that the leave team link is present
expect(view.$(leaveTeamLinkSelector).text()).toContain('Leave Team');
}); });
it('can leave team successfully', function() { it('can leave team successfully', function() {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var leaveTeamLinkSelector = '.leave-team-link';
var view = createTeamProfileView( var view = createTeamProfileView(
requests, { country: 'US', language: 'en', membership: DEFAULT_MEMBERSHIP} requests, {country: 'US', language: 'en', membership: DEFAULT_MEMBERSHIP}
); );
assertTeamDetails(view, 1, true); assertTeamDetails(view, 1, true);
clickLeaveTeam(requests, view);
expect(view.$(leaveTeamLinkSelector).length).toBe(1);
// click on Leave Team link under Team Details
view.$(leaveTeamLinkSelector).click();
// response to DELETE
AjaxHelpers.respondWithNoContent(requests);
// response to model fetch request
AjaxHelpers.respondWithJson(requests, createTeamModelData({country: 'US', language: 'en'}));
assertTeamDetails(view, 0, false); assertTeamDetails(view, 0, false);
}); });
it('shows correct error messages', function () { it('shows correct error messages', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
......
...@@ -5,11 +5,8 @@ define(['jquery', ...@@ -5,11 +5,8 @@ define(['jquery',
function ($, _, TopicCardView, Topic) { function ($, _, TopicCardView, Topic) {
describe('topic card view', function () { describe('topic card view', function () {
var view; var createTopicCardView = function() {
return new TopicCardView({
beforeEach(function () {
spyOn(TopicCardView.prototype, 'action');
view = new TopicCardView({
model: new Topic({ model: new Topic({
'id': 'renewables', 'id': 'renewables',
'name': 'Renewable Energy', 'name': 'Renewable Energy',
...@@ -17,9 +14,14 @@ define(['jquery', ...@@ -17,9 +14,14 @@ define(['jquery',
'team_count': 34 'team_count': 34
}) })
}); });
};
beforeEach(function () {
spyOn(TopicCardView.prototype, 'action');
}); });
it('can render itself', function () { it('can render itself', function () {
var view = createTopicCardView();
expect(view.$el).toHaveClass('square-card'); expect(view.$el).toHaveClass('square-card');
expect(view.$el.find('.card-title').text()).toContain('Renewable Energy'); expect(view.$el.find('.card-title').text()).toContain('Renewable Energy');
expect(view.$el.find('.card-description').text()).toContain('changes in <ⓡⓔⓝⓔⓦⓐⓑⓛⓔ> ʎƃɹǝuǝ'); expect(view.$el.find('.card-description').text()).toContain('changes in <ⓡⓔⓝⓔⓦⓐⓑⓛⓔ> ʎƃɹǝuǝ');
...@@ -28,6 +30,7 @@ define(['jquery', ...@@ -28,6 +30,7 @@ define(['jquery',
}); });
it('navigates when action button is clicked', function () { it('navigates when action button is clicked', function () {
var view = createTopicCardView();
view.$el.find('.action').trigger('click'); view.$el.find('.action').trigger('click');
// TODO test actual navigation once implemented // TODO test actual navigation once implemented
expect(view.action).toHaveBeenCalled(); expect(view.action).toHaveBeenCalled();
......
...@@ -3,8 +3,9 @@ define([ ...@@ -3,8 +3,9 @@ define([
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership', 'teams/js/collections/team_membership',
'teams/js/views/topic_teams', 'teams/js/views/topic_teams',
'teams/js/spec_helpers/team_spec_helpers' 'teams/js/spec_helpers/team_spec_helpers',
], function (Backbone, TeamCollection, TeamMembershipCollection, TopicTeamsView, TeamSpecHelpers) { 'common/js/spec_helpers/ajax_helpers'
], function (Backbone, TeamCollection, TeamMembershipCollection, TopicTeamsView, TeamSpecHelpers, AjaxHelpers) {
'use strict'; 'use strict';
describe('Topic Teams View', function () { describe('Topic Teams View', function () {
var createTopicTeamsView = function(options) { var createTopicTeamsView = function(options) {
...@@ -21,6 +22,24 @@ define([ ...@@ -21,6 +22,24 @@ define([
}).render(); }).render();
}; };
var verifyActions = function(teamsView, options) {
if (!options) {
options = {showActions: true};
}
var expectedTitle = 'Are you having trouble finding a team to join?',
expectedMessage = 'Try browsing all teams or searching team descriptions. If you ' +
'still can\'t find a team to join, create a new team in this topic.',
title = teamsView.$('.title').text().trim(),
message = teamsView.$('.copy').text().trim();
if (options.showActions) {
expect(title).toBe(expectedTitle);
expect(message).toBe(expectedMessage);
} else {
expect(title).not.toBe(expectedTitle);
expect(message).not.toBe(expectedMessage);
}
};
beforeEach(function () { beforeEach(function () {
setFixtures('<div class="teams-container"></div>'); setFixtures('<div class="teams-container"></div>');
}); });
...@@ -39,12 +58,7 @@ define([ ...@@ -39,12 +58,7 @@ define([
expect(footerEl).not.toHaveClass('hidden'); expect(footerEl).not.toHaveClass('hidden');
TeamSpecHelpers.verifyCards(teamsView, testTeamData); TeamSpecHelpers.verifyCards(teamsView, testTeamData);
verifyActions(teamsView);
expect(teamsView.$('.title').text()).toBe('Are you having trouble finding a team to join?');
expect(teamsView.$('.copy').text()).toBe(
"Try browsing all teams or searching team descriptions. If you " +
"still can't find a team to join, create a new team in this topic."
);
}); });
it('can browse all teams', function () { it('can browse all teams', function () {
...@@ -74,9 +88,7 @@ define([ ...@@ -74,9 +88,7 @@ define([
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 () {
var teamsView = createTopicTeamsView({}); var teamsView = createTopicTeamsView({});
expect(teamsView.$el.text()).not.toContain( verifyActions(teamsView, {showActions: false});
'Are you having trouble finding a team to join?'
);
}); });
it('shows actions for a privileged user already in a team', function () { it('shows actions for a privileged user already in a team', function () {
...@@ -85,9 +97,32 @@ define([ ...@@ -85,9 +97,32 @@ define([
{ privileged: true } { privileged: true }
), ),
teamsView = createTopicTeamsView({ teamMemberships: staffMembership }); teamsView = createTopicTeamsView({ teamMemberships: staffMembership });
expect(teamsView.$el.text()).toContain( verifyActions(teamsView);
'Are you having trouble finding a team to join?' });
/*
// TODO: make this ready for prime time
it('refreshes when the team membership changes', function() {
var requests = AjaxHelpers.requests(this),
teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createTopicTeamsView({ teamMemberships: teamMemberships });
verifyActions(teamsView, {showActions: true});
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
teamsView.render();
AjaxHelpers.expectJsonRequestURL(
requests,
'foo',
{
expand : 'team',
username : 'testUser',
course_id : 'my/course/id',
page : '1',
page_size : '10'
}
); );
AjaxHelpers.respondWithJson(requests, {});
verifyActions(teamsView, {showActions: false});
}); });
*/
}); });
}); });
define([ define([
'teams/js/collections/topic', 'teams/js/views/topics' 'backbone', 'teams/js/collections/topic', 'teams/js/views/topics',
], function (TopicCollection, TopicsView) { 'teams/js/spec_helpers/team_spec_helpers'
], function (Backbone, TopicCollection, TopicsView, TeamSpecHelpers) {
'use strict'; 'use strict';
describe('TopicsView', function () { describe('TopicsView', function () {
var initialTopics, topicCollection, topicsView, var initialTopics, topicCollection, createTopicsView,
generateTopics = function (startIndex, stopIndex) { generateTopics = function (startIndex, stopIndex) {
return _.map(_.range(startIndex, stopIndex + 1), function (i) { return _.map(_.range(startIndex, stopIndex + 1), function (i) {
return { return {
...@@ -15,6 +16,14 @@ define([ ...@@ -15,6 +16,14 @@ define([
}); });
}; };
createTopicsView = function() {
return new TopicsView({
teamEvents: TeamSpecHelpers.teamEvents,
el: '.topics-container',
collection: topicCollection
}).render();
};
beforeEach(function () { beforeEach(function () {
setFixtures('<div class="topics-container"></div>'); setFixtures('<div class="topics-container"></div>');
initialTopics = generateTopics(1, 5); initialTopics = generateTopics(1, 5);
...@@ -26,13 +35,17 @@ define([ ...@@ -26,13 +35,17 @@ define([
"start": 0, "start": 0,
"results": initialTopics "results": initialTopics
}, },
{course_id: 'my/course/id', parse: true} {
teamEvents: TeamSpecHelpers.teamEvents,
course_id: 'my/course/id',
parse: true
}
); );
topicsView = new TopicsView({el: '.topics-container', collection: topicCollection}).render();
}); });
it('can render the first of many pages', function () { it('can render the first of many pages', function () {
var footerEl = topicsView.$('.topics-paging-footer'), var topicsView = createTopicsView(),
footerEl = topicsView.$('.topics-paging-footer'),
topicCards = topicsView.$('.topic-card'); topicCards = topicsView.$('.topic-card');
expect(topicsView.$('.topics-paging-header').text()).toMatch('Showing 1-5 out of 6 total'); expect(topicsView.$('.topics-paging-header').text()).toMatch('Showing 1-5 out of 6 total');
_.each(initialTopics, function (topic, index) { _.each(initialTopics, function (topic, index) {
......
define([ define([
'backbone',
'underscore', 'underscore',
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership', 'teams/js/collections/team_membership',
], function (_, TeamCollection, TeamMembershipCollection) { ], function (Backbone, _, TeamCollection, TeamMembershipCollection) {
'use strict'; 'use strict';
var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse, var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse,
testCourseID = 'course/1', testCourseID = 'course/1',
testUser = 'testUser', testUser = 'testUser',
testTeamDiscussionID = "12345", testTeamDiscussionID = "12345",
teamEvents = _.clone(Backbone.Events),
testCountries = [ testCountries = [
['', ''], ['', ''],
['US', 'United States'], ['US', 'United States'],
...@@ -47,6 +49,7 @@ define([ ...@@ -47,6 +49,7 @@ define([
results: teamData results: teamData
}, },
{ {
teamEvents: teamEvents,
course_id: 'my/course/id', course_id: 'my/course/id',
parse: true parse: true
} }
...@@ -79,6 +82,7 @@ define([ ...@@ -79,6 +82,7 @@ define([
results: teamMembershipData results: teamMembershipData
}, },
_.extend(_.extend({}, { _.extend(_.extend({}, {
teamEvents: teamEvents,
course_id: 'my/course/id', course_id: 'my/course/id',
parse: true, parse: true,
url: 'api/teams/team_memberships', url: 'api/teams/team_memberships',
...@@ -225,6 +229,7 @@ define([ ...@@ -225,6 +229,7 @@ define([
}; };
return { return {
teamEvents: teamEvents,
testCourseID: testCourseID, testCourseID: testCourseID,
testUser: testUser, testUser: testUser,
testCountries: testCountries, testCountries: testCountries,
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
}, },
initialize: function(options) { initialize: function(options) {
this.teamEvents = options.teamEvents;
this.courseID = options.teamParams.courseID; this.courseID = options.teamParams.courseID;
this.topicID = options.teamParams.topicID; this.topicID = options.teamParams.topicID;
this.collection = options.collection; this.collection = options.collection;
...@@ -113,6 +114,10 @@ ...@@ -113,6 +114,10 @@
this.teamModel.save(data, { wait: true }) this.teamModel.save(data, { wait: true })
.done(function(result) { .done(function(result) {
view.teamEvents.trigger('teams:update', {
action: 'create',
team: result
});
Backbone.history.navigate( Backbone.history.navigate(
'teams/' + view.topicID + '/' + view.teamModel.id, 'teams/' + view.topicID + '/' + view.teamModel.id,
{trigger: true} {trigger: true}
......
...@@ -5,10 +5,17 @@ ...@@ -5,10 +5,17 @@
function (Backbone, gettext, TeamsView) { function (Backbone, gettext, TeamsView) {
var MyTeamsView = TeamsView.extend({ var MyTeamsView = TeamsView.extend({
render: function() { render: function() {
TeamsView.prototype.render.call(this); var view = this;
if (this.collection.length === 0) { if (this.collection.isStale) {
this.$el.append('<p>' + gettext('You are not currently a member of any teams.') + '</p>'); this.$el.html('');
} }
this.collection.refresh()
.done(function() {
TeamsView.prototype.render.call(view);
if (view.collection.length === 0) {
view.$el.append('<p>' + gettext('You are not currently a member of any team.') + '</p>');
}
});
return this; return this;
}, },
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['backbone', define(['backbone',
'underscore', 'underscore',
'gettext', 'gettext',
'teams/js/views/team_utils', 'teams/js/views/team_utils',
...@@ -18,6 +18,7 @@ define(['backbone', ...@@ -18,6 +18,7 @@ define(['backbone',
}, },
initialize: function(options) { initialize: function(options) {
this.teamEvents = options.teamEvents;
this.template = _.template(teamJoinTemplate); this.template = _.template(teamJoinTemplate);
this.courseID = options.courseID; this.courseID = options.courseID;
this.maxTeamSize = options.maxTeamSize; this.maxTeamSize = options.maxTeamSize;
...@@ -28,11 +29,10 @@ define(['backbone', ...@@ -28,11 +29,10 @@ define(['backbone',
}, },
render: function() { render: function() {
var message, var view = this,
message,
showButton, showButton,
teamHasSpace; teamHasSpace;
var view = this;
this.getUserTeamInfo(this.currentUsername, view.maxTeamSize).done(function (info) { this.getUserTeamInfo(this.currentUsername, view.maxTeamSize).done(function (info) {
teamHasSpace = info.teamHasSpace; teamHasSpace = info.teamHasSpace;
...@@ -59,7 +59,13 @@ define(['backbone', ...@@ -59,7 +59,13 @@ define(['backbone',
url: view.teamMembershipsUrl, url: view.teamMembershipsUrl,
data: {'username': view.currentUsername, 'team_id': view.model.get('id')} data: {'username': view.currentUsername, 'team_id': view.model.get('id')}
}).done(function (data) { }).done(function (data) {
view.model.fetch({}); view.model.fetch()
.done(function() {
view.teamEvents.trigger('teams:update', {
action: 'join',
team: view.model
});
});
}).fail(function (data) { }).fail(function (data) {
TeamUtils.parseAndShowMessage(data, view.errorMessage); TeamUtils.parseAndShowMessage(data, view.errorMessage);
}); });
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
'click .leave-team-link': 'leaveTeam' 'click .leave-team-link': 'leaveTeam'
}, },
initialize: function (options) { initialize: function (options) {
this.listenTo(this.model, "change", this.render); this.teamEvents = options.teamEvents;
this.courseID = options.courseID; this.courseID = options.courseID;
this.maxTeamSize = options.maxTeamSize; this.maxTeamSize = options.maxTeamSize;
this.requestUsername = options.requestUsername; this.requestUsername = options.requestUsername;
...@@ -26,13 +26,13 @@ ...@@ -26,13 +26,13 @@
this.countries = TeamUtils.selectorOptionsArrayToHashWithBlank(options.countries); this.countries = TeamUtils.selectorOptionsArrayToHashWithBlank(options.countries);
this.languages = TeamUtils.selectorOptionsArrayToHashWithBlank(options.languages); this.languages = TeamUtils.selectorOptionsArrayToHashWithBlank(options.languages);
this.listenTo(this.model, "change", this.render);
}, },
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.requestUsername);
this.$el.html(_.template(teamTemplate, { this.$el.html(_.template(teamTemplate, {
courseID: this.courseID, courseID: this.courseID,
discussionTopicID: discussionTopicID, discussionTopicID: discussionTopicID,
...@@ -76,7 +76,13 @@ ...@@ -76,7 +76,13 @@
type: 'DELETE', type: 'DELETE',
url: view.teamMembershipDetailUrl.replace('team_id', view.model.get('id')) url: view.teamMembershipDetailUrl.replace('team_id', view.model.get('id'))
}).done(function (data) { }).done(function (data) {
view.model.fetch({}); view.model.fetch()
.done(function() {
view.teamEvents.trigger('teams:update', {
action: 'leave',
team: view.model
});
});
}).fail(function (data) { }).fail(function (data) {
TeamUtils.parseAndShowMessage(data, view.errorMessage); TeamUtils.parseAndShowMessage(data, view.errorMessage);
}); });
......
...@@ -81,9 +81,13 @@ ...@@ -81,9 +81,13 @@
router.route.apply(router, route); router.route.apply(router, route);
}); });
// Create an event queue to track team changes
this.teamEvents = _.clone(Backbone.Events);
this.teamMemberships = new TeamMembershipCollection( this.teamMemberships = new TeamMembershipCollection(
this.userInfo.team_memberships_data, this.userInfo.team_memberships_data,
{ {
teamEvents: this.teamEvents,
url: this.teamMembershipsUrl, url: this.teamMembershipsUrl,
course_id: this.courseID, course_id: this.courseID,
username: this.userInfo.username, username: this.userInfo.username,
...@@ -94,6 +98,7 @@ ...@@ -94,6 +98,7 @@
this.myTeamsView = new MyTeamsView({ this.myTeamsView = new MyTeamsView({
router: this.router, router: this.router,
teamEvents: this.teamEvents,
collection: this.teamMemberships, collection: this.teamMemberships,
teamMemberships: this.teamMemberships, teamMemberships: this.teamMemberships,
maxTeamSize: this.maxTeamSize, maxTeamSize: this.maxTeamSize,
...@@ -107,12 +112,18 @@ ...@@ -107,12 +112,18 @@
this.topicsCollection = new TopicCollection( this.topicsCollection = new TopicCollection(
this.topics, this.topics,
{url: options.topicsUrl, course_id: this.courseID, parse: true} {
teamEvents: this.teamEvents,
url: options.topicsUrl,
course_id: this.courseID,
parse: true
}
).bootstrap(); ).bootstrap();
this.topicsView = new TopicsView({ this.topicsView = new TopicsView({
collection: this.topicsCollection, router: this.router,
router: this.router teamEvents: this.teamEvents,
collection: this.topicsCollection
}); });
this.mainView = this.tabbedView = new ViewWithHeader({ this.mainView = this.tabbedView = new ViewWithHeader({
...@@ -196,6 +207,7 @@ ...@@ -196,6 +207,7 @@
}) })
}), }),
main: new TeamEditView({ main: new TeamEditView({
teamEvents: self.teamEvents,
tagName: 'create-new-team', tagName: 'create-new-team',
teamParams: teamsView.main.teamParams, teamParams: teamsView.main.teamParams,
primaryButtonTitle: 'Create' primaryButtonTitle: 'Create'
...@@ -220,6 +232,7 @@ ...@@ -220,6 +232,7 @@
this.getTopic(topicID) this.getTopic(topicID)
.done(function(topic) { .done(function(topic) {
var collection = new TeamCollection([], { var collection = new TeamCollection([], {
teamEvents: self.teamEvents,
course_id: self.courseID, course_id: self.courseID,
topic_id: topicID, topic_id: topicID,
url: self.teamsUrl, url: self.teamsUrl,
...@@ -229,7 +242,7 @@ ...@@ -229,7 +242,7 @@
collection.goTo(1) collection.goTo(1)
.done(function() { .done(function() {
var teamsView = new TopicTeamsView({ var teamsView = new TopicTeamsView({
router: router, router: self.router,
topic: topic, topic: topic,
collection: collection, collection: collection,
teamMemberships: self.teamMemberships, teamMemberships: self.teamMemberships,
...@@ -278,6 +291,8 @@ ...@@ -278,6 +291,8 @@
self.getTopic(topicID).done(function(topic) { self.getTopic(topicID).done(function(topic) {
self.getTeam(teamID, true).done(function(team) { self.getTeam(teamID, true).done(function(team) {
var view = new TeamProfileView({ var view = new TeamProfileView({
teamEvents: self.teamEvents,
router: self.router,
courseID: courseID, courseID: courseID,
model: team, model: team,
maxTeamSize: self.maxTeamSize, maxTeamSize: self.maxTeamSize,
...@@ -287,16 +302,15 @@ ...@@ -287,16 +302,15 @@
languages: self.languages, languages: self.languages,
teamMembershipDetailUrl: self.teamMembershipDetailUrl teamMembershipDetailUrl: self.teamMembershipDetailUrl
}); });
var teamJoinView = new TeamJoinView( var teamJoinView = new TeamJoinView({
{ teamEvents: self.teamEvents,
courseID: courseID, courseID: courseID,
model: team, model: team,
teamsUrl: self.teamsUrl, teamsUrl: self.teamsUrl,
maxTeamSize: self.maxTeamSize, maxTeamSize: self.maxTeamSize,
currentUsername: self.userInfo.username, currentUsername: self.userInfo.username,
teamMembershipsUrl: self.teamMembershipsUrl teamMembershipsUrl: self.teamMembershipsUrl
} });
);
deferred.resolve( deferred.resolve(
self.createViewWithHeader( self.createViewWithHeader(
{ {
......
...@@ -17,9 +17,14 @@ ...@@ -17,9 +17,14 @@
}, },
render: function() { render: function() {
TeamsView.prototype.render.call(this); var self = this;
$.when(
if (this.teamMemberships.canUserCreateTeam()) { this.collection.refresh(),
this.teamMemberships.refresh()
).done(function() {
TeamsView.prototype.render.call(self);
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}.")), _.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}.")),
{ {
...@@ -29,8 +34,9 @@ ...@@ -29,8 +34,9 @@
'span_end': '</a>' 'span_end': '</a>'
} }
); );
this.$el.append(_.template(teamActionsTemplate, {message: message})); self.$el.append(_.template(teamActionsTemplate, {message: message}));
} }
});
return this; return this;
}, },
......
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define([ define([
'gettext',
'teams/js/views/topic_card', 'teams/js/views/topic_card',
'common/js/components/views/paginated_view' 'common/js/components/views/paginated_view'
], function (TopicCardView, PaginatedView) { ], function (gettext, TopicCardView, PaginatedView) {
var TopicsView = PaginatedView.extend({ var TopicsView = PaginatedView.extend({
type: 'topics', type: 'topics',
...@@ -18,6 +19,16 @@ ...@@ -18,6 +19,16 @@
srInfo: this.srInfo srInfo: this.srInfo
}); });
PaginatedView.prototype.initialize.call(this); PaginatedView.prototype.initialize.call(this);
},
render: function() {
var self = this;
this.collection.refresh()
.done(function() {
self.collection.isStale = false;
PaginatedView.prototype.render.call(self);
});
return this;
} }
}); });
return TopicsView; return TopicsView;
......
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