From 3af23b7bc527a478744f572720a9efce45bc509a Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 7 Oct 2015 14:30:19 -0700 Subject: [PATCH] Added test cases for notification service --- bundles.json | 1 + .../notification/src/MessageSeverity.js | 9 + .../notification/src/NotificationService.js | 164 +++++++++++++----- .../test/NotificationServiceSpec.js | 146 ++++++++++++++++ .../commonUI/notification/test/suite.json | 3 + 5 files changed, 280 insertions(+), 43 deletions(-) create mode 100644 platform/commonUI/notification/src/MessageSeverity.js create mode 100644 platform/commonUI/notification/test/NotificationServiceSpec.js create mode 100644 platform/commonUI/notification/test/suite.json diff --git a/bundles.json b/bundles.json index a0f7edd7a8..15a05f9334 100644 --- a/bundles.json +++ b/bundles.json @@ -26,6 +26,7 @@ "platform/policy", "platform/entanglement", "platform/search", + "platform/commonUI/notification", "example/imagery", "example/eventGenerator", diff --git a/platform/commonUI/notification/src/MessageSeverity.js b/platform/commonUI/notification/src/MessageSeverity.js new file mode 100644 index 0000000000..5f17b917fe --- /dev/null +++ b/platform/commonUI/notification/src/MessageSeverity.js @@ -0,0 +1,9 @@ +/** + * Created by akhenry on 10/7/15. + */ +define(function(){ + return { + SUCCESS: 0, + ERROR: 1 + } +}) \ No newline at end of file diff --git a/platform/commonUI/notification/src/NotificationService.js b/platform/commonUI/notification/src/NotificationService.js index 07aea14da4..30e746cc42 100644 --- a/platform/commonUI/notification/src/NotificationService.js +++ b/platform/commonUI/notification/src/NotificationService.js @@ -27,8 +27,8 @@ * @namespace platform/commonUI/dialog */ define( - [], - function () { + ["./MessageSeverity"], + function (MessageSeverity) { "use strict"; /** * The notification service is responsible for informing the user of @@ -36,28 +36,9 @@ define( * @memberof platform/commonUI/notification * @constructor */ - function NotificationService($log, $timeout, messageSeverity, DEFAULT_AUTO_DISMISS) { - //maintain an array of notifications. - //expose a method for adding notifications. - //expose a method for dismissing notifications. - //expose a method for minimizing notifications. - //expose a method for getting the 'current' notification. How - //this is determined could be a little nuanced. - //Allow for auto-dismissal of success messages - // - // - // - // Questions: - // 1) What happens when a newer, but lower priority - // message arrives (eg. success). Just show it? It will - // auto-dismissed, and the existing error message will be - // exposed. - // - + function NotificationService($timeout, DEFAULT_AUTO_DISMISS) { this.notifications = []; - this.$log = $log; this.$timeout = $timeout; - this.messageSeverity = messageSeverity; this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS; /** @@ -71,7 +52,6 @@ define( * @type {{notification: undefined}} */ this.active = { - notification: undefined }; } /** @@ -97,14 +77,18 @@ define( this.model = model; } - Notification.prototype.minimize = function () { + Notification.prototype.minimize = function (setValue) { if (typeof setValue !== undefined){ - model.minimized = setValue; + this.model.minimized = setValue; } else { - return model.minimized; + return this.model.minimized; } }; + NotificationService.prototype.getActiveNotification = function (){ + return this.active.notification; + } + /** * model = { * @@ -112,31 +96,125 @@ define( * @param model */ NotificationService.prototype.notify = function (model) { - var notification = new Notification(model); + var notification = new Notification(model), + that=this; this.notifications.push(notification); - this.setActiveNotification(notification); - }; + /* + Check if there is already an active (ie. visible) notification + */ + if (!this.active.notification){ + setActiveNotification.call(this, notification); + } else if (!this.active.timeout){ + /* + If there is already an active notification, time it out. If it's + already got a timeout in progress (either because it has had + timeout forced because of a queue of messages, or it had an + autodismiss specified), leave it to run. - - NotificationService.prototype.setActiveNotification = function () { - //If there is a message active, time it out, and then chain a - // new message to appear. - if (this.active.timeout){ - this.active.timeout = this.active.timeout.then(function (){ - this.active.timeout = $timeout(function(){ - this.dismiss(this.active.notification); - }); + This notifcation has been added to queue and will be + serviced as soon as possible. + */ + this.active.timeout = this.$timeout(function () { + that.dismissOrMinimize(that.active.notification); }); } + }; - NotificationService.prototype.dismiss = function (notification) { - var index = this.notifications.indexOf(notification); - if (index >= 0) { - this.notifications = this.notifications.splice(index, 1); - delete this.active.notification; + function setActiveNotification (notification) { + var that = this; + this.active.notification = notification; + /* + If autoDismiss has been specified, setup a timeout to + dismiss the dialog. + + If there are other notifications pending in the queue, set this + one to auto-dismiss + */ + if (notification.model.autoDismiss + || selectNextNotification.call(this)) { + var timeout = isNaN(notification.model.autoDismiss) ? + this.DEFAULT_AUTO_DISMISS : notification.model.autoDismiss; + + this.active.timeout = this.$timeout(function () { + that.dismissOrMinimize(notification); + }, timeout); } } + + function selectNextNotification () { + /* + Loop through the notifications queue and find the first one that + has not already been minimized (manually or otherwise). + */ + for (var i=0; i< this.notifications.length; i++) { + var notification = this.notifications[i]; + + if (!notification.model.minimized + && notification!= this.activeNotification) { + + return notification; + } + } + }; + + /** + * Minimize a notification. The notification will still be available + * from the notification list. Typically notifications with a + * severity of SUCCESS should not be minimized, but rather + * dismissed. + * @see dismiss + * @see dismissOrMinimize + * @param notification + */ + NotificationService.prototype.minimize = function (notification) { + //Check this is a known notification + var index = this.notifications.indexOf(notification); + if (index >= 0) { + notification.minimize(true); + delete this.active.notification; + delete this.active.timeout; + setActiveNotification.call(this, selectNextNotification.call(this)); + } + } + + /** + * Completely remove a notification. This will dismiss it from the + * message banner and remove it from the list of notifications. + * Typically only notifications with a severity of SUCCESS should be + * dismissed. If you're not sure whether to dismiss or minimize a + * notification, use the dismissOrMinimize method. + * dismiss + * @see dismissOrMinimize + * @param notification The notification to dismiss + */ + NotificationService.prototype.dismiss = function (notification) { + //Check this is a known notification + var index = this.notifications.indexOf(notification); + if (index >= 0) { + this.notifications.splice(index, 1); + + delete this.active.notification; + delete this.active.timeout; + + setActiveNotification.call(this, selectNextNotification.call(this)); + } + } + + /** + * Depending on the severity of the notification will selectively + * dismiss or minimize where appropriate. + * @see dismiss + * @see minimize + * @param notification + */ + NotificationService.prototype.dismissOrMinimize = function (notification){ + if (notification.model.severity > MessageSeverity.SUCCESS){ + this.minimize(notification); + } else { + this.dismiss(notification); + } + }; return NotificationService; }); \ No newline at end of file diff --git a/platform/commonUI/notification/test/NotificationServiceSpec.js b/platform/commonUI/notification/test/NotificationServiceSpec.js new file mode 100644 index 0000000000..c7a5fee234 --- /dev/null +++ b/platform/commonUI/notification/test/NotificationServiceSpec.js @@ -0,0 +1,146 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,describe,it,expect,beforeEach,waitsFor,jasmine */ + +define( + ['../src/NotificationService','../src/MessageSeverity'], + function (NotificationService, MessageSeverity) { + "use strict"; + + describe("The notification service ", function () { + var notificationService, + mockTimeout, + mockAutoDismiss, + successModel = { + title: "Mock Success Notification", + severity: MessageSeverity.SUCCESS + }, + errorModel = { + title: "Mock Error Notification", + severity: MessageSeverity.ERROR + }; + + /** + * 1) Calling .notify results in a new notification being created + * with the provided model and set to the active notification + * + * 2) Calling .notify with autoDismiss results in a SUCCESS notification + * becoming dismissed after timeout has elapsed + * + * 3) Calling .notify with autoDismiss results in an ERROR notification + * being MINIMIZED after a timeout has elapsed + * + * 4) Calling .notify with an active success notification results in that + * notification being auto-dismissed, and the new notification becoming + * active. DONE + * + * 5) Calling .notify with an active error notification results in that + * notification being auto-minimized and the new notification becoming + * active. DONE + * + * 6) Calling .notify with an active error notification, AND a + * queued error notification results in the active notification + * being auto-dismissed, the next message in the queue becoming + * active, then auto-dismissed, and then the provided notification + * becoming active. + */ + + /** + var model = { + title: string, + progress: number, + severity: MessageSeverity, + unknownProgress: boolean, + minimized: boolean, + autoDismiss: boolean | number, + actions: { + label: string, + action: function + } + } + */ + + beforeEach(function(){ + mockTimeout = jasmine.createSpy("$timeout"); + mockAutoDismiss = 0; + notificationService = new NotificationService( + mockTimeout, mockAutoDismiss); + }); + + it("Calls the notification service with a new notification, making" + + " the notification active", function() { + var activeNotification; + notificationService.notify(successModel); + activeNotification = notificationService.getActiveNotification(); + expect(activeNotification.model).toBe(successModel); + }); + + describe(" called with multiple notifications", function(){ + it("auto-dismisses the previously active notification, making" + + " the new notification active", function() { + var activeNotification; + //First pre-load with a success message + notificationService.notify(successModel); + activeNotification = + notificationService.getActiveNotification(); + //Initially expect the active notification to be success + expect(activeNotification.model).toBe(successModel); + //Then notify of an error + notificationService.notify(errorModel); + //But it should be auto-dismissed and replaced with the + // error notification + mockTimeout.mostRecentCall.args[0](); + activeNotification = notificationService.getActiveNotification(); + expect(activeNotification.model).toBe(errorModel); + }); + it("auto-dismisses an active success notification, removing" + + " it completely", function() { + //First pre-load with a success message + notificationService.notify(successModel); + //Then notify of an error + notificationService.notify(errorModel); + expect(notificationService.notifications.length).toEqual(2); + mockTimeout.mostRecentCall.args[0](); + //Previous success message should be completely dismissed + expect(notificationService.notifications.length).toEqual(1); + }); + it("auto-minimizes an active error notification", function() { + var activeNotification; + //First pre-load with a success message + notificationService.notify(errorModel); + //Then notify of an error + notificationService.notify(successModel); + expect(notificationService.notifications.length).toEqual(2); + //Mock the auto-minimize + mockTimeout.mostRecentCall.args[0](); + //Previous error message should be minimized, not + // dismissed + expect(notificationService.notifications.length).toEqual(2); + activeNotification = + notificationService.getActiveNotification(); + expect(activeNotification.model).toBe(successModel); + expect(errorModel.minimized).toEqual(true); + + }); + }); + }); + }); \ No newline at end of file diff --git a/platform/commonUI/notification/test/suite.json b/platform/commonUI/notification/test/suite.json new file mode 100644 index 0000000000..08238b16c0 --- /dev/null +++ b/platform/commonUI/notification/test/suite.json @@ -0,0 +1,3 @@ +[ + "NotificationService" +] \ No newline at end of file