Merge pull request #9376 from edx/muzaffar/tnl1914-team-edit-page

Edit team page for Instructors only.
This commit is contained in:
Muzaffar yousaf
2015-08-28 15:31:46 +05:00
15 changed files with 902 additions and 414 deletions

View File

@@ -4,13 +4,14 @@ define([
'backbone',
'common/js/spec_helpers/ajax_helpers',
'teams/js/views/edit_team',
'teams/js/models/team',
'teams/js/spec_helpers/team_spec_helpers'
], function ($, _, Backbone, AjaxHelpers, TeamEditView, TeamSpecHelpers) {
], function ($, _, Backbone, AjaxHelpers, TeamEditView, TeamModel, TeamSpecHelpers) {
'use strict';
describe('EditTeam', function () {
describe('CreateEditTeam', function() {
var teamsUrl = '/api/team/v0/teams/',
teamsData = {
createTeamData = {
id: null,
name: "TeamName",
is_active: null,
@@ -18,11 +19,17 @@ define([
topic_id: "awesomeness",
date_created: "",
description: "TeamDescription",
country: "c",
language: "a",
country: "US",
language: "en",
membership: [],
last_activity_at: ''
},
editTeamData = {
name: "UpdatedAvengers",
description: "We do not discuss about avengers.",
country: "US",
language: "en"
},
verifyValidation = function (requests, teamEditView, fieldsData) {
_.each(fieldsData, function (fieldData) {
teamEditView.$(fieldData[0]).val(fieldData[1]);
@@ -32,33 +39,57 @@ define([
var message = teamEditView.$('.wrapper-msg');
expect(message.hasClass('is-hidden')).toBeFalsy();
expect(message.find('.title').text().trim()).toBe("Your team could not be created!");
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('.copy').text().trim()).toBe(
"Check the highlighted fields below and try again."
);
_.each(fieldsData, function (fieldData) {
if(fieldData[2] === 'error') {
if (fieldData[2] === 'error') {
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(requests.length).toBe(0);
};
},
editTeamID = 'av',
teamAction;
var createEditTeamView = function () {
var teamModel = {};
if (teamAction === 'edit') {
teamModel = new TeamModel(
{
id: editTeamID,
name: 'Avengers',
description: 'Team of dumbs',
language: 'en',
country: 'US',
membership: [],
url: '/api/team/v0/teams/' + editTeamID
},
{
parse: true
}
);
}
var createTeamEditView = function() {
return new TeamEditView({
teamEvents: TeamSpecHelpers.teamEvents,
el: $('.teams-content'),
action: teamAction,
model: teamModel,
teamParams: {
teamsUrl: teamsUrl,
courseID: "a/b/c",
topicID: 'awesomeness',
topicName: 'Awesomeness',
languages: [['a', 'aaa'], ['b', 'bbb']],
countries: [['c', 'ccc'], ['d', 'ddd']]
languages: [['aa', 'Afar'], ['fr', 'French'], ['en', 'English']],
countries: [['af', 'Afghanistan'], ['CA', 'Canada'], ['US', 'United States']],
teamsDetailUrl: teamModel.url
}
}).render();
};
@@ -68,15 +99,15 @@ define([
spyOn(Backbone.history, 'navigate');
});
it('can render itself correctly', function () {
var assertFormRendersCorrectly = function() {
var fieldClasses = [
'.u-field-name',
'.u-field-description',
'.u-field-optional_description',
'.u-field-language',
'.u-field-country'
],
teamEditView = createTeamEditView();
'.u-field-name',
'.u-field-description',
'.u-field-optional_description',
'.u-field-language',
'.u-field-country'
],
teamEditView = createEditTeamView();
_.each(fieldClasses, function (fieldClass) {
expect(teamEditView.$el.find(fieldClass).length).toBe(1);
@@ -84,32 +115,46 @@ define([
expect(teamEditView.$('.create-team.form-actions .action-primary').length).toBe(1);
expect(teamEditView.$('.create-team.form-actions .action-cancel').length).toBe(1);
});
it('can create a team', function () {
var requests = AjaxHelpers.requests(this),
teamEditView = createTeamEditView();
if (teamAction === 'edit') {
expect(teamEditView.$el.find('.u-field-name input').val()).toBe('Avengers');
expect(teamEditView.$el.find('.u-field-description textarea').val()).toBe('Team of dumbs');
expect(teamEditView.$el.find('.u-field-language select option:selected').text()).toBe('English');
expect(teamEditView.$el.find('.u-field-country select option:selected').text()).toBe('United States');
}
};
var requestMethod = function() {
return teamAction === 'create' ? 'POST' : 'PATCH';
};
var assertTeamCreateUpdateInfo = function(that, teamsData, teamsUrl, expectedUrl) {
var requests = AjaxHelpers.requests(that),
teamEditView = createEditTeamView();
teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
teamEditView.$('.u-field-language select').val('a').attr("selected", "selected");
teamEditView.$('.u-field-country select').val('c').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.$('.create-team.form-actions .action-primary').click();
AjaxHelpers.expectJsonRequest(requests, 'POST', teamsUrl, teamsData);
AjaxHelpers.respondWithJson(requests, _.extend(_.extend({}, teamsData), { id: '123'}));
AjaxHelpers.expectJsonRequest(requests, requestMethod(), teamsUrl, teamsData);
AjaxHelpers.respondWithJson(requests, _.extend(_.extend({}, teamsData), teamAction === 'create' ? {id: '123'} : {}));
expect(teamEditView.$('.create-team.wrapper-msg .copy').text().trim().length).toBe(0);
expect(Backbone.history.navigate.calls[0].args).toContain('teams/awesomeness/123');
});
expect(Backbone.history.navigate.calls[0].args).toContain(expectedUrl);
};
var assertValidationMessagesWhenFieldsEmpty = function(that) {
var requests = AjaxHelpers.requests(that),
teamEditView = createEditTeamView();
it('shows validation error message when field is empty', function () {
var requests = AjaxHelpers.requests(this),
teamEditView = createTeamEditView();
verifyValidation(requests, teamEditView, [
['.u-field-name input', 'Name', 'success'],
['.u-field-textarea textarea', '', 'error']
]);
teamEditView.render();
verifyValidation(requests, teamEditView, [
['.u-field-name input', '', 'error'],
@@ -120,13 +165,13 @@ define([
['.u-field-name input', '', 'error'],
['.u-field-textarea textarea', '', 'error']
]);
});
};
it('shows validation error message when field value length exceeded the limit', function () {
var requests = AjaxHelpers.requests(this),
teamEditView = createTeamEditView(),
teamName = new Array(500 + 1).join( '$'),
teamDescription = new Array(500 + 1).join( '$' );
var assertValidationMessagesWhenInvalidData = function(that) {
var requests = AjaxHelpers.requests(that),
teamEditView = createEditTeamView(),
teamName = new Array(500 + 1).join('$'),
teamDescription = new Array(500 + 1).join('$');
verifyValidation(requests, teamEditView, [
['.u-field-name input', teamName, 'error'],
@@ -142,48 +187,114 @@ define([
['.u-field-name input', teamName, 'error'],
['.u-field-textarea textarea', teamDescription, 'error']
]);
});
};
it("shows an error message for HTTP 500", function () {
var teamEditView = createTeamEditView(),
requests = AjaxHelpers.requests(this);
var assertShowMessageOnError = function(that, teamsData, teamsUrl, errorCode) {
var teamEditView = createEditTeamView(),
requests = AjaxHelpers.requests(that);
teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
teamEditView.$('.create-team.form-actions .action-primary').click();
teamsData.country = '';
teamsData.language = '';
AjaxHelpers.expectJsonRequest(requests, 'POST', teamsUrl, teamsData);
AjaxHelpers.respondWithError(requests);
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("An error occurred. Please try again.");
});
if (teamAction === 'create') {
teamsData.country = '';
teamsData.language = '';
}
AjaxHelpers.expectJsonRequest(requests, requestMethod(), teamsUrl, teamsData);
it("shows correct error message when server returns an error", function () {
var requests = AjaxHelpers.requests(this),
teamEditView = createTeamEditView();
if (errorCode < 500) {
AjaxHelpers.respondWithError(
requests,
errorCode,
{'user_message': 'User message', 'developer_message': 'Developer message'}
);
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("User message");
} else {
AjaxHelpers.respondWithError(requests);
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("An error occurred. Please try again.");
}
};
teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
teamEditView.$('.create-team.form-actions .action-primary').click();
teamsData.country = '';
teamsData.language = '';
AjaxHelpers.expectJsonRequest(requests, 'POST', teamsUrl, teamsData);
AjaxHelpers.respondWithError(
requests,
400,
{'user_message': 'User message', 'developer_message': 'Developer message'}
);
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("User message");
});
it("changes route on cancel click", function () {
var teamEditView = createTeamEditView();
var assertRedirectsToCorrectUrlOnCancel = function(expectedUrl) {
var teamEditView = createEditTeamView();
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(expectedUrl);
};
describe('NewTeam', function () {
beforeEach(function() {
teamAction = 'create';
});
it('can render itself correctly', function () {
assertFormRendersCorrectly();
});
it('can create a team', function () {
assertTeamCreateUpdateInfo(this, createTeamData, teamsUrl, 'teams/awesomeness/123');
});
it('shows validation error message when field is empty', function () {
assertValidationMessagesWhenFieldsEmpty(this);
});
it('shows validation error message when field value length exceeded the limit', function () {
assertValidationMessagesWhenInvalidData(this);
});
it("shows an error message for HTTP 500", function () {
assertShowMessageOnError(this, createTeamData, teamsUrl, 500);
});
it("shows correct error message when server returns an error", function () {
assertShowMessageOnError(this, createTeamData, teamsUrl, 400);
});
it("changes route on cancel click", function () {
assertRedirectsToCorrectUrlOnCancel('topics/awesomeness');
});
});
describe('EditTeam', function () {
beforeEach(function() {
teamAction = 'edit';
});
it('can render itself correctly', function () {
assertFormRendersCorrectly();
});
it('can edit a team', function () {
var copyTeamsData = _.clone(editTeamData);
copyTeamsData.country = 'CA';
copyTeamsData.language = 'fr';
assertTeamCreateUpdateInfo(this, copyTeamsData, teamsUrl + editTeamID + '?expand=user', 'teams/awesomeness/' + editTeamID);
});
it('shows validation error message when field is empty', function () {
assertValidationMessagesWhenFieldsEmpty(this);
});
it('shows validation error message when field value length exceeded the limit', function () {
assertValidationMessagesWhenInvalidData(this);
});
it("shows an error message for HTTP 500", function () {
assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 500);
});
it("shows correct error message when server returns an error", function () {
assertShowMessageOnError(this, editTeamData, teamsUrl + editTeamID + '?expand=user', 400);
});
it("changes route on cancel click", function () {
assertRedirectsToCorrectUrlOnCancel('teams/awesomeness/' + editTeamID);
});
});
});
});

View File

@@ -1,218 +0,0 @@
define([
'backbone', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/models/team',
'teams/js/views/team_join', 'teams/js/spec_helpers/team_spec_helpers'
], function (Backbone, _, AjaxHelpers, TeamModel, TeamJoinView, TeamSpecHelpers) {
'use strict';
describe('TeamJoinView', function () {
var createTeamsUrl,
createTeamModelData,
createMembershipData,
createJoinView,
verifyErrorMessage,
ACCOUNTS_API_URL = '/api/user/v1/accounts/',
TEAMS_URL = '/api/team/v0/teams/',
TEAMS_MEMBERSHIP_URL = '/api/team/v0/team_membership/';
beforeEach(function () {
setFixtures(
'<div class="teams-content"><div class="msg-content"><div class="copy"></div></div><div class="header-action-view"></div></div>'
);
});
verifyErrorMessage = function (requests, errorMessage, expectedMessage, joinTeam) {
var view = createJoinView(1, 'ma', createTeamModelData('teamA', 'teamAlpha', []));
if (joinTeam) {
// if we want the error to return when user try to join team, respond with no membership
AjaxHelpers.respondWithJson(requests, {"count": 0});
view.$('.action.action-primary').click();
}
AjaxHelpers.respondWithTextError(requests, 400, errorMessage);
expect($('.msg-content .copy').text().trim()).toBe(expectedMessage);
};
createTeamsUrl = function (teamId) {
return TEAMS_URL + teamId + '?expand=user';
};
createTeamModelData = function (teamId, teamName, membership) {
return {
id: teamId,
name: teamName,
membership: membership
};
};
createMembershipData = function (username) {
return [
{
"user": {
"username": username,
"url": ACCOUNTS_API_URL + username
}
}
];
};
createJoinView = function(maxTeamSize, currentUsername, teamModelData, teamId) {
teamId = teamId || 'teamA';
var model = new TeamModel(teamModelData, { parse: true });
model.url = createTeamsUrl(teamId);
var teamJoinView = new TeamJoinView(
{
courseID: TeamSpecHelpers.testCourseID,
teamEvents: TeamSpecHelpers.teamEvents,
model: model,
teamsUrl: createTeamsUrl(teamId),
maxTeamSize: maxTeamSize,
currentUsername: currentUsername,
teamMembershipsUrl: TEAMS_MEMBERSHIP_URL
}
);
return teamJoinView.render();
};
it('can render itself', function () {
var teamModelData = createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'));
var view = createJoinView(1, 'ma', teamModelData);
expect(view.$('.join-team').length).toEqual(1);
});
it('can join team successfully', function () {
var requests = AjaxHelpers.requests(this);
var currentUsername = 'ma1';
var teamId = 'teamA';
var teamName = 'teamAlpha';
var teamModelData = createTeamModelData(teamId, teamName, []);
var view = createJoinView(1, currentUsername, teamModelData);
// a get request will be sent to get user membership info
// because current user is not member of current team
AjaxHelpers.expectRequest(
requests,
'GET',
TEAMS_MEMBERSHIP_URL + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
})
);
// current user is not a member of any team so we should see the Join Team button
AjaxHelpers.respondWithJson(requests, {"count": 0});
expect(view.$('.action.action-primary').length).toEqual(1);
// a post request will be sent to add current user to current team
view.$('.action.action-primary').click();
AjaxHelpers.expectRequest(
requests,
'POST',
TEAMS_MEMBERSHIP_URL,
$.param({'username': currentUsername, 'team_id': teamId})
);
AjaxHelpers.respondWithJson(requests, {});
// on success, team model will be fetched and
// join team view and team profile will be re-rendered
AjaxHelpers.expectRequest(
requests,
'GET',
createTeamsUrl(teamId)
);
AjaxHelpers.respondWithJson(
requests, createTeamModelData(teamId, teamName, createMembershipData(currentUsername))
);
// current user is now member of the current team then there should be no button and no message
expect(view.$('.action.action-primary').length).toEqual(0);
expect(view.$('.join-team-message').length).toEqual(0);
});
it('shows already member message', function () {
var requests = AjaxHelpers.requests(this);
var currentUsername = 'ma1';
var view = createJoinView(1, currentUsername, createTeamModelData('teamA', 'teamAlpha', []));
// a get request will be sent to get user membership info
// because current user is not member of current team
AjaxHelpers.expectRequest(
requests,
'GET',
TEAMS_MEMBERSHIP_URL + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
})
);
// current user is a member of another team so we should see the correct message
AjaxHelpers.respondWithJson(requests, {"count": 1});
expect(view.$('.action.action-primary').length).toEqual(0);
expect(view.$('.join-team-message').text().trim()).toBe(view.alreadyMemberMessage);
});
it('shows team full message', function () {
var requests = AjaxHelpers.requests(this);
var view = createJoinView(
1,
'ma1',
createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'))
);
// team has no space and current user is a not member of
// current team so we should see the correct message
expect(view.$('.action.action-primary').length).toEqual(0);
expect(view.$('.join-team-message').text().trim()).toBe(view.teamFullMessage);
// there should be no request made
expect(requests.length).toBe(0);
});
it('shows correct error message if user fails to join team', function () {
var requests = AjaxHelpers.requests(this);
// verify user_message
verifyErrorMessage(
requests,
JSON.stringify({'user_message': "Can't be made member"}),
"Can't be made member",
true
);
// verify generic error message
verifyErrorMessage(
requests,
'',
'An error occurred. Try again.',
true
);
// verify error message when json parsing succeeded but error message format is incorrect
verifyErrorMessage(
requests,
JSON.stringify({'blah': "Can't be made member"}),
'An error occurred. Try again.',
true
);
});
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);
// verify user_message
verifyErrorMessage(
requests,
JSON.stringify({'user_message': "Can't return user memberships"}),
"Can't return user memberships",
false
);
// verify generic error message
verifyErrorMessage(
requests,
'',
'An error occurred. Try again.',
false
);
});
});
});

View File

@@ -0,0 +1,254 @@
define([
'backbone', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'teams/js/models/team',
'teams/js/views/team_profile_header_actions', 'teams/js/spec_helpers/team_spec_helpers'
], function (Backbone, _, AjaxHelpers, TeamModel, TeamProfileHeaderActionsView, TeamSpecHelpers) {
'use strict';
describe('TeamProfileHeaderActionsView', function () {
var createTeamsUrl,
createTeamModelData,
createMembershipData,
createHeaderActionsView,
verifyErrorMessage,
ACCOUNTS_API_URL = '/api/user/v1/accounts/',
TEAMS_URL = '/api/team/v0/teams/',
TEAMS_MEMBERSHIP_URL = '/api/team/v0/team_membership/';
createTeamsUrl = function (teamId) {
return TEAMS_URL + teamId + '?expand=user';
};
createTeamModelData = function (teamId, teamName, membership) {
return {
id: teamId,
name: teamName,
membership: membership,
url: createTeamsUrl(teamId)
};
};
createHeaderActionsView = function(maxTeamSize, currentUsername, teamModelData, showEditButton) {
var teamId = 'teamA';
var model = new TeamModel(teamModelData, { parse: true });
return new TeamProfileHeaderActionsView(
{
courseID: TeamSpecHelpers.testCourseID,
teamEvents: TeamSpecHelpers.teamEvents,
model: model,
teamsUrl: createTeamsUrl(teamId),
maxTeamSize: maxTeamSize,
currentUsername: currentUsername,
teamMembershipsUrl: TEAMS_MEMBERSHIP_URL,
topicID: '',
showEditButton: showEditButton
}
).render();
};
createMembershipData = function (username) {
return [
{
"user": {
"username": username,
"url": ACCOUNTS_API_URL + username
}
}
];
};
describe('JoinButton', function () {
beforeEach(function () {
setFixtures(
'<div class="teams-content"><div class="msg-content"><div class="copy"></div></div><div class="header-action-view"></div></div>'
);
});
verifyErrorMessage = function (requests, errorMessage, expectedMessage, joinTeam) {
var view = createHeaderActionsView(1, 'ma', createTeamModelData('teamA', 'teamAlpha', []));
if (joinTeam) {
// if we want the error to return when user try to join team, respond with no membership
AjaxHelpers.respondWithJson(requests, {"count": 0});
view.$('.action.action-primary').click();
}
AjaxHelpers.respondWithTextError(requests, 400, errorMessage);
expect($('.msg-content .copy').text().trim()).toBe(expectedMessage);
};
it('can render itself', function () {
var teamModelData = createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'));
var view = createHeaderActionsView(1, 'ma', teamModelData);
expect(view.$('.join-team').length).toEqual(1);
});
it('can join team successfully', function () {
var requests = AjaxHelpers.requests(this);
var currentUsername = 'ma1';
var teamId = 'teamA';
var teamName = 'teamAlpha';
var teamModelData = createTeamModelData(teamId, teamName, []);
var view = createHeaderActionsView(1, currentUsername, teamModelData);
// a get request will be sent to get user membership info
// because current user is not member of current team
AjaxHelpers.expectRequest(
requests,
'GET',
TEAMS_MEMBERSHIP_URL + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
})
);
// current user is not a member of any team so we should see the Join Team button
AjaxHelpers.respondWithJson(requests, {"count": 0});
expect(view.$('.action.action-primary').length).toEqual(1);
// a post request will be sent to add current user to current team
view.$('.action.action-primary').click();
AjaxHelpers.expectRequest(
requests,
'POST',
TEAMS_MEMBERSHIP_URL,
$.param({'username': currentUsername, 'team_id': teamId})
);
AjaxHelpers.respondWithJson(requests, {});
// on success, team model will be fetched and
// join team view and team profile will be re-rendered
AjaxHelpers.expectRequest(
requests,
'GET',
createTeamsUrl(teamId)
);
AjaxHelpers.respondWithJson(
requests, createTeamModelData(teamId, teamName, createMembershipData(currentUsername))
);
// current user is now member of the current team then there should be no button and no message
expect(view.$('.action.action-primary').length).toEqual(0);
expect(view.$('.join-team-message').length).toEqual(0);
});
it('shows already member message', function () {
var requests = AjaxHelpers.requests(this);
var currentUsername = 'ma1';
var view = createHeaderActionsView(1, currentUsername, createTeamModelData('teamA', 'teamAlpha', []));
// a get request will be sent to get user membership info
// because current user is not member of current team
AjaxHelpers.expectRequest(
requests,
'GET',
TEAMS_MEMBERSHIP_URL + '?' + $.param({
'username': currentUsername, 'course_id': TeamSpecHelpers.testCourseID
})
);
// current user is a member of another team so we should see the correct message
AjaxHelpers.respondWithJson(requests, {"count": 1});
expect(view.$('.action.action-primary').length).toEqual(0);
expect(view.$('.join-team-message').text().trim()).toBe(view.alreadyMemberMessage);
});
it('shows team full message', function () {
var requests = AjaxHelpers.requests(this);
var view = createHeaderActionsView(
1,
'ma1',
createTeamModelData('teamA', 'teamAlpha', createMembershipData('ma'))
);
// team has no space and current user is a not member of
// current team so we should see the correct message
expect(view.$('.action.action-primary').length).toEqual(0);
expect(view.$('.join-team-message').text().trim()).toBe(view.teamFullMessage);
// there should be no request made
expect(requests.length).toBe(0);
});
it('shows correct error message if user fails to join team', function () {
var requests = AjaxHelpers.requests(this);
// verify user_message
verifyErrorMessage(
requests,
JSON.stringify({'user_message': "Can't be made member"}),
"Can't be made member",
true
);
// verify generic error message
verifyErrorMessage(
requests,
'',
'An error occurred. Try again.',
true
);
// verify error message when json parsing succeeded but error message format is incorrect
verifyErrorMessage(
requests,
JSON.stringify({'blah': "Can't be made member"}),
'An error occurred. Try again.',
true
);
});
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);
// verify user_message
verifyErrorMessage(
requests,
JSON.stringify({'user_message': "Can't return user memberships"}),
"Can't return user memberships",
false
);
// verify generic error message
verifyErrorMessage(
requests,
'',
'An error occurred. Try again.',
false
);
});
});
describe('EditButton', function() {
var teamModelData,
view,
createAndAssertView;
createAndAssertView = function(showEditButton) {
teamModelData = createTeamModelData('aveA', 'avengers', createMembershipData('ma'));
view = createHeaderActionsView(1, 'ma', teamModelData, showEditButton);
expect(view.$('.action-edit-team').length).toEqual(showEditButton ? 1 : 0);
};
it('renders when option showEditButton is true', function () {
createAndAssertView(true);
});
it('does not render when option showEditButton is false', function () {
createAndAssertView(false);
});
it("can navigate to correct url", function () {
spyOn(Backbone.history, 'navigate');
createAndAssertView(true);
var editButton = view.$('.action-edit-team');
expect(editButton.length).toEqual(1);
$(editButton).click();
expect(Backbone.history.navigate.calls[0].args[0]).toContain('/edit-team');
});
});
});
});

View File

@@ -14,9 +14,9 @@
maxTeamDescriptionLength: 300,
events: {
'click .action-primary': 'createTeam',
'submit form': 'createTeam',
'click .action-cancel': 'goBackToTopic'
'click .action-primary': 'createOrUpdateTeam',
'submit form': 'createOrUpdateTeam',
'click .action-cancel': 'cancelAndGoBack'
},
initialize: function(options) {
@@ -27,12 +27,20 @@
this.teamsUrl = options.teamParams.teamsUrl;
this.languages = options.teamParams.languages;
this.countries = options.teamParams.countries;
this.primaryButtonTitle = options.primaryButtonTitle || 'Submit';
this.teamsDetailUrl = options.teamParams.teamsDetailUrl;
this.action = options.action;
_.bindAll(this, 'goBackToTopic', 'createTeam');
_.bindAll(this, 'cancelAndGoBack', 'createOrUpdateTeam');
this.teamModel = new TeamModel({});
this.teamModel.url = this.teamsUrl;
if (this.action === 'create') {
this.teamModel = new TeamModel({});
this.teamModel.url = this.teamsUrl;
this.primaryButtonTitle = 'Create';
} else if(this.action === 'edit' ) {
this.teamModel = options.model;
this.teamModel.url = this.teamsDetailUrl.replace('team_id', options.model.get('id')) + '?expand=user';
this.primaryButtonTitle = 'Update';
}
this.teamNameField = new FieldViews.TextFieldView({
model: this.teamModel,
@@ -74,7 +82,11 @@
},
render: function() {
this.$el.html(_.template(editTeamTemplate)({primaryButtonTitle: this.primaryButtonTitle}));
this.$el.html(_.template(editTeamTemplate) ({
primaryButtonTitle: this.primaryButtonTitle,
action: this.action,
totalMembers: _.isUndefined(this.teamModel) ? 0 : this.teamModel.get('membership').length
}));
this.set(this.teamNameField, '.team-required-fields');
this.set(this.teamDescriptionField, '.team-required-fields');
this.set(this.teamLanguageField, '.team-optional-fields');
@@ -91,20 +103,28 @@
}
},
createTeam: function (event) {
createOrUpdateTeam: function (event) {
event.preventDefault();
var view = this,
teamLanguage = this.teamLanguageField.fieldValue(),
teamCountry = this.teamCountryField.fieldValue();
teamCountry = this.teamCountryField.fieldValue(),
data = {
name: this.teamNameField.fieldValue(),
description: this.teamDescriptionField.fieldValue(),
language: _.isNull(teamLanguage) ? '' : teamLanguage,
country: _.isNull(teamCountry) ? '' : teamCountry
},
saveOptions = {
wait: true
};
var data = {
course_id: this.courseID,
topic_id: this.topicID,
name: this.teamNameField.fieldValue(),
description: this.teamDescriptionField.fieldValue(),
language: _.isNull(teamLanguage) ? '' : teamLanguage,
country: _.isNull(teamCountry) ? '' : teamCountry
};
if (this.action === 'create') {
data.course_id = this.courseID;
data.topic_id = this.topicID;
} else if (this.action === 'edit' ) {
saveOptions.patch = true;
saveOptions.contentType = 'application/merge-patch+json';
}
var validationResult = this.validateTeamData(data);
if (validationResult.status === false) {
@@ -112,10 +132,10 @@
return;
}
this.teamModel.save(data, { wait: true })
this.teamModel.save(data, saveOptions)
.done(function(result) {
view.teamEvents.trigger('teams:update', {
action: 'create',
action: view.action,
team: result
});
Backbone.history.navigate(
@@ -186,8 +206,15 @@
}
},
goBackToTopic: function () {
Backbone.history.navigate('topics/' + this.topicID, {trigger: true});
cancelAndGoBack: function (event) {
event.preventDefault();
var url;
if (this.action === 'create') {
url = 'topics/' + this.topicID;
} else if (this.action === 'edit' ) {
url = 'teams/' + this.topicID + '/' + this.teamModel.get('id');
}
Backbone.history.navigate(url, {trigger: true});
}
});
});

View File

@@ -5,8 +5,8 @@
'underscore',
'gettext',
'teams/js/views/team_utils',
'text!teams/templates/team-join.underscore'],
function (Backbone, _, gettext, TeamUtils, teamJoinTemplate) {
'text!teams/templates/team-profile-header-actions.underscore'],
function (Backbone, _, gettext, TeamUtils, teamProfileHeaderActionsTemplate) {
return Backbone.View.extend({
errorMessage: gettext("An error occurred. Try again."),
@@ -14,31 +14,34 @@
teamFullMessage: gettext("This team is full."),
events: {
"click .action-primary": "joinTeam"
"click .action-primary": "joinTeam",
"click .action-edit-team": "editTeam"
},
initialize: function(options) {
this.teamEvents = options.teamEvents;
this.template = _.template(teamJoinTemplate);
this.template = _.template(teamProfileHeaderActionsTemplate);
this.courseID = options.courseID;
this.maxTeamSize = options.maxTeamSize;
this.currentUsername = options.currentUsername;
this.teamMembershipsUrl = options.teamMembershipsUrl;
_.bindAll(this, 'render', 'joinTeam', 'getUserTeamInfo');
this.showEditButton = options.showEditButton;
this.topicID = options.topicID;
_.bindAll(this, 'render', 'joinTeam','editTeam', 'getUserTeamInfo');
this.listenTo(this.model, "change", this.render);
},
render: function() {
var view = this,
message,
showButton,
showJoinButton,
teamHasSpace;
this.getUserTeamInfo(this.currentUsername, view.maxTeamSize).done(function (info) {
teamHasSpace = info.teamHasSpace;
// if user is the member of current team then we wouldn't show anything
if (!info.memberOfCurrentTeam) {
showButton = !info.alreadyMember && teamHasSpace;
showJoinButton = !info.alreadyMember && teamHasSpace;
if (info.alreadyMember) {
message = info.memberOfCurrentTeam ? '' : view.alreadyMemberMessage;
@@ -47,7 +50,11 @@
}
}
view.$el.html(view.template({showButton: showButton, message: message}));
view.$el.html(view.template({
showJoinButton: showJoinButton,
message: message,
showEditButton: view.showEditButton
}));
});
return view;
},
@@ -108,6 +115,10 @@
}
return deferred.promise();
},
editTeam: function (event) {
event.preventDefault();
Backbone.history.navigate('topics/' + this.topicID + '/' + this.model.get('id') +'/edit-team', {trigger: true});
}
});
});

View File

@@ -17,12 +17,12 @@
'teams/js/views/my_teams',
'teams/js/views/topic_teams',
'teams/js/views/edit_team',
'teams/js/views/team_join',
'teams/js/views/team_profile_header_actions',
'text!teams/templates/teams_tab.underscore'],
function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection,
TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
TeamJoinView, teamsTemplate) {
TeamProfileHeaderActionsView, teamsTemplate) {
var TeamsHeaderModel = HeaderModel.extend({
initialize: function (attributes) {
_.extend(this.defaults, {nav_aria_label: gettext('teams')});
@@ -52,6 +52,7 @@
this.topics = options.topics;
this.topicUrl = options.topicUrl;
this.teamsUrl = options.teamsUrl;
this.teamsDetailUrl = options.teamsDetailUrl;
this.teamMembershipsUrl = options.teamMembershipsUrl;
this.teamMembershipDetailUrl = options.teamMembershipDetailUrl;
this.maxTeamSize = options.maxTeamSize;
@@ -74,6 +75,7 @@
}, this)],
['topics/:topic_id(/)', _.bind(this.browseTopic, this)],
['topics/:topic_id/create-team(/)', _.bind(this.newTeam, this)],
['topics/:topic_id/:team_id/edit-team(/)', _.bind(this.editTeam, this)],
['teams/:topic_id/:team_id(/)', _.bind(this.browseTeam, this)],
[new RegExp('^(browse)\/?$'), _.bind(this.goToTab, this)],
[new RegExp('^(my-teams)\/?$'), _.bind(this.goToTab, this)]
@@ -208,16 +210,53 @@
})
}),
main: new TeamEditView({
action: 'create',
teamEvents: self.teamEvents,
tagName: 'create-new-team',
teamParams: teamsView.main.teamParams,
primaryButtonTitle: 'Create'
teamParams: teamsView.main.teamParams
})
});
self.render();
});
},
/**
* Render the edit team form.
*/
editTeam: function (topicID, teamID) {
var self = this,
editViewWithHeader;
this.getTopic(topicID).done(function (topic) {
self.getTeam(teamID, false).done(function(team) {
var view = new TeamEditView({
action: 'edit',
teamEvents: self.teamEvents,
teamParams: {
courseID: self.courseID,
topicID: topic.get('id'),
teamsUrl: self.teamsUrl,
topicName: topic.get('name'),
languages: self.languages,
countries: self.countries,
teamsDetailUrl: self.teamsDetailUrl
},
model: team
});
editViewWithHeader = self.createViewWithHeader({
mainView: view,
subject: {
name: gettext("Edit Team"),
description: gettext("If you make significant changes, make sure you notify members of the team before making these changes.")
},
parentTeam: team,
parentTopic: topic
}
);
self.mainView = editViewWithHeader;
self.render();
});
});
},
/**
* Return a promise for the TeamsView for the given topic ID.
*/
@@ -227,7 +266,7 @@
var self = this,
router = this.router,
deferred = $.Deferred();
if (this.teamsCollection && this.teamsCollection.topic_id === topicID) {
if (this.teamsCollection && this.teamsCollection.topic_id === topicID && this.teamsView) {
deferred.resolve(this.teamsView);
} else {
this.getTopic(topicID)
@@ -254,7 +293,8 @@
teamsUrl: self.teamsUrl,
topicName: topic.get('name'),
languages: self.languages,
countries: self.countries
countries: self.countries,
teamsDetailUrl: self.teamsDetailUrl
}
});
deferred.resolve(
@@ -311,14 +351,17 @@
teamMembershipDetailUrl: self.teamMembershipDetailUrl,
setFocusToHeaderFunc: self.setFocusToHeader
});
var teamJoinView = new TeamJoinView({
var TeamProfileActionsView = new TeamProfileHeaderActionsView({
teamEvents: self.teamEvents,
courseID: courseID,
model: team,
courseID: courseID,
model: team,
teamsUrl: self.teamsUrl,
maxTeamSize: self.maxTeamSize,
currentUsername: self.userInfo.username,
teamMembershipsUrl: self.teamMembershipsUrl
teamMembershipsUrl: self.teamMembershipsUrl,
topicID: topicID,
showEditButton: self.userInfo.privileged || self.userInfo.staff
});
deferred.resolve(
self.createViewWithHeader(
@@ -326,7 +369,7 @@
mainView: view,
subject: team,
parentTopic: topic,
headerActionsView: teamJoinView
headerActionsView: TeamProfileActionsView
}
)
);
@@ -337,7 +380,8 @@
createViewWithHeader: function (options) {
var router = this.router,
breadcrumbs, headerView;
breadcrumbs, headerView,
viewDescription, viewTitle;
breadcrumbs = [{
title: gettext('All Topics'),
url: '#browse'
@@ -348,10 +392,25 @@
url: '#topics/' + options.parentTopic.id
});
}
if (options.parentTeam) {
breadcrumbs.push({
title: options.parentTeam.get('name'),
url: '#teams/' + options.parentTopic.id + '/' + options.parentTeam.id
});
}
if (options.subject instanceof Backbone.Model) {
viewDescription = options.subject.get('description');
viewTitle = options.subject.get('name');
} else if (options.subject) {
viewDescription = options.subject.description;
viewTitle = options.subject.name;
}
headerView = new HeaderView({
model: new TeamsHeaderModel({
description: options.subject.get('description'),
title: options.subject.get('name'),
description: viewDescription,
title: viewTitle,
breadcrumbs: breadcrumbs
}),
headerActionsView: options.headerActionsView,

View File

@@ -1,8 +1,14 @@
<form>
<div class="create-team wrapper-msg is-incontext urgency-low warning is-hidden" tabindex="-1">
<div class="create-team wrapper-msg is-incontext urgency-low error is-hidden" tabindex="-1">
<div class="msg">
<div class="msg-content">
<h3 class="title"><%- gettext("Your team could not be created!") %></h3>
<h3 class="title">
<% if (action === 'create') { %>
<%- gettext("Your team could not be created.") %>
<% } else if (action === 'edit') { %>
<%- gettext("Your team could not be updated.") %>
<% } %>
</h3>
<span class="screen-reader-message sr"></span>
<div class="copy">
<p></p>
@@ -11,15 +17,16 @@
</div>
</div>
<div class="form-instructions create-team-instructions">
<p class="copy">
<%- gettext("Enter information to describe your team. You cannot change these details after you create the team.") %></p>
</div>
<% if (action === 'create') { %>
<div class="form-instructions create-team-instructions">
<p class="copy">
<%- gettext("Enter information to describe your team. You cannot change these details after you create the team.") %></p>
</div>
<% } %>
<div class="team-edit-fields">
<div class="team-required-fields">
</div>
<div class="team-optional-fields">
<fieldset>
<div class="u-field u-field-optional_description">
@@ -38,7 +45,7 @@
<button class="action action-primary">
<%=
interpolate_text(
_.escape(gettext("{primaryButtonTitle} {span_start}a new team{span_end}")),
_.escape(gettext("{primaryButtonTitle} {span_start}a team{span_end}")),
{
'primaryButtonTitle': primaryButtonTitle, 'span_start': '<span class="sr">', 'span_end': '</span>'
}
@@ -48,9 +55,10 @@
<button class="action action-cancel">
<%=
interpolate_text(
_.escape(gettext("Cancel {span_start}a new team{span_end}")),
_.escape(gettext("Cancel {span_start} {action} team {span_end}")),
{
'span_start': '<span class="sr">', 'span_end': '</span>'
'span_start': '<span class="sr">', 'span_end': '</span>',
'action': action === 'create' ? 'creating' : 'updating'
}
)
%>

View File

@@ -1,9 +1,13 @@
<div class="join-team form-actions">
<% if (showButton) {%>
<% if (showJoinButton) {%>
<button class="action action-primary">
<%- gettext("Join Team") %>
</button>
<% } else if (message) { %>
<p class="join-team-message"><%- message %></p>
<% } %>
<% if (showEditButton) { %>
<button class="btn btn-secondary action-edit-team"><%- gettext("Edit Team") %></button>
<% } %>
</div>

View File

@@ -50,7 +50,7 @@
<% if (isMember) { %>
<div class="leave-team">
<button class="btn btn-link btn-base btn-secondary leave-team-link"><%- gettext("Leave Team") %></button>
<button class="btn btn-link leave-team-link"><%- gettext("Leave Team") %></button>
</div>
<div class="divider-lv1"></div>

View File

@@ -39,6 +39,7 @@
topicUrl: '${ topic_url }',
topicsUrl: '${ topics_url }',
teamsUrl: '${ teams_url }',
teamsDetailUrl: '${ teams_detail_url }',
teamMembershipsUrl: '${ team_memberships_url }',
teamMembershipDetailUrl: '${ team_membership_detail_url }',
maxTeamSize: ${ course.teams_max_size },

View File

@@ -117,6 +117,7 @@ class TeamsDashboardView(View):
),
"topics_url": reverse('topics_list', request=request),
"teams_url": reverse('teams_list', request=request),
"teams_detail_url": reverse('teams_detail', args=['team_id']),
"team_memberships_url": reverse('team_membership_list', request=request),
"team_membership_detail_url": reverse('team_membership_detail', args=['team_id', user.username]),
"languages": settings.ALL_LANGUAGES,