I've changed the logic so that AjaxHelpers keeps track of which requests have not yet had mock responses sent. This ensures that every response is handled before moving on to the next one, rather than always handling the last request. My intention is that this won't allow bugs to creep in where a request isn't fired and instead the test responds to an old request. It also should ensure that extra events aren't accidentally fired.
1039 lines
41 KiB
JavaScript
1039 lines
41 KiB
JavaScript
define([
|
|
'underscore', 'js/models/course', 'js/models/group_configuration', 'js/models/group',
|
|
'js/collections/group_configuration', 'js/collections/group',
|
|
'js/views/group_configuration_details', 'js/views/group_configurations_list', 'js/views/group_configuration_editor',
|
|
'js/views/group_configuration_item', 'js/views/experiment_group_edit', 'js/views/content_group_list',
|
|
'js/views/content_group_details', 'js/views/content_group_editor', 'js/views/content_group_item',
|
|
'common/js/components/views/feedback_notification', 'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/template_helpers',
|
|
'common/js/spec_helpers/view_helpers', 'jasmine-stealth'
|
|
], function(
|
|
_, Course, GroupConfigurationModel, GroupModel, GroupConfigurationCollection, GroupCollection,
|
|
GroupConfigurationDetailsView, GroupConfigurationsListView, GroupConfigurationEditorView,
|
|
GroupConfigurationItemView, ExperimentGroupEditView, GroupList, ContentGroupDetailsView,
|
|
ContentGroupEditorView, ContentGroupItemView, Notification, AjaxHelpers, TemplateHelpers, ViewHelpers
|
|
) {
|
|
'use strict';
|
|
var SELECTORS = {
|
|
detailsView: '.group-configuration-details',
|
|
editView: '.group-configuration-edit',
|
|
itemView: '.group-configurations-list-item',
|
|
group: '.group',
|
|
groupFields: '.groups-fields',
|
|
name: '.group-configuration-name',
|
|
description: '.group-configuration-description',
|
|
groupsCount: '.group-configuration-groups-count',
|
|
groupsAllocation: '.group-allocation',
|
|
errorMessage: '.group-configuration-edit-error',
|
|
inputGroupName: '.group-name',
|
|
inputName: '.collection-name-input',
|
|
inputDescription: '.group-configuration-description-input',
|
|
usageCount: '.group-configuration-usage-count',
|
|
usage: '.group-configuration-usage',
|
|
usageText: '.group-configuration-usage-text',
|
|
usageTextAnchor: '.group-configuration-usage-text > a',
|
|
usageUnit: '.group-configuration-usage-unit',
|
|
usageUnitAnchor: '.group-configuration-usage-unit a',
|
|
usageUnitMessage: '.group-configuration-validation-message',
|
|
usageUnitWarningIcon: '.group-configuration-usage-unit i.fa-warning',
|
|
usageUnitErrorIcon: '.group-configuration-usage-unit i.fa-exclamation-circle',
|
|
warningMessage: '.group-configuration-validation-text',
|
|
warningIcon: '.wrapper-group-configuration-validation > i',
|
|
note: '.wrapper-delete-button'
|
|
};
|
|
|
|
var assertTheDetailsView = function (view, text) {
|
|
expect(view.$el).toContainText(text);
|
|
expect(view.$el).toContainText('ID: 0');
|
|
expect(view.$('.delete')).toExist();
|
|
};
|
|
var assertShowEmptyUsages = function (view, usageText) {
|
|
expect(view.$(SELECTORS.usageCount)).not.toExist();
|
|
expect(view.$(SELECTORS.usageText)).toContainText(usageText);
|
|
expect(view.$(SELECTORS.usageTextAnchor)).toExist();
|
|
expect(view.$(SELECTORS.usageUnit)).not.toExist();
|
|
};
|
|
var assertHideEmptyUsages = function (view) {
|
|
expect(view.$(SELECTORS.usageText)).not.toExist();
|
|
expect(view.$(SELECTORS.usageUnit)).not.toExist();
|
|
expect(view.$(SELECTORS.usageCount)).toContainText('Not in Use');
|
|
};
|
|
var assertShowNonEmptyUsages = function (view, usageText, toolTipText) {
|
|
var usageUnitAnchors = view.$(SELECTORS.usageUnitAnchor);
|
|
|
|
expect(view.$(SELECTORS.note)).toHaveAttr(
|
|
'data-tooltip', toolTipText
|
|
);
|
|
expect(view.$('.delete')).toHaveClass('is-disabled');
|
|
expect(view.$(SELECTORS.usageCount)).not.toExist();
|
|
expect(view.$(SELECTORS.usageText)).toContainText(usageText);
|
|
expect(view.$(SELECTORS.usageUnit).length).toBe(2);
|
|
expect(usageUnitAnchors.length).toBe(2);
|
|
expect(usageUnitAnchors.eq(0)).toContainText('label1');
|
|
expect(usageUnitAnchors.eq(0).attr('href')).toBe('url1');
|
|
expect(usageUnitAnchors.eq(1)).toContainText('label2');
|
|
expect(usageUnitAnchors.eq(1).attr('href')).toBe('url2');
|
|
};
|
|
var assertHideNonEmptyUsages = function (view) {
|
|
expect(view.$('.delete')).toHaveClass('is-disabled');
|
|
expect(view.$(SELECTORS.usageText)).not.toExist();
|
|
expect(view.$(SELECTORS.usageUnit)).not.toExist();
|
|
expect(view.$(SELECTORS.usageCount)).toContainText('Used in 2 units');
|
|
};
|
|
var setUsageInfo = function (model) {
|
|
model.set('usage', [
|
|
{'label': 'label1', 'url': 'url1'},
|
|
{'label': 'label2', 'url': 'url2'}
|
|
]);
|
|
};
|
|
var assertHideValidationContent = function (view) {
|
|
expect(view.$(SELECTORS.usageUnitMessage)).not.toExist();
|
|
expect(view.$(SELECTORS.usageUnitWarningIcon)).not.toExist();
|
|
expect(view.$(SELECTORS.usageUnitErrorIcon)).not.toExist();
|
|
};
|
|
var assertControllerView = function (view, detailsView, editView) {
|
|
// Details view by default
|
|
expect(view.$(detailsView)).toExist();
|
|
view.$('.action-edit .edit').click();
|
|
expect(view.$(editView)).toExist();
|
|
expect(view.$(detailsView)).not.toExist();
|
|
view.$('.action-cancel').click();
|
|
expect(view.$(detailsView)).toExist();
|
|
expect(view.$(editView)).not.toExist();
|
|
};
|
|
var assertAndDeleteItemError = function (that, url, promptText) {
|
|
var requests = AjaxHelpers.requests(that),
|
|
promptSpy = ViewHelpers.createPromptSpy(),
|
|
notificationSpy = ViewHelpers.createNotificationSpy();
|
|
|
|
ViewHelpers.clickDeleteItem(that, promptSpy, promptText);
|
|
|
|
ViewHelpers.patchAndVerifyRequest(requests, url, notificationSpy);
|
|
|
|
AjaxHelpers.respondWithNoContent(requests);
|
|
ViewHelpers.verifyNotificationHidden(notificationSpy);
|
|
expect($(SELECTORS.itemView)).not.toExist();
|
|
};
|
|
var assertAndDeleteItemWithError = function (that, url, listItemView, promptText) {
|
|
var requests = AjaxHelpers.requests(that),
|
|
promptSpy = ViewHelpers.createPromptSpy(),
|
|
notificationSpy = ViewHelpers.createNotificationSpy();
|
|
|
|
ViewHelpers.clickDeleteItem(that, promptSpy, promptText);
|
|
ViewHelpers.patchAndVerifyRequest(requests, url, notificationSpy);
|
|
|
|
AjaxHelpers.respondWithError(requests);
|
|
ViewHelpers.verifyNotificationShowing(notificationSpy, /Deleting/);
|
|
expect($(listItemView)).toExist();
|
|
};
|
|
var assertCannotDeleteUsed = function (that, toolTipText, warningText){
|
|
setUsageInfo(that.model);
|
|
that.view.render();
|
|
expect(that.view.$(SELECTORS.note)).toHaveAttr(
|
|
'data-tooltip', toolTipText
|
|
);
|
|
expect(that.view.$(SELECTORS.warningMessage)).toContainText(warningText);
|
|
expect(that.view.$(SELECTORS.warningIcon)).toExist();
|
|
expect(that.view.$('.delete')).toHaveClass('is-disabled');
|
|
};
|
|
var assertUnusedOptions = function (that) {
|
|
that.model.set('usage', []);
|
|
that.view.render();
|
|
expect(that.view.$(SELECTORS.warningMessage)).not.toExist();
|
|
expect(that.view.$(SELECTORS.warningIcon)).not.toExist();
|
|
};
|
|
|
|
|
|
beforeEach(function() {
|
|
window.course = new Course({
|
|
id: '5',
|
|
name: 'Course Name',
|
|
url_name: 'course_name',
|
|
org: 'course_org',
|
|
num: 'course_num',
|
|
revision: 'course_rev'
|
|
});
|
|
|
|
this.addMatchers({
|
|
toContainText: function(text) {
|
|
var trimmedText = $.trim(this.actual.text());
|
|
|
|
if (text && $.isFunction(text.test)) {
|
|
return text.test(trimmedText);
|
|
} else {
|
|
return trimmedText.indexOf(text) !== -1;
|
|
}
|
|
},
|
|
toBeCorrectValuesInInputs: function (values) {
|
|
var expected = {
|
|
name: this.actual.$(SELECTORS.inputName).val(),
|
|
description: this.actual
|
|
.$(SELECTORS.inputDescription).val()
|
|
};
|
|
|
|
return _.isEqual(values, expected);
|
|
},
|
|
toBeCorrectValuesInModel: function (values) {
|
|
return _.every(values, function (value, key) {
|
|
return this.actual.get(key) === value;
|
|
}.bind(this));
|
|
},
|
|
toHaveDefaultNames: function (values) {
|
|
var actualValues = $.map(this.actual, function (item) {
|
|
return $(item).val();
|
|
});
|
|
|
|
return _.isEqual(actualValues, values);
|
|
}
|
|
});
|
|
});
|
|
|
|
afterEach(function() {
|
|
delete window.course;
|
|
});
|
|
|
|
describe('Experiment group configurations details view', function() {
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplate('group-configuration-details', true);
|
|
|
|
this.model = new GroupConfigurationModel({
|
|
name: 'Configuration',
|
|
description: 'Configuration Description',
|
|
id: 0
|
|
});
|
|
|
|
this.collection = new GroupConfigurationCollection([ this.model ]);
|
|
this.collection.outlineUrl = '/outline';
|
|
this.view = new GroupConfigurationDetailsView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should render properly', function() {
|
|
assertTheDetailsView(this.view, 'Configuration');
|
|
});
|
|
|
|
it('should show groups appropriately', function() {
|
|
this.model.get('groups').add([{}]);
|
|
this.model.set('showGroups', false);
|
|
this.view.$('.show-groups').click();
|
|
|
|
expect(this.model.get('showGroups')).toBeTruthy();
|
|
expect(this.view.$(SELECTORS.group).length).toBe(3);
|
|
expect(this.view.$(SELECTORS.groupsCount)).not.toExist();
|
|
expect(this.view.$(SELECTORS.description))
|
|
.toContainText('Configuration Description');
|
|
expect(this.view.$(SELECTORS.groupsAllocation))
|
|
.toContainText('33%');
|
|
});
|
|
|
|
it('should hide groups appropriately', function() {
|
|
this.model.get('groups').add([{}]);
|
|
this.model.set('showGroups', true);
|
|
this.view.$('.hide-groups').click();
|
|
|
|
expect(this.model.get('showGroups')).toBeFalsy();
|
|
expect(this.view.$(SELECTORS.group)).not.toExist();
|
|
expect(this.view.$(SELECTORS.groupsCount))
|
|
.toContainText('Contains 3 groups');
|
|
expect(this.view.$(SELECTORS.description)).not.toExist();
|
|
expect(this.view.$(SELECTORS.groupsAllocation)).not.toExist();
|
|
});
|
|
|
|
it('should show empty usage appropriately', function() {
|
|
this.model.set('showGroups', false);
|
|
this.view.$('.show-groups').click();
|
|
assertShowEmptyUsages(
|
|
this.view,
|
|
'This Group Configuration is not in use. ' +
|
|
'Start by adding a content experiment to any Unit via the'
|
|
);
|
|
});
|
|
|
|
it('should hide empty usage appropriately', function() {
|
|
this.model.set('showGroups', true);
|
|
this.view.$('.hide-groups').click();
|
|
assertHideEmptyUsages(this.view)
|
|
});
|
|
|
|
it('should show non-empty usage appropriately', function() {
|
|
setUsageInfo(this.model);
|
|
this.model.set('showGroups', false);
|
|
this.view.$('.show-groups').click();
|
|
|
|
assertShowNonEmptyUsages(
|
|
this.view,
|
|
'This Group Configuration is used in:',
|
|
'Cannot delete when in use by an experiment'
|
|
)
|
|
});
|
|
|
|
it('should hide non-empty usage appropriately', function() {
|
|
setUsageInfo(this.model);
|
|
this.model.set('showGroups', true);
|
|
this.view.$('.hide-groups').click();
|
|
|
|
expect(this.view.$(SELECTORS.note)).toHaveAttr(
|
|
'data-tooltip', 'Cannot delete when in use by an experiment'
|
|
);
|
|
assertHideNonEmptyUsages(this.view);
|
|
});
|
|
|
|
it('should show validation warning icon and message appropriately', function() {
|
|
this.model.set('usage', [
|
|
{
|
|
'label': 'label1',
|
|
'url': 'url1',
|
|
'validation': {
|
|
'text': "Warning message",
|
|
'type': 'warning'
|
|
}
|
|
}
|
|
]);
|
|
this.model.set('showGroups', false);
|
|
this.view.$('.show-groups').click();
|
|
|
|
expect(this.view.$(SELECTORS.usageUnitMessage)).toContainText('Warning message');
|
|
expect(this.view.$(SELECTORS.usageUnitWarningIcon)).toExist();
|
|
});
|
|
|
|
it('should show validation error icon and message appropriately', function() {
|
|
this.model.set('usage', [
|
|
{
|
|
'label': 'label1',
|
|
'url': 'url1',
|
|
'validation': {
|
|
'text': "Error message",
|
|
'type': 'error'
|
|
}
|
|
}
|
|
]);
|
|
this.model.set('showGroups', false);
|
|
this.view.$('.show-groups').click();
|
|
|
|
expect(this.view.$(SELECTORS.usageUnitMessage)).toContainText('Error message');
|
|
expect(this.view.$(SELECTORS.usageUnitErrorIcon)).toExist();
|
|
});
|
|
|
|
it('should hide validation icons and messages appropriately', function() {
|
|
setUsageInfo(this.model);
|
|
this.model.set('showGroups', true);
|
|
this.view.$('.hide-groups').click();
|
|
|
|
assertHideValidationContent(this.view);
|
|
});
|
|
});
|
|
|
|
describe('Experiment group configurations editor view', function() {
|
|
|
|
var setValuesToInputs = function (view, values) {
|
|
_.each(values, function (value, selector) {
|
|
if (SELECTORS[selector]) {
|
|
view.$(SELECTORS[selector]).val(value);
|
|
}
|
|
});
|
|
};
|
|
|
|
beforeEach(function() {
|
|
ViewHelpers.installViewTemplates();
|
|
TemplateHelpers.installTemplates([
|
|
'group-configuration-editor', 'group-edit'
|
|
]);
|
|
|
|
this.model = new GroupConfigurationModel({
|
|
name: 'Configuration',
|
|
description: 'Configuration Description',
|
|
id: 0,
|
|
editing: true
|
|
});
|
|
this.collection = new GroupConfigurationCollection([this.model]);
|
|
this.collection.url = '/group_configurations';
|
|
this.view = new GroupConfigurationEditorView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should render properly', function() {
|
|
expect(this.view).toBeCorrectValuesInInputs({
|
|
name: 'Configuration',
|
|
description: 'Configuration Description'
|
|
});
|
|
expect(this.view.$('.delete')).toExist();
|
|
});
|
|
|
|
it ('should allow you to create new groups', function() {
|
|
var numGroups = this.model.get('groups').length;
|
|
this.view.$('.action-add-group').click();
|
|
expect(this.model.get('groups').length).toEqual(numGroups + 1);
|
|
});
|
|
|
|
it('should save properly', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
notificationSpy = ViewHelpers.createNotificationSpy(),
|
|
groups;
|
|
|
|
this.view.$('.action-add-group').click();
|
|
setValuesToInputs(this.view, {
|
|
inputName: 'New Configuration',
|
|
inputDescription: 'New Description'
|
|
});
|
|
|
|
ViewHelpers.submitAndVerifyFormSuccess(this.view, requests, notificationSpy);
|
|
|
|
expect(this.model).toBeCorrectValuesInModel({
|
|
name: 'New Configuration',
|
|
description: 'New Description'
|
|
});
|
|
|
|
groups = this.model.get('groups');
|
|
expect(groups.length).toBe(3);
|
|
expect(groups.at(2).get('name')).toBe('Group C');
|
|
expect(this.view.$el).not.toExist();
|
|
});
|
|
|
|
it('does not hide saving message if failure', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
notificationSpy = ViewHelpers.createNotificationSpy();
|
|
|
|
setValuesToInputs(this.view, { inputName: 'New Configuration' });
|
|
ViewHelpers.submitAndVerifyFormError(this.view, requests, notificationSpy);
|
|
});
|
|
|
|
it('does not save on cancel', function() {
|
|
this.view.$('.action-add-group').click();
|
|
setValuesToInputs(this.view, {
|
|
inputName: 'New Configuration',
|
|
inputDescription: 'New Description'
|
|
});
|
|
|
|
expect(this.model.get('groups').length).toBe(3);
|
|
|
|
this.view.$('.action-cancel').click();
|
|
expect(this.model).toBeCorrectValuesInModel({
|
|
name: 'Configuration',
|
|
description: 'Configuration Description'
|
|
});
|
|
// Model is still exist in the collection
|
|
expect(this.collection.indexOf(this.model)).toBeGreaterThan(-1);
|
|
expect(this.collection.length).toBe(1);
|
|
expect(this.model.get('groups').length).toBe(2);
|
|
});
|
|
|
|
it('should be removed on cancel if it is a new item', function() {
|
|
spyOn(this.model, 'isNew').andReturn(true);
|
|
setValuesToInputs(this.view, {
|
|
inputName: 'New Configuration',
|
|
inputDescription: 'New Description'
|
|
});
|
|
this.view.$('.action-cancel').click();
|
|
// Model is removed from the collection
|
|
expect(this.collection.length).toBe(0);
|
|
});
|
|
|
|
it('should be possible to correct validation errors', function() {
|
|
var requests = AjaxHelpers.requests(this);
|
|
|
|
// Set incorrect value
|
|
setValuesToInputs(this.view, { inputName: '' });
|
|
// Try to save
|
|
this.view.$('form').submit();
|
|
// See error message
|
|
expect(this.view.$(SELECTORS.errorMessage)).toContainText(
|
|
'Group Configuration name is required'
|
|
);
|
|
// No request
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
// Set correct value
|
|
setValuesToInputs(this.view, { inputName: 'New Configuration' });
|
|
// Try to save
|
|
this.view.$('form').submit();
|
|
AjaxHelpers.respondWithJson(requests, {});
|
|
// Model is updated
|
|
expect(this.model).toBeCorrectValuesInModel({
|
|
name: 'New Configuration'
|
|
});
|
|
// Error message disappear
|
|
expect(this.view.$(SELECTORS.errorMessage)).not.toExist();
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
});
|
|
|
|
it('should have appropriate class names on focus/blur', function () {
|
|
var groupInput = this.view.$(SELECTORS.inputGroupName).first(),
|
|
groupFields = this.view.$(SELECTORS.groupFields);
|
|
|
|
groupInput.focus();
|
|
expect(groupFields).toHaveClass('is-focused');
|
|
groupInput.blur();
|
|
expect(groupFields).not.toHaveClass('is-focused');
|
|
});
|
|
|
|
describe('removes all newly created groups on cancel', function () {
|
|
it('if the model has a non-empty groups', function() {
|
|
var groups = this.model.get('groups');
|
|
|
|
this.view.render();
|
|
groups.add([{ name: 'non-empty' }]);
|
|
expect(groups.length).toEqual(3);
|
|
this.view.$('.action-cancel').click();
|
|
// Restore to default state (2 groups by default).
|
|
expect(groups.length).toEqual(2);
|
|
});
|
|
|
|
it('if the model has no non-empty groups', function() {
|
|
var groups = this.model.get('groups');
|
|
|
|
this.view.render();
|
|
groups.add([{}, {}, {}]);
|
|
expect(groups.length).toEqual(5);
|
|
this.view.$('.action-cancel').click();
|
|
// Restore to default state (2 groups by default).
|
|
expect(groups.length).toEqual(2);
|
|
});
|
|
});
|
|
|
|
it('groups have correct default names', function () {
|
|
var group1 = new GroupModel({ name: 'Group A' }),
|
|
group2 = new GroupModel({ name: 'Group B' }),
|
|
collection = this.model.get('groups');
|
|
|
|
collection.reset([group1, group2]); // Group A, Group B
|
|
this.view.$('.action-add-group').click(); // Add Group C
|
|
this.view.$('.action-add-group').click(); // Add Group D
|
|
this.view.$('.action-add-group').click(); // Add Group E
|
|
|
|
expect(this.view.$(SELECTORS.inputGroupName)).toHaveDefaultNames([
|
|
'Group A', 'Group B', 'Group C', 'Group D', 'Group E'
|
|
]);
|
|
|
|
// Remove Group B
|
|
this.view.$('.group-1 .action-close').click();
|
|
|
|
expect(this.view.$(SELECTORS.inputGroupName)).toHaveDefaultNames([
|
|
'Group A', 'Group C', 'Group D', 'Group E'
|
|
]);
|
|
|
|
this.view.$('.action-add-group').click(); // Add Group F
|
|
this.view.$('.action-add-group').click(); // Add Group G
|
|
|
|
expect(this.view.$(SELECTORS.inputGroupName)).toHaveDefaultNames([
|
|
'Group A', 'Group C', 'Group D', 'Group E', 'Group F', 'Group G'
|
|
]);
|
|
});
|
|
|
|
it('cannot be deleted if it is in use', function () {
|
|
assertCannotDeleteUsed(
|
|
this,
|
|
'Cannot delete when in use by an experiment',
|
|
'This configuration is currently used in content ' +
|
|
'experiments. If you make changes to the groups, you may ' +
|
|
'need to edit those experiments.'
|
|
);
|
|
});
|
|
|
|
it('does not contain warning message if it is not in use', function () {
|
|
assertUnusedOptions(this);
|
|
});
|
|
});
|
|
|
|
describe('Experiment group configurations list view', function() {
|
|
var emptyMessage = 'You have not created any group configurations yet.';
|
|
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplates(
|
|
['group-configuration-editor', 'group-edit', 'list']
|
|
);
|
|
|
|
this.model = new GroupConfigurationModel({ id: 0 });
|
|
this.collection = new GroupConfigurationCollection();
|
|
this.view = new GroupConfigurationsListView({
|
|
collection: this.collection
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
describe('empty template', function () {
|
|
it('should be rendered if no group configurations', function() {
|
|
expect(this.view.$el).toContainText(emptyMessage);
|
|
expect(this.view.$('.new-button')).toExist();
|
|
expect(this.view.$(SELECTORS.itemView)).not.toExist();
|
|
});
|
|
|
|
it('should disappear if group configuration is added', function() {
|
|
expect(this.view.$el).toContainText(emptyMessage);
|
|
expect(this.view.$(SELECTORS.itemView)).not.toExist();
|
|
this.collection.add(this.model);
|
|
expect(this.view.$el).not.toContainText(emptyMessage);
|
|
expect(this.view.$(SELECTORS.itemView)).toExist();
|
|
});
|
|
|
|
it('should appear if configurations were removed', function() {
|
|
this.collection.add(this.model);
|
|
expect(this.view.$(SELECTORS.itemView)).toExist();
|
|
this.collection.remove(this.model);
|
|
expect(this.view.$el).toContainText(emptyMessage);
|
|
expect(this.view.$(SELECTORS.itemView)).not.toExist();
|
|
});
|
|
|
|
it('can create a new group configuration', function () {
|
|
this.view.$('.new-button').click();
|
|
expect($('.group-configuration-edit').length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Experiment group configurations controller view', function() {
|
|
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplates([
|
|
'group-configuration-editor', 'group-configuration-details'
|
|
], true);
|
|
this.model = new GroupConfigurationModel({ id: 0 });
|
|
this.collection = new GroupConfigurationCollection([ this.model ]);
|
|
this.collection.url = '/group_configurations';
|
|
this.view = new GroupConfigurationItemView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should render properly', function() {
|
|
assertControllerView(this.view, SELECTORS.detailsView, SELECTORS.editView);
|
|
});
|
|
|
|
it('should destroy itself on confirmation of deleting', function () {
|
|
assertAndDeleteItemError(this, '/group_configurations/0', 'Delete this group configuration?');
|
|
});
|
|
|
|
it('does not hide deleting message if failure', function() {
|
|
assertAndDeleteItemWithError(
|
|
this,
|
|
'/group_configurations/0',
|
|
SELECTORS.itemView,
|
|
'Delete this group configuration?'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Experiment group configurations group editor view', function() {
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplate('group-edit', true);
|
|
|
|
this.model = new GroupModel({
|
|
name: 'Group A'
|
|
});
|
|
|
|
this.collection = new GroupCollection([this.model]);
|
|
|
|
this.view = new ExperimentGroupEditView({
|
|
model: this.model
|
|
});
|
|
});
|
|
|
|
describe('Basic', function () {
|
|
it('can render properly', function() {
|
|
this.view.render();
|
|
expect(this.view.$('.group-name').val()).toBe('Group A');
|
|
expect(this.view.$('.group-allocation')).toContainText('100%');
|
|
});
|
|
|
|
it ('can delete itself', function() {
|
|
this.view.render().$('.action-close').click();
|
|
expect(this.collection.length).toEqual(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Content groups list view', function() {
|
|
var newGroupCss = '.new-button',
|
|
addGroupCss = '.action-add',
|
|
inputCss = '.collection-name-input',
|
|
saveButtonCss = '.action-primary',
|
|
cancelButtonCss = '.action-cancel',
|
|
validationErrorCss = '.content-group-edit-error',
|
|
scopedGroupSelector, createGroups, renderView, saveOrCancel, editNewGroup, editExistingGroup,
|
|
verifyEditingGroup, respondToSave, expectGroupsVisible, correctValidationError;
|
|
|
|
scopedGroupSelector = function(groupIndex, additionalSelectors) {
|
|
var groupSelector = '.content-groups-list-item-' + groupIndex;
|
|
if (additionalSelectors) {
|
|
return groupSelector + ' ' + additionalSelectors;
|
|
} else {
|
|
return groupSelector;
|
|
}
|
|
};
|
|
|
|
createGroups = function (groupNamesWithId) {
|
|
var groups = new GroupCollection(_.map(groupNamesWithId, function (groupName, id) {
|
|
return {id: id, name: groupName};
|
|
})),
|
|
groupConfiguration = new GroupConfigurationModel({
|
|
id: 0,
|
|
name: 'Content Group Configuration',
|
|
groups: groups
|
|
}, {canBeEmpty: true});
|
|
groupConfiguration.urlRoot = '/mock_url';
|
|
groupConfiguration.outlineUrl = '/mock_url';
|
|
return groups;
|
|
};
|
|
|
|
renderView = function(groupNamesWithId) {
|
|
var view = new GroupList({collection: createGroups(groupNamesWithId || {})}).render();
|
|
appendSetFixtures(view.el);
|
|
return view;
|
|
};
|
|
|
|
saveOrCancel = function(view, options, groupIndex) {
|
|
if (options.save) {
|
|
view.$(scopedGroupSelector(groupIndex, saveButtonCss)).click();
|
|
} else if (options.cancel) {
|
|
view.$(scopedGroupSelector(groupIndex, cancelButtonCss)).click();
|
|
}
|
|
};
|
|
|
|
editNewGroup = function(view, options) {
|
|
var newGroupIndex;
|
|
if (view.collection.length === 0) {
|
|
view.$(newGroupCss).click();
|
|
} else {
|
|
view.$(addGroupCss).click();
|
|
}
|
|
newGroupIndex = view.collection.length - 1;
|
|
view.$(inputCss).val(options.newName);
|
|
verifyEditingGroup(view, true, newGroupIndex);
|
|
saveOrCancel(view, options, newGroupIndex);
|
|
};
|
|
|
|
editExistingGroup = function(view, options) {
|
|
var groupIndex = options.groupIndex || 0;
|
|
view.$(scopedGroupSelector(groupIndex, '.edit')).click();
|
|
view.$(scopedGroupSelector(groupIndex, inputCss)).val(options.newName);
|
|
saveOrCancel(view, options, groupIndex);
|
|
};
|
|
|
|
verifyEditingGroup = function(view, expectEditing, index) {
|
|
// Should prevent the user from opening more than one edit
|
|
// form at a time by removing the add button(s) when
|
|
// editing a group.
|
|
index = index || 0;
|
|
if (expectEditing) {
|
|
expect(view.$(scopedGroupSelector(index, '.content-group-edit'))).toExist();
|
|
expect(view.$(newGroupCss)).not.toExist();
|
|
expect(view.$(addGroupCss)).toHaveClass('is-hidden');
|
|
} else {
|
|
expect(view.$('.content-group-edit')).not.toExist();
|
|
if (view.collection.length === 0) {
|
|
expect(view.$(newGroupCss)).toExist();
|
|
expect(view.$(addGroupCss)).not.toExist();
|
|
} else {
|
|
expect(view.$(newGroupCss)).not.toExist();
|
|
expect(view.$(addGroupCss)).not.toHaveClass('is-hidden');
|
|
}
|
|
}
|
|
};
|
|
|
|
respondToSave = function(requests, view) {
|
|
var request = AjaxHelpers.currentRequest(requests);
|
|
expect(request.method).toBe('POST');
|
|
expect(request.url).toBe('/mock_url/0');
|
|
AjaxHelpers.respondWithJson(requests, {
|
|
name: 'Content Group Configuration',
|
|
groups: view.collection.map(function(groupModel, index) {
|
|
return _.extend(groupModel.toJSON(), {id: index});
|
|
})
|
|
});
|
|
};
|
|
|
|
correctValidationError = function(view, requests, newGroupName) {
|
|
expect(view.$(validationErrorCss)).toExist();
|
|
verifyEditingGroup(view, true);
|
|
view.$(inputCss).val(newGroupName);
|
|
view.$(saveButtonCss).click();
|
|
respondToSave(requests, view);
|
|
expect(view.$(validationErrorCss)).not.toExist();
|
|
};
|
|
|
|
expectGroupsVisible = function(view, groupNames) {
|
|
_.each(groupNames, function(groupName) {
|
|
expect(view.$('.content-groups-list-item')).toContainText(groupName);
|
|
});
|
|
};
|
|
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplates(
|
|
['content-group-editor', 'content-group-details', 'list']
|
|
);
|
|
});
|
|
|
|
it('shows a message when no groups are present', function() {
|
|
expect(renderView().$('.no-content'))
|
|
.toContainText('You have not created any content groups yet.');
|
|
});
|
|
|
|
it('can render groups', function() {
|
|
var groupNames = ['Group 1', 'Group 2', 'Group 3'];
|
|
renderView(groupNames).$('.content-group-details').each(function(index) {
|
|
expect($(this)).toContainText(groupNames[index]);
|
|
});
|
|
});
|
|
|
|
it('can create an initial group and save', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
newGroupName = 'New Group Name',
|
|
view = renderView();
|
|
editNewGroup(view, {newName: newGroupName, save: true});
|
|
respondToSave(requests, view);
|
|
verifyEditingGroup(view, false);
|
|
expectGroupsVisible(view, [newGroupName]);
|
|
});
|
|
|
|
it('can add another group and save', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
oldGroupName = 'Old Group Name',
|
|
newGroupName = 'New Group Name',
|
|
view = renderView({1: oldGroupName});
|
|
editNewGroup(view, {newName: newGroupName, save: true});
|
|
respondToSave(requests, view);
|
|
verifyEditingGroup(view, false, 1);
|
|
expectGroupsVisible(view, [oldGroupName, newGroupName]);
|
|
});
|
|
|
|
it('can cancel adding a group', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
newGroupName = 'New Group Name',
|
|
view = renderView();
|
|
editNewGroup(view, {newName: newGroupName, cancel: true});
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
verifyEditingGroup(view, false);
|
|
expect(view.$()).not.toContainText(newGroupName);
|
|
});
|
|
|
|
it('can cancel editing a group', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
originalGroupName = 'Original Group Name',
|
|
view = renderView([originalGroupName]);
|
|
editExistingGroup(view, {newName: 'New Group Name', cancel: true});
|
|
verifyEditingGroup(view, false);
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
expect(view.collection.at(0).get('name')).toBe(originalGroupName);
|
|
});
|
|
|
|
it('can show and correct a validation error', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
newGroupName = 'New Group Name',
|
|
view = renderView();
|
|
editNewGroup(view, {newName: '', save: true});
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
correctValidationError(view, requests, newGroupName);
|
|
});
|
|
|
|
it('can not invalidate an existing content group', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
oldGroupName = 'Old Group Name',
|
|
view = renderView([oldGroupName]);
|
|
editExistingGroup(view, {newName: '', save: true});
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
correctValidationError(view, requests, oldGroupName);
|
|
});
|
|
|
|
it('trims whitespace', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
newGroupName = 'New Group Name',
|
|
view = renderView();
|
|
editNewGroup(view, {newName: ' ' + newGroupName + ' ', save: true});
|
|
respondToSave(requests, view);
|
|
expect(view.collection.at(0).get('name')).toBe(newGroupName);
|
|
});
|
|
|
|
it('only edits one form at a time', function() {
|
|
var view = renderView();
|
|
view.collection.add({name: 'Editing Group', editing: true});
|
|
verifyEditingGroup(view, true);
|
|
});
|
|
|
|
});
|
|
|
|
describe('Content groups details view', function() {
|
|
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplate('content-group-details', true);
|
|
this.model = new GroupModel({name: 'Content Group', id: 0});
|
|
|
|
var saveableModel = new GroupConfigurationModel({
|
|
name: 'Content Group Configuration',
|
|
id: 0,
|
|
scheme:'cohort',
|
|
groups: new GroupCollection([this.model]),
|
|
}, {canBeEmpty: true});
|
|
|
|
saveableModel.urlRoot = '/mock_url';
|
|
|
|
this.collection = new GroupConfigurationCollection([ saveableModel ]);
|
|
this.collection.outlineUrl = '/outline';
|
|
|
|
this.view = new ContentGroupDetailsView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should render properly', function() {
|
|
assertTheDetailsView(this.view, 'Content Group');
|
|
});
|
|
|
|
it('should show empty usage appropriately', function() {
|
|
this.view.$('.show-groups').click();
|
|
assertShowEmptyUsages(this.view, 'This content group is not in use. ');
|
|
});
|
|
|
|
it('should hide empty usage appropriately', function() {
|
|
this.view.$('.hide-groups').click();
|
|
assertHideEmptyUsages(this.view)
|
|
});
|
|
|
|
it('should show non-empty usage appropriately', function() {
|
|
setUsageInfo(this.model);
|
|
this.view.$('.show-groups').click();
|
|
|
|
assertShowNonEmptyUsages(
|
|
this.view,
|
|
'This content group is used in:',
|
|
'Cannot delete when in use by a unit'
|
|
)
|
|
});
|
|
|
|
it('should hide non-empty usage appropriately', function() {
|
|
setUsageInfo(this.model);
|
|
this.view.$('.hide-groups').click();
|
|
|
|
expect(this.view.$('li.action-delete')).toHaveAttr(
|
|
'data-tooltip', 'Cannot delete when in use by a unit'
|
|
);
|
|
assertHideNonEmptyUsages(this.view);
|
|
});
|
|
|
|
it('should hide validation icons and messages appropriately', function() {
|
|
setUsageInfo(this.model);
|
|
this.view.$('.hide-groups').click();
|
|
assertHideValidationContent(this.view);
|
|
});
|
|
});
|
|
|
|
describe('Content groups editor view', function() {
|
|
|
|
beforeEach(function() {
|
|
ViewHelpers.installViewTemplates();
|
|
TemplateHelpers.installTemplates(['content-group-editor']);
|
|
|
|
this.model = new GroupModel({name: 'Content Group', id: 0});
|
|
|
|
this.saveableModel = new GroupConfigurationModel({
|
|
name: 'Content Group Configuration',
|
|
id: 0,
|
|
scheme:'cohort',
|
|
groups: new GroupCollection([this.model]),
|
|
editing:true
|
|
});
|
|
|
|
this.collection = new GroupConfigurationCollection([ this.saveableModel ]);
|
|
this.collection.outlineUrl = '/outline';
|
|
this.collection.url = '/group_configurations';
|
|
|
|
this.view = new ContentGroupEditorView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should save properly', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
notificationSpy = ViewHelpers.createNotificationSpy();
|
|
|
|
this.view.$('.action-add').click();
|
|
this.view.$(SELECTORS.inputName).val('New Content Group');
|
|
|
|
ViewHelpers.submitAndVerifyFormSuccess(this.view, requests, notificationSpy);
|
|
|
|
expect(this.model).toBeCorrectValuesInModel({
|
|
name: 'New Content Group'
|
|
});
|
|
expect(this.view.$el).not.toExist();
|
|
});
|
|
|
|
it('does not hide saving message if failure', function() {
|
|
var requests = AjaxHelpers.requests(this),
|
|
notificationSpy = ViewHelpers.createNotificationSpy();
|
|
this.view.$(SELECTORS.inputName).val('New Content Group')
|
|
|
|
ViewHelpers.submitAndVerifyFormError(this.view, requests, notificationSpy)
|
|
});
|
|
|
|
it('does not save on cancel', function() {
|
|
expect(this.view.$('.action-add'));
|
|
this.view.$('.action-add').click();
|
|
this.view.$(SELECTORS.inputName).val('New Content Group');
|
|
|
|
this.view.$('.action-cancel').click();
|
|
expect(this.model).toBeCorrectValuesInModel({
|
|
name: 'Content Group',
|
|
});
|
|
// Model is still exist in the collection
|
|
expect(this.collection.indexOf(this.saveableModel)).toBeGreaterThan(-1);
|
|
expect(this.collection.length).toBe(1);
|
|
});
|
|
|
|
it('cannot be deleted if it is in use', function () {
|
|
assertCannotDeleteUsed(
|
|
this,
|
|
'Cannot delete when in use by a unit',
|
|
'This content group is used in one or more units.'
|
|
);
|
|
});
|
|
|
|
it('does not contain warning message if it is not in use', function () {
|
|
assertUnusedOptions(this);
|
|
});
|
|
});
|
|
|
|
describe('Content group controller view', function() {
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplates([
|
|
'content-group-editor', 'content-group-details'
|
|
], true);
|
|
|
|
this.model = new GroupModel({name: 'Content Group', id: 0});
|
|
|
|
this.saveableModel = new GroupConfigurationModel({
|
|
name: 'Content Group Configuration',
|
|
id: 0,
|
|
scheme:'cohort',
|
|
groups: new GroupCollection([this.model])
|
|
});
|
|
this.saveableModel.urlRoot = '/group_configurations';
|
|
this.collection = new GroupConfigurationCollection([ this.saveableModel ]);
|
|
this.collection.url = '/group_configurations';
|
|
this.view = new ContentGroupItemView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should render properly', function() {
|
|
assertControllerView(this.view, '.content-group-details', '.content-group-edit');
|
|
});
|
|
|
|
it('should destroy itself on confirmation of deleting', function () {
|
|
assertAndDeleteItemError(this, '/group_configurations/0/0', 'Delete this content group');
|
|
});
|
|
|
|
it('does not hide deleting message if failure', function() {
|
|
assertAndDeleteItemWithError(
|
|
this,
|
|
'/group_configurations/0/0',
|
|
'.content-groups-list-item',
|
|
'Delete this content group'
|
|
);
|
|
});
|
|
});
|
|
});
|