Jasmine test for views.
This commit is contained in:
1
cms/static/coffee/fixtures/metadata-editor.underscore
Symbolic link
1
cms/static/coffee/fixtures/metadata-editor.underscore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../templates/js/metadata-editor.underscore
|
||||
1
cms/static/coffee/fixtures/metadata-number-entry.underscore
Symbolic link
1
cms/static/coffee/fixtures/metadata-number-entry.underscore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../templates/js/metadata-number-entry.underscore
|
||||
1
cms/static/coffee/fixtures/metadata-option-entry.underscore
Symbolic link
1
cms/static/coffee/fixtures/metadata-option-entry.underscore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../templates/js/metadata-option-entry.underscore
|
||||
1
cms/static/coffee/fixtures/metadata-string-entry.underscore
Symbolic link
1
cms/static/coffee/fixtures/metadata-string-entry.underscore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../templates/js/metadata-string-entry.underscore
|
||||
@@ -1,35 +1,298 @@
|
||||
beforeEach ->
|
||||
# TODO: update these
|
||||
setFixtures(sandbox({id: "page-alert"}))
|
||||
appendSetFixtures(sandbox({id: "page-notification"}))
|
||||
editorTemplate = readFixtures('metadata-editor.underscore')
|
||||
numberEntryTemplate = readFixtures('metadata-number-entry.underscore')
|
||||
stringEntryTemplate = readFixtures('metadata-string-entry.underscore')
|
||||
optionEntryTemplate = readFixtures('metadata-option-entry.underscore')
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "metadata-editor-tpl", type: "text/template"}).text(editorTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-number-entry", type: "text/template"}).text(numberEntryTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-option-entry", type: "text/template"}).text(optionEntryTemplate))
|
||||
|
||||
genericEntry = {
|
||||
default_value: 'default value',
|
||||
display_name: "Display Name",
|
||||
explicitly_set: true,
|
||||
field_name: "display_name",
|
||||
help: "Specifies the name for this component.",
|
||||
inheritable: false,
|
||||
options: [],
|
||||
type: CMS.Models.Metadata.GENERIC_TYPE,
|
||||
value: "Word cloud"
|
||||
}
|
||||
|
||||
selectEntry = {
|
||||
default_value: "answered",
|
||||
display_name: "Show Answer",
|
||||
explicitly_set: false,
|
||||
field_name: "show_answer",
|
||||
help: "When should you show the answer",
|
||||
inheritable: true,
|
||||
options: [
|
||||
{"display_name": "Always", "value": "always"},
|
||||
{"display_name": "Answered", "value": "answered"},
|
||||
{"display_name": "Never", "value": "never"}
|
||||
],
|
||||
type: CMS.Models.Metadata.SELECT_TYPE,
|
||||
value: "always"
|
||||
}
|
||||
|
||||
integerEntry = {
|
||||
default_value: 5,
|
||||
display_name: "Inputs",
|
||||
explicitly_set: false,
|
||||
field_name: "num_inputs",
|
||||
help: "Number of text boxes for student to input words/sentences.",
|
||||
inheritable: false,
|
||||
options: {min: 1},
|
||||
type: CMS.Models.Metadata.INTEGER_TYPE,
|
||||
value: 5
|
||||
}
|
||||
|
||||
floatEntry = {
|
||||
default_value: 2.7,
|
||||
display_name: "Weight",
|
||||
explicitly_set: true,
|
||||
field_name: "weight",
|
||||
help: "Weight for this problem",
|
||||
inheritable: true,
|
||||
options: {min: 1.3, max:100.2, step:0.1},
|
||||
type: CMS.Models.Metadata.FLOAT_TYPE,
|
||||
value: 10.2
|
||||
}
|
||||
|
||||
# Test for the editor that creates the individual views.
|
||||
describe "CMS.Views.Metadata.Editor creates editors for each field", ->
|
||||
beforeEach ->
|
||||
@model = new Backbone.Model({
|
||||
display_name: {
|
||||
default_value: null,
|
||||
display_name: "Display Name",
|
||||
explicitly_set: true,
|
||||
field_name: "display_name",
|
||||
help: "Specifies the name for this component. The name appears as a tooltip in the course ribbon at the top of the page.",
|
||||
inheritable: false,
|
||||
options: [],
|
||||
type: "Generic",
|
||||
value: "Word cloud"
|
||||
},
|
||||
num_inputs: {
|
||||
default_value: 5,
|
||||
display_name: "Inputs",
|
||||
explicitly_set: false,
|
||||
field_name: "num_inputs",
|
||||
help: "Number of text boxes for student to input words/sentences.",
|
||||
inheritable: false,
|
||||
options: {min: 1},
|
||||
type: "Integer",
|
||||
value: 5
|
||||
num_inputs: integerEntry,
|
||||
weight: floatEntry,
|
||||
show_answer: selectEntry,
|
||||
display_name: genericEntry,
|
||||
unknown_type: {
|
||||
default_value: null,
|
||||
display_name: "Unknown",
|
||||
explicitly_set: true,
|
||||
field_name: "unknown_type",
|
||||
help: "Mystery property.",
|
||||
inheritable: false,
|
||||
options: [
|
||||
{"display_name": "Always", "value": "always"},
|
||||
{"display_name": "Answered", "value": "answered"},
|
||||
{"display_name": "Never", "value": "never"}],
|
||||
type: "unknown type",
|
||||
value: null
|
||||
}
|
||||
})
|
||||
|
||||
it "renders on initalize", ->
|
||||
it "creates child views on initialize, and sorts them alphabetically", ->
|
||||
view = new CMS.Views.Metadata.Editor({model: @model})
|
||||
expect(view.models).toBeDefined()
|
||||
childModels = view.models
|
||||
expect(childModels.length).toBe(5)
|
||||
childViews = view.$el.find('.setting-input')
|
||||
expect(childViews.length).toBe(5)
|
||||
|
||||
verifyEntry = (index, display_name, type) ->
|
||||
expect(childModels[index].get('display_name')).toBe(display_name)
|
||||
expect(childViews[index].type).toBe(type)
|
||||
|
||||
verifyEntry(0, 'Display Name', 'text')
|
||||
verifyEntry(1, 'Inputs', 'number')
|
||||
verifyEntry(2, 'Show Answer', 'select-one')
|
||||
verifyEntry(3, 'Unknown', 'text')
|
||||
verifyEntry(4, 'Weight', 'number')
|
||||
|
||||
it "returns its display name", ->
|
||||
view = new CMS.Views.Metadata.Editor({model: @model})
|
||||
expect(view.getDisplayName()).toBe("Word cloud")
|
||||
|
||||
it "returns an empty string if there is no display name property with a valid value", ->
|
||||
view = new CMS.Views.Metadata.Editor({model: new Backbone.Model()})
|
||||
expect(view.getDisplayName()).toBe("")
|
||||
|
||||
view = new CMS.Views.Metadata.Editor({model: new Backbone.Model({
|
||||
display_name:
|
||||
{
|
||||
default_value: null,
|
||||
display_name: "Display Name",
|
||||
explicitly_set: false,
|
||||
field_name: "display_name",
|
||||
help: "",
|
||||
inheritable: false,
|
||||
options: [],
|
||||
type: CMS.Models.Metadata.GENERIC_TYPE,
|
||||
value: null
|
||||
}
|
||||
})
|
||||
})
|
||||
expect(view.getDisplayName()).toBe("")
|
||||
|
||||
it "has no modified values by default", ->
|
||||
view = new CMS.Views.Metadata.Editor({model: @model})
|
||||
expect(view.getModifiedMetadataValues()).toEqual({})
|
||||
|
||||
it "returns modified values only", ->
|
||||
view = new CMS.Views.Metadata.Editor({model: @model})
|
||||
childModels = view.models
|
||||
childModels[0].setValue('updated display name')
|
||||
childModels[1].setValue(20)
|
||||
expect(view.getModifiedMetadataValues()).toEqual({
|
||||
display_name : 'updated display name',
|
||||
num_inputs: 20
|
||||
})
|
||||
|
||||
# Tests for individual views.
|
||||
assertInputType = (view, expectedType) ->
|
||||
input = view.$el.find('.setting-input')
|
||||
expect(input.length).toBe(1)
|
||||
expect(input[0].type).toBe(expectedType)
|
||||
|
||||
assertValueInView = (view, expectedValue) ->
|
||||
expect(view.getValueFromEditor()).toBe(expectedValue)
|
||||
|
||||
assertCanUpdateView = (view, newValue) ->
|
||||
view.setValueInEditor(newValue)
|
||||
expect(view.getValueFromEditor()).toBe(newValue)
|
||||
|
||||
assertClear = (view, modelValue, editorValue=modelValue) ->
|
||||
view.clear()
|
||||
expect(view.model.getValue()).toBe(null)
|
||||
expect(view.model.getDisplayValue()).toBe(modelValue)
|
||||
expect(view.getValueFromEditor()).toBe(editorValue)
|
||||
|
||||
assertUpdateModel = (view, originalValue, newValue) ->
|
||||
view.setValueInEditor(newValue)
|
||||
expect(view.model.getValue()).toBe(originalValue)
|
||||
view.updateModel()
|
||||
expect(view.model.getValue()).toBe(newValue)
|
||||
|
||||
describe "CMS.Views.Metadata.String is a basic string input with clear functionality", ->
|
||||
beforeEach ->
|
||||
model = new CMS.Models.Metadata(genericEntry)
|
||||
@view = new CMS.Views.Metadata.String({model: model})
|
||||
|
||||
it "uses a text input type", ->
|
||||
assertInputType(@view, 'text')
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@view, 'Word cloud')
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@view, "updated")
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@view, 'default value')
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@view, 'Word cloud', 'updated')
|
||||
|
||||
describe "CMS.Views.Metadata.Option is an option input type with clear functionality", ->
|
||||
beforeEach ->
|
||||
model = new CMS.Models.Metadata(selectEntry)
|
||||
@view = new CMS.Views.Metadata.Option({model: model})
|
||||
|
||||
it "uses a select input type", ->
|
||||
assertInputType(@view, 'select-one')
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@view, 'always')
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@view, "never")
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@view, 'answered')
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@view, null, 'never')
|
||||
|
||||
it "does not update to a value that is not an option", ->
|
||||
@view.setValueInEditor("not an option")
|
||||
expect(@view.getValueFromEditor()).toBe('always')
|
||||
|
||||
describe "CMS.Views.Metadata.Number supports integer or float type and has clear functionality", ->
|
||||
beforeEach ->
|
||||
integerModel = new CMS.Models.Metadata(integerEntry)
|
||||
@integerView = new CMS.Views.Metadata.Number({model: integerModel})
|
||||
|
||||
floatModel = new CMS.Models.Metadata(floatEntry)
|
||||
@floatView = new CMS.Views.Metadata.Number({model: floatModel})
|
||||
|
||||
it "uses a number input type", ->
|
||||
assertInputType(@integerView, 'number')
|
||||
assertInputType(@floatView, 'number')
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@integerView, '5')
|
||||
assertValueInView(@floatView, '10.2')
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@integerView, "12")
|
||||
assertCanUpdateView(@floatView, "-2.4")
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@integerView, 5, '5')
|
||||
assertClear(@floatView, 2.7, '2.7')
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@integerView, null, '90')
|
||||
assertUpdateModel(@floatView, 10.2, '-9.5')
|
||||
|
||||
it "knows the difference between integer and float", ->
|
||||
expect(@integerView.isIntegerField()).toBeTruthy()
|
||||
expect(@floatView.isIntegerField()).toBeFalsy()
|
||||
|
||||
it "sets attribtues related to min, max, and step", ->
|
||||
verifyAttributes = (view, min, step, max=null) ->
|
||||
inputEntry = view.$el.find('input')
|
||||
expect(Number(inputEntry.attr('min'))).toEqual(min)
|
||||
expect(Number(inputEntry.attr('step'))).toEqual(step)
|
||||
if max is not null
|
||||
expect(Number(inputEntry.attr('max'))).toEqual(max)
|
||||
|
||||
verifyAttributes(@integerView, 1, 1)
|
||||
verifyAttributes(@floatView, 1.3, .1, 100.2)
|
||||
|
||||
it "corrects values that are out of range", ->
|
||||
verifyValueAfterChanged = (view, value, expectedResult) ->
|
||||
view.setValueInEditor(value)
|
||||
view.changed()
|
||||
expect(view.getValueFromEditor()).toBe(expectedResult)
|
||||
|
||||
verifyValueAfterChanged(@integerView, '-4', '1')
|
||||
verifyValueAfterChanged(@integerView, '1', '1')
|
||||
verifyValueAfterChanged(@integerView, '0', '1')
|
||||
verifyValueAfterChanged(@integerView, '3001', '3001')
|
||||
|
||||
verifyValueAfterChanged(@floatView, '-4', '1.3')
|
||||
verifyValueAfterChanged(@floatView, '1.3', '1.3')
|
||||
verifyValueAfterChanged(@floatView, '1.2', '1.3')
|
||||
verifyValueAfterChanged(@floatView, '100.2', '100.2')
|
||||
verifyValueAfterChanged(@floatView, '100.3', '100.2')
|
||||
|
||||
it "disallows invalid characters", ->
|
||||
verifyValueAfterKeyPressed = (view, character, reject) ->
|
||||
event = {
|
||||
type : 'keypress',
|
||||
which : character.charCodeAt(0),
|
||||
keyCode: character.charCodeAt(0),
|
||||
preventDefault : () -> 'no op'
|
||||
}
|
||||
spyOn(event, 'preventDefault')
|
||||
view.$el.find('input').trigger(event)
|
||||
if (reject)
|
||||
expect(event.preventDefault).toHaveBeenCalled()
|
||||
else
|
||||
expect(event.preventDefault).not.toHaveBeenCalled()
|
||||
|
||||
verifyDisallowedChars = (view) ->
|
||||
verifyValueAfterKeyPressed(view, 'a', true)
|
||||
verifyValueAfterKeyPressed(view, '.', view.isIntegerField())
|
||||
verifyValueAfterKeyPressed(view, '[', true)
|
||||
verifyValueAfterKeyPressed(view, '@', true)
|
||||
|
||||
for i in [0...9]
|
||||
verifyValueAfterKeyPressed(view, String(i), false)
|
||||
|
||||
verifyDisallowedChars(@integerView)
|
||||
verifyDisallowedChars(@floatView)
|
||||
|
||||
@@ -3,7 +3,6 @@ if (!CMS.Views['Metadata']) CMS.Views.Metadata = {};
|
||||
CMS.Views.Metadata.Editor = Backbone.View.extend({
|
||||
|
||||
// Model is simply a Backbone.Model instance.
|
||||
|
||||
initialize : function() {
|
||||
var tpl = $("#metadata-editor-tpl").text();
|
||||
if(!tpl) {
|
||||
@@ -45,6 +44,9 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the just the modified metadata values, in the format used to persist to the server.
|
||||
*/
|
||||
getModifiedMetadataValues: function () {
|
||||
var modified_values = {};
|
||||
_.each(this.models,
|
||||
@@ -57,8 +59,16 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
|
||||
return modified_values;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a display name for the component related to this metadata. This method looks to see
|
||||
* if there is a metadata entry called 'display_name', and if so, it returns its value. If there
|
||||
* is no such entry, or if display_name does not have a value set, it returns an empty string.
|
||||
*/
|
||||
getDisplayName: function () {
|
||||
// It is possible that there is no display name set. In that case, return empty string.
|
||||
if (this.model.get('display_name') === undefined) {
|
||||
return '';
|
||||
}
|
||||
var displayNameValue = this.model.get('display_name').value;
|
||||
return displayNameValue ? displayNameValue : '';
|
||||
}
|
||||
@@ -67,7 +77,6 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
|
||||
CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
|
||||
|
||||
// Model is CMS.Models.Metadata.
|
||||
|
||||
initialize : function() {
|
||||
var self = this;
|
||||
var templateName = this.getTemplateName();
|
||||
@@ -82,22 +91,42 @@ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the ID/name of the template. Subclasses should implement this method.
|
||||
*/
|
||||
getTemplateName : function () {},
|
||||
|
||||
/**
|
||||
* Returns the value currently displayed in the editor/view. Subclasses should implement this method.
|
||||
*/
|
||||
getValueFromEditor : function () {},
|
||||
|
||||
/**
|
||||
* Sets the value currently displayed in the editor/view. Subclasses should implement this method.
|
||||
*/
|
||||
setValueInEditor : function (value) {},
|
||||
|
||||
/**
|
||||
* Sets the value in the model, using the value currently displayed in the view. Afterward,
|
||||
* this method re-renders to update the clear button.
|
||||
*/
|
||||
updateModel: function () {
|
||||
this.model.setValue(this.getValueFromEditor());
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the value currently set in the model (reverting to the default). Afterward, this method
|
||||
* re-renders the view.
|
||||
*/
|
||||
clear: function () {
|
||||
this.model.clear();
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the clear button, if it is not already showing.
|
||||
*/
|
||||
showClearButton: function() {
|
||||
if (!this.$el.hasClass('is-set')) {
|
||||
this.$el.addClass('is-set');
|
||||
@@ -106,10 +135,17 @@ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the clear button.
|
||||
*/
|
||||
getClearButton: function () {
|
||||
return this.$el.find('.setting-clear');
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the editor, updating the value displayed in the view, as well as the state of
|
||||
* the clear button.
|
||||
*/
|
||||
render: function () {
|
||||
if (!this.template) return;
|
||||
|
||||
@@ -172,7 +208,7 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({
|
||||
}
|
||||
if (options.hasOwnProperty(max)) {
|
||||
this.max = Number(options[max]);
|
||||
this.$el.find('input').attr(max, numToString(this.max.toFixed));
|
||||
this.$el.find('input').attr(max, numToString(this.max));
|
||||
}
|
||||
var stepValue = undefined;
|
||||
if (options.hasOwnProperty(step)) {
|
||||
@@ -208,6 +244,9 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({
|
||||
this.$el.find('input').val(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this view is restricted to integers, as opposed to floating points values.
|
||||
*/
|
||||
isIntegerField : function () {
|
||||
return this.model.getType() === 'Integer';
|
||||
},
|
||||
@@ -275,7 +314,7 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({
|
||||
value = modelValue['display_name'];
|
||||
}
|
||||
});
|
||||
$('#' + this.uniqueId + " option").filter(function() {
|
||||
this.$el.find('#' + this.uniqueId + " option").filter(function() {
|
||||
return $(this).text() === value;
|
||||
}).prop('selected', true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user