Răsfoiți Sursa

Implemented Locale and used Locale#t in the existing code.

I also implemented the mock for testing against Locale. It will be useful in the future.

For now the Locale is passed to the views manually. We were considering adding some
views factory, but calling editor.view( ViewClass, model, ...other args ) would not be that
simpler and there's a problem with arguments order, because a view may accept more than
a model and then the locale must be injected in the middle of (model, ..., other args).
Piotrek Koszuliński 10 ani în urmă
părinte
comite
b02519fec2

+ 20 - 5
packages/ckeditor5-engine/src/editor.js

@@ -9,6 +9,7 @@ import ObservableMixin from './observablemixin.js';
 import EditorConfig from './editorconfig.js';
 import EditorConfig from './editorconfig.js';
 import PluginCollection from './plugincollection.js';
 import PluginCollection from './plugincollection.js';
 import CKEditorError from './ckeditorerror.js';
 import CKEditorError from './ckeditorerror.js';
+import Locale from './locale.js';
 import isArray from './lib/lodash/isArray.js';
 import isArray from './lib/lodash/isArray.js';
 import utils from './utils.js';
 import utils from './utils.js';
 
 
@@ -33,7 +34,7 @@ export default class Editor {
 		 * editor creation and is not subject to be modified.
 		 * editor creation and is not subject to be modified.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @type {HTMLElement}
+		 * @member {HTMLElement}
 		 */
 		 */
 		this.element = element;
 		this.element = element;
 
 
@@ -45,23 +46,37 @@ export default class Editor {
 		 * instance itself.
 		 * instance itself.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @type {Config}
+		 * @member {Config}
 		 */
 		 */
-		this.config = new EditorConfig( config );
+		this.config = config = new EditorConfig( config );
 
 
 		/**
 		/**
 		 * The plugins loaded and in use by this editor instance.
 		 * The plugins loaded and in use by this editor instance.
 		 *
 		 *
 		 * @readonly
 		 * @readonly
-		 * @type {PluginCollection}
+		 * @member {PluginCollection}
 		 */
 		 */
 		this.plugins = new PluginCollection( this );
 		this.plugins = new PluginCollection( this );
 
 
+		/**
+		 * @readonly
+		 * @member {core.Locale} core.Editor#locale
+		 */
+		this.locale = new Locale( config.lang );
+
+		/**
+		 * Shorthand for {@link core.Locale#t}.
+		 *
+		 * @see core.Locale#t
+		 * @method t
+		 */
+		this.t = this.locale.t;
+
 		/**
 		/**
 		 * The chosen creator.
 		 * The chosen creator.
 		 *
 		 *
 		 * @protected
 		 * @protected
-		 * @type {Creator}
+		 * @member {core.Creator} core.Editor#_creator
 		 */
 		 */
 	}
 	}
 
 

+ 2 - 2
packages/ckeditor5-engine/src/editorui/editoruiview.js

@@ -15,8 +15,8 @@ import View from '../ui/view.js';
  */
  */
 
 
 export default class EditorUIView extends View {
 export default class EditorUIView extends View {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 
 		this._createBodyRegion();
 		this._createBodyRegion();
 
 

+ 62 - 0
packages/ckeditor5-engine/src/locale.js

@@ -0,0 +1,62 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * Represents the localization services.
+ *
+ * @memberOf core
+ */
+export default class Locale {
+	/**
+	 * Creates a new instance of the Locale class. {@link Foo#bar}
+	 *
+	 * @param {String} [lang='en'] The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+	 */
+	constructor( lang ) {
+		/**
+		 * The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+		 *
+		 * @readonly
+		 * @member {String} core.Locale#lang
+		 */
+		this.lang = lang || 'en';
+
+		/**
+		 * Translates the given string to the {@link #lang}. This method is also availble in {@link core.Editor#t} and
+		 * {@link core.ui.View#t}.
+		 *
+		 * The strings may contain placeholders (`%<index>`) for values which are passed as the second argument.
+		 * `<index>` is the index in the `values` array.
+		 *
+		 *		editor.t( 'Created file "%0" in %1ms.', [ fileName, timeTaken ] );
+		 *
+		 * This method's context is statically bound to Locale instance,
+		 * so it can be called as a function:
+		 *
+		 *		const t = this.t;
+		 *		t( 'Label' );
+		 *
+		 * @method t
+		 * @param {String} str The string to translate.
+		 * @param {String[]} values Values that should be used to interpolate the string.
+		 */
+		this.t = ( ...args ) => this._t( ...args );
+	}
+
+	/**
+	 * Base for the {@link #t} method.
+	 *
+	 * @private
+	 */
+	_t( str, values ) {
+		if ( values ) {
+			str = str.replace( /\%(\d+)/g, ( match, index ) => ( index < values.length ) ? values[ index ] : match );
+		}
+
+		return str;
+	}
+}

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

@@ -74,7 +74,7 @@ export default class ComponentFactory {
 		const component = this._components.get( name );
 		const component = this._components.get( name );
 
 
 		const model = component.model;
 		const model = component.model;
-		const view = new component.ViewClass( model );
+		const view = new component.ViewClass( model, this.editor.locale );
 		const controller = new component.ControllerClass( model, view, this.editor );
 		const controller = new component.ControllerClass( model, view, this.editor );
 
 
 		return controller;
 		return controller;

+ 38 - 17
packages/ckeditor5-engine/src/ui/view.js

@@ -27,19 +27,36 @@ export default class View {
 	 * Creates an instance of the {@link View} class.
 	 * Creates an instance of the {@link View} class.
 	 *
 	 *
 	 * @param {core.ui.Model} model (View)Model of this View.
 	 * @param {core.ui.Model} model (View)Model of this View.
+	 * @param {core.Locale} [locale] The {@link core.Editor#locale editor's locale} instance.
 	 */
 	 */
-	constructor( model ) {
+	constructor( model, locale ) {
 		/**
 		/**
 		 * Model of this view.
 		 * Model of this view.
 		 *
 		 *
-		 * @type {core.ui.Model}
+		 * @member {core.ui.Model} core.ui.View#model
 		 */
 		 */
-		this.model = model || null;
+		this.model = model;
+
+		/**
+		 * @readonly
+		 * @member {core.Locale} core.ui.View#locale
+		 */
+		this.locale = locale;
+
+		/**
+		 * Shorthand for {@link core.Locale#t}.
+		 *
+		 * Note: If locale instance hasn't been passed to the view this method may not be available.
+		 *
+		 * @see core.Locale#t
+		 * @method t
+		 */
+		this.t = locale && locale.t;
 
 
 		/**
 		/**
 		 * Regions of this view. See {@link core.ui.View#register}.
 		 * Regions of this view. See {@link core.ui.View#register}.
 		 *
 		 *
-		 * @type {Collection}
+		 * @member {core.Collection} core.ui.View#regions
 		 */
 		 */
 		this.regions = new Collection( {
 		this.regions = new Collection( {
 			idProperty: 'name'
 			idProperty: 'name'
@@ -48,38 +65,37 @@ export default class View {
 		/**
 		/**
 		 * Template of this view.
 		 * Template of this view.
 		 *
 		 *
-		 * @type {Object}
+		 * @member {Object} core.ui.View#template
 		 */
 		 */
-		this.template = null;
 
 
 		/**
 		/**
 		 * Region selectors of this view. See {@link core.ui.View#register}.
 		 * Region selectors of this view. See {@link core.ui.View#register}.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @type {Object}
+		 * @member {Object} core.ui.View#_regionSelectors
 		 */
 		 */
-		this._regionsSelectors = {};
+		this._regionSelectors = {};
 
 
 		/**
 		/**
 		 * Element of this view.
 		 * Element of this view.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @type {HTMLElement}
+		 * @member {HTMLElement} core.ui.View.#_element
 		 */
 		 */
-		this._element = null;
 
 
 		/**
 		/**
 		 * An instance of Template to generate {@link core.ui.View#_el}.
 		 * An instance of Template to generate {@link core.ui.View#_el}.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @type {Template}
+		 * @member {Template} core.ui.View#_template
 		 */
 		 */
-		this._template = null;
 	}
 	}
 
 
 	/**
 	/**
 	 * Element of this view. The element is rendered on first reference
 	 * Element of this view. The element is rendered on first reference
-	 * using {@link core.ui.View#template} definition and {@link core.ui.View#_template} object.
+	 * using {@link core.ui.View#template} definition.
+	 *
+	 * @type {HTMLElement}
 	 */
 	 */
 	get element() {
 	get element() {
 		if ( this._element ) {
 		if ( this._element ) {
@@ -112,6 +128,7 @@ export default class View {
 	 * is synchronized with {@link View#model}.
 	 * is synchronized with {@link View#model}.
 	 *
 	 *
 	 * @readonly
 	 * @readonly
+	 * @type core.ui.ViewModelBinding
 	 */
 	 */
 	get attributeBinder() {
 	get attributeBinder() {
 		if ( this._attributeBinder ) {
 		if ( this._attributeBinder ) {
@@ -194,6 +211,9 @@ export default class View {
 
 
 	/**
 	/**
 	 * Initializes the view.
 	 * Initializes the view.
+	 *
+	 * Note: {@link core.Controller} supports if a promise is returned by this method,
+	 * what means that view initialization may be asynchronous.
 	 */
 	 */
 	init() {
 	init() {
 		this._initRegions();
 		this._initRegions();
@@ -271,7 +291,7 @@ export default class View {
 			}
 			}
 		}
 		}
 
 
-		this._regionsSelectors[ regionName ] = regionSelector;
+		this._regionSelectors[ regionName ] = regionSelector;
 	}
 	}
 
 
 	/**
 	/**
@@ -334,12 +354,13 @@ export default class View {
 			this.element.remove();
 			this.element.remove();
 		}
 		}
 
 
-		this.model = this.regions = this.template = this._regionsSelectors = this._element = this._template = null;
+		this.model = this.regions = this.template = this.locale = this.t = null;
+		this._regionSelectors = this._element = this._template = null;
 	}
 	}
 
 
 	/**
 	/**
 	 * Initializes {@link core.ui.View#regions} of this view by passing a DOM element
 	 * Initializes {@link core.ui.View#regions} of this view by passing a DOM element
-	 * generated from {@link core.ui.View#_regionsSelectors} into {@link Region#init}.
+	 * generated from {@link core.ui.View#_regionSelectors} into {@link Region#init}.
 	 *
 	 *
 	 * @protected
 	 * @protected
 	 */
 	 */
@@ -347,7 +368,7 @@ export default class View {
 		let region, regionEl, regionSelector;
 		let region, regionEl, regionSelector;
 
 
 		for ( region of this.regions ) {
 		for ( region of this.regions ) {
-			regionSelector = this._regionsSelectors[ region.name ];
+			regionSelector = this._regionSelectors[ region.name ];
 
 
 			if ( typeof regionSelector == 'string' ) {
 			if ( typeof regionSelector == 'string' ) {
 				regionEl = this.element.querySelector( regionSelector );
 				regionEl = this.element.querySelector( regionSelector );

+ 27 - 0
packages/ckeditor5-engine/tests/_utils/locale-mock.js

@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * A replacement for the {@link core.Locale} class.
+ *
+ * @memberOf tests.core._utils
+ */
+export default class Locale {
+	constructor() {
+		this.t = ( str ) => `t( ${ str } )`;
+	}
+
+	/**
+	 * Injects instance of this class to the editor.
+	 *
+	 * @param {core.Editor} editor
+	 */
+	static inject( editor ) {
+		editor.locale = new Locale();
+		editor.t = editor.locale.t;
+	}
+}

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

@@ -8,8 +8,8 @@
 import EditorUIView from '/ckeditor5/core/editorui/editoruiview.js';
 import EditorUIView from '/ckeditor5/core/editorui/editoruiview.js';
 
 
 export default class BoxedEditorUIView extends EditorUIView {
 export default class BoxedEditorUIView extends EditorUIView {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 
 		this.template = {
 		this.template = {
 			tag: 'div',
 			tag: 'div',

+ 2 - 2
packages/ckeditor5-engine/tests/_utils/ui/editable/framed/framededitableview.js

@@ -8,8 +8,8 @@
 import EditableView from '/ckeditor5/core/editable/editableview.js';
 import EditableView from '/ckeditor5/core/editable/editableview.js';
 
 
 export default class FramedEditableView extends EditableView {
 export default class FramedEditableView extends EditableView {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 
 		const bind = this.attributeBinder;
 		const bind = this.attributeBinder;
 
 

+ 2 - 2
packages/ckeditor5-engine/tests/_utils/ui/editable/inline/inlineeditableview.js

@@ -8,8 +8,8 @@
 import EditableView from '/ckeditor5/core/editable/editableview.js';
 import EditableView from '/ckeditor5/core/editable/editableview.js';
 
 
 export default class InlineEditableView extends EditableView {
 export default class InlineEditableView extends EditableView {
-	constructor( model, editableElement ) {
-		super( model );
+	constructor( model, locale, editableElement ) {
+		super( model, locale );
 
 
 		this.element = editableElement;
 		this.element = editableElement;
 	}
 	}

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

@@ -8,8 +8,8 @@
 import ToolbarView from '/ckeditor5/ui/toolbar/toolbarview.js';
 import ToolbarView from '/ckeditor5/ui/toolbar/toolbarview.js';
 
 
 export default class FloatingToolbarView extends ToolbarView {
 export default class FloatingToolbarView extends ToolbarView {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 
 		const bind = this.attributeBinder;
 		const bind = this.attributeBinder;
 
 

+ 4 - 3
packages/ckeditor5-engine/tests/creator/manual/_utils/creator/classiccreator.js

@@ -49,7 +49,8 @@ export default class ClassicCreator extends Creator {
 	}
 	}
 
 
 	_setupToolbar() {
 	_setupToolbar() {
-		const toolbar = new Toolbar( new Model(), new ToolbarView(), this.editor );
+		const toolbarModel = new Model();
+		const toolbar = new Toolbar( toolbarModel, new ToolbarView( toolbarModel, this.editor.locale ), this.editor );
 
 
 		toolbar.addButtons( this.editor.config.toolbar );
 		toolbar.addButtons( this.editor.config.toolbar );
 
 
@@ -58,7 +59,7 @@ export default class ClassicCreator extends Creator {
 
 
 	_createEditable() {
 	_createEditable() {
 		const editable = new FramedEditable( this.editor );
 		const editable = new FramedEditable( this.editor );
-		const editableView = new FramedEditableView( editable.viewModel );
+		const editableView = new FramedEditableView( editable.viewModel, this.editor.locale );
 
 
 		editable.view = editableView;
 		editable.view = editableView;
 
 
@@ -67,7 +68,7 @@ export default class ClassicCreator extends Creator {
 
 
 	_createEditorUI() {
 	_createEditorUI() {
 		const editorUI = new BoxedEditorUI( this.editor );
 		const editorUI = new BoxedEditorUI( this.editor );
-		const editorUIView = new BoxedEditorUIView( editorUI.viewModel );
+		const editorUIView = new BoxedEditorUIView( editorUI.viewModel, this.editor.locale );
 
 
 		editorUI.view = editorUIView;
 		editorUI.view = editorUIView;
 
 

+ 6 - 4
packages/ckeditor5-engine/tests/creator/manual/_utils/creator/inlinecreator.js

@@ -47,11 +47,13 @@ export default class InlineCreator extends Creator {
 	}
 	}
 
 
 	_setupToolbar() {
 	_setupToolbar() {
+		const locale = this.editor.locale;
+
 		const toolbar1Model = new Model();
 		const toolbar1Model = new Model();
 		const toolbar2Model = new Model();
 		const toolbar2Model = new Model();
 
 
-		const toolbar1 = new FloatingToolbar( toolbar1Model, new FloatingToolbarView( toolbar1Model ), this.editor );
-		const toolbar2 = new FloatingToolbar( toolbar2Model, new FloatingToolbarView( toolbar2Model ), this.editor );
+		const toolbar1 = new FloatingToolbar( toolbar1Model, new FloatingToolbarView( toolbar1Model, locale ), this.editor );
+		const toolbar2 = new FloatingToolbar( toolbar2Model, new FloatingToolbarView( toolbar2Model, locale ), this.editor );
 
 
 		toolbar1.addButtons( this.editor.config.toolbar );
 		toolbar1.addButtons( this.editor.config.toolbar );
 		toolbar2.addButtons( this.editor.config.toolbar.reverse() );
 		toolbar2.addButtons( this.editor.config.toolbar.reverse() );
@@ -62,7 +64,7 @@ export default class InlineCreator extends Creator {
 
 
 	_createEditable() {
 	_createEditable() {
 		const editable = new InlineEditable( this.editor );
 		const editable = new InlineEditable( this.editor );
-		const editableView = new InlineEditableView( editable.viewModel, this.editor.element );
+		const editableView = new InlineEditableView( editable.viewModel, this.editor.locale, this.editor.element );
 
 
 		editable.view = editableView;
 		editable.view = editableView;
 
 
@@ -72,7 +74,7 @@ export default class InlineCreator extends Creator {
 	_createEditorUI() {
 	_createEditorUI() {
 		const editorUI = new BoxlessEditorUI( this.editor );
 		const editorUI = new BoxlessEditorUI( this.editor );
 
 
-		editorUI.view = new EditorUIView( editorUI.viewModel );
+		editorUI.view = new EditorUIView( editorUI.viewModel, this.editor.locale );
 
 
 		return editorUI;
 		return editorUI;
 	}
 	}

+ 4 - 2
packages/ckeditor5-engine/tests/creator/manual/_utils/imitatefeatures.js

@@ -15,10 +15,12 @@ import ButtonView from '/ckeditor5/ui/button/buttonview.js';
  * @param {core.Editor} editor
  * @param {core.Editor} editor
  */
  */
 export function imitateFeatures( editor ) {
 export function imitateFeatures( editor ) {
+	const t = editor.t;
+
 	const boldModel = new Model( {
 	const boldModel = new Model( {
 		isEnabled: true,
 		isEnabled: true,
 		isOn: false,
 		isOn: false,
-		label: 'bold'
+		label: t( 'Bold' )
 	} );
 	} );
 
 
 	boldModel.on( 'execute', () => {
 	boldModel.on( 'execute', () => {
@@ -33,7 +35,7 @@ export function imitateFeatures( editor ) {
 	const italicModel = new Model( {
 	const italicModel = new Model( {
 		isEnabled: true,
 		isEnabled: true,
 		isOn: false,
 		isOn: false,
-		label: 'italic'
+		label: t( 'Italic' )
 	} );
 	} );
 
 
 	italicModel.on( 'execute', () => {
 	italicModel.on( 'execute', () => {

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

@@ -5,7 +5,7 @@
   * Framed editor should be created.
   * Framed editor should be created.
   * It should be rectangular (400x400).
   * It should be rectangular (400x400).
   * Original element should disappear.
   * Original element should disappear.
-  * There should be a toolbar with "bold" and "italic" buttons.
+  * There should be a toolbar with "Bold" and "Italic" buttons.
 3. Click "Destroy editor".
 3. Click "Destroy editor".
 4. Expected:
 4. Expected:
   * Editor should be destroyed.
   * Editor should be destroyed.

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

@@ -4,8 +4,8 @@
 2. Expected:
 2. Expected:
   * Inline editor should be created.
   * Inline editor should be created.
   * There should be **two** toolbars:
   * There should be **two** toolbars:
-    * one with "bold" and "italic" buttons,
-    * second with "italic" and "bold" buttons.
+    * one with "Bold" and "Italic" buttons,
+    * second with "Italic" and "Bold" buttons.
 3. Click "Destroy editor".
 3. Click "Destroy editor".
 4. Expected:
 4. Expected:
   * Editor should be destroyed (the element should not be editable).
   * Editor should be destroyed (the element should not be editable).

+ 16 - 0
packages/ckeditor5-engine/tests/editor/editor.js

@@ -12,6 +12,7 @@ import coreTestUtils from '/tests/core/_utils/utils.js';
 import Editor from '/ckeditor5/core/editor.js';
 import Editor from '/ckeditor5/core/editor.js';
 import EditorConfig from '/ckeditor5/core/editorconfig.js';
 import EditorConfig from '/ckeditor5/core/editorconfig.js';
 import Plugin from '/ckeditor5/core/plugin.js';
 import Plugin from '/ckeditor5/core/plugin.js';
+import Locale from '/ckeditor5/core/locale.js';
 
 
 const pluginClasses = {};
 const pluginClasses = {};
 let element;
 let element;
@@ -51,6 +52,21 @@ describe( 'config', () => {
 	} );
 	} );
 } );
 } );
 
 
+describe( 'locale', () => {
+	it( 'is instantiated and t() is exposed', () => {
+		const editor = new Editor( element );
+
+		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( element, { lang: 'pl' } );
+
+		expect( editor.locale.lang ).to.equal( 'pl' );
+	} );
+} );
+
 describe( 'init', () => {
 describe( 'init', () => {
 	it( 'should return a promise that resolves properly', () => {
 	it( 'should return a promise that resolves properly', () => {
 		const editor = new Editor( element, {
 		const editor = new Editor( element, {

+ 64 - 0
packages/ckeditor5-engine/tests/locale.js

@@ -0,0 +1,64 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Locale from '/ckeditor5/core/locale.js';
+
+describe( 'Locale', () => {
+	let locale;
+
+	beforeEach( () => {
+		locale = new Locale();
+	} );
+
+	describe( 'constructor', () => {
+		it( 'sets the lang', () => {
+			const locale = new Locale( 'pl' );
+
+			expect( locale ).to.have.property( 'lang', 'pl' );
+		} );
+
+		it( 'defaults lang to en', () => {
+			const locale = new Locale();
+
+			expect( locale ).to.have.property( 'lang', 'en' );
+		} );
+	} );
+
+	describe( 't', () => {
+		it( 'has the context bound', () => {
+			const t = locale.t;
+
+			expect( t( 'Foo' ) ).to.equal( 'Foo' );
+		} );
+
+		it( 'interpolates 1 value', () => {
+			const t = locale.t;
+
+			expect( t( '%0 - %0', [ 'foo' ] ) ).to.equal( 'foo - foo' );
+		} );
+
+		it( 'interpolates 3 values', () => {
+			const t = locale.t;
+
+			expect( t( '%1 - %0 - %2', [ 'a', 'b', 'c' ] ) ).to.equal( 'b - a - c' );
+		} );
+
+		// Those test make sure that if %0 is really to be used, then it's going to work.
+		// It'd be a super rare case if one would need to use %0 and at the same time interpolate something.
+		it( 'does not interpolate placeholders if values not passed', () => {
+			const t = locale.t;
+
+			expect( t( '%1 - %0 - %2' ) ).to.equal( '%1 - %0 - %2' );
+		} );
+
+		it( 'does not interpolate those placeholders for which values has not been passed', () => {
+			const t = locale.t;
+
+			expect( t( '%1 - %0 - %2', [ 'a' ] ) ).to.equal( '%1 - a - %2' );
+		} );
+	} );
+} );

+ 94 - 78
packages/ckeditor5-engine/tests/ui/view.js

@@ -34,15 +34,23 @@ describe( 'View', () => {
 		} );
 		} );
 
 
 		it( 'defines basic view properties', () => {
 		it( 'defines basic view properties', () => {
-			view = new View();
+			const model = new Model();
 
 
-			expect( view.model ).to.be.null;
-			expect( view.regions.length ).to.be.equal( 0 );
-			expect( view.template ).to.be.null;
-			expect( view._regionsSelectors ).to.be.empty;
-			expect( view._element ).to.be.null;
-			expect( view._template ).to.be.null;
-			expect( view.element ).to.be.null;
+			view = new View( model );
+
+			expect( view.model ).to.equal( model );
+			expect( view.t ).to.be.undefined;
+			expect( view.regions.length ).to.equal( 0 );
+		} );
+
+		it( 'defines the locale property and the t function', () => {
+			const model = new Model();
+			const locale = { t() {} };
+
+			view = new View( model, locale );
+
+			expect( view.locale ).to.equal( locale );
+			expect( view.t ).to.equal( locale.t );
 		} );
 		} );
 	} );
 	} );
 
 
@@ -86,8 +94,8 @@ describe( 'View', () => {
 
 
 			view.init();
 			view.init();
 
 
-			expect( region1.element ).to.be.equal( view.element.firstChild );
-			expect( region2.element ).to.be.equal( view.element.lastChild );
+			expect( region1.element ).to.equal( view.element.firstChild );
+			expect( region2.element ).to.equal( view.element.lastChild );
 		} );
 		} );
 
 
 		it( 'initializes view regions with function selector', () => {
 		it( 'initializes view regions with function selector', () => {
@@ -101,8 +109,8 @@ describe( 'View', () => {
 
 
 			view.init();
 			view.init();
 
 
-			expect( region1.element ).to.be.equal( view.element.firstChild );
-			expect( region2.element ).to.be.equal( view.element.lastChild );
+			expect( region1.element ).to.equal( view.element.firstChild );
+			expect( region2.element ).to.equal( view.element.lastChild );
 		} );
 		} );
 
 
 		it( 'initializes view regions with boolean selector', () => {
 		it( 'initializes view regions with boolean selector', () => {
@@ -169,6 +177,13 @@ describe( 'View', () => {
 		} );
 		} );
 
 
 		it( 'should override an existing region with override flag', () => {
 		it( 'should override an existing region with override flag', () => {
+			view.template = {
+				tag: 'div',
+				children: [
+					{ tag: 'span' }
+				]
+			};
+
 			const region1 = new Region( 'x' );
 			const region1 = new Region( 'x' );
 			const region2 = new Region( 'x' );
 			const region2 = new Region( 'x' );
 
 
@@ -176,8 +191,10 @@ describe( 'View', () => {
 			view.register( region2, true, true );
 			view.register( region2, true, true );
 			view.register( 'x', 'span', true );
 			view.register( 'x', 'span', true );
 
 
-			expect( view.regions.get( 'x' ) ).to.be.equal( region2 );
-			expect( view._regionsSelectors.x ).to.be.equal( 'span' );
+			view.init();
+
+			expect( view.regions.get( 'x' ) ).to.equal( region2 );
+			expect( view.regions.get( 'x' ).element ).to.equal( view.element.firstChild );
 		} );
 		} );
 
 
 		it( 'should not override an existing region with the same region with override flag', () => {
 		it( 'should not override an existing region with the same region with override flag', () => {
@@ -198,7 +215,7 @@ describe( 'View', () => {
 			setTestViewInstance( { a: 1 } );
 			setTestViewInstance( { a: 1 } );
 
 
 			expect( view.element ).to.be.an.instanceof( HTMLElement );
 			expect( view.element ).to.be.an.instanceof( HTMLElement );
-			expect( view.element.nodeName ).to.be.equal( 'A' );
+			expect( view.element.nodeName ).to.equal( 'A' );
 		} );
 		} );
 
 
 		it( 'can be explicitly declared', () => {
 		it( 'can be explicitly declared', () => {
@@ -221,8 +238,8 @@ describe( 'View', () => {
 			const bind = view.attributeBinder;
 			const bind = view.attributeBinder;
 
 
 			expect( bind ).to.have.keys( 'to', 'if' );
 			expect( bind ).to.have.keys( 'to', 'if' );
-			expect( typeof bind.to ).to.be.equal( 'function' );
-			expect( typeof bind.if ).to.be.equal( 'function' );
+			expect( typeof bind.to ).to.equal( 'function' );
+			expect( typeof bind.if ).to.equal( 'function' );
 		} );
 		} );
 
 
 		describe( 'to', () => {
 		describe( 'to', () => {
@@ -254,10 +271,10 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 
 
 				view.model.foo = 'baz';
 				view.model.foo = 'baz';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="baz">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="baz">abc</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – simple (Text Node)', () => {
 			it( 'allows binding attribute to the model – simple (Text Node)', () => {
@@ -275,10 +292,10 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 
 
 				view.model.foo = 'baz';
 				view.model.foo = 'baz';
-				expect( view.element.outerHTML ).to.be.equal( '<p>baz</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>baz</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – value processing', () => {
 			it( 'allows binding attribute to the model – value processing', () => {
@@ -300,10 +317,10 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 3 } );
 				setTestViewInstance( { foo: 3 } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="positive">positive</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="positive">positive</p>' );
 
 
 				view.model.foo = -7;
 				view.model.foo = -7;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="negative">negative</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="negative">negative</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – value processing (use Node)', () => {
 			it( 'allows binding attribute to the model – value processing (use Node)', () => {
@@ -327,10 +344,10 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 3 } );
 				setTestViewInstance( { foo: 3 } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="HTMLElement positive"></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="HTMLElement positive"></p>' );
 
 
 				view.model.foo = -7;
 				view.model.foo = -7;
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – custom callback', () => {
 			it( 'allows binding attribute to the model – custom callback', () => {
@@ -352,10 +369,10 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'moo' } );
 				setTestViewInstance( { foo: 'moo' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="undefined">moo</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="undefined">moo</p>' );
 
 
 				view.model.foo = 'changed';
 				view.model.foo = 'changed';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="changed">changed</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="changed">changed</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – array of bindings (HTMLElement attribute)', () => {
 			it( 'allows binding attribute to the model – array of bindings (HTMLElement attribute)', () => {
@@ -378,11 +395,11 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'a', bar: 'b' } );
 				setTestViewInstance( { foo: 'a', bar: 'b' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="ck-class a b foo-is-a ck-end">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="ck-class a b foo-is-a ck-end">abc</p>' );
 
 
 				view.model.foo = 'c';
 				view.model.foo = 'c';
 				view.model.bar = 'd';
 				view.model.bar = 'd';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="ck-class c d foo-is-c ck-end">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="ck-class c d foo-is-c ck-end">abc</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – array of bindings (Text Node)', () => {
 			it( 'allows binding attribute to the model – array of bindings (Text Node)', () => {
@@ -408,11 +425,11 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'a', bar: 'b' } );
 				setTestViewInstance( { foo: 'a', bar: 'b' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>ck-class a b foo-is-a ck-end</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>ck-class a b foo-is-a ck-end</p>' );
 
 
 				view.model.foo = 'c';
 				view.model.foo = 'c';
 				view.model.bar = 'd';
 				view.model.bar = 'd';
-				expect( view.element.outerHTML ).to.be.equal( '<p>ck-class c d foo-is-c ck-end</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>ck-class c d foo-is-c ck-end</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – falsy values', () => {
 			it( 'allows binding attribute to the model – falsy values', () => {
@@ -429,22 +446,22 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="false">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="false">abc</p>' );
 
 
 				view.model.foo = null;
 				view.model.foo = null;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="null">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="null">abc</p>' );
 
 
 				view.model.foo = undefined;
 				view.model.foo = undefined;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="undefined">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="undefined">abc</p>' );
 
 
 				view.model.foo = 0;
 				view.model.foo = 0;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="0">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="0">abc</p>' );
 
 
 				view.model.foo = '';
 				view.model.foo = '';
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 			} );
 		} );
 		} );
 
 
@@ -478,13 +495,13 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: true } );
 				setTestViewInstance( { foo: true } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="">abc</p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = 'bar';
 				view.model.foo = 'bar';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="">abc</p>' );
 			} );
 			} );
 
 
 			// TODO: Is this alright? It makes sense but it's pretty useless. Text Node cannot be
 			// TODO: Is this alright? It makes sense but it's pretty useless. Text Node cannot be
@@ -504,13 +521,13 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: true } );
 				setTestViewInstance( { foo: true } );
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 
 
 				view.model.foo = 'bar';
 				view.model.foo = 'bar';
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – value of an attribute (HTMLElement attribute)', () => {
 			it( 'allows binding attribute to the model – value of an attribute (HTMLElement attribute)', () => {
@@ -527,13 +544,13 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = 64;
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – value of an attribute (Text Node)', () => {
 			it( 'allows binding attribute to the model – value of an attribute (Text Node)', () => {
@@ -551,13 +568,13 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 
 
 				view.model.foo = 64;
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback', () => {
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback', () => {
@@ -574,13 +591,13 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="there–is–no–foo">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="there–is–no–foo">abc</p>' );
 
 
 				view.model.foo = 64;
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback (use Node)', () => {
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback (use Node)', () => {
@@ -597,13 +614,13 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = 'P';
 				view.model.foo = 'P';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="eqls-tag-name">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="eqls-tag-name">abc</p>' );
 
 
 				view.model.foo = 64;
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 			} );
 
 
 			it( 'allows binding attribute to the model – falsy values', () => {
 			it( 'allows binding attribute to the model – falsy values', () => {
@@ -620,22 +637,22 @@ describe( 'View', () => {
 				} );
 				} );
 
 
 				setTestViewInstance( { foo: 'bar' } );
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="foo-is-set">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="foo-is-set">abc</p>' );
 
 
 				view.model.foo = false;
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = null;
 				view.model.foo = null;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = undefined;
 				view.model.foo = undefined;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = '';
 				view.model.foo = '';
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 
 				view.model.foo = 0;
 				view.model.foo = 0;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 			} );
 		} );
 		} );
 	} );
 	} );
@@ -872,9 +889,8 @@ describe( 'View', () => {
 			expect( view.model ).to.be.null;
 			expect( view.model ).to.be.null;
 			expect( view.regions ).to.be.null;
 			expect( view.regions ).to.be.null;
 			expect( view.template ).to.be.null;
 			expect( view.template ).to.be.null;
-			expect( view._regionsSelectors ).to.be.null;
-			expect( view._element ).to.be.null;
-			expect( view._template ).to.be.null;
+			expect( view.locale ).to.be.null;
+			expect( view.t ).to.be.null;
 		} );
 		} );
 
 
 		it( 'detaches the element from DOM', () => {
 		it( 'detaches the element from DOM', () => {
@@ -897,8 +913,8 @@ describe( 'View', () => {
 			view.regions.get( 'x' ).views.add( new View() );
 			view.regions.get( 'x' ).views.add( new View() );
 			view.destroy();
 			view.destroy();
 
 
-			expect( regionsRef.length ).to.be.equal( 0 );
-			expect( regionViewsRef.length ).to.be.equal( 0 );
+			expect( regionsRef.length ).to.equal( 0 );
+			expect( regionViewsRef.length ).to.equal( 0 );
 			expect( spy.calledOnce ).to.be.true;
 			expect( spy.calledOnce ).to.be.true;
 		} );
 		} );
 
 
@@ -919,15 +935,15 @@ describe( 'View', () => {
 			const modelRef = view.model;
 			const modelRef = view.model;
 			const elRef = view.element;
 			const elRef = view.element;
 
 
-			expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+			expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 
 
 			modelRef.foo = 'baz';
 			modelRef.foo = 'baz';
-			expect( view.element.outerHTML ).to.be.equal( '<p>baz</p>' );
+			expect( view.element.outerHTML ).to.equal( '<p>baz</p>' );
 
 
 			view.destroy();
 			view.destroy();
 
 
 			modelRef.foo = 'abc';
 			modelRef.foo = 'abc';
-			expect( elRef.outerHTML ).to.be.equal( '<p>baz</p>' );
+			expect( elRef.outerHTML ).to.equal( '<p>baz</p>' );
 		} );
 		} );
 
 
 		it( 'destroy a template–less view', () => {
 		it( 'destroy a template–less view', () => {
@@ -958,11 +974,11 @@ describe( 'View', () => {
 				}
 				}
 			} );
 			} );
 
 
-			expect( el.outerHTML ).to.be.equal( '<div class="42" id="foo" checked="checked"></div>' );
+			expect( el.outerHTML ).to.equal( '<div class="42" id="foo" checked="checked"></div>' );
 
 
 			view.model.b = 64;
 			view.model.b = 64;
 
 
-			expect( el.outerHTML ).to.be.equal( '<div class="64" id="foo" checked="checked"></div>' );
+			expect( el.outerHTML ).to.equal( '<div class="64" id="foo" checked="checked"></div>' );
 		} );
 		} );
 
 
 		it( 'should initialize DOM listeners', () => {
 		it( 'should initialize DOM listeners', () => {
@@ -999,7 +1015,7 @@ describe( 'View', () => {
 			el.appendChild( childA );
 			el.appendChild( childA );
 			el.appendChild( childB );
 			el.appendChild( childB );
 
 
-			expect( el.outerHTML ).to.be.equal( '<div><a>anchor</a><b>bold</b></div>' );
+			expect( el.outerHTML ).to.equal( '<div><a>anchor</a><b>bold</b></div>' );
 
 
 			const spy1 = testUtils.sinon.spy();
 			const spy1 = testUtils.sinon.spy();
 			const spy2 = testUtils.sinon.spy();
 			const spy2 = testUtils.sinon.spy();
@@ -1042,14 +1058,14 @@ describe( 'View', () => {
 				}
 				}
 			} );
 			} );
 
 
-			expect( el.outerHTML ).to.be.equal( '<div id="FOO" class="applied-parent-42">' +
+			expect( el.outerHTML ).to.equal( '<div id="FOO" class="applied-parent-42">' +
 				'<a class="applied-A-42" id="applied-A">Text applied to childA.</a>' +
 				'<a class="applied-A-42" id="applied-A">Text applied to childA.</a>' +
 				'<b class="applied-B-42" id="applied-B">Text applied to childB.</b>' +
 				'<b class="applied-B-42" id="applied-B">Text applied to childB.</b>' +
 			'</div>' );
 			'</div>' );
 
 
 			view.model.b = 16;
 			view.model.b = 16;
 
 
-			expect( el.outerHTML ).to.be.equal( '<div id="FOO" class="applied-parent-16">' +
+			expect( el.outerHTML ).to.equal( '<div id="FOO" class="applied-parent-16">' +
 				'<a class="applied-A-16" id="applied-A">Text applied to childA.</a>' +
 				'<a class="applied-A-16" id="applied-A">Text applied to childA.</a>' +
 				'<b class="applied-B-16" id="applied-B">Text applied to childB.</b>' +
 				'<b class="applied-B-16" id="applied-B">Text applied to childB.</b>' +
 			'</div>' );
 			'</div>' );
@@ -1087,8 +1103,8 @@ describe( 'View', () => {
 			view.applyTemplateToElement( el2, def );
 			view.applyTemplateToElement( el2, def );
 
 
 			// Attacher function didn't change because it's still the same View instance.
 			// Attacher function didn't change because it's still the same View instance.
-			expect( attacherFn ).to.be.equal( def.on.click );
-			expect( def.on._listenerView ).to.be.equal( view );
+			expect( attacherFn ).to.equal( def.on.click );
+			expect( def.on._listenerView ).to.equal( view );
 
 
 			document.body.appendChild( el1 );
 			document.body.appendChild( el1 );
 			document.body.appendChild( el2 );
 			document.body.appendChild( el2 );
@@ -1115,14 +1131,14 @@ describe( 'View', () => {
 			document.body.appendChild( el );
 			document.body.appendChild( el );
 
 
 			view1.applyTemplateToElement( el, def );
 			view1.applyTemplateToElement( el, def );
-			expect( def.on._listenerView ).to.be.equal( view1 );
+			expect( def.on._listenerView ).to.equal( view1 );
 
 
 			dispatchEvent( el, 'click' );
 			dispatchEvent( el, 'click' );
 			sinon.assert.calledOnce( spy );
 			sinon.assert.calledOnce( spy );
 
 
 			// Use another view1 to see if attachers are re–defined.
 			// Use another view1 to see if attachers are re–defined.
 			view2.applyTemplateToElement( el, def );
 			view2.applyTemplateToElement( el, def );
-			expect( def.on._listenerView ).to.be.equal( view2 );
+			expect( def.on._listenerView ).to.equal( view2 );
 
 
 			dispatchEvent( el, 'click' );
 			dispatchEvent( el, 'click' );
 			// Spy was called for view1 (1st dispatch), then for view1 and view2 (2nd dispatch).
 			// Spy was called for view1 (1st dispatch), then for view1 and view2 (2nd dispatch).