Commit 5b9e0e9d by Andy Armstrong

Merge pull request #10957 from edx/andya/studio-xblock-debugging

Support local debugging of XBlock JavaScript
parents a6e83834 cc24ca55
define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit", "js/models/module_info", "xmodule"], ($, edit_helpers, ModuleEdit, ModuleModel) ->
define ["jquery", "common/js/components/utils/view_utils", "js/spec_helpers/edit_helpers",
"coffee/src/views/module_edit", "js/models/module_info", "xmodule"],
($, ViewUtils, edit_helpers, ModuleEdit, ModuleModel) ->
describe "ModuleEdit", ->
beforeEach ->
......@@ -60,7 +62,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
spyOn(@moduleEdit, 'loadDisplay')
spyOn(@moduleEdit, 'delegateEvents')
spyOn($.fn, 'append')
spyOn($, 'getScript').andReturn($.Deferred().resolve().promise())
spyOn(ViewUtils, 'loadJavaScript').andReturn($.Deferred().resolve().promise());
window.MockXBlock = (runtime, element) ->
return { }
......@@ -150,7 +152,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
expect($('head').append).toHaveBeenCalledWith("<script>inline-js</script>")
it "loads js urls from fragments", ->
expect($.getScript).toHaveBeenCalledWith("js-url")
expect(ViewUtils.loadJavaScript).toHaveBeenCalledWith("js-url")
it "loads head html", ->
expect($('head').append).toHaveBeenCalledWith("head-html")
......
define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xblock", "js/models/xblock_info",
"xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function ($, AjaxHelpers, URI, XBlockView, XBlockInfo) {
define(["jquery", "URI", "common/js/spec_helpers/ajax_helpers", "common/js/components/utils/view_utils",
"js/views/xblock", "js/models/xblock_info", "xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function ($, URI, AjaxHelpers, ViewUtils, XBlockView, XBlockInfo) {
"use strict";
describe("XBlockView", function() {
var model, xblockView, mockXBlockHtml;
......@@ -89,11 +89,11 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc
it('aborts rendering when a dependent script fails to load', function() {
var requests = AjaxHelpers.requests(this),
mockJavaScriptUrl = "mock.js",
missingJavaScriptUrl = "no_such_file.js",
promise;
spyOn($, 'getScript').andReturn($.Deferred().reject().promise());
spyOn(ViewUtils, 'loadJavaScript').andReturn($.Deferred().reject().promise());
promise = postXBlockRequest(requests, [
["hash5", { mimetype: "application/javascript", kind: "url", data: mockJavaScriptUrl }]
["hash5", { mimetype: "application/javascript", kind: "url", data: missingJavaScriptUrl }]
]);
expect(promise.isRejected()).toBe(true);
});
......@@ -104,7 +104,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc
postXBlockRequest(AjaxHelpers.requests(this), []);
xblockView.$el.find(".notification-action-button").click();
expect(notifySpy).toHaveBeenCalledWith("add-missing-groups", model.get("id"));
})
});
});
});
});
define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
function ($, _, BaseView, XBlock) {
define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/views/baseview", "xblock/runtime.v1"],
function ($, _, ViewUtils, BaseView, XBlock) {
'use strict';
var XBlockView = BaseView.extend({
// takes XBlockInfo as a model
......@@ -83,7 +84,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
* may have thrown JavaScript errors after rendering in which case the xblock parameter
* will be null.
*/
xblockReady: function(xblock) {
xblockReady: function(xblock) { // jshint ignore:line
// Do nothing
},
......@@ -95,7 +96,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
* represents this process.
* @param fragment The fragment returned from the xblock_handler
* @param element The element into which to render the fragment (defaults to this.$el)
* @returns {jQuery promise} A promise representing the rendering process
* @returns {Promise} A promise representing the rendering process
*/
renderXBlockFragment: function(fragment, element) {
var html = fragment.html,
......@@ -131,7 +132,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
* Dynamically loads all of an XBlock's dependent resources. This is an asynchronous
* process so a promise is returned.
* @param resources The resources to be rendered
* @returns {jQuery promise} A promise representing the rendering process
* @returns {Promise} A promise representing the rendering process
*/
addXBlockFragmentResources: function(resources) {
var self = this,
......@@ -171,7 +172,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
/**
* Loads the specified resource into the page.
* @param resource The resource to be loaded.
* @returns {jQuery promise} A promise representing the loading of the resource.
* @returns {Promise} A promise representing the loading of the resource.
*/
loadResource: function(resource) {
var head = $('head'),
......@@ -189,8 +190,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
if (kind === "text") {
head.append("<script>" + data + "</script>");
} else if (kind === "url") {
// Return a promise for the script resolution
return $.getScript(data);
return ViewUtils.loadJavaScript(data);
}
} else if (mimetype === "text/html") {
if (placement === "head") {
......@@ -202,11 +202,11 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
},
fireNotificationActionEvent: function(event) {
var eventName = $(event.currentTarget).data("notification-action");
if (eventName) {
event.preventDefault();
this.notifyRuntime(eventName, this.model.get("id"));
}
var eventName = $(event.currentTarget).data("notification-action");
if (eventName) {
event.preventDefault();
this.notifyRuntime(eventName, this.model.get("id"));
}
}
});
......
/**
* Provides useful utilities for views.
*/
;(function (define) {
;(function (define, require) {
'use strict';
define(["jquery", "underscore", "gettext", "common/js/components/views/feedback_notification",
"common/js/components/views/feedback_prompt"],
function ($, _, gettext, NotificationView, PromptView) {
var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation,
runOperationShowingMessage, withDisabledElement, disableElementWhileRunning,
getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes, deleteNotificationHandler,
validateRequiredField, validateURLItemEncoding, validateTotalKeyLength, checkTotalKeyLengthViolations;
"common/js/components/views/feedback_prompt"],
function ($, _, gettext, NotificationView, PromptView) {
var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation,
runOperationShowingMessage, withDisabledElement, disableElementWhileRunning,
getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes,
deleteNotificationHandler, validateRequiredField, validateURLItemEncoding,
validateTotalKeyLength, checkTotalKeyLengthViolations, loadJavaScript;
// see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65
var MAX_SUM_KEY_LENGTH = 65;
// see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65
var MAX_SUM_KEY_LENGTH = 65;
/**
* Toggles the expanded state of the current element.
*/
toggleExpandCollapse = function(target, collapsedClass) {
// Support the old 'collapsed' option until fully switched over to is-collapsed
if (!collapsedClass) {
collapsedClass = 'collapsed';
}
target.closest('.expand-collapse').toggleClass('expand collapse');
target.closest('.is-collapsible, .window').toggleClass(collapsedClass);
target.closest('.is-collapsible').children('article').slideToggle();
};
/**
* Toggles the expanded state of the current element.
*/
toggleExpandCollapse = function(target, collapsedClass) {
// Support the old 'collapsed' option until fully switched over to is-collapsed
if (!collapsedClass) {
collapsedClass = 'collapsed';
}
target.closest('.expand-collapse').toggleClass('expand collapse');
target.closest('.is-collapsible, .window').toggleClass(collapsedClass);
target.closest('.is-collapsible').children('article').slideToggle();
};
/**
* Show the page's loading indicator.
*/
showLoadingIndicator = function() {
$('.ui-loading').show();
};
/**
* Show the page's loading indicator.
*/
showLoadingIndicator = function() {
$('.ui-loading').show();
};
/**
* Hide the page's loading indicator.
*/
hideLoadingIndicator = function() {
$('.ui-loading').hide();
};
/**
* Hide the page's loading indicator.
*/
hideLoadingIndicator = function() {
$('.ui-loading').hide();
};
/**
* Confirms with the user whether to run an operation or not, and then runs it if desired.
*/
confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) {
return new PromptView.Warning({
title: title,
message: message,
actions: {
primary: {
text: actionLabel,
click: function(prompt) {
prompt.hide();
operation();
}
},
secondary: {
text: gettext('Cancel'),
click: function(prompt) {
if (onCancelCallback) {
onCancelCallback();
/**
* Confirms with the user whether to run an operation or not, and then runs it if desired.
*/
confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) {
return new PromptView.Warning({
title: title,
message: message,
actions: {
primary: {
text: actionLabel,
click: function(prompt) {
prompt.hide();
operation();
}
},
secondary: {
text: gettext('Cancel'),
click: function(prompt) {
if (onCancelCallback) {
onCancelCallback();
}
return prompt.hide();
}
return prompt.hide();
}
}
}
}).show();
};
/**
* Shows a progress message for the duration of an asynchronous operation.
* Note: this does not remove the notification upon failure because an error
* will be shown that shouldn't be removed.
* @param message The message to show.
* @param operation A function that returns a promise representing the operation.
*/
runOperationShowingMessage = function(message, operation) {
var notificationView;
notificationView = new NotificationView.Mini({
title: gettext(message)
});
notificationView.show();
return operation().done(function() {
notificationView.hide();
});
};
}).show();
};
/**
* Wraps a Backbone event callback to disable the event's target element.
*
* This paradigm is designed to be used in Backbone event maps where
* multiple events firing simultaneously is not desired.
*
* @param functionName the function to execute, as a string.
* The function must return a jQuery promise and be able to take an event
*/
withDisabledElement = function(functionName) {
return function(event) {
var view = this;
disableElementWhileRunning($(event.currentTarget), function() {
//call view.functionName(event), with view as the current this
return view[functionName].apply(view, [event]);
/**
* Shows a progress message for the duration of an asynchronous operation.
* Note: this does not remove the notification upon failure because an error
* will be shown that shouldn't be removed.
* @param message The message to show.
* @param operation A function that returns a promise representing the operation.
*/
runOperationShowingMessage = function(message, operation) {
var notificationView;
notificationView = new NotificationView.Mini({
title: gettext(message)
});
notificationView.show();
return operation().done(function() {
notificationView.hide();
});
};
};
/**
* Disables a given element when a given operation is running.
* @param {jQuery} element the element to be disabled.
* @param operation the operation during whose duration the
* element should be disabled. The operation should return
* a JQuery promise.
*/
disableElementWhileRunning = function(element, operation) {
element.addClass("is-disabled").attr('aria-disabled', true);
return operation().always(function() {
element.removeClass("is-disabled").attr('aria-disabled', false);
});
};
/**
* Returns a handler that removes a notification, both dismissing it and deleting it from the database.
* @param callback function to call when deletion succeeds
*/
deleteNotificationHandler = function(callback) {
return function (event) {
event.preventDefault();
$.ajax({
url: $(this).data('dismiss-link'),
type: 'DELETE',
success: callback
/**
* Wraps a Backbone event callback to disable the event's target element.
*
* This paradigm is designed to be used in Backbone event maps where
* multiple events firing simultaneously is not desired.
*
* @param functionName the function to execute, as a string.
* The function must return a jQuery promise and be able to take an event
*/
withDisabledElement = function(functionName) {
return function(event) {
var view = this;
disableElementWhileRunning($(event.currentTarget), function() {
//call view.functionName(event), with view as the current this
return view[functionName].apply(view, [event]);
});
};
};
/**
* Disables a given element when a given operation is running.
* @param {jQuery} element the element to be disabled.
* @param operation the operation during whose duration the
* element should be disabled. The operation should return
* a JQuery promise.
*/
disableElementWhileRunning = function(element, operation) {
element.addClass("is-disabled").attr('aria-disabled', true);
return operation().always(function() {
element.removeClass("is-disabled").attr('aria-disabled', false);
});
};
};
/**
* Performs an animated scroll so that the window has the specified scroll top.
* @param scrollTop The desired scroll top for the window.
*/
setScrollTop = function(scrollTop) {
$('html, body').animate({
scrollTop: scrollTop
}, 500);
};
/**
* Returns a handler that removes a notification, both dismissing it and deleting it from the database.
* @param callback function to call when deletion succeeds
*/
deleteNotificationHandler = function(callback) {
return function (event) {
event.preventDefault();
$.ajax({
url: $(this).data('dismiss-link'),
type: 'DELETE',
success: callback
});
};
};
/**
* Returns the relative position that the element is scrolled from the top of the view port.
* @param element The element in question.
*/
getScrollOffset = function(element) {
var elementTop = element.offset().top;
return elementTop - $(window).scrollTop();
};
/**
* Performs an animated scroll so that the window has the specified scroll top.
* @param scrollTop The desired scroll top for the window.
*/
setScrollTop = function(scrollTop) {
$('html, body').animate({
scrollTop: scrollTop
}, 500);
};
/**
* Returns the relative position that the element is scrolled from the top of the view port.
* @param element The element in question.
*/
getScrollOffset = function(element) {
var elementTop = element.offset().top;
return elementTop - $(window).scrollTop();
};
/**
* Scrolls the window so that the element is scrolled down to the specified relative position
* from the top of the view port.
* @param element The element in question.
* @param offset The amount by which the element should be scrolled from the top of the view port.
*/
setScrollOffset = function(element, offset) {
var elementTop = element.offset().top,
newScrollTop = elementTop - offset;
setScrollTop(newScrollTop);
};
/**
* Scrolls the window so that the element is scrolled down to the specified relative position
* from the top of the view port.
* @param element The element in question.
* @param offset The amount by which the element should be scrolled from the top of the view port.
*/
setScrollOffset = function(element, offset) {
var elementTop = element.offset().top,
newScrollTop = elementTop - offset;
setScrollTop(newScrollTop);
};
/**
* Redirects to the specified URL. This is broken out as its own function for unit testing.
*/
redirect = function(url) {
window.location = url;
};
/**
* Redirects to the specified URL. This is broken out as its own function for unit testing.
*/
redirect = function(url) {
window.location = url;
};
/**
* Reloads the page. This is broken out as its own function for unit testing.
*/
reload = function() {
window.location.reload();
};
/**
* Reloads the page. This is broken out as its own function for unit testing.
*/
reload = function() {
window.location.reload();
};
/**
* Returns true if a model has changes to at least one of the specified attributes.
* @param model The model in question.
* @param attributes The list of attributes to be compared.
* @returns {boolean} Returns true if attribute changes are found.
*/
hasChangedAttributes = function(model, attributes) {
var i, changedAttributes = model.changedAttributes();
if (!changedAttributes) {
return false;
}
for (i=0; i < attributes.length; i++) {
if (_.has(changedAttributes, attributes[i])) {
return true;
/**
* Returns true if a model has changes to at least one of the specified attributes.
* @param model The model in question.
* @param attributes The list of attributes to be compared.
* @returns {boolean} Returns true if attribute changes are found.
*/
hasChangedAttributes = function(model, attributes) {
var i, changedAttributes = model.changedAttributes();
if (!changedAttributes) {
return false;
}
for (i=0; i < attributes.length; i++) {
if (_.has(changedAttributes, attributes[i])) {
return true;
}
}
}
return false;
};
return false;
};
/**
* Helper method for course/library creation - verifies a required field is not blank.
*/
validateRequiredField = function (msg) {
return msg.length === 0 ? gettext('Required field.') : '';
};
/**
* Helper method for course/library creation - verifies a required field is not blank.
*/
validateRequiredField = function (msg) {
return msg.length === 0 ? gettext('Required field.') : '';
};
/**
* Helper method for course/library creation.
* Check that a course (org, number, run) doesn't use any special characters
*/
validateURLItemEncoding = function (item, allowUnicode) {
var required = validateRequiredField(item);
if (required) {
return required;
}
if (allowUnicode) {
if (/\s/g.test(item)) {
return gettext('Please do not use any spaces in this field.');
/**
* Helper method for course/library creation.
* Check that a course (org, number, run) doesn't use any special characters
*/
validateURLItemEncoding = function (item, allowUnicode) {
var required = validateRequiredField(item);
if (required) {
return required;
}
}
else {
if (item !== encodeURIComponent(item) || item.match(/[!'()*]/)) {
return gettext('Please do not use any spaces or special characters in this field.');
if (allowUnicode) {
if (/\s/g.test(item)) {
return gettext('Please do not use any spaces in this field.');
}
}
}
return '';
};
else {
if (item !== encodeURIComponent(item) || item.match(/[!'()*]/)) {
return gettext('Please do not use any spaces or special characters in this field.');
}
}
return '';
};
// Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars.
validateTotalKeyLength = function (key_field_selectors) {
var totalLength = _.reduce(
key_field_selectors,
function (sum, ele) { return sum + $(ele).val().length;},
0
);
return totalLength <= MAX_SUM_KEY_LENGTH;
};
// Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars.
validateTotalKeyLength = function (key_field_selectors) {
var totalLength = _.reduce(
key_field_selectors,
function (sum, ele) { return sum + $(ele).val().length;},
0
);
return totalLength <= MAX_SUM_KEY_LENGTH;
};
checkTotalKeyLengthViolations = function(selectors, classes, key_field_selectors, message_tpl) {
if (!validateTotalKeyLength(key_field_selectors)) {
$(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
$(selectors.errorMessage).html('<p>' + _.template(message_tpl, {limit: MAX_SUM_KEY_LENGTH}) + '</p>');
$(selectors.save).addClass(classes.disabled);
} else {
$(selectors.errorWrapper).removeClass(classes.shown).addClass(classes.hiding);
}
};
checkTotalKeyLengthViolations = function(selectors, classes, key_field_selectors, message_tpl) {
if (!validateTotalKeyLength(key_field_selectors)) {
$(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding);
$(selectors.errorMessage).html(
'<p>' + _.template(message_tpl, {limit: MAX_SUM_KEY_LENGTH}) + '</p>'
);
$(selectors.save).addClass(classes.disabled);
} else {
$(selectors.errorWrapper).removeClass(classes.shown).addClass(classes.hiding);
}
};
return {
'toggleExpandCollapse': toggleExpandCollapse,
'showLoadingIndicator': showLoadingIndicator,
'hideLoadingIndicator': hideLoadingIndicator,
'confirmThenRunOperation': confirmThenRunOperation,
'runOperationShowingMessage': runOperationShowingMessage,
'withDisabledElement': withDisabledElement,
'disableElementWhileRunning': disableElementWhileRunning,
'deleteNotificationHandler': deleteNotificationHandler,
'setScrollTop': setScrollTop,
'getScrollOffset': getScrollOffset,
'setScrollOffset': setScrollOffset,
'redirect': redirect,
'reload': reload,
'hasChangedAttributes': hasChangedAttributes,
'validateRequiredField': validateRequiredField,
'validateURLItemEncoding': validateURLItemEncoding,
'validateTotalKeyLength': validateTotalKeyLength,
'checkTotalKeyLengthViolations': checkTotalKeyLengthViolations
};
});
}).call(this, define || RequireJS.define);
/**
* Dynamically loads the specified JavaScript file.
* @param url The URL to a JavaScript file.
* @returns {Promise} A promise indicating when the URL has been loaded.
*/
loadJavaScript = function(url) {
var deferred = $.Deferred();
require([url],
function() {
deferred.resolve();
},
function() {
deferred.reject();
});
return deferred.promise();
};
return {
'toggleExpandCollapse': toggleExpandCollapse,
'showLoadingIndicator': showLoadingIndicator,
'hideLoadingIndicator': hideLoadingIndicator,
'confirmThenRunOperation': confirmThenRunOperation,
'runOperationShowingMessage': runOperationShowingMessage,
'withDisabledElement': withDisabledElement,
'disableElementWhileRunning': disableElementWhileRunning,
'deleteNotificationHandler': deleteNotificationHandler,
'setScrollTop': setScrollTop,
'getScrollOffset': getScrollOffset,
'setScrollOffset': setScrollOffset,
'redirect': redirect,
'reload': reload,
'hasChangedAttributes': hasChangedAttributes,
'validateRequiredField': validateRequiredField,
'validateURLItemEncoding': validateURLItemEncoding,
'validateTotalKeyLength': validateTotalKeyLength,
'checkTotalKeyLengthViolations': checkTotalKeyLengthViolations,
'loadJavaScript': loadJavaScript
};
});
}).call(this, define || RequireJS.define, require || RequireJS.require);
;(function (define) {
'use strict';
define(["jquery", "underscore", "common/js/components/utils/view_utils", "common/js/spec_helpers/view_helpers", 'jasmine-stealth'],
function ($, _, ViewUtils, ViewHelpers) {
define(["jquery", "underscore", "backbone", "common/js/components/utils/view_utils",
"common/js/spec_helpers/view_helpers", "jasmine-stealth"],
function ($, _, Backbone, ViewUtils, ViewHelpers) {
describe("ViewUtils", function() {
describe("disabled element while running", function() {
it("adds 'is-disabled' class to element while action is running and removes it after", function() {
var link,
deferred = new $.Deferred(),
promise = deferred.promise();
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
link = $("#link");
expect(link).not.toHaveClass("is-disabled");
ViewUtils.disableElementWhileRunning(link, function() { return promise; });
expect(link).toHaveClass("is-disabled");
deferred.resolve();
expect(link).not.toHaveClass("is-disabled");
});
describe("ViewUtils", function() {
describe("disabled element while running", function() {
it("adds 'is-disabled' class to element while action is running and removes it after", function() {
var link,
deferred = new $.Deferred(),
promise = deferred.promise();
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
link = $("#link");
expect(link).not.toHaveClass("is-disabled");
ViewUtils.disableElementWhileRunning(link, function() { return promise; });
expect(link).toHaveClass("is-disabled");
deferred.resolve();
expect(link).not.toHaveClass("is-disabled");
});
it("uses withDisabledElement wrapper to disable element while running a Backbone event handler", function() {
var link,
eventCallback,
event,
deferred = new $.Deferred(),
promise = deferred.promise(),
MockView = Backbone.View.extend({
testFunction: function() {
return promise;
}
}),
testView = new MockView();
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
link = $("#link");
expect(link).not.toHaveClass("is-disabled");
eventCallback = ViewUtils.withDisabledElement('testFunction');
event = {currentTarget: link};
eventCallback.apply(testView, [event]);
expect(link).toHaveClass("is-disabled");
deferred.resolve();
expect(link).not.toHaveClass("is-disabled");
it("disables elements within withDisabledElement", function() {
var link,
eventCallback,
event,
deferred = new $.Deferred(),
promise = deferred.promise(),
MockView = Backbone.View.extend({
testFunction: function() {
return promise;
}
}),
testView = new MockView();
setFixtures("<a href='#' id='link'>ripe apples drop about my head</a>");
link = $("#link");
expect(link).not.toHaveClass("is-disabled");
eventCallback = ViewUtils.withDisabledElement('testFunction');
event = {currentTarget: link};
eventCallback.apply(testView, [event]);
expect(link).toHaveClass("is-disabled");
deferred.resolve();
expect(link).not.toHaveClass("is-disabled");
});
});
});
describe("progress notification", function() {
it("shows progress notification and removes it upon success", function() {
var testMessage = "Testing...",
deferred = new $.Deferred(),
promise = deferred.promise(),
notificationSpy = ViewHelpers.createNotificationSpy();
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
deferred.resolve();
ViewHelpers.verifyNotificationHidden(notificationSpy);
});
describe("progress notification", function() {
it("shows progress notification and removes it upon success", function() {
var testMessage = "Testing...",
deferred = new $.Deferred(),
promise = deferred.promise(),
notificationSpy = ViewHelpers.createNotificationSpy();
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
deferred.resolve();
ViewHelpers.verifyNotificationHidden(notificationSpy);
});
it("shows progress notification and leaves it showing upon failure", function() {
var testMessage = "Testing...",
deferred = new $.Deferred(),
promise = deferred.promise(),
notificationSpy = ViewHelpers.createNotificationSpy();
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
deferred.fail();
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
it("shows progress notification and leaves it showing upon failure", function() {
var testMessage = "Testing...",
deferred = new $.Deferred(),
promise = deferred.promise(),
notificationSpy = ViewHelpers.createNotificationSpy();
ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; });
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
deferred.fail();
ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/);
});
});
});
describe("course/library fields validation", function() {
describe("without unicode support", function() {
it("validates presence of field", function() {
var error = ViewUtils.validateURLItemEncoding('', false);
expect(error).toBeTruthy();
});
describe("course/library fields validation", function() {
describe("without unicode support", function() {
it("validates presence of field", function() {
var error = ViewUtils.validateURLItemEncoding('', false);
expect(error).toBeTruthy();
});
it("checks for presence of special characters in the field", function() {
var error;
// Special characters are not allowed.
error = ViewUtils.validateURLItemEncoding('my+field', false);
expect(error).toBeTruthy();
error = ViewUtils.validateURLItemEncoding('2014!', false);
expect(error).toBeTruthy();
error = ViewUtils.validateURLItemEncoding('*field*', false);
expect(error).toBeTruthy();
// Spaces not allowed.
error = ViewUtils.validateURLItemEncoding('Jan 2014', false);
expect(error).toBeTruthy();
// -_~. are allowed.
error = ViewUtils.validateURLItemEncoding('2015-Math_X1.0~', false);
expect(error).toBeFalsy();
});
it("checks for presence of special characters in the field", function() {
var error;
// Special characters are not allowed.
error = ViewUtils.validateURLItemEncoding('my+field', false);
expect(error).toBeTruthy();
error = ViewUtils.validateURLItemEncoding('2014!', false);
expect(error).toBeTruthy();
error = ViewUtils.validateURLItemEncoding('*field*', false);
expect(error).toBeTruthy();
// Spaces not allowed.
error = ViewUtils.validateURLItemEncoding('Jan 2014', false);
expect(error).toBeTruthy();
// -_~. are allowed.
error = ViewUtils.validateURLItemEncoding('2015-Math_X1.0~', false);
expect(error).toBeFalsy();
});
it("does not allow unicode characters", function() {
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', false);
expect(error).toBeTruthy();
it("does not allow unicode characters", function() {
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', false);
expect(error).toBeTruthy();
});
});
});
describe("with unicode support", function() {
it("validates presence of field", function() {
var error = ViewUtils.validateURLItemEncoding('', true);
expect(error).toBeTruthy();
});
describe("with unicode support", function() {
it("validates presence of field", function() {
var error = ViewUtils.validateURLItemEncoding('', true);
expect(error).toBeTruthy();
});
it("checks for presence of spaces", function() {
var error = ViewUtils.validateURLItemEncoding('My Field', true);
expect(error).toBeTruthy();
});
it("checks for presence of spaces", function() {
var error = ViewUtils.validateURLItemEncoding('My Field', true);
expect(error).toBeTruthy();
});
it("allows unicode characters", function() {
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', true);
expect(error).toBeFalsy();
it("allows unicode characters", function() {
var error = ViewUtils.validateURLItemEncoding('Field-\u010d', true);
expect(error).toBeFalsy();
});
});
});
});
});
});
}).call(this, define || RequireJS.define);
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