From 23ac5d0dcd508fdfbedd4655e206bf3988c2db18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Kup=C5=9B?= Date: Fri, 7 Apr 2017 12:00:11 +0200 Subject: [PATCH 1/2] Added notification plugin. --- src/notification.js | 195 ++++++++++++++++++++++++++++++++++++++++++ tests/notification.js | 152 ++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+) create mode 100644 src/notification.js create mode 100644 tests/notification.js diff --git a/src/notification.js b/src/notification.js new file mode 100644 index 00000000..1a8d8974 --- /dev/null +++ b/src/notification.js @@ -0,0 +1,195 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/notification + */ + +/* globals window */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +/** + * The Notification plugin. + * + * This plugin sends few base types of notifications: `success`, `info` and `warning`. This notifications need to be + * handled and displayed by plugin responsible for showing UI of the notifications. Using this plugin for dispatching + * notifications makes possible to switch the notifications UI. + * + * Note that every unhandled and not stopped `warning` notification will be displayed as system alert. + * See {@link module:ui/notification~Notification#showWarning}. + * + * @extends module:core/plugin~Plugin + */ +export default class Notification extends Plugin { + /** + * @inheritDoc + */ + static get pluginName() { + return 'notification'; + } + + /** + * @inheritDoc + */ + init() { + // Each unhandled and not stopped `show:warning` event is displayed as system alert. + this.on( 'show:warning', ( evt, data ) => { + window.alert( data.message ); + }, { priority: 'lowest' } ); + } + + /** + * Shows success notification. + * + * At default it fires `show:success` event with given data but event namespace can be extended + * by `data.namespace` option e.g. + * + * showSuccess( 'Image is uploaded.', { + * namespace: 'upload:image' + * } ); + * + * will fire `show:success:upload:image` event. + * + * @param {String} message Content of the notification. + * @param {Object} [data={}] Additional data. + * @param {String} [data.namespace] Additional event namespace. + */ + showSuccess( message, data = {} ) { + this._showNotification( { + message: message, + type: 'success', + namespace: data.namespace + } ); + } + + /** + * Shows info notification. + * + * At default it fires `show:info` event with given data but event namespace can be extended + * by `data.namespace` option e.g. + * + * showInfo( 'Editor is offline.', { + * namespace: 'editor:status' + * } ); + * + * will fire `show:info:editor:status` event. + * + * @param {String} message Content of the notification. + * @param {Object} [data={}] Additional data. + * @param {String} [data.namespace] Additional event namespace. + */ + showInfo( message, data = {} ) { + this._showNotification( { + message: message, + type: 'info', + namespace: data.namespace + } ); + } + + /** + * Shows warning notification. + * + * At default it fires `show:warning` event with given data but event namespace can be extended + * by `data.namespace` option e.g. + * + * showWarning( 'Image upload error.', { + * namespace: 'upload:image' + * } ); + * + * will fire `show:warning:upload:image` event. + * + * Note that each unhandled and not stopped `warning` notification will be displayed as system alert. + * Plugin responsible for displaying warnings should `stop()` the event to prevent of displaying it as alert: + * + * notifications.on( 'show:warning', ( evt, data ) => { + * // Do something with data. + * + * // Stop this event to prevent of displaying as alert. + * evt.stop(); + * } ); + * + * You can attach many listeners to the same event and `stop()` this event in the listener with the low priority: + * + * notifications.on( 'show:warning', ( evt, data ) => { + * // Show warning in the UI, but not stop it. + * } ); + * + * notifications.on( 'show:warning', ( evt, data ) => { + * // Log warning to some error tracker. + * + * // Stop this event to prevent of displaying as alert. + * evt.stop(); + * }, { priority: 'low' } ); + * + * @param {String} message Content of the notification. + * @param {Object} [data={}] Additional data. + * @param {String} [data.namespace] Additional event namespace. + */ + showWarning( message, data = {} ) { + this._showNotification( { + message: message, + type: 'warning', + namespace: data.namespace + } ); + } + + /** + * Fires `show` event with specified type, namespace and message. + * + * @private + * @param {Object} data Message data. + * @param {String} data.message Content of the notification. + * @param {'success'|'info'|'warning'} data.type Type of message. + * @param {String} [data.namespace] Additional event namespace. + */ + _showNotification( data ) { + const event = `show:${ data.type }` + ( data.namespace ? `:${ data.namespace }` : '' ); + + this.fire( event, { + message: data.message, + type: data.type + } ); + } + + /** + * Fired when one of `showSuccess`, `showInfo`, `showWarning` methods is called. + * + * @event show + * @param {Object} data Notification data. + * @param {String} data.message Content of the notification. + * @param {'success'|'info'|'warning'} data.type Type of notification. + */ + + /** + * Fired when `showSuccess` method is called. + * + * @event show:success + * @param {Object} data Notification data. + * @param {String} data.message Content of the notification. + * @param {'success'} data.type Type of notification. + */ + + /** + * Fired when `showInfo` method is called. + * + * @event show:info + * @param {Object} data Notification data. + * @param {String} data.message Content of the notification. + * @param {'info'} data.type Type of notification. + */ + + /** + * Fired when `showWarning` method is called. + * + * When this event won't be handled and stopped by `event.stop()` then data.message of this event will + * be automatically displayed as system alert. + * + * @event show:warning + * @param {Object} data Notification data. + * @param {String} data.message Content of the notification. + * @param {'warning'} data.type Type of notification. + */ +} diff --git a/tests/notification.js b/tests/notification.js new file mode 100644 index 00000000..85ae2904 --- /dev/null +++ b/tests/notification.js @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2016, CKSource - Frederico Knabben. All rights reserved. + */ + +/* globals window */ + +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import Notification from '../src/notification'; + +describe( 'Notification', () => { + let editor, notification; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + return VirtualTestEditor.create( { + plugins: [ Notification ] + } ) + .then( newEditor => { + editor = newEditor; + notification = editor.plugins.get( Notification ); + } ); + } ); + + describe( 'init()', () => { + it( 'should create notification plugin', () => { + expect( notification ).to.instanceof( Notification ); + expect( notification ).to.instanceof( Plugin ); + } ); + } ); + + describe( 'showSuccess()', () => { + it( 'should fire `show:success` event with given data', () => { + const spy = testUtils.sinon.spy(); + + notification.on( 'show:success', spy ); + + notification.showSuccess( 'foo bar' ); + + sinon.assert.calledOnce( spy ); + expect( spy.firstCall.args[ 1 ] ).to.deep.equal( { + message: 'foo bar', + type: 'success' + } ); + } ); + + it( 'should fire `show:success` event with additional namespace', () => { + const spy = testUtils.sinon.spy(); + + notification.on( 'show:success:something:else', spy ); + + notification.showSuccess( 'foo bar', { + namespace: 'something:else' + } ); + + sinon.assert.calledOnce( spy ); + expect( spy.firstCall.args[ 1 ] ).to.deep.equal( { + message: 'foo bar', + type: 'success' + } ); + } ); + } ); + + describe( 'showInfo()', () => { + it( 'should fire `show:info` event with given data', () => { + const spy = testUtils.sinon.spy(); + + notification.on( 'show:info', spy ); + + notification.showInfo( 'foo bar' ); + + sinon.assert.calledOnce( spy ); + expect( spy.firstCall.args[ 1 ] ).to.deep.equal( { + message: 'foo bar', + type: 'info' + } ); + } ); + + it( 'should fire `show:info` event with additional namespace', () => { + const spy = testUtils.sinon.spy(); + + notification.on( 'show:info:something:else', spy ); + + notification.showInfo( 'foo bar', { + namespace: 'something:else' + } ); + + sinon.assert.calledOnce( spy ); + expect( spy.firstCall.args[ 1 ] ).to.deep.equal( { + message: 'foo bar', + type: 'info' + } ); + } ); + } ); + + describe( 'showWarning()', () => { + let alertStub; + + beforeEach( () => { + alertStub = testUtils.sinon.stub( window, 'alert' ); + } ); + + it( 'should fire `show:warning` event with given data', () => { + const spy = testUtils.sinon.spy(); + + notification.on( 'show:warning', spy ); + + notification.showWarning( 'foo bar' ); + + sinon.assert.calledOnce( spy ); + expect( spy.firstCall.args[ 1 ] ).to.deep.equal( { + message: 'foo bar', + type: 'warning' + } ); + } ); + + it( 'should fire `show:warning` event with additional namespace', () => { + const spy = testUtils.sinon.spy(); + + notification.on( 'show:warning:something:else', spy ); + + notification.showWarning( 'foo bar', { + namespace: 'something:else' + } ); + + sinon.assert.calledOnce( spy ); + expect( spy.firstCall.args[ 1 ] ).to.deep.equal( { + message: 'foo bar', + type: 'warning' + } ); + } ); + + it( 'should display `warning` message as system alert if is not cached and stopped by other plugins', () => { + notification.showWarning( 'foo bar' ); + + sinon.assert.calledOnce( alertStub ); + expect( alertStub.firstCall.args[ 0 ] ).to.equal( 'foo bar' ); + } ); + + it( 'should not display alert when `warning` message is cached and stopped by other plugins', () => { + notification.on( 'show:warning', evt => { + evt.stop(); + } ); + + notification.showWarning( 'foo bar' ); + + sinon.assert.notCalled( alertStub ); + } ); + } ); +} ); From d06f85ed5dd615b8f5c6d65482a957414726806e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Kup=C5=9B?= Date: Fri, 7 Apr 2017 13:02:02 +0200 Subject: [PATCH 2/2] Moved notification plugin to separate directory. --- src/{ => notification}/notification.js | 0 tests/{ => notification}/notification.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{ => notification}/notification.js (100%) rename tests/{ => notification}/notification.js (98%) diff --git a/src/notification.js b/src/notification/notification.js similarity index 100% rename from src/notification.js rename to src/notification/notification.js diff --git a/tests/notification.js b/tests/notification/notification.js similarity index 98% rename from tests/notification.js rename to tests/notification/notification.js index 85ae2904..66738ede 100644 --- a/tests/notification.js +++ b/tests/notification/notification.js @@ -7,7 +7,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import Notification from '../src/notification'; +import Notification from '../../src/notification/notification'; describe( 'Notification', () => { let editor, notification;