Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ckeditor/ckeditor5-core

Package Overview
Dependencies
Maintainers
1
Versions
705
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-core - npm Package Compare versions

Comparing version 0.8.1 to 0.9.0

.eslintrc.js

44

CHANGELOG.md
Changelog
=========
## [0.9.0](https://github.com/ckeditor/ckeditor5-core/compare/v0.8.1...v0.9.0) (2017-09-03)
### Bug fixes
* `EditingKeystrokeHandler` should prevent default action only for commands. Closes [#90](https://github.com/ckeditor/ckeditor5-core/issues/90). ([82ff39a](https://github.com/ckeditor/ckeditor5-core/commit/82ff39a))
* `ToggleAttributeCommand` should listen to reliable events in order to determine its state. Closes [#50](https://github.com/ckeditor/ckeditor5-core/issues/50). ([6816505](https://github.com/ckeditor/ckeditor5-core/commit/6816505))
* SVG icons should not define own fill if controlled by the styles. Closes [#79](https://github.com/ckeditor/ckeditor5-core/issues/79). ([fadf5ec](https://github.com/ckeditor/ckeditor5-core/commit/fadf5ec))
### Features
* `EditingKeystrokeHandler` should support priorities and proper cancelling. Closes [#101](https://github.com/ckeditor/ckeditor5-core/issues/101). ([c74b9a3](https://github.com/ckeditor/ckeditor5-core/commit/c74b9a3))
* `Editor#destroy()` will destroy all loaded plugins. Closes [#86](https://github.com/ckeditor/ckeditor5-core/issues/86). ([77e5217](https://github.com/ckeditor/ckeditor5-core/commit/77e5217))
Added default implementation for `Plugin#destroy()`. Introduced `PluginCollection#destroy()` method which calls `Plugin#destroy()` for every loaded plugin.
* `PluginCollection` will warn if the user wants to load two or more plugins with the same name. Closes [#85](https://github.com/ckeditor/ckeditor5-core/issues/85). ([e00a282](https://github.com/ckeditor/ckeditor5-core/commit/e00a282))
* Introduced `Editor#isReadOnly` property which disables all commands and prevents from modifying the document. Closes [#96](https://github.com/ckeditor/ckeditor5-core/issues/96). Closes https://github.com/ckeditor/ckeditor5/issues/492. ([1ca5608](https://github.com/ckeditor/ckeditor5-core/commit/1ca5608))
### Other changes
* Bound `EditingController#isReadOnly` to the editor. Closes [#98](https://github.com/ckeditor/ckeditor5-core/issues/98). ([ec02906](https://github.com/ckeditor/ckeditor5-core/commit/ec02906))
* Cleaned up SVG icons. ([ffac7e7](https://github.com/ckeditor/ckeditor5-core/commit/ffac7e7))
* Introduced `PluginInterface`. A plugin doesn't need to inherit directly from the `Plugin` class, as long as it implements some minimal interface. See [#78](https://github.com/ckeditor/ckeditor5-core/issues/78). ([f476f34](https://github.com/ckeditor/ckeditor5-core/commit/f476f34))
* Removed `ToggleAttributeCommand` class as well as other helpers from the `core/command` namespace. Closes [#14](https://github.com/ckeditor/ckeditor5-core/issues/14). ([7c68581](https://github.com/ckeditor/ckeditor5-core/commit/7c68581))
* The command API has been redesigned. The `Command` methods are now public and consistent. Commands can be used in a standalone mode (without the editor). The `CommandCollection` was introduced and replaced the `Map` of commands used in `editor.commands`. Closes [#88](https://github.com/ckeditor/ckeditor5-core/issues/88). ([b76983b](https://github.com/ckeditor/ckeditor5-core/commit/b76983b))
Besides changes mentioned in this point and in the "Breaking changes" section, other minor changes were done:
* `Editor#execute()` now accepts multiple command arguments.
* `Command#value` property was standardized.
### BREAKING CHANGES
* The `ToggleAttributeCommand` was moved to the `ckeditor5-basic-styles` package as `AttributeCommand` and the other command helpers to `ckeditor5-engine` as `Schema` methods.
* The `Command`'s protected `_doExecute()` and `_checkEnabled()` methods have been replaced by public `execute()` and `refresh()` methods.
* The `Command`'s `refreshState` event was removed and one should use `change:isEnabled` in order to control and override its state.
* The `core/command/command` module has been moved to the root directory (so the `Command` class is `core/command~Command` now).
* The `Command#refresh()` method is now automatically called on `editor.document#changesDone`.
* The `editor.commands` map was replaced by a `CommandCollection` instance so `editor.commands.set()` calls need to be replaced with `editor.commands.add()`.
### NOTE
* Plugin naming convention has changed.
## [0.8.1](https://github.com/ckeditor/ckeditor5-core/compare/v0.8.0...v0.8.1) (2017-05-07)

@@ -5,0 +49,0 @@

11

gulpfile.js

@@ -6,3 +6,3 @@ /**

/* jshint browser: false, node: true, strict: true */
/* eslint-env node */

@@ -12,3 +12,4 @@ 'use strict';

const gulp = require( 'gulp' );
const ckeditor5Lint = require( '@ckeditor/ckeditor5-dev-lint' )( {
const ckeditor5Lint = require( '@ckeditor/ckeditor5-dev-lint' );
const options = {
// Files ignored by `gulp lint` task.

@@ -19,6 +20,6 @@ // Files from .gitignore will be added automatically during task execution.

]
} );
};
gulp.task( 'lint', ckeditor5Lint.lint );
gulp.task( 'lint-staged', ckeditor5Lint.lintStaged );
gulp.task( 'lint', () => ckeditor5Lint.lint( options ) );
gulp.task( 'lint-staged', () => ckeditor5Lint.lintStaged( options ) );
gulp.task( 'pre-commit', [ 'lint-staged' ] );
{
"name": "@ckeditor/ckeditor5-core",
"version": "0.8.1",
"version": "0.9.0",
"description": "",
"keywords": [],
"dependencies": {
"@ckeditor/ckeditor5-engine": "^0.10.0",
"@ckeditor/ckeditor5-utils": "^0.9.1"
"@ckeditor/ckeditor5-engine": "^0.11.0",
"@ckeditor/ckeditor5-utils": "^0.10.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-dev-lint": "^2.0.2",
"@ckeditor/ckeditor5-ui": "^0.9.0",
"gulp": "^3.9.0",
"@ckeditor/ckeditor5-dev-lint": "^3.1.0",
"@ckeditor/ckeditor5-ui": "^0.10.0",
"eslint-config-ckeditor5": "^1.0.5",
"gulp": "^3.9.1",
"guppy-pre-commit": "^0.4.0"

@@ -15,0 +16,0 @@ },

@@ -1,4 +0,5 @@

CKEditor 5 Core Editor Architecture
CKEditor 5 core editor architecture
========================================
[![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-core.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-core)

@@ -5,0 +6,0 @@ [![Build Status](https://travis-ci.org/ckeditor/ckeditor5-core.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-core)

@@ -19,5 +19,5 @@ /**

*
* editor.keystrokes.set( 'ctrl + Z', 'undo' );
* editor.keystrokes.set( 'ctrl + shift + Z', 'redo' );
* editor.keystrokes.set( 'ctrl + Y', 'redo' );
* editor.keystrokes.set( 'Ctrl+Z', 'undo' );
* editor.keystrokes.set( 'Ctrl+Shift+Z', 'redo' );
* editor.keystrokes.set( 'Ctrl+Y', 'redo' );
*

@@ -47,36 +47,28 @@ * @extends utils/keystrokehandler~KeystrokeHandler

*
* * The handler can be specified as a command name or a callback.
* The handler can be specified as a command name or a callback.
*
* @param {String|Array.<String|Number>} keystroke Keystroke defined in a format accepted by
* the {@link module:utils/keyboard~parseKeystroke} function.
* @param {Function} callback If a string is passed, then the keystroke will
* @param {Function|String} callback If a string is passed, then the keystroke will
* {@link module:core/editor/editor~Editor#execute execute a command}.
* If a function, then it will be called with the
* {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and
* a helper to both `preventDefault` and `stopPropagation` of the event.
* a `cancel()` helper to both `preventDefault()` and `stopPropagation()` of the event.
* @param {Object} [options={}] Additional options.
* @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of the keystroke
* callback. The higher the priority value the sooner the callback will be executed. Keystrokes having the same priority
* are called in the order they were added.
*/
set( keystroke, callback ) {
set( keystroke, callback, options = {} ) {
if ( typeof callback == 'string' ) {
const commandName = callback;
callback = () => {
callback = ( evtData, cancel ) => {
this.editor.execute( commandName );
cancel();
};
}
super.set( keystroke, callback );
super.set( keystroke, callback, options );
}
/**
* @inheritDoc
*/
listenTo( emitter ) {
this._listener.listenTo( emitter, 'keydown', ( evt, data ) => {
const handled = this.press( data );
if ( handled ) {
data.preventDefault();
}
} );
}
}

@@ -10,5 +10,5 @@ /**

import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
import Config from '@ckeditor/ckeditor5-utils/src/config';
import PluginCollection from '../plugincollection';
import CommandCollection from '../commandcollection';
import Locale from '@ckeditor/ckeditor5-utils/src/locale';

@@ -18,3 +18,3 @@ import DataController from '@ckeditor/ckeditor5-engine/src/controller/datacontroller';

import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
import mix from '@ckeditor/ckeditor5-utils/src/mix';

@@ -27,3 +27,3 @@

*
* @mixes module:utils/emittermixin~EmitterMixin
* @mixes module:utils/observablemixin~ObservableMixin
*/

@@ -61,5 +61,5 @@ export default class Editor {

* @readonly
* @member {Map.<module:core/command/command~Command>}
* @member {module:core/commandcollection~CommandCollection}
*/
this.commands = new Map();
this.commands = new CommandCollection();

@@ -81,4 +81,15 @@ /**

/**
* Tree Model document managed by this editor.
* The editor's model document.
*
* The center of the editor's abstract data model. The document contains
* {@link module:engine/model/document~Document#getRoot all editing roots},
* {@link module:engine/model/document~Document#selection} and allows
* applying changes to through the {@link module:engine/model/document~Document#batch batch interface}.
*
* Besides the model document, the editor usually contains two controllers –
* {@link #data data controller} and {@link #editing editing controller}.
* The former is used e.g. when setting or retrieving editor data and contains a useful
* set of methods for operating on the content. The latter controls user input and rendering
* the content for editing.
*
* @readonly

@@ -90,3 +101,3 @@ * @member {module:engine/model/document~Document}

/**
* Instance of the {@link module:engine/controller/datacontroller~DataController data controller}.
* The {@link module:engine/controller/datacontroller~DataController data controller}.
*

@@ -99,4 +110,15 @@ * @readonly

/**
* Instance of the {@link module:engine/controller/editingcontroller~EditingController editing controller}.
* Defines whether this editor is in read-only mode.
*
* In read-only mode the editor {@link #commands commands} are disabled so it is not possible
* to modify document using them.
*
* @observable
* @member {Boolean} #isReadOnly
*/
this.set( 'isReadOnly', false );
/**
* The {@link module:engine/controller/editingcontroller~EditingController editing controller}.
*
* This property is set by more specialized editor classes (such as {@link module:core/editor/standardeditor~StandardEditor}),

@@ -123,3 +145,3 @@ * however, it's required for features to work as their engine-related parts will try to connect converters.

return loadPlugins()
.then( ( loadedPlugins ) => {
.then( loadedPlugins => {
return initPlugins( loadedPlugins, 'init' )

@@ -139,2 +161,6 @@ .then( () => initPlugins( loadedPlugins, 'afterInit' ) );

return loadedPlugins.reduce( ( promise, plugin ) => {
if ( !plugin[ method ] ) {
return promise;
}
return promise.then( plugin[ method ].bind( plugin ) );

@@ -148,3 +174,3 @@ }, Promise.resolve() );

*
* @fires module:core/editor/editor~Editor#destroy
* @fires destroy
* @returns {Promise} A promise that resolves once the editor instance is fully destroyed.

@@ -154,5 +180,8 @@ */

this.fire( 'destroy' );
this.stopListening();
return Promise.resolve()
this.commands.destroy();
return this.plugins.destroy()
.then( () => {

@@ -165,20 +194,13 @@ this.document.destroy();

/**
* Executes specified command with given parameter.
* Executes specified command with given parameters.
*
* Shorthand for:
*
* editor.commands.get( commandName ).execute( ... );
*
* @param {String} commandName Name of command to execute.
* @param {*} [commandParam] If set, command will be executed with this parameter.
* @param {*} [...commandParams] Command parameters.
*/
execute( commandName, commandParam ) {
let command = this.commands.get( commandName );
if ( !command ) {
/**
* Specified command has not been added to the editor.
*
* @error editor-command-not-found
*/
throw new CKEditorError( 'editor-command-not-found: Specified command has not been added to the editor.' );
}
command._execute( commandParam );
execute( ...args ) {
this.commands.execute( ...args );
}

@@ -189,3 +211,4 @@

*
* @param {Object} config See {@link module:core/editor/editor~Editor}'s param.
* @param {Object} config The editor config. You can find the list of config options in
* {@link module:core/editor/editorconfig~EditorConfig}.
* @returns {Promise} Promise resolved once editor is ready.

@@ -195,3 +218,3 @@ * @returns {module:core/editor/editor~Editor} return.editor The editor instance.

static create( config ) {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
const editor = new this( config );

@@ -211,3 +234,3 @@

mix( Editor, EmitterMixin );
mix( Editor, ObservableMixin );

@@ -246,1 +269,15 @@ /**

*/
/**
* Additional data built into the editor class. It's used while bundling the editor in order to provide
* the default set of plugins and config options which are later used during editor initialization.
*
* Two properties are supported:
*
* * `plugins` – an array of plugin constructors. They will be automatically initialized by the editor, unless listed
* in `config.removePlugins` or unless `config.plugins` is passed.
* * `config` – the defalt config options.
*
* @static
* @member {Object} module:core/editor/editor~Editor.build
*/

@@ -42,8 +42,9 @@ /**

this.editing = new EditingController( this.document );
this.editing.view.bind( 'isReadOnly' ).to( this );
/**
* Instance of the {@link module:core/keystrokehandler~KeystrokeHandler}.
* Instance of the {@link module:core/editingkeystrokehandler~EditingKeystrokeHandler}.
*
* @readonly
* @member {module:core/keystrokehandler~KeystrokeHandler}
* @member {module:core/editingkeystrokehandler~EditingKeystrokeHandler}
*/

@@ -110,3 +111,4 @@ this.keystrokes = new EditingKeystrokeHandler( this );

* @param {HTMLElement} element See {@link module:core/editor/standardeditor~StandardEditor}'s param.
* @param {Object} config See {@link module:core/editor/standardeditor~StandardEditor}'s param.
* @param {Object} config The editor config. You can find the list of config options in
* {@link module:core/editor/editorconfig~EditorConfig}.
* @returns {Promise} Promise resolved once editor is ready.

@@ -116,3 +118,3 @@ * @returns {module:core/editor/standardeditor~StandardEditor} return.editor The editor instance.

static create( element, config ) {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
const editor = new this( element, config );

@@ -119,0 +121,0 @@

@@ -16,18 +16,8 @@ /**

*
* @mixes module:utils/observablemixin~ObservaleMixin
* @implements module:core/plugin~PluginInterface
* @mixes module:utils/observablemixin~ObservableMixin
*/
export default class Plugin {
/**
* Creates a new Plugin instance. This is the first step of a plugin initialization.
* See also {@link #init} and {@link #afterInit}.
*
* A plugin is always instantiated after its {@link module:core/plugin~Plugin.requires dependencies} and the
* {@link #init} and {@link #afterInit} methods are called in the same order.
*
* Usually, you'll want to put your plugin's initialization code in the {@link #init} method.
* The constructor can be understood as "before init" and used in special cases, just like
* {@link #afterInit} servers for the special "after init" scenarios (e.g. code which depends on other
* plugins, but which doesn't {@link module:core/plugin~Plugin.requires explicitly require} them).
*
* @param {module:core/editor/editor~Editor} editor
* @inheritDoc
*/

@@ -39,3 +29,3 @@ constructor( editor ) {

* @readonly
* @member {module:core/editor/editor~Editor} module:core/plugin~Plugin#editor
* @member {module:core/editor/editor~Editor} #editor
*/

@@ -46,66 +36,126 @@ this.editor = editor;

/**
* An array of plugins required by this plugin.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* import Image from './image.js';
*
* export default class ImageCaption extends Plugin {
* static get requires() {
* return [ Image ];
* }
* }
*
* @static
* @member {Array.<Function>|undefined} module:core/plugin~Plugin.requires
* @inheritDoc
*/
destroy() {
this.stopListening();
}
}
/**
* Optional name of the plugin. If set, the plugin will be available in
* {@link module:core/plugincollection~PluginCollection#get} by its
* name and its constructor. If not, then only by its constructor.
*
* The name should reflect the package name + the plugin module name. E.g. `ckeditor5-image/src/image.js` plugin
* should be named `image/image` (the `ckeditor5-` prefix is stripped during compilation). If plugin is kept
* deeper in the directory structure, it's recommended to only use the module file name, not the whole path.
* So, e.g. a plugin defined in `ckeditor5-ui/src/notification/notification.js` file may be named `ui/notification`.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* export default class ImageCaption {
* static get pluginName() {
* return 'image/imagecaption';
* }
* }
*
* @static
* @member {String|undefined} module:core/plugin~Plugin.pluginName
*/
mix( Plugin, ObservableMixin );
/**
* The second stage (after plugin {@link #constructor}) of plugin initialization.
* Unlike the plugin constructor this method can perform asynchronous.
*
* A plugin's `init()` method is called after its {@link module:core/plugin~Plugin.requires dependencies} are initialized,
* so in the same order as constructors of these plugins.
*
* @returns {null|Promise}
*/
init() {}
/**
* The base interface for CKEditor plugins.
*
* In its minimal form it can be a simple function (it will be used as a constructor) which accepts
* {@link module:core/editor/editor~Editor the editor} as a parm.
* It can also implement a few methods which, when present, will be used to properly initialize and destroy the plugin.
*
* // A simple plugin which enables a data processor.
* function MyPlugin( editor ) {
* editor.data.processor = new MyDataProcessor();
* }
*
* In most cases, however, you'll want to inherit from the {@link module:core/plugin~Plugin} class which implements the
* {@link module:utils/observablemixin~ObservableMixin} and is, therefore, more convenient:
*
* class MyPlugin extends Plugin {
* init() {
* // `listenTo()` and `editor` are available thanks to `Plugin`.
* // By using `listenTo()` you'll ensure that the listener will be removed when
* // the plugin is destroyed.
* this.listenTo( this.editor, 'dataReady', () => {
* // Do something when data is ready.
* } );
* }
* }
*
* @interface PluginInterface
*/
/**
* The third (and last) stage of plugin initialization. See also {@link #constructor} and {@link #init}.
*
* @returns {null|Promise}
*/
afterInit() {}
/**
* Creates a new plugin instance. This is the first step of a plugin initialization.
* See also {@link #init} and {@link #afterInit}.
*
* A plugin is always instantiated after its {@link module:core/plugin~PluginInterface.requires dependencies} and the
* {@link #init} and {@link #afterInit} methods are called in the same order.
*
* Usually, you'll want to put your plugin's initialization code in the {@link #init} method.
* The constructor can be understood as "before init" and used in special cases, just like
* {@link #afterInit} servers for the special "after init" scenarios (e.g. code which depends on other
* plugins, but which doesn't {@link module:core/plugin~PluginInterface.requires explicitly require} them).
*
* @method #constructor
* @param {module:core/editor/editor~Editor} editor
*/
/**
* Destroys the plugin.
*
* @returns {null|Promise}
*/
destroy() {}
}
/**
* An array of plugins required by this plugin.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* import Image from './image.js';
*
* export default class ImageCaption {
* static get requires() {
* return [ Image ];
* }
* }
*
* @static
* @readonly
* @member {Array.<Function>|undefined} module:core/plugin~PluginInterface.requires
*/
mix( Plugin, ObservableMixin );
/**
* Optional name of the plugin. If set, the plugin will be available in
* {@link module:core/plugincollection~PluginCollection#get} by its
* name and its constructor. If not, then only by its constructor.
*
* The name should reflect the constructor name.
*
* To keep a plugin class definition tight it's recommended to define this property as a static getter:
*
* export default class ImageCaption {
* static get pluginName() {
* return 'ImageCaption';
* }
* }
*
* Note: The native `Function.name` property could not be used to keep the plugin name because
* it will be mangled during code minification.
*
* @static
* @readonly
* @member {String|undefined} module:core/plugin~PluginInterface.pluginName
*/
/**
* The second stage (after plugin {@link #constructor}) of plugin initialization.
* Unlike the plugin constructor this method can be asynchronous.
*
* A plugin's `init()` method is called after its {@link module:core/plugin~PluginInterface.requires dependencies} are initialized,
* so in the same order as constructors of these plugins.
*
* **Note:** This method is optional. A plugin instance does not need to have to have it defined.
*
* @method #init
* @returns {null|Promise}
*/
/**
* The third (and last) stage of plugin initialization. See also {@link #constructor} and {@link #init}.
*
* **Note:** This method is optional. A plugin instance does not need to have to have it defined.
*
* @method #afterInit
* @returns {null|Promise}
*/
/**
* Destroys the plugin.
*
* **Note:** This method is optional. A plugin instance does not need to have to have it defined.
*
* @method #destroy
* @returns {null|Promise}
*/

@@ -10,3 +10,2 @@ /**

import Plugin from './plugin';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';

@@ -25,3 +24,3 @@ import log from '@ckeditor/ckeditor5-utils/src/log';

* @param {Array.<Function>} [availablePlugins] Plugins (constructors) which the collection will be able to use
* when {@link module:core/plugin~PluginCollection#load} is used with plugin names (strings, instead of constructors).
* when {@link module:core/plugincollection~PluginCollection#load} is used with plugin names (strings, instead of constructors).
* Usually, the editor will pass its built-in plugins to the collection so they can later be

@@ -63,3 +62,3 @@ * used in `config.plugins` or `config.removePlugins` by names.

*/
*[ Symbol.iterator ]() {
* [ Symbol.iterator ]() {
for ( const entry of this._plugins ) {

@@ -75,4 +74,4 @@ if ( typeof entry[ 0 ] == 'function' ) {

*
* @param {Function|String} key The plugin constructor or {@link module:core/plugin~Plugin.pluginName name}.
* @returns {module:core/plugin~Plugin}
* @param {Function|String} key The plugin constructor or {@link module:core/plugin~PluginInterface.pluginName name}.
* @returns {module:core/plugin~PluginInterface}
*/

@@ -86,4 +85,4 @@ get( key ) {

*
* @param {Array.<Function|String>} plugins An array of {@link module:core/plugin~Plugin plugin constructors}
* or {@link module:core/plugin~Plugin.pluginName plugin names}. The second option (names) work only if
* @param {Array.<Function|String>} plugins An array of {@link module:core/plugin~PluginInterface plugin constructors}
* or {@link module:core/plugin~PluginInterface.pluginName plugin names}. The second option (names) work only if
* `availablePlugins` were passed to the {@link #constructor}.

@@ -94,3 +93,3 @@ * @param {Array.<String|Function>} [removePlugins] Names of plugins or plugin constructors

* collection.
* @returns {Promise.<Array.<module:core/plugin~Plugin>>} returns.loadedPlugins The array of loaded plugins.
* @returns {Promise.<Array.<module:core/plugin~PluginInterface>>} returns.loadedPlugins The array of loaded plugins.
*/

@@ -142,3 +141,3 @@ load( plugins, removePlugins = [] ) {

return instantiatePlugin( PluginConstructor )
.catch( ( err ) => {
.catch( err => {
/**

@@ -157,9 +156,7 @@ * It was not possible to load the plugin.

function instantiatePlugin( PluginConstructor ) {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
loading.add( PluginConstructor );
assertIsPlugin( PluginConstructor );
if ( PluginConstructor.requires ) {
PluginConstructor.requires.forEach( ( RequiredPluginConstructorOrName ) => {
PluginConstructor.requires.forEach( RequiredPluginConstructorOrName => {
const RequiredPluginConstructor = getPluginConstructor( RequiredPluginConstructorOrName );

@@ -202,17 +199,2 @@

function assertIsPlugin( PluginConstructor ) {
if ( !( PluginConstructor.prototype instanceof Plugin ) ) {
/**
* The loaded plugin module is not an instance of {@link module:core/plugin~Plugin}.
*
* @error plugincollection-instance
* @param {*} plugin The constructor which is meant to be loaded as a plugin.
*/
throw new CKEditorError(
'plugincollection-instance: The loaded plugin module is not an instance of Plugin.',
{ plugin: PluginConstructor }
);
}
}
function getMissingPluginNames( plugins ) {

@@ -238,2 +220,16 @@ const missingPlugins = [];

/**
* Destroys all loaded plugins.
*
* @returns {Promise}
*/
destroy() {
const promises = Array.from( this )
.map( ( [ , pluginInstance ] ) => pluginInstance )
.filter( pluginInstance => typeof pluginInstance.destroy == 'function' )
.map( pluginInstance => pluginInstance.destroy() );
return Promise.all( promises );
}
/**
* Adds the plugin to the collection. Exposed mainly for testing purposes.

@@ -243,3 +239,3 @@ *

* @param {Function} PluginConstructor The plugin constructor.
* @param {module:core/plugin~Plugin} plugin The instance of the plugin.
* @param {module:core/plugin~PluginInterface} plugin The instance of the plugin.
*/

@@ -249,6 +245,27 @@ _add( PluginConstructor, plugin ) {

if ( PluginConstructor.pluginName ) {
this._plugins.set( PluginConstructor.pluginName, plugin );
const pluginName = PluginConstructor.pluginName;
if ( !pluginName ) {
return;
}
if ( this._plugins.has( pluginName ) ) {
/**
* Two plugins with the same {@link module:core/plugin~PluginInterface.pluginName} were loaded.
* This may lead to runtime conflicts between these plugins. This usually means that incorrect
* params were passed to {@link module:core/editor/editor~Editor.create}.
*
* @error plugincollection-plugin-name-conflict
* @param {String} pluginName The duplicated plugin name.
* @param {Function} plugin1 The first plugin constructor.
* @param {Function} plugin2 The second plugin constructor.
*/
log.warn(
'plugincollection-plugin-name-conflict: Two plugins with the same name were loaded.',
{ pluginName, plugin1: this._plugins.get( pluginName ).constructor, plugin2: PluginConstructor }
);
} else {
this._plugins.set( pluginName, plugin );
}
}
}

@@ -110,3 +110,4 @@ /**

return ClassicTestEditor.create( editorElement, {
return ClassicTestEditor
.create( editorElement, {
plugins: [ EventWatcher ]

@@ -113,0 +114,0 @@ } )

@@ -39,12 +39,7 @@ /**

describe( 'init()', () => {
it( 'returns a promise', () => {
expect( ui.init() ).to.be.instanceof( Promise );
} );
it( 'initializes the #view', () => {
const spy = sinon.spy( view, 'init' );
return ui.init().then( () => {
sinon.assert.calledOnce( spy );
} );
ui.init();
sinon.assert.calledOnce( spy );
} );

@@ -54,18 +49,10 @@ } );

describe( 'destroy()', () => {
it( 'returns a promise', () => {
return ui.init().then( () => {
expect( ui.destroy() ).to.be.instanceof( Promise );
} );
} );
it( 'destroys the #view', () => {
const spy = sinon.spy( view, 'destroy' );
return ui.init()
.then( () => ui.destroy() )
.then( () => {
sinon.assert.calledOnce( spy );
} );
ui.init();
ui.destroy();
sinon.assert.calledOnce( spy );
} );
} );
} );

@@ -61,3 +61,4 @@ /**

return ModelTestEditor.create( {
return ModelTestEditor
.create( {
plugins: [ EventWatcher ]

@@ -64,0 +65,0 @@ } )

@@ -60,3 +60,4 @@ /**

return VirtualTestEditor.create( {
return VirtualTestEditor
.create( {
plugins: [ EventWatcher ]

@@ -63,0 +64,0 @@ } )

@@ -45,5 +45,5 @@ /**

this._elementReplacer.restore();
this.ui.destroy();
return this.ui.destroy()
.then( () => super.destroy() );
return super.destroy();
}

@@ -55,3 +55,3 @@

static create( element, config ) {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
const editor = new this( element, config );

@@ -62,4 +62,6 @@

.then( () => editor._elementReplacer.replace( element, editor.ui.view.element ) )
.then( () => editor.ui.init() )
.then( () => editor.fire( 'uiReady' ) )
.then( () => {
editor.ui.init();
editor.fire( 'uiReady' );
} )
.then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) )

@@ -66,0 +68,0 @@ .then( () => editor.loadDataFromEditorElement() )

@@ -61,3 +61,3 @@ /**

init() {
return this.view.init();
this.view.init();
}

@@ -71,4 +71,4 @@

destroy() {
return this.view.destroy();
this.view.destroy();
}
}

@@ -50,3 +50,3 @@ /**

static create( config ) {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
const editor = new this( config );

@@ -53,0 +53,0 @@

@@ -36,3 +36,3 @@ /**

static create( config ) {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
const editor = new this( config );

@@ -39,0 +39,0 @@

@@ -11,3 +11,3 @@ /**

describe( 'EditingKeystrokeHandler', () => {
let editor, keystrokes;
let editor, keystrokes, executeSpy;

@@ -19,26 +19,76 @@ beforeEach( () => {

keystrokes = new EditingKeystrokeHandler( editor );
executeSpy = sinon.stub( editor, 'execute' );
} );
} );
describe( 'listenTo()', () => {
it( 'prevents default when keystroke was handled', () => {
const keyEvtData = { keyCode: 1, preventDefault: sinon.spy() };
describe( 'set()', () => {
describe( 'with a command', () => {
it( 'prevents default when the keystroke was handled', () => {
const keyEvtData = getCtrlA();
sinon.stub( keystrokes, 'press' ).returns( true );
keystrokes.set( 'Ctrl+A', 'foo' );
keystrokes.press( keyEvtData );
keystrokes.listenTo( editor.editing.view );
editor.editing.view.fire( 'keydown', keyEvtData );
sinon.assert.calledWithExactly( executeSpy, 'foo' );
sinon.assert.calledOnce( keyEvtData.preventDefault );
sinon.assert.calledOnce( keyEvtData.stopPropagation );
} );
sinon.assert.calledOnce( keyEvtData.preventDefault );
it( 'does not prevent default when the keystroke was not handled', () => {
const keyEvtData = getCtrlA();
keystrokes.press( keyEvtData );
sinon.assert.notCalled( executeSpy );
sinon.assert.notCalled( keyEvtData.preventDefault );
sinon.assert.notCalled( keyEvtData.stopPropagation );
} );
it( 'provides a callback which stops the event and remaining callbacks in the keystroke handler', () => {
const spy1 = sinon.spy();
const spy2 = sinon.spy();
const spy3 = sinon.spy();
keystrokes.set( [ 'ctrl', 'A' ], spy1 );
keystrokes.set( [ 'ctrl', 'A' ], spy2, { priority: 'high' } );
keystrokes.set( [ 'ctrl', 'A' ], 'foo', { priority: 'low' } );
keystrokes.set( [ 'ctrl', 'A' ], ( keyEvtData, cancel ) => {
spy3();
cancel();
} );
keystrokes.press( getCtrlA() );
sinon.assert.callOrder( spy2, spy1, spy3 );
sinon.assert.notCalled( executeSpy );
} );
} );
it( 'does not prevent default when keystroke was not handled', () => {
const keyEvtData = { keyCode: 1, preventDefault: sinon.spy() };
describe( 'with a callback', () => {
it( 'never prevents default', () => {
const callback = sinon.spy();
const keyEvtData = getCtrlA();
sinon.stub( keystrokes, 'press' ).returns( false );
keystrokes.set( 'Ctrl+A', callback );
keystrokes.press( keyEvtData );
keystrokes.listenTo( editor.editing.view );
editor.editing.view.fire( 'keydown', keyEvtData );
sinon.assert.calledOnce( callback );
sinon.assert.notCalled( keyEvtData.preventDefault );
sinon.assert.notCalled( keyEvtData.stopPropagation );
} );
} );
sinon.assert.notCalled( keyEvtData.preventDefault );
it( 'supports priorities', () => {
const spy1 = sinon.spy();
const spy2 = sinon.spy();
const spy3 = sinon.spy();
keystrokes.set( [ 'ctrl', 'A' ], spy1 );
keystrokes.set( [ 'ctrl', 'A' ], spy2, { priority: 'high' } );
keystrokes.set( [ 'ctrl', 'A' ], 'foo', { priority: 'low' } );
keystrokes.set( [ 'ctrl', 'A' ], spy3 );
keystrokes.press( getCtrlA() );
sinon.assert.callOrder( spy2, spy1, spy3, executeSpy );
} );

@@ -49,10 +99,8 @@ } );

it( 'executes a command', () => {
const spy = sinon.stub( editor, 'execute' );
keystrokes.set( 'Ctrl+A', 'foo' );
keystrokes.set( 'ctrl + A', 'foo' );
const wasHandled = keystrokes.press( getCtrlA() );
sinon.assert.calledOnce( spy );
sinon.assert.calledWithExactly( spy, 'foo' );
sinon.assert.calledOnce( executeSpy );
sinon.assert.calledWithExactly( executeSpy, 'foo' );
expect( wasHandled ).to.be.true;

@@ -62,6 +110,5 @@ } );

it( 'executes a callback', () => {
const executeSpy = sinon.stub( editor, 'execute' );
const callback = sinon.spy();
keystrokes.set( 'ctrl + A', callback );
keystrokes.set( 'Ctrl+A', callback );

@@ -78,3 +125,8 @@ const wasHandled = keystrokes.press( getCtrlA() );

function getCtrlA() {
return { keyCode: keyCodes.a, ctrlKey: true };
return {
keyCode: keyCodes.a,
ctrlKey: true,
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
}

@@ -12,2 +12,6 @@ /**

import PluginCollection from '../../src/plugincollection';
import CommandCollection from '../../src/commandcollection';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import Locale from '@ckeditor/ckeditor5-utils/src/locale';
import Command from '../../src/command';

@@ -70,2 +74,24 @@ class PluginA extends Plugin {

class PluginE {
constructor( editor ) {
this.editor = editor;
this.init = sinon.spy().named( 'E' );
}
static get pluginName() {
return 'E';
}
}
class PluginF {
constructor( editor ) {
this.editor = editor;
this.afterInit = sinon.spy().named( 'F-after' );
}
static get pluginName() {
return 'F';
}
}
describe( 'Editor', () => {

@@ -81,3 +107,3 @@ afterEach( () => {

expect( editor.config ).to.be.an.instanceof( Config );
expect( editor.commands ).to.be.an.instanceof( Map );
expect( editor.commands ).to.be.an.instanceof( CommandCollection );

@@ -123,5 +149,93 @@ expect( editor.plugins ).to.be.an.instanceof( PluginCollection );

describe( 'create', () => {
describe( 'locale', () => {
it( 'is instantiated and t() is exposed', () => {
const editor = new Editor();
expect( editor.locale ).to.be.instanceof( Locale );
expect( editor.t ).to.equal( editor.locale.t );
} );
it( 'is configured with the config.lang', () => {
const editor = new Editor( { lang: 'pl' } );
expect( editor.locale.lang ).to.equal( 'pl' );
} );
} );
describe( 'isReadOnly', () => {
it( 'is false initially', () => {
const editor = new Editor();
expect( editor.isReadOnly ).to.false;
} );
it( 'is observable', () => {
const editor = new Editor();
const spy = sinon.spy();
editor.on( 'change:isReadOnly', spy );
editor.isReadOnly = true;
sinon.assert.calledOnce( spy );
} );
} );
describe( 'destroy()', () => {
it( 'should fire "destroy"', () => {
const editor = new Editor();
const spy = sinon.spy();
editor.on( 'destroy', spy );
return editor.destroy().then( () => {
expect( spy.calledOnce ).to.be.true;
} );
} );
it( 'should destroy all components it initialized', () => {
const editor = new Editor();
const spy1 = sinon.spy( editor.data, 'destroy' );
const spy2 = sinon.spy( editor.document, 'destroy' );
const spy3 = sinon.spy( editor.plugins, 'destroy' );
return editor.destroy()
.then( () => {
expect( spy1.calledOnce ).to.be.true;
expect( spy2.calledOnce ).to.be.true;
expect( spy3.calledOnce ).to.be.true;
} );
} );
} );
describe( 'execute()', () => {
it( 'should execute specified command', () => {
class SomeCommand extends Command {
execute() {}
}
const editor = new Editor();
const command = new SomeCommand( editor );
sinon.spy( command, 'execute' );
editor.commands.add( 'someCommand', command );
editor.execute( 'someCommand' );
expect( command.execute.calledOnce ).to.be.true;
} );
it( 'should throw an error if specified command has not been added', () => {
const editor = new Editor();
expect( () => {
editor.execute( 'command' );
} ).to.throw( CKEditorError, /^commandcollection-command-not-found:/ );
} );
} );
describe( 'create()', () => {
it( 'should return a promise that resolves properly', () => {
let promise = Editor.create();
const promise = Editor.create();

@@ -134,5 +248,3 @@ expect( promise ).to.be.an.instanceof( Promise );

it( 'loads plugins', () => {
return Editor.create( {
plugins: [ PluginA ]
} )
return Editor.create( { plugins: [ PluginA ] } )
.then( editor => {

@@ -160,5 +272,3 @@ expect( getPlugins( editor ).length ).to.equal( 1 );

return Editor.create( {
plugins: [ EventWatcher ]
} )
return Editor.create( { plugins: [ EventWatcher ] } )
.then( () => {

@@ -170,3 +280,3 @@ expect( fired ).to.deep.equal( [ 'pluginsReady', 'dataReady', 'ready' ] );

describe( 'initPlugins', () => {
describe( 'initPlugins()', () => {
it( 'should load plugins', () => {

@@ -195,15 +305,16 @@ const editor = new Editor( {

return editor.initPlugins().then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).init,
editor.plugins.get( PluginB ).init,
editor.plugins.get( PluginC ).init,
editor.plugins.get( PluginD ).init,
editor.plugins.get( PluginA ).afterInit,
editor.plugins.get( PluginB ).afterInit,
editor.plugins.get( PluginC ).afterInit,
editor.plugins.get( PluginD ).afterInit,
pluginsReadySpy
);
} );
return editor.initPlugins()
.then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).init,
editor.plugins.get( PluginB ).init,
editor.plugins.get( PluginC ).init,
editor.plugins.get( PluginD ).init,
editor.plugins.get( PluginA ).afterInit,
editor.plugins.get( PluginB ).afterInit,
editor.plugins.get( PluginC ).afterInit,
editor.plugins.get( PluginD ).afterInit,
pluginsReadySpy
);
} );
} );

@@ -231,3 +342,3 @@

this.init = sinon.spy( () => {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
setTimeout( () => {

@@ -246,11 +357,12 @@ asyncSpy();

return editor.initPlugins().then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).init,
editor.plugins.get( PluginAsync ).init,
// This one is called with delay by the async init.
asyncSpy,
editor.plugins.get( PluginSync ).init
);
} );
return editor.initPlugins()
.then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).init,
editor.plugins.get( PluginAsync ).init,
// This one is called with delay by the async init.
asyncSpy,
editor.plugins.get( PluginSync ).init
);
} );
} );

@@ -278,3 +390,3 @@

this.afterInit = sinon.spy( () => {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
setTimeout( () => {

@@ -293,12 +405,13 @@ asyncSpy();

return editor.initPlugins().then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).afterInit,
editor.plugins.get( PluginAsync ).afterInit,
return editor.initPlugins()
.then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).afterInit,
editor.plugins.get( PluginAsync ).afterInit,
// This one is called with delay by the async init.
asyncSpy,
editor.plugins.get( PluginSync ).afterInit
);
} );
// This one is called with delay by the async init.
asyncSpy,
editor.plugins.get( PluginSync ).afterInit
);
} );
} );

@@ -458,5 +571,43 @@

expect( getPlugins( editor ).length ).to.equal( 1 );
expect( editor.plugins.get( PluginA ) ).to.be.an.instanceof( Plugin );
expect( editor.plugins.get( PluginA ) ).to.not.be.undefined;
} );
} );
it( 'should not call "afterInit" method if plugin does not have this method', () => {
const editor = new Editor( {
plugins: [ PluginA, PluginE ]
} );
const pluginsReadySpy = sinon.spy().named( 'pluginsReady' );
editor.on( 'pluginsReady', pluginsReadySpy );
return editor.initPlugins()
.then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).init,
editor.plugins.get( PluginE ).init,
editor.plugins.get( PluginA ).afterInit,
pluginsReadySpy
);
} );
} );
it( 'should not call "init" method if plugin does not have this method', () => {
const editor = new Editor( {
plugins: [ PluginA, PluginF ]
} );
const pluginsReadySpy = sinon.spy().named( 'pluginsReady' );
editor.on( 'pluginsReady', pluginsReadySpy );
return editor.initPlugins()
.then( () => {
sinon.assert.callOrder(
editor.plugins.get( PluginA ).init,
editor.plugins.get( PluginA ).afterInit,
editor.plugins.get( PluginF ).afterInit,
pluginsReadySpy
);
} );
} );
} );

@@ -463,0 +614,0 @@ } );

@@ -34,2 +34,14 @@ /**

it( 'should bind editing.view#isReadOnly to the editor', () => {
const editor = new StandardEditor( editorElement, { foo: 1 } );
editor.isReadOnly = false;
expect( editor.editing.view.isReadOnly ).to.false;
editor.isReadOnly = true;
expect( editor.editing.view.isReadOnly ).to.true;
} );
it( 'activates #keystrokes', () => {

@@ -97,6 +109,3 @@ const spy = sinon.spy( EditingKeystrokeHandler.prototype, 'listenTo' );

return StandardEditor.create( editorElement, {
foo: 1,
plugins: [ PluginFoo ]
} )
return StandardEditor.create( editorElement, { foo: 1, plugins: [ PluginFoo ] } )
.then( editor => {

@@ -127,5 +136,3 @@ expect( editor ).to.be.instanceof( StandardEditor );

return StandardEditor.create( editorElement, {
plugins: [ EventWatcher ]
} )
return StandardEditor.create( editorElement, { plugins: [ EventWatcher ] } )
.then( () => {

@@ -132,0 +139,0 @@ expect( fired ).to.deep.equal( [ 'pluginsReady', 'dataReady', 'ready' ] );

@@ -17,26 +17,23 @@ /**

it( 'should set the `editor` property', () => {
let plugin = new Plugin( editor );
const plugin = new Plugin( editor );
expect( plugin ).to.have.property( 'editor' ).to.equal( editor );
} );
} );
describe( 'init', () => {
it( 'should exist and do nothing', () => {
let plugin = new Plugin( editor );
describe( 'destroy()', () => {
it( 'should be defined', () => {
const plugin = new Plugin( editor );
expect( plugin.init ).to.be.a( 'function' );
expect( plugin.destroy ).to.be.a( 'function' );
} );
plugin.init();
} );
} );
it( 'should stop listening', () => {
const plugin = new Plugin( editor );
const stopListeningSpy = sinon.spy( plugin, 'stopListening' );
describe( 'destroy', () => {
it( 'should exist and do nothing', () => {
let plugin = new Plugin( editor );
plugin.destroy();
expect( plugin.destroy ).to.be.a( 'function' );
plugin.destroy();
expect( stopListeningSpy.calledOnce ).to.equal( true );
} );
} );
} );

@@ -6,2 +6,4 @@ /**

/* globals setTimeout */
import testUtils from '../tests/_utils/utils';

@@ -15,3 +17,3 @@ import Editor from '../src/editor/editor';

let editor, availablePlugins;
let PluginA, PluginB, PluginC, PluginD, PluginE, PluginF, PluginG, PluginH, PluginI, PluginJ, PluginK, PluginX;
let PluginA, PluginB, PluginC, PluginD, PluginE, PluginF, PluginG, PluginH, PluginI, PluginJ, PluginK, PluginX, PluginFoo, AnotherPluginFoo;
class TestError extends Error {}

@@ -42,2 +44,4 @@ class ChildPlugin extends Plugin {}

};
PluginFoo = createPlugin( 'Foo' );
AnotherPluginFoo = createPlugin( 'Foo' );

@@ -71,2 +75,4 @@ PluginC.requires = [ PluginB ];

];
PluginFoo.requires = [];
} );

@@ -76,3 +82,3 @@

it( 'should not fail when trying to load 0 plugins (empty array)', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -86,3 +92,3 @@ return plugins.load( [] )

it( 'should add collection items for loaded plugins', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -99,3 +105,3 @@ return plugins.load( [ PluginA, PluginB ] )

it( 'should add collection items for loaded plugins using plugin names', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -112,7 +118,7 @@ return plugins.load( [ 'A', 'B' ] )

it( 'should load dependency plugins', () => {
let plugins = new PluginCollection( editor, availablePlugins );
let spy = sinon.spy( plugins, '_add' );
const plugins = new PluginCollection( editor, availablePlugins );
const spy = sinon.spy( plugins, '_add' );
return plugins.load( [ PluginA, PluginC ] )
.then( ( loadedPlugins ) => {
.then( loadedPlugins => {
expect( getPlugins( plugins ).length ).to.equal( 3 );

@@ -128,7 +134,7 @@

it( 'should load dependency plugins defined by plugin names', () => {
let plugins = new PluginCollection( editor, availablePlugins );
let spy = sinon.spy( plugins, '_add' );
const plugins = new PluginCollection( editor, availablePlugins );
const spy = sinon.spy( plugins, '_add' );
return plugins.load( [ 'J' ] )
.then( ( loadedPlugins ) => {
.then( loadedPlugins => {
expect( getPlugins( plugins ).length ).to.equal( 3 );

@@ -144,7 +150,7 @@

it( 'should be ok when dependencies are loaded first', () => {
let plugins = new PluginCollection( editor, availablePlugins );
let spy = sinon.spy( plugins, '_add' );
const plugins = new PluginCollection( editor, availablePlugins );
const spy = sinon.spy( plugins, '_add' );
return plugins.load( [ PluginA, PluginB, PluginC ] )
.then( ( loadedPlugins ) => {
.then( loadedPlugins => {
expect( getPlugins( plugins ).length ).to.equal( 3 );

@@ -160,7 +166,7 @@

it( 'should load deep dependency plugins', () => {
let plugins = new PluginCollection( editor, availablePlugins );
let spy = sinon.spy( plugins, '_add' );
const plugins = new PluginCollection( editor, availablePlugins );
const spy = sinon.spy( plugins, '_add' );
return plugins.load( [ PluginD ] )
.then( ( loadedPlugins ) => {
.then( loadedPlugins => {
expect( getPlugins( plugins ).length ).to.equal( 4 );

@@ -177,7 +183,7 @@

it( 'should handle cross dependency plugins', () => {
let plugins = new PluginCollection( editor, availablePlugins );
let spy = sinon.spy( plugins, '_add' );
const plugins = new PluginCollection( editor, availablePlugins );
const spy = sinon.spy( plugins, '_add' );
return plugins.load( [ PluginA, PluginE ] )
.then( ( loadedPlugins ) => {
.then( loadedPlugins => {
expect( getPlugins( plugins ).length ).to.equal( 3 );

@@ -194,3 +200,3 @@

it( 'should load grand child classes', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -203,41 +209,54 @@ return plugins.load( [ PluginG ] )

it( 'should set the `editor` property on loaded plugins', () => {
let plugins = new PluginCollection( editor, availablePlugins );
it( 'should load plugin which does not extend the base Plugin class', () => {
class Y { }
return plugins.load( [ PluginA, PluginB ] )
const plugins = new PluginCollection( editor, availablePlugins );
return plugins.load( [ Y ] )
.then( () => {
expect( plugins.get( PluginA ).editor ).to.equal( editor );
expect( plugins.get( PluginB ).editor ).to.equal( editor );
expect( getPlugins( plugins ).length ).to.equal( 1 );
} );
} );
it( 'should reject on broken plugins (forward the error thrown in a plugin)', () => {
let logSpy = testUtils.sinon.stub( log, 'error' );
it( 'should load plugin which is a simple function', () => {
function pluginAsFunction( editor ) {
this.editor = editor;
}
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );
return plugins.load( [ PluginA, PluginX, PluginB ] )
// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.
return plugins.load( [ pluginAsFunction ] )
.then( () => {
throw new Error( 'Test error: this promise should not be resolved successfully' );
} )
.catch( ( err ) => {
expect( err ).to.be.an.instanceof( TestError );
expect( err ).to.have.property( 'message', 'Some error inside a plugin' );
sinon.assert.calledOnce( logSpy );
expect( logSpy.args[ 0 ][ 0 ] ).to.match( /^plugincollection-load:/ );
expect( getPlugins( plugins ).length ).to.equal( 1 );
} );
} );
it( 'should reject when loading a module which is not a plugin', () => {
class Y {}
it( 'should set the `editor` property on loaded plugins', () => {
const plugins = new PluginCollection( editor, availablePlugins );
availablePlugins.push( Y );
function pluginAsFunction( editor ) {
this.editor = editor;
}
let logSpy = testUtils.sinon.stub( log, 'error' );
class Y {
constructor( editor ) {
this.editor = editor;
}
}
let plugins = new PluginCollection( editor, availablePlugins );
return plugins.load( [ PluginA, PluginB, pluginAsFunction, Y ] )
.then( () => {
expect( plugins.get( PluginA ).editor ).to.equal( editor );
expect( plugins.get( PluginB ).editor ).to.equal( editor );
expect( plugins.get( pluginAsFunction ).editor ).to.equal( editor );
expect( plugins.get( Y ).editor ).to.equal( editor );
} );
} );
return plugins.load( [ Y ] )
it( 'should reject on broken plugins (forward the error thrown in a plugin)', () => {
const logSpy = testUtils.sinon.stub( log, 'error' );
const plugins = new PluginCollection( editor, availablePlugins );
return plugins.load( [ PluginA, PluginX, PluginB ] )
// Throw here, so if by any chance plugins.load() was resolved correctly catch() will be stil executed.

@@ -247,5 +266,5 @@ .then( () => {

} )
.catch( ( err ) => {
expect( err ).to.be.an.instanceof( CKEditorError );
expect( err.message ).to.match( /^plugincollection-instance/ );
.catch( err => {
expect( err ).to.be.an.instanceof( TestError );
expect( err ).to.have.property( 'message', 'Some error inside a plugin' );

@@ -258,5 +277,5 @@ sinon.assert.calledOnce( logSpy );

it( 'should reject when loading non-existent plugin', () => {
let logSpy = testUtils.sinon.stub( log, 'error' );
const logSpy = testUtils.sinon.stub( log, 'error' );
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -268,3 +287,3 @@ return plugins.load( [ 'NonExistentPlugin' ] )

} )
.catch( ( err ) => {
.catch( err => {
expect( err ).to.be.an.instanceof( CKEditorError );

@@ -279,3 +298,3 @@ expect( err.message ).to.match( /^plugincollection-plugin-not-found/ );

it( 'should load chosen plugins (plugins and removePlugins are constructors)', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -292,3 +311,3 @@ return plugins.load( [ PluginA, PluginB, PluginC ], [ PluginA ] )

it( 'should load chosen plugins (plugins are constructors, removePlugins are names)', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -305,3 +324,3 @@ return plugins.load( [ PluginA, PluginB, PluginC ], [ 'A' ] )

it( 'should load chosen plugins (plugins and removePlugins are names)', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -318,3 +337,3 @@ return plugins.load( [ 'A', 'B', 'C' ], [ 'A' ] )

it( 'should load chosen plugins (plugins are names, removePlugins are constructors)', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -331,5 +350,5 @@ return plugins.load( [ 'A', 'B', 'C' ], [ PluginA ] )

it( 'should load chosen plugins (plugins are names, removePlugins contains an anonymous plugin)', () => {
class AnonymousPlugin extends Plugin {}
class AnonymousPlugin {}
let plugins = new PluginCollection( editor, [ AnonymousPlugin ].concat( availablePlugins ) );
const plugins = new PluginCollection( editor, [ AnonymousPlugin ].concat( availablePlugins ) );

@@ -346,4 +365,4 @@ return plugins.load( [ AnonymousPlugin, 'A', 'B' ], [ AnonymousPlugin ] )

it( 'should reject when loaded plugin requires not allowed plugins', () => {
let logSpy = testUtils.sinon.stub( log, 'error' );
let plugins = new PluginCollection( editor, availablePlugins );
const logSpy = testUtils.sinon.stub( log, 'error' );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -355,3 +374,3 @@ return plugins.load( [ PluginA, PluginB, PluginC, PluginD ], [ PluginA, PluginB ] )

} )
.catch( ( err ) => {
.catch( err => {
expect( err ).to.be.an.instanceof( CKEditorError );

@@ -363,2 +382,65 @@ expect( err.message ).to.match( /^plugincollection-required/ );

} );
it( 'logs if tries to load more than one plugin with the same name', () => {
const logSpy = testUtils.sinon.stub( log, 'warn' );
const plugins = new PluginCollection( editor );
return plugins.load( [ PluginFoo, AnotherPluginFoo ] )
.then( () => {
expect( getPlugins( plugins ).length ).to.equal( 2 );
expect( plugins.get( 'Foo' ) ).to.be.an.instanceof( PluginFoo );
expect( plugins.get( PluginFoo ) ).to.be.an.instanceof( PluginFoo );
expect( plugins.get( AnotherPluginFoo ) ).to.be.an.instanceof( AnotherPluginFoo );
expect( logSpy.calledOnce ).to.equal( true );
expect( logSpy.firstCall.args[ 0 ] ).to.match( /^plugincollection-plugin-name-conflict:/ );
const warnData = logSpy.firstCall.args[ 1 ];
expect( warnData.pluginName ).to.equal( 'Foo' );
expect( warnData.plugin1 ).to.equal( PluginFoo );
expect( warnData.plugin2 ).to.equal( AnotherPluginFoo );
} );
} );
it( 'logs if tries to load more than one plugin with the same name (plugin requires plugin with the same name)', () => {
PluginFoo.requires = [ AnotherPluginFoo ];
const logSpy = testUtils.sinon.stub( log, 'warn' );
const plugins = new PluginCollection( editor );
return plugins.load( [ PluginFoo ] )
.then( () => {
expect( getPlugins( plugins ).length ).to.equal( 2 );
expect( plugins.get( 'Foo' ) ).to.be.an.instanceof( AnotherPluginFoo );
expect( plugins.get( AnotherPluginFoo ) ).to.be.an.instanceof( AnotherPluginFoo );
expect( plugins.get( PluginFoo ) ).to.be.an.instanceof( PluginFoo );
expect( logSpy.calledOnce ).to.equal( true );
expect( logSpy.firstCall.args[ 0 ] ).to.match( /^plugincollection-plugin-name-conflict:/ );
} );
} );
it(
'logs if tries to load more than one plugin with the same name (plugin with the same name is built-in the PluginCollection)',
() => {
availablePlugins = [ PluginFoo ];
const logSpy = testUtils.sinon.stub( log, 'warn' );
const plugins = new PluginCollection( editor, availablePlugins );
return plugins.load( [ 'Foo', AnotherPluginFoo ] )
.then( () => {
expect( getPlugins( plugins ).length ).to.equal( 2 );
expect( plugins.get( 'Foo' ) ).to.be.an.instanceof( PluginFoo );
expect( plugins.get( PluginFoo ) ).to.be.an.instanceof( PluginFoo );
expect( plugins.get( AnotherPluginFoo ) ).to.be.an.instanceof( AnotherPluginFoo );
expect( logSpy.calledOnce ).to.equal( true );
expect( logSpy.firstCall.args[ 0 ] ).to.match( /^plugincollection-plugin-name-conflict:/ );
} );
}
);
} );

@@ -372,3 +454,3 @@

let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -387,3 +469,3 @@ return plugins.load( [ SomePlugin ] )

let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -398,5 +480,80 @@ return plugins.load( [ SomePlugin ] )

describe( 'destroy()', () => {
it( 'calls Plugin#destroy() method on every loaded plugin', () => {
let destroySpyForPluginA, destroySpyForPluginB;
const plugins = new PluginCollection( editor, [] );
return plugins.load( [ PluginA, PluginB ] )
.then( () => {
destroySpyForPluginA = sinon.spy( plugins.get( PluginA ), 'destroy' );
destroySpyForPluginB = sinon.spy( plugins.get( PluginB ), 'destroy' );
return plugins.destroy();
} )
.then( () => {
expect( destroySpyForPluginA.calledOnce ).to.equal( true );
expect( destroySpyForPluginB.calledOnce ).to.equal( true );
} );
} );
it( 'waits until all plugins are destroyed', () => {
const destroyedPlugins = [];
class AsynchronousPluginA extends Plugin {
destroy() {
return new Promise( resolve => {
setTimeout( () => {
super.destroy();
destroyedPlugins.push( 'AsynchronousPluginA.destroy()' );
resolve();
} );
} );
}
}
class AsynchronousPluginB extends Plugin {
destroy() {
return new Promise( resolve => {
setTimeout( () => {
super.destroy();
destroyedPlugins.push( 'AsynchronousPluginB.destroy()' );
resolve();
} );
} );
}
}
const plugins = new PluginCollection( editor, [] );
return plugins.load( [ AsynchronousPluginA, AsynchronousPluginB ] )
.then( () => plugins.destroy() )
.then( () => {
expect( destroyedPlugins ).to.contain( 'AsynchronousPluginB.destroy()' );
expect( destroyedPlugins ).to.contain( 'AsynchronousPluginA.destroy()' );
} );
} );
it( 'does not execute Plugin#destroy() for plugins which do not have this method', () => {
class FooPlugin {
constructor( editor ) {
this.editor = editor;
}
}
const plugins = new PluginCollection( editor, [] );
return plugins.load( [ PluginA, FooPlugin ] )
.then( () => plugins.destroy() )
.then( destroyedPlugins => {
expect( destroyedPlugins.length ).to.equal( 1 );
} );
} );
} );
describe( 'iterator', () => {
it( 'exists', () => {
let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -414,3 +571,3 @@ expect( plugins ).to.have.property( Symbol.iterator );

let plugins = new PluginCollection( editor, availablePlugins );
const plugins = new PluginCollection( editor, availablePlugins );

@@ -448,10 +605,10 @@ return plugins.load( [ SomePlugin1, SomePlugin2 ] )

return addSpy.args
.map( ( arg ) => arg[ 0 ] )
.map( arg => arg[ 0 ] )
// Entries may be kept twice in the plugins map - once as a pluginName => plugin, once as pluginClass => plugin.
// Return only pluginClass => plugin entries as these will always represent all plugins.
.filter( ( plugin ) => typeof plugin == 'function' );
.filter( plugin => typeof plugin == 'function' );
}
function getPluginNames( plugins ) {
return plugins.map( ( plugin ) => plugin.pluginName );
return plugins.map( plugin => plugin.pluginName );
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc