8
0
فهرست منبع

Merge branch 't/209'. Closes #209.

Aleksander Nowodzinski 9 سال پیش
والد
کامیت
1c99897ff3
36فایلهای تغییر یافته به همراه911 افزوده شده و 61 حذف شده
  1. 19 0
      packages/ckeditor5-engine/src/editable/editable.js
  2. 2 0
      packages/ckeditor5-engine/src/editable/editableview.js
  3. 0 32
      packages/ckeditor5-engine/src/editorui.js
  4. 47 0
      packages/ckeditor5-engine/src/editorui/editorui.js
  5. 55 0
      packages/ckeditor5-engine/src/editorui/editoruiview.js
  6. 54 0
      packages/ckeditor5-engine/src/ui/button/button.js
  7. 61 0
      packages/ckeditor5-engine/src/ui/button/buttonview.js
  8. 82 0
      packages/ckeditor5-engine/src/ui/componentfactory.js
  9. 1 1
      packages/ckeditor5-engine/src/ui/region.js
  10. 24 0
      packages/ckeditor5-engine/src/ui/toolbar/toolbar.js
  11. 30 0
      packages/ckeditor5-engine/src/ui/toolbar/toolbarview.js
  12. 4 9
      packages/ckeditor5-engine/src/ui/view.js
  13. 2 1
      packages/ckeditor5-engine/tests/_utils/ui/boxededitorui/boxededitorui.js
  14. 24 6
      packages/ckeditor5-engine/tests/_utils/ui/boxededitorui/boxededitoruiview.js
  15. 3 2
      packages/ckeditor5-engine/tests/_utils/ui/boxlesseditorui/boxlesseditorui.js
  16. 16 0
      packages/ckeditor5-engine/tests/_utils/ui/floatingtoolbar/floatingtoolbar.js
  17. 27 0
      packages/ckeditor5-engine/tests/_utils/ui/floatingtoolbar/floatingtoolbarview.js
  18. 55 0
      packages/ckeditor5-engine/tests/creator/manual/_assets/styles.css
  19. 17 0
      packages/ckeditor5-engine/tests/creator/manual/_utils/creator/classiccreator.js
  20. 25 2
      packages/ckeditor5-engine/tests/creator/manual/_utils/creator/inlinecreator.js
  21. 55 0
      packages/ckeditor5-engine/tests/creator/manual/_utils/imitatefeatures.js
  22. 4 0
      packages/ckeditor5-engine/tests/creator/manual/creator-classic.html
  23. 2 1
      packages/ckeditor5-engine/tests/creator/manual/creator-classic.js
  24. 4 0
      packages/ckeditor5-engine/tests/creator/manual/creator-classic.md
  25. 4 0
      packages/ckeditor5-engine/tests/creator/manual/creator-inline.html
  26. 2 1
      packages/ckeditor5-engine/tests/creator/manual/creator-inline.js
  27. 8 1
      packages/ckeditor5-engine/tests/creator/manual/creator-inline.md
  28. 7 1
      packages/ckeditor5-engine/tests/editorui/editorui.js
  29. 38 0
      packages/ckeditor5-engine/tests/editorui/editoruiview.js
  30. 34 0
      packages/ckeditor5-engine/tests/ui/button/button.js
  31. 84 0
      packages/ckeditor5-engine/tests/ui/button/buttonview.js
  32. 59 0
      packages/ckeditor5-engine/tests/ui/componentfactory.js
  33. 2 0
      packages/ckeditor5-engine/tests/ui/model.js
  34. 25 0
      packages/ckeditor5-engine/tests/ui/toolbar/toolbar.js
  35. 34 0
      packages/ckeditor5-engine/tests/ui/toolbar/toolbarview.js
  36. 1 4
      packages/ckeditor5-engine/tests/ui/view.js

+ 19 - 0
packages/ckeditor5-engine/src/editable/editable.js

@@ -84,3 +84,22 @@ export default class Editable extends Controller {
 }
 
 utils.mix( Editable, ObservableMixin );
+
+/**
+ * The editable model interface.
+ *
+ * @memberOf core.editable
+ * @interface EditableModel
+ */
+
+/**
+ * Whether the editable has focus.
+ *
+ * @member {Boolean} core.editable.EditableModel#isFocused
+ */
+
+/**
+ * Whether the editable is not in read-only mode.
+ *
+ * @member {Boolean} core.editable.EditableModel#isEditable
+ */

+ 2 - 0
packages/ckeditor5-engine/src/editable/editableview.js

@@ -21,6 +21,8 @@ export default class EditableView extends View {
 	 */
 
 	/**
+	 * Sets the {@link #editableElement} property and applies necessary bindings to it.
+	 *
 	 * @param {HTMLElement} editableElement
 	 */
 	setEditableElement( editableElement ) {

+ 0 - 32
packages/ckeditor5-engine/src/editorui.js

@@ -1,32 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-import Controller from './ui/controller.js';
-import utils from './utils.js';
-import ObservableMixin from './observablemixin.js';
-
-/**
- * Base class for the editor main view controllers.
- *
- * @memberOf core
- * @extends core.ui.Controller
- * @mixes core.ObservableMixin
- */
-
-export default class EditorUI extends Controller {
-	constructor( editor ) {
-		super();
-
-		/**
-		 * @readonly
-		 * @member {core.Editor} core.EditorUI.editor
-		 */
-		this.editor = editor;
-	}
-}
-
-utils.mix( EditorUI, ObservableMixin );

+ 47 - 0
packages/ckeditor5-engine/src/editorui/editorui.js

@@ -0,0 +1,47 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Controller from '../ui/controller.js';
+import ControllerCollection from '../ui/controllercollection.js';
+import ComponentFactory from '../ui/componentfactory.js';
+import ObservableMixin from '../observablemixin.js';
+import utils from '../utils.js';
+
+/**
+ * Base class for the editor main view controllers.
+ *
+ * @memberOf core.editorUI
+ * @extends core.ui.Controller
+ * @mixes core.ObservableMixin
+ */
+
+export default class EditorUI extends Controller {
+	/**
+	 * Creates an EditorUI instance.
+	 *
+	 * @param {core.Editor} editor
+	 */
+	constructor( editor ) {
+		super();
+
+		/**
+		 * @readonly
+		 * @member {core.Editor} core.editorUI.EditorUI#editor
+		 */
+		this.editor = editor;
+
+		/**
+		 * @readonly
+		 * @member {core.ui.ComponentFactory} core.editorUI.EditorUI#featureComponents
+		 */
+		this.featureComponents = new ComponentFactory( editor );
+
+		this.collections.add( new ControllerCollection( 'body' ) );
+	}
+}
+
+utils.mix( EditorUI, ObservableMixin );

+ 55 - 0
packages/ckeditor5-engine/src/editorui/editoruiview.js

@@ -0,0 +1,55 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import View from '../ui/view.js';
+
+/**
+ * Base class for the editor main views.
+ *
+ * @memberOf core.editorUI
+ * @extends core.ui.View
+ */
+
+export default class EditorUIView extends View {
+	constructor( model ) {
+		super( model );
+
+		this._createBodyRegion();
+
+		/**
+		 * The element holding elements of the 'body' region.
+		 *
+		 * @private
+		 * @member {HTMLElement} core.editorUI.EditorUIView#_bodyRegionContainer
+		 */
+	}
+
+	destroy() {
+		this._bodyRegionContainer.remove();
+		this._bodyRegionContainer = null;
+	}
+
+	/**
+	 * Creates and appends to `<body>` the 'body' region container.
+	 *
+	 * @private
+	 */
+	_createBodyRegion() {
+		const bodyElement = document.createElement( 'div' );
+		document.body.appendChild( bodyElement );
+
+		this.applyTemplateToElement( bodyElement, {
+			attributes: {
+				class: 'ck-body'
+			}
+		} );
+
+		this._bodyRegionContainer = bodyElement;
+
+		this.register( 'body', () => bodyElement );
+	}
+}

+ 54 - 0
packages/ckeditor5-engine/src/ui/button/button.js

@@ -0,0 +1,54 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Controller from '../controller.js';
+
+/**
+ * The basic button controller class.
+ *
+ * @memberOf core.ui.button
+ * @extends core.ui.Controller
+ */
+
+export default class Button extends Controller {
+	constructor( model, view ) {
+		super( model, view );
+
+		view.on( 'click', () => model.fire( 'execute' ) );
+	}
+}
+
+/**
+ * The basic button model interface.
+ *
+ * @memberOf core.ui.button
+ * @interface ButtonModel
+ */
+
+/**
+ * The label of the button.
+ *
+ * @member {String} core.ui.button.ButtonModel#label
+ */
+
+/**
+ * Whether the button is "on" (e.g. some feature which this button represents is currently enabled).
+ *
+ * @member {Boolean} core.ui.button.ButtonModel#isOn
+ */
+
+/**
+ * Whether the button is enabled (can be clicked).
+ *
+ * @member {Boolean} core.ui.button.ButtonModel#isEnabled
+ */
+
+/**
+ * Fired when the button action should be executed.
+ *
+ * @event core.ui.button.ButtonModel#execute
+ */

+ 61 - 0
packages/ckeditor5-engine/src/ui/button/buttonview.js

@@ -0,0 +1,61 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import View from '../view.js';
+
+/**
+ * The basic button view class.
+ *
+ * @memberOf core.ui.button
+ * @extends core.ui.View
+ */
+
+export default class ButtonView extends View {
+	constructor( model ) {
+		super( model );
+
+		const bind = this.attributeBinder;
+
+		this.template = {
+			tag: 'button',
+
+			attributes: {
+				class: [
+					'ck-button',
+					bind.to( 'isEnabled', value => value ? 'ck-enabled' : 'ck-disabled' ),
+					bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' )
+				]
+			},
+
+			children: [
+				{
+					text: bind.to( 'label' )
+				}
+			],
+
+			on: {
+				mousedown: ( evt ) => {
+					evt.preventDefault();
+				},
+
+				click: () => {
+					// We can't make the button disabled using the disabled attribute, because it won't be focusable.
+					// Though, shouldn't this condition be moved to the button controller?
+					if ( model.isEnabled ) {
+						this.fire( 'click' );
+					}
+				}
+			}
+		};
+	}
+}
+
+/**
+ * Fired when the button is being clicked. It won't be fired when the button is disabled.
+ *
+ * @event core.ui.button.ButtonView#click
+ */

+ 82 - 0
packages/ckeditor5-engine/src/ui/componentfactory.js

@@ -0,0 +1,82 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import CKEditorError from '../ckeditorerror.js';
+
+/**
+ * Class implementing the UI component factory.
+ *
+ * Factories of specific UI components can be registered under their unique names. Registered
+ * components can be later instantiated by providing the name of the component. The model is shared between all
+ * instances of that component and has to be provided upon registering its factory.
+ *
+ * The main use case for the component factory is the {@link core.editorUI.EditorUI#featureComponents} factory.
+ *
+ * @memberOf core.ui
+ */
+
+export default class ComponentFactory {
+	/**
+	 * Creates ComponentFactory instance.
+	 *
+	 * @constructor
+	 * @param {core.Editor} editor The editor instance.
+	 */
+	constructor( editor ) {
+		/**
+		 * @readonly
+		 * @member {core.Editor} core.ui.ComponentFactory#editor
+		 */
+		this.editor = editor;
+
+		/**
+		 * Registered component factories.
+		 *
+		 * @private
+		 * @member {Map} core.ui.ComponentFactory#_components
+		 */
+		this._components = new Map();
+	}
+
+	/**
+	 * Registers a component factory.
+	 *
+	 * @param {String} name The name of the component.
+	 * @param {Function} ControllerClass The component controller constructor.
+	 * @param {Function} ViewClass The component view constructor.
+	 * @param {core.ui.Model} model The model of the component.
+	 */
+	add( name, ControllerClass, ViewClass, model ) {
+		if ( this._components.get( name ) ) {
+			throw new CKEditorError(
+				'componentfactory-item-exists: The item already exists in the component factory.', { name }
+			);
+		}
+
+		this._components.set( name, {
+			ControllerClass,
+			ViewClass,
+			model
+		} );
+	}
+
+	/**
+	 * Creates a component instance.
+	 *
+	 * @param {String} name The name of the component.
+	 * @returns {core.ui.Controller} The instantiated component.
+	 */
+	create( name ) {
+		const component = this._components.get( name );
+
+		const model = component.model;
+		const view = new component.ViewClass( model );
+		const controller = new component.ControllerClass( this.editor, model, view );
+
+		return controller;
+	}
+}

+ 1 - 1
packages/ckeditor5-engine/src/ui/region.js

@@ -29,7 +29,7 @@ export default class Region {
 		/**
 		 * Views which belong to the region.
 		 *
-		 * @member {Collection} core.ui.Region#views
+		 * @member {core.Collection} core.ui.Region#views
 		 */
 		this.views = new Collection();
 

+ 24 - 0
packages/ckeditor5-engine/src/ui/toolbar/toolbar.js

@@ -0,0 +1,24 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Controller from '../controller.js';
+import ControllerCollection from '../controllercollection.js';
+
+/**
+ * The basic toolbar controller class.
+ *
+ * @memberOf core.ui.toolbar
+ * @extends core.ui.Controller
+ */
+
+export default class Toolbar extends Controller {
+	constructor( model, view ) {
+		super( model, view );
+
+		this.collections.add( new ControllerCollection( 'buttons' ) );
+	}
+}

+ 30 - 0
packages/ckeditor5-engine/src/ui/toolbar/toolbarview.js

@@ -0,0 +1,30 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import View from '../view.js';
+
+/**
+ * The basic toolbar view class.
+ *
+ * @memberOf core.ui.toolbar
+ * @extends core.ui.View
+ */
+
+export default class ToolbarView extends View {
+	constructor( model ) {
+		super( model );
+
+		this.template = {
+			tag: 'div',
+			attributes: {
+				class: [ 'ck-toolbar' ]
+			}
+		};
+
+		this.register( 'buttons', el => el );
+	}
+}

+ 4 - 9
packages/ckeditor5-engine/src/ui/view.js

@@ -13,8 +13,8 @@ import DOMEmitterMixin from './domemittermixin.js';
 import utils from '../utils.js';
 import isPlainObject from '../lib/lodash/isPlainObject.js';
 
-const bindToSymbol = Symbol( 'bind-to' );
-const bindIfSymbol = Symbol( 'bind-if' );
+const bindToSymbol = Symbol( 'bindTo' );
+const bindIfSymbol = Symbol( 'bindIf' );
 
 /**
  * Basic View class.
@@ -86,14 +86,9 @@ export default class View {
 			return this._element;
 		}
 
+		// No template means no element (a virtual view).
 		if ( !this.template ) {
-			/**
-			 * Attempting to access an element of a view, which has no `template`
-			 * property.
-			 *
-			 * @error ui-view-notemplate
-			 */
-			throw new CKEditorError( 'ui-view-notemplate' );
+			return null;
 		}
 
 		// Prepare pre–defined listeners.

+ 2 - 1
packages/ckeditor5-engine/tests/_utils/ui/boxededitorui/boxededitorui.js

@@ -5,13 +5,14 @@
 
 'use strict';
 
-import EditorUI from '/ckeditor5/core/editorui.js';
+import EditorUI from '/ckeditor5/core/editorui/editorui.js';
 import ControllerCollection from '/ckeditor5/core/ui/controllercollection.js';
 
 export default class BoxedEditorUI extends EditorUI {
 	constructor( editor ) {
 		super( editor );
 
+		this.collections.add( new ControllerCollection( 'top' ) );
 		this.collections.add( new ControllerCollection( 'main' ) );
 
 		const config = editor.config;

+ 24 - 6
packages/ckeditor5-engine/tests/_utils/ui/boxededitorui/boxededitoruiview.js

@@ -5,19 +5,37 @@
 
 'use strict';
 
-import View from '/ckeditor5/core/ui/view.js';
+import EditorUIView from '/ckeditor5/core/editorui/editoruiview.js';
 
-export default class BoxedEditorUIView extends View {
+export default class BoxedEditorUIView extends EditorUIView {
 	constructor( model ) {
 		super( model );
 
 		this.template = {
 			tag: 'div',
-			attrs: {
-				'class': [ 'ck-chrome' ]
-			}
+
+			attributes: {
+				class: 'ck-box'
+			},
+
+			children: [
+				{
+					tag: 'div',
+					attributes: {
+						class: 'ck-box-region ck-top'
+					}
+				},
+
+				{
+					tag: 'div',
+					attributes: {
+						class: 'ck-box-region ck-main'
+					}
+				}
+			]
 		};
 
-		this.register( 'main', el => el );
+		this.register( 'top', '.ck-top' );
+		this.register( 'main', '.ck-main' );
 	}
 }

+ 3 - 2
packages/ckeditor5-engine/tests/_utils/ui/boxlesseditorui/boxlesseditorui.js

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import EditorUI from '/ckeditor5/core/editorui.js';
+import EditorUI from '/ckeditor5/core/editorui/editorui.js';
 import ControllerCollection from '/ckeditor5/core/ui/controllercollection.js';
 
 export default class BoxlessEditorUI extends EditorUI {
@@ -16,7 +16,8 @@ export default class BoxlessEditorUI extends EditorUI {
 
 		/**
 		 * @private
-		 * @property {View} _view
+		 * @type {core.ui.View}
+		 * @property _view
 		 */
 	}
 

+ 16 - 0
packages/ckeditor5-engine/tests/_utils/ui/floatingtoolbar/floatingtoolbar.js

@@ -0,0 +1,16 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Toolbar from '/ckeditor5/ui/toolbar/toolbar.js';
+
+export default class FloatingToolbar extends Toolbar {
+	constructor( editor, model, view ) {
+		super( editor, model, view );
+
+		model.bind( 'isVisible' ).to( editor.editable, 'isFocused' );
+	}
+}

+ 27 - 0
packages/ckeditor5-engine/tests/_utils/ui/floatingtoolbar/floatingtoolbarview.js

@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import ToolbarView from '/ckeditor5/ui/toolbar/toolbarview.js';
+
+export default class FloatingToolbarView extends ToolbarView {
+	constructor( model ) {
+		super( model );
+
+		const bind = this.attributeBinder;
+
+		this.template.attributes.class.push(
+			bind.to( 'isVisible', value => value ? 'ck-visible' : 'ck-hidden' )
+		);
+
+		// This has a high risk of breaking if someone defines "on" in the parent template.
+		// See https://github.com/ckeditor/ckeditor5-core/issues/219
+		this.template.on = {
+			// Added just for fun, but needed to keep the focus in the editable.
+			mousedown: ( evt ) => evt.preventDefault()
+		};
+	}
+}

+ 55 - 0
packages/ckeditor5-engine/tests/creator/manual/_assets/styles.css

@@ -0,0 +1,55 @@
+.ck-body {
+	margin: 10px 0;
+	border: solid 3px red;
+}
+.ck-body::before {
+	content: '[[ ck-body region ]]';
+}
+
+.ck-box {
+	margin: 10px 0;
+}
+
+.ck-main {
+	border: solid 1px rgb( 200, 200, 200 );
+	padding: 5px
+}
+
+.ck-toolbar {
+	border: solid 1px rgb( 200, 200, 200 );
+	background: rgb( 240, 240, 240 );
+	padding: 5px;
+}
+
+.ck-toolbar.ck-hidden {
+	opacity: 0.3;
+}
+
+.ck-button {
+	border: solid 1px transparent;
+	background: transparent;
+	display: inline-block;
+	padding: 5px 10px;
+	border-radius: 1px;
+	margin-right: 5px;
+
+	font-size: 16px;
+	color: rgb( 69, 69, 69 );
+
+	cursor: pointer;
+}
+
+.ck-button:hover:not(.ck-disabled) {
+	border: solid 1px rgb( 180, 180, 180 );
+	box-shadow: 0 0 2px rgba( 0, 0, 0, 0.1 );
+}
+
+.ck-button.ck-on {
+	border: solid 1px rgb( 200, 200, 200 );
+	box-shadow: inset 0 0 3px rgba( 0, 0, 0, 0.1 );
+	background: rgba( 0, 0, 0, 0.05 );
+}
+
+.ck-button.ck-disabled {
+	color: rgb( 180, 180, 180 );
+}

+ 17 - 0
packages/ckeditor5-engine/tests/creator/manual/_utils/creator/classiccreator.js

@@ -10,6 +10,10 @@ import BoxedEditorUI from '/tests/core/_utils/ui/boxededitorui/boxededitorui.js'
 import BoxedEditorUIView from '/tests/core/_utils/ui/boxededitorui/boxededitoruiview.js';
 import FramedEditable from '/tests/core/_utils/ui/editable/framed/framededitable.js';
 import FramedEditableView from '/tests/core/_utils/ui/editable/framed/framededitableview.js';
+import Model from '/ckeditor5/core/ui/model.js';
+import Toolbar from '/ckeditor5/ui/toolbar/toolbar.js';
+import ToolbarView from '/ckeditor5/ui/toolbar/toolbarview.js';
+import { imitateFeatures, imitateDestroyFeatures } from '../imitatefeatures.js';
 
 export default class ClassicCreator extends Creator {
 	constructor( editor ) {
@@ -19,14 +23,19 @@ export default class ClassicCreator extends Creator {
 	}
 
 	create() {
+		imitateFeatures( this.editor );
+
 		this._replaceElement();
 		this._setupEditable();
+		this._setupToolbar();
 
 		return super.create()
 			.then( () => this.loadDataFromEditorElement() );
 	}
 
 	destroy() {
+		imitateDestroyFeatures();
+
 		this.updateEditorElement();
 
 		return super.destroy();
@@ -39,6 +48,14 @@ export default class ClassicCreator extends Creator {
 		this.editor.ui.add( 'main', editable );
 	}
 
+	_setupToolbar() {
+		const toolbar = new Toolbar( this.editor, new Model(), new ToolbarView() );
+
+		toolbar.addButtons( this.editor.config.toolbar );
+
+		this.editor.ui.add( 'top', toolbar );
+	}
+
 	_createEditable() {
 		const editable = new FramedEditable( this.editor );
 		const editableView = new FramedEditableView( editable.viewModel );

+ 25 - 2
packages/ckeditor5-engine/tests/creator/manual/_utils/creator/inlinecreator.js

@@ -6,10 +6,14 @@
 'use strict';
 
 import Creator from '/ckeditor5/core/creator.js';
-import View from '/ckeditor5/core/ui/view.js';
+import EditorUIView from '/ckeditor5/core/editorui/editoruiview.js';
 import BoxlessEditorUI from '/tests/core/_utils/ui/boxlesseditorui/boxlesseditorui.js';
 import InlineEditable from '/tests/core/_utils/ui/editable/inline/inlineeditable.js';
 import InlineEditableView from '/tests/core/_utils/ui/editable/inline/inlineeditableview.js';
+import Model from '/ckeditor5/core/ui/model.js';
+import FloatingToolbar from '/tests/core/_utils/ui/floatingtoolbar/floatingtoolbar.js';
+import FloatingToolbarView from '/tests/core/_utils/ui/floatingtoolbar/floatingtoolbarview.js';
+import { imitateFeatures, imitateDestroyFeatures } from '../imitatefeatures.js';
 
 export default class InlineCreator extends Creator {
 	constructor( editor ) {
@@ -19,13 +23,18 @@ export default class InlineCreator extends Creator {
 	}
 
 	create() {
+		imitateFeatures( this.editor );
+
 		this._setupEditable();
+		this._setupToolbar();
 
 		return super.create()
 			.then( () => this.loadDataFromEditorElement() );
 	}
 
 	destroy() {
+		imitateDestroyFeatures();
+
 		this.updateEditorElement();
 
 		return super.destroy();
@@ -37,6 +46,20 @@ export default class InlineCreator extends Creator {
 		this.editor.ui.add( 'editable', this.editor.editable );
 	}
 
+	_setupToolbar() {
+		const toolbar1Model = new Model();
+		const toolbar2Model = new Model();
+
+		const toolbar1 = new FloatingToolbar( this.editor, toolbar1Model, new FloatingToolbarView( toolbar1Model ) );
+		const toolbar2 = new FloatingToolbar( this.editor, toolbar2Model, new FloatingToolbarView( toolbar2Model ) );
+
+		toolbar1.addButtons( this.editor.config.toolbar );
+		toolbar2.addButtons( this.editor.config.toolbar.reverse() );
+
+		this.editor.ui.add( 'body', toolbar1 );
+		this.editor.ui.add( 'body', toolbar2 );
+	}
+
 	_createEditable() {
 		const editable = new InlineEditable( this.editor );
 		const editableView = new InlineEditableView( editable.viewModel, this.editor.element );
@@ -49,7 +72,7 @@ export default class InlineCreator extends Creator {
 	_createEditorUI() {
 		const editorUI = new BoxlessEditorUI( this.editor );
 
-		editorUI.view = new View( editorUI.viewModel );
+		editorUI.view = new EditorUIView( editorUI.viewModel );
 
 		return editorUI;
 	}

+ 55 - 0
packages/ckeditor5-engine/tests/creator/manual/_utils/imitatefeatures.js

@@ -0,0 +1,55 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Model from '/ckeditor5/core/ui/model.js';
+import Button from '/ckeditor5/ui/button/button.js';
+import ButtonView from '/ckeditor5/ui/button/buttonview.js';
+
+/**
+ * Immitates that some features were loaded and did their job.
+ *
+ * @param {core.Editor} editor
+ */
+export function imitateFeatures( editor ) {
+	const boldModel = new Model( {
+		isEnabled: true,
+		isOn: false,
+		label: 'bold'
+	} );
+
+	boldModel.on( 'execute', () => {
+		/* global console */
+		console.log( 'bold executed' );
+
+		boldModel.isOn = !boldModel.isOn;
+	} );
+
+	editor.ui.featureComponents.add( 'bold', Button, ButtonView, boldModel );
+
+	const italicModel = new Model( {
+		isEnabled: true,
+		isOn: false,
+		label: 'italic'
+	} );
+
+	italicModel.on( 'execute', () => {
+		/* global console */
+		console.log( 'italic executed' );
+
+		italicModel.isOn = !italicModel.isOn;
+	} );
+
+	editor.ui.featureComponents.add( 'italic', Button, ButtonView, italicModel );
+
+	window.boldModel = boldModel;
+	window.italicModel = italicModel;
+}
+
+export function imitateDestroyFeatures() {
+	delete window.boldModel;
+	delete window.italicModel;
+}

+ 4 - 0
packages/ckeditor5-engine/tests/creator/manual/creator-classic.html

@@ -1,3 +1,7 @@
+<head>
+	<link rel="stylesheet" href="%TEST_DIR%_assets/styles.css">
+</head>
+
 <div id="editor">
 	<h1>Hello world!</h1>
 	<p>This is an editor instance.</p>

+ 2 - 1
packages/ckeditor5-engine/tests/creator/manual/creator-classic.js

@@ -19,7 +19,8 @@ function initEditor() {
 		ui: {
 			width: 400,
 			height: 400
-		}
+		},
+		toolbar: [ 'bold', 'italic' ]
 	} )
 	.then( ( newEditor ) => {
 		console.log( 'Editor was initialized', newEditor );

+ 4 - 0
packages/ckeditor5-engine/tests/creator/manual/creator-classic.md

@@ -5,15 +5,19 @@
   * Framed editor should be created.
   * It should be rectangular (400x400).
   * Original element should disappear.
+  * There should be a toolbar with "bold" and "italic" buttons.
 3. Click "Destroy editor".
 4. Expected:
   * Editor should be destroyed.
   * Original element should be visible.
   * The element should contain its data (updated).
+  * The 'ck-body region' should be removed.
 
 ## Notes:
 
 * You can play with:
   * `editor.editable.isEditable`,
   * `editor.ui.width/height`.
+  * `boldModel.isEnabled` and `italicModel.isEnabled`.
 * Changes to `editable.isFocused/isEditable` should be logged to the console.
+* Clicks on the buttons should be logged to the console.

+ 4 - 0
packages/ckeditor5-engine/tests/creator/manual/creator-inline.html

@@ -1,3 +1,7 @@
+<head>
+	<link rel="stylesheet" href="%TEST_DIR%_assets/styles.css">
+</head>
+
 <div id="editor">
 	<h1>Hello world!</h1>
 	<p>This is an editor instance.</p>

+ 2 - 1
packages/ckeditor5-engine/tests/creator/manual/creator-inline.js

@@ -15,7 +15,8 @@ let editor, observer;
 
 function initEditor() {
 	CKEDITOR.create( '#editor', {
-		creator: InlineCreator
+		creator: InlineCreator,
+		toolbar: [ 'bold', 'italic' ]
 	} )
 	.then( ( newEditor ) => {
 		console.log( 'Editor was initialized', newEditor );

+ 8 - 1
packages/ckeditor5-engine/tests/creator/manual/creator-inline.md

@@ -3,12 +3,19 @@
 1. Click "Init editor".
 2. Expected:
   * Inline editor should be created.
+  * There should be **two** toolbars:
+    * one with "bold" and "italic" buttons,
+    * second with "italic" and "bold" buttons.
 3. Click "Destroy editor".
 4. Expected:
   * Editor should be destroyed (the element should not be editable).
   * The element should contain its data (updated).
+  * The 'ck-body region' should be removed.
 
 ## Notes:
 
-* You can play with `editor.editable.isEditable`.
+* You can play with:
+  * `editor.editable.isEditable`.
+  * `boldModel.isEnabled` and `italicModel.isEnabled`.
 * Changes to `editable.isFocused/isEditable` should be logged to the console.
+* Buttons' states should be synchronised between toolbars (they share models).

+ 7 - 1
packages/ckeditor5-engine/tests/editorui.js → packages/ckeditor5-engine/tests/editorui/editorui.js

@@ -6,7 +6,9 @@
 'use strict';
 
 import Editor from '/ckeditor5/core/editor.js';
-import EditorUI from '/ckeditor5/core/editorui.js';
+import EditorUI from '/ckeditor5/core/editorui/editorui.js';
+import ComponentFactory from '/ckeditor5/core/ui/componentfactory.js';
+import ControllerCollection from '/ckeditor5/core/ui/controllercollection.js';
 
 describe( 'EditorUI', () => {
 	let editor, editorUI;
@@ -19,6 +21,10 @@ describe( 'EditorUI', () => {
 	describe( 'constructor', () => {
 		it( 'sets all the properties', () => {
 			expect( editorUI ).to.have.property( 'editor', editor );
+
+			expect( editorUI.featureComponents ).to.be.instanceof( ComponentFactory );
+
+			expect( editorUI.collections.get( 'body' ) ).to.be.instanceof( ControllerCollection );
 		} );
 	} );
 } );

+ 38 - 0
packages/ckeditor5-engine/tests/editorui/editoruiview.js

@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import EditorUIView from '/ckeditor5/core/editorui/editoruiview.js';
+import Model from '/ckeditor5/core/ui/model.js';
+
+describe( 'EditorUIView', () => {
+	let editorUIView;
+
+	beforeEach( () => {
+		editorUIView = new EditorUIView( new Model() );
+
+		return editorUIView.init();
+	} );
+
+	describe( 'constructor', () => {
+		it( 'creates the body region', () => {
+			const el = editorUIView.regions.get( 'body' ).element;
+
+			expect( el.parentNode ).to.equal( document.body );
+			expect( el.nextSibling ).to.be.null;
+		} );
+	} );
+
+	describe( 'destroy', () => {
+		it( 'removes the body region container', () => {
+			const el = editorUIView.regions.get( 'body' ).element;
+
+			editorUIView.destroy();
+
+			expect( el.parentNode ).to.be.null;
+		} );
+	} );
+} );

+ 34 - 0
packages/ckeditor5-engine/tests/ui/button/button.js

@@ -0,0 +1,34 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, button */
+
+'use strict';
+
+import Button from '/ckeditor5/core/ui/button/button.js';
+import View from '/ckeditor5/core/ui/view.js';
+import Model from '/ckeditor5/core/ui/model.js';
+
+describe( 'Button', () => {
+	let model, button, view;
+
+	beforeEach( () => {
+		model = new Model();
+		view = new View();
+		button = new Button( model, view );
+	} );
+
+	describe( 'constructor', () => {
+		it( 'creates view#click -> model#execute binding', () => {
+			const spy = sinon.spy();
+
+			model.on( 'execute', spy );
+
+			view.fire( 'click' );
+
+			expect( spy.calledOnce ).to.be.true;
+		} );
+	} );
+} );

+ 84 - 0
packages/ckeditor5-engine/tests/ui/button/buttonview.js

@@ -0,0 +1,84 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, button */
+
+'use strict';
+
+import ButtonView from '/ckeditor5/core/ui/button/buttonview.js';
+import Model from '/ckeditor5/core/ui/model.js';
+
+describe( 'ButtonView', () => {
+	let model, view;
+
+	beforeEach( () => {
+		model = new Model( {
+			label: 'foo',
+			isEnabled: true,
+			isOn: false
+		} );
+		view = new ButtonView( model );
+
+		return view.init();
+	} );
+
+	describe( '<button> bindings', () => {
+		describe( 'class', () => {
+			it( 'is set initially', () => {
+				expect( view.element.classList.contains( 'ck-button' ) ).to.be.true( 'ck-button' );
+				expect( view.element.classList.contains( 'ck-enabled' ) ).to.be.true( 'ck-enabled' );
+				expect( view.element.classList.contains( 'ck-off' ) ).to.be.true( 'ck-off' );
+			} );
+
+			it( 'reacts on model.isEnabled', () => {
+				model.isEnabled = false;
+
+				expect( view.element.classList.contains( 'ck-disabled' ) ).to.be.true( 'ck-disabled' );
+			} );
+
+			it( 'reacts on model.isOn', () => {
+				model.isOn = true;
+
+				expect( view.element.classList.contains( 'ck-on' ) ).to.be.true( 'ck-on' );
+			} );
+		} );
+
+		describe( 'text', () => {
+			it( 'is set initially', () => {
+				expect( view.element.textContent ).to.equal( 'foo' );
+			} );
+
+			it( 'reacts on model.label', () => {
+				model.label = 'bar';
+
+				expect( view.element.textContent ).to.equal( 'bar' );
+			} );
+		} );
+
+		describe( 'mousedown event', () => {
+			it( 'should be prevented', () => {
+				const ret = view.element.dispatchEvent( new Event( 'mousedown', { cancelable: true } ) );
+
+				expect( ret ).to.be.false;
+			} );
+		} );
+
+		describe( 'click event', () => {
+			it( 'triggers click event if button is not disabled', () => {
+				const spy = sinon.spy();
+
+				view.on( 'click', spy );
+
+				view.element.dispatchEvent( new Event( 'click' ) );
+				expect( spy.callCount ).to.equal( 1 );
+
+				model.isEnabled = false;
+
+				view.element.dispatchEvent( new Event( 'click' ) );
+				expect( spy.callCount ).to.equal( 1 );
+			} );
+		} );
+	} );
+} );

+ 59 - 0
packages/ckeditor5-engine/tests/ui/componentfactory.js

@@ -0,0 +1,59 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui */
+
+'use strict';
+
+import Editor from '/ckeditor5/core/editor.js';
+import ComponentFactory from '/ckeditor5/core/ui/componentfactory.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+
+describe( 'ComponentFactory', () => {
+	let editor, factory;
+
+	beforeEach( () => {
+		editor = new Editor();
+		factory = new ComponentFactory( editor );
+	} );
+
+	describe( 'constructor', () => {
+		it( 'sets all the properties', () => {
+			expect( factory ).to.have.property( 'editor', editor );
+		} );
+	} );
+
+	describe( 'add', () => {
+		it( 'throws when trying to override already registered component', () => {
+			factory.add( 'foo', class {}, class {}, {} );
+
+			expect( () => {
+				factory.add( 'foo', class {}, class {}, {} );
+			} ).to.throw( CKEditorError, /^componentfactory-item-exists/ );
+		} );
+	} );
+
+	describe( 'create', () => {
+		it( 'creates an instance', () => {
+			class View {}
+
+			class Controller {
+				constructor( ed, model, view ) {
+					expect( ed ).to.equal( editor );
+					expect( model ).to.equal( model );
+					expect( view ).to.be.instanceof( View );
+				}
+			}
+
+			const model = {};
+
+			factory.add( 'foo', Controller, View, model );
+
+			const instance = factory.create( 'foo' );
+
+			expect( instance ).to.be.instanceof( Controller );
+		} );
+	} );
+} );

+ 2 - 0
packages/ckeditor5-engine/tests/ui/model.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* bender-tags: ui */
+
 'use strict';
 
 import Model from '/ckeditor5/core/ui/model.js';

+ 25 - 0
packages/ckeditor5-engine/tests/ui/toolbar/toolbar.js

@@ -0,0 +1,25 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, toolbar */
+
+'use strict';
+
+import Toolbar from '/ckeditor5/core/ui/toolbar/toolbar.js';
+import ControllerCollection from '/ckeditor5/core/ui/controllercollection.js';
+
+describe( 'Toolbar', () => {
+	let toolbar;
+
+	beforeEach( () => {
+		toolbar = new Toolbar();
+	} );
+
+	describe( 'constructor', () => {
+		it( 'creates buttons collection', () => {
+			expect( toolbar.collections.get( 'buttons' ) ).to.be.instanceof( ControllerCollection );
+		} );
+	} );
+} );

+ 34 - 0
packages/ckeditor5-engine/tests/ui/toolbar/toolbarview.js

@@ -0,0 +1,34 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, toolbar */
+
+'use strict';
+
+import ToolbarView from '/ckeditor5/core/ui/toolbar/toolbarview.js';
+import Model from '/ckeditor5/core/ui/model.js';
+
+describe( 'ToolbarView', () => {
+	let model, view;
+
+	beforeEach( () => {
+		model = new Model();
+		view = new ToolbarView( model );
+
+		return view.init();
+	} );
+
+	describe( 'the main element bindings', () => {
+		it( 'is fine', () => {
+			expect( view.element.classList.contains( 'ck-toolbar' ) );
+		} );
+	} );
+
+	describe( 'buttons region', () => {
+		it( 'is bound to the main element', () => {
+			expect( view.regions.get( 'buttons' ).element ).to.equal( view.element );
+		} );
+	} );
+} );

+ 1 - 4
packages/ckeditor5-engine/tests/ui/view.js

@@ -42,10 +42,7 @@ describe( 'View', () => {
 			expect( view._regionsSelectors ).to.be.empty;
 			expect( view._element ).to.be.null;
 			expect( view._template ).to.be.null;
-
-			expect( () => {
-				view.element;
-			} ).to.throw( CKEditorError, /ui-view-notemplate/ );
+			expect( view.element ).to.be.null;
 		} );
 	} );