mirror of
https://github.com/nasa/openmct.git
synced 2024-12-21 14:07:50 +00:00
Merge pull request #1222 from BogdanAlexandru/mct1221
[Notifications] Fix and harden the NotificationService
This commit is contained in:
commit
025b69541e
@ -61,7 +61,7 @@ define(
|
||||
* @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
|
||||
* @property {boolean} [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.
|
||||
@ -109,18 +109,18 @@ define(
|
||||
* @memberof platform/commonUI/notification
|
||||
* @constructor
|
||||
* @param $timeout the Angular $timeout service
|
||||
* @param DEFAULT_AUTO_DISMISS The period of time that an
|
||||
* @param defaultAutoDismissTimeout The period of time that an
|
||||
* auto-dismissed message will be displayed for.
|
||||
* @param MINIMIZE_TIMEOUT When notifications are minimized, a brief
|
||||
* @param minimizeAnimationTimeout When notifications are minimized, a brief
|
||||
* animation is shown. This animation requires some time to execute,
|
||||
* so a timeout is required before the notification is hidden
|
||||
*/
|
||||
function NotificationService($timeout, topic, DEFAULT_AUTO_DISMISS, MINIMIZE_TIMEOUT) {
|
||||
function NotificationService($timeout, topic, defaultAutoDismissTimeout, minimizeAnimationTimeout) {
|
||||
this.notifications = [];
|
||||
this.$timeout = $timeout;
|
||||
this.highest = { severity: "info" };
|
||||
this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS;
|
||||
this.MINIMIZE_TIMEOUT = MINIMIZE_TIMEOUT;
|
||||
this.AUTO_DISMISS_TIMEOUT = defaultAutoDismissTimeout;
|
||||
this.MINIMIZE_ANIMATION_TIMEOUT = minimizeAnimationTimeout;
|
||||
this.topic = topic;
|
||||
|
||||
/*
|
||||
@ -162,7 +162,7 @@ define(
|
||||
// in order to allow the minimize animation to run through.
|
||||
service.$timeout(function () {
|
||||
service.setActiveNotification(service.selectNextNotification());
|
||||
}, service.MINIMIZE_TIMEOUT);
|
||||
}, service.MINIMIZE_ANIMATION_TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
@ -208,11 +208,16 @@ define(
|
||||
* @private
|
||||
*/
|
||||
NotificationService.prototype.dismissOrMinimize = function (notification) {
|
||||
|
||||
//For now minimize everything, and have discussion around which
|
||||
//kind of messages should or should not be in the minimized
|
||||
//notifications list
|
||||
notification.minimize();
|
||||
var model = notification.model;
|
||||
if (model.severity === "info") {
|
||||
if (model.autoDismiss === false) {
|
||||
notification.minimize();
|
||||
} else {
|
||||
notification.dismiss();
|
||||
}
|
||||
} else {
|
||||
notification.minimize();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -226,7 +231,9 @@ define(
|
||||
/**
|
||||
* A convenience method for info notifications. Notifications
|
||||
* created via this method will be auto-dismissed after a default
|
||||
* wait period
|
||||
* wait period unless explicitly forbidden by the caller through
|
||||
* the {autoDismiss} property on the {NotificationModel}, in which
|
||||
* case the notification will be minimized after the wait.
|
||||
* @param {NotificationModel | string} message either a string for
|
||||
* the title of the notification message, or a {@link NotificationModel}
|
||||
* defining the options notification to display
|
||||
@ -235,7 +242,6 @@ define(
|
||||
*/
|
||||
NotificationService.prototype.info = function (message) {
|
||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
||||
notificationModel.autoDismiss = notificationModel.autoDismiss || true;
|
||||
notificationModel.severity = "info";
|
||||
return this.notify(notificationModel);
|
||||
};
|
||||
@ -306,28 +312,29 @@ define(
|
||||
activeNotification = self.active.notification,
|
||||
topic = this.topic();
|
||||
|
||||
notificationModel.severity = notificationModel.severity || "info";
|
||||
|
||||
notification = {
|
||||
model: notificationModel,
|
||||
|
||||
minimize: function () {
|
||||
self.minimize(self, notification);
|
||||
},
|
||||
|
||||
dismiss: function () {
|
||||
self.dismiss(self, notification);
|
||||
topic.notify();
|
||||
},
|
||||
|
||||
dismissOrMinimize: function () {
|
||||
self.dismissOrMinimize(notification);
|
||||
},
|
||||
|
||||
onDismiss: function (callback) {
|
||||
topic.listen(callback);
|
||||
}
|
||||
};
|
||||
|
||||
notificationModel.severity = notificationModel.severity || "info";
|
||||
if (notificationModel.autoDismiss === true) {
|
||||
notificationModel.autoDismiss = this.DEFAULT_AUTO_DISMISS;
|
||||
}
|
||||
|
||||
//Notifications support a 'dismissable' attribute. This is a
|
||||
// convenience to support adding a 'dismiss' option to the
|
||||
// notification for the common case of dismissing a
|
||||
@ -366,38 +373,39 @@ define(
|
||||
*/
|
||||
this.active.timeout = this.$timeout(function () {
|
||||
activeNotification.dismissOrMinimize();
|
||||
}, this.DEFAULT_AUTO_DISMISS);
|
||||
}, this.AUTO_DISMISS_TIMEOUT);
|
||||
}
|
||||
|
||||
return notification;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Used internally by the NotificationService
|
||||
* @private
|
||||
*/
|
||||
NotificationService.prototype.setActiveNotification =
|
||||
function (notification) {
|
||||
var timeout;
|
||||
NotificationService.prototype.setActiveNotification = function (notification) {
|
||||
var shouldAutoDismiss;
|
||||
this.active.notification = notification;
|
||||
|
||||
this.active.notification = notification;
|
||||
/*
|
||||
If autoDismiss has been specified, OR there are other
|
||||
notifications queued for display, setup a timeout to
|
||||
dismiss the dialog.
|
||||
*/
|
||||
if (notification && (notification.model.autoDismiss ||
|
||||
this.selectNextNotification())) {
|
||||
if (!notification) {
|
||||
delete this.active.timeout;
|
||||
return;
|
||||
}
|
||||
|
||||
timeout = notification.model.autoDismiss || this.DEFAULT_AUTO_DISMISS;
|
||||
this.active.timeout = this.$timeout(function () {
|
||||
notification.dismissOrMinimize();
|
||||
}, timeout);
|
||||
} else {
|
||||
delete this.active.timeout;
|
||||
}
|
||||
};
|
||||
if (notification.model.severity === "info") {
|
||||
shouldAutoDismiss = true;
|
||||
} else {
|
||||
shouldAutoDismiss = notification.model.autoDismiss;
|
||||
}
|
||||
|
||||
if (shouldAutoDismiss || this.selectNextNotification()) {
|
||||
this.active.timeout = this.$timeout(function () {
|
||||
notification.dismissOrMinimize();
|
||||
}, this.AUTO_DISMISS_TIMEOUT);
|
||||
} else {
|
||||
delete this.active.timeout;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used internally by the NotificationService
|
||||
|
@ -19,6 +19,7 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global describe,it,expect,beforeEach,jasmine*/
|
||||
|
||||
define(
|
||||
['../src/NotificationService'],
|
||||
@ -29,11 +30,16 @@ define(
|
||||
mockTimeout,
|
||||
mockAutoDismiss,
|
||||
mockMinimizeTimeout,
|
||||
successModel,
|
||||
mockTopicFunction,
|
||||
mockTopicObject,
|
||||
infoModel,
|
||||
alertModel,
|
||||
errorModel;
|
||||
|
||||
function elapseTimeout() {
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockTopicFunction = jasmine.createSpy("topic");
|
||||
@ -41,153 +47,189 @@ define(
|
||||
mockTopicFunction.andReturn(mockTopicObject);
|
||||
|
||||
mockAutoDismiss = mockMinimizeTimeout = 1000;
|
||||
notificationService = new NotificationService(
|
||||
mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
|
||||
successModel = {
|
||||
title: "Mock Success Notification",
|
||||
notificationService = new NotificationService(mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
|
||||
|
||||
infoModel = {
|
||||
title: "Mock Info Notification",
|
||||
severity: "info"
|
||||
};
|
||||
|
||||
alertModel = {
|
||||
title: "Mock Alert Notification",
|
||||
severity: "alert"
|
||||
};
|
||||
|
||||
errorModel = {
|
||||
title: "Mock Error Notification",
|
||||
severity: "error"
|
||||
};
|
||||
});
|
||||
|
||||
it("gets a new success notification, making" +
|
||||
" the notification active", function () {
|
||||
var activeNotification;
|
||||
notificationService.notify(successModel);
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(successModel);
|
||||
});
|
||||
|
||||
it("notifies listeners on dismissal of notification", function () {
|
||||
var notification,
|
||||
dismissListener = jasmine.createSpy("ondismiss");
|
||||
notification = notificationService.notify(successModel);
|
||||
var dismissListener = jasmine.createSpy("ondismiss");
|
||||
var notification = notificationService.notify(infoModel);
|
||||
notification.onDismiss(dismissListener);
|
||||
expect(mockTopicObject.listen).toHaveBeenCalled();
|
||||
notification.dismiss();
|
||||
expect(mockTopicObject.notify).toHaveBeenCalled();
|
||||
mockTopicObject.listen.mostRecentCall.args[0]();
|
||||
expect(dismissListener).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it("allows specification of an info notification given just a" +
|
||||
" title, making the notification active", function () {
|
||||
var activeNotification,
|
||||
notificationTitle = "Test info notification";
|
||||
notificationService.info(notificationTitle);
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model.title).toBe(notificationTitle);
|
||||
expect(activeNotification.model.severity).toBe("info");
|
||||
});
|
||||
|
||||
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.model).toBe(successModel);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
expect(mockTimeout.calls.length).toBe(2);
|
||||
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.model).toBe(successModel);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
expect(mockTimeout.calls.length).toBe(2);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification).toBeUndefined();
|
||||
});
|
||||
|
||||
it("allows minimization of notifications", function () {
|
||||
var notification,
|
||||
activeNotification;
|
||||
|
||||
successModel.autoDismiss = false;
|
||||
notification = notificationService.notify(successModel);
|
||||
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(successModel);
|
||||
notification.minimize();
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toBe(1);
|
||||
});
|
||||
|
||||
it("allows dismissal of notifications", function () {
|
||||
var notification,
|
||||
activeNotification;
|
||||
|
||||
successModel.autoDismiss = false;
|
||||
notification = notificationService.notify(successModel);
|
||||
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(successModel);
|
||||
it("dismisses a notification when the notification's dismiss method is used", function () {
|
||||
var notification = notificationService.info(infoModel);
|
||||
notification.dismiss();
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toBe(0);
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(0);
|
||||
});
|
||||
|
||||
describe(" gets called with multiple notifications", function () {
|
||||
it("auto-dismisses the previously active notification, making" +
|
||||
" the new notification active", function () {
|
||||
it("minimizes a notification when the notification's minimize method is used", function () {
|
||||
var notification = notificationService.info(infoModel);
|
||||
notification.minimize();
|
||||
elapseTimeout(); // needed for the minimize animation timeout
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
|
||||
describe("when receiving info notifications", function () {
|
||||
it("minimizes info notifications if the caller disables auto-dismiss", function () {
|
||||
infoModel.autoDismiss = false;
|
||||
var notification = notificationService.info(infoModel);
|
||||
elapseTimeout();
|
||||
// 2nd elapse for the minimize animation timeout
|
||||
elapseTimeout();
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
|
||||
it("dismisses info notifications if the caller ignores auto-dismiss", function () {
|
||||
notificationService.info(infoModel);
|
||||
elapseTimeout();
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("dismisses info notifications if the caller requests auto-dismiss", function () {
|
||||
infoModel.autoDismiss = true;
|
||||
notificationService.info(infoModel);
|
||||
elapseTimeout();
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when receiving alert notifications", function () {
|
||||
it("minimizes alert notifications if the caller enables auto-dismiss", function () {
|
||||
alertModel.autoDismiss = true;
|
||||
var notification = notificationService.alert(alertModel);
|
||||
elapseTimeout();
|
||||
elapseTimeout();
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
|
||||
it("keeps alert notifications active if the caller disables auto-dismiss", function () {
|
||||
mockTimeout.andCallFake(function (callback, time) {
|
||||
callback();
|
||||
});
|
||||
alertModel.autoDismiss = false;
|
||||
var notification = notificationService.alert(alertModel);
|
||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
|
||||
it("keeps alert notifications active if the caller ignores auto-dismiss", function () {
|
||||
mockTimeout.andCallFake(function (callback, time) {
|
||||
callback();
|
||||
});
|
||||
var notification = notificationService.alert(alertModel);
|
||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when receiving error notifications", function () {
|
||||
it("minimizes error notifications if the caller enables auto-dismiss", function () {
|
||||
errorModel.autoDismiss = true;
|
||||
var notification = notificationService.error(errorModel);
|
||||
elapseTimeout();
|
||||
elapseTimeout();
|
||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
|
||||
it("keeps error notifications active if the caller disables auto-dismiss", function () {
|
||||
mockTimeout.andCallFake(function (callback, time) {
|
||||
callback();
|
||||
});
|
||||
errorModel.autoDismiss = false;
|
||||
var notification = notificationService.error(errorModel);
|
||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
|
||||
it("keeps error notifications active if the caller ignores auto-dismiss", function () {
|
||||
mockTimeout.andCallFake(function (callback, time) {
|
||||
callback();
|
||||
});
|
||||
var notification = notificationService.error(errorModel);
|
||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
||||
expect(notificationService.notifications.length).toEqual(1);
|
||||
expect(notificationService.notifications[0]).toEqual(notification);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when called with multiple notifications", function () {
|
||||
it("auto-dismisses the previously active notification, making the new notification active", function () {
|
||||
var activeNotification;
|
||||
infoModel.autoDismiss = false;
|
||||
//First pre-load with a info message
|
||||
notificationService.notify(successModel);
|
||||
activeNotification =
|
||||
notificationService.getActiveNotification();
|
||||
notificationService.notify(infoModel);
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
//Initially expect the active notification to be info
|
||||
expect(activeNotification.model).toBe(successModel);
|
||||
expect(activeNotification.model).toBe(infoModel);
|
||||
//Then notify of an error
|
||||
notificationService.notify(errorModel);
|
||||
//But it should be auto-dismissed and replaced with the
|
||||
// error notification
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
//Two timeouts, one is to force minimization after
|
||||
// displaying the message for a minimum period, the
|
||||
// second is to allow minimization animation to take place.
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(errorModel);
|
||||
});
|
||||
|
||||
it("auto-minimizes an active error notification", function () {
|
||||
var activeNotification;
|
||||
//First pre-load with an error message
|
||||
notificationService.notify(errorModel);
|
||||
//Then notify of info
|
||||
notificationService.notify(successModel);
|
||||
notificationService.notify(infoModel);
|
||||
expect(notificationService.notifications.length).toEqual(2);
|
||||
//Mock the auto-minimize
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
//Two timeouts, one is to force minimization after
|
||||
// displaying the message for a minimum period, the
|
||||
// second is to allow minimization animation to take place.
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
//Previous error message should be minimized, not
|
||||
// dismissed
|
||||
expect(notificationService.notifications.length).toEqual(2);
|
||||
activeNotification =
|
||||
notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(successModel);
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(infoModel);
|
||||
expect(errorModel.minimized).toEqual(true);
|
||||
});
|
||||
it("auto-minimizes errors when a number of them arrive in" +
|
||||
" short succession ", function () {
|
||||
|
||||
it("auto-minimizes errors when a number of them arrive in short succession", function () {
|
||||
var activeNotification,
|
||||
error2 = {
|
||||
title: "Second Mock Error Notification",
|
||||
@ -205,30 +247,27 @@ define(
|
||||
notificationService.notify(error3);
|
||||
expect(notificationService.notifications.length).toEqual(3);
|
||||
//Mock the auto-minimize
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
//Two timeouts, one is to force minimization after
|
||||
// displaying the message for a minimum period, the
|
||||
// second is to allow minimization animation to take place.
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
//Previous error message should be minimized, not
|
||||
// dismissed
|
||||
expect(notificationService.notifications.length).toEqual(3);
|
||||
activeNotification =
|
||||
notificationService.getActiveNotification();
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(error2);
|
||||
expect(errorModel.minimized).toEqual(true);
|
||||
|
||||
//Mock the second auto-minimize
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
elapseTimeout();
|
||||
//Two timeouts, one is to force minimization after
|
||||
// displaying the message for a minimum period, the
|
||||
// second is to allow minimization animation to take place.
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
activeNotification =
|
||||
notificationService.getActiveNotification();
|
||||
elapseTimeout();
|
||||
activeNotification = notificationService.getActiveNotification();
|
||||
expect(activeNotification.model).toBe(error3);
|
||||
expect(error2.minimized).toEqual(true);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user