浏览代码

Moar tests.

Piotrek Koszuliński 9 年之前
父节点
当前提交
478379ebd0

+ 9 - 1
src/editable.js

@@ -10,6 +10,9 @@ import ObservableMixin from './utils/observablemixin.js';
 import FocusObserver from './core/treeview/observer/focusobserver.js';
 
 /**
+ * Class representing a single editable element. It combines the {@link ckeditor5.Editable#viewElement editable view}
+ * with the {@link ckeditor5.Editable#domElement editable DOM element} to which the view is rendering.
+ *
  * @memberOf ckeditor5
  * @mixes utils.ObservaleMixin
  */
@@ -70,7 +73,9 @@ export default class Editable {
 	}
 
 	/**
-	 * @param {HTMLElement} domElement
+	 * Binds the {@link ckeditor5.Editable#viewElement editable's view} to a concrete DOM element.
+	 *
+	 * @param {HTMLElement} domElement The DOM element which holds the editable.
 	 */
 	bindTo( domElement ) {
 		const editingView = this.editor.editing.view;
@@ -95,6 +100,9 @@ export default class Editable {
 		} );
 	}
 
+	/**
+	 * Destroys the editable.
+	 */
 	destroy() {
 		this.stopListening();
 		this.domElement = this.viewElement = null;

+ 10 - 0
src/editablecollection.js

@@ -10,11 +10,16 @@ import Collection from './utils/collection.js';
 import ObservableMixin from './utils/observablemixin.js';
 
 /**
+ * A collection of {@link ckeditor5.Editable editables}.
+ *
  * @memberOf ckeditor5
  * @mixes utils.ObservaleMixin
  * @extends utils.Collection
  */
 export default class EditableCollection extends Collection {
+	/**
+	 * Creates a new instance of Editabe.
+	 */
 	constructor() {
 		super( { idProperty: 'name' } );
 
@@ -37,12 +42,17 @@ export default class EditableCollection extends Collection {
 		} );
 	}
 
+	/**
+	 * Destorys the collection.
+	 */
 	destroy() {
 		this.stopListening();
 
 		for ( let editable of this ) {
 			editable.destroy();
 		}
+
+		this.clear();
 	}
 }
 

+ 1 - 1
tests/ckeditor.js

@@ -207,7 +207,7 @@ describe( 'create', () => {
 		} );
 
 		function assertElements( editor, expectedSize ) {
-			expect( editor.elements ).to.be.instanceOf( Map );
+			expect( editor.elements ).to.be.instanceof( Map );
 			expect( editor.elements ).to.have.property( 'size', expectedSize );
 		}
 	} );

+ 2 - 2
tests/creator/basecreator.js

@@ -24,13 +24,13 @@ describe( 'BaseCreator', () => {
 
 	describe( 'constructor', () => {
 		it( 'inherits from the Plugin', () => {
-			expect( creator ).to.be.instanceOf( Plugin );
+			expect( creator ).to.be.instanceof( Plugin );
 		} );
 	} );
 
 	describe( 'create', () => {
 		it( 'returns a promise', () => {
-			expect( creator.create() ).to.be.instanceOf( Promise );
+			expect( creator.create() ).to.be.instanceof( Promise );
 		} );
 	} );
 } );

+ 6 - 6
tests/creator/creator.js

@@ -34,20 +34,20 @@ describe( 'Creator', () => {
 
 	describe( 'constructor', () => {
 		it( 'inherits from the BaseCreator', () => {
-			expect( creator ).to.be.instanceOf( BaseCreator );
+			expect( creator ).to.be.instanceof( BaseCreator );
 		} );
 
 		it( 'creates the engine', () => {
-			expect( editor.document ).to.be.instanceOf( Document );
-			expect( editor.editing ).to.be.instanceOf( EditingController );
-			expect( editor.data ).to.be.instanceOf( DataController );
-			expect( editor.data.processor ).to.be.instanceOf( HtmlDataProcessor );
+			expect( editor.document ).to.be.instanceof( Document );
+			expect( editor.editing ).to.be.instanceof( EditingController );
+			expect( editor.data ).to.be.instanceof( DataController );
+			expect( editor.data.processor ).to.be.instanceof( HtmlDataProcessor );
 		} );
 	} );
 
 	describe( 'create', () => {
 		it( 'returns a promise', () => {
-			expect( creator.create() ).to.be.instanceOf( Promise );
+			expect( creator.create() ).to.be.instanceof( Promise );
 		} );
 	} );
 

+ 126 - 0
tests/editable.js

@@ -0,0 +1,126 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Editor from '/ckeditor5/editor.js';
+import Editable from '/ckeditor5/editable.js';
+import EditingController from '/ckeditor5/core/treecontroller/editingcontroller.js';
+import ViewElement from '/ckeditor5/core/treeview/element.js';
+
+describe( 'Editable', () => {
+	const ELEMENT_NAME = 'h1';
+	const EDITABLE_NAME = 'editableNaNaNaNa';
+
+	let editable, editor;
+
+	beforeEach( () => {
+		editor = new Editor();
+		editable = new Editable( editor, EDITABLE_NAME );
+	} );
+
+	describe( 'constructor', () => {
+		it( 'sets the properties', () => {
+			expect( editable ).to.have.property( 'editor', editor );
+			expect( editable ).to.have.property( 'name', EDITABLE_NAME );
+			expect( editable ).to.have.property( 'isEditable', true );
+			expect( editable ).to.have.property( 'isFocused', false );
+		} );
+	} );
+
+	describe( 'isEditable', () => {
+		it( 'is observable', () => {
+			const spy = sinon.spy();
+
+			editable.on( 'change:isEditable', spy );
+
+			editable.isEditable = false;
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+	} );
+
+	describe( 'isFocused', () => {
+		it( 'is observable', () => {
+			const spy = sinon.spy();
+
+			editable.on( 'change:isFocused', spy );
+
+			editable.isFocused = true;
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+	} );
+
+	describe( 'bindTo', () => {
+		let domElement, editingView;
+
+		beforeEach( () => {
+			domElement = document.createElement( ELEMENT_NAME );
+
+			editor.editing = new EditingController();
+			editingView = editor.editing.view;
+
+			editable.bindTo( domElement );
+		} );
+
+		it( 'creates view root element', () => {
+			expect( editable.viewElement ).to.be.instanceof( ViewElement );
+			expect( editable.viewElement ).to.have.property( 'name', ELEMENT_NAME );
+
+			expect( editingView.viewRoots.get( EDITABLE_NAME ) ).to.equal( editable.viewElement );
+		} );
+
+		describe( 'isFocused binding', () => {
+			it( 'reacts on editingView#focus', () => {
+				editingView.fire( 'focus', {
+					target: editable.viewElement
+				} );
+
+				expect( editable ).to.have.property( 'isFocused', true );
+			} );
+
+			it( 'reacts on editingView#blur', () => {
+				editable.isFocused = true;
+
+				editingView.fire( 'blur', {
+					target: editable.viewElement
+				} );
+
+				expect( editable ).to.have.property( 'isFocused', false );
+			} );
+
+			it( 'reacts on editingView#focus only if target matches', () => {
+				editingView.fire( 'focus', {
+					target: new ViewElement( 'foo' )
+				} );
+
+				expect( editable ).to.have.property( 'isFocused', false );
+			} );
+
+			it( 'reacts on editingView#blur only if target matches', () => {
+				editable.isFocused = true;
+
+				editingView.fire( 'blur', {
+					target: new ViewElement( 'foo' )
+				} );
+
+				expect( editable ).to.have.property( 'isFocused', true );
+			} );
+		} );
+	} );
+
+	describe( 'destroy', () => {
+		it( 'offs everything', () => {
+			const spy = sinon.spy( editable, 'stopListening' );
+
+			editable.destroy();
+
+			expect( spy.calledOnce ).to.be.true;
+			expect( editable.viewElement ).to.be.null;
+			expect( editable.domElement ).to.be.null;
+		} );
+	} );
+} );

+ 13 - 0
tests/editablecollection.js

@@ -0,0 +1,13 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+describe( 'EditableCollection', () => {
+	describe( 'constructor', () => {
+		it( 'does something', () => {
+		} );
+	} );
+} );

+ 131 - 163
tests/editor/creator.js

@@ -10,73 +10,57 @@
 import moduleUtils from '/tests/ckeditor5/_utils/module.js';
 import testUtils from '/tests/ckeditor5/_utils/utils.js';
 import Editor from '/ckeditor5/editor.js';
-import Creator from '/ckeditor5/creator.js';
+import Creator from '/ckeditor5/creator/creator.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 
-let editor, element;
+let editor;
 
 function initEditor( config ) {
-	element = document.createElement( 'div' );
-	document.body.appendChild( element );
-
-	editor = new Editor( element, config );
+	editor = new Editor( null, config );
 
 	return editor.init();
 }
 
 testUtils.createSinonSandbox();
 
-before( () => {
-	testUtils.defineEditorCreatorMock( 'test1', {
-		create: sinon.spy(),
-		destroy: sinon.spy()
-	} );
-
-	testUtils.defineEditorCreatorMock( 'test-config1', {
-		create: sinon.spy()
-	} );
-	testUtils.defineEditorCreatorMock( 'test-config2', {
-		create: sinon.spy()
-	} );
-
-	moduleUtils.define( 'test3/test3', [ 'plugin' ], ( Plugin ) => {
-		return class extends Plugin {};
-	} );
+testUtils.defineEditorCreatorMock( 'test1', {
+	create: sinon.spy(),
+	destroy: sinon.spy()
+} );
 
-	moduleUtils.define( 'test/creator-async-create', [ 'creator' ], ( Creator ) => {
-		return class extends Creator {
-			create() {
-				return new Promise( ( resolve, reject ) => {
-					reject( new Error( 'Catch me - create.' ) );
-				} );
-			}
+testUtils.defineEditorCreatorMock( 'test-config1', {
+	create: sinon.spy()
+} );
+testUtils.defineEditorCreatorMock( 'test-config2', {
+	create: sinon.spy()
+} );
 
-			destroy() {}
-		};
-	} );
+moduleUtils.define( 'test3/test3', [ 'plugin' ], ( Plugin ) => {
+	return class extends Plugin {};
+} );
 
-	moduleUtils.define( 'test/creator-async-destroy', [ 'creator' ], ( Creator ) => {
-		return class extends Creator {
-			create() {}
+moduleUtils.define( 'test/creator-async-create', [ 'creator/creator' ], ( Creator ) => {
+	return class extends Creator {
+		create() {
+			return new Promise( ( resolve, reject ) => {
+				reject( new Error( 'Catch me - create.' ) );
+			} );
+		}
 
-			destroy() {
-				return new Promise( ( resolve, reject ) => {
-					reject( new Error( 'Catch me - destroy.' ) );
-				} );
-			}
-		};
-	} );
+		destroy() {}
+	};
+} );
 
-	moduleUtils.define( 'test/creator-destroy-order', [ 'creator' ], ( Creator ) => {
-		return class extends Creator {
-			create() {}
+moduleUtils.define( 'test/creator-async-destroy', [ 'creator/creator' ], ( Creator ) => {
+	return class extends Creator {
+		create() {}
 
-			destroy() {
-				editor._elementInsideCreatorDestroy = this.editor.element;
-				editor._destroyOrder.push( 'creator' );
-			}
-		};
-	} );
+		destroy() {
+			return new Promise( ( resolve, reject ) => {
+				reject( new Error( 'Catch me - destroy.' ) );
+			} );
+		}
+	};
 } );
 
 afterEach( () => {
@@ -85,124 +69,108 @@ afterEach( () => {
 
 ///////////////////
 
-describe( 'init', () => {
-	it( 'should instantiate the creator and call create()', () => {
-		return initEditor( {
-				creator: 'creator-test1'
-			} )
-			.then( () => {
-				let creator = editor.plugins.get( 'creator-test1' );
-
-				expect( creator ).to.be.instanceof( Creator );
+describe( 'Editor creator', () => {
+	describe( 'init', () => {
+		it( 'should instantiate the creator and call create()', () => {
+			return initEditor( {
+					creator: 'creator-test1'
+				} )
+				.then( () => {
+					let creator = editor.plugins.get( 'creator-test1' );
 
-				// The create method has been called.
-				sinon.assert.calledOnce( creator.create );
-			} );
-	} );
+					expect( creator ).to.be.instanceof( Creator );
 
-	it( 'should throw if creator is not defined', () => {
-		return initEditor( {} )
-			.then( () => {
-				throw new Error( 'This should not be executed.' );
-			} )
-			.catch( ( err ) => {
-				expect( err ).to.be.instanceof( CKEditorError );
-				expect( err.message ).to.match( /^editor-undefined-creator:/ );
-			} );
-	} );
-
-	it( 'should use the creator specified in config.creator', () => {
-		return initEditor( {
-				creator: 'creator-test-config2',
-				features: [ 'creator-test-config1', 'creator-test-config2' ],
-			} )
-			.then( () => {
-				let creator1 = editor.plugins.get( 'creator-test-config1' );
-				let creator2 = editor.plugins.get( 'creator-test-config2' );
-
-				sinon.assert.calledOnce( creator2.create );
-				sinon.assert.notCalled( creator1.create );
-			} );
-	} );
-
-	it( 'should throw an error if the creator doesn\'t exist', () => {
-		return initEditor( {
-				creator: 'bad'
-			} )
-			.then( () => {
-				throw new Error( 'This should not be executed.' );
-			} )
-			.catch( ( err ) => {
-				// It's the Require.JS error.
-				expect( err ).to.be.an.instanceof( Error );
-				expect( err.message ).to.match( /^Script error for/ );
-			} );
-	} );
-
-	it( 'should chain the promise from the creator (enables async creators)', () => {
-		return initEditor( {
-				creator: 'test/creator-async-create'
-			} )
-			.then( () => {
-				throw new Error( 'This should not be executed.' );
-			} )
-			.catch( ( err ) => {
-				// Unfortunately fake timers don't work with promises, so throwing in the creator's create()
-				// seems to be the only way to test that the promise chain isn't broken.
-				expect( err ).to.have.property( 'message', 'Catch me - create.' );
-			} );
+					// The create method has been called.
+					sinon.assert.calledOnce( creator.create );
+				} );
+		} );
+
+		it( 'should throw if creator is not defined', () => {
+			return initEditor( {} )
+				.then( () => {
+					throw new Error( 'This should not be executed.' );
+				} )
+				.catch( ( err ) => {
+					expect( err ).to.be.instanceof( CKEditorError );
+					expect( err.message ).to.match( /^editor-undefined-creator:/ );
+				} );
+		} );
+
+		it( 'should use the creator specified in config.creator', () => {
+			return initEditor( {
+					creator: 'creator-test-config2',
+					features: [ 'creator-test-config1', 'creator-test-config2' ],
+				} )
+				.then( () => {
+					let creator1 = editor.plugins.get( 'creator-test-config1' );
+					let creator2 = editor.plugins.get( 'creator-test-config2' );
+
+					sinon.assert.calledOnce( creator2.create );
+					sinon.assert.notCalled( creator1.create );
+				} );
+		} );
+
+		it( 'should throw an error if the creator doesn\'t exist', () => {
+			return initEditor( {
+					creator: 'bad'
+				} )
+				.then( () => {
+					throw new Error( 'This should not be executed.' );
+				} )
+				.catch( ( err ) => {
+					// It's the Require.JS error.
+					expect( err ).to.be.an.instanceof( Error );
+					expect( err.message ).to.match( /^Script error for/ );
+				} );
+		} );
+
+		it( 'should chain the promise from the creator (enables async creators)', () => {
+			return initEditor( {
+					creator: 'test/creator-async-create'
+				} )
+				.then( () => {
+					throw new Error( 'This should not be executed.' );
+				} )
+				.catch( ( err ) => {
+					// Unfortunately fake timers don't work with promises, so throwing in the creator's create()
+					// seems to be the only way to test that the promise chain isn't broken.
+					expect( err ).to.have.property( 'message', 'Catch me - create.' );
+				} );
+		} );
 	} );
-} );
-
-describe( 'destroy', () => {
-	it( 'should call "destroy" on the creator', () => {
-		let creator1;
 
-		return initEditor( {
-				creator: 'creator-test1'
-			} )
-			.then( () => {
-				creator1 = editor.plugins.get( 'creator-test1' );
+	describe( 'destroy', () => {
+		it( 'should call "destroy" on the creator', () => {
+			let creator1;
 
-				return editor.destroy();
-			} )
-			.then( () => {
-				sinon.assert.calledOnce( creator1.destroy );
-			} );
-	} );
+			return initEditor( {
+					creator: 'creator-test1'
+				} )
+				.then( () => {
+					creator1 = editor.plugins.get( 'creator-test1' );
 
-	it( 'should chain the promise from the creator (enables async creators)', () => {
-		return initEditor( {
-				creator: 'test/creator-async-destroy'
-			} )
-			.then( () => {
-				return editor.destroy();
-			} )
-			.then( () => {
-				throw new Error( 'This should not be executed.' );
-			} )
-			.catch( ( err ) => {
-				// Unfortunately fake timers don't work with promises, so throwing in the creator's destroy()
-				// seems to be the only way to test that the promise chain isn't broken.
-				expect( err ).to.have.property( 'message', 'Catch me - destroy.' );
-			} );
-	} );
-
-	it( 'should do things in the correct order', () => {
-		return initEditor( {
-				creator: 'test/creator-destroy-order'
-			} )
-			.then( () => {
-				editor._destroyOrder = [];
-				editor.on( 'destroy', () => {
-					editor._destroyOrder.push( 'event' );
+					return editor.destroy();
+				} )
+				.then( () => {
+					sinon.assert.calledOnce( creator1.destroy );
 				} );
-
-				return editor.destroy();
-			} )
-			.then( () => {
-				expect( editor._elementInsideCreatorDestroy ).to.not.be.undefined;
-				expect( editor._destroyOrder ).to.deep.equal( [ 'event', 'creator' ] );
-			} );
+		} );
+
+		it( 'should chain the promise from the creator (enables async creators)', () => {
+			return initEditor( {
+					creator: 'test/creator-async-destroy'
+				} )
+				.then( () => {
+					return editor.destroy();
+				} )
+				.then( () => {
+					throw new Error( 'This should not be executed.' );
+				} )
+				.catch( ( err ) => {
+					// Unfortunately fake timers don't work with promises, so throwing in the creator's destroy()
+					// seems to be the only way to test that the promise chain isn't broken.
+					expect( err ).to.have.property( 'message', 'Catch me - destroy.' );
+				} );
+		} );
 	} );
 } );

+ 209 - 149
tests/editor/editor.js

@@ -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:/ );
+		} );
 	} );
 } );
 

+ 1 - 1
tests/plugincollection.js

@@ -10,7 +10,7 @@ import testUtils from '/tests/ckeditor5/_utils/utils.js';
 import Editor from '/ckeditor5/editor.js';
 import PluginCollection from '/ckeditor5/plugincollection.js';
 import Plugin from '/ckeditor5/plugin.js';
-import Creator from '/ckeditor5/creator.js';
+import Creator from '/ckeditor5/creator/creator.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 import log from '/ckeditor5/utils/log.js';