1056 lines
42 KiB
JavaScript
1056 lines
42 KiB
JavaScript
define([
|
|
'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/js/spec_helpers/template_helpers',
|
|
'common/js/spec_helpers/view_helpers', '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/partition_group_list', 'js/views/partition_group_details',
|
|
'js/views/content_group_editor', 'js/views/partition_group_item'
|
|
], function(
|
|
_, AjaxHelpers, TemplateHelpers, ViewHelpers, Course, GroupConfigurationModel, GroupModel,
|
|
GroupConfigurationCollection, GroupCollection, GroupConfigurationDetailsView, GroupConfigurationsListView,
|
|
GroupConfigurationEditorView, GroupConfigurationItemView, ExperimentGroupEditView, GroupList,
|
|
PartitionGroupDetailsView, ContentGroupEditorView, PartitionGroupItemView
|
|
) {
|
|
'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 .fa-warning',
|
|
usageUnitErrorIcon: '.group-configuration-usage-unit .fa-exclamation-circle',
|
|
warningMessage: '.group-configuration-validation-text',
|
|
warningIcon: '.wrapper-group-configuration-validation > .fa-warning',
|
|
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 locations');
|
|
};
|
|
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'
|
|
});
|
|
|
|
jasmine.addMatchers({
|
|
toContainText: function() {
|
|
return {
|
|
compare: function(actual, text) {
|
|
var trimmedText = $.trim(actual.text()),
|
|
passed;
|
|
|
|
if (text && $.isFunction(text.test)) {
|
|
passed = text.test(trimmedText);
|
|
} else {
|
|
passed = trimmedText.indexOf(text) !== -1;
|
|
}
|
|
|
|
return {
|
|
pass: passed
|
|
};
|
|
}
|
|
};
|
|
},
|
|
toBeCorrectValuesInInputs: function() {
|
|
return {
|
|
compare: function(actual, values) {
|
|
var expected = {
|
|
name: actual.$(SELECTORS.inputName).val(),
|
|
description: actual
|
|
.$(SELECTORS.inputDescription).val()
|
|
};
|
|
|
|
var passed = _.isEqual(values, expected);
|
|
|
|
return {
|
|
pass: passed
|
|
};
|
|
}
|
|
};
|
|
},
|
|
toBeCorrectValuesInModel: function() {
|
|
return {
|
|
compare: function(actual, values) {
|
|
var passed = _.every(values, function(value, key) {
|
|
return actual.get(key) === value;
|
|
});
|
|
|
|
return {
|
|
pass: passed
|
|
};
|
|
}
|
|
};
|
|
},
|
|
toHaveDefaultNames: function() {
|
|
return {
|
|
compare: function(actual, values) {
|
|
var actualValues = $.map(actual, function(item) {
|
|
return $(item).val();
|
|
});
|
|
|
|
var passed = _.isEqual(actualValues, values);
|
|
|
|
return {
|
|
pass: passed
|
|
};
|
|
}
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
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.toBeInDOM();
|
|
});
|
|
|
|
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').and.returnValue(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.toBeInDOM();
|
|
AjaxHelpers.expectNoRequests(requests);
|
|
});
|
|
|
|
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 = '.partition-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.$('.partition-groups-list-item')).toContainText(groupName);
|
|
});
|
|
};
|
|
|
|
beforeEach(function() {
|
|
TemplateHelpers.installTemplates(
|
|
['content-group-editor', 'partition-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).$('.partition-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('partition-group-details', true);
|
|
this.model = new GroupModel({name: 'Content Group', id: 0, courseOutlineUrl: 'CourseOutlineUrl'});
|
|
|
|
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 PartitionGroupDetailsView({
|
|
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, 'use this group to control access to a component');
|
|
});
|
|
|
|
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 group controls access to:',
|
|
'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.toBeInDOM();
|
|
});
|
|
|
|
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', 'partition-group-details'
|
|
], true);
|
|
|
|
this.model = new GroupModel({name: 'Content Group', id: 0, courseOutlineUrl: 'CourseOutlineUrl'});
|
|
|
|
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 PartitionGroupItemView({
|
|
model: this.model
|
|
});
|
|
appendSetFixtures(this.view.render().el);
|
|
});
|
|
|
|
it('should render properly', function() {
|
|
assertControllerView(this.view, '.partition-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',
|
|
'.partition-groups-list-item',
|
|
'Delete this content group'
|
|
);
|
|
});
|
|
});
|
|
});
|