Topic teams actions (#24336)

* modern eslint allowances

* update topic teams to request team membership per local teamset

* update tests for topic_teams change

* cleanup

Co-authored-by: Ben Warzeski <benwarzeski@edX-C02CD0HCLVDM.cable.rcn.com>
This commit is contained in:
Ben Warzeski
2020-06-26 14:46:51 -04:00
committed by GitHub
parent c962423bbc
commit 64e8332d38
5 changed files with 187 additions and 129 deletions

View File

@@ -0,0 +1,7 @@
{
"rules": {
"comma-dangle": "off",
"object-curly-spacing": "off",
"no-underscore-dangle": "off"
}
}

View File

@@ -38,14 +38,14 @@ define([
));
};
var createTeamsTabView = function(test, options) {
var createTeamsTabView = function(options) {
var teamsTabView = new TeamsTabView(
{
el: $('.teams-content'),
context: TeamSpecHelpers.createMockContext(options)
}
);
requests = AjaxHelpers.requests(test);
requests = AjaxHelpers.requests();
PageHelpers.preventBackboneChangingUrl();
teamsTabView.start();
return teamsTabView;
@@ -65,7 +65,7 @@ define([
describe('Navigation', function() {
it('does not render breadcrumbs for the top level tabs', function() {
var teamsTabView = createTeamsTabView(this);
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('#my-teams', {trigger: true});
expect(teamsTabView.$('.breadcrumbs').length).toBe(0);
teamsTabView.router.navigate('#browse', {trigger: true});
@@ -73,20 +73,20 @@ define([
});
it('does not interfere with anchor links to #main', function() {
var teamsTabView = createTeamsTabView(this);
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('#main', {trigger: true});
expect(teamsTabView.$('.wrapper-msg')).toHaveClass('is-hidden');
});
it('displays and focuses an error message when trying to navigate to a nonexistent page', function() {
var teamsTabView = createTeamsTabView(this);
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('no_such_page', {trigger: true});
expectError(teamsTabView, 'The page "no_such_page" could not be found.');
expectFocus(teamsTabView.$('.warning'));
});
it('displays and focuses an error message when trying to navigate to a nonexistent topic', function() {
var teamsTabView = createTeamsTabView(this);
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('topics/no_such_topic', {trigger: true});
AjaxHelpers.expectRequest(requests, 'GET', '/api/team/v0/topics/no_such_topic,course/1', null);
AjaxHelpers.respondWithError(requests, 404);
@@ -95,7 +95,7 @@ define([
});
it('displays and focuses an error message when trying to navigate to a nonexistent team', function() {
var teamsTabView = createTeamsTabView(this);
var teamsTabView = createTeamsTabView();
teamsTabView.router.navigate('teams/' + TeamSpecHelpers.testTopicID + '/no_such_team', {trigger: true});
AjaxHelpers.expectRequest(requests, 'GET', '/api/team/v0/teams/no_such_team?expand=user', null);
AjaxHelpers.respondWithError(requests, 404);
@@ -104,7 +104,7 @@ define([
});
it('displays and focuses an error message when it receives a 401 AJAX response', function() {
var teamsTabView = createTeamsTabView(this).render();
var teamsTabView = createTeamsTabView().render();
teamsTabView.router.navigate('topics/' + TeamSpecHelpers.testTopicID, {trigger: true});
AjaxHelpers.respondWithError(requests, 401);
expectError(teamsTabView, 'Your request could not be completed. Reload the page and try again.');
@@ -112,7 +112,7 @@ define([
});
it('displays and focuses an error message when it receives a 500 AJAX response', function() {
var teamsTabView = createTeamsTabView(this).render();
var teamsTabView = createTeamsTabView().render();
teamsTabView.router.navigate('topics/' + TeamSpecHelpers.testTopicID, {trigger: true});
AjaxHelpers.respondWithError(requests, 500);
expectError(
@@ -124,7 +124,7 @@ define([
});
it('does not navigate to the topics page when syncing its collection if not on search page', function() {
var teamsTabView = createTeamsTabView(this),
var teamsTabView = createTeamsTabView(),
collection = TeamSpecHelpers.createMockTeams();
teamsTabView.createTeamsListView({
collection: collection,
@@ -179,7 +179,7 @@ define([
}
]
}, function(url, expectedEvent) {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({staff: true})
});
teamsTabView.teamsCollection = TeamSpecHelpers.createMockTeams();
@@ -193,7 +193,7 @@ define([
describe('Discussion privileges', function() {
it('allows privileged access to any team', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: true})
});
// Note: using `undefined` here to ensure that we
@@ -203,7 +203,7 @@ define([
});
it('allows access to a team which an unprivileged user is a member of', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({
username: TeamSpecHelpers.testUser,
privileged: false
@@ -221,7 +221,7 @@ define([
});
it('does not allow access if the user is neither privileged nor a team member', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: false, staff: true})
});
expect(teamsTabView.readOnlyDiscussion({
@@ -233,7 +233,7 @@ define([
describe('Manage Tab', function() {
var manageTabSelector = '.page-content-nav>.nav-item[data-url=manage]';
it('is not visible to unprivileged users', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: false}),
hasManagedTopic: true
});
@@ -241,7 +241,7 @@ define([
});
it('is not visible when there are no managed topics', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: true}),
hasManagedTopic: false
});
@@ -249,7 +249,7 @@ define([
});
it('is visible to privileged users when there is a managed topic', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({privileged: true}),
hasManagedTopic: true
});
@@ -260,7 +260,7 @@ define([
describe('Browse Tab', function() {
var browseTabSelector = '.page-content-nav>.nav-item[data-url=browse]';
it('is not visible if there are no open and no public teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
hasOpenTopic: false,
hasPublicManagedTopic: false
});
@@ -268,7 +268,7 @@ define([
});
it('is visible if there are open teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
hasOpenTopic: true,
hasPublicManagedTopic: false
});
@@ -276,7 +276,7 @@ define([
});
it('is visible if there are public teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
hasOpenTopic: false,
hasPublicManagedTopic: true
});
@@ -284,7 +284,7 @@ define([
});
it('is visible if there are both public and open teamsets', function() {
var teamsTabView = createTeamsTabView(this, {
var teamsTabView = createTeamsTabView({
hasOpenTopic: true,
hasPublicManagedTopic: true
});
@@ -293,39 +293,46 @@ define([
});
describe('Search', function() {
var performSearch = function(reqs, teamsTabView) {
var teamsTabView;
var performSearch = function() {
teamsTabView.$('.search-field').val('foo');
teamsTabView.$('.action-search').click();
verifyTeamsRequest({
order_by: '',
text_search: 'foo'
});
AjaxHelpers.respondWithJson(reqs, TeamSpecHelpers.createMockTeamsResponse({results: []}));
AjaxHelpers.respondWithJson(
requests,
TeamSpecHelpers.createMockTeamsResponse({ results: [] }
));
AjaxHelpers.respondWithJson(requests, { count: 0 });
// Expect exactly one search request to be fired
AjaxHelpers.expectNoRequests(reqs);
// Expect exactly one search request to be fired, and one request to see if the user is
// in a team in the current teamset
AjaxHelpers.expectNoRequests(requests);
};
it('can search teams', function() {
var teamsTabView = createTeamsTabView(this);
beforeEach(function() {
teamsTabView = createTeamsTabView();
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
verifyTeamsRequest({
order_by: 'last_activity_at',
text_search: ''
});
AjaxHelpers.respondWithJson(requests, {});
performSearch(requests, teamsTabView);
AjaxHelpers.respondWithJson(requests, { count: 0 });
});
it('can search teams', function() {
performSearch();
expect(teamsTabView.$('.page-title').text()).toBe('Team Search');
expect(teamsTabView.$('.page-description').text()).toBe('Showing results for "foo"');
});
it('can clear a search', function() {
var teamsTabView = createTeamsTabView(this);
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
AjaxHelpers.respondWithJson(requests, {});
// Perform a search
performSearch(requests, teamsTabView);
performSearch();
// Clear the search and submit it again
teamsTabView.$('.search-field').val('');
@@ -335,17 +342,14 @@ define([
order_by: 'last_activity_at'
});
AjaxHelpers.respondWithJson(requests, {});
AjaxHelpers.respondWithJson(requests, { count: 0 });
expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
});
it('can navigate back to all teams from a search', function() {
var teamsTabView = createTeamsTabView(this);
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
AjaxHelpers.respondWithJson(requests, {});
// Perform a search
performSearch(requests, teamsTabView);
performSearch();
// Verify the breadcrumbs have a link back to the teams list, and click on it
expect(teamsTabView.$('.breadcrumbs a').length).toBe(2);
@@ -355,15 +359,12 @@ define([
text_search: ''
});
AjaxHelpers.respondWithJson(requests, {});
AjaxHelpers.respondWithJson(requests, { count: 0 });
expect(teamsTabView.$('.page-title').text()).toBe('Test Topic 1');
expect(teamsTabView.$('.page-description').text()).toBe('Test description 1');
});
it('does not switch to showing results when the search returns an error', function() {
var teamsTabView = createTeamsTabView(this);
teamsTabView.browseTopic(TeamSpecHelpers.testTopicID);
AjaxHelpers.respondWithJson(requests, {});
// Perform a search but respond with a 500
teamsTabView.$('.search-field').val('foo');
teamsTabView.$('.action-search').click();

View File

@@ -2,33 +2,69 @@ define([
'backbone',
'underscore',
'teams/js/views/topic_teams',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'teams/js/spec_helpers/team_spec_helpers',
'common/js/spec_helpers/page_helpers'
], function(Backbone, _, TopicTeamsView, TeamSpecHelpers, PageHelpers) {
], function(Backbone, _, TopicTeamsView, AjaxHelpers, TeamSpecHelpers, PageHelpers) {
'use strict';
describe('Topic Teams View', function() {
var createTopicTeamsView = function(options, isInstructorManagedTopic) {
options = options || {}; // eslint-disable-line no-param-reassign
return new TopicTeamsView({
var requests, teamsView;
var verifyTeamsetTeamsRequest = function(hasTeams) {
AjaxHelpers.expectRequestURL(
requests,
TeamSpecHelpers.testContext.teamMembershipsUrl,
{
username: TeamSpecHelpers.testUser,
course_id: TeamSpecHelpers.testCourseID,
teamset_id: TeamSpecHelpers.testTopicID,
}
);
AjaxHelpers.respondWithJson(
requests,
{ count: hasTeams ? 1 : 0 }
);
AjaxHelpers.expectNoRequests(requests);
};
var createTopicTeamsView = function(_options) {
var options = _options || {};
var onTeamInTeamset = options.onTeamInTeamset || false,
isInstructorManagedTopic = options.isInstructorManagedTopic || false;
var result, topicTeamsView;
requests = AjaxHelpers.requests(this);
topicTeamsView = new TopicTeamsView({
el: '.teams-container',
model: isInstructorManagedTopic ?
TeamSpecHelpers.createMockInstructorManagedTopic() : TeamSpecHelpers.createMockTopic(),
collection: options.teams || TeamSpecHelpers.createMockTeams({results: []}),
myTopicTeamsCollection: (
options.myTopicTeamsCollection || TeamSpecHelpers.createMockTeams({results: []})
),
context: _.extend({}, TeamSpecHelpers.testContext, options)
}).render();
});
result = topicTeamsView.render();
if (
topicTeamsView.context.userInfo.staff ||
topicTeamsView.context.userInfo.privileged ||
isInstructorManagedTopic
) {
AjaxHelpers.expectNoRequests(requests);
} else {
verifyTeamsetTeamsRequest(onTeamInTeamset);
}
return result;
};
var verifyActions = function(teamsView, options) {
var verifyActions = function(showActions) {
var expectedTitle = 'Are you having trouble finding a team to join?',
expectedMessage = 'Browse teams in other topics or search teams in this topic. ' +
'If you still can\'t find a team to join, create a new team in this topic.',
title = teamsView.$('.title').text().trim(),
message = teamsView.$('.copy').text().trim();
options = options || {showActions: true}; // eslint-disable-line no-param-reassign
if (options.showActions) {
message = teamsView.$('.copy').text().trim(),
_showActions = showActions === undefined ? true : showActions;
if (_showActions) {
expect(title).toBe(expectedTitle);
expect(message).toBe(expectedMessage);
} else {
@@ -49,32 +85,35 @@ define([
results: testTeamData
})
};
var teamsView = createTopicTeamsView(options);
var footerEl = teamsView.$('.teams-paging-footer');
var footerEl;
teamsView = createTopicTeamsView(options);
footerEl = teamsView.$('.teams-paging-footer');
expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total');
expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+2'); // eslint-disable-line no-useless-escape
expect(footerEl).not.toHaveClass('hidden');
TeamSpecHelpers.verifyCards(teamsView, testTeamData);
verifyActions(teamsView);
verifyActions();
});
it('can browse all teams', function() {
var teamsView = createTopicTeamsView();
teamsView = createTopicTeamsView();
spyOn(Backbone.history, 'navigate');
teamsView.$('.browse-teams').click();
expect(Backbone.history.navigate.calls.mostRecent().args[0]).toBe('browse');
});
it('gives the search field focus when clicking on the search teams link', function() {
var teamsView = createTopicTeamsView();
teamsView = createTopicTeamsView();
spyOn($.fn, 'focus').and.callThrough();
teamsView.$('.search-teams').click();
expect(teamsView.$('.search-field').first().focus).toHaveBeenCalled();
});
it('can show the create team modal', function() {
var teamsView = createTopicTeamsView();
teamsView = createTopicTeamsView();
spyOn(Backbone.history, 'navigate');
teamsView.$('a.create-team').click();
expect(Backbone.history.navigate.calls.mostRecent().args[0]).toBe(
@@ -83,14 +122,13 @@ define([
});
it('does not show actions for a user already in a team in the teamset', function() {
var options = {myTopicTeamsCollection: TeamSpecHelpers.createMockTeams()};
var teamsView = createTopicTeamsView(options);
verifyActions(teamsView, {showActions: false});
teamsView = createTopicTeamsView({ onTeamInTeamset: true });
verifyActions(false);
});
it('does not show actions for a student in an instructor managed topic', function() {
var teamsView = createTopicTeamsView({}, true);
verifyActions(teamsView, {showActions: false});
teamsView = createTopicTeamsView({ isInstructorManagedTopic: true });
verifyActions(false);
});
it('shows actions for a privileged user already in a team', function() {
@@ -99,10 +137,10 @@ define([
privileged: true,
staff: false
},
myTopicTeamsCollection: TeamSpecHelpers.createMockTeams()
onTeamInTeamset: true,
};
var teamsView = createTopicTeamsView(options);
verifyActions(teamsView, {showActions: true});
teamsView = createTopicTeamsView(options);
verifyActions();
});
it('shows actions for a staff user already in a team', function() {
@@ -111,10 +149,10 @@ define([
privileged: false,
staff: true
},
myTopicTeamsCollection: TeamSpecHelpers.createMockTeams()
onTeamInTeamset: true,
};
var teamsView = createTopicTeamsView(options);
verifyActions(teamsView, {showActions: true});
teamsView = createTopicTeamsView(options);
verifyActions();
});
/*
@@ -123,7 +161,7 @@ define([
var requests = AjaxHelpers.requests(this),
teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]),
teamsView = createTopicTeamsView({ teamMemberships: teamMemberships });
verifyActions(teamsView, {showActions: true});
verifyActions({showActions: true});
teamMemberships.teamEvents.trigger('teams:update', { action: 'create' });
teamsView.render();
AjaxHelpers.expectRequestURL(
@@ -138,7 +176,7 @@ define([
}
);
AjaxHelpers.respondWithJson(requests, {});
verifyActions(teamsView, {showActions: false});
verifyActions({showActions: false});
});
*/
});

View File

@@ -380,24 +380,11 @@
createTeamsListView: function(options) {
var topic = options.topic,
collection = options.collection,
myTopicTeamsCollection = new TeamCollection(
this.context.userInfo.teams,
{
teamEvents: this.teamEvents,
course_id: this.context.courseID,
username: this.context.userInfo.username,
topic_id: topic.get('id'),
perPage: 5,
parse: true,
url: this.context.myTeamsUrl
}
),
teamsView = new TopicTeamsView({
router: this.router,
context: this.context,
model: topic,
collection: collection,
myTopicTeamsCollection: myTopicTeamsCollection,
showSortControls: options.showSortControls
}),
searchFieldView = new SearchFieldView({

View File

@@ -1,6 +1,7 @@
(function(define) {
'use strict';
define([
'jquery',
'underscore',
'backbone',
'gettext',
@@ -9,7 +10,34 @@
'common/js/components/views/paging_header',
'text!teams/templates/team-actions.underscore',
'teams/js/views/team_utils'
], function(_, Backbone, gettext, HtmlUtils, TeamsView, PagingHeader, teamActionsTemplate, TeamUtils) {
], function($, _, Backbone, gettext, HtmlUtils, TeamsView, PagingHeader, teamActionsTemplate, TeamUtils) {
// Translators: this string is shown at the bottom of the teams page
// to find a team to join or else to create a new one. There are three
// links that need to be included in the message:
// 1. Browse teams in other topics
// 2. search teams
// 3. create a new team
// Be careful to start each link with the appropriate start indicator
// (e.g. {browse_span_start} for #1) and finish it with {span_end}.
var actionMessage = interpolate_text( // eslint-disable-line no-undef
_.escape(gettext(
'{browse_span_start}Browse teams in other ' +
'topics{span_end} or {search_span_start}search teams{span_end} ' +
'in this topic. If you still can\'t find a team to join, ' +
'{create_span_start}create a new team in this topic{span_end}.'
)),
{
browse_span_start: '<a class="browse-teams" href="">',
search_span_start: '<a class="search-teams" href="">',
create_span_start: '<a class="create-team" href="">',
span_end: '</a>'
}
);
var canUserCreateTeamErrorMessage = gettext(
'An error occurred while looking up team membership. Try refreshing the page.'
);
var TopicTeamsView = TeamsView.extend({
events: {
'click a.browse-teams': 'browseTeams',
@@ -24,51 +52,48 @@
TeamsView.prototype.initialize.call(this, options);
},
canUserCreateTeam: function() {
// Note: non-staff and non-privileged users are automatically added to any team
// that they create. This means that if multiple team membership is
// disabled that they cannot create a new team when they already
// belong to one.
return this.context.userInfo.staff
|| this.context.userInfo.privileged
|| (!TeamUtils.isInstructorManagedTopic(this.model.attributes.type)
&& this.options.myTopicTeamsCollection.length === 0);
checkIfUserCanCreateTeam: function() {
var deferred = $.Deferred();
if (this.context.userInfo.staff || this.context.userInfo.privileged) {
deferred.resolve(true);
} else if (TeamUtils.isInstructorManagedTopic(this.model.attributes.type)) {
deferred.resolve(false);
} else {
$.ajax({
type: 'GET',
url: this.context.teamMembershipsUrl,
data: {
username: this.context.userInfo.username,
course_id: this.context.courseID,
teamset_id: this.model.get('id')
}
}).done(function(data) {
deferred.resolve(data.count === 0);
}).fail(function(data) {
TeamUtils.parseAndShowMessage(data, canUserCreateTeamErrorMessage);
deferred.resolve(false);
});
}
return deferred.promise();
},
showActions: function() {
HtmlUtils.append(
this.$el,
HtmlUtils.template(teamActionsTemplate)({message: actionMessage})
);
},
render: function() {
var self = this;
this.collection.refresh().done(function() {
var message;
TeamsView.prototype.render.call(self);
if (self.canUserCreateTeam()) {
message = interpolate_text( // eslint-disable-line no-undef
// Translators: this string is shown at the bottom of the teams page
// to find a team to join or else to create a new one. There are three
// links that need to be included in the message:
// 1. Browse teams in other topics
// 2. search teams
// 3. create a new team
// Be careful to start each link with the appropriate start indicator
// (e.g. {browse_span_start} for #1) and finish it with {span_end}.
_.escape(gettext(
'{browse_span_start}Browse teams in other ' +
'topics{span_end} or {search_span_start}search teams{span_end} ' +
'in this topic. If you still can\'t find a team to join, ' +
'{create_span_start}create a new team in this topic{span_end}.'
)),
{
browse_span_start: '<a class="browse-teams" href="">',
search_span_start: '<a class="search-teams" href="">',
create_span_start: '<a class="create-team" href="">',
span_end: '</a>'
}
);
HtmlUtils.append(
self.$el,
HtmlUtils.template(teamActionsTemplate)({message: message})
);
}
});
this.collection.refresh().done(_.bind(function() {
TeamsView.prototype.render.call(this);
this.checkIfUserCanCreateTeam().done(_.bind(function(canUserCreateTeam) {
if (canUserCreateTeam) {
this.showActions();
}
}, this));
}, this));
return this;
},