|
|
@@ -11,13 +11,15 @@ import moduleUtils from '/tests/ckeditor5/_utils/module.js';
|
|
|
import testUtils from '/tests/ckeditor5/_utils/utils.js';
|
|
|
import Editor from '/ckeditor5/editor.js';
|
|
|
import EditorConfig from '/ckeditor5/editorconfig.js';
|
|
|
+import PluginCollection from '/ckeditor5/plugincollection.js';
|
|
|
+import EditableCollection from '/ckeditor5/editablecollection.js';
|
|
|
import Plugin from '/ckeditor5/plugin.js';
|
|
|
import Command from '/ckeditor5/command/command.js';
|
|
|
import Locale from '/ckeditor5/utils/locale.js';
|
|
|
import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
|
|
|
+import Document from '/ckeditor5/core/treemodel/document.js';
|
|
|
|
|
|
const pluginClasses = {};
|
|
|
-let element;
|
|
|
|
|
|
before( () => {
|
|
|
// Define fake plugins to be used in tests.
|
|
|
@@ -31,218 +33,276 @@ before( () => {
|
|
|
pluginDefinition( 'D/D', [ 'C/C' ] );
|
|
|
} );
|
|
|
|
|
|
-beforeEach( () => {
|
|
|
- element = document.createElement( 'div' );
|
|
|
- document.body.appendChild( element );
|
|
|
-} );
|
|
|
-
|
|
|
///////////////////
|
|
|
|
|
|
-describe( 'constructor', () => {
|
|
|
- it( 'should create a new editor instance', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
+describe( 'Editor', () => {
|
|
|
+ describe( 'constructor', () => {
|
|
|
+ it( 'should create a new editor instance', () => {
|
|
|
+ const editor = new Editor();
|
|
|
|
|
|
- expect( editor ).to.have.property( 'element' ).to.equal( element );
|
|
|
+ expect( editor ).to.have.property( 'elements', null );
|
|
|
+ expect( editor.config ).to.be.an.instanceof( EditorConfig );
|
|
|
+ expect( editor.editables ).to.be.an.instanceof( EditableCollection );
|
|
|
+ expect( editor.commands ).to.be.an.instanceof( Map );
|
|
|
+
|
|
|
+ expect( editor.plugins ).to.be.an.instanceof( PluginCollection );
|
|
|
+ expect( getPlugins( editor ) ).to.be.empty;
|
|
|
+ } );
|
|
|
} );
|
|
|
-} );
|
|
|
|
|
|
-describe( 'config', () => {
|
|
|
- it( 'should be an instance of EditorConfig', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
+ describe( 'config', () => {
|
|
|
+ it( 'should be an instance of EditorConfig', () => {
|
|
|
+ const editor = new Editor();
|
|
|
|
|
|
- expect( editor.config ).to.be.an.instanceof( EditorConfig );
|
|
|
+ expect( editor.config ).to.be.an.instanceof( EditorConfig );
|
|
|
+ } );
|
|
|
} );
|
|
|
-} );
|
|
|
|
|
|
-describe( 'locale', () => {
|
|
|
- it( 'is instantiated and t() is exposed', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
+ 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 );
|
|
|
+ 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( null, { lang: 'pl' } );
|
|
|
+
|
|
|
+ expect( editor.locale.lang ).to.equal( 'pl' );
|
|
|
+ } );
|
|
|
} );
|
|
|
|
|
|
- it( 'is configured with the config.lang', () => {
|
|
|
- const editor = new Editor( element, { lang: 'pl' } );
|
|
|
+ describe( 'plugins', () => {
|
|
|
+ it( 'should be empty on new editor', () => {
|
|
|
+ const editor = new Editor();
|
|
|
|
|
|
- expect( editor.locale.lang ).to.equal( 'pl' );
|
|
|
+ expect( getPlugins( editor ) ).to.be.empty;
|
|
|
+ } );
|
|
|
} );
|
|
|
-} );
|
|
|
|
|
|
-describe( 'init', () => {
|
|
|
- it( 'should return a promise that resolves properly', () => {
|
|
|
- const editor = new Editor( element, {
|
|
|
- creator: 'creator-test'
|
|
|
+ describe( 'init', () => {
|
|
|
+ it( 'should return a promise that resolves properly', () => {
|
|
|
+ const editor = new Editor( null, {
|
|
|
+ creator: 'creator-test'
|
|
|
+ } );
|
|
|
+
|
|
|
+ let promise = editor.init();
|
|
|
+
|
|
|
+ expect( promise ).to.be.an.instanceof( Promise );
|
|
|
+
|
|
|
+ return promise;
|
|
|
} );
|
|
|
|
|
|
- let promise = editor.init();
|
|
|
+ it( 'should load features and creator', () => {
|
|
|
+ const editor = new Editor( null, {
|
|
|
+ features: [ 'A', 'B' ],
|
|
|
+ creator: 'creator-test'
|
|
|
+ } );
|
|
|
|
|
|
- expect( promise ).to.be.an.instanceof( Promise );
|
|
|
+ expect( getPlugins( editor ) ).to.be.empty;
|
|
|
|
|
|
- return promise;
|
|
|
- } );
|
|
|
+ return editor.init().then( () => {
|
|
|
+ expect( getPlugins( editor ).length ).to.equal( 3 );
|
|
|
|
|
|
- it( 'should load features and creator', () => {
|
|
|
- const editor = new Editor( element, {
|
|
|
- features: [ 'A', 'B' ],
|
|
|
- creator: 'creator-test'
|
|
|
+ expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ expect( editor.plugins.get( 'creator-test' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ } );
|
|
|
} );
|
|
|
|
|
|
- expect( getPlugins( editor ) ).to.be.empty;
|
|
|
+ it( 'should load features passed as a string', () => {
|
|
|
+ const editor = new Editor( null, {
|
|
|
+ features: 'A,B',
|
|
|
+ creator: 'creator-test'
|
|
|
+ } );
|
|
|
+
|
|
|
+ expect( getPlugins( editor ) ).to.be.empty;
|
|
|
|
|
|
- return editor.init().then( () => {
|
|
|
- expect( getPlugins( editor ).length ).to.equal( 3 );
|
|
|
+ return editor.init().then( () => {
|
|
|
+ expect( getPlugins( editor ).length ).to.equal( 3 );
|
|
|
|
|
|
- expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
|
|
|
- expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
|
|
|
- expect( editor.plugins.get( 'creator-test' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ } );
|
|
|
} );
|
|
|
- } );
|
|
|
|
|
|
- it( 'should load features passed as a string', () => {
|
|
|
- const editor = new Editor( element, {
|
|
|
- features: 'A,B',
|
|
|
- creator: 'creator-test'
|
|
|
+ it( 'should initialize plugins in the right order', () => {
|
|
|
+ const editor = new Editor( null, {
|
|
|
+ features: [ 'A', 'D' ],
|
|
|
+ creator: 'creator-test'
|
|
|
+ } );
|
|
|
+
|
|
|
+ return editor.init().then( () => {
|
|
|
+ sinon.assert.callOrder(
|
|
|
+ editor.plugins.get( 'creator-test' ).init,
|
|
|
+ editor.plugins.get( pluginClasses[ 'A/A' ] ).init,
|
|
|
+ editor.plugins.get( pluginClasses[ 'B/B' ] ).init,
|
|
|
+ editor.plugins.get( pluginClasses[ 'C/C' ] ).init,
|
|
|
+ editor.plugins.get( pluginClasses[ 'D/D' ] ).init
|
|
|
+ );
|
|
|
+ } );
|
|
|
} );
|
|
|
|
|
|
- expect( getPlugins( editor ) ).to.be.empty;
|
|
|
+ it( 'should initialize plugins in the right order, waiting for asynchronous ones', () => {
|
|
|
+ class PluginAsync extends Plugin {}
|
|
|
+ const asyncSpy = sinon.spy().named( 'async-call-spy' );
|
|
|
+
|
|
|
+ // Synchronous plugin that depends on an asynchronous one.
|
|
|
+ pluginDefinition( 'sync/sync', [ 'async/async' ] );
|
|
|
+
|
|
|
+ moduleUtils.define( 'async/async', () => {
|
|
|
+ PluginAsync.prototype.init = sinon.spy( () => {
|
|
|
+ return new Promise( ( resolve ) => {
|
|
|
+ setTimeout( () => {
|
|
|
+ asyncSpy();
|
|
|
+ resolve();
|
|
|
+ }, 0 );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ return PluginAsync;
|
|
|
+ } );
|
|
|
|
|
|
- return editor.init().then( () => {
|
|
|
- expect( getPlugins( editor ).length ).to.equal( 3 );
|
|
|
+ const editor = new Editor( null, {
|
|
|
+ features: [ 'A', 'sync' ],
|
|
|
+ creator: 'creator-test'
|
|
|
+ } );
|
|
|
|
|
|
- expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
|
|
|
- expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
|
|
|
+ return editor.init().then( () => {
|
|
|
+ sinon.assert.callOrder(
|
|
|
+ editor.plugins.get( 'creator-test' ).init,
|
|
|
+ editor.plugins.get( pluginClasses[ 'A/A' ] ).init,
|
|
|
+ editor.plugins.get( PluginAsync ).init,
|
|
|
+ // This one is called with delay by the async init.
|
|
|
+ asyncSpy,
|
|
|
+ editor.plugins.get( pluginClasses[ 'sync/sync' ] ).init
|
|
|
+ );
|
|
|
+ } );
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
- it( 'should initialize plugins in the right order', () => {
|
|
|
- const editor = new Editor( element, {
|
|
|
- features: [ 'A', 'D' ],
|
|
|
- creator: 'creator-test'
|
|
|
- } );
|
|
|
+ describe( 'destroy', () => {
|
|
|
+ it( 'should fire "destroy"', () => {
|
|
|
+ const editor = new Editor();
|
|
|
+ let spy = sinon.spy();
|
|
|
+
|
|
|
+ editor.on( 'destroy', spy );
|
|
|
|
|
|
- return editor.init().then( () => {
|
|
|
- sinon.assert.callOrder(
|
|
|
- editor.plugins.get( 'creator-test' ).init,
|
|
|
- editor.plugins.get( pluginClasses[ 'A/A' ] ).init,
|
|
|
- editor.plugins.get( pluginClasses[ 'B/B' ] ).init,
|
|
|
- editor.plugins.get( pluginClasses[ 'C/C' ] ).init,
|
|
|
- editor.plugins.get( pluginClasses[ 'D/D' ] ).init
|
|
|
- );
|
|
|
+ return editor.destroy().then( () => {
|
|
|
+ expect( spy.calledOnce ).to.be.true;
|
|
|
+ } );
|
|
|
} );
|
|
|
+
|
|
|
+ // Note: Tests for destroying creators are in creator/creator.js.
|
|
|
+ // When destroying creator will be generalized to destroying plugins,
|
|
|
+ // move that code here.
|
|
|
} );
|
|
|
|
|
|
- it( 'should initialize plugins in the right order, waiting for asynchronous ones', () => {
|
|
|
- class PluginAsync extends Plugin {}
|
|
|
- const asyncSpy = sinon.spy().named( 'async-call-spy' );
|
|
|
+ describe( 'execute', () => {
|
|
|
+ it( 'should execute specified command', () => {
|
|
|
+ const editor = new Editor();
|
|
|
|
|
|
- // Synchronous plugin that depends on an asynchronous one.
|
|
|
- pluginDefinition( 'sync/sync', [ 'async/async' ] );
|
|
|
+ let command = new Command( editor );
|
|
|
+ sinon.spy( command, '_execute' );
|
|
|
|
|
|
- moduleUtils.define( 'async/async', () => {
|
|
|
- PluginAsync.prototype.init = sinon.spy( () => {
|
|
|
- return new Promise( ( resolve ) => {
|
|
|
- setTimeout( () => {
|
|
|
- asyncSpy();
|
|
|
- resolve();
|
|
|
- }, 0 );
|
|
|
- } );
|
|
|
- } );
|
|
|
+ editor.commands.set( 'commandName', command );
|
|
|
+ editor.execute( 'commandName' );
|
|
|
|
|
|
- return PluginAsync;
|
|
|
+ expect( command._execute.calledOnce ).to.be.true;
|
|
|
} );
|
|
|
|
|
|
- const editor = new Editor( element, {
|
|
|
- features: [ 'A', 'sync' ],
|
|
|
- creator: 'creator-test'
|
|
|
- } );
|
|
|
+ it( 'should throw an error if specified command has not been added', () => {
|
|
|
+ const editor = new Editor();
|
|
|
|
|
|
- return editor.init().then( () => {
|
|
|
- sinon.assert.callOrder(
|
|
|
- editor.plugins.get( 'creator-test' ).init,
|
|
|
- editor.plugins.get( pluginClasses[ 'A/A' ] ).init,
|
|
|
- editor.plugins.get( PluginAsync ).init,
|
|
|
- // This one is called with delay by the async init.
|
|
|
- asyncSpy,
|
|
|
- editor.plugins.get( pluginClasses[ 'sync/sync' ] ).init
|
|
|
- );
|
|
|
+ expect( () => {
|
|
|
+ editor.execute( 'command' );
|
|
|
+ } ).to.throw( CKEditorError, /^editor-command-not-found:/ );
|
|
|
} );
|
|
|
} );
|
|
|
-} );
|
|
|
|
|
|
-describe( 'plugins', () => {
|
|
|
- it( 'should be empty on new editor', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
+ describe( 'setData', () => {
|
|
|
+ let editor;
|
|
|
|
|
|
- expect( getPlugins( editor ) ).to.be.empty;
|
|
|
- } );
|
|
|
-} );
|
|
|
+ beforeEach( () => {
|
|
|
+ editor = new Editor();
|
|
|
|
|
|
-describe( 'destroy', () => {
|
|
|
- it( 'should fire "destroy"', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
- let spy = sinon.spy();
|
|
|
+ editor.document = new Document();
|
|
|
+ editor.data = {
|
|
|
+ set: sinon.spy()
|
|
|
+ };
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should set data of the first root', () => {
|
|
|
+ editor.document.createRoot( 'firstRoot', 'div' );
|
|
|
|
|
|
- editor.on( 'destroy', spy );
|
|
|
+ editor.setData( 'foo' );
|
|
|
|
|
|
- return editor.destroy().then( () => {
|
|
|
- sinon.assert.called( spy );
|
|
|
+ expect( editor.data.set.calledOnce ).to.be.true;
|
|
|
+ expect( editor.data.set.calledWithExactly( 'firstRoot', 'foo' ) ).to.be.true;
|
|
|
} );
|
|
|
- } );
|
|
|
|
|
|
- it( 'should delete the "element" property', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
+ it( 'should set data of the specified root', () => {
|
|
|
+ editor.setData( 'foo', 'someRoot' );
|
|
|
|
|
|
- return editor.destroy().then( () => {
|
|
|
- expect( editor ).to.not.have.property( 'element' );
|
|
|
+ expect( editor.data.set.calledOnce ).to.be.true;
|
|
|
+ expect( editor.data.set.calledWithExactly( 'someRoot', 'foo' ) ).to.be.true;
|
|
|
} );
|
|
|
- } );
|
|
|
-} );
|
|
|
|
|
|
-describe( 'execute', () => {
|
|
|
- it( 'should execute specified command', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
-
|
|
|
- let command = new Command( editor );
|
|
|
- sinon.spy( command, '_execute' );
|
|
|
+ it( 'should throw when no roots', () => {
|
|
|
+ expect( () => {
|
|
|
+ editor.setData( 'foo' );
|
|
|
+ } ).to.throw( CKEditorError, /^editor-no-root-editables:/ );
|
|
|
+ } );
|
|
|
|
|
|
- editor.commands.set( 'command_name', command );
|
|
|
- editor.execute( 'command_name' );
|
|
|
+ it( 'should throw when more than one root and no root name given', () => {
|
|
|
+ editor.document.createRoot( 'firstRoot', 'div' );
|
|
|
+ editor.document.createRoot( 'secondRoot', 'div' );
|
|
|
|
|
|
- expect( command._execute.calledOnce ).to.be.true;
|
|
|
+ expect( () => {
|
|
|
+ editor.setData( 'foo' );
|
|
|
+ } ).to.throw( CKEditorError, /^editor-root-editable-name-missing:/ );
|
|
|
+ } );
|
|
|
} );
|
|
|
|
|
|
- it( 'should throw an error if specified command has not been added', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
+ describe( 'getData', () => {
|
|
|
+ let editor;
|
|
|
|
|
|
- expect( () => {
|
|
|
- editor.execute( 'command' );
|
|
|
- } ).to.throw( CKEditorError, /editor-command-not-found/ );
|
|
|
- } );
|
|
|
-} );
|
|
|
+ beforeEach( () => {
|
|
|
+ editor = new Editor();
|
|
|
|
|
|
-describe( 'setData', () => {
|
|
|
- it( 'should set data on the editable', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
- editor.editable = {
|
|
|
- setData: sinon.spy()
|
|
|
- };
|
|
|
+ editor.document = new Document();
|
|
|
+ editor.data = {
|
|
|
+ get( rootName ) {
|
|
|
+ return `data for ${ rootName }`;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ } );
|
|
|
|
|
|
- editor.setData( 'foo' );
|
|
|
+ it( 'should get data from the first root', () => {
|
|
|
+ editor.document.createRoot( 'firstRoot', 'div' );
|
|
|
|
|
|
- expect( editor.editable.setData.calledOnce ).to.be.true;
|
|
|
- expect( editor.editable.setData.args[ 0 ][ 0 ] ).to.equal( 'foo' );
|
|
|
- } );
|
|
|
+ expect( editor.getData() ).to.equal( 'data for firstRoot' );
|
|
|
+ } );
|
|
|
|
|
|
- it( 'should get data from the editable', () => {
|
|
|
- const editor = new Editor( element );
|
|
|
- editor.editable = {
|
|
|
- getData() {
|
|
|
- return 'bar';
|
|
|
- }
|
|
|
- };
|
|
|
+ it( 'should get data from the specified root', () => {
|
|
|
+ expect( editor.getData( 'someRoot' ) ).to.equal( 'data for someRoot' );
|
|
|
+ } );
|
|
|
|
|
|
- expect( editor.getData() ).to.equal( 'bar' );
|
|
|
+ it( 'should throw when no roots', () => {
|
|
|
+ expect( () => {
|
|
|
+ editor.getData();
|
|
|
+ } ).to.throw( CKEditorError, /^editor-no-root-editables:/ );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should throw when more than one root and no root name given', () => {
|
|
|
+ editor.document.createRoot( 'firstRoot', 'div' );
|
|
|
+ editor.document.createRoot( 'secondRoot', 'div' );
|
|
|
+
|
|
|
+ expect( () => {
|
|
|
+ editor.getData();
|
|
|
+ } ).to.throw( CKEditorError, /^editor-root-editable-name-missing:/ );
|
|
|
+ } );
|
|
|
} );
|
|
|
} );
|
|
|
|