Commit 31993cf4 by Daniel Friedman Committed by cahrens

Escape xblock model field in string editor

Also add standalone spec

STUD-1984
parent 285beb38
......@@ -227,6 +227,7 @@ define([
"js/spec/views/unit_outline_spec",
"js/spec/views/xblock_spec",
"js/spec/views/xblock_editor_spec",
"js/spec/views/xblock_string_field_editor_spec",
"js/spec/views/pages/container_spec",
"js/spec/views/pages/container_subviews_spec",
......
......@@ -95,7 +95,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
describe("Editing the container", function() {
var updatedDisplayName = 'Updated Test Container',
expectEditCanceled, getDisplayNameWrapper;
getDisplayNameWrapper;
afterEach(function() {
edit_helpers.cancelModalIfShowing();
......@@ -105,24 +105,6 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
return containerPage.$('.wrapper-xblock-field');
};
expectEditCanceled = function(test, options) {
var initialRequests, displayNameWrapper, displayNameInput;
renderContainerPage(test, mockContainerXBlockHtml);
displayNameWrapper = getDisplayNameWrapper();
initialRequests = requests.length;
displayNameInput = edit_helpers.inlineEdit(displayNameWrapper, options.newTitle);
if (options.pressEscape) {
displayNameInput.simulate("keydown", { keyCode: $.simulate.keyCode.ESCAPE });
displayNameInput.simulate("keyup", { keyCode: $.simulate.keyCode.ESCAPE });
} else {
displayNameInput.change();
}
// No requests should be made when the edit is cancelled client-side
expect(initialRequests).toBe(requests.length);
edit_helpers.verifyInlineEditChange(displayNameWrapper, initialDisplayName);
expect(containerPage.model.get('display_name')).toBe(initialDisplayName);
};
it('can edit itself', function() {
var editButtons, displayNameElement;
renderContainerPage(this, mockContainerXBlockHtml);
......@@ -173,46 +155,6 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
edit_helpers.verifyInlineEditChange(displayNameWrapper, updatedDisplayName);
expect(containerPage.model.get('display_name')).toBe(updatedDisplayName);
});
it('does not change the title when a display name update fails', function() {
var initialRequests, displayNameInput, displayNameWrapper;
renderContainerPage(this, mockContainerXBlockHtml);
displayNameWrapper = getDisplayNameWrapper();
displayNameInput = edit_helpers.inlineEdit(displayNameWrapper, updatedDisplayName);
initialRequests = requests.length;
displayNameInput.change();
create_sinon.respondWithError(requests);
// No fetch operation should occur.
expect(initialRequests + 1).toBe(requests.length);
edit_helpers.verifyInlineEditChange(displayNameWrapper, initialDisplayName, updatedDisplayName);
expect(containerPage.model.get('display_name')).toBe(initialDisplayName);
});
it('trims whitespace from the display name', function() {
var displayNameInput, displayNameWrapper;
renderContainerPage(this, mockContainerXBlockHtml);
displayNameWrapper = getDisplayNameWrapper();
displayNameInput = edit_helpers.inlineEdit(displayNameWrapper, updatedDisplayName + ' ');
displayNameInput.change();
// This is the response for the change operation.
create_sinon.respondWithJson(requests, { });
// This is the response for the subsequent fetch operation.
create_sinon.respondWithJson(requests, {"display_name": updatedDisplayName});
edit_helpers.verifyInlineEditChange(displayNameWrapper, updatedDisplayName);
expect(containerPage.model.get('display_name')).toBe(updatedDisplayName);
});
it('does not change the title when input is the empty string', function() {
expectEditCanceled(this, {newTitle: ''});
});
it('does not change the title when input is whitespace-only', function() {
expectEditCanceled(this, {newTitle: ' '});
});
it('can cancel an inline edit', function() {
expectEditCanceled(this, {newTitle: updatedDisplayName, pressEscape: true});
});
});
describe("Editing an xblock", function() {
......
define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/spec_helpers/edit_helpers", "js/models/xblock_info", "js/views/xblock_string_field_editor"],
function ($, create_sinon, view_helpers, edit_helpers, XBlockInfo, XBlockStringFieldEditor) {
describe("XBlockStringFieldEditorView", function () {
var initialDisplayName, updatedDisplayName, getXBlockInfo, getFieldEditorView;
getXBlockInfo = function (displayName) {
return new XBlockInfo(
{
display_name: displayName,
id: "my_xblock"
},
{ parse: true }
);
};
getFieldEditorView = function (xblockInfo) {
if (xblockInfo === undefined) {
xblockInfo = getXBlockInfo(initialDisplayName);
}
return new XBlockStringFieldEditor({
model: xblockInfo,
el: $('.wrapper-xblock-field')
});
};
beforeEach(function () {
initialDisplayName = "Default Display Name";
updatedDisplayName = "Updated Display Name";
view_helpers.installTemplate('xblock-string-field-editor');
appendSetFixtures(
'<div class="wrapper-xblock-field incontext-editor is-editable"' +
'data-field="display_name" data-field-display-name="Display Name">' +
'<h1 class="page-header-title xblock-field-value incontext-editor-value"><span class="title-value">' + initialDisplayName + '</span></h1>' +
'</div>'
);
});
describe('Editing', function () {
var expectPostedNewDisplayName, expectEditCanceled;
expectPostedNewDisplayName = function (requests, displayName) {
create_sinon.expectJsonRequest(requests, 'POST', '/xblock/my_xblock', {
metadata: {
display_name: displayName
}
});
};
expectEditCanceled = function (test, fieldEditorView, options) {
var requests, initialRequests, displayNameInput;
requests = create_sinon.requests(test);
initialRequests = requests.length;
displayNameInput = edit_helpers.inlineEdit(fieldEditorView.$el, options.newTitle);
if (options.pressEscape) {
displayNameInput.simulate("keydown", { keyCode: $.simulate.keyCode.ESCAPE });
displayNameInput.simulate("keyup", { keyCode: $.simulate.keyCode.ESCAPE });
} else if (options.clickCancel) {
fieldEditorView.$('button[name=cancel]').click();
} else {
displayNameInput.change();
}
// No requests should be made when the edit is cancelled client-side
expect(initialRequests).toBe(requests.length);
edit_helpers.verifyInlineEditChange(fieldEditorView.$el, initialDisplayName);
expect(fieldEditorView.model.get('display_name')).toBe(initialDisplayName);
};
it('can inline edit the display name', function () {
var requests, fieldEditorView;
requests = create_sinon.requests(this);
fieldEditorView = getFieldEditorView().render();
edit_helpers.inlineEdit(fieldEditorView.$el, updatedDisplayName);
fieldEditorView.$('button[name=submit]').click();
expectPostedNewDisplayName(requests, updatedDisplayName);
// This is the response for the change operation.
create_sinon.respondWithJson(requests, { });
// This is the response for the subsequent fetch operation.
create_sinon.respondWithJson(requests, {display_name: updatedDisplayName});
edit_helpers.verifyInlineEditChange(fieldEditorView.$el, updatedDisplayName);
});
it('does not change the title when a display name update fails', function () {
var requests, fieldEditorView, initialRequests;
requests = create_sinon.requests(this);
initialRequests = requests.length;
fieldEditorView = getFieldEditorView().render();
edit_helpers.inlineEdit(fieldEditorView.$el, updatedDisplayName);
fieldEditorView.$('button[name=submit]').click();
expectPostedNewDisplayName(requests, updatedDisplayName);
create_sinon.respondWithError(requests);
// No fetch operation should occur.
expect(initialRequests + 1).toBe(requests.length);
edit_helpers.verifyInlineEditChange(fieldEditorView.$el, initialDisplayName, updatedDisplayName);
});
it('trims whitespace from the display name', function () {
var requests, fieldEditorView;
requests = create_sinon.requests(this);
fieldEditorView = getFieldEditorView().render();
updatedDisplayName += ' ';
edit_helpers.inlineEdit(fieldEditorView.$el, updatedDisplayName);
fieldEditorView.$('button[name=submit]').click();
expectPostedNewDisplayName(requests, updatedDisplayName.trim());
// This is the response for the change operation.
create_sinon.respondWithJson(requests, { });
// This is the response for the subsequent fetch operation.
create_sinon.respondWithJson(requests, {display_name: updatedDisplayName.trim()});
edit_helpers.verifyInlineEditChange(fieldEditorView.$el, updatedDisplayName.trim());
});
it('does not change the title when input is the empty string', function () {
var fieldEditorView = getFieldEditorView().render();
expectEditCanceled(this, fieldEditorView, {newTitle: ''});
});
it('does not change the title when input is whitespace-only', function () {
var fieldEditorView = getFieldEditorView().render();
expectEditCanceled(this, fieldEditorView, {newTitle: ' '});
});
it('can cancel an inline edit by pressing escape', function () {
var fieldEditorView = getFieldEditorView().render();
expectEditCanceled(this, fieldEditorView, {newTitle: updatedDisplayName, pressEscape: true});
});
it('can cancel an inline edit by clicking cancel', function () {
var fieldEditorView = getFieldEditorView().render();
expectEditCanceled(this, fieldEditorView, {newTitle: updatedDisplayName, clickCancel: true});
});
});
describe('Rendering', function () {
var expectInputMatchesModelDisplayName = function (displayName) {
var fieldEditorView = getFieldEditorView(getXBlockInfo(displayName)).render();
expect(fieldEditorView.$('.xblock-field-input').val()).toBe(displayName);
};
it('renders single quotes in input field', function () {
expectInputMatchesModelDisplayName('Updated \'Display Name\'');
});
it('renders double quotes in input field', function () {
expectInputMatchesModelDisplayName('Updated "Display Name"');
});
it('renders open angle bracket in input field', function () {
expectInputMatchesModelDisplayName(updatedDisplayName + '<');
});
it('renders close angle bracket in input field', function () {
expectInputMatchesModelDisplayName('>' + updatedDisplayName);
});
});
});
});
......@@ -11,7 +11,8 @@ define(["js/views/baseview", "js/views/utils/xblock_utils"],
var XBlockStringFieldEditor = BaseView.extend({
events: {
'click .xblock-field-value-edit': 'showInput',
'click button[type=submit]': 'onClickSubmit',
'click button[name=submit]': 'onClickSubmit',
'click button[name=cancel]': 'onClickCancel',
'change .xblock-field-input': 'updateField',
'focusout .xblock-field-input': 'onInputFocusLost',
'keyup .xblock-field-input': 'handleKeyUp'
......@@ -29,7 +30,7 @@ define(["js/views/baseview", "js/views/utils/xblock_utils"],
render: function() {
this.$el.append(this.template({
value: this.model.get(this.fieldName),
value: this.model.escape(this.fieldName),
fieldName: this.fieldName,
fieldDisplayName: this.fieldDisplayName
}));
......@@ -56,6 +57,11 @@ define(["js/views/baseview", "js/views/utils/xblock_utils"],
this.updateField();
},
onClickCancel: function(event) {
event.preventDefault();
this.cancelInput();
},
onChangeField: function() {
var value = this.model.get(this.fieldName);
this.getLabel().text(value);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment