Jelajahi Sumber

Merge pull request #24 from ckeditor/t/22

T/22: Provide necessary API for Classic Creator to happen.
Piotrek Koszuliński 9 tahun lalu
induk
melakukan
1dfbffe0ac

+ 29 - 0
packages/ckeditor5-ui/src/bindings/stickytoolbar.js

@@ -0,0 +1,29 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Toolbar from './toolbar.js';
+
+/**
+ * The editor sticky toolbar controller class.
+ *
+ * @memberOf ui.bindings
+ * @extends ui.toolbar.Toolbar
+ */
+export default class StickyToolbar extends Toolbar {
+	/**
+	 * Creates a new toolbar instance.
+	 *
+	 * @param {ui.Model} model
+	 * @param {ui.View} view
+	 * @param {ckeditor5.Editor} editor
+	 */
+	constructor( model, view, editor ) {
+		super( model, view, editor );
+
+		model.bind( 'isActive' ).to( editor.editables, 'current', c => !!c );
+	}
+}

+ 2 - 1
packages/ckeditor5-ui/src/domemittermixin.js

@@ -9,6 +9,7 @@ import EmitterMixin from '../utils/emittermixin.js';
 import uid from '../utils/uid.js';
 import extend from '../utils/lib/lodash/extend.js';
 import log from '../utils/log.js';
+import isNative from '/ckeditor5/utils/lib/lodash/isNative.js';
 
 /**
  * Creates a ProxyEmitter instance. Such an instance is a bridge between a DOM Node firing events
@@ -268,7 +269,7 @@ function getNodeUID( node ) {
 // @param {Node} node
 // @return {Boolean} True when native DOM Node.
 function isDomNode( node ) {
-	return node && node.nodeType && ( typeof node.addEventListener == 'function' );
+	return node && isNative( node.addEventListener );
 }
 
 /**

+ 1 - 0
packages/ckeditor5-ui/src/editableui/editableui.js

@@ -38,5 +38,6 @@ export default class EditableUI extends Controller {
 		 */
 		this.viewModel = new Model();
 		this.viewModel.bind( 'isEditable', 'isFocused' ).to( editableModel );
+		this.viewModel.set( 'editableName', editableModel.name );
 	}
 }

+ 40 - 26
packages/ckeditor5-ui/src/editableui/editableuiview.js

@@ -6,7 +6,6 @@
 'use strict';
 
 import View from '../view.js';
-import CKEditorError from '../../utils/ckeditorerror.js';
 
 /**
  * @memberOf ui.editableUI
@@ -16,41 +15,56 @@ export default class EditableUIView extends View {
 	/**
 	 * Creates an instance of the EditableUIView class.
 	 *
-	 * @method constructor
-	 * @param {ui.Model} model (View)Model of this view.
+	 * @param {utils.Observable} model (View)Model of this View.
 	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
-	 * @param {HTMLElement} [editableElement] The editable element. If not specified the editable UI view
+	 * @param {HTMLElement} [editableElement] The editable element. If not specified, this View
 	 * should create it. Otherwise, the existing element should be used.
 	 */
+	constructor( model, locale, editableElement ) {
+		super( model, locale );
 
-	/**
-	 * The element which is the main editable element (usually the one with `contentEditable="true"`).
-	 *
-	 * @readonly
-	 * @member {HTMLElement} ui.editable.EditableUIView#editableElement
-	 */
-
-	/**
-	 * Sets the {@link #editableElement} property and applies necessary bindings to it.
-	 *
-	 * @param {HTMLElement} editableElement
-	 */
-	setEditableElement( editableElement ) {
 		const bind = this.attributeBinder;
 
-		if ( this.editableElement ) {
-			throw new CKEditorError(
-				'editableview-cannot-override-editableelement: The editableElement cannot be overriden.'
-			);
+		if ( editableElement ) {
+			this.element = this.editableElement = editableElement;
 		}
 
-		this.editableElement = editableElement;
-
-		this.applyTemplateToElement( editableElement, {
+		this.template = {
+			tag: 'div',
 			attributes: {
+				class: [
+					bind.to( 'isFocused', value => value ? 'ck-focused' : 'ck-blurred' ),
+					'ck-editor__editable'
+				],
 				contenteditable: bind.to( 'isEditable' ),
-				class: [ bind.to( 'isFocused', value => value ? 'ck-focused' : 'ck-blurred' ) ]
 			}
-		} );
+		};
 	}
+
+	/**
+	 * Initializes the View by either applying the {@link template} to the existing
+	 * {@link editableElement} or assigns {@link element} as {@link editableElement}.
+	 */
+	init() {
+		if ( this.editableElement ) {
+			this.applyTemplateToElement( this.editableElement, this.template );
+		} else {
+			this.editableElement = this.element;
+		}
+
+		super.init();
+	}
+
+	destroy() {
+		super.destroy();
+
+		this.editableElement.contentEditable = false;
+	}
+
+	/**
+	 * The element which is the main editable element (usually the one with `contentEditable="true"`).
+	 *
+	 * @readonly
+	 * @member {HTMLElement} ui.editableUI.EditableUIView#editableElement
+	 */
 }

+ 38 - 0
packages/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js

@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import EditableUIView from '/ckeditor5/ui/editableui/editableuiview.js';
+
+/**
+ * The class implementing an inline {@link ui.editableUI.EditableUIView}.
+ *
+ * @memberOf ui.editableUI.inline
+ * @extends ui.editableUI.EditableUIView
+ */
+export default class InlineEditableUIView extends EditableUIView {
+	/**
+	 * Creates an instance of the InlineEditableUIView class.
+	 *
+	 * @param {utils.Observable} model (View)Model of this view.
+	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
+	 * @param {HTMLElement} [editableElement] The editable element. If not specified, the {@link EditableUIView}
+	 * should create it. Otherwise, the existing element should be used.
+	 */
+	constructor( model, locale, editableElement ) {
+		super( model, locale, editableElement );
+
+		const label = this.t( 'Rich Text Editor, %0', [ this.model.editableName ] );
+
+		Object.assign( this.template.attributes, {
+			role: 'textbox',
+			'aria-label': label,
+			title: label
+		} );
+
+		this.template.attributes.class.push( 'ck-editor__editable_inline' );
+	}
+}

+ 60 - 0
packages/ckeditor5-ui/src/editorui/boxed/boxededitorui.js

@@ -0,0 +1,60 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import EditorUI from '/ckeditor5/ui/editorui/editorui.js';
+import ControllerCollection from '/ckeditor5/ui/controllercollection.js';
+
+/**
+ * Boxed editor UI. The class representing classic editor interface, which contains of a toolbar and editable are,
+ * closed within a box.
+ *
+ * @member ui.editorUI.boxed
+ * @extends ui.editorUI.EditorUI
+ */
+export default class BoxedEditorUI extends EditorUI {
+	/**
+	 * Creates a BoxedEditorUI instance.
+	 *
+	 * @param {ckeditor5.Editor} editor
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		this.collections.add( new ControllerCollection( 'top' ) );
+		this.collections.add( new ControllerCollection( 'main' ) );
+
+		const config = editor.config;
+
+		/**
+		 * The editor's width. Defaults to {@link ckeditor5.editor.config.ui.width}.
+		 *
+		 * Note: a specific creator that was used must support this setting.
+		 *
+		 * @observable
+		 * @property {Number} width
+		 */
+		this.set( 'width', config.get( 'ui.width' ) );
+
+		/**
+		 * The editor's height. Defaults to {@link ckeditor5.editor.config.ui.height}.
+		 *
+		 * Note: a specific creator that was used must support this setting.
+		 *
+		 * @observable
+		 * @property {Number} height
+		 */
+		this.set( 'height', config.get( 'ui.height' ) );
+	}
+
+	/**
+	 * @readonly
+	 * @property {ui.Model} viewModel
+	 */
+	get viewModel() {
+		return this;
+	}
+}

+ 73 - 0
packages/ckeditor5-ui/src/editorui/boxed/boxededitoruiview.js

@@ -0,0 +1,73 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import EditorUIView from '/ckeditor5/ui/editorui/editoruiview.js';
+import uid from '/ckeditor5/utils/uid.js';
+
+/**
+ * Boxed editor UI view.
+ *
+ * @member ui.editorUI.boxed
+ * @extends ui.editorUI.EditorUIView
+ */
+export default class BoxedEditorUIView extends EditorUIView {
+	/**
+	 * Creates a BoxedEditorUIView instance.
+	 *
+	 * @param {utils.Observable} model (View)Model of this view.
+	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
+	 */
+	constructor( model, locale ) {
+		super( model, locale );
+
+		const t = this.t;
+		const ariaLabelUid = uid();
+
+		this.template = {
+			tag: 'div',
+
+			attributes: {
+				class: 'ck-reset ck-editor',
+				role: 'application',
+				dir: 'ltr',
+				lang: locale.lang,
+				'aria-labelledby': `cke-editor__aria-label_${ ariaLabelUid }`
+			},
+
+			children: [
+				{
+					tag: 'span',
+					attributes: {
+						id: `cke-editor__aria-label_${ ariaLabelUid }`,
+						class: 'cke-voice-label',
+						children: [
+							// TODO: Editor name?
+							t( 'Rich Text Editor' )
+						]
+					}
+				},
+				{
+					tag: 'div',
+					attributes: {
+						class: 'ck-editor__top ck-reset-all',
+						role: 'presentation'
+					}
+				},
+				{
+					tag: 'div',
+					attributes: {
+						class: 'ck-editor__main',
+						role: 'presentation'
+					}
+				}
+			]
+		};
+
+		this.register( 'top', '.ck-editor__top' );
+		this.register( 'main', '.ck-editor__main' );
+	}
+}

+ 0 - 1
packages/ckeditor5-ui/src/editorui/editorui.js

@@ -20,7 +20,6 @@ import mix from '../../utils/mix.js';
  * @extends ui.Controller
  * @mixes utils.ObservaleMixin
  */
-
 export default class EditorUI extends Controller {
 	/**
 	 * Creates an EditorUI instance.

+ 6 - 1
packages/ckeditor5-ui/src/editorui/editoruiview.js

@@ -13,8 +13,13 @@ import View from '../view.js';
  * @memberOf ui.editorUI
  * @extends ui.View
  */
-
 export default class EditorUIView extends View {
+	/**
+	 * Creates a EditorUIView instance.
+	 *
+	 * @param {utils.Observable} model (View)Model of this view.
+	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
+	 */
 	constructor( model, locale ) {
 		super( model, locale );
 

+ 18 - 9
packages/ckeditor5-ui/src/view.js

@@ -473,7 +473,11 @@ export default class View {
 						modelValue = schemaItem.callback( modelValue, node );
 					}
 
-					return modelValue;
+					if ( schemaItem.type === bindIfSymbol ) {
+						return !!modelValue ? schemaItem.valueIfTrue || true : '';
+					} else {
+						return modelValue;
+					}
 				} else {
 					return schemaItem;
 				}
@@ -498,20 +502,22 @@ export default class View {
 			// A function executed each time bound model attribute changes.
 			const onModelChange = () => {
 				let value = getBoundValue( node );
+				let shouldSet;
 
 				if ( isPlainBindIf ) {
 					value = value[ 0 ];
+					shouldSet = value !== '';
+
+					if ( shouldSet ) {
+						value = value === true ? '' : value;
+					}
 				} else {
 					value = value.reduce( binderValueReducer, '' );
+					shouldSet = value;
 				}
 
-				const isSet = isPlainBindIf ? !!value : value;
-
-				const valueToSet = isPlainBindIf ?
-					( valueSchema[ 0 ].valueIfTrue || '' ) : value;
-
-				if ( isSet ) {
-					domUpdater.set( valueToSet );
+				if ( shouldSet ) {
+					domUpdater.set( value );
 				} else {
 					domUpdater.remove();
 				}
@@ -709,7 +715,10 @@ function hasModelBinding( valueSchema ) {
  * @returns {String}
  */
 function binderValueReducer( prev, cur ) {
-	return prev === '' ? `${cur}` : `${prev} ${cur}`;
+	return prev === '' ?
+			`${cur}`
+		:
+			cur === '' ? `${prev}` : `${prev} ${cur}`;
 }
 
 /**

+ 38 - 0
packages/ckeditor5-ui/tests/bindings/stickytoolbar.js

@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, stickytoolbar */
+
+'use strict';
+
+import Editor from '/ckeditor5/editor.js';
+import Editable from '/ckeditor5/editable.js';
+import Model from '/ckeditor5/ui/model.js';
+import View from '/ckeditor5/ui/view.js';
+import StickyToolbar from '/ckeditor5/ui/bindings/stickytoolbar.js';
+
+describe( 'StickyToolbar', () => {
+	let toolbar, view, model, editor, editable;
+
+	beforeEach( () => {
+		editor = new Editor();
+		editable = new Editable( editor, 'foo' );
+		model = new Model();
+		view = new View( model );
+		toolbar = new StickyToolbar( model, view, editor );
+	} );
+
+	describe( 'constructor', () => {
+		it( 'binds model#isActive to editor.editables#current', () => {
+			expect( model.isActive ).to.be.false;
+
+			editor.editables.current = editable;
+			expect( model.isActive ).to.be.true;
+
+			editor.editables.current = null;
+			expect( model.isActive ).to.be.false;
+		} );
+	} );
+} );

+ 2 - 2
packages/ckeditor5-ui/tests/bindings/toolbar.js

@@ -20,7 +20,7 @@ describe( 'Toolbar', () => {
 		editor = new Editor();
 		model = new Model();
 		view = new View( model );
-		toolbar = new Toolbar( view, model, editor );
+		toolbar = new Toolbar( model, view, editor );
 	} );
 
 	describe( 'constructor', () => {
@@ -46,7 +46,7 @@ describe( 'Toolbar', () => {
 			expect( createSpy.secondCall.calledWith( 'bar' ) ).to.be.true;
 		} );
 
-		it( 'adds created compoments to the collection of buttons', () => {
+		it( 'adds created components to the collection of buttons', () => {
 			const component = new Controller();
 			const createSpy = sinon.spy( () => component );
 

+ 42 - 0
packages/ckeditor5-ui/tests/boxededitorui/boxededitorui.js

@@ -0,0 +1,42 @@
+/**
+ * @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 BoxedEditorUI from '/ckeditor5/ui/editorui/boxed/boxededitorui.js';
+import ControllerCollection from '/ckeditor5/ui/controllercollection.js';
+
+describe( 'BoxedEditorUI', () => {
+	let editor, boxedEditorUI;
+
+	beforeEach( () => {
+		editor = new Editor( null, {
+			ui: {
+				width: 100,
+				height: 200
+			}
+		} );
+		boxedEditorUI = new BoxedEditorUI( editor );
+	} );
+
+	describe( 'constructor', () => {
+		it( 'adds controller collections', () => {
+			expect( boxedEditorUI.collections.get( 'top' ) ).to.be.instanceof( ControllerCollection );
+			expect( boxedEditorUI.collections.get( 'main' ) ).to.be.instanceof( ControllerCollection );
+		} );
+
+		it( 'sets "width" and "height" attributes', () => {
+			expect( boxedEditorUI.width ).to.equal( editor.config.get( 'ui.width' ) );
+			expect( boxedEditorUI.height ).to.equal( editor.config.get( 'ui.height' ) );
+		} );
+	} );
+
+	describe( 'viewModel', () => {
+		it( 'points to the BoxedEditorUI instance', () => {
+			expect( boxedEditorUI.viewModel ).to.equal( boxedEditorUI );
+		} );
+	} );
+} );

+ 53 - 0
packages/ckeditor5-ui/tests/boxededitorui/boxededitoruiview.js

@@ -0,0 +1,53 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import BoxedEditorUIView from '/ckeditor5/ui/editorui/boxed/boxededitoruiview.js';
+import Model from '/ckeditor5/ui/model.js';
+import Locale from '/ckeditor5/utils/locale.js';
+
+describe( 'BoxedEditorUIView', () => {
+	let boxedEditorUIView, element, topRegionEl, mainRegionEl;
+
+	beforeEach( () => {
+		boxedEditorUIView = new BoxedEditorUIView( new Model(), new Locale( 'en' ) );
+		boxedEditorUIView.init();
+
+		element = boxedEditorUIView.element;
+
+		const regions = boxedEditorUIView.regions;
+		topRegionEl = regions.get( 'top' ).element;
+		mainRegionEl = regions.get( 'main' ).element;
+	} );
+
+	describe( 'constructor', () => {
+		it( 'creates the regions', () => {
+			expect( topRegionEl.parentNode ).to.equal( boxedEditorUIView.element );
+			expect( mainRegionEl.parentNode ).to.equal( boxedEditorUIView.element );
+		} );
+
+		it( 'bootstraps the view element from template', () => {
+			expect( boxedEditorUIView.element.classList.contains( 'ck-editor' ) ).to.be.true;
+		} );
+
+		it( 'setups accessibility of the view element', () => {
+			expect( element.attributes.getNamedItem( 'aria-labelledby' ).value ).to.equal(
+				boxedEditorUIView.element.firstChild.id );
+			expect( element.attributes.getNamedItem( 'role' ).value ).to.equal( 'application' );
+			expect( element.attributes.getNamedItem( 'lang' ).value ).to.equal( 'en' );
+		} );
+
+		it( 'bootstraps the view region elements from template', () => {
+			expect( topRegionEl.classList.contains( 'ck-editor__top' ) ).to.be.true;
+			expect( mainRegionEl.classList.contains( 'ck-editor__main' ) ).to.be.true;
+		} );
+
+		it( 'setups accessibility of the view region elements', () => {
+			expect( topRegionEl.attributes.getNamedItem( 'role' ).value ).to.equal( 'presentation' );
+			expect( mainRegionEl.attributes.getNamedItem( 'role' ).value ).to.equal( 'presentation' );
+		} );
+	} );
+} );

+ 20 - 0
packages/ckeditor5-ui/tests/domemittermixin.js

@@ -60,6 +60,26 @@ describe( 'DOMEmitterMixin', () => {
 			sinon.assert.calledOnce( spy );
 		} );
 
+		it( 'should listen to native DOM events - window as source', () => {
+			const spy = testUtils.sinon.spy();
+
+			domEmitter.listenTo( window, 'test', spy );
+
+			window.dispatchEvent( new Event( 'test' ) );
+
+			sinon.assert.calledOnce( spy );
+		} );
+
+		it( 'should listen to native DOM events - document as source', () => {
+			const spy = testUtils.sinon.spy();
+
+			domEmitter.listenTo( document, 'test', spy );
+
+			document.dispatchEvent( new Event( 'test' ) );
+
+			sinon.assert.calledOnce( spy );
+		} );
+
 		// #187
 		it( 'should work for DOM Nodes belonging to another window', () => {
 			const spy = testUtils.sinon.spy();

+ 48 - 25
packages/ckeditor5-ui/tests/editableui/editableuiview.js

@@ -7,56 +7,79 @@
 
 'use strict';
 
-import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
 import EditableUIView from '/ckeditor5/ui/editableui/editableuiview.js';
 import Model from '/ckeditor5/ui/model.js';
+import Locale from '/ckeditor5/utils/locale.js';
 
 describe( 'EditableUIView', () => {
-	let model, view, editableElement;
+	let model, view, editableElement, locale;
 
 	beforeEach( () => {
 		model = new Model( { isEditable: true, isFocused: false } );
-		view = new EditableUIView( model );
+		locale = new Locale( 'en' );
+		view = new EditableUIView( model, locale );
 		editableElement = document.createElement( 'div' );
 
-		return view.init();
+		view.init();
 	} );
 
-	describe( 'setEditableElement', () => {
-		it( 'sets the editableElement property', () => {
-			view.setEditableElement( editableElement );
+	describe( 'constructor', () => {
+		it( 'renders element from template when no editableElement', () => {
+			view = new EditableUIView( model, locale );
+			view.init();
 
-			expect( view ).to.have.property( 'editableElement', editableElement );
+			expect( view.element ).to.equal( view.editableElement );
+			expect( view.element.classList.contains( 'ck-editor__editable' ) ).to.be.true;
 		} );
 
-		it( 'throws when trying to use it twice', () => {
-			view.setEditableElement( editableElement );
+		it( 'accepts editableElement as an argument', () => {
+			view = new EditableUIView( model, locale, editableElement );
+			view.init();
 
-			expect( view ).to.have.property( 'editableElement', editableElement );
-
-			expect( () => {
-				view.setEditableElement( editableElement );
-			} ).to.throw( CKEditorError, /^editableview-cannot-override-editableelement/ );
+			expect( view.element ).to.equal( editableElement );
+			expect( view.element ).to.equal( view.editableElement );
+			expect( view.element.classList.contains( 'ck-editor__editable' ) ).to.be.true;
 		} );
+	} );
+
+	describe( 'View bindings', () => {
+		describe( 'class', () => {
+			it( 'has initial value set', () => {
+				expect( view.element.classList.contains( 'ck-blurred' ) ).to.be.true;
+				expect( view.element.classList.contains( 'ck-focused' ) ).to.be.false;
+			} );
 
-		it( 'sets the contentEditable attribute to model.isEditable', () => {
-			view.setEditableElement( editableElement );
+			it( 'reacts on model.isFocused', () => {
+				model.isFocused = true;
 
-			expect( editableElement.contentEditable ).to.equal( 'true' );
+				expect( view.element.classList.contains( 'ck-blurred' ) ).to.be.false;
+				expect( view.element.classList.contains( 'ck-focused' ) ).to.be.true;
+			} );
+		} );
+
+		describe( 'contenteditable', () => {
+			it( 'has initial value set', () => {
+				expect( view.element.attributes.getNamedItem( 'contenteditable' ).value ).to.equal( 'true' );
+			} );
 
-			model.isEditable = false;
+			it( 'reacts on model.isEditable', () => {
+				model.isEditable = false;
 
-			expect( editableElement.contentEditable ).to.equal( 'false' );
+				expect( view.element.attributes.getNamedItem( 'contenteditable' ).value ).to.equal( 'false' );
+			} );
 		} );
+	} );
 
-		it( 'sets the contentEditable attribute to model.isEditable', () => {
-			view.setEditableElement( editableElement );
+	describe( 'destroy', () => {
+		it( 'updates contentEditable property of editableElement', () => {
+			view = new EditableUIView( model, locale, editableElement );
+			view.init();
 
-			expect( editableElement.classList.contains( 'ck-blurred' ) ).to.be.true;
+			expect( view.editableElement.contentEditable ).to.equal( 'true' );
 
-			model.isFocused = true;
+			view.destroy();
 
-			expect( editableElement.classList.contains( 'ck-focused' ) ).to.be.true;
+			expect( view.editableElement.contentEditable ).to.equal( 'false' );
 		} );
 	} );
 } );

+ 64 - 0
packages/ckeditor5-ui/tests/editableui/inline/inlineeditableuiview.js

@@ -0,0 +1,64 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: editable */
+
+'use strict';
+
+import InlineEditableUIView from '/ckeditor5/ui/editableui/inline/inlineeditableuiview.js';
+import Model from '/ckeditor5/ui/model.js';
+import Locale from '/ckeditor5/utils/locale.js';
+
+describe( 'InlineEditableUIView', () => {
+	let model, view, editableElement, locale;
+
+	beforeEach( () => {
+		model = new Model( { isEditable: true, isFocused: false } );
+		locale = new Locale( 'en' );
+		view = new InlineEditableUIView( model, locale );
+		editableElement = document.createElement( 'div' );
+
+		return view.init();
+	} );
+
+	describe( 'constructor', () => {
+		it( 'accepts model', () => {
+			expect( view.model ).to.equal( model );
+		} );
+
+		it( 'accepts locale', () => {
+			expect( view.locale ).to.equal( locale );
+		} );
+
+		it( 'accepts editableElement', () => {
+			view = new InlineEditableUIView( model, locale, editableElement );
+
+			expect( view.element ).to.equal( editableElement );
+		} );
+
+		it( 'creates view#element from template when no editableElement provided', () => {
+			expect( view.template ).to.be.an( 'object' );
+		} );
+	} );
+
+	describe( 'editableElement', () => {
+		it( 'has proper accessibility role', () => {
+			expect( view.element.attributes.getNamedItem( 'role' ).value ).to.equal( 'textbox' );
+		} );
+
+		it( 'has proper ARIA label', () => {
+			expect( view.element.attributes.getNamedItem( 'aria-label' ).value ).to.be.a( 'string' );
+		} );
+
+		it( 'has proper title', () => {
+			expect( view.element.attributes.getNamedItem( 'title' ).value ).to.be.a( 'string' );
+		} );
+
+		it( 'has proper class name', () => {
+			expect( view.element.classList.contains( 'ck-editor__editable' ) ).to.be.true;
+			expect( view.element.classList.contains( 'ck-editor__editable_inline' ) ).to.be.true;
+		} );
+	} );
+} );

+ 25 - 0
packages/ckeditor5-ui/tests/view.js

@@ -607,6 +607,31 @@ describe( 'View', () => {
 				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 			} );
 
+			it( 'allows binding attribute to the model – array of bindings (HTMLElement attribute)', () => {
+				setTestViewClass( function() {
+					const bind = this.attributeBinder;
+
+					return {
+						tag: 'p',
+						attributes: {
+							'class': [
+								'ck-class',
+								bind.if( 'foo', 'foo-set' ),
+								bind.if( 'bar', 'bar-not-set', ( value ) => !value ),
+								'ck-end'
+							]
+						},
+						children: [ 'abc' ]
+					};
+				} );
+
+				setTestViewInstance( { foo: true, bar: true } );
+				expect( view.element.outerHTML ).to.equal( '<p class="ck-class foo-set ck-end">abc</p>' );
+
+				view.model.foo = view.model.bar = false;
+				expect( view.element.outerHTML ).to.equal( '<p class="ck-class bar-not-set ck-end">abc</p>' );
+			} );
+
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback', () => {
 				setTestViewClass( function() {
 					const bind = this.attributeBinder;