8
0
فهرست منبع

Merge branch 'master' into t/118b

Piotrek Koszuliński 10 سال پیش
والد
کامیت
7331f5f13c
64فایلهای تغییر یافته به همراه1170 افزوده شده و 1540 حذف شده
  1. 1 1
      packages/ckeditor5-engine/src/editorconfig.js
  2. 232 205
      packages/ckeditor5-engine/src/model.js
  3. 24 3
      packages/ckeditor5-engine/src/plugincollection.js
  4. 4 5
      packages/ckeditor5-engine/tests/.jshintrc
  5. 0 94
      packages/ckeditor5-engine/tests/_tools/tools.js
  6. 68 0
      packages/ckeditor5-engine/tests/_utils/utils.js
  7. 9 19
      packages/ckeditor5-engine/tests/ckeditor/ckeditor.js
  8. 1 7
      packages/ckeditor5-engine/tests/ckeditorerror.js
  9. 9 14
      packages/ckeditor5-engine/tests/collection.js
  10. 2 6
      packages/ckeditor5-engine/tests/config.js
  11. 15 19
      packages/ckeditor5-engine/tests/editor/creator.js
  12. 8 11
      packages/ckeditor5-engine/tests/editor/editor.js
  13. 2 5
      packages/ckeditor5-engine/tests/editorconfig.js
  14. 4 8
      packages/ckeditor5-engine/tests/emittermixin.js
  15. 1 7
      packages/ckeditor5-engine/tests/eventinfo.js
  16. 1 7
      packages/ckeditor5-engine/tests/lodash.js
  17. 2 4
      packages/ckeditor5-engine/tests/log.js
  18. 365 310
      packages/ckeditor5-engine/tests/model.js
  19. 3 5
      packages/ckeditor5-engine/tests/plugin.js
  20. 38 30
      packages/ckeditor5-engine/tests/plugincollection.js
  21. 36 0
      packages/ckeditor5-engine/tests/treemodel/_utils/utils.js
  22. 1 7
      packages/ckeditor5-engine/tests/treemodel/attribute.js
  23. 4 13
      packages/ckeditor5-engine/tests/treemodel/attributelist.js
  24. 3 13
      packages/ckeditor5-engine/tests/treemodel/batch.js
  25. 6 17
      packages/ckeditor5-engine/tests/treemodel/character.js
  26. 9 25
      packages/ckeditor5-engine/tests/treemodel/delta/attributedelta.js
  27. 3 12
      packages/ckeditor5-engine/tests/treemodel/delta/delta.js
  28. 3 15
      packages/ckeditor5-engine/tests/treemodel/delta/insertdelta.js
  29. 7 19
      packages/ckeditor5-engine/tests/treemodel/delta/mergedelta.js
  30. 7 19
      packages/ckeditor5-engine/tests/treemodel/delta/movedelta.js
  31. 6 17
      packages/ckeditor5-engine/tests/treemodel/delta/removedelta.js
  32. 7 19
      packages/ckeditor5-engine/tests/treemodel/delta/splitdelta.js
  33. 3 17
      packages/ckeditor5-engine/tests/treemodel/delta/unwrapdelta.js
  34. 3 13
      packages/ckeditor5-engine/tests/treemodel/delta/weakinsertdelta.js
  35. 5 19
      packages/ckeditor5-engine/tests/treemodel/delta/wrapdelta.js
  36. 9 31
      packages/ckeditor5-engine/tests/treemodel/document/change-event.js
  37. 4 15
      packages/ckeditor5-engine/tests/treemodel/document/document.js
  38. 6 17
      packages/ckeditor5-engine/tests/treemodel/element.js
  39. 5 16
      packages/ckeditor5-engine/tests/treemodel/liveposition.js
  40. 5 16
      packages/ckeditor5-engine/tests/treemodel/liverange.js
  41. 6 19
      packages/ckeditor5-engine/tests/treemodel/node.js
  42. 4 15
      packages/ckeditor5-engine/tests/treemodel/nodelist.js
  43. 11 29
      packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js
  44. 7 23
      packages/ckeditor5-engine/tests/treemodel/operation/insertoperation.js
  45. 6 20
      packages/ckeditor5-engine/tests/treemodel/operation/moveoperation.js
  46. 2 11
      packages/ckeditor5-engine/tests/treemodel/operation/nooperation.js
  47. 5 17
      packages/ckeditor5-engine/tests/treemodel/operation/reinsertoperation.js
  48. 5 17
      packages/ckeditor5-engine/tests/treemodel/operation/removeoperation.js
  49. 10 32
      packages/ckeditor5-engine/tests/treemodel/operation/transform.js
  50. 5 17
      packages/ckeditor5-engine/tests/treemodel/position.js
  51. 6 16
      packages/ckeditor5-engine/tests/treemodel/positioniterator.js
  52. 5 17
      packages/ckeditor5-engine/tests/treemodel/range.js
  53. 5 15
      packages/ckeditor5-engine/tests/treemodel/rootelement.js
  54. 13 28
      packages/ckeditor5-engine/tests/treemodel/selection.js
  55. 2 7
      packages/ckeditor5-engine/tests/treemodel/text.js
  56. 31 0
      packages/ckeditor5-engine/tests/treemodel/utils-tests/utils.js
  57. 16 33
      packages/ckeditor5-engine/tests/ui/controller.js
  58. 7 16
      packages/ckeditor5-engine/tests/ui/controllercollection.js
  59. 40 36
      packages/ckeditor5-engine/tests/ui/domemittermixin.js
  60. 2 7
      packages/ckeditor5-engine/tests/ui/region.js
  61. 15 21
      packages/ckeditor5-engine/tests/ui/template.js
  62. 22 33
      packages/ckeditor5-engine/tests/ui/view.js
  63. 23 24
      packages/ckeditor5-engine/tests/utils-tests/utils.js
  64. 1 7
      packages/ckeditor5-engine/tests/utils.js

+ 1 - 1
packages/ckeditor5-engine/src/editorconfig.js

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import CKEDITOR from '../ckeditor.js';
+import CKEDITOR from '../../ckeditor.js';
 import Config from './config.js';
 
 /**

+ 232 - 205
packages/ckeditor5-engine/src/model.js

@@ -177,7 +177,9 @@ export default class Model {
 			set: ( value ) => {
 				const oldValue = this._attributes[ name ];
 
-				if ( oldValue !== value ) {
+				// Allow undefined as an initial value like A.set( 'x', undefined ) (#132).
+				// Note: When _attributes has no such own property, then its value is undefined.
+				if ( oldValue !== value || !this._attributes.hasOwnProperty( name ) ) {
 					this._attributes[ name ] = value;
 					this.fire( 'change', name, value, oldValue );
 					this.fire( 'change:' + name, value, oldValue );
@@ -189,18 +191,18 @@ export default class Model {
 	}
 
 	/**
-	 * Binds model attributes to another Model instance.
+	 * Binds model attributes to another {@link Model} instance.
 	 *
 	 * Once bound, the model will immediately share the current state of attributes
 	 * of the model it is bound to and react to the changes to these attributes
 	 * in the future.
 	 *
-	 * To release the binding use {@link #unbind}.
+	 * **Note**: To release the binding use {@link #unbind}.
 	 *
 	 *		A.bind( 'a' ).to( B );
 	 *		A.bind( 'a' ).to( B, 'b' );
 	 *		A.bind( 'a', 'b' ).to( B, 'c', 'd' );
-	 *		A.bind( 'a' ).to( B, 'b' ).to( C, 'd' ).as( ( b, d ) => b + d );
+	 *		A.bind( 'a' ).to( B, 'b', C, 'd', ( b, d ) => b + d );
 	 *
 	 * @param {String...} bindAttrs Model attributes use that will be bound to another model(s).
 	 * @returns {BindChain}
@@ -237,22 +239,26 @@ export default class Model {
 
 		const bindings = {};
 
+		/**
+		 * @typedef Binding
+		 * @type Object
+		 * @property {Array} attr Attribute which is bound.
+		 * @property {Array} to Array of model–attribute components of the binding (`{ model: ..., attr: .. }`).
+		 * @property {Array} callback A function which processes `to` components.
+		 */
 		bindAttrs.forEach( a => {
-			this._boundAttributes[ a ] = bindings[ a ] = { model: this, attr: a, to: [] };
+			this._boundAttributes[ a ] = bindings[ a ] = { attr: a, to: [] };
 		} );
 
 		/**
 		 * @typedef BindChain
 		 * @type Object
 		 * @property {Function} to See {@link #_bindTo}.
-		 * @property {Function} as See {@link #_bindAs} (available after `to()` called in chain).
 		 * @property {Model} _model The model which initializes the binding.
 		 * @property {Array} _bindAttrs Array of `_model` attributes to be bound.
 		 * @property {Array} _to Array of `to()` model–attributes (`{ model: toModel, attrs: ...toAttrs }`).
 		 * @property {Object} _bindings Stores bindings to be kept in {@link #_boundAttributes}/{@link #_boundModels}
 		 * initiated in this binding chain.
-		 * @property {Function} _lastToModel A helper, retrieves `model` from last of `_to`.
-		 * @property {Function} _lastToAttrs A helper, retrieves `attrs` from last of `_to`.
 		 */
 		return {
 			to: this._bindTo,
@@ -260,15 +266,7 @@ export default class Model {
 			_model: this,
 			_bindAttrs: bindAttrs,
 			_to: [],
-			_bindings: bindings,
-
-			get _lastToModel() {
-				return this._to[ this._to.length - 1 ].model;
-			},
-
-			get _lastToAttrs() {
-				return this._to[ this._to.length - 1 ].attrs;
-			}
+			_bindings: bindings
 		};
 	}
 
@@ -297,7 +295,7 @@ export default class Model {
 				let toModel, toAttr, toAttrs, toAttrBindings;
 
 				binding.to.forEach( to => {
-					// TODO: Destructuring.
+					// TODO: ES6 destructuring.
 					toModel = to[ 0 ];
 					toAttr = to[ 1 ];
 					toAttrs = this._boundModels.get( toModel );
@@ -331,103 +329,81 @@ export default class Model {
 	 * A chaining for {@link #bind} providing `.to()` interface.
 	 *
 	 * @protected
-	 * @param {Model} model A model used for binding.
-	 * @param {String...} [toAttrs] Attributes of the model used for binding.
-	 * @returns {BindChain}
+	 * @param {...[Model|String|Function]} args Arguments of the `.to( args )` binding.
 	 */
-	_bindTo( toModel, ...toAttrs ) {
-		if ( !toModel || !( toModel instanceof Model ) ) {
-			/**
-			 * An instance of Model is required.
-			 *
-			 * @error model-bind-to-wrong-model
-			 */
-			throw new CKEditorError( 'model-bind-to-wrong-model: An instance of Model is required.' );
-		}
+	_bindTo( ...args ) {
+		const parsedArgs = parseBindToArgs( ...args );
+		const bindingsKeys = Object.keys( this._bindings );
+		const numberOfBindings = bindingsKeys.length;
 
-		if ( !isStringArray( toAttrs ) ) {
+		// Eliminate A.bind( 'x' ).to( B, C )
+		if ( !parsedArgs.callback && parsedArgs.to.length > 1 ) {
 			/**
-			 * Model attributes must be strings.
+			 * Binding multiple models only possible with callback.
 			 *
-			 * @error model-bind-to-wrong-attrs
+			 * @error model-bind-no-callback
 			 */
-			throw new CKEditorError( 'model-bind-to-wrong-attrs: Model attributes must be strings.' );
+			throw new CKEditorError( 'model-bind-to-no-callback: Binding multiple models only possible with callback.' ) ;
 		}
 
-		// Eliminate A.bind( 'x' ).to( B, 'y', 'z' )
-		// Eliminate A.bind( 'x', 'y' ).to( B, 'z' )
-		if ( toAttrs.length && toAttrs.length !== Object.keys( this._bindings ).length ) {
+		// Eliminate A.bind( 'x', 'y' ).to( B, callback )
+		if ( numberOfBindings > 1 && parsedArgs.callback ) {
 			/**
-			 * The number of attributes must match.
+			 * Cannot bind multiple attributes and use a callback in one binding.
 			 *
-			 * @error model-bind-to-attrs-length
+			 * @error model-bind-to-extra-callback
 			 */
-			throw new CKEditorError( 'model-bind-to-attrs-length: The number of attributes must match.' );
+			throw new CKEditorError( 'model-bind-to-extra-callback: Cannot bind multiple attributes and use a callback in one binding.' ) ;
 		}
 
-		// Eliminate A.bind( 'x' ).to( B, 'y' ), when B.y == undefined.
-		// Eliminate A.bind( 'x' ).to( B ), when B.x == undefined.
-		if ( !hasAttributes( toModel, toAttrs ) || ( !toAttrs.length && !hasAttributes( toModel, this._bindAttrs ) ) ) {
-			/**
-			 * Model has no such attribute(s).
-			 *
-			 * @error model-bind-to-missing-attr
-			 */
-			throw new CKEditorError( 'model-bind-to-missing-attr: Model has no such attribute(s).' );
-		}
-
-		// Eliminate A.bind( 'x', 'y' ).to( B ).to( C ) when no trailing .as().
-		// Eliminate A.bind( 'x', 'y' ).to( B, 'x', 'y' ).to( C, 'x', 'y' ).
-		if ( this._to.length && ( toAttrs.length > 1 || this._bindAttrs.length > 1 ) ) {
-			/**
-			 * Chaining only allowed for a single attribute.
-			 *
-			 * @error model-bind-to-chain-multiple-attrs
-			 */
-			throw new CKEditorError( 'model-bind-to-chain-multiple-attrs: Chaining only allowed for a single attribute.' );
-		}
+		parsedArgs.to.forEach( to => {
+			// Eliminate A.bind( 'x', 'y' ).to( B, 'a' )
+			if ( to.attrs.length && to.attrs.length !== numberOfBindings ) {
+				/**
+				 * The number of attributes must match.
+				 *
+				 * @error model-bind-to-attrs-length
+				 */
+				throw new CKEditorError( 'model-bind-to-attrs-length: The number of attributes must match.' );
+			}
 
-		// When no toAttrs specified, observing MODEL attributes, like MODEL.bind( 'foo' ).to( TOMODEL )
-		if ( !toAttrs.length ) {
-			toAttrs = this._bindAttrs;
-		}
+			// When no to.attrs specified, observing MODEL attributes instead.
+			if ( !to.attrs.length ) {
+				to.attrs = this._bindAttrs;
+			}
 
-		// Extend current chain with the new binding information.
-		this._to.push( { model: toModel, attrs: toAttrs } );
+			// Eliminate A.bind( 'x', 'y' ).to( B, 'a', 'b' ) when B has no 'a'.
+			if ( !hasAttributes( to.model, to.attrs ) ) {
+				/*
+				 * Model has no such attribute(s).
+				 *
+				 * @error model-bind-to-missing-attr
+				 */
+				throw new CKEditorError( 'model-bind-to-missing-attr: Model has no such attribute(s).' );
+			}
+		} );
 
-		setupBindToBinding( this );
+		this._to = parsedArgs.to;
 
-		if ( !this.as ) {
-			this.as = this._model._bindAs;
+		// Fill {@link BindChain#_bindings} with callback.
+		if ( parsedArgs.callback ) {
+			this._bindings[ bindingsKeys[ 0 ] ].callback = parsedArgs.callback;
 		}
 
-		return this;
-	}
-
-	/**
-	 * A chaining for {@link #bind} providing `.as()` interface.
-	 *
-	 * @protected
-	 * @param {Function} callback A callback to combine model's attributes.
-	 */
-	_bindAs( callback ) {
-		if ( !callback || typeof callback !== 'function' ) {
-			/**
-			 * Callback must be a Function.
-			 *
-			 * @error model-bind-as-wrong-callback
-			 */
-			throw new CKEditorError( 'model-bind-as-wrong-callback: Callback must be a Function.' );
-		}
+		attachBindToListeners( this._model, this._to );
 
-		this._model._boundAttributes[ this._bindAttrs[ 0 ] ].callback = this._callback = callback;
+		// Update model._boundAttributes and model._boundModels.
+		updateBindToBound( this );
 
-		updateModelAttrs( this._model, this._lastToModel, this._lastToAttrs[ 0 ] );
+		// Set initial values of bound attributes.
+		this._bindAttrs.forEach( attrName => {
+			updateBoundModelAttr( this._model, attrName );
+		} );
 	}
 }
 
 /**
- * Check if the `model` has given `attrs`.
+ * Check if the {@link Model} has given `attrs`.
  *
  * @private
  * @param {Model} model Model to be checked.
@@ -450,151 +426,202 @@ function isStringArray( arr ) {
 }
 
 /**
- * Synchronizes `chain._model._boundAttributes` and `chain._model._boundModels`
- * with `chain`.
+ * Parses and validates {@link Model#bind}`.to( args )` arguments and returns
+ * an object with a parsed structure. For example
+ *
+ *		A.bind( 'x' ).to( B, 'a', C, 'b', call );
+ *
+ * becomes
+ *
+ *		{
+ *			to: [
+ *				{ model: B, attrs: [ 'a' ] },
+ *				{ model: C, attrs: [ 'b' ] },
+ *			],
+ *			callback: call
+ * 		}
  *
  * @private
- * @param {BindChain} chain The binding initialized by {@link Model#bind}.
+ * @param {...*} args Arguments of {@link Model#bind}`.to( args )`.
+ * @returns {Object}
  */
-function updateBoundAttributesAndModels( chain ) {
-	const lastToModel = chain._lastToModel;
-	const lastToAttrs = chain._lastToAttrs;
+function parseBindToArgs( ...args ) {
+	// Eliminate A.bind( 'x' ).to()
+	if ( !args.length ) {
+		/**
+		 * Invalid argument syntax in `to()`.
+		 *
+		 * @error model-bind-to-parse-error
+		 */
+		throw new CKEditorError( 'model-bind-to-parse-error: Invalid argument syntax in `to()`.' );
+	}
 
-	let lastBoundAttr, bindingsToLastModel, bindings, binding;
+	const parsed = { to: [] };
+	let lastModel;
+
+	args.forEach( a => {
+		// Callback has already been defined.
+		// Eliminate A.bind( 'x' ).to( B, 'a', callback, C )
+		if ( parsed.callback ) {
+			throw new CKEditorError( 'model-bind-to-parse-error: Invalid argument syntax in `to()`.' );
+		} else if ( a instanceof Model ) {
+			parsed.to.push( ( lastModel = { model: a, attrs: [] } ) );
+		} else if ( typeof a == 'string' ) {
+			lastModel.attrs.push( a );
+		} else if ( typeof a == 'function' ) {
+			parsed.callback = a;
+		}
+		// Eliminate A.bind( 'x' ).to( null, new Date(), etc. )
+		else {
+			throw new CKEditorError( 'model-bind-to-parse-error: Invalid argument syntax in `to()`.' );
+		}
+	} );
 
-	// Assuming the following binding being created
-	//
-	// 		A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y' );
-	//
-	// the following bindings were initialized in `Model#bind` in `chain._bindings`:
-	//
-	// 		{
-	// 			a: { model: A, attr: 'a', to: [] },
-	// 			b: { model: A, attr: 'b', to: [] },
-	// 		}
-	//
-	// Iterate over all bindings in this chain and fill their `to` properties with
-	// the latest to( ... ) call arguments.
-	for ( let attrName in chain._bindings ) {
-		binding = chain._bindings[ attrName ];
+	return parsed;
+}
 
-		// Update `to` property, so the bindings are:
-		//
-		// 		a: { model: A, attr: 'a', to: [ [ B, 'x' ] ] },
-		//
-		//	and
-		//
-		// 		b: { model: A, attr: 'b', to: [ [ B, 'y' ] ] },
-		//
-		// But since `chain._bindings` and `chain._model._boundAttributes` share
-		// the instances of the bindings, a model is also updated.
-		lastBoundAttr = lastToAttrs[ chain._bindAttrs.indexOf( attrName ) ];
-		binding.to.push( [ lastToModel, lastBoundAttr ] );
-
-		// Update the structure of `chain._model._boundModels` with updated
-		// binding, so:
-		//
-		// 		chain._model._boundModels == Map( {
-		// 			B: {
-		// 				x: Set( [
-		// 					{ model: A, attr: 'a', to: [ [ B, 'x' ] ] }
-		// 				] ),
-		// 				y: Set( [
-		// 					{ model: A, attr: 'b', to: [ [ B, 'y' ] ] },
-		// 				] )
-		//			}
-		// 		} )
-		//
-		bindingsToLastModel = chain._model._boundModels.get( lastToModel );
-		bindings = bindingsToLastModel || {};
-
-		if ( !bindings[ lastBoundAttr ] ) {
-			bindings[ lastBoundAttr ] = new Set();
-		}
+/**
+ * Synchronizes {@link Model#_boundModels} with {@link Binding}.
+ *
+ * @private
+ * @param {Binding} binding A binding to store in {@link Model#_boundModels}.
+ * @param {Model} toModel A model, which is a new component of `binding`.
+ * @param {String} toAttrName A name of `toModel`'s attribute, a new component of the `binding`.
+ */
+function updateBoundModels( model, binding, toModel, toAttrName ) {
+	const bindingsToModel = model._boundModels.get( toModel );
+	const bindings = bindingsToModel || {};
 
-		// Pass the binding to a corresponding Set in `chain._model._boundModels`.
-		bindings[ lastBoundAttr ].add( binding );
+	if ( !bindings[ toAttrName ] ) {
+		bindings[ toAttrName ] = new Set();
+	}
 
-		if ( !bindingsToLastModel ) {
-			chain._model._boundModels.set( lastToModel, bindings );
-		}
+	// Pass the binding to a corresponding Set in `model._boundModels`.
+	bindings[ toAttrName ].add( binding );
+
+	if ( !bindingsToModel ) {
+		model._boundModels.set( toModel, bindings );
 	}
 }
 
 /**
- * Updates all bound attributes of `updateModel` with the `value` of `attrName`
- * of `withModel` model.
+ * Synchronizes {@link Model#_boundAttributes} and {@link Model#_boundModels}
+ * with {@link BindChain}.
+ *
+ * Assuming the following binding being created
+ *
+ * 		A.bind( 'a', 'b' ).to( B, 'x', 'y' );
+ *
+ * the following bindings were initialized by {@link Model#bind} in {@link BindChain#_bindings}:
+ *
+ * 		{
+ * 			a: { model: A, attr: 'a', to: [] },
+ * 			b: { model: A, attr: 'b', to: [] },
+ * 		}
+ *
+ * Iterate over all bindings in this chain and fill their `to` properties with
+ * corresponding to( ... ) arguments (components of the binding), so
  *
- *		// Given that A == updateModel and B == withModel and B.x has just changed.
- *		A.bind( 'a', 'b', 'c' ).to( B, 'x', 'y', 'x' );
+ * 		{
+ * 			a: { model: A, attr: 'a', to: [ B, 'x' ] },
+ * 			b: { model: A, attr: 'b', to: [ B, 'y' ] },
+ * 		}
  *
- *		// The following is updated
- *		A.a = A.c = B.x;
+ * Then update the structure of {@link Model#_boundModels} with updated
+ * binding, so it becomes:
+ *
+ * 		Map( {
+ * 			B: {
+ * 				x: Set( [
+ * 					{ model: A, attr: 'a', to: [ [ B, 'x' ] ] }
+ * 				] ),
+ * 				y: Set( [
+ * 					{ model: A, attr: 'b', to: [ [ B, 'y' ] ] },
+ * 				] )
+ *			}
+ * 		} )
  *
  * @private
- * @param {Model} updateModel The model to be updated.
- * @param {Model} withModel The model to be be used as a source.
- * @param {String} attrName One of the attributes of `withModel`.
- * @param {*} value The value of the attribute.
+ * @param {BindChain} chain The binding initialized by {@link Model#bind}.
  */
-function updateModelAttrs( updateModel, withModel, attrName, value ) {
-	const bindings = updateModel._boundModels.get( withModel )[ attrName ];
-	let attrValue;
+function updateBindToBound( chain ) {
+	let binding, toAttr;
 
-	if ( bindings ) {
-		bindings.forEach( binding => {
-			attrValue = value;
-
-			// A.bind( 'a' ).to( B, 'b' ).to( C, 'c' ).as( callback );
-			//  \-> Collect B.b and C.c and pass the values to callback to set A.a.
-			if ( binding.callback ) {
-				attrValue = binding.callback.apply(
-					binding.model,
-					binding.to.map( bound => {
-						return bound[ 0 ][ bound[ 1 ] ];
-					} )
-				);
-			}
+	for ( let attrName in chain._bindings ) {
+		binding = chain._bindings[ attrName ];
 
-			// A.bind( 'a' ).to( B )[ .to( N ) ];
-			//  \-> If multiple .to() models but **no** .as( callback ), then the binding is invalid.
-			else if ( binding.to.length > 1 ) {
-				attrValue = undefined;
-			}
+		// Note: For a binding without a callback, this will run only once
+		// like in A.bind( 'x', 'y' ).to( B, 'a', 'b' )
+		// TODO: ES6 destructuring.
+		chain._to.forEach( to => {
+			toAttr = to.attrs[ binding.callback ? 0 : chain._bindAttrs.indexOf( attrName ) ];
 
-			// TODO: Needs update after https://github.com/ckeditor/ckeditor5-core/issues/132.
-			if ( binding.model.hasOwnProperty( binding.attr ) ) {
-				binding.model[ binding.attr ] = attrValue;
-			} else {
-				binding.model.set( binding.attr, attrValue );
-			}
+			binding.to.push( [ to.model, toAttr ] );
+			updateBoundModels( chain._model, binding, to.model, toAttr );
 		} );
 	}
 }
 
 /**
- * Starts listening to changes in `chain._lastToModel` to update `chain._model`
- * attributes. Also sets the initial state of `chain._model` bound attributes.
+ * Updates an attribute of a {@link Model} with a value
+ * determined by an entry in {@link Model#_boundAttributes}.
  *
  * @private
- * @param {BindChain} chain The chain initialized by {@link Model#bind}.
+ * @param {Model} model A model which attribute is to be updated.
+ * @param {String} attrName An attribute to be updated.
  */
-function setupBindToBinding( chain ) {
-	const lastToModel = chain._lastToModel;
-
-	// If there's already a chain between the models (`chain._model` listens to
-	// `chain._lastToModel`), there's no need to create another `change` event listener.
-	if ( !chain._model._boundModels.get( lastToModel ) ) {
-		chain._model.listenTo( lastToModel, 'change', ( evt, ...rest ) => {
-			updateModelAttrs( chain._model, lastToModel, ...rest );
-		} );
+function updateBoundModelAttr( model, attrName ) {
+	const binding = model._boundAttributes[ attrName ];
+	let attrValue;
+
+	// When a binding with callback is created like
+	//
+	// 		A.bind( 'a' ).to( B, 'b', C, 'c', callback );
+	//
+	// collect B.b and C.c, then pass them to callback to set A.a.
+	if ( binding.callback ) {
+		attrValue = binding.callback.apply( model, binding.to.map( to => to[ 0 ][ to[ 1 ] ] ) );
+	} else {
+		attrValue = binding.to[ 0 ];
+		attrValue = attrValue[ 0 ][ attrValue[ 1 ] ];
 	}
 
-	updateBoundAttributesAndModels( chain );
+	// TODO: Needs update after https://github.com/ckeditor/ckeditor5-core/issues/132.
+	if ( model.hasOwnProperty( attrName ) ) {
+		model[ attrName ] = attrValue;
+	} else {
+		model.set( attrName, attrValue );
+	}
+}
 
-	// Synchronize initial state of `chain._model` with `chain._lastToModel`.
-	chain._lastToAttrs.forEach( attrName => {
-		updateModelAttrs( chain._model, lastToModel, attrName, lastToModel[ attrName ] );
+/**
+ * Starts listening to changes in {@link BindChain._to} models to update
+ * {@link BindChain._model} {@link BindChain._bindAttrs}. Also sets the
+ * initial state of {@link BindChain._model}.
+ *
+ * @private
+ * @param {BindChain} chain The chain initialized by {@link Model#bind}.
+ */
+function attachBindToListeners( model, toBindings ) {
+	toBindings.forEach( to => {
+		const boundModels = model._boundModels;
+		let bindings;
+
+		// If there's already a chain between the models (`model` listens to
+		// `to.model`), there's no need to create another `change` event listener.
+		if ( !boundModels.get( to.model ) ) {
+			model.listenTo( to.model, 'change', ( evt, attrName ) => {
+				bindings = boundModels.get( to.model )[ attrName ];
+
+				// Note: to.model will fire for any attribute change, react
+				// to changes of attributes which are bound only.
+				if ( bindings ) {
+					bindings.forEach( binding => {
+						updateBoundModelAttr( model, binding.attr );
+					} );
+				}
+			} );
+		}
 	} );
 }
 

+ 24 - 3
packages/ckeditor5-engine/src/plugincollection.js

@@ -5,11 +5,10 @@
 
 'use strict';
 
-import pathUtils from '../ckeditor5/path.js';
 import Plugin from './plugin.js';
 import CKEditorError from './ckeditorerror.js';
 import log from './log.js';
-import load from '../ckeditor5/load.js';
+import load from '../load.js';
 
 /**
  * Manages a list of CKEditor plugins, including loading, resolving dependencies and initialization.
@@ -98,7 +97,7 @@ export default class PluginCollection {
 		}
 
 		function loadPluginByName( pluginName ) {
-			return load( pathUtils.getModulePath( pluginName ) )
+			return load( PluginCollection.getPluginPath( pluginName ) )
 				.then( ( PluginModule ) => {
 					return loadPluginByClass( PluginModule.default, pluginName );
 				} );
@@ -143,6 +142,28 @@ export default class PluginCollection {
 		}
 	}
 
+	/**
+	 * Resolves a simplified plugin name to a real path. The returned
+	 * paths are relative to the main `ckeditor.js` file, but they do not start with `./`.
+	 *
+	 * For instance:
+	 *
+	 * * `foo` will be transformed to `ckeditor5/foo/foo.js`,
+	 * * `core/editor` to `ckeditor5/core/editor.js` and
+	 * * `foo/bar/bom` to `ckeditor5/foo/bar/bom.js`.
+	 *
+	 * @param {String} name
+	 * @returns {String} Path to the module.
+	 */
+	static getPluginPath( name ) {
+		// Resolve shortened feature names to `featureName/featureName`.
+		if ( name.indexOf( '/' ) < 0 ) {
+			name = name + '/' + name;
+		}
+
+		return 'ckeditor5/' + name + '.js';
+	}
+
 	/**
 	 * Adds the plugin to the collection. Exposed mainly for testing purposes.
 	 *

+ 4 - 5
packages/ckeditor5-engine/tests/.jshintrc

@@ -15,14 +15,13 @@
 	"globalstrict": true,
 
 	"globals": {
-		"bender": false,
-		"describe": false,
-		"it": false,
-		"before": false,
-		"beforeEach": false,
 		"after": false,
 		"afterEach": false,
+		"before": false,
+		"beforeEach": false,
+		"describe": false,
 		"expect": false,
+		"it": false,
 		"sinon": false
 	}
 }

+ 0 - 94
packages/ckeditor5-engine/tests/_tools/tools.js

@@ -1,94 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-( () => {
-	bender.tools.core = {
-		/**
-		 * Defines CKEditor plugin which is a mock of an editor creator.
-		 *
-		 * If `proto` is not set or it does not define `create()` and `destroy()` methods,
-		 * then they will be set to Sinon spies. Therefore the shortest usage is:
-		 *
-		 *		bender.tools.defineEditorCreatorMock( 'test1' );
-		 *
-		 * The mocked creator is available under:
-		 *
-		 *		editor.plugins.get( 'creator-thename' );
-		 *
-		 * @param {String} creatorName Name of the creator.
-		 * @param {Object} [proto] Prototype of the creator. Properties from the proto param will
-		 * be copied to the prototype of the creator.
-		 */
-		defineEditorCreatorMock( creatorName, proto ) {
-			bender.amd.define( 'creator-' + creatorName, [ 'core/creator' ], ( Creator ) => {
-				class TestCreator extends Creator {}
-
-				if ( proto ) {
-					for ( let propName in proto ) {
-						TestCreator.prototype[ propName ] = proto[ propName ];
-					}
-				}
-
-				if ( !TestCreator.prototype.create ) {
-					TestCreator.prototype.create = sinon.spy().named( creatorName + '-create' );
-				}
-
-				if ( !TestCreator.prototype.destroy ) {
-					TestCreator.prototype.destroy = sinon.spy().named( creatorName + '-destroy' );
-				}
-
-				return TestCreator;
-			} );
-		},
-
-		/**
-		 * Returns the number of elements return by the iterator.
-		 *
-		 *		bender.tools.core.getIteratorCount( [ 1, 2, 3, 4, 5 ] ); // 5;
-		 *
-		 * @param {Iterable.<*>} iterator Any iterator.
-		 * @returns {Number} Number of elements returned by that iterator.
-		 */
-		getIteratorCount( iterator ) {
-			let count = 0;
-
-			for ( let _ of iterator ) { // jshint ignore:line
-				count++;
-			}
-
-			return count;
-		}
-	};
-
-	bender.tools.treemodel = {
-		/**
-		 * Returns tree structure as a simplified string. Elements are uppercase and characters are lowercase.
-		 * Start and end of an element is marked the same way, by the element's name (in uppercase).
-		 *
-		 *		let element = new Element( 'div', [], [ 'abc', new Element( 'p', [], 'foo' ), 'xyz' ] );
-		 *		bender.tools.treemodel.getNodesAndText( element ); // abcPfooPxyz
-		 *
-		 * @param {treeModel.Range} range Range to stringify.
-		 * @returns {String} String representing element inner structure.
-		 */
-		getNodesAndText: ( range ) => {
-			let txt = '';
-
-			for ( let step of range ) {
-				let node = step.node;
-
-				if ( node.character ) {
-					txt += node.character.toLowerCase();
-				} else if ( node.name ) {
-					txt += node.name.toUpperCase();
-				}
-			}
-
-			return txt;
-		}
-	};
-} )();

+ 68 - 0
packages/ckeditor5-engine/tests/_utils/utils.js

@@ -0,0 +1,68 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import amdUtils from '/tests/_utils/amd.js';
+
+const utils = {
+	/**
+	 * Defines CKEditor plugin which is a mock of an editor creator.
+	 *
+	 * If `proto` is not set or it does not define `create()` and `destroy()` methods,
+	 * then they will be set to Sinon spies. Therefore the shortest usage is:
+	 *
+	 *		testUtils.defineEditorCreatorMock( 'test1' );
+	 *
+	 * The mocked creator is available under:
+	 *
+	 *		editor.plugins.get( 'creator-thename' );
+	 *
+	 * @param {String} creatorName Name of the creator.
+	 * @param {Object} [proto] Prototype of the creator. Properties from the proto param will
+	 * be copied to the prototype of the creator.
+	 */
+	defineEditorCreatorMock( creatorName, proto ) {
+		amdUtils.define( 'creator-' + creatorName, [ 'core/creator' ], ( Creator ) => {
+			class TestCreator extends Creator {}
+
+			if ( proto ) {
+				for ( let propName in proto ) {
+					TestCreator.prototype[ propName ] = proto[ propName ];
+				}
+			}
+
+			if ( !TestCreator.prototype.create ) {
+				TestCreator.prototype.create = sinon.spy().named( creatorName + '-create' );
+			}
+
+			if ( !TestCreator.prototype.destroy ) {
+				TestCreator.prototype.destroy = sinon.spy().named( creatorName + '-destroy' );
+			}
+
+			return TestCreator;
+		} );
+	},
+
+	/**
+	 * Returns the number of elements return by the iterator.
+	 *
+	 *		testUtils.getIteratorCount( [ 1, 2, 3, 4, 5 ] ); // 5;
+	 *
+	 * @param {Iterable.<*>} iterator Any iterator.
+	 * @returns {Number} Number of elements returned by that iterator.
+	 */
+	getIteratorCount( iterator ) {
+		let count = 0;
+
+		for ( let _ of iterator ) { // jshint ignore:line
+			count++;
+		}
+
+		return count;
+	}
+};
+
+export default utils;

+ 9 - 19
packages/ckeditor5-engine/tests/ckeditor/ckeditor.js

@@ -3,21 +3,22 @@
  * For licensing, see LICENSE.md.
  */
 
-/* bender-include: ../_tools/tools.js */
-
 'use strict';
 
-const modules = bender.amd.require( 'ckeditor', 'core/editor', 'core/config' );
+import testUtils from '/tests/_utils/utils.js';
+import coreTestUtils from '/tests/core/_utils/utils.js';
+
+import CKEDITOR from '/ckeditor.js';
+import Editor from '/ckeditor5/core/editor.js';
+import Config from '/ckeditor5/core/config.js';
 
 let content = document.getElementById( 'content' );
 let editorConfig = { creator: 'creator-test' };
 
-bender.tools.createSinonSandbox();
-bender.tools.core.defineEditorCreatorMock( 'test' );
+testUtils.createSinonSandbox();
+coreTestUtils.defineEditorCreatorMock( 'test' );
 
 beforeEach( () => {
-	const CKEDITOR = modules.ckeditor;
-
 	// Destroy all editor instances.
 	while ( CKEDITOR.instances.length ) {
 		CKEDITOR.instances.get( 0 ).destroy();
@@ -25,14 +26,6 @@ beforeEach( () => {
 } );
 
 describe( 'create', () => {
-	let CKEDITOR, Editor, Config;
-
-	before( () => {
-		CKEDITOR = modules.ckeditor;
-		Editor = modules[ 'core/editor' ];
-		Config = modules[ 'core/config' ];
-	} );
-
 	it( 'should return a promise', () => {
 		expect( CKEDITOR.create( content, editorConfig ) ).to.be.instanceof( Promise );
 	} );
@@ -91,7 +84,7 @@ describe( 'create', () => {
 	} );
 
 	it( 'should be rejected on element not found', () => {
-		let addSpy = bender.sinon.spy( CKEDITOR.instances, 'add' );
+		let addSpy = testUtils.sinon.spy( CKEDITOR.instances, 'add' );
 
 		return CKEDITOR.create( '.undefined' ).then( () => {
 			throw new Error( 'It should not enter this function' );
@@ -107,9 +100,6 @@ describe( 'create', () => {
 
 describe( 'config', () => {
 	it( 'should be an instance of Config', () => {
-		const CKEDITOR = modules.ckeditor;
-		const Config = modules[ 'core/config' ];
-
 		expect( CKEDITOR.config ).to.be.an.instanceof( Config );
 	} );
 } );

+ 1 - 7
packages/ckeditor5-engine/tests/ckeditorerror.js

@@ -5,15 +5,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/ckeditorerror' );
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'CKEditorError', () => {
-	let CKEditorError;
-
-	before( () => {
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	it( 'inherits from Error', () => {
 		let error = new CKEditorError( 'foo' );
 

+ 9 - 14
packages/ckeditor5-engine/tests/collection.js

@@ -5,9 +5,12 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/collection', 'core/ckeditorerror', 'core/utils' );
+import testUtils from '/tests/_utils/utils.js';
+import Collection from '/ckeditor5/core/collection.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+import utils from '/ckeditor5/core/utils.js';
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 function getItem( id, idProperty ) {
 	idProperty = idProperty || 'id';
@@ -18,14 +21,6 @@ function getItem( id, idProperty ) {
 }
 
 describe( 'Collection', () => {
-	let Collection, CKEditorError, utils;
-
-	before( () => {
-		Collection = modules[ 'core/collection' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-		utils = modules[ 'core/utils' ];
-	} );
-
 	let collection;
 
 	beforeEach( () => {
@@ -150,7 +145,7 @@ describe( 'Collection', () => {
 			() => {
 				let nextUid = 0;
 
-				bender.sinon.stub( utils, 'uid', () => {
+				testUtils.sinon.stub( utils, 'uid', () => {
 					return nextUid++;
 				} );
 
@@ -440,7 +435,7 @@ describe( 'Collection', () => {
 
 	describe( 'map', () => {
 		it( 'uses native map', () => {
-			let spy = bender.sinon.stub( Array.prototype, 'map', () => {
+			let spy = testUtils.sinon.stub( Array.prototype, 'map', () => {
 				return [ 'foo' ];
 			} );
 			let ctx = {};
@@ -458,7 +453,7 @@ describe( 'Collection', () => {
 		it( 'uses native find', () => {
 			let needl = getItem( 'foo' );
 
-			let spy = bender.sinon.stub( Array.prototype, 'find', () => {
+			let spy = testUtils.sinon.stub( Array.prototype, 'find', () => {
 				return needl;
 			} );
 			let ctx = {};
@@ -476,7 +471,7 @@ describe( 'Collection', () => {
 		it( 'uses native filter', () => {
 			let needl = getItem( 'foo' );
 
-			let spy = bender.sinon.stub( Array.prototype, 'filter', () => {
+			let spy = testUtils.sinon.stub( Array.prototype, 'filter', () => {
 				return [ needl ];
 			} );
 			let ctx = {};

+ 2 - 6
packages/ckeditor5-engine/tests/config.js

@@ -5,13 +5,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/config' );
+import Config from '/ckeditor5/core/config.js';
 
-let Config, config;
-
-before( () => {
-	Config = modules[ 'core/config' ];
-} );
+let config;
 
 beforeEach( () => {
 	config = new Config( {

+ 15 - 19
packages/ckeditor5-engine/tests/editor/creator.js

@@ -5,11 +5,14 @@
 
 'use strict';
 
-/* bender-include: ../_tools/tools.js */
+import amdUtils from '/tests/_utils/amd.js';
+import testUtils from '/tests/_utils/utils.js';
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Editor from '/ckeditor5/core/editor.js';
+import Creator from '/ckeditor5/core/creator.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const modules = bender.amd.require( 'core/editor', 'core/plugin', 'core/creator', 'core/ckeditorerror' );
 let editor, element;
-let Editor, Plugin, Creator, CKEditorError;
 
 function initEditor( config ) {
 	element = document.createElement( 'div' );
@@ -20,29 +23,22 @@ function initEditor( config ) {
 	return editor.init();
 }
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 before( () => {
-	Editor = modules[ 'core/editor' ];
-	Plugin = modules[ 'core/plugin' ];
-	Creator = modules[ 'core/creator' ];
-	CKEditorError = modules[ 'core/ckeditorerror' ];
+	coreTestUtils.defineEditorCreatorMock( 'test1' );
 
-	const coreTools = bender.tools.core;
+	coreTestUtils.defineEditorCreatorMock( 'test-throw-on-many1' );
+	coreTestUtils.defineEditorCreatorMock( 'test-throw-on-many2' );
 
-	coreTools.defineEditorCreatorMock( 'test1' );
+	coreTestUtils.defineEditorCreatorMock( 'test-config1' );
+	coreTestUtils.defineEditorCreatorMock( 'test-config2' );
 
-	coreTools.defineEditorCreatorMock( 'test-throw-on-many1' );
-	coreTools.defineEditorCreatorMock( 'test-throw-on-many2' );
-
-	coreTools.defineEditorCreatorMock( 'test-config1' );
-	coreTools.defineEditorCreatorMock( 'test-config2' );
-
-	bender.amd.define( 'test3', [ 'core/plugin' ], ( Plugin ) => {
+	amdUtils.define( 'test3', [ 'core/plugin' ], ( Plugin ) => {
 		return class extends Plugin {};
 	} );
 
-	bender.amd.define( 'creator-async-create', [ 'core/creator' ], ( Creator ) => {
+	amdUtils.define( 'creator-async-create', [ 'core/creator' ], ( Creator ) => {
 		return class extends Creator {
 			create() {
 				return new Promise( ( resolve, reject ) => {
@@ -54,7 +50,7 @@ before( () => {
 		};
 	} );
 
-	bender.amd.define( 'creator-async-destroy', [ 'core/creator' ], ( Creator ) => {
+	amdUtils.define( 'creator-async-destroy', [ 'core/creator' ], ( Creator ) => {
 		return class extends Creator {
 			create() {}
 

+ 8 - 11
packages/ckeditor5-engine/tests/editor/editor.js

@@ -3,23 +3,20 @@
  * For licensing, see LICENSE.md.
  */
 
-/* bender-include: ../_tools/tools.js */
-
 'use strict';
 
-const modules = bender.amd.require( 'core/editor', 'core/editorconfig', 'core/plugin' );
-let Editor, EditorConfig, Plugin;
+import amdUtils from '/tests/_utils/amd.js';
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Editor from '/ckeditor5/core/editor.js';
+import EditorConfig from '/ckeditor5/core/editorconfig.js';
+import Plugin from '/ckeditor5/core/plugin.js';
 
 const pluginClasses = {};
 let element;
 
 before( () => {
-	Editor = modules[ 'core/editor' ];
-	EditorConfig = modules[ 'core/editorconfig' ];
-	Plugin = modules[ 'core/plugin' ];
-
 	// Define fake plugins to be used in tests.
-	bender.tools.core.defineEditorCreatorMock( 'test', {
+	coreTestUtils.defineEditorCreatorMock( 'test', {
 		init: sinon.spy().named( 'creator-test' )
 	} );
 
@@ -122,7 +119,7 @@ describe( 'init', () => {
 		// Synchronous plugin that depends on an asynchronous one.
 		pluginDefinition( 'sync', [ 'async' ] );
 
-		bender.amd.define( 'async', () => {
+		amdUtils.define( 'async', () => {
 			PluginAsync.prototype.init = sinon.spy( () => {
 				return new Promise( ( resolve ) => {
 					setTimeout( () => {
@@ -187,7 +184,7 @@ describe( 'destroy', () => {
  * @param {String[]} deps Dependencies of the plugin (only other plugins).
  */
 function pluginDefinition( name, deps ) {
-	bender.amd.define( name, deps || [], function() {
+	amdUtils.define( name, deps || [], function() {
 		class NewPlugin extends Plugin {}
 
 		NewPlugin.prototype.init = sinon.spy().named( name );

+ 2 - 5
packages/ckeditor5-engine/tests/editorconfig.js

@@ -5,13 +5,12 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/editorconfig', 'ckeditor' );
+import EditorConfig from '/ckeditor5/core/editorconfig.js';
+import CKEDITOR from '/ckeditor.js';
 
 let config;
 
 beforeEach( () => {
-	const EditorConfig = modules[ 'core/editorconfig' ];
-
 	config = new EditorConfig( {
 		test: 1
 	} );
@@ -29,8 +28,6 @@ describe( 'get', () => {
 	} );
 
 	it( 'should fallback to CKEDITOR.config', () => {
-		const CKEDITOR = modules.ckeditor;
-
 		CKEDITOR.config.set( {
 			globalConfig: 2
 		} );

+ 4 - 8
packages/ckeditor5-engine/tests/emittermixin.js

@@ -5,15 +5,11 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/emittermixin', 'core/eventinfo', 'core/lib/lodash/object' );
-let EmitterMixin, EventInfo, utilsObject;
-let emitter, listener;
+import EmitterMixin from '/ckeditor5/core/emittermixin.js';
+import EventInfo from '/ckeditor5/core/eventinfo.js';
+import utilsObject from '/ckeditor5/core/lib/lodash/object.js';
 
-before( () => {
-	EmitterMixin = modules[ 'core/emittermixin' ];
-	EventInfo = modules[ 'core/eventinfo' ];
-	utilsObject = modules[ 'core/lib/lodash/object' ];
-} );
+let emitter, listener;
 
 beforeEach( refreshEmitter );
 

+ 1 - 7
packages/ckeditor5-engine/tests/eventinfo.js

@@ -5,15 +5,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/eventinfo' );
+import EventInfo from '/ckeditor5/core/eventinfo.js';
 
 describe( 'EventInfo', () => {
-	let EventInfo;
-
-	before( () => {
-		EventInfo = modules[ 'core/eventinfo' ];
-	} );
-
 	it( 'should be created properly', () => {
 		let event = new EventInfo( this, 'test' );
 

+ 1 - 7
packages/ckeditor5-engine/tests/lodash.js

@@ -5,15 +5,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/lib/lodash/object' );
+import objectUtils from '/ckeditor5/core/lib/lodash/object.js';
 
 describe( 'utils', () => {
-	let objectUtils;
-
-	before( () => {
-		objectUtils = modules[ 'core/lib/lodash/object' ];
-	} );
-
 	describe( 'extend()', () => {
 		// Properties of the subsequent objects should override properties of the preceding objects. This is critical for
 		// CKEditor so we keep this test to ensure that Lo-Dash (or whatever) implements it in the way we need it.

+ 2 - 4
packages/ckeditor5-engine/tests/log.js

@@ -7,13 +7,11 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/log' );
-let log;
+import log from '/ckeditor5/core/log.js';
+
 let spy;
 
 beforeEach( () => {
-	log = modules[ 'core/log' ];
-
 	if ( spy ) {
 		spy.restore();
 	}

+ 365 - 310
packages/ckeditor5-engine/tests/model.js

@@ -5,21 +5,16 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/model', 'core/eventinfo', 'core/ckeditorerror' );
+import testUtils from '/tests/_utils/utils.js';
+import Model from '/ckeditor5/core/model.js';
+import EventInfo from '/ckeditor5/core/eventinfo.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 let Car, car;
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 describe( 'Model', () => {
-	let Model, EventInfo, CKEditorError;
-
-	before( () => {
-		Model = modules[ 'core/model' ];
-		EventInfo = modules[ 'core/eventinfo' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	beforeEach( 'Create a test model instance', () => {
 		Car = class extends Model {};
 
@@ -164,6 +159,22 @@ describe( 'Model', () => {
 
 			expect( car.method ).to.be.a( 'function' );
 		} );
+
+		it( 'should allow setting attributes with undefined value', () => {
+			let spy = sinon.spy();
+
+			car.on( 'change', spy );
+			car.set( 'seats', undefined );
+
+			sinon.assert.calledOnce( spy );
+			expect( car ).to.contain.keys( 'seats' );
+			expect( car.seats ).to.be.undefined;
+
+			car.set( 'seats', 5 );
+
+			sinon.assert.calledTwice( spy );
+			expect( car ).to.have.property( 'seats', 5 );
+		} );
 	} );
 
 	describe( 'extend', () => {
@@ -218,66 +229,121 @@ describe( 'Model', () => {
 		} );
 
 		describe( 'to', () => {
-			it( 'should chain', () => {
-				const returned = car.bind( 'color' ).to( new Model( { color: 'red' } ) );
+			it( 'should not chain', () => {
+				expect(
+					car.bind( 'color' ).to( new Model( { color: 'red' } ) )
+				).to.be.undefined;
+			} );
+
+			it( 'should throw when arguments are of invalid type', () => {
+				expect( () => {
+					car = new Car();
+
+					car.bind( 'color' ).to();
+				} ).to.throw( CKEditorError, /model-bind-to-parse-error/ );
+
+				expect( () => {
+					car = new Car();
+
+					car.bind( 'color' ).to( new Model(), new Date() );
+				} ).to.throw( CKEditorError, /model-bind-to-parse-error/ );
+
+				expect( () => {
+					car = new Car( { color: 'red' } );
 
-				expect( returned ).to.have.property( 'to' );
-				expect( returned ).to.have.property( 'as' );
+					car.bind( 'color' ).to( new Model(), 'color', new Date() );
+				} ).to.throw( CKEditorError, /model-bind-to-parse-error/ );
 			} );
 
-			it( 'should chain multiple times', () => {
-				const returned = car.bind( 'color' )
-					.to( new Model( { color: 'red' } ) )
-					.to( new Model( { color: 'red' } ) )
-					.to( new Model( { color: 'red' } ) );
+			it( 'should throw when binding multiple attributes to multiple models', () => {
+				let vehicle = new Car();
+				const car1 = new Car( { color: 'red', year: 1943 } );
+				const car2 = new Car( { color: 'yellow', year: 1932 } );
+
+				expect( () => {
+					vehicle.bind( 'color', 'year' ).to( car1, 'color', car2, 'year' );
+				} ).to.throw( CKEditorError, /model-bind-to-no-callback/ );
+
+				expect( () => {
+					vehicle = new Car();
+
+					vehicle.bind( 'color', 'year' ).to( car1, car2 );
+				} ).to.throw( CKEditorError, /model-bind-to-no-callback/ );
+
+				expect( () => {
+					vehicle = new Car();
+
+					vehicle.bind( 'color', 'year' ).to( car1, car2, 'year' );
+				} ).to.throw( CKEditorError, /model-bind-to-no-callback/ );
+
+				expect( () => {
+					vehicle = new Car();
+
+					vehicle.bind( 'color', 'year' ).to( car1, 'color', car2 );
+				} ).to.throw( CKEditorError, /model-bind-to-no-callback/ );
+
+				expect( () => {
+					vehicle = new Car();
 
-				expect( returned ).to.have.property( 'to' );
-				expect( returned ).to.have.property( 'as' );
+					vehicle.bind( 'color', 'year', 'custom' ).to( car, car );
+				} ).to.throw( CKEditorError, /model-bind-to-no-callback/ );
 			} );
 
-			it( 'should throw when .to() model is not Model', () => {
+			it( 'should throw when binding multiple attributes but passed a callback', () => {
+				let vehicle = new Car();
+
 				expect( () => {
-					car.bind( 'color' ).to();
-				} ).to.throw( CKEditorError, /model-bind-to-wrong-model/ );
+					vehicle.bind( 'color', 'year' ).to( car, () => {} );
+				} ).to.throw( CKEditorError, /model-bind-to-extra-callback/ );
 
 				expect( () => {
-					car.bind( 'year' ).to( 'it\'s not a model' );
-				} ).to.throw( CKEditorError, /model-bind-to-wrong-model/ );
+					vehicle = new Car();
+
+					vehicle.bind( 'color', 'year' ).to( car, car, () => {} );
+				} ).to.throw( CKEditorError, /model-bind-to-extra-callback/ );
 			} );
 
-			it( 'should throw when attributes are not strings', () => {
+			it( 'should throw when binding a single attribute but multiple callbacks', () => {
+				let vehicle = new Car();
+
 				expect( () => {
-					car.bind( 'color' ).to( new Model(), new Date() );
-				} ).to.throw( CKEditorError, /model-bind-to-wrong-attrs/ );
+					vehicle.bind( 'color' ).to( car, () => {}, () => {} );
+				} ).to.throw( CKEditorError, /model-bind-to-parse-error/ );
 
 				expect( () => {
-					car = new Car( { color: 'red' } );
+					vehicle = new Car();
 
-					car.bind( 'color' ).to( new Model(), 'color', new Date() );
-				} ).to.throw( CKEditorError, /model-bind-to-wrong-attrs/ );
+					vehicle.bind( 'color' ).to( car, car, () => {}, () => {} );
+				} ).to.throw( CKEditorError, /model-bind-to-parse-error/ );
 			} );
 
 			it( 'should throw when a number of attributes does not match', () => {
+				let vehicle = new Car();
+
+				expect( () => {
+					vehicle.bind( 'color' ).to( car, 'color', 'year' );
+				} ).to.throw( CKEditorError, /model-bind-to-attrs-length/ );
+
 				expect( () => {
-					const vehicle = new Car();
+					vehicle = new Car();
 
 					vehicle.bind( 'color', 'year' ).to( car, 'color' );
 				} ).to.throw( CKEditorError, /model-bind-to-attrs-length/ );
 
 				expect( () => {
-					const vehicle = new Car();
+					vehicle = new Car();
 
-					vehicle.bind( 'color' ).to( car, 'color', 'year' );
+					vehicle.bind( 'color' ).to( car, 'color', 'year', () => {} );
 				} ).to.throw( CKEditorError, /model-bind-to-attrs-length/ );
 
 				expect( () => {
-					const vehicle = new Car();
+					vehicle = new Car();
 
-					vehicle.bind( 'color' ).to( car, 'color' ).to( car, 'color', 'year' );
+					vehicle.bind( 'color' ).to( car, 'color', car, 'color', 'year', () => {} );
 				} ).to.throw( CKEditorError, /model-bind-to-attrs-length/ );
 			} );
 
-			it( 'should throw when no attribute specified and those from bind() don\'t exist in to() model', () => {
+			it( 'should throw when attributes don\'t exist in to() model', () => {
 				const vehicle = new Car();
 
 				expect( () => {
@@ -287,23 +353,10 @@ describe( 'Model', () => {
 				expect( () => {
 					vehicle.bind( 'nonexistent in car' ).to( car );
 				} ).to.throw( CKEditorError, /model-bind-to-missing-attr/ );
-			} );
-
-			it( 'should throw when to() more than once and multiple attributes', () => {
-				const car1 = new Car( { color: 'red', year: 2000 } );
-				const car2 = new Car( { color: 'red', year: 2000 } );
-
-				expect( () => {
-					const vehicle = new Car();
-
-					vehicle.bind( 'color', 'year' ).to( car1 ).to( car2 );
-				} ).to.throw( CKEditorError, /model-bind-to-chain-multiple-attrs/ );
 
 				expect( () => {
-					const vehicle = new Car();
-
-					vehicle.bind( 'color', 'year' ).to( car1, 'color', 'year' ).to( car2, 'color', 'year' );
-				} ).to.throw( CKEditorError, /model-bind-to-chain-multiple-attrs/ );
+					vehicle.bind( 'year' ).to( car, 'color', car, 'nonexistent in car', () => {} );
+				} ).to.throw( CKEditorError, /model-bind-to-missing-attr/ );
 			} );
 
 			it( 'should set new model attributes', () => {
@@ -385,280 +438,282 @@ describe( 'Model', () => {
 				);
 			} );
 
-			it( 'should not throw when binding more than once but no as() afterwards', () => {
+			it( 'should work when binding more that once', () => {
 				const vehicle = new Car();
-				const car1 = new Car( { color: 'red' } );
-				const car2 = new Car( { color: 'red' } );
 
-				vehicle.bind( 'color' ).to( car1 ).to( car2 );
+				vehicle.bind( 'color' ).to( car, 'color' );
+				vehicle.bind( 'year' ).to( car, 'year' );
 
 				assertBinding( vehicle,
-					{ color: undefined, year: undefined },
+					{ color: car.color, year: car.year },
 					[
 						[ car, { color: 'blue', year: 1969 } ]
 					],
-					{ color: undefined, year: undefined }
+					{ color: 'blue', year: 1969 }
 				);
 			} );
 
-			it( 'should work when binding more that once', () => {
+			it( 'should work with callback – set a new model attribute', () => {
 				const vehicle = new Car();
+				const car1 = new Car( { type: 'pickup' } );
+				const car2 = new Car( { type: 'truck' } );
 
-				vehicle.bind( 'color' ).to( car, 'color' );
-				vehicle.bind( 'year' ).to( car, 'year' );
+				vehicle.bind( 'type' )
+					.to( car1, car2, ( ...args ) => args.join( '' ) );
+
+				expect( vehicle._attributes ).to.have.keys( [ 'type' ] );
+			} );
+
+			it( 'should work with callback #1', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { color: 'black' } );
+				const car2 = new Car( { color: 'brown' } );
+
+				vehicle.bind( 'color' )
+					.to( car1, car2, ( ...args ) => args.join( '' ) );
 
 				assertBinding( vehicle,
-					{ 'color': car.color, year: car.year },
+					{ color: car1.color + car2.color, year: undefined },
 					[
-						[ car, { color: 'blue', year: 1969 } ]
+						[ car1, { color: 'black', year: 1930 } ],
+						[ car2, { color: 'green', year: 1950 } ]
 					],
-					{ color: 'blue', year: 1969 }
+					{ color: 'blackgreen', year: undefined }
 				);
 			} );
 
-			describe( 'as', () => {
-				it( 'should not chain', () => {
-					const car1 = new Car( { year: 1999 } );
-					const car2 = new Car( { year: 2000 } );
-
-					expect(
-						car.bind( 'year' ).to( car1, 'year' ).to( car2, 'year' ).as( () => {} )
-					).to.be.undefined;
-				} );
-
-				it( 'should throw when not a function passed', () => {
-					const car1 = new Car( { color: 'brown' } );
-					const car2 = new Car( { color: 'green' } );
-
-					expect( () => {
-						car.bind( 'year' ).to( car1, 'color' ).to( car2, 'color' ).as();
-					} ).to.throw( CKEditorError, /model-bind-as-wrong-callback/ );
-
-					expect( () => {
-						car.bind( 'color' ).to( car1, 'color' ).to( car2, 'color' ).as( 'not-a-function' );
-					} ).to.throw( CKEditorError, /model-bind-as-wrong-callback/ );
-				} );
-
-				it( 'should set new model attributes', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { type: 'pickup' } );
-					const car2 = new Car( { type: 'truck' } );
-
-					vehicle.bind( 'type' )
-						.to( car1 )
-						.to( car2 )
-						.as( ( ...args ) => args.join( '' ) );
-
-					expect( vehicle._attributes ).to.have.keys( [ 'type' ] );
-				} );
-
-				it( 'should work for a single attribute #1', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { color: 'black' } );
-					const car2 = new Car( { color: 'brown' } );
-
-					vehicle.bind( 'color' )
-						.to( car1 )
-						.to( car2 )
-						.as( ( ...args ) => args.join( '' ) );
-
-					assertBinding( vehicle,
-						{ color: car1.color + car2.color, year: undefined },
-						[
-							[ car1, { color: 'black', year: 1930 } ],
-							[ car2, { color: 'green', year: 1950 } ]
-						],
-						{ color: 'blackgreen', year: undefined }
-					);
-				} );
-
-				it( 'should work for a single attribute #2', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { color: 'black' } );
-					const car2 = new Car( { color: 'brown' } );
-
-					vehicle.bind( 'color' )
-						.to( car1, 'color' )
-						.to( car2, 'color' )
-						.as( ( ...args ) => args.join( '' ) );
-
-					assertBinding( vehicle,
-						{ color: car1.color + car2.color, year: undefined },
-						[
-							[ car1, { color: 'black', year: 1930 } ],
-							[ car2, { color: 'green', year: 1950 } ]
-						],
-						{ color: 'blackgreen', year: undefined }
-					);
-				} );
-
-				it( 'should work for a single attribute #3', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { color: 'black' } );
-					const car2 = new Car( { color: 'brown' } );
-					const car3 = new Car( { color: 'yellow' } );
-
-					vehicle.bind( 'color' )
-						.to( car1 )
-						.to( car2 )
-						.to( car3 )
-						.as( ( ...args ) => args.join( '' ) );
-
-					assertBinding( vehicle,
-						{ color: car1.color + car2.color + car3.color, year: undefined },
-						[
-							[ car1, { color: 'black', year: 1930 } ],
-							[ car2, { color: 'green', year: 1950 } ]
-						],
-						{ color: 'blackgreenyellow', year: undefined }
-					);
-				} );
-
-				it( 'should work for a single attribute #4', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { color: 'black' } );
-					const car2 = new Car( { lightness: 'bright' } );
-					const car3 = new Car( { color: 'yellow' } );
-
-					vehicle.bind( 'color' )
-						.to( car1 )
-						.to( car2, 'lightness' )
-						.to( car3 )
-						.as( ( ...args ) => args.join( '' ) );
-
-					assertBinding( vehicle,
-						{ color: car1.color + car2.lightness + car3.color, year: undefined },
-						[
-							[ car1, { color: 'black', year: 1930 } ],
-							[ car2, { color: 'green', year: 1950 } ]
-						],
-						{ color: 'blackbrightyellow', year: undefined }
-					);
-				} );
-
-				it( 'should work for a single attribute #5', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { hue: 'reds' } );
-					const car2 = new Car( { lightness: 'bright' } );
-
-					vehicle.bind( 'color' )
-						.to( car1, 'hue' )
-						.to( car2, 'lightness' )
-						.as( ( ...args ) => args.join( '' ) );
-
-					assertBinding( vehicle,
-						{ color: car1.hue + car2.lightness, year: undefined },
-						[
-							[ car1, { hue: 'greens', year: 1930 } ],
-							[ car2, { lightness: 'dark', year: 1950 } ]
-						],
-						{ color: 'greensdark', year: undefined }
-					);
-				} );
-
-				it( 'should work when binding more that once #1', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { hue: 'reds', produced: 1920 } );
-					const car2 = new Car( { lightness: 'bright', sold: 1921 } );
-
-					vehicle.bind( 'color' )
-						.to( car1, 'hue' )
-						.to( car2, 'lightness' )
-						.as( ( ...args ) => args.join( '' ) );
-
-					vehicle.bind( 'year' )
-						.to( car1, 'produced' )
-						.to( car2, 'sold' )
-						.as( ( ...args ) => args.join( '/' ) );
-
-					assertBinding( vehicle,
-						{ color: car1.hue + car2.lightness, year: car1.produced + '/' + car2.sold },
-						[
-							[ car1, { hue: 'greens', produced: 1930 } ],
-							[ car2, { lightness: 'dark', sold: 2000 } ]
-						],
-						{ color: 'greensdark', year: '1930/2000' }
-					);
-				} );
-
-				it( 'should work when binding more that once #2', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { hue: 'reds', produced: 1920 } );
-					const car2 = new Car( { lightness: 'bright', sold: 1921 } );
-
-					vehicle.bind( 'color' )
-						.to( car1, 'hue' )
-						.to( car2, 'lightness' )
-						.as( ( ...args ) => args.join( '' ) );
-
-					vehicle.bind( 'year' )
-						.to( car1, 'produced' )
-						.to( car2, 'sold' )
-						.as( ( ...args ) => args.join( '/' ) );
-
-					vehicle.bind( 'mix' )
-						.to( car1, 'hue' )
-						.to( car2, 'sold' )
-						.as( ( ...args ) => args.join( '+' ) );
-
-					assertBinding( vehicle,
-						{
-							color: car1.hue + car2.lightness,
-							year: car1.produced + '/' + car2.sold,
-							mix: car1.hue + '+' + car2.sold
-						},
-						[
-							[ car1, { hue: 'greens', produced: 1930 } ],
-							[ car2, { lightness: 'dark', sold: 2000 } ]
-						],
-						{
-							color: 'greensdark',
-							year: '1930/2000',
-							mix: 'greens+2000'
-						}
-					);
-				} );
-
-				it( 'should work when binding more that once #3', () => {
-					const vehicle = new Car();
-					const car1 = new Car( { hue: 'reds', produced: 1920 } );
-					const car2 = new Car( { lightness: 'bright', sold: 1921 } );
-
-					vehicle.bind( 'color' )
-						.to( car1, 'hue' )
-						.to( car2, 'lightness' )
-						.as( ( ...args ) => args.join( '' ) );
-
-					vehicle.bind( 'custom1' ).to( car1, 'hue' );
-
-					vehicle.bind( 'year' )
-						.to( car1, 'produced' )
-						.to( car2, 'sold' )
-						.as( ( ...args ) => args.join( '/' ) );
-
-					vehicle.bind( 'custom2', 'custom3' ).to( car1, 'produced', 'hue' );
-
-					assertBinding( vehicle,
-						{
-							color: car1.hue + car2.lightness,
-							year: car1.produced + '/' + car2.sold,
-							custom1: car1.hue,
-							custom2: car1.produced,
-							custom3: car1.hue
-						},
-						[
-							[ car1, { hue: 'greens', produced: 1930 } ],
-							[ car2, { lightness: 'dark', sold: 2000 } ]
-						],
-						{
-							color: 'greensdark',
-							year: '1930/2000',
-							custom1: 'greens',
-							custom2: 1930,
-							custom3: 'greens'
-						}
-					);
-				} );
+			it( 'should work with callback #2', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { color: 'black' } );
+				const car2 = new Car( { color: 'brown' } );
+
+				vehicle.bind( 'color' )
+					.to( car1, 'color', car2, 'color', ( ...args ) => args.join( '' ) );
+
+				assertBinding( vehicle,
+					{ color: car1.color + car2.color, year: undefined },
+					[
+						[ car1, { color: 'black', year: 1930 } ],
+						[ car2, { color: 'green', year: 1950 } ]
+					],
+					{ color: 'blackgreen', year: undefined }
+				);
+			} );
+
+			it( 'should work with callback #3', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { color: 'black' } );
+				const car2 = new Car( { color: 'brown' } );
+				const car3 = new Car( { color: 'yellow' } );
+
+				vehicle.bind( 'color' )
+					.to( car1, car2, car3, ( ...args ) => args.join( '' ) );
+
+				assertBinding( vehicle,
+					{ color: car1.color + car2.color + car3.color, year: undefined },
+					[
+						[ car1, { color: 'black', year: 1930 } ],
+						[ car2, { color: 'green', year: 1950 } ]
+					],
+					{ color: 'blackgreenyellow', year: undefined }
+				);
+			} );
+
+			it( 'should work with callback #4', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { color: 'black' } );
+				const car2 = new Car( { lightness: 'bright' } );
+				const car3 = new Car( { color: 'yellow' } );
+
+				vehicle.bind( 'color' )
+					.to( car1, car2, 'lightness', car3, ( ...args ) => args.join( '' ) );
+
+				assertBinding( vehicle,
+					{ color: car1.color + car2.lightness + car3.color, year: undefined },
+					[
+						[ car1, { color: 'black', year: 1930 } ],
+						[ car2, { color: 'green', year: 1950 } ]
+					],
+					{ color: 'blackbrightyellow', year: undefined }
+				);
+			} );
+
+			it( 'should work with callback #5', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { hue: 'reds' } );
+				const car2 = new Car( { lightness: 'bright' } );
+
+				vehicle.bind( 'color' )
+					.to( car1, 'hue', car2, 'lightness', ( ...args ) => args.join( '' ) );
+
+				assertBinding( vehicle,
+					{ color: car1.hue + car2.lightness, year: undefined },
+					[
+						[ car1, { hue: 'greens', year: 1930 } ],
+						[ car2, { lightness: 'dark', year: 1950 } ]
+					],
+					{ color: 'greensdark', year: undefined }
+				);
+			} );
+
+			it( 'should work with callback #6', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { hue: 'reds' } );
+
+				vehicle.bind( 'color' )
+					.to( car1, 'hue', ( h ) => h.toUpperCase() );
+
+				assertBinding( vehicle,
+					{ color: car1.hue.toUpperCase(), year: undefined },
+					[
+						[ car1, { hue: 'greens', year: 1930 } ]
+					],
+					{ color: 'GREENS', year: undefined }
+				);
+			} );
+
+			it( 'should work with callback #7', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { color: 'red', year: 1943 } );
+				const car2 = new Car( { color: 'yellow', year: 1932 } );
+
+				vehicle.bind( 'custom' )
+					.to( car1, 'color', car2, 'year', ( ...args ) => args.join( '/' ) );
+
+				assertBinding( vehicle,
+					{ color: undefined, year: undefined, 'custom': car1.color + '/' + car2.year },
+					[
+						[ car1, { color: 'blue', year: 2100 } ],
+						[ car2, { color: 'violet', year: 1969 } ]
+					],
+					{ color: undefined, year: undefined, 'custom': 'blue/1969' }
+				);
+			} );
+
+			it( 'should work with callback #8', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { color: 'red', year: 1943 } );
+				const car2 = new Car( { color: 'yellow', year: 1932 } );
+				const car3 = new Car( { hue: 'reds' } );
+
+				vehicle.bind( 'custom' )
+					.to( car1, 'color', car2, 'year', car3, 'hue', ( ...args ) => args.join( '/' ) );
+
+				assertBinding( vehicle,
+					{ color: undefined, year: undefined, hue: undefined, 'custom': car1.color + '/' + car2.year + '/' + car3.hue },
+					[
+						[ car1, { color: 'blue', year: 2100 } ],
+						[ car2, { color: 'violet', year: 1969 } ]
+					],
+					{ color: undefined, year: undefined, hue: undefined, 'custom': 'blue/1969/reds' }
+				);
+			} );
+
+			it( 'should work with callback – binding more that once #1', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { hue: 'reds', produced: 1920 } );
+				const car2 = new Car( { lightness: 'bright', sold: 1921 } );
+
+				vehicle.bind( 'color' )
+					.to( car1, 'hue', car2, 'lightness', ( ...args ) => args.join( '' ) );
+
+				vehicle.bind( 'year' )
+					.to( car1, 'produced', car2, 'sold', ( ...args ) => args.join( '/' ) );
+
+				assertBinding( vehicle,
+					{ color: car1.hue + car2.lightness, year: car1.produced + '/' + car2.sold },
+					[
+						[ car1, { hue: 'greens', produced: 1930 } ],
+						[ car2, { lightness: 'dark', sold: 2000 } ]
+					],
+					{ color: 'greensdark', year: '1930/2000' }
+				);
+			} );
+
+			it( 'should work with callback – binding more that once #2', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { hue: 'reds', produced: 1920 } );
+				const car2 = new Car( { lightness: 'bright', sold: 1921 } );
+
+				vehicle.bind( 'color' )
+					.to( car1, 'hue', car2, 'lightness', ( ...args ) => args.join( '' ) );
+
+				vehicle.bind( 'year' )
+					.to( car1, 'produced', car2, 'sold', ( ...args ) => args.join( '/' ) );
+
+				vehicle.bind( 'mix' )
+					.to( car1, 'hue', car2, 'sold', ( ...args ) => args.join( '+' ) );
+
+				assertBinding( vehicle,
+					{
+						color: car1.hue + car2.lightness,
+						year: car1.produced + '/' + car2.sold,
+						mix: car1.hue + '+' + car2.sold
+					},
+					[
+						[ car1, { hue: 'greens', produced: 1930 } ],
+						[ car2, { lightness: 'dark', sold: 2000 } ]
+					],
+					{
+						color: 'greensdark',
+						year: '1930/2000',
+						mix: 'greens+2000'
+					}
+				);
+			} );
+
+			it( 'should work with callback – binding more that once #3', () => {
+				const vehicle = new Car();
+				const car1 = new Car( { hue: 'reds', produced: 1920 } );
+				const car2 = new Car( { lightness: 'bright', sold: 1921 } );
+
+				vehicle.bind( 'color' )
+					.to( car1, 'hue', car2, 'lightness', ( ...args ) => args.join( '' ) );
+
+				vehicle.bind( 'custom1' ).to( car1, 'hue' );
+
+				vehicle.bind( 'year' )
+					.to( car1, 'produced', car2, 'sold', ( ...args ) => args.join( '/' ) );
+
+				vehicle.bind( 'custom2', 'custom3' ).to( car1, 'produced', 'hue' );
+
+				assertBinding( vehicle,
+					{
+						color: car1.hue + car2.lightness,
+						year: car1.produced + '/' + car2.sold,
+						custom1: car1.hue,
+						custom2: car1.produced,
+						custom3: car1.hue
+					},
+					[
+						[ car1, { hue: 'greens', produced: 1930 } ],
+						[ car2, { lightness: 'dark', sold: 2000 } ]
+					],
+					{
+						color: 'greensdark',
+						year: '1930/2000',
+						custom1: 'greens',
+						custom2: 1930,
+						custom3: 'greens'
+					}
+				);
+			} );
+
+			it( 'should fire a single change event per bound attribute', () => {
+				const vehicle = new Car();
+				const car = new Car( { color: 'red', year: 1943 } );
+				const spy = sinon.spy();
+
+				vehicle.on( 'change', spy );
+
+				vehicle.bind( 'color', 'year' ).to( car );
+
+				car.color = 'violet';
+				car.custom = 'foo';
+				car.year = 2001;
+
+				expect( spy.args.map( args => args[ 1 ] ) )
+					.to.have.members( [ 'color', 'year', 'color', 'year' ] );
 			} );
 		} );
 	} );
@@ -701,12 +756,12 @@ describe( 'Model', () => {
 			);
 		} );
 
-		it( 'should remove bindings of certain attributes, as()', () => {
+		it( 'should remove bindings of certain attributes, callback', () => {
 			const vehicle = new Car();
 			const car1 = new Car( { color: 'red' } );
 			const car2 = new Car( { color: 'blue' } );
 
-			vehicle.bind( 'color' ).to( car1 ).to( car2 ).as( ( c1, c2 ) => c1 + c2 );
+			vehicle.bind( 'color' ).to( car1, car2, ( c1, c2 ) => c1 + c2 );
 			vehicle.unbind( 'color' );
 
 			assertBinding( vehicle,

+ 3 - 5
packages/ckeditor5-engine/tests/plugin.js

@@ -5,19 +5,17 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/plugin', 'core/editor' );
+import Plugin from '/ckeditor5/core/plugin.js';
+import Editor from '/ckeditor5/core/editor.js';
+
 let editor;
 
 before( () => {
-	const Editor = modules[ 'core/editor' ];
-
 	editor = new Editor( document.body.appendChild( document.createElement( 'div' ) ) );
 } );
 
 describe( 'constructor', () => {
 	it( 'should set the `editor` property', () => {
-		const Plugin = modules[ 'core/plugin' ];
-
 		let plugin = new Plugin( editor );
 
 		expect( plugin ).to.have.property( 'editor' ).to.equal( editor );

+ 38 - 30
packages/ckeditor5-engine/tests/plugincollection.js

@@ -5,30 +5,22 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/editor',
-	'core/plugincollection',
-	'core/plugin',
-	'core/creator',
-	'core/ckeditorerror',
-	'core/log'
-);
-let PluginCollection, Plugin, Editor, Creator, CKEditorError, log;
+import amdUtils from '/tests/_utils/amd.js';
+import testUtils from '/tests/_utils/utils.js';
+import Editor from '/ckeditor5/core/editor.js';
+import PluginCollection from '/ckeditor5/core/plugincollection.js';
+import Plugin from '/ckeditor5/core/plugin.js';
+import Creator from '/ckeditor5/core/creator.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+import log from '/ckeditor5/core/log.js';
 
 let editor;
 let PluginA, PluginB, PluginC, PluginD, PluginE, PluginF, PluginG;
 class TestError extends Error {}
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 before( () => {
-	PluginCollection = modules[ 'core/plugincollection' ];
-	Editor = modules[ 'core/editor' ];
-	Plugin = modules[ 'core/plugin' ];
-	Creator = modules[ 'core/creator' ];
-	CKEditorError = modules[ 'core/ckeditorerror' ];
-	log = modules[ 'core/log' ];
-
 	PluginA = createPlugin( 'A' );
 	PluginB = createPlugin( 'B' );
 	PluginC = createPlugin( 'C' );
@@ -47,41 +39,41 @@ before( () => {
 
 // Create fake plugins that will be used on tests.
 
-bender.amd.define( 'A', () => {
+amdUtils.define( 'A', () => {
 	return PluginA;
 } );
 
-bender.amd.define( 'B', () => {
+amdUtils.define( 'B', () => {
 	return PluginB;
 } );
 
-bender.amd.define( 'C', [ 'core/plugin', 'B' ], () => {
+amdUtils.define( 'C', [ 'core/editor', 'B' ], () => {
 	return PluginC;
 } );
 
-bender.amd.define( 'D', [ 'core/plugin', 'A', 'C' ], () => {
+amdUtils.define( 'D', [ 'core/editor', 'A', 'C' ], () => {
 	return PluginD;
 } );
 
-bender.amd.define( 'E', [ 'core/plugin', 'F' ], () => {
+amdUtils.define( 'E', [ 'core/editor', 'F' ], () => {
 	return PluginE;
 } );
 
-bender.amd.define( 'F', [ 'core/plugin', 'E' ], () => {
+amdUtils.define( 'F', [ 'core/editor', 'E' ], () => {
 	return PluginF;
 } );
 
-bender.amd.define( 'G', () => {
+amdUtils.define( 'G', () => {
 	return PluginG;
 } );
 
 // Erroneous cases.
 
-bender.amd.define( 'X', () => {
+amdUtils.define( 'X', () => {
 	throw new TestError( 'Some error inside a plugin' );
 } );
 
-bender.amd.define( 'Y', () => {
+amdUtils.define( 'Y', () => {
 	return class {};
 } );
 
@@ -194,7 +186,7 @@ describe( 'load', () => {
 	} );
 
 	it( 'should reject on invalid plugin names (forward require.js loading error)', () => {
-		let logSpy = bender.sinon.stub( log, 'error' );
+		let logSpy = testUtils.sinon.stub( log, 'error' );
 
 		let plugins = new PluginCollection( editor );
 
@@ -214,7 +206,7 @@ describe( 'load', () => {
 	} );
 
 	it( 'should reject on broken plugins (forward the error thrown in a plugin)', () => {
-		let logSpy = bender.sinon.stub( log, 'error' );
+		let logSpy = testUtils.sinon.stub( log, 'error' );
 
 		let plugins = new PluginCollection( editor );
 
@@ -233,7 +225,7 @@ describe( 'load', () => {
 	} );
 
 	it( 'should reject when loading a module which is not a plugin', () => {
-		let logSpy = bender.sinon.stub( log, 'error' );
+		let logSpy = testUtils.sinon.stub( log, 'error' );
 
 		let plugins = new PluginCollection( editor );
 
@@ -252,8 +244,24 @@ describe( 'load', () => {
 	} );
 } );
 
+describe( 'getPluginPath()', () => {
+	it( 'generates path for modules within some package', () => {
+		const p = PluginCollection.getPluginPath( 'some/ba' );
+
+		expect( p ).to.equal( 'ckeditor5/some/ba.js' );
+	} );
+
+	it( 'generates path from simplified feature name', () => {
+		const p = PluginCollection.getPluginPath( 'foo' );
+
+		expect( p ).to.equal( 'ckeditor5/foo/foo.js' );
+	} );
+} );
+
 function createPlugin( name, baseClass ) {
-	const P = class extends ( baseClass || Plugin ) {
+	baseClass = baseClass || Plugin;
+
+	const P = class extends baseClass {
 		constructor( editor ) {
 			super( editor );
 			this._pluginName = name;

+ 36 - 0
packages/ckeditor5-engine/tests/treemodel/_utils/utils.js

@@ -0,0 +1,36 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const utils = {
+	/**
+	 * Returns tree structure as a simplified string. Elements are uppercase and characters are lowercase.
+	 * Start and end of an element is marked the same way, by the element's name (in uppercase).
+	 *
+	 *		let element = new Element( 'div', [], [ 'abc', new Element( 'p', [], 'foo' ), 'xyz' ] );
+	 *		treemodelUtils.getNodesAndText( element ); // abcPfooPxyz
+	 *
+	 * @param {treeModel.Range} range Range to stringify.
+	 * @returns {String} String representing element inner structure.
+	 */
+	getNodesAndText( range ) {
+		let txt = '';
+
+		for ( let step of range ) {
+			let node = step.node;
+
+			if ( node.character ) {
+				txt += node.character.toLowerCase();
+			} else if ( node.name ) {
+				txt += node.name.toUpperCase();
+			}
+		}
+
+		return txt;
+	}
+};
+
+export default utils;

+ 1 - 7
packages/ckeditor5-engine/tests/treemodel/attribute.js

@@ -7,15 +7,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/treemodel/attribute' );
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
 describe( 'Attribute', () => {
-	let Attribute;
-
-	before( () => {
-		Attribute = modules[ 'core/treemodel/attribute' ];
-	} );
-
 	beforeEach( () => {
 		Attribute._register = {};
 	} );

+ 4 - 13
packages/ckeditor5-engine/tests/treemodel/attributelist.js

@@ -4,25 +4,16 @@
  */
 
 /* bender-tags: treemodel */
-/* bender-include: ../_tools/tools.js */
 
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/attributelist',
-	'core/treemodel/attribute'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'AttributeList', () => {
-	let AttributeList, Attribute;
-
-	before( () => {
-		AttributeList = modules[ 'core/treemodel/attributelist' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-	} );
-
 	let list, attrFooBar;
 
 	beforeEach( () => {

+ 3 - 13
packages/ckeditor5-engine/tests/treemodel/batch.js

@@ -7,21 +7,11 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/batch',
-	'core/treemodel/delta/delta',
-	'core/ckeditorerror'
-);
+import Batch from '/ckeditor5/core/treemodel/batch.js';
+import Delta from '/ckeditor5/core/treemodel/delta/delta.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Batch', () => {
-	let Batch, Delta, CKEditorError;
-
-	before( () => {
-		Batch = modules[ 'core/treemodel/batch' ];
-		Delta = modules[ 'core/treemodel/delta/delta' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	it( 'should have registered basic methods', () => {
 		const batch = new Batch();
 

+ 6 - 17
packages/ckeditor5-engine/tests/treemodel/character.js

@@ -4,29 +4,18 @@
  */
 
 /* bender-tags: treemodel */
-/* bender-include: ../_tools/tools.js */
 
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
+import Node from '/ckeditor5/core/treemodel/node.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/character',
-	'core/treemodel/node',
-	'core/treemodel/element',
-	'core/treemodel/attribute'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Character', () => {
-	let Element, Character, Node, Attribute;
-
-	before( () => {
-		Element = modules[ 'core/treemodel/element' ];
-		Character = modules[ 'core/treemodel/character' ];
-		Node = modules[ 'core/treemodel/node' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-	} );
-
 	describe( 'constructor', () => {
 		it( 'should create character without attributes', () => {
 			let character = new Character( 'f' );

+ 9 - 25
packages/ckeditor5-engine/tests/treemodel/delta/attributedelta.js

@@ -5,38 +5,22 @@
 
 /* bender-tags: treemodel, delta */
 
-/* bender-include: ../../_tools/tools.js */
-
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Text from '/ckeditor5/core/treemodel/text.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/batch',
-	'core/treemodel/document',
-	'core/treemodel/text',
-	'core/treemodel/attribute',
-	'core/treemodel/range',
-	'core/treemodel/position',
-	'core/treemodel/element',
-	'core/treemodel/character' );
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Batch', () => {
-	let Batch, Document, Text, Attribute, Range, Position, Element, Character;
-
 	let doc, root, batch;
 
-	before( () => {
-		Batch = modules[ 'core/treemodel/batch' ];
-		Document = modules[ 'core/treemodel/document' ];
-		Text = modules[ 'core/treemodel/text' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Character = modules[ 'core/treemodel/character' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 3 - 12
packages/ckeditor5-engine/tests/treemodel/delta/delta.js

@@ -5,23 +5,14 @@
 
 /* bender-tags: treemodel, delta */
 
-/* bender-include: ../../_tools/tools.js */
-
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Delta from '/ckeditor5/core/treemodel/delta/delta.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/delta/delta'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Delta', () => {
-	let Delta;
-
-	before( () => {
-		Delta = modules[ 'core/treemodel/delta/delta' ];
-	} );
-
 	describe( 'constructor', () => {
 		it( 'should create an delta with empty properties', () => {
 			const delta = new Delta();

+ 3 - 15
packages/ckeditor5-engine/tests/treemodel/delta/insertdelta.js

@@ -7,23 +7,11 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/position',
-	'core/treemodel/delta/insertdelta'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
 
 describe( 'Batch', () => {
-	let Document, Element, Position, InsertDelta;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Position = modules[ 'core/treemodel/position' ];
-		InsertDelta = modules[ 'core/treemodel/delta/insertdelta' ];
-	} );
-
 	let doc, root, batch, p, ul, chain;
 
 	beforeEach( () => {

+ 7 - 19
packages/ckeditor5-engine/tests/treemodel/delta/mergedelta.js

@@ -5,32 +5,20 @@
 
 /* bender-tags: treemodel, delta */
 
-/* bender-include: ../../_tools/tools.js */
-
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/position',
-	'core/treemodel/element',
-	'core/treemodel/attribute',
-	'core/ckeditorerror' );
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Batch', () => {
-	let Document, Position, Element, Attribute, CKEditorError;
-
 	let doc, root, p1, p2;
 
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 7 - 19
packages/ckeditor5-engine/tests/treemodel/delta/movedelta.js

@@ -4,33 +4,21 @@
  */
 
 /* bender-tags: treemodel, delta */
-/* bender-include: ../../_tools/tools.js */
 
 'use strict';
 
-const getNodesAndText = bender.tools.treemodel.getNodesAndText;
+import treeModelTestUtils from '/tests/core/treemodel/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/position',
-	'core/treemodel/range',
-	'core/treemodel/element',
-	'core/ckeditorerror'
-);
+const getNodesAndText = treeModelTestUtils.getNodesAndText;
 
 describe( 'Batch', () => {
-	let Document, Position, Range, Element, CKEditorError;
-
 	let doc, root, div, p, batch, chain;
 
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Element = modules[ 'core/treemodel/element' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 6 - 17
packages/ckeditor5-engine/tests/treemodel/delta/removedelta.js

@@ -4,31 +4,20 @@
  */
 
 /* bender-tags: treemodel, delta */
-/* bender-include: ../../_tools/tools.js */
 
 'use strict';
 
-const getNodesAndText = bender.tools.treemodel.getNodesAndText;
+import treeModelTestUtils from '/tests/core/treemodel/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/position',
-	'core/treemodel/range',
-	'core/treemodel/element'
-);
+const getNodesAndText = treeModelTestUtils.getNodesAndText;
 
 describe( 'Batch', () => {
-	let Document, Position, Range, Element;
-
 	let doc, root, div, p, batch, chain, range;
 
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Element = modules[ 'core/treemodel/element' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 7 - 19
packages/ckeditor5-engine/tests/treemodel/delta/splitdelta.js

@@ -5,32 +5,20 @@
 
 /* bender-tags: treemodel, delta */
 
-/* bender-include: ../../_tools/tools.js */
-
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/position',
-	'core/treemodel/element',
-	'core/treemodel/attribute',
-	'core/ckeditorerror' );
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Batch', () => {
-	let Document, Position, Element, Attribute, CKEditorError;
-
 	let doc, root, p;
 
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 3 - 17
packages/ckeditor5-engine/tests/treemodel/delta/unwrapdelta.js

@@ -5,29 +5,15 @@
 
 /* bender-tags: treemodel, delta */
 
-/* bender-include: ../../_tools/tools.js */
-
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/position',
-	'core/treemodel/element',
-	'core/ckeditorerror'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Batch', () => {
-	let Document, Position, Element, CKEditorError;
-
 	let doc, root, p;
 
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Element = modules[ 'core/treemodel/element' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 3 - 13
packages/ckeditor5-engine/tests/treemodel/delta/weakinsertdelta.js

@@ -7,21 +7,11 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/attribute',
-	'core/treemodel/position'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
 
 describe( 'Batch', () => {
-	let Document, Attribute, Position;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		Position = modules[ 'core/treemodel/position' ];
-	} );
-
 	let doc, root, batch, chain, attrs;
 
 	beforeEach( () => {

+ 5 - 19
packages/ckeditor5-engine/tests/treemodel/delta/wrapdelta.js

@@ -5,31 +5,17 @@
 
 /* bender-tags: treemodel, delta */
 
-/* bender-include: ../../_tools/tools.js */
-
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/position',
-	'core/treemodel/range',
-	'core/treemodel/element',
-	'core/ckeditorerror'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Batch', () => {
-	let Document, Position, Range, Element, CKEditorError;
-
 	let doc, root, range;
 
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Element = modules[ 'core/treemodel/element' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	beforeEach( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );

+ 9 - 31
packages/ckeditor5-engine/tests/treemodel/document/change-event.js

@@ -7,39 +7,17 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/rootelement',
-	'core/treemodel/attribute',
-	'core/treemodel/position',
-	'core/treemodel/range',
-	'core/treemodel/operation/attributeoperation',
-	'core/treemodel/operation/insertoperation',
-	'core/treemodel/operation/moveoperation',
-	'core/treemodel/operation/reinsertoperation',
-	'core/treemodel/operation/removeoperation'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
+import InsertOperation from '/ckeditor5/core/treemodel/operation/insertoperation.js';
+import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
+import RemoveOperation from '/ckeditor5/core/treemodel/operation/removeoperation.js';
 
 describe( 'Document change event', () => {
-	let Document, RootElement, Element, Range, Position;
-	let AttributeOperation, InsertOperation, MoveOperation, ReinsertOperation, RemoveOperation, Attribute;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		RootElement = modules[ 'core/treemodel/rootelement' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-
-		InsertOperation = modules[ 'core/treemodel/operation/insertoperation' ];
-		AttributeOperation = modules[ 'core/treemodel/operation/attributeoperation' ];
-		MoveOperation = modules[ 'core/treemodel/operation/moveoperation' ];
-		ReinsertOperation = modules[ 'core/treemodel/operation/reinsertoperation' ];
-		RemoveOperation = modules[ 'core/treemodel/operation/removeoperation' ];
-	} );
-
 	let doc, root, graveyard, types, changes;
 
 	beforeEach( () => {

+ 4 - 15
packages/ckeditor5-engine/tests/treemodel/document/document.js

@@ -7,23 +7,12 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/rootelement',
-	'core/treemodel/batch',
-	'core/ckeditorerror'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import RootElement from '/ckeditor5/core/treemodel/rootelement.js';
+import Batch from '/ckeditor5/core/treemodel/batch.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Document', () => {
-	let Document, RootElement, Batch, CKEditorError;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		RootElement = modules[ 'core/treemodel/rootelement' ];
-		Batch = modules[ 'core/treemodel/batch' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	let doc;
 
 	beforeEach( () => {

+ 6 - 17
packages/ckeditor5-engine/tests/treemodel/element.js

@@ -4,29 +4,18 @@
  */
 
 /* bender-tags: treemodel */
-/* bender-include: ../_tools/tools.js */
 
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Node from '/ckeditor5/core/treemodel/node.js';
+import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/node',
-	'core/treemodel/nodelist',
-	'core/treemodel/element',
-	'core/treemodel/attribute'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Element', () => {
-	let Element, Node, NodeList, Attribute;
-
-	before( () => {
-		Element = modules[ 'core/treemodel/element' ];
-		Node = modules[ 'core/treemodel/node' ];
-		NodeList = modules[ 'core/treemodel/nodelist' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-	} );
-
 	describe( 'constructor', () => {
 		it( 'should create element without attributes', () => {
 			let element = new Element( 'elem' );

+ 5 - 16
packages/ckeditor5-engine/tests/treemodel/liveposition.js

@@ -7,27 +7,16 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/position',
-	'core/treemodel/liveposition',
-	'core/treemodel/range',
-	'core/emittermixin'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import LivePosition from '/ckeditor5/core/treemodel/liveposition.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
 
 describe( 'LivePosition', () => {
-	let Document, Element, Position, LivePosition, Range, EmitterMixin;
 	let doc, root, ul, p, li1, li2;
 
 	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Position = modules[ 'core/treemodel/position' ];
-		LivePosition = modules[ 'core/treemodel/liveposition' ];
-		Range = modules[ 'core/treemodel/range' ];
-		EmitterMixin = modules[ 'core/emittermixin' ];
-
 		doc = new Document();
 		root = doc.createRoot( 'root' );
 

+ 5 - 16
packages/ckeditor5-engine/tests/treemodel/liverange.js

@@ -7,27 +7,16 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/position',
-	'core/treemodel/liverange',
-	'core/treemodel/range',
-	'core/emittermixin'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import LiveRange from '/ckeditor5/core/treemodel/liverange.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
 
 describe( 'LiveRange', () => {
-	let Document, Element, Position, LiveRange, Range, EmitterMixin;
 	let doc, root, ul, p;
 
 	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Position = modules[ 'core/treemodel/position' ];
-		LiveRange = modules[ 'core/treemodel/liverange' ];
-		Range = modules[ 'core/treemodel/range' ];
-		EmitterMixin = modules[ 'core/emittermixin' ];
-
 		doc = new Document();
 		root = doc.createRoot( 'root' );
 

+ 6 - 19
packages/ckeditor5-engine/tests/treemodel/node.js

@@ -4,37 +4,24 @@
  */
 
 /* bender-tags: treemodel */
-/* bender-include: ../_tools/tools.js */
 
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/element',
-	'core/treemodel/character',
-	'core/treemodel/attribute',
-	'core/treemodel/attributelist',
-	'core/treemodel/nodelist',
-	'core/ckeditorerror'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Node', () => {
-	let Element, Character, Attribute, AttributeList, NodeList, CKEditorError;
-
 	let root;
 	let one, two, three;
 	let charB, charA, charR, img, attrEle;
 	let attrFooBar;
 
 	before( () => {
-		Element = modules[ 'core/treemodel/element' ];
-		Character = modules[ 'core/treemodel/character' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		AttributeList = modules[ 'core/treemodel/attributelist' ];
-		NodeList = modules[ 'core/treemodel/nodelist' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-
 		charB = new Character( 'b' );
 		charA = new Character( 'a' );
 		img = new Element( 'img' );

+ 4 - 15
packages/ckeditor5-engine/tests/treemodel/nodelist.js

@@ -7,23 +7,12 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/nodelist',
-	'core/treemodel/character',
-	'core/treemodel/text',
-	'core/treemodel/attribute'
-);
+import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
+import Text from '/ckeditor5/core/treemodel/text.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
 describe( 'NodeList', () => {
-	let NodeList, Character, Text, Attribute;
-
-	before( () => {
-		NodeList = modules[ 'core/treemodel/nodelist' ];
-		Character = modules[ 'core/treemodel/character' ];
-		Text = modules[ 'core/treemodel/text' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-	} );
-
 	describe( 'constructor', () => {
 		it( 'should change array of strings into a set of nodes', () => {
 			let nodeList = new NodeList( [ 'foo', new Character( 'x' ), 'bar' ] );

+ 11 - 29
packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js

@@ -4,41 +4,23 @@
  */
 
 /* bender-tags: treemodel, operation */
-/* global describe, before, beforeEach, it, expect */
-
-/* bender-include: ../../_tools/tools.js */
 
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import Text from '/ckeditor5/core/treemodel/text.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/operation/attributeoperation',
-	'core/treemodel/position',
-	'core/treemodel/range',
-	'core/treemodel/character',
-	'core/treemodel/attribute',
-	'core/treemodel/text',
-	'core/ckeditorerror'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'AttributeOperation', () => {
-	let Document, Element, AttributeOperation, Position, Range, Character, Attribute, Text, CKEditorError;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		AttributeOperation = modules[ 'core/treemodel/operation/attributeoperation' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Character = modules[ 'core/treemodel/character' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		Text = modules[ 'core/treemodel/text' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	let doc, root;
 
 	beforeEach( () => {

+ 7 - 23
packages/ckeditor5-engine/tests/treemodel/operation/insertoperation.js

@@ -4,34 +4,18 @@
  */
 
 /* bender-tags: treemodel, operation */
-/* global describe, before, beforeEach, it, expect */
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/node',
-	'core/treemodel/nodelist',
-	'core/treemodel/operation/insertoperation',
-	'core/treemodel/operation/removeoperation',
-	'core/treemodel/position',
-	'core/treemodel/character',
-	'core/treemodel/nodelist'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Node from '/ckeditor5/core/treemodel/node.js';
+import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
+import InsertOperation from '/ckeditor5/core/treemodel/operation/insertoperation.js';
+import RemoveOperation from '/ckeditor5/core/treemodel/operation/removeoperation.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
 
 describe( 'InsertOperation', () => {
-	let Document, Node, NodeList, InsertOperation, RemoveOperation, Position, Character;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Node = modules[ 'core/treemodel/node' ];
-		NodeList = modules[ 'core/treemodel/nodelist' ];
-		InsertOperation = modules[ 'core/treemodel/operation/insertoperation' ];
-		RemoveOperation = modules[ 'core/treemodel/operation/removeoperation' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Character = modules[ 'core/treemodel/character' ];
-	} );
-
 	let doc, root;
 
 	beforeEach( () => {

+ 6 - 20
packages/ckeditor5-engine/tests/treemodel/operation/moveoperation.js

@@ -4,31 +4,17 @@
  */
 
 /* bender-tags: treemodel, operation */
-/* global describe, before, beforeEach, it, expect */
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/operation/moveoperation',
-	'core/treemodel/position',
-	'core/treemodel/element',
-	'core/treemodel/nodelist',
-	'core/ckeditorerror'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'MoveOperation', () => {
-	let Document, MoveOperation, Position, Element, NodeList, CKEditorError;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		MoveOperation = modules[ 'core/treemodel/operation/moveoperation' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Element = modules[ 'core/treemodel/element' ];
-		NodeList = modules[ 'core/treemodel/nodelist' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-	} );
-
 	let doc, root;
 
 	beforeEach( () => {

+ 2 - 11
packages/ckeditor5-engine/tests/treemodel/operation/nooperation.js

@@ -7,19 +7,10 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/operation/nooperation'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import NoOperation from '/ckeditor5/core/treemodel/operation/nooperation.js';
 
 describe( 'NoOperation', () => {
-	let Document, NoOperation;
-
-	before( function() {
-		Document = modules[ 'core/treemodel/document' ];
-		NoOperation = modules[ 'core/treemodel/operation/nooperation' ];
-	} );
-
 	let noop, doc, root;
 
 	beforeEach( () => {

+ 5 - 17
packages/ckeditor5-engine/tests/treemodel/operation/reinsertoperation.js

@@ -7,25 +7,13 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/operation/reinsertoperation',
-	'core/treemodel/operation/removeoperation',
-	'core/treemodel/operation/moveoperation',
-	'core/treemodel/position'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import ReinsertOperation from '/ckeditor5/core/treemodel/operation/reinsertoperation.js';
+import RemoveOperation from '/ckeditor5/core/treemodel/operation/removeoperation.js';
+import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
 
 describe( 'ReinsertOperation', () => {
-	let Document, ReinsertOperation, RemoveOperation, MoveOperation, Position;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		ReinsertOperation = modules[ 'core/treemodel/operation/reinsertoperation' ];
-		RemoveOperation = modules[ 'core/treemodel/operation/removeoperation' ];
-		MoveOperation = modules[ 'core/treemodel/operation/moveoperation' ];
-		Position = modules[ 'core/treemodel/position' ];
-	} );
-
 	let doc, root, graveyard, operation, graveyardPosition, rootPosition;
 
 	beforeEach( () => {

+ 5 - 17
packages/ckeditor5-engine/tests/treemodel/operation/removeoperation.js

@@ -7,25 +7,13 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/operation/reinsertoperation',
-	'core/treemodel/operation/removeoperation',
-	'core/treemodel/operation/moveoperation',
-	'core/treemodel/position'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import ReinsertOperation from '/ckeditor5/core/treemodel/operation/reinsertoperation.js';
+import RemoveOperation from '/ckeditor5/core/treemodel/operation/removeoperation.js';
+import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
 
 describe( 'RemoveOperation', () => {
-	let Document, ReinsertOperation, RemoveOperation, MoveOperation, Position;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		ReinsertOperation = modules[ 'core/treemodel/operation/reinsertoperation' ];
-		RemoveOperation = modules[ 'core/treemodel/operation/removeoperation' ];
-		MoveOperation = modules[ 'core/treemodel/operation/removeoperation' ];
-		Position = modules[ 'core/treemodel/position' ];
-	} );
-
 	let doc, root, graveyard;
 
 	beforeEach( () => {

+ 10 - 32
packages/ckeditor5-engine/tests/treemodel/operation/transform.js

@@ -3,44 +3,22 @@
  * For licensing, see LICENSE.md.
  */
 
-/* jshint expr: true */
-
 /* bender-tags: treemodel, operation */
-/* global describe, before, beforeEach, it, expect */
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/rootelement',
-	'core/treemodel/node',
-	'core/treemodel/position',
-	'core/treemodel/range',
-	'core/treemodel/attribute',
-	'core/treemodel/operation/transform',
-	'core/treemodel/operation/insertoperation',
-	'core/treemodel/operation/attributeoperation',
-	'core/treemodel/operation/moveoperation',
-	'core/treemodel/operation/nooperation'
-);
+import RootElement from '/ckeditor5/core/treemodel/rootelement.js';
+import Node from '/ckeditor5/core/treemodel/node.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import transform from '/ckeditor5/core/treemodel/operation/transform.js';
+import InsertOperation from '/ckeditor5/core/treemodel/operation/insertoperation.js';
+import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
+import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
+import NoOperation from '/ckeditor5/core/treemodel/operation/nooperation.js';
 
 describe( 'transform', () => {
-	let RootElement, Node, Position, Range, Attribute, InsertOperation, AttributeOperation, MoveOperation, NoOperation;
-	let transform;
-
-	before( () => {
-		RootElement = modules[ 'core/treemodel/rootelement' ];
-		Node = modules[ 'core/treemodel/node' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		InsertOperation = modules[ 'core/treemodel/operation/insertoperation' ];
-		AttributeOperation = modules[ 'core/treemodel/operation/attributeoperation' ];
-		MoveOperation = modules[ 'core/treemodel/operation/moveoperation' ];
-		NoOperation = modules[ 'core/treemodel/operation/nooperation' ];
-
-		transform = modules[ 'core/treemodel/operation/transform' ];
-	} );
-
 	let root, op, nodeA, nodeB, expected, baseVersion;
 
 	beforeEach( () => {

+ 5 - 17
packages/ckeditor5-engine/tests/treemodel/position.js

@@ -7,18 +7,13 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/element',
-	'core/treemodel/character',
-	'core/treemodel/position',
-	'core/treemodel/document',
-	'core/ckeditorerror',
-	'core/treemodel/nodelist'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'position', () => {
-	let Element, Character, Document, NodeList, Position, CKEditorError;
-
 	let doc, root, otherRoot, p, ul, li1, li2, f, o, z, b, a, r;
 
 	// root
@@ -33,13 +28,6 @@ describe( 'position', () => {
 	//        |- a   Before: [ 1, 1, 1 ] After: [ 1, 1, 2 ]
 	//        |- r   Before: [ 1, 1, 2 ] After: [ 1, 1, 3 ]
 	before( () => {
-		Element = modules[ 'core/treemodel/element' ];
-		Character = modules[ 'core/treemodel/character' ];
-		Document = modules[ 'core/treemodel/document' ];
-		NodeList = modules[ 'core/treemodel/nodelist' ];
-		Position = modules[ 'core/treemodel/position' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-
 		doc = new Document();
 
 		root = doc.createRoot( 'root' );

+ 6 - 16
packages/ckeditor5-engine/tests/treemodel/positioniterator.js

@@ -7,29 +7,19 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/character',
-	'core/treemodel/positioniterator',
-	'core/treemodel/position',
-	'core/treemodel/range'
-);
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
+import PositionIterator from '/ckeditor5/core/treemodel/positioniterator.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
 
 describe( 'range iterator', () => {
-	let Document, Element, Character, PositionIterator, Position, Range;
 	let ELEMENT_ENTER, ELEMENT_LEAVE, CHARACTER;
 
 	let doc, expectedItems, root, img1, paragraph, b, a, r, img2, x;
 
 	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Character = modules[ 'core/treemodel/character' ];
-		PositionIterator = modules[ 'core/treemodel/positioniterator' ];
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-
 		ELEMENT_ENTER = PositionIterator.ELEMENT_ENTER;
 		ELEMENT_LEAVE = PositionIterator.ELEMENT_LEAVE;
 		CHARACTER = PositionIterator.CHARACTER;

+ 5 - 17
packages/ckeditor5-engine/tests/treemodel/range.js

@@ -7,25 +7,13 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/range',
-	'core/treemodel/position',
-	'core/treemodel/element',
-	'core/treemodel/character',
-	'core/treemodel/document'
-);
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Character from '/ckeditor5/core/treemodel/character.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
 
 describe( 'Range', () => {
-	let Range, Position, Element, Character, Document;
-
-	before( () => {
-		Position = modules[ 'core/treemodel/position' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Character = modules[ 'core/treemodel/character' ];
-		Document = modules[ 'core/treemodel/document' ];
-	} );
-
 	let range, start, end, root, otherRoot;
 
 	beforeEach( () => {

+ 5 - 15
packages/ckeditor5-engine/tests/treemodel/rootelement.js

@@ -4,27 +4,17 @@
  */
 
 /* bender-tags: treemodel */
-/* bender-include: ../_tools/tools.js */
 
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import RootElement from '/ckeditor5/core/treemodel/rootelement.js';
 
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/element',
-	'core/treemodel/rootelement'
-);
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Element', () => {
-	let Document, Element, RootElement;
-
-	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Element = modules[ 'core/treemodel/element' ];
-		RootElement = modules[ 'core/treemodel/rootelement' ];
-	} );
-
 	describe( 'constructor', () => {
 		it( 'should create root element without attributes', () => {
 			let doc = new Document();

+ 13 - 28
packages/ckeditor5-engine/tests/treemodel/selection.js

@@ -5,41 +5,26 @@
 
 /* bender-tags: treemodel */
 
-/* bender-include: ../_tools/tools.js */
-
 'use strict';
 
-const getIteratorCount = bender.tools.core.getIteratorCount;
-
-const modules = bender.amd.require(
-	'core/treemodel/document',
-	'core/treemodel/attribute',
-	'core/treemodel/element',
-	'core/treemodel/range',
-	'core/treemodel/position',
-	'core/treemodel/liverange',
-	'core/treemodel/selection',
-	'core/treemodel/operation/insertoperation',
-	'core/treemodel/operation/moveoperation',
-	'core/ckeditorerror'
-);
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Position from '/ckeditor5/core/treemodel/position.js';
+import LiveRange from '/ckeditor5/core/treemodel/liverange.js';
+import Selection from '/ckeditor5/core/treemodel/selection.js';
+import InsertOperation from '/ckeditor5/core/treemodel/operation/insertoperation.js';
+import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+
+const getIteratorCount = coreTestUtils.getIteratorCount;
 
 describe( 'Selection', () => {
-	let Document, Attribute, Element, Range, Position, LiveRange, Selection, InsertOperation, MoveOperation, CKEditorError;
 	let attrFooBar;
 
 	before( () => {
-		Document = modules[ 'core/treemodel/document' ];
-		Attribute = modules[ 'core/treemodel/attribute' ];
-		Element = modules[ 'core/treemodel/element' ];
-		Range = modules[ 'core/treemodel/range' ];
-		Position = modules[ 'core/treemodel/position' ];
-		LiveRange = modules[ 'core/treemodel/liverange' ];
-		Selection = modules[ 'core/treemodel/selection' ];
-		InsertOperation = modules[ 'core/treemodel/operation/insertoperation' ];
-		MoveOperation = modules[ 'core/treemodel/operation/moveoperation' ];
-		CKEditorError = modules[ 'core/ckeditorerror' ];
-
 		attrFooBar = new Attribute( 'foo', 'bar' );
 	} );
 

+ 2 - 7
packages/ckeditor5-engine/tests/treemodel/text.js

@@ -7,17 +7,12 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/treemodel/text',
-	'core/treemodel/attribute'
-);
+import Text from '/ckeditor5/core/treemodel/text.js';
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
 describe( 'Text', () => {
 	describe( 'constructor', () => {
 		it( 'should create character without attributes', () => {
-			const Text = modules[ 'core/treemodel/text' ];
-			const Attribute = modules[ 'core/treemodel/attribute' ];
-
 			let attrs = [ new Attribute( 'bold', true ) ];
 			let text = new Text( 'bar', attrs );
 

+ 31 - 0
packages/ckeditor5-engine/tests/treemodel/utils-tests/utils.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import treeModelTestUtils from '/tests/core/treemodel/_utils/utils.js';
+import Document from '/ckeditor5/core/treemodel/document.js';
+import Range from '/ckeditor5/core/treemodel/range.js';
+import Element from '/ckeditor5/core/treemodel/element.js';
+
+const getNodesAndText = treeModelTestUtils.getNodesAndText;
+
+describe( 'getNodesAndText', () => {
+	let doc, root, div, p;
+
+	beforeEach( () => {
+		doc = new Document();
+		root = doc.createRoot( 'root' );
+
+		div = new Element( 'div', [], 'foobar' );
+		p = new Element( 'p', [], 'abcxyz' );
+
+		root.insertChildren( 0, [ div, p ] );
+	} );
+
+	it( 'reads two elements with text', () => {
+		expect( getNodesAndText( Range.createFromElement( root ) ) ).to.equal( 'DIVfoobarDIVPabcxyzP' );
+	} );
+} );

+ 16 - 33
packages/ckeditor5-engine/tests/ui/controller.js

@@ -7,25 +7,19 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'ckeditor',
-	'core/ui/view',
-	'core/ui/controller',
-	'core/ui/controllercollection',
-	'core/ui/region',
-	'core/ckeditorerror',
-	'core/model',
-	'core/collection',
-	'core/eventinfo'
-);
-
-let View, Controller, Model, CKEditorError, Collection, ControllerCollection, EventInfo;
+import testUtils from '/tests/_utils/utils.js';
+import View from '/ckeditor5/core/ui/view.js';
+import Controller from '/ckeditor5/core/ui/controller.js';
+import ControllerCollection from '/ckeditor5/core/ui/controllercollection.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+import Model from '/ckeditor5/core/model.js';
+import EventInfo from '/ckeditor5/core/eventinfo.js';
+
 let ParentController, ParentView;
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 describe( 'Controller', () => {
-	beforeEach( updateModuleReference );
-
 	describe( 'constructor', () => {
 		it( 'defines basic properties', () => {
 			const controller = new Controller();
@@ -73,7 +67,7 @@ describe( 'Controller', () => {
 		it( 'should initialize own view', () => {
 			const view = new View();
 			const controller = new Controller( null, view );
-			const spy = bender.sinon.spy( view, 'init' );
+			const spy = testUtils.sinon.spy( view, 'init' );
 
 			return controller.init().then( () => {
 				sinon.assert.calledOnce( spy );
@@ -87,8 +81,8 @@ describe( 'Controller', () => {
 
 			const childController1 = new Controller();
 			const childController2 = new Controller();
-			const spy1 = bender.sinon.spy( childController1, 'init' );
-			const spy2 = bender.sinon.spy( childController2, 'init' );
+			const spy1 = testUtils.sinon.spy( childController1, 'init' );
+			const spy2 = testUtils.sinon.spy( childController2, 'init' );
 
 			buttonCollection.add( childController1 );
 			buttonCollection.add( childController2 );
@@ -127,7 +121,7 @@ describe( 'Controller', () => {
 				const parentController = new ParentController( null, parentView );
 				const collection = parentController.collections.get( 'x' );
 				const childController = new Controller( null, new View() );
-				const spy = bender.sinon.spy();
+				const spy = testUtils.sinon.spy();
 
 				parentView.regions.get( 'x' ).views.on( 'add', spy );
 
@@ -180,8 +174,7 @@ describe( 'Controller', () => {
 				const parentController = new ParentController( null, parentView );
 				const collection = parentController.collections.get( 'x' );
 				const childController = new Controller( null, new View() );
-
-				const spy = bender.sinon.spy();
+				const spy = testUtils.sinon.spy();
 				parentView.regions.get( 'x' ).views.on( 'remove', spy );
 
 				collection.add( childController );
@@ -206,7 +199,7 @@ describe( 'Controller', () => {
 		it( 'should destroy the controller', () => {
 			const view = new View();
 			const controller = new Controller( null, view );
-			const spy = bender.sinon.spy( view, 'destroy' );
+			const spy = testUtils.sinon.spy( view, 'destroy' );
 
 			return controller.init()
 				.then( () => {
@@ -241,7 +234,7 @@ describe( 'Controller', () => {
 			const collection = parentController.collections.get( 'x' );
 			const childView = new View();
 			const childController = new Controller( null, childView );
-			const spy = bender.sinon.spy( childView, 'destroy' );
+			const spy = testUtils.sinon.spy( childView, 'destroy' );
 
 			collection.add( childController );
 
@@ -277,16 +270,6 @@ describe( 'Controller', () => {
 	} );
 } );
 
-function updateModuleReference() {
-	View = modules[ 'core/ui/view' ];
-	Controller = modules[ 'core/ui/controller' ];
-	Model = modules[ 'core/model' ];
-	Collection = modules[ 'core/collection ' ];
-	ControllerCollection = modules[ 'core/ui/controllercollection' ];
-	CKEditorError = modules[ 'core/ckeditorerror' ];
-	EventInfo = modules[ 'core/eventinfo' ];
-}
-
 function defineParentViewClass() {
 	ParentView = class extends View {
 		constructor() {

+ 7 - 16
packages/ckeditor5-engine/tests/ui/controllercollection.js

@@ -7,19 +7,16 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'ckeditor',
-	'core/ui/controllercollection',
-	'core/ui/controller',
-	'core/ui/view'
-);
+import testUtils from '/tests/_utils/utils.js';
+import ControllerCollection from '/ckeditor5/core/ui/controllercollection.js';
+import Controller from '/ckeditor5/core/ui/controller.js';
+import View from '/ckeditor5/core/ui/view.js';
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
-let ControllerCollection, Controller, View;
 let ParentView;
 
 describe( 'ControllerCollection', () => {
-	beforeEach( updateModuleReference );
 	beforeEach( defineParentViewClass );
 
 	describe( 'constructor', () => {
@@ -62,7 +59,7 @@ describe( 'ControllerCollection', () => {
 		it( 'should initialize child controller if parent is ready', () => {
 			const parentController = new Controller( null, new ParentView() );
 			const childController = new Controller( null, new View() );
-			const spy = bender.sinon.spy( childController, 'init' );
+			const spy = testUtils.sinon.spy( childController, 'init' );
 			const collection = new ControllerCollection( 'x' );
 
 			parentController.collections.add( collection );
@@ -83,7 +80,7 @@ describe( 'ControllerCollection', () => {
 		it( 'should not initialize child controller twice', () => {
 			const parentController = new Controller( null, new ParentView() );
 			const childController = new Controller( null, new View() );
-			const spy = bender.sinon.spy( childController, 'init' );
+			const spy = testUtils.sinon.spy( childController, 'init' );
 			const collection = new ControllerCollection( 'x' );
 
 			parentController.collections.add( collection );
@@ -102,12 +99,6 @@ describe( 'ControllerCollection', () => {
 	} );
 } );
 
-function updateModuleReference() {
-	View = modules[ 'core/ui/view' ];
-	Controller = modules[ 'core/ui/controller' ];
-	ControllerCollection = modules[ 'core/ui/controllercollection' ];
-}
-
 function defineParentViewClass() {
 	ParentView = class extends View {
 		constructor() {

+ 40 - 36
packages/ckeditor5-engine/tests/ui/domemittermixin.js

@@ -8,13 +8,17 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/lib/lodash/object', 'core/ui/domemittermixin', 'core/emittermixin' );
+import testUtils from '/tests/_utils/utils.js';
+import objectUtils from '/ckeditor5/core/lib/lodash/object.js';
+import DOMEmitterMixin from '/ckeditor5/core/ui/domemittermixin.js';
+import EmitterMixin from '/ckeditor5/core/emittermixin.js';
+
 let emitter, domEmitter, node;
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
-let getEmitterInstance = () => modules[ 'core/lib/lodash/object' ].extend( {}, modules[ 'core/emittermixin' ] );
-let getDOMEmitterInstance = () => modules[ 'core/lib/lodash/object' ].extend( {}, modules[ 'core/ui/domemittermixin' ] );
+let getEmitterInstance = () => objectUtils.extend( {}, EmitterMixin );
+let getDOMEmitterInstance = () => objectUtils.extend( {}, DOMEmitterMixin );
 let getDOMNodeInstance = () => document.createElement( 'div' );
 
 function updateEmitterInstance() {
@@ -35,7 +39,7 @@ beforeEach( updateDOMNodeInstance );
 
 describe( 'listenTo', () => {
 	it( 'should listen to EmitterMixin events', () => {
-		let spy = bender.sinon.spy();
+		let spy = testUtils.sinon.spy();
 
 		domEmitter.listenTo( emitter, 'test', spy );
 		emitter.fire( 'test' );
@@ -44,7 +48,7 @@ describe( 'listenTo', () => {
 	} );
 
 	it( 'should listen to native DOM events', () => {
-		let spy = bender.sinon.spy();
+		let spy = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'test', spy );
 
@@ -57,8 +61,8 @@ describe( 'listenTo', () => {
 
 describe( 'stopListening', () => {
 	it( 'should stop listening to a specific event callback', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
+		let spy1 = testUtils.sinon.spy();
+		let spy2 = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'event1', spy1 );
 		domEmitter.listenTo( node, 'event2', spy2 );
@@ -76,9 +80,9 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should stop listening to an specific event', () => {
-		let spy1a = bender.sinon.spy();
-		let spy1b = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
+		let spy1a = testUtils.sinon.spy();
+		let spy1b = testUtils.sinon.spy();
+		let spy2 = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'event1', spy1a );
 		domEmitter.listenTo( node, 'event1', spy1b );
@@ -102,8 +106,8 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should stop listening to all events from a specific node', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
+		let spy1 = testUtils.sinon.spy();
+		let spy2 = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'event1', spy1 );
 		domEmitter.listenTo( node, 'event2', spy2 );
@@ -121,8 +125,8 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should stop listening to everything', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
+		let spy1 = testUtils.sinon.spy();
+		let spy2 = testUtils.sinon.spy();
 
 		let node1 = getDOMNodeInstance();
 		let node2 = getDOMNodeInstance();
@@ -147,7 +151,7 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should not stop other nodes when a non-listened node is provided', () => {
-		let spy = bender.sinon.spy();
+		let spy = testUtils.sinon.spy();
 
 		let node1 = getDOMNodeInstance();
 		let node2 = getDOMNodeInstance();
@@ -162,7 +166,7 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should pass DOM Event data to the listener', () => {
-		let spy = bender.sinon.spy();
+		let spy = testUtils.sinon.spy();
 
 		let node = getDOMNodeInstance();
 
@@ -180,13 +184,13 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should detach native DOM event listener proxy, specific event', () => {
-		let spy1a = bender.sinon.spy();
-		let spy1b = bender.sinon.spy();
+		let spy1a = testUtils.sinon.spy();
+		let spy1b = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'test', spy1a );
 
 		let proxyEmitter = domEmitter._getProxyEmitter( node );
-		let spy2 = bender.sinon.spy( proxyEmitter, 'fire' );
+		let spy2 = testUtils.sinon.spy( proxyEmitter, 'fire' );
 
 		node.dispatchEvent( new Event( 'test' ) );
 
@@ -211,15 +215,15 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should detach native DOM event listener proxy, specific callback', () => {
-		let spy1a = bender.sinon.spy();
-		let spy1b = bender.sinon.spy();
-		let spy1c = bender.sinon.spy();
+		let spy1a = testUtils.sinon.spy();
+		let spy1b = testUtils.sinon.spy();
+		let spy1c = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'test', spy1a );
 		domEmitter.listenTo( node, 'test', spy1b );
 
 		let proxyEmitter = domEmitter._getProxyEmitter( node );
-		let spy2 = bender.sinon.spy( proxyEmitter, 'fire' );
+		let spy2 = testUtils.sinon.spy( proxyEmitter, 'fire' );
 
 		node.dispatchEvent( new Event( 'test' ) );
 
@@ -254,16 +258,16 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should detach native DOM event listener proxy, specific emitter', () => {
-		let spy1a = bender.sinon.spy();
-		let spy1b = bender.sinon.spy();
-		let spy1c = bender.sinon.spy();
-		let spy1d = bender.sinon.spy();
+		let spy1a = testUtils.sinon.spy();
+		let spy1b = testUtils.sinon.spy();
+		let spy1c = testUtils.sinon.spy();
+		let spy1d = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'test1', spy1a );
 		domEmitter.listenTo( node, 'test2', spy1b );
 
 		let proxyEmitter = domEmitter._getProxyEmitter( node );
-		let spy2 = bender.sinon.spy( proxyEmitter, 'fire' );
+		let spy2 = testUtils.sinon.spy( proxyEmitter, 'fire' );
 
 		node.dispatchEvent( new Event( 'test1' ) );
 		node.dispatchEvent( new Event( 'test2' ) );
@@ -287,7 +291,7 @@ describe( 'stopListening', () => {
 
 		// Old proxy emitter died when stopped listening to the node.
 		let proxyEmitter2 = domEmitter._getProxyEmitter( node );
-		let spy3 = bender.sinon.spy( proxyEmitter2, 'fire' );
+		let spy3 = testUtils.sinon.spy( proxyEmitter2, 'fire' );
 
 		node.dispatchEvent( new Event( 'test1' ) );
 		node.dispatchEvent( new Event( 'test2' ) );
@@ -303,16 +307,16 @@ describe( 'stopListening', () => {
 	} );
 
 	it( 'should detach native DOM event listener proxy, everything', () => {
-		let spy1a = bender.sinon.spy();
-		let spy1b = bender.sinon.spy();
-		let spy1c = bender.sinon.spy();
-		let spy1d = bender.sinon.spy();
+		let spy1a = testUtils.sinon.spy();
+		let spy1b = testUtils.sinon.spy();
+		let spy1c = testUtils.sinon.spy();
+		let spy1d = testUtils.sinon.spy();
 
 		domEmitter.listenTo( node, 'test1', spy1a );
 		domEmitter.listenTo( node, 'test2', spy1b );
 
 		let proxyEmitter = domEmitter._getProxyEmitter( node );
-		let spy2 = bender.sinon.spy( proxyEmitter, 'fire' );
+		let spy2 = testUtils.sinon.spy( proxyEmitter, 'fire' );
 
 		node.dispatchEvent( new Event( 'test1' ) );
 		node.dispatchEvent( new Event( 'test2' ) );
@@ -336,7 +340,7 @@ describe( 'stopListening', () => {
 
 		// Old proxy emitter died when stopped listening to the node.
 		let proxyEmitter2 = domEmitter._getProxyEmitter( node );
-		let spy3 = bender.sinon.spy( proxyEmitter2, 'fire' );
+		let spy3 = testUtils.sinon.spy( proxyEmitter2, 'fire' );
 
 		node.dispatchEvent( new Event( 'test1' ) );
 		node.dispatchEvent( new Event( 'test2' ) );

+ 2 - 7
packages/ckeditor5-engine/tests/ui/region.js

@@ -8,11 +8,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/ui/region', 'core/ui/view' );
+import Region from '/ckeditor5/core/ui/region.js';
+import View from '/ckeditor5/core/ui/view.js';
 
-bender.tools.createSinonSandbox();
-
-let Region, View;
 let TestViewA, TestViewB;
 let region, el;
 
@@ -107,9 +105,6 @@ describe( 'View', () => {
 } );
 
 function createRegionInstance() {
-	Region = modules[ 'core/ui/region' ];
-	View = modules[ 'core/ui/view' ];
-
 	class A extends View {
 		constructor() {
 			super();

+ 15 - 21
packages/ckeditor5-engine/tests/ui/template.js

@@ -8,14 +8,12 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/ui/template' );
-let Template;
+import testUtils from '/tests/_utils/utils.js';
+import Template from '/ckeditor5/core/ui/template.js';
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 describe( 'Template', () => {
-	beforeEach( createClassReferences );
-
 	describe( 'constructor', () => {
 		it( 'accepts the definition', () => {
 			let def = {
@@ -75,8 +73,8 @@ describe( 'Template', () => {
 
 		describe( 'callback', () => {
 			it( 'works for attributes', () => {
-				let spy1 = bender.sinon.spy();
-				let spy2 = bender.sinon.spy();
+				let spy1 = testUtils.sinon.spy();
+				let spy2 = testUtils.sinon.spy();
 
 				let el = new Template( {
 					tag: 'p',
@@ -103,8 +101,8 @@ describe( 'Template', () => {
 			} );
 
 			it( 'works for "text" property', () => {
-				let spy1 = bender.sinon.spy();
-				let spy2 = bender.sinon.spy();
+				let spy1 = testUtils.sinon.spy();
+				let spy2 = testUtils.sinon.spy();
 
 				let el = new Template( {
 					tag: 'p',
@@ -128,10 +126,10 @@ describe( 'Template', () => {
 			} );
 
 			it( 'works for "on" property', () => {
-				let spy1 = bender.sinon.spy();
-				let spy2 = bender.sinon.spy();
-				let spy3 = bender.sinon.spy();
-				let spy4 = bender.sinon.spy();
+				let spy1 = testUtils.sinon.spy();
+				let spy2 = testUtils.sinon.spy();
+				let spy3 = testUtils.sinon.spy();
+				let spy4 = testUtils.sinon.spy();
 
 				let el = new Template( {
 					tag: 'p',
@@ -156,10 +154,10 @@ describe( 'Template', () => {
 			} );
 
 			it( 'works for "on" property with selectors', () => {
-				let spy1 = bender.sinon.spy();
-				let spy2 = bender.sinon.spy();
-				let spy3 = bender.sinon.spy();
-				let spy4 = bender.sinon.spy();
+				let spy1 = testUtils.sinon.spy();
+				let spy2 = testUtils.sinon.spy();
+				let spy3 = testUtils.sinon.spy();
+				let spy4 = testUtils.sinon.spy();
 
 				let el = new Template( {
 					tag: 'p',
@@ -194,7 +192,3 @@ describe( 'Template', () => {
 		} );
 	} );
 } );
-
-function createClassReferences() {
-	Template = modules[ 'core/ui/template' ];
-}

+ 22 - 33
packages/ckeditor5-engine/tests/ui/view.js

@@ -8,21 +8,17 @@
 
 'use strict';
 
-const modules = bender.amd.require(
-	'core/ui/view',
-	'core/ui/region',
-	'core/ckeditorerror',
-	'core/model'
-);
+import testUtils from '/tests/_utils/utils.js';
+import View from '/ckeditor5/core/ui/view.js';
+import Region from '/ckeditor5/core/ui/region.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
+import Model from '/ckeditor5/core/model.js';
 
-let View, TestView, Model, Region, CKEditorError;
-let view;
+let TestView, view;
 
-bender.tools.createSinonSandbox();
+testUtils.createSinonSandbox();
 
 describe( 'View', () => {
-	beforeEach( updateModuleReference );
-
 	describe( 'constructor', () => {
 		beforeEach( () => {
 			setTestViewClass();
@@ -73,8 +69,8 @@ describe( 'View', () => {
 			view.register( region1, el => el );
 			view.register( region2, el => el );
 
-			const spy1 = bender.sinon.spy( region1, 'init' );
-			const spy2 = bender.sinon.spy( region2, 'init' );
+			const spy1 = testUtils.sinon.spy( region1, 'init' );
+			const spy2 = testUtils.sinon.spy( region2, 'init' );
 
 			view.init();
 
@@ -189,7 +185,7 @@ describe( 'View', () => {
 
 		it( 'should not override an existing region with the same region with override flag', () => {
 			const region = new Region( 'x' );
-			const spy = bender.sinon.spy( view.regions, 'remove' );
+			const spy = testUtils.sinon.spy( view.regions, 'remove' );
 
 			view.register( region, true );
 			view.register( region, true, true );
@@ -229,7 +225,7 @@ describe( 'View', () => {
 		it( 'returns a function that passes arguments', () => {
 			setTestViewInstance( { a: 'foo' } );
 
-			let spy = bender.sinon.spy();
+			let spy = testUtils.sinon.spy();
 			let callback = view.bindToAttribute( 'a', spy );
 
 			expect( spy.called ).to.be.false;
@@ -333,7 +329,7 @@ describe( 'View', () => {
 
 	describe( 'on', () => {
 		it( 'accepts plain binding', () => {
-			let spy = bender.sinon.spy();
+			let spy = testUtils.sinon.spy();
 
 			setTestViewClass( function() {
 				return {
@@ -356,8 +352,8 @@ describe( 'View', () => {
 		} );
 
 		it( 'accepts an array of event bindings', () => {
-			let spy1 = bender.sinon.spy();
-			let spy2 = bender.sinon.spy();
+			let spy1 = testUtils.sinon.spy();
+			let spy2 = testUtils.sinon.spy();
 
 			setTestViewClass( function() {
 				return {
@@ -385,9 +381,9 @@ describe( 'View', () => {
 		} );
 
 		it( 'accepts DOM selectors', () => {
-			let spy1 = bender.sinon.spy();
-			let spy2 = bender.sinon.spy();
-			let spy3 = bender.sinon.spy();
+			let spy1 = testUtils.sinon.spy();
+			let spy2 = testUtils.sinon.spy();
+			let spy3 = testUtils.sinon.spy();
 
 			setTestViewClass( function() {
 				return {
@@ -470,8 +466,8 @@ describe( 'View', () => {
 		} );
 
 		it( 'accepts function callbacks', () => {
-			let spy1 = bender.sinon.spy();
-			let spy2 = bender.sinon.spy();
+			let spy1 = testUtils.sinon.spy();
+			let spy2 = testUtils.sinon.spy();
 
 			setTestViewClass( function() {
 				return {
@@ -503,7 +499,7 @@ describe( 'View', () => {
 		} );
 
 		it( 'supports event delegation', () => {
-			let spy = bender.sinon.spy();
+			let spy = testUtils.sinon.spy();
 
 			setTestViewClass( function() {
 				return {
@@ -531,7 +527,7 @@ describe( 'View', () => {
 		} );
 
 		it( 'works for future elements', () => {
-			let spy = bender.sinon.spy();
+			let spy = testUtils.sinon.spy();
 
 			setTestViewClass( function() {
 				return {
@@ -580,7 +576,7 @@ describe( 'View', () => {
 
 		it( 'destroys child regions', () => {
 			const region = new Region( 'x' );
-			const spy = bender.sinon.spy( region, 'destroy' );
+			const spy = testUtils.sinon.spy( region, 'destroy' );
 			const regionsRef = view.regions;
 			const regionViewsRef = region.views;
 
@@ -627,13 +623,6 @@ describe( 'View', () => {
 	} );
 } );
 
-function updateModuleReference() {
-	View = modules[ 'core/ui/view' ];
-	Region = modules[ 'core/ui/region' ];
-	Model = modules[ 'core/model' ];
-	CKEditorError = modules[ 'core/ckeditorerror' ];
-}
-
 function createViewInstanceWithTemplate() {
 	setTestViewClass( () => ( { tag: 'a' } ) );
 	setTestViewInstance();

+ 23 - 24
packages/ckeditor5-engine/tests/bender/tools.js → packages/ckeditor5-engine/tests/utils-tests/utils.js

@@ -3,55 +3,54 @@
  * For licensing, see LICENSE.md.
  */
 
-/* bender-include: ../_tools/tools.js */
-
 'use strict';
 
+import amdTestUtils from '/tests/_utils/amd.js';
+import coreTestUtils from '/tests/core/_utils/utils.js';
+import Creator from '/ckeditor5/core/creator.js';
+
 let createFn3 = () => {};
 let destroyFn3 = () => {};
 
-bender.tools.core.defineEditorCreatorMock( 'test1' );
-bender.tools.core.defineEditorCreatorMock( 'test2', {
+coreTestUtils.defineEditorCreatorMock( 'test1' );
+coreTestUtils.defineEditorCreatorMock( 'test2', {
 	foo: 1,
 	bar: 2
 } );
-bender.tools.core.defineEditorCreatorMock( 'test3', {
+coreTestUtils.defineEditorCreatorMock( 'test3', {
 	create: createFn3,
 	destroy: destroyFn3
 } );
 
-const modules = bender.amd.require( 'core/creator', 'creator-test1', 'creator-test2', 'creator-test3' );
-let Creator;
+const modules = amdTestUtils.require( {
+	testCreator1: 'creator-test1/creator-test1',
+	testCreator2: 'creator-test2/creator-test2',
+	testCreator3: 'creator-test3/creator-test3'
+} );
 
 ///////////////////
 
+let TestCreator1, TestCreator2, TestCreator3;
+
 before( () => {
-	Creator = modules[ 'core/creator' ];
+	TestCreator1 = modules.testCreator1;
+	TestCreator2 = modules.testCreator2;
+	TestCreator3 = modules.testCreator3;
 } );
 
-describe( 'bender.tools.core.defineEditorCreatorMock()', () => {
+describe( 'coreTestUtils.defineEditorCreatorMock()', () => {
 	it( 'should register all creators', () => {
-		const TestCreator1 = modules[ 'creator-test1' ];
-		const TestCreator2 = modules[ 'creator-test2' ];
-		const TestCreator3 = modules[ 'creator-test3' ];
-
 		expect( TestCreator1.prototype ).to.be.instanceof( Creator );
 		expect( TestCreator2.prototype ).to.be.instanceof( Creator );
 		expect( TestCreator3.prototype ).to.be.instanceof( Creator );
 	} );
 
 	it( 'should copy properties from the second argument', () => {
-		const TestCreator = modules[ 'creator-test2' ];
-
-		expect( TestCreator.prototype ).to.have.property( 'foo', 1 );
-		expect( TestCreator.prototype ).to.have.property( 'bar', 2 );
+		expect( TestCreator2.prototype ).to.have.property( 'foo', 1 );
+		expect( TestCreator2.prototype ).to.have.property( 'bar', 2 );
 	} );
 
 	it( 'should create spies for create() and destroy() if not defined', () => {
-		const TestCreator1 = modules[ 'creator-test1' ];
-		const TestCreator2 = modules[ 'creator-test2' ];
-		const TestCreator3 = modules[ 'creator-test3' ];
-
 		expect( TestCreator1.prototype.create ).to.have.property( 'called', false, 'test1.create' );
 		expect( TestCreator1.prototype.destroy ).to.have.property( 'called', false, 'test1.destroy' );
 		expect( TestCreator2.prototype.create ).to.have.property( 'called', false, 'test2.create' );
@@ -63,9 +62,9 @@ describe( 'bender.tools.core.defineEditorCreatorMock()', () => {
 	} );
 } );
 
-describe( 'bender.tools.core.getIteratorCount()', () => {
+describe( 'coreTestUtils.getIteratorCount()', () => {
 	it( 'should returns number of editable items ', () => {
-		const count = bender.tools.core.getIteratorCount( [ 1, 2, 3, 4, 5 ] );
+		const count = coreTestUtils.getIteratorCount( [ 1, 2, 3, 4, 5 ] );
 		expect( count ).to.equal( 5 );
 	} );
-} );
+} );

+ 1 - 7
packages/ckeditor5-engine/tests/utils.js

@@ -5,15 +5,9 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'core/utils' );
+import utils from '/ckeditor5/core/utils.js';
 
 describe( 'utils', () => {
-	let utils;
-
-	before( () => {
-		utils = modules[ 'core/utils' ];
-	} );
-
 	describe( 'spy', () => {
 		it( 'should not have `called` after creation', () => {
 			let spy = utils.spy();