Commit cc24ca55 by Andy Armstrong

Support local debugging of XBlock JavaScript

It turns out that loading JavaScript with $.getScript
causes Chrome to treat the file as an XHR request
and not JS. I've switched over to using RequireJS
to load the URL which already has the ability to
dynamically load files and have the browser
recognize them.
parent c14c146d
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