Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Feature: Introduced notification plugin. Closes #189.
Browse files Browse the repository at this point in the history
  • Loading branch information
oskarwrobel authored Apr 7, 2017
2 parents 3ceb6a6 + d06f85e commit f2dd63f
Show file tree
Hide file tree
Showing 2 changed files with 347 additions and 0 deletions.
195 changes: 195 additions & 0 deletions src/notification/notification.js
Original file line number Diff line number Diff line change
@@ -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.
*/
}
152 changes: 152 additions & 0 deletions tests/notification/notification.js
Original file line number Diff line number Diff line change
@@ -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/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 );
} );
} );
} );

0 comments on commit f2dd63f

Please sign in to comment.