Commit d8b419c6 by muhammad-ammar

Review changes

parent 6134e8b5
define ["jquery", "js/views/feedback", "js/views/feedback_notification", "js/views/feedback_alert",
"js/views/feedback_prompt", "sinon"],
($, SystemFeedback, NotificationView, AlertView, PromptView, sinon) ->
tpl = readFixtures('system-feedback.underscore')
beforeEach ->
setFixtures(sandbox({id: "page-alert"}))
appendSetFixtures(sandbox({id: "page-notification"}))
appendSetFixtures(sandbox({id: "page-prompt"}))
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
@addMatchers
toBeShown: ->
@actual.hasClass("is-shown") and not @actual.hasClass("is-hiding")
toBeHiding: ->
@actual.hasClass("is-hiding") and not @actual.hasClass("is-shown")
toContainText: (text) ->
# remove this when we upgrade jasmine-jquery
trimmedText = $.trim(@actual.text())
if text and $.isFunction(text.test)
return text.test(trimmedText)
else
return trimmedText.indexOf(text) != -1;
toHaveBeenPrevented: ->
# remove this when we upgrade jasmine-jquery
eventName = @actual.eventName
selector = @actual.selector
@message = ->
[
"Expected event #{eventName} to have been prevented on #{selector}",
"Expected event #{eventName} not to have been prevented on #{selector}"
]
return jasmine.JQuery.events.wasPrevented(selector, eventName)
describe "SystemFeedback", ->
beforeEach ->
@options =
title: "Portal"
message: "Welcome to the Aperture Science Computer-Aided Enrichment Center"
# it will be interesting to see when this.render is called, so lets spy on it
@renderSpy = spyOn(AlertView.Confirmation.prototype, 'render').andCallThrough()
@showSpy = spyOn(AlertView.Confirmation.prototype, 'show').andCallThrough()
@hideSpy = spyOn(AlertView.Confirmation.prototype, 'hide').andCallThrough()
@clock = sinon.useFakeTimers()
afterEach ->
@clock.restore()
it "requires a type and an intent", ->
neither = =>
new SystemFeedback(@options)
noType = =>
options = $.extend({}, @options)
options.intent = "confirmation"
new SystemFeedback(options)
noIntent = =>
options = $.extend({}, @options)
options.type = "alert"
new SystemFeedback(options)
both = =>
options = $.extend({}, @options)
options.type = "alert"
options.intent = "confirmation"
new SystemFeedback(options)
expect(neither).toThrow()
expect(noType).toThrow()
expect(noIntent).toThrow()
expect(both).not.toThrow()
# for simplicity, we'll use AlertView.Confirmation from here on,
# which extends and proxies to SystemFeedback
it "does not show on initalize", ->
view = new AlertView.Confirmation(@options)
expect(@renderSpy).not.toHaveBeenCalled()
expect(@showSpy).not.toHaveBeenCalled()
# Disabled flaky test TNL-559
xit "renders the template", ->
view = new AlertView.Confirmation(@options)
view.show()
expect(view.$(".action-close")).toBeDefined()
expect(view.$('.wrapper')).toBeShown()
expect(view.$el).toContainText(@options.title)
expect(view.$el).toContainText(@options.message)
# Disabled flaky test TNL-559
xit "close button sends a .hide() message", ->
view = new AlertView.Confirmation(@options).show()
view.$(".action-close").click()
expect(@hideSpy).toHaveBeenCalled()
@clock.tick(900)
expect(view.$('.wrapper')).toBeHiding()
describe "PromptView", ->
# for some reason, expect($("body")) blows up the test runner, so this test
# just exercises the Prompt rather than asserting on anything. Best I can
# do for now. :(
it "changes class on body", ->
# expect($("body")).not.toHaveClass("prompt-is-shown")
view = new PromptView.Confirmation({
title: "Portal"
message: "Welcome to the Aperture Science Computer-Aided Enrichment Center"
})
# expect($("body")).toHaveClass("prompt-is-shown")
view.hide()
# expect($("body")).not.toHaveClass("prompt-is-shown")
describe "NotificationView.Mini", ->
beforeEach ->
@view = new NotificationView.Mini()
it "should have minShown set to 1250 by default", ->
expect(@view.options.minShown).toEqual(1250)
it "should have closeIcon set to false by default", ->
expect(@view.options.closeIcon).toBeFalsy()
# Disabled flaky test TNL-559
xdescribe "SystemFeedback click events", ->
beforeEach ->
@primaryClickSpy = jasmine.createSpy('primaryClick')
@secondaryClickSpy = jasmine.createSpy('secondaryClick')
@view = new NotificationView.Warning(
title: "Unsaved",
message: "Your content is currently Unsaved.",
actions:
primary:
text: "Save",
class: "save-button",
click: @primaryClickSpy
secondary:
text: "Revert",
class: "cancel-button",
click: @secondaryClickSpy
)
@view.show()
it "should trigger the primary event on a primary click", ->
@view.$(".action-primary").click()
expect(@primaryClickSpy).toHaveBeenCalled()
expect(@secondaryClickSpy).not.toHaveBeenCalled()
it "should trigger the secondary event on a secondary click", ->
@view.$(".action-secondary").click()
expect(@secondaryClickSpy).toHaveBeenCalled()
expect(@primaryClickSpy).not.toHaveBeenCalled()
it "should apply class to primary action", ->
expect(@view.$(".action-primary")).toHaveClass("save-button")
it "should apply class to secondary action", ->
expect(@view.$(".action-secondary")).toHaveClass("cancel-button")
it "should preventDefault on primary action", ->
spyOnEvent(".action-primary", "click")
@view.$(".action-primary").click()
expect("click").toHaveBeenPreventedOn(".action-primary")
it "should preventDefault on secondary action", ->
spyOnEvent(".action-secondary", "click")
@view.$(".action-secondary").click()
expect("click").toHaveBeenPreventedOn(".action-secondary")
# Disabled flaky test TNL-559
xdescribe "SystemFeedback not preventing events", ->
beforeEach ->
@clickSpy = jasmine.createSpy('clickSpy')
@view = new AlertView.Confirmation(
title: "It's all good"
message: "No reason for this alert"
actions:
primary:
text: "Whatever"
click: @clickSpy
preventDefault: false
)
@view.show()
it "should not preventDefault", ->
spyOnEvent(".action-primary", "click")
@view.$(".action-primary").click()
expect("click").not.toHaveBeenPreventedOn(".action-primary")
expect(@clickSpy).toHaveBeenCalled()
# Disabled flaky test TNL-559
xdescribe "SystemFeedback multiple secondary actions", ->
beforeEach ->
@secondarySpyOne = jasmine.createSpy('secondarySpyOne')
@secondarySpyTwo = jasmine.createSpy('secondarySpyTwo')
@view = new NotificationView.Warning(
title: "No Primary",
message: "Pick a secondary action",
actions:
secondary: [
{
text: "Option One"
class: "option-one"
click: @secondarySpyOne
}, {
text: "Option Two"
class: "option-two"
click: @secondarySpyTwo
}
]
)
@view.show()
it "should render both", ->
expect(@view.el).toContain(".action-secondary.option-one")
expect(@view.el).toContain(".action-secondary.option-two")
expect(@view.el).not.toContain(".action-secondary.option-one.option-two")
expect(@view.$(".action-secondary.option-one")).toContainText("Option One")
expect(@view.$(".action-secondary.option-two")).toContainText("Option Two")
it "should differentiate clicks (1)", ->
@view.$(".option-one").click()
expect(@secondarySpyOne).toHaveBeenCalled()
expect(@secondarySpyTwo).not.toHaveBeenCalled()
it "should differentiate clicks (2)", ->
@view.$(".option-two").click()
expect(@secondarySpyOne).not.toHaveBeenCalled()
expect(@secondarySpyTwo).toHaveBeenCalled()
describe "NotificationView minShown and maxShown", ->
beforeEach ->
@showSpy = spyOn(NotificationView.Confirmation.prototype, 'show')
@showSpy.andCallThrough()
@hideSpy = spyOn(NotificationView.Confirmation.prototype, 'hide')
@hideSpy.andCallThrough()
@clock = sinon.useFakeTimers()
afterEach ->
@clock.restore()
# Disabled flaky test TNL-559
xit "should not have minShown or maxShown by default", ->
view = new NotificationView.Confirmation()
expect(view.options.minShown).toEqual(0)
expect(view.options.maxShown).toEqual(Infinity)
# Disabled flaky test TNL-559
xit "a minShown view should not hide too quickly", ->
view = new NotificationView.Confirmation({minShown: 1000})
view.show()
expect(view.$('.wrapper')).toBeShown()
# call hide() on it, but the minShown should prevent it from hiding right away
view.hide()
expect(view.$('.wrapper')).toBeShown()
# wait for the minShown timeout to expire, and check again
@clock.tick(1001)
expect(view.$('.wrapper')).toBeHiding()
# Disabled flaky test TNL-559
xit "a maxShown view should hide by itself", ->
view = new NotificationView.Confirmation({maxShown: 1000})
view.show()
expect(view.$('.wrapper')).toBeShown()
# wait for the maxShown timeout to expire, and check again
@clock.tick(1001)
expect(view.$('.wrapper')).toBeHiding()
# Disabled flaky test TNL-559
xit "a minShown view can stay visible longer", ->
view = new NotificationView.Confirmation({minShown: 1000})
view.show()
expect(view.$('.wrapper')).toBeShown()
# wait for the minShown timeout to expire, and check again
@clock.tick(1001)
expect(@hideSpy).not.toHaveBeenCalled()
expect(view.$('.wrapper')).toBeShown()
# can now hide immediately
view.hide()
expect(view.$('.wrapper')).toBeHiding()
# Disabled flaky test TNL-559
xit "a maxShown view can hide early", ->
view = new NotificationView.Confirmation({maxShown: 1000})
view.show()
expect(view.$('.wrapper')).toBeShown()
# wait 50 milliseconds, and hide it early
@clock.tick(50)
view.hide()
expect(view.$('.wrapper')).toBeHiding()
# wait for timeout to expire, make sure it doesn't do anything weird
@clock.tick(1000)
expect(view.$('.wrapper')).toBeHiding()
it "a view can have both maxShown and minShown", ->
view = new NotificationView.Confirmation({minShown: 1000, maxShown: 2000})
view.show()
# can't hide early
@clock.tick(50)
view.hide()
expect(view.$('.wrapper')).toBeShown()
@clock.tick(1000)
expect(view.$('.wrapper')).toBeHiding()
# show it again, and let it hide automatically
view.show()
@clock.tick(1050)
expect(view.$('.wrapper')).toBeShown()
@clock.tick(1000)
expect(view.$('.wrapper')).toBeHiding()
/** /**
* Provides useful utilities for views. * Provides useful utilities for views.
*/ */
define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js/views/feedback_prompt"], ;(function (define) {
'use strict';
define(["jquery", "underscore", "gettext", "common/js/components/views/feedback_notification",
"common/js/components/views/feedback_prompt"],
function ($, _, gettext, NotificationView, PromptView) { function ($, _, gettext, NotificationView, PromptView) {
var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation, var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation,
runOperationShowingMessage, disableElementWhileRunning, getScrollOffset, setScrollOffset, runOperationShowingMessage, disableElementWhileRunning, getScrollOffset, setScrollOffset,
...@@ -246,3 +249,4 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js ...@@ -246,3 +249,4 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
'checkTotalKeyLengthViolations': checkTotalKeyLengthViolations 'checkTotalKeyLengthViolations': checkTotalKeyLengthViolations
}; };
}); });
}).call(this, define || RequireJS.define);
define(["jquery", "underscore", "underscore.string", "backbone", "js/utils/templates"], ;(function (define) {
function($, _, str, Backbone, TemplateUtils) { 'use strict';
var SystemFeedback = Backbone.View.extend({ define(["jquery",
options: { "underscore",
title: "", "underscore.string",
message: "", "backbone",
intent: null, // "warning", "confirmation", "error", "announcement", "step-required", etc "text!common/templates/components/system-feedback.underscore"],
type: null, // "alert", "notification", or "prompt": set by subclass function($, _, str, Backbone, systemFeedbackTemplate) {
shown: true, // is this view currently being shown? var SystemFeedback = Backbone.View.extend({
icon: true, // should we render an icon related to the message intent? options: {
closeIcon: true, // should we render a close button in the top right corner? title: "",
minShown: 0, // length of time after this view has been shown before it can be hidden (milliseconds) message: "",
maxShown: Infinity // length of time after this view has been shown before it will be automatically hidden (milliseconds) intent: null, // "warning", "confirmation", "error", "announcement", "step-required", etc
type: null, // "alert", "notification", or "prompt": set by subclass
shown: true, // is this view currently being shown?
icon: true, // should we render an icon related to the message intent?
closeIcon: true, // should we render a close button in the top right corner?
minShown: 0, // length of time after this view has been shown before it can be hidden (milliseconds)
maxShown: Infinity // length of time after this view has been shown before it will be automatically hidden (milliseconds)
/* Could also have an "actions" hash: here is an example demonstrating /* Could also have an "actions" hash: here is an example demonstrating
the expected structure. For each action, by default the framework the expected structure. For each action, by default the framework
will call preventDefault on the click event before the function is will call preventDefault on the click event before the function is
run; to make it not do that, just pass `preventDefault: false` in run; to make it not do that, just pass `preventDefault: false` in
the action object. the action object.
actions: { actions: {
primary: { primary: {
"text": "Save", "text": "Save",
"class": "action-save", "class": "action-save",
"click": function(view) { "click": function(view) {
// do something when Save is clicked // do something when Save is clicked
} }
}, },
secondary: [ secondary: [
{ {
"text": "Cancel", "text": "Cancel",
"class": "action-cancel", "class": "action-cancel",
"click": function(view) {} "click": function(view) {}
}, { }, {
"text": "Discard Changes", "text": "Discard Changes",
"class": "action-discard", "class": "action-discard",
"click": function(view) {} "click": function(view) {}
} }
] ]
}
*/
},
initialize: function() {
if (!this.options.type) {
throw "SystemFeedback: type required (given " +
JSON.stringify(this.options) + ")";
} }
if (!this.options.intent) { */
throw "SystemFeedback: intent required (given " + },
JSON.stringify(this.options) + ")";
}
this.template = TemplateUtils.loadTemplate("system-feedback");
this.setElement($("#page-" + this.options.type));
// handle single "secondary" action
if (this.options.actions && this.options.actions.secondary &&
!_.isArray(this.options.actions.secondary)) {
this.options.actions.secondary = [this.options.actions.secondary];
}
return this;
},
// public API: show() and hide() initialize: function() {
show: function() { if (!this.options.type) {
clearTimeout(this.hideTimeout); throw "SystemFeedback: type required (given " +
this.options.shown = true; JSON.stringify(this.options) + ")";
this.shownAt = new Date(); }
this.render(); if (!this.options.intent) {
if ($.isNumeric(this.options.maxShown)) { throw "SystemFeedback: intent required (given " +
this.hideTimeout = setTimeout(_.bind(this.hide, this), JSON.stringify(this.options) + ")";
this.options.maxShown); }
} this.setElement($("#page-" + this.options.type));
return this; // handle single "secondary" action
}, if (this.options.actions && this.options.actions.secondary &&
!_.isArray(this.options.actions.secondary)) {
this.options.actions.secondary = [this.options.actions.secondary];
}
return this;
},
hide: function() { // public API: show() and hide()
if (this.shownAt && $.isNumeric(this.options.minShown) && show: function() {
this.options.minShown > new Date() - this.shownAt) {
clearTimeout(this.hideTimeout); clearTimeout(this.hideTimeout);
this.hideTimeout = setTimeout(_.bind(this.hide, this), this.options.shown = true;
this.options.minShown - (new Date() - this.shownAt)); this.shownAt = new Date();
} else {
this.options.shown = false;
delete this.shownAt;
this.render(); this.render();
} if ($.isNumeric(this.options.maxShown)) {
return this; this.hideTimeout = setTimeout(_.bind(this.hide, this),
}, this.options.maxShown);
}
return this;
},
hide: function() {
if (this.shownAt && $.isNumeric(this.options.minShown) &&
this.options.minShown > new Date() - this.shownAt) {
clearTimeout(this.hideTimeout);
this.hideTimeout = setTimeout(_.bind(this.hide, this),
this.options.minShown - (new Date() - this.shownAt));
} else {
this.options.shown = false;
delete this.shownAt;
this.render();
}
return this;
},
// the rest of the API should be considered semi-private // the rest of the API should be considered semi-private
events: { events: {
"click .action-close": "hide", "click .action-close": "hide",
"click .action-primary": "primaryClick", "click .action-primary": "primaryClick",
"click .action-secondary": "secondaryClick" "click .action-secondary": "secondaryClick"
}, },
render: function() { render: function() {
// there can be only one active view of a given type at a time: only // there can be only one active view of a given type at a time: only
// one alert, only one notification, only one prompt. Therefore, we'll // one alert, only one notification, only one prompt. Therefore, we'll
// use a singleton approach. // use a singleton approach.
var singleton = SystemFeedback["active_" + this.options.type]; var singleton = SystemFeedback["active_" + this.options.type];
if (singleton && singleton !== this) { if (singleton && singleton !== this) {
singleton.stopListening(); singleton.stopListening();
singleton.undelegateEvents(); singleton.undelegateEvents();
} }
this.$el.html(this.template(this.options)); this.$el.html(_.template(systemFeedbackTemplate)(this.options));
SystemFeedback["active_" + this.options.type] = this; SystemFeedback["active_" + this.options.type] = this;
return this; return this;
}, },
primaryClick: function(event) { primaryClick: function(event) {
var actions, primary; var actions, primary;
actions = this.options.actions; actions = this.options.actions;
if (!actions) { return; } if (!actions) { return; }
primary = actions.primary; primary = actions.primary;
if (!primary) { return; } if (!primary) { return; }
if (primary.preventDefault !== false) { if (primary.preventDefault !== false) {
event.preventDefault(); event.preventDefault();
} }
if (primary.click) { if (primary.click) {
primary.click.call(event.target, this, event); primary.click.call(event.target, this, event);
} }
}, },
secondaryClick: function(event) { secondaryClick: function(event) {
var actions, secondaryList, secondary, i; var actions, secondaryList, secondary, i;
actions = this.options.actions; actions = this.options.actions;
if (!actions) { return; } if (!actions) { return; }
secondaryList = actions.secondary; secondaryList = actions.secondary;
if (!secondaryList) { return; } if (!secondaryList) { return; }
// which secondary action was clicked? // which secondary action was clicked?
i = 0; // default to the first secondary action (easier for testing) i = 0; // default to the first secondary action (easier for testing)
if (event && event.target) { if (event && event.target) {
i = _.indexOf(this.$(".action-secondary"), event.target); i = _.indexOf(this.$(".action-secondary"), event.target);
} }
secondary = secondaryList[i]; secondary = secondaryList[i];
if (secondary.preventDefault !== false) { if (secondary.preventDefault !== false) {
event.preventDefault(); event.preventDefault();
} }
if (secondary.click) { if (secondary.click) {
secondary.click.call(event.target, this, event); secondary.click.call(event.target, this, event);
}
} }
} });
return SystemFeedback;
}); });
return SystemFeedback; }).call(this, define || RequireJS.define);
});
define(["jquery", "underscore", "underscore.string", "js/views/feedback"], function($, _, str, SystemFeedbackView) { ;(function (define) {
var Alert = SystemFeedbackView.extend({ 'use strict';
options: $.extend({}, SystemFeedbackView.prototype.options, { define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
type: "alert" function($, _, str, SystemFeedbackView) {
}), str = str || _.str;
slide_speed: 900, var Alert = SystemFeedbackView.extend({
show: function() { options: $.extend({}, SystemFeedbackView.prototype.options, {
SystemFeedbackView.prototype.show.apply(this, arguments); type: "alert"
this.$el.hide(); }),
this.$el.slideDown(this.slide_speed); slide_speed: 900,
return this; show: function() {
}, SystemFeedbackView.prototype.show.apply(this, arguments);
hide: function () { this.$el.hide();
this.$el.slideUp({ this.$el.slideDown(this.slide_speed);
duration: this.slide_speed return this;
}); },
setTimeout(_.bind(SystemFeedbackView.prototype.hide, this, arguments), hide: function () {
this.slideSpeed); this.$el.slideUp({
} duration: this.slide_speed
}); });
setTimeout(_.bind(SystemFeedbackView.prototype.hide, this, arguments),
this.slideSpeed);
}
});
// create Alert.Warning, Alert.Confirmation, etc // create Alert.Warning, Alert.Confirmation, etc
var capitalCamel, intents; var capitalCamel, intents;
capitalCamel = _.compose(str.capitalize, str.camelize); capitalCamel = _.compose(str.capitalize, str.camelize);
intents = ["warning", "error", "confirmation", "announcement", "step-required", "help", "mini"]; intents = ["warning", "error", "confirmation", "announcement", "step-required", "help", "mini"];
_.each(intents, function(intent) { _.each(intents, function(intent) {
var subclass; var subclass;
subclass = Alert.extend({ subclass = Alert.extend({
options: $.extend({}, Alert.prototype.options, { options: $.extend({}, Alert.prototype.options, {
intent: intent intent: intent
}) })
});
Alert[capitalCamel(intent)] = subclass;
}); });
Alert[capitalCamel(intent)] = subclass;
});
return Alert; return Alert;
}); });
}).call(this, define || RequireJS.define);
define(["jquery", "underscore", "underscore.string", "js/views/feedback"], function($, _, str, SystemFeedbackView) { ;(function (define) {
var Notification = SystemFeedbackView.extend({ 'use strict';
options: $.extend({}, SystemFeedbackView.prototype.options, { define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
type: "notification", function($, _, str, SystemFeedbackView) {
closeIcon: false str = str || _.str;
}) var Notification = SystemFeedbackView.extend({
}); options: $.extend({}, SystemFeedbackView.prototype.options, {
type: "notification",
// create Notification.Warning, Notification.Confirmation, etc closeIcon: false
var capitalCamel, intents;
capitalCamel = _.compose(str.capitalize, str.camelize);
intents = ["warning", "error", "confirmation", "announcement", "step-required", "help", "mini"];
_.each(intents, function(intent) {
var subclass;
subclass = Notification.extend({
options: $.extend({}, Notification.prototype.options, {
intent: intent
}) })
}); });
Notification[capitalCamel(intent)] = subclass;
});
// set more sensible defaults for Notification.Mini views // create Notification.Warning, Notification.Confirmation, etc
var miniOptions = Notification.Mini.prototype.options; var capitalCamel, intents;
miniOptions.minShown = 1250; capitalCamel = _.compose(str.capitalize, str.camelize);
miniOptions.closeIcon = false; intents = ["warning", "error", "confirmation", "announcement", "step-required", "help", "mini"];
_.each(intents, function(intent) {
var subclass;
subclass = Notification.extend({
options: $.extend({}, Notification.prototype.options, {
intent: intent
})
});
Notification[capitalCamel(intent)] = subclass;
});
// set more sensible defaults for Notification.Mini views
var miniOptions = Notification.Mini.prototype.options;
miniOptions.minShown = 1250;
miniOptions.closeIcon = false;
return Notification; return Notification;
}); });
}).call(this, define || RequireJS.define);
define(["jquery", "underscore", "underscore.string", "js/views/feedback"], function($, _, str, SystemFeedbackView) { ;(function (define) {
var Prompt = SystemFeedbackView.extend({ 'use strict';
options: $.extend({}, SystemFeedbackView.prototype.options, { define(["jquery", "underscore", "underscore.string", "common/js/components/views/feedback"],
type: "prompt", function($, _, str, SystemFeedbackView) {
closeIcon: false, str = str || _.str;
icon: false var Prompt = SystemFeedbackView.extend({
}), options: $.extend({}, SystemFeedbackView.prototype.options, {
render: function() { type: "prompt",
if(!window.$body) { window.$body = $(document.body); } closeIcon: false,
if(this.options.shown) { icon: false
$body.addClass('prompt-is-shown'); }),
} else { render: function() {
$body.removeClass('prompt-is-shown'); if(!window.$body) { window.$body = $(document.body); }
} if(this.options.shown) {
// super() in Javascript has awkward syntax :( $body.addClass('prompt-is-shown');
return SystemFeedbackView.prototype.render.apply(this, arguments); } else {
} $body.removeClass('prompt-is-shown');
}); }
// super() in Javascript has awkward syntax :(
return SystemFeedbackView.prototype.render.apply(this, arguments);
}
});
// create Prompt.Warning, Prompt.Confirmation, etc // create Prompt.Warning, Prompt.Confirmation, etc
var capitalCamel, intents; var capitalCamel, intents;
capitalCamel = _.compose(str.capitalize, str.camelize); capitalCamel = _.compose(str.capitalize, str.camelize);
intents = ["warning", "error", "confirmation", "announcement", "step-required", "help", "mini"]; intents = ["warning", "error", "confirmation", "announcement", "step-required", "help", "mini"];
_.each(intents, function(intent) { _.each(intents, function(intent) {
var subclass; var subclass;
subclass = Prompt.extend({ subclass = Prompt.extend({
options: $.extend({}, Prompt.prototype.options, { options: $.extend({}, Prompt.prototype.options, {
intent: intent intent: intent
}) })
}); });
Prompt[capitalCamel(intent)] = subclass; Prompt[capitalCamel(intent)] = subclass;
}); });
return Prompt; return Prompt;
}); });
}).call(this, define || RequireJS.define);
// Generated by CoffeeScript 1.6.1
(function() {
define(["jquery", "common/js/components/views/feedback", "common/js/components/views/feedback_notification", "common/js/components/views/feedback_alert", "common/js/components/views/feedback_prompt", "sinon"], function($, SystemFeedback, NotificationView, AlertView, PromptView, sinon) {
var tpl;
tpl = readFixtures('system-feedback.underscore');
beforeEach(function() {
setFixtures(sandbox({
id: "page-alert"
}));
appendSetFixtures(sandbox({
id: "page-notification"
}));
appendSetFixtures(sandbox({
id: "page-prompt"
}));
appendSetFixtures($("<script>", {
id: "system-feedback-tpl",
type: "text/template"
}).text(tpl));
return this.addMatchers({
toBeShown: function() {
return this.actual.hasClass("is-shown") && !this.actual.hasClass("is-hiding");
},
toBeHiding: function() {
return this.actual.hasClass("is-hiding") && !this.actual.hasClass("is-shown");
},
toContainText: function(text) {
var trimmedText;
trimmedText = $.trim(this.actual.text());
if (text && $.isFunction(text.test)) {
return text.test(trimmedText);
} else {
return trimmedText.indexOf(text) !== -1;
}
},
toHaveBeenPrevented: function() {
var eventName, selector;
eventName = this.actual.eventName;
selector = this.actual.selector;
this.message = function() {
return ["Expected event " + eventName + " to have been prevented on " + selector, "Expected event " + eventName + " not to have been prevented on " + selector];
};
return jasmine.JQuery.events.wasPrevented(selector, eventName);
}
});
});
describe("SystemFeedback", function() {
beforeEach(function() {
this.options = {
title: "Portal",
message: "Welcome to the Aperture Science Computer-Aided Enrichment Center"
};
this.renderSpy = spyOn(AlertView.Confirmation.prototype, 'render').andCallThrough();
this.showSpy = spyOn(AlertView.Confirmation.prototype, 'show').andCallThrough();
this.hideSpy = spyOn(AlertView.Confirmation.prototype, 'hide').andCallThrough();
return this.clock = sinon.useFakeTimers();
});
afterEach(function() {
return this.clock.restore();
});
it("requires a type and an intent", function() {
var both, neither, noIntent, noType,
_this = this;
neither = function() {
return new SystemFeedback(_this.options);
};
noType = function() {
var options;
options = $.extend({}, _this.options);
options.intent = "confirmation";
return new SystemFeedback(options);
};
noIntent = function() {
var options;
options = $.extend({}, _this.options);
options.type = "alert";
return new SystemFeedback(options);
};
both = function() {
var options;
options = $.extend({}, _this.options);
options.type = "alert";
options.intent = "confirmation";
return new SystemFeedback(options);
};
expect(neither).toThrow();
expect(noType).toThrow();
expect(noIntent).toThrow();
return expect(both).not.toThrow();
});
it("does not show on initalize", function() {
var view;
view = new AlertView.Confirmation(this.options);
expect(this.renderSpy).not.toHaveBeenCalled();
return expect(this.showSpy).not.toHaveBeenCalled();
});
xit("renders the template", function() {
var view;
view = new AlertView.Confirmation(this.options);
view.show();
expect(view.$(".action-close")).toBeDefined();
expect(view.$('.wrapper')).toBeShown();
expect(view.$el).toContainText(this.options.title);
return expect(view.$el).toContainText(this.options.message);
});
return xit("close button sends a .hide() message", function() {
var view;
view = new AlertView.Confirmation(this.options).show();
view.$(".action-close").click();
expect(this.hideSpy).toHaveBeenCalled();
this.clock.tick(900);
return expect(view.$('.wrapper')).toBeHiding();
});
});
describe("PromptView", function() {
return it("changes class on body", function() {
var view;
view = new PromptView.Confirmation({
title: "Portal",
message: "Welcome to the Aperture Science Computer-Aided Enrichment Center"
});
return view.hide();
});
});
describe("NotificationView.Mini", function() {
beforeEach(function() {
return this.view = new NotificationView.Mini();
});
it("should have minShown set to 1250 by default", function() {
return expect(this.view.options.minShown).toEqual(1250);
});
return it("should have closeIcon set to false by default", function() {
return expect(this.view.options.closeIcon).toBeFalsy();
});
});
xdescribe("SystemFeedback click events", function() {
beforeEach(function() {
this.primaryClickSpy = jasmine.createSpy('primaryClick');
this.secondaryClickSpy = jasmine.createSpy('secondaryClick');
this.view = new NotificationView.Warning({
title: "Unsaved",
message: "Your content is currently Unsaved.",
actions: {
primary: {
text: "Save",
"class": "save-button",
click: this.primaryClickSpy
},
secondary: {
text: "Revert",
"class": "cancel-button",
click: this.secondaryClickSpy
}
}
});
return this.view.show();
});
it("should trigger the primary event on a primary click", function() {
this.view.$(".action-primary").click();
expect(this.primaryClickSpy).toHaveBeenCalled();
return expect(this.secondaryClickSpy).not.toHaveBeenCalled();
});
it("should trigger the secondary event on a secondary click", function() {
this.view.$(".action-secondary").click();
expect(this.secondaryClickSpy).toHaveBeenCalled();
return expect(this.primaryClickSpy).not.toHaveBeenCalled();
});
it("should apply class to primary action", function() {
return expect(this.view.$(".action-primary")).toHaveClass("save-button");
});
it("should apply class to secondary action", function() {
return expect(this.view.$(".action-secondary")).toHaveClass("cancel-button");
});
it("should preventDefault on primary action", function() {
spyOnEvent(".action-primary", "click");
this.view.$(".action-primary").click();
return expect("click").toHaveBeenPreventedOn(".action-primary");
});
return it("should preventDefault on secondary action", function() {
spyOnEvent(".action-secondary", "click");
this.view.$(".action-secondary").click();
return expect("click").toHaveBeenPreventedOn(".action-secondary");
});
});
xdescribe("SystemFeedback not preventing events", function() {
beforeEach(function() {
this.clickSpy = jasmine.createSpy('clickSpy');
this.view = new AlertView.Confirmation({
title: "It's all good",
message: "No reason for this alert",
actions: {
primary: {
text: "Whatever",
click: this.clickSpy,
preventDefault: false
}
}
});
return this.view.show();
});
return it("should not preventDefault", function() {
spyOnEvent(".action-primary", "click");
this.view.$(".action-primary").click();
expect("click").not.toHaveBeenPreventedOn(".action-primary");
return expect(this.clickSpy).toHaveBeenCalled();
});
});
xdescribe("SystemFeedback multiple secondary actions", function() {
beforeEach(function() {
this.secondarySpyOne = jasmine.createSpy('secondarySpyOne');
this.secondarySpyTwo = jasmine.createSpy('secondarySpyTwo');
this.view = new NotificationView.Warning({
title: "No Primary",
message: "Pick a secondary action",
actions: {
secondary: [
{
text: "Option One",
"class": "option-one",
click: this.secondarySpyOne
}, {
text: "Option Two",
"class": "option-two",
click: this.secondarySpyTwo
}
]
}
});
return this.view.show();
});
it("should render both", function() {
expect(this.view.el).toContain(".action-secondary.option-one");
expect(this.view.el).toContain(".action-secondary.option-two");
expect(this.view.el).not.toContain(".action-secondary.option-one.option-two");
expect(this.view.$(".action-secondary.option-one")).toContainText("Option One");
return expect(this.view.$(".action-secondary.option-two")).toContainText("Option Two");
});
it("should differentiate clicks (1)", function() {
this.view.$(".option-one").click();
expect(this.secondarySpyOne).toHaveBeenCalled();
return expect(this.secondarySpyTwo).not.toHaveBeenCalled();
});
return it("should differentiate clicks (2)", function() {
this.view.$(".option-two").click();
expect(this.secondarySpyOne).not.toHaveBeenCalled();
return expect(this.secondarySpyTwo).toHaveBeenCalled();
});
});
return describe("NotificationView minShown and maxShown", function() {
beforeEach(function() {
this.showSpy = spyOn(NotificationView.Confirmation.prototype, 'show');
this.showSpy.andCallThrough();
this.hideSpy = spyOn(NotificationView.Confirmation.prototype, 'hide');
this.hideSpy.andCallThrough();
return this.clock = sinon.useFakeTimers();
});
afterEach(function() {
return this.clock.restore();
});
xit("should not have minShown or maxShown by default", function() {
var view;
view = new NotificationView.Confirmation();
expect(view.options.minShown).toEqual(0);
return expect(view.options.maxShown).toEqual(Infinity);
});
xit("a minShown view should not hide too quickly", function() {
var view;
view = new NotificationView.Confirmation({
minShown: 1000
});
view.show();
expect(view.$('.wrapper')).toBeShown();
view.hide();
expect(view.$('.wrapper')).toBeShown();
this.clock.tick(1001);
return expect(view.$('.wrapper')).toBeHiding();
});
xit("a maxShown view should hide by itself", function() {
var view;
view = new NotificationView.Confirmation({
maxShown: 1000
});
view.show();
expect(view.$('.wrapper')).toBeShown();
this.clock.tick(1001);
return expect(view.$('.wrapper')).toBeHiding();
});
xit("a minShown view can stay visible longer", function() {
var view;
view = new NotificationView.Confirmation({
minShown: 1000
});
view.show();
expect(view.$('.wrapper')).toBeShown();
this.clock.tick(1001);
expect(this.hideSpy).not.toHaveBeenCalled();
expect(view.$('.wrapper')).toBeShown();
view.hide();
return expect(view.$('.wrapper')).toBeHiding();
});
xit("a maxShown view can hide early", function() {
var view;
view = new NotificationView.Confirmation({
maxShown: 1000
});
view.show();
expect(view.$('.wrapper')).toBeShown();
this.clock.tick(50);
view.hide();
expect(view.$('.wrapper')).toBeHiding();
this.clock.tick(1000);
return expect(view.$('.wrapper')).toBeHiding();
});
return it("a view can have both maxShown and minShown", function() {
var view;
view = new NotificationView.Confirmation({
minShown: 1000,
maxShown: 2000
});
view.show();
this.clock.tick(50);
view.hide();
expect(view.$('.wrapper')).toBeShown();
this.clock.tick(1000);
expect(view.$('.wrapper')).toBeHiding();
view.show();
this.clock.tick(1050);
expect(view.$('.wrapper')).toBeShown();
this.clock.tick(1000);
return expect(view.$('.wrapper')).toBeHiding();
});
});
});
}).call(this);
define(["jquery", "underscore", "js/views/baseview", "js/views/utils/view_utils", "js/spec_helpers/edit_helpers"], ;(function (define) {
function ($, _, BaseView, ViewUtils, ViewHelpers) { 'use strict';
define(["jquery", "underscore", "common/js/components/utils/view_utils", "common/js/spec_helpers/view_helpers", 'jasmine-stealth'],
function ($, _, ViewUtils, ViewHelpers) {
describe("ViewUtils", function() { describe("ViewUtils", function() {
describe("disabled element while running", function() { describe("disabled element while running", function() {
...@@ -90,3 +92,4 @@ define(["jquery", "underscore", "js/views/baseview", "js/views/utils/view_utils" ...@@ -90,3 +92,4 @@ define(["jquery", "underscore", "js/views/baseview", "js/views/utils/view_utils"
}); });
}); });
}); });
}).call(this, define || RequireJS.define);
\ No newline at end of file
/** /**
* Provides helper methods for invoking Studio modal windows in Jasmine tests. * Provides helper methods for invoking Studio modal windows in Jasmine tests.
*/ */
define(["jquery", "js/views/feedback_notification", "js/views/feedback_prompt", 'common/js/spec_helpers/ajax_helpers', ;(function (define) {
"common/js/spec_helpers/template_helpers"], 'use strict';
function($, NotificationView, Prompt, AjaxHelpers, TemplateHelpers) { define(["jquery", "common/js/components/views/feedback_notification", "common/js/components/views/feedback_prompt",
'common/js/spec_helpers/ajax_helpers'],
function($, NotificationView, Prompt, AjaxHelpers) {
var installViewTemplates, createFeedbackSpy, verifyFeedbackShowing, var installViewTemplates, createFeedbackSpy, verifyFeedbackShowing,
verifyFeedbackHidden, createNotificationSpy, verifyNotificationShowing, verifyFeedbackHidden, createNotificationSpy, verifyNotificationShowing,
verifyNotificationHidden, createPromptSpy, confirmPrompt, inlineEdit, verifyInlineEditChange, verifyNotificationHidden, createPromptSpy, confirmPrompt, inlineEdit, verifyInlineEditChange,
installMockAnalytics, removeMockAnalytics, verifyPromptShowing, verifyPromptHidden; installMockAnalytics, removeMockAnalytics, verifyPromptShowing, verifyPromptHidden,
clickDeleteItem, patchAndVerifyRequest, submitAndVerifyFormSuccess, submitAndVerifyFormError;
installViewTemplates = function(append) { installViewTemplates = function() {
TemplateHelpers.installTemplate('system-feedback', !append);
appendSetFixtures('<div id="page-notification"></div>'); appendSetFixtures('<div id="page-notification"></div>');
}; };
...@@ -144,3 +146,4 @@ define(["jquery", "js/views/feedback_notification", "js/views/feedback_prompt", ...@@ -144,3 +146,4 @@ define(["jquery", "js/views/feedback_notification", "js/views/feedback_prompt",
'submitAndVerifyFormError': submitAndVerifyFormError 'submitAndVerifyFormError': submitAndVerifyFormError
}; };
}); });
}).call(this, define || RequireJS.define);
...@@ -21,17 +21,16 @@ ...@@ -21,17 +21,16 @@
<% if(obj.actions) { %> <% if(obj.actions) { %>
<nav class="nav-actions"> <nav class="nav-actions">
<h3 class="sr"><%= type %> Actions</h3>
<ul> <ul>
<% if(actions.primary) { %> <% if(actions.primary) { %>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button action-primary <%= actions.primary.class %>"><%= actions.primary.text %></a> <button class="action-primary <%= actions.primary.class %>"><%= actions.primary.text %></button>
</li> </li>
<% } %> <% } %>
<% if(actions.secondary) { <% if(actions.secondary) {
_.each(actions.secondary, function(secondary) { %> _.each(actions.secondary, function(secondary) { %>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button action-secondary <%= secondary.class %>"><%= secondary.text %></a> <button class="action-secondary <%= secondary.class %>"><%= secondary.text %></button>
</li> </li>
<% }); <% });
} %> } %>
......
...@@ -74,13 +74,13 @@ define([ ...@@ -74,13 +74,13 @@ define([
return profileView; return profileView;
}; };
clickLeaveTeam = function(requests, view, confirmLeave) { clickLeaveTeam = function(requests, view, options) {
expect(view.$(leaveTeamLinkSelector).length).toBe(1); expect(view.$(leaveTeamLinkSelector).length).toBe(1);
// click on Leave Team link under Team Details // click on Leave Team link under Team Details
view.$(leaveTeamLinkSelector).click(); view.$(leaveTeamLinkSelector).click();
if (confirmLeave) { if (!options.cancel) {
// click on Confirm button on dialog // click on Confirm button on dialog
$('.prompt.warning .action-primary').click(); $('.prompt.warning .action-primary').click();
...@@ -121,7 +121,7 @@ define([ ...@@ -121,7 +121,7 @@ define([
view = createTeamProfileView(requests, {membership: DEFAULT_MEMBERSHIP}); view = createTeamProfileView(requests, {membership: DEFAULT_MEMBERSHIP});
expect(view.$('.new-post-btn').length).toEqual(1); expect(view.$('.new-post-btn').length).toEqual(1);
clickLeaveTeam(requests, view, true); clickLeaveTeam(requests, view, {cancel: false});
expect(view.$('.new-post-btn').length).toEqual(0); expect(view.$('.new-post-btn').length).toEqual(0);
}); });
}); });
...@@ -188,7 +188,7 @@ define([ ...@@ -188,7 +188,7 @@ define([
requests, {country: 'US', language: 'en', membership: DEFAULT_MEMBERSHIP} requests, {country: 'US', language: 'en', membership: DEFAULT_MEMBERSHIP}
); );
assertTeamDetails(view, 1, true); assertTeamDetails(view, 1, true);
clickLeaveTeam(requests, view, true); clickLeaveTeam(requests, view, {cancel: false});
assertTeamDetails(view, 0, false); assertTeamDetails(view, 0, false);
}); });
...@@ -199,7 +199,7 @@ define([ ...@@ -199,7 +199,7 @@ define([
requests, {country: 'US', language: 'en', membership: DEFAULT_MEMBERSHIP} requests, {country: 'US', language: 'en', membership: DEFAULT_MEMBERSHIP}
); );
assertTeamDetails(view, 1, true); assertTeamDetails(view, 1, true);
clickLeaveTeam(requests, view, false); clickLeaveTeam(requests, view, {cancel: true});
assertTeamDetails(view, 1, true); assertTeamDetails(view, 1, true);
}); });
......
...@@ -81,4 +81,4 @@ ...@@ -81,4 +81,4 @@
// overrides // overrides
@import 'developer'; // used for any developer-created scss that needs further polish/refactoring @import 'developer'; // used for any developer-created scss that needs further polish/refactoring
@import 'shame'; // used for any bad-form/orphaned scss @import 'shame'; // used for any bad-form/orphaned scss
\ No newline at end of file
../../../common/static/sass/_mixins-inherited.scss
\ No newline at end of file
../../../common/static/sass/_mixins.scss
\ No newline at end of file
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