Added more tests, some refactoring

This commit is contained in:
Henry 2015-10-08 11:03:48 -07:00
parent 3af23b7bc5
commit 5ff90f7254
2 changed files with 200 additions and 101 deletions

View File

@ -23,13 +23,61 @@
/** /**
* This bundle implements the notification service, which can be used to * This bundle implements the notification service, which can be used to
* show banner notifications to the user. * show banner notifications to the user. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @namespace platform/commonUI/dialog * @namespace platform/commonUI/dialog
*/ */
define( define(
["./MessageSeverity"], ["./MessageSeverity"],
function (MessageSeverity) { function (MessageSeverity) {
"use strict"; "use strict";
/**
* A representation of a user action. Actions are provided to
* dialogs and notifications and are shown as buttons.
*
* @typedef {object} NotificationAction
* @property {string} label the label to appear on the button for
* this action
* @property {function} action a callback function to be invoked
* when the button is clicked
*/
/**
* A representation of a banner notification. Banner notifications
* are used to inform users of events in a non-intrusive way. As
* much as possible, notifications share a model with blocking
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @typedef {object} Notification
* @property {string} title The title of the message
* @property {number} progress The completion status of a task
* represented numerically
* @property {MessageSeverity} messageSeverity The importance of the
* message (eg. error, success)
* @property {boolean} unknownProgress a boolean indicating that the
* progress of the underlying task is unknown. This will result in a
* visually distinct progress bar.
* @property {boolean | number} autoDismiss If truthy, dialog will
* be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed.
* @property {NotificationAction} primaryAction the default user
* response to
* this message. Will be represented as a button with the provided
* label and action. May be used by banner notifications to display
* only the most important option to users.
* @property {NotificationAction[]} additionalActions any additional
* actions
* that the user can take. Will be represented as additional buttons
* that may or may not be available from a banner.
*/
/** /**
* The notification service is responsible for informing the user of * The notification service is responsible for informing the user of
* events via the use of banner notifications. * events via the use of banner notifications.
@ -41,69 +89,51 @@ define(
this.$timeout = $timeout; this.$timeout = $timeout;
this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS; this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS;
/** /*
* Exposes the current "active" notification. This is a * A context in which to hold the active notification and a
* notification that is of current highest importance that has * handle to its timeout.
* not been dismissed. The deinition of what is of highest
* importance might be a little nuanced and require tweaking.
* For example, if an important error message is visible and a
* success message is triggered, it may be desirable to
* temporarily show the success message and then auto-dismiss it.
* @type {{notification: undefined}}
*/ */
this.active = { this.active = {
}; };
} }
/**
var model = {
title: string,
progress: number,
severity: MessageSeverity,
unknownProgress: boolean,
minimized: boolean,
autoDismiss: boolean | number,
actions: {
label: string,
action: function
}
}
*/
/** /**
* Possibly refactor this out to a provider? * Returns the notification that is currently visible in the banner area
* @constructor * @returns {Notification}
*/ */
function Notification (model) {
this.model = model;
}
Notification.prototype.minimize = function (setValue) {
if (typeof setValue !== undefined){
this.model.minimized = setValue;
} else {
return this.model.minimized;
}
};
NotificationService.prototype.getActiveNotification = function (){ NotificationService.prototype.getActiveNotification = function (){
return this.active.notification; return this.active.notification;
} }
/** /**
* model = { * A convenience method for success notifications. Notifications
* * created via this method will be auto-dismissed after a default
* } * wait period
* @param model * @param {Notification} notification The notification to display
*/ */
NotificationService.prototype.notify = function (model) { NotificationService.prototype.success = function (notification) {
var notification = new Notification(model), notification.autoDismiss = notification.autoDismiss || true;
that=this; NotificationService.prototype.notify(notification);
}
/**
* Notifies the user of an event. If there is a banner notification
* already active, then it will be dismissed or minimized automatically,
* and the provided notification displayed in its place.
*
* @param {Notification} notification The notification to display
*/
NotificationService.prototype.notify = function (notification) {
/*var notification = new Notification(model),
that=this; */
var that = this;
this.notifications.push(notification); this.notifications.push(notification);
/* /*
Check if there is already an active (ie. visible) notification Check if there is already an active (ie. visible) notification
*/ */
if (!this.active.notification){ if (!this.active.notification){
setActiveNotification.call(this, notification); this.setActiveNotification(notification);
} else if (!this.active.timeout){ } else if (!this.active.timeout){
/* /*
@ -122,28 +152,42 @@ define(
}; };
function setActiveNotification (notification) { /**
var that = this; * Used internally by the NotificationService
this.active.notification = notification; * @private
/* */
If autoDismiss has been specified, setup a timeout to NotificationService.prototype.setActiveNotification =
dismiss the dialog. function (notification) {
If there are other notifications pending in the queue, set this var that = this;
one to auto-dismiss this.active.notification = notification;
*/ /*
if (notification.model.autoDismiss If autoDismiss has been specified, setup a timeout to
|| selectNextNotification.call(this)) { dismiss the dialog.
var timeout = isNaN(notification.model.autoDismiss) ?
this.DEFAULT_AUTO_DISMISS : notification.model.autoDismiss;
this.active.timeout = this.$timeout(function () { If there are other notifications pending in the queue, set this
that.dismissOrMinimize(notification); one to auto-dismiss
}, timeout); */
} if (notification && (notification.autoDismiss
} || this.selectNextNotification())) {
var timeout = isNaN(notification.autoDismiss) ?
this.DEFAULT_AUTO_DISMISS :
notification.autoDismiss;
function selectNextNotification () { this.active.timeout = this.$timeout(function () {
that.dismissOrMinimize(notification);
}, timeout);
} else {
delete this.active.timeout;
}
};
/**
* Used internally by the NotificationService
*
* @private
*/
NotificationService.prototype.selectNextNotification = function () {
/* /*
Loop through the notifications queue and find the first one that Loop through the notifications queue and find the first one that
has not already been minimized (manually or otherwise). has not already been minimized (manually or otherwise).
@ -151,7 +195,7 @@ define(
for (var i=0; i< this.notifications.length; i++) { for (var i=0; i< this.notifications.length; i++) {
var notification = this.notifications[i]; var notification = this.notifications[i];
if (!notification.model.minimized if (!notification.minimized
&& notification!= this.activeNotification) { && notification!= this.activeNotification) {
return notification; return notification;
@ -162,8 +206,9 @@ define(
/** /**
* Minimize a notification. The notification will still be available * Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a * from the notification list. Typically notifications with a
* severity of SUCCESS should not be minimized, but rather * severity of 'success' should not be minimized, but rather
* dismissed. * dismissed. If you're not sure which is appropriate,
* use {@link NotificationService#dismissOrMinimize}
* @see dismiss * @see dismiss
* @see dismissOrMinimize * @see dismissOrMinimize
* @param notification * @param notification
@ -172,19 +217,17 @@ define(
//Check this is a known notification //Check this is a known notification
var index = this.notifications.indexOf(notification); var index = this.notifications.indexOf(notification);
if (index >= 0) { if (index >= 0) {
notification.minimize(true); notification.minimized=true;
delete this.active.notification; this.setActiveNotification(this.selectNextNotification());
delete this.active.timeout;
setActiveNotification.call(this, selectNextNotification.call(this));
} }
} };
/** /**
* Completely remove a notification. This will dismiss it from the * Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications. * message banner and remove it from the list of notifications.
* Typically only notifications with a severity of SUCCESS should be * Typically only notifications with a severity of success should be
* dismissed. If you're not sure whether to dismiss or minimize a * dismissed. If you're not sure whether to dismiss or minimize a
* notification, use the dismissOrMinimize method. * notification, use {@link NotificationService#dismissOrMinimize}.
* dismiss * dismiss
* @see dismissOrMinimize * @see dismissOrMinimize
* @param notification The notification to dismiss * @param notification The notification to dismiss
@ -194,11 +237,7 @@ define(
var index = this.notifications.indexOf(notification); var index = this.notifications.indexOf(notification);
if (index >= 0) { if (index >= 0) {
this.notifications.splice(index, 1); this.notifications.splice(index, 1);
this.setActiveNotification(this.selectNextNotification());
delete this.active.notification;
delete this.active.timeout;
setActiveNotification.call(this, selectNextNotification.call(this));
} }
} }
@ -210,7 +249,7 @@ define(
* @param notification * @param notification
*/ */
NotificationService.prototype.dismissOrMinimize = function (notification){ NotificationService.prototype.dismissOrMinimize = function (notification){
if (notification.model.severity > MessageSeverity.SUCCESS){ if (notification.severity > MessageSeverity.SUCCESS){
this.minimize(notification); this.minimize(notification);
} else { } else {
this.dismiss(notification); this.dismiss(notification);

View File

@ -30,24 +30,18 @@ define(
var notificationService, var notificationService,
mockTimeout, mockTimeout,
mockAutoDismiss, mockAutoDismiss,
successModel = { successModel,
title: "Mock Success Notification", errorModel;
severity: MessageSeverity.SUCCESS
},
errorModel = {
title: "Mock Error Notification",
severity: MessageSeverity.ERROR
};
/** /**
* 1) Calling .notify results in a new notification being created * 1) Calling .notify results in a new notification being created
* with the provided model and set to the active notification * with the provided model and set to the active notification. DONE
* *
* 2) Calling .notify with autoDismiss results in a SUCCESS notification * 2) Calling .notify with autoDismiss results in a SUCCESS notification
* becoming dismissed after timeout has elapsed * becoming dismissed after timeout has elapsed DONE
* *
* 3) Calling .notify with autoDismiss results in an ERROR notification * 3) Calling .notify with autoDismiss results in an ERROR notification
* being MINIMIZED after a timeout has elapsed * being MINIMIZED after a timeout has elapsed DONE
* *
* 4) Calling .notify with an active success notification results in that * 4) Calling .notify with an active success notification results in that
* notification being auto-dismissed, and the new notification becoming * notification being auto-dismissed, and the new notification becoming
@ -84,17 +78,49 @@ define(
mockAutoDismiss = 0; mockAutoDismiss = 0;
notificationService = new NotificationService( notificationService = new NotificationService(
mockTimeout, mockAutoDismiss); mockTimeout, mockAutoDismiss);
successModel = {
title: "Mock Success Notification",
severity: MessageSeverity.SUCCESS
};
errorModel = {
title: "Mock Error Notification",
severity: MessageSeverity.ERROR
};
}); });
it("Calls the notification service with a new notification, making" + it("gets a new success notification, making" +
" the notification active", function() { " the notification active", function() {
var activeNotification; var activeNotification;
notificationService.notify(successModel); notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification(); activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(successModel); expect(activeNotification).toBe(successModel);
}); });
describe(" called with multiple notifications", function(){ it("gets a new success notification with" +
" numerical auto-dismiss specified. ", function() {
var activeNotification;
successModel.autoDismiss = 1000;
notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBe(successModel);
mockTimeout.mostRecentCall.args[0]();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBeUndefined();
});
it("gets a new notification with" +
" boolean auto-dismiss specified. ", function() {
var activeNotification;
successModel.autoDismiss = true;
notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBe(successModel);
mockTimeout.mostRecentCall.args[0]();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBeUndefined();
});
describe(" gets called with multiple notifications", function(){
it("auto-dismisses the previously active notification, making" + it("auto-dismisses the previously active notification, making" +
" the new notification active", function() { " the new notification active", function() {
var activeNotification; var activeNotification;
@ -103,14 +129,14 @@ define(
activeNotification = activeNotification =
notificationService.getActiveNotification(); notificationService.getActiveNotification();
//Initially expect the active notification to be success //Initially expect the active notification to be success
expect(activeNotification.model).toBe(successModel); expect(activeNotification).toBe(successModel);
//Then notify of an error //Then notify of an error
notificationService.notify(errorModel); notificationService.notify(errorModel);
//But it should be auto-dismissed and replaced with the //But it should be auto-dismissed and replaced with the
// error notification // error notification
mockTimeout.mostRecentCall.args[0](); mockTimeout.mostRecentCall.args[0]();
activeNotification = notificationService.getActiveNotification(); activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(errorModel); expect(activeNotification).toBe(errorModel);
}); });
it("auto-dismisses an active success notification, removing" + it("auto-dismisses an active success notification, removing" +
" it completely", function() { " it completely", function() {
@ -125,9 +151,9 @@ define(
}); });
it("auto-minimizes an active error notification", function() { it("auto-minimizes an active error notification", function() {
var activeNotification; var activeNotification;
//First pre-load with a success message //First pre-load with an error message
notificationService.notify(errorModel); notificationService.notify(errorModel);
//Then notify of an error //Then notify of success
notificationService.notify(successModel); notificationService.notify(successModel);
expect(notificationService.notifications.length).toEqual(2); expect(notificationService.notifications.length).toEqual(2);
//Mock the auto-minimize //Mock the auto-minimize
@ -137,8 +163,42 @@ define(
expect(notificationService.notifications.length).toEqual(2); expect(notificationService.notifications.length).toEqual(2);
activeNotification = activeNotification =
notificationService.getActiveNotification(); notificationService.getActiveNotification();
expect(activeNotification.model).toBe(successModel); expect(activeNotification).toBe(successModel);
expect(errorModel.minimized).toEqual(true); expect(errorModel.minimized).toEqual(true);
});
it("auto-minimizes errors when a number of them arrive in" +
" short succession ", function() {
var activeNotification;
var error2 = {
title: "Second Mock Error Notification",
severity: MessageSeverity.ERROR
}
var error3 = {
title: "Third Mock Error Notification",
severity: MessageSeverity.ERROR
}
//First pre-load with a success message
notificationService.notify(errorModel);
//Then notify of a third error
notificationService.notify(error2);
notificationService.notify(error3);
expect(notificationService.notifications.length).toEqual(3);
//Mock the auto-minimize
mockTimeout.mostRecentCall.args[0]();
//Previous error message should be minimized, not
// dismissed
expect(notificationService.notifications.length).toEqual(3);
activeNotification =
notificationService.getActiveNotification();
expect(activeNotification).toBe(error2);
expect(errorModel.minimized).toEqual(true);
//Mock the second auto-minimize
mockTimeout.mostRecentCall.args[0]();
activeNotification =
notificationService.getActiveNotification();
expect(activeNotification).toBe(error3);
expect(error2.minimized).toEqual(true);
}); });
}); });