8
0
Quellcode durchsuchen

Merge remote-tracking branch 'origin/master' into t/10

Conflicts:
	src/emitter.js
	src/utils.js
fredck vor 10 Jahren
Ursprung
Commit
ebc71d68da

+ 9 - 3
packages/ckeditor5-utils/LICENSE.md

@@ -6,11 +6,11 @@ Copyright (c) 2003-2015, [CKSource](http://cksource.com) Frederico Knabben. All
 
 Licensed under the terms of any of the following licenses at your choice:
 
- - [GNU General Public License Version 2 or later (the "GPL")](http://www.gnu.org/licenses/gpl.html)
+ * [GNU General Public License Version 2 or later (the "GPL")](http://www.gnu.org/licenses/gpl.html)
 
- - [GNU Lesser General Public License Version 2.1 or later (the "LGPL")](http://www.gnu.org/licenses/lgpl.html)
+ * [GNU Lesser General Public License Version 2.1 or later (the "LGPL")](http://www.gnu.org/licenses/lgpl.html)
 
- - [Mozilla Public License Version 1.1 or later (the "MPL")](http://www.mozilla.org/MPL/MPL-1.1.html)
+ * [Mozilla Public License Version 1.1 or later (the "MPL")](http://www.mozilla.org/MPL/MPL-1.1.html)
 
 You are not required to, but if you want to explicitly declare the license you have chosen to be bound to when using,
 reproducing, modifying and distributing this software, just include a text file titled "legal.txt" in your version of
@@ -24,6 +24,12 @@ Where not otherwise indicated, all CKEditor content is authored by CKSource engi
 intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource
 with their express permission.
 
+Third parties' software included:
+
+ * Lo-Dash (src/lib/lodash) <br>
+   Copyright 2012-2015 [The Dojo Foundation](http://dojofoundation.org/) <br>
+   Available under [MIT license](http://lodash.com/license)
+
 Trademarks
 ----------
 

+ 7 - 0
packages/ckeditor5-utils/dev/tasks/lodash.js

@@ -0,0 +1,7 @@
+/* jshint node: true */
+
+'use strict';
+
+module.exports = function( grunt ) {
+	grunt.loadNpmTasks( 'grunt-lodash' );
+};

+ 17 - 1
packages/ckeditor5-utils/gruntfile.js

@@ -8,6 +8,7 @@ module.exports = function( grunt ) {
 
 	// Files that will be ignored by the "jscs" and "jshint" tasks.
 	var ignoreFiles = [
+		'src/lib/**',
 		// Automatically loaded from .gitignore. Add more if necessary.
 	];
 
@@ -15,10 +16,25 @@ module.exports = function( grunt ) {
 	grunt.initConfig( {
 		pkg: grunt.file.readJSON( 'package.json' ),
 
+		lodash: {
+			build: {
+				dest: 'src/lib/lodash/lodash-ckeditor.js',
+				options: {
+					modifier: 'modern',
+					exports: 'amd',
+					flags: [
+						'debug'
+					],
+					include: require( './src/utils-lodash' )
+				}
+			}
+		},
+
 		jshint: {
 			options: {
 				globals: {
-					'CKEDITOR': false
+					'CKEDITOR': false,
+					'bender': false
 				},
 				ignores: ignoreFiles
 			}

+ 3 - 1
packages/ckeditor5-utils/package.json

@@ -11,7 +11,9 @@
     "grunt-jscs": "~1",
     "grunt-contrib-jshint": "~0",
     "grunt-githooks": "~0",
-    "shelljs": "~0"
+    "shelljs": "~0",
+    "lodash-cli": "~2.4.2",
+    "grunt-lodash": "~0.3.0"
   },
   "author": "CKSource (http://cksource.com/)",
   "license": "See LICENSE.md",

+ 48 - 43
packages/ckeditor5-utils/src/emitter.js

@@ -9,7 +9,7 @@
  * Mixin that injects the events API into its host.
  *
  * @class Emitter
- * @static
+ * @singleton
  */
 
 CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
@@ -21,15 +21,13 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 		 * @param {Function} callback The function to be called on event.
 		 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
 		 * event.
-		 * @param {Number} [priority] The priority of this callback in relation to other callbacks to that same event.
-		 * Lower values are called first. Defaults to `10`.
+		 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
+		 * Lower values are called first.
 		 */
 		on: function( event, callback, ctx, priority ) {
 			var callbacks = getCallbacks( this, event );
 
-			var wasAdded;
-
-			// Priority defaults to 10.
+			// Set the priority defaults.
 			if ( typeof priority != 'number' ) {
 				priority = 10;
 			}
@@ -41,29 +39,27 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 			};
 
 			// Add the callback to the list in the right priority position.
-			for ( var i = 0; i < callbacks.length ; i++ ) {
-				if ( callbacks[ i ].priority > priority ) {
-					callbacks.splice( i, 0, callback );
-					wasAdded = true;
-					break;
+			for ( var i = callbacks.length - 1; i >= 0; i-- ) {
+				if ( callbacks[ i ].priority <= priority ) {
+					callbacks.splice( i + 1, 0, callback );
+
+					return;
 				}
 			}
 
-			if ( !wasAdded ) {
-				callbacks.push( callback );
-			}
+			callbacks.unshift( callback );
 		},
 
 		/**
 		 * Registers a callback function to be executed on the next time the event is fired only. This is similar to
-		 * calling `on()` followed by `off()` in the callback.
+		 * calling {@link #on} followed by {@link #off} in the callback.
 		 *
 		 * @param {String} event The name of the event.
 		 * @param {Function} callback The function to be called on event.
 		 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to the object firing the
 		 * event.
-		 * @param {Number} [priority] The priority of this callback in relation to other callbacks to that same event.
-		 * Lower values are called first. Defaults to `10`.
+		 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
+		 * Lower values are called first.
 		 */
 		once: function( event, callback, ctx, priority ) {
 			var onceCallback = function( event ) {
@@ -83,8 +79,10 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 		 *
 		 * @param {String} event The name of the event.
 		 * @param {Function} callback The function to stop being called.
+		 * @param {Object} [ctx] The context object to be removed, pared with the given callback. To handle cases where
+		 * the same callback is used several times with different contexts.
 		 */
-		off: function( event, callback ) {
+		off: function( event, callback, ctx ) {
 			var callbacks = getCallbacksIfAny( this, event );
 
 			if ( !callbacks ) {
@@ -93,9 +91,11 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 
 			for ( var i = 0; i < callbacks.length; i++ ) {
 				if ( callbacks[ i ].callback == callback ) {
-					// Remove the callback from the list (fixing the next index).
-					callbacks.splice( i, 1 );
-					i--;
+					if ( !ctx || ctx == callbacks[ i ].ctx ) {
+						// Remove the callback from the list (fixing the next index).
+						callbacks.splice( i, 1 );
+						i--;
+					}
 				}
 			}
 		},
@@ -107,8 +107,8 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 		 * @param {String} event The name of the event.
 		 * @param {Function} callback The function to be called on event.
 		 * @param {Object} [ctx] The object that represents `this` in the callback. Defaults to `emitter`.
-		 * @param {Number} [priority] The priority of this callback in relation to other callbacks to that same event.
-		 * Lower values are called first. Defaults to `10`.
+		 * @param {Number} [priority=10] The priority of this callback in relation to other callbacks to that same event.
+		 * Lower values are called first.
 		 */
 		listenTo: function( emitter, event, callback, ctx, priority ) {
 			var emitters, emitterId, emitterInfo, eventCallbacks;
@@ -153,17 +153,17 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 		},
 
 		/**
-		 * Stops listening for events. It can be usued at different levels:
+		 * Stops listening for events. It can be used at different levels:
 		 *
-		 *  * To stop listening to a specific callback.
-		 *  * To stop listening to a specific event.
-		 *  * To stop listening to all events fired by a specific object.
-		 *  * To stop listening to all events fired by all object.
+		 * * To stop listening to a specific callback.
+		 * * To stop listening to a specific event.
+		 * * To stop listening to all events fired by a specific object.
+		 * * To stop listening to all events fired by all object.
 		 *
 		 * @param {Emitter} [emitter] The object to stop listening to. If omitted, stops it for all objects.
-		 * @param {String} [event] (Requires `emitter`) The name of the event to stop listening to. If omitted, stops it
+		 * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it
 		 * for all events from `emitter`.
-		 * @param {Function} callback (Requires `event`) The function be removed from the call list for the give
+		 * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given
 		 * `event`.
 		 */
 		stopListening: function( emitter, event, callback ) {
@@ -177,23 +177,26 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 				return;
 			}
 
+			// All params provided. off() that single callback.
 			if ( callback ) {
-				// All params provided. off() that single callback.
 				emitter.off( event, callback );
-			} else if ( eventCallbacks ) {
-				// Only emitter and event provided. off() all callbacks for that event.
+			}
+			// Only `emitter` and `event` provided. off() all callbacks for that event.
+			else if ( eventCallbacks ) {
 				while ( ( callback = eventCallbacks.pop() ) ) {
 					emitter.off( event, callback );
 				}
 				delete emitterInfo.callbacks[ event ];
-			} else if ( emitterInfo ) {
-				// Only emitter provided. off() all events for that emitter.
+			}
+			// Only `emitter` provided. off() all events for that emitter.
+			else if ( emitterInfo ) {
 				for ( event in emitterInfo.callbacks ) {
 					this.stopListening( emitter, event );
 				}
 				delete emitters[ emitterId ];
-			} else {
-				// No params provided. off() all emitters.
+			}
+			// No params provided. off() all emitters.
+			else {
 				for ( emitterId in emitters ) {
 					this.stopListening( emitters[ emitterId ].emitter );
 				}
@@ -202,9 +205,9 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 		},
 
 		/**
-		 * Fires and event, executing all callbacks registered for it.
+		 * Fires an event, executing all callbacks registered for it.
 		 *
-		 * The first parameter passed to callbacks is a {EventInfo} object, followed by the optional `args` provided in
+		 * The first parameter passed to callbacks is an {@link EventInfo} object, followed by the optional `args` provided in
 		 * the `fire()` method call.
 		 *
 		 * @param {String} event The name of the event.
@@ -233,10 +236,7 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 				for ( var i = 0; i < callbacks.length; i++ ) {
 					callbacks[ i ].callback.apply( callbacks[ i ].ctx, args );
 
-					if ( eventInfo.stop.called ) {
-						break;
-					}
-
+					// Remove the callback from future requests if off() has been called.
 					if ( eventInfo.off.called ) {
 						// Remove the called mark for the next calls.
 						delete eventInfo.off.called;
@@ -245,6 +245,11 @@ CKEDITOR.define( [ 'eventinfo', 'utils' ], function( EventInfo, utils ) {
 						callbacks.splice( i, 1 );
 						i--;
 					}
+
+					// Do not execute next callbacks if stop() was called.
+					if ( eventInfo.stop.called ) {
+						break;
+					}
 				}
 			}
 

+ 744 - 0
packages/ckeditor5-utils/src/lib/lodash/lodash-ckeditor.js

@@ -0,0 +1,744 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Build: `lodash modern exports="amd" include="extend" --debug --output src/lib/lodash/lodash-ckeditor.js`
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+  /** Used to detected named functions */
+  var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+  /** Used to detect functions containing a `this` reference */
+  var reThis = /\bthis\b/;
+
+  /** Used as the property descriptor for `__bindData__` */
+  var descriptor = {
+    'configurable': false,
+    'enumerable': false,
+    'value': null,
+    'writable': false
+  };
+
+  /** Used to determine if values are of the language type Object */
+  var objectTypes = {
+    'boolean': false,
+    'function': true,
+    'object': true,
+    'number': false,
+    'string': false,
+    'undefined': false
+  };
+
+  /** Used as a reference to the global object */
+  var root = (objectTypes[typeof window] && window) || this;
+
+  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+  var freeGlobal = objectTypes[typeof global] && global;
+  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+    root = freeGlobal;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Slices the `collection` from the `start` index up to, but not including,
+   * the `end` index.
+   *
+   * Note: This function is used instead of `Array#slice` to support node lists
+   * in IE < 9 and to ensure dense arrays are returned.
+   *
+   * @private
+   * @param {Array|Object|string} collection The collection to slice.
+   * @param {number} start The start index.
+   * @param {number} end The end index.
+   * @returns {Array} Returns the new array.
+   */
+  function slice(array, start, end) {
+    start || (start = 0);
+    if (typeof end == 'undefined') {
+      end = array ? array.length : 0;
+    }
+    var index = -1,
+        length = end - start || 0,
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = array[start + index];
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Used for `Array` method references.
+   *
+   * Normally `Array.prototype` would suffice, however, using an array literal
+   * avoids issues in Narwhal.
+   */
+  var arrayRef = [];
+
+  /** Used for native method references */
+  var objectProto = Object.prototype;
+
+  /** Used to resolve the internal [[Class]] of values */
+  var toString = objectProto.toString;
+
+  /** Used to detect if a method is native */
+  var reNative = RegExp('^' +
+    String(toString)
+      .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+      .replace(/toString| for [^\]]+/g, '.*?') + '$'
+  );
+
+  /** Native method shortcuts */
+  var fnToString = Function.prototype.toString,
+      hasOwnProperty = objectProto.hasOwnProperty,
+      push = arrayRef.push,
+      unshift = arrayRef.unshift;
+
+  /** Used to set meta data on functions */
+  var defineProperty = (function() {
+    // IE 8 only accepts DOM elements
+    try {
+      var o = {},
+          func = isNative(func = Object.defineProperty) && func,
+          result = func(o, o, o) && func;
+    } catch(e) { }
+    return result;
+  }());
+
+  /* Native method shortcuts for methods with the same name as other `lodash` methods */
+  var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+      nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a `lodash` object which wraps the given value to enable intuitive
+   * method chaining.
+   *
+   * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+   * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+   * and `unshift`
+   *
+   * Chaining is supported in custom builds as long as the `value` method is
+   * implicitly or explicitly included in the build.
+   *
+   * The chainable wrapper functions are:
+   * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+   * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+   * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+   * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+   * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+   * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+   * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+   * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+   * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+   * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+   * and `zip`
+   *
+   * The non-chainable wrapper functions are:
+   * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+   * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+   * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+   * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+   * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+   * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+   * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+   * `template`, `unescape`, `uniqueId`, and `value`
+   *
+   * The wrapper functions `first` and `last` return wrapped values when `n` is
+   * provided, otherwise they return unwrapped values.
+   *
+   * Explicit chaining can be enabled by using the `_.chain` method.
+   *
+   * @name _
+   * @constructor
+   * @category Chaining
+   * @param {*} value The value to wrap in a `lodash` instance.
+   * @returns {Object} Returns a `lodash` instance.
+   * @example
+   *
+   * var wrapped = _([1, 2, 3]);
+   *
+   * // returns an unwrapped value
+   * wrapped.reduce(function(sum, num) {
+   *   return sum + num;
+   * });
+   * // => 6
+   *
+   * // returns a wrapped value
+   * var squares = wrapped.map(function(num) {
+   *   return num * num;
+   * });
+   *
+   * _.isArray(squares);
+   * // => false
+   *
+   * _.isArray(squares.value());
+   * // => true
+   */
+  function lodash() {
+    // no operation performed
+  }
+
+  /**
+   * An object used to flag environments features.
+   *
+   * @static
+   * @memberOf _
+   * @type Object
+   */
+  var support = lodash.support = {};
+
+  /**
+   * Detect if functions can be decompiled by `Function#toString`
+   * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+   *
+   * @memberOf _.support
+   * @type boolean
+   */
+  support.funcDecomp = !isNative(root.WinRTError) && reThis.test(function() { return this; });
+
+  /**
+   * Detect if `Function#name` is supported (all but IE).
+   *
+   * @memberOf _.support
+   * @type boolean
+   */
+  support.funcNames = typeof Function.name == 'string';
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `_.bind` that creates the bound function and
+   * sets its meta data.
+   *
+   * @private
+   * @param {Array} bindData The bind data array.
+   * @returns {Function} Returns the new bound function.
+   */
+  function baseBind(bindData) {
+    var func = bindData[0],
+        partialArgs = bindData[2],
+        thisArg = bindData[4];
+
+    function bound() {
+      // `Function#bind` spec
+      // http://es5.github.io/#x15.3.4.5
+      if (partialArgs) {
+        // avoid `arguments` object deoptimizations by using `slice` instead
+        // of `Array.prototype.slice.call` and not assigning `arguments` to a
+        // variable as a ternary expression
+        var args = slice(partialArgs);
+        push.apply(args, arguments);
+      }
+      // mimic the constructor's `return` behavior
+      // http://es5.github.io/#x13.2.2
+      if (this instanceof bound) {
+        // ensure `new bound` is an instance of `func`
+        var thisBinding = baseCreate(func.prototype),
+            result = func.apply(thisBinding, args || arguments);
+        return isObject(result) ? result : thisBinding;
+      }
+      return func.apply(thisArg, args || arguments);
+    }
+    setBindData(bound, bindData);
+    return bound;
+  }
+
+  /**
+   * The base implementation of `_.create` without support for assigning
+   * properties to the created object.
+   *
+   * @private
+   * @param {Object} prototype The object to inherit from.
+   * @returns {Object} Returns the new object.
+   */
+  function baseCreate(prototype, properties) {
+    return isObject(prototype) ? nativeCreate(prototype) : {};
+  }
+  // fallback for browsers without `Object.create`
+  if (!nativeCreate) {
+    baseCreate = (function() {
+      function Object() {}
+      return function(prototype) {
+        if (isObject(prototype)) {
+          Object.prototype = prototype;
+          var result = new Object;
+          Object.prototype = null;
+        }
+        return result || root.Object();
+      };
+    }());
+  }
+
+  /**
+   * The base implementation of `_.createCallback` without support for creating
+   * "_.pluck" or "_.where" style callbacks.
+   *
+   * @private
+   * @param {*} [func=identity] The value to convert to a callback.
+   * @param {*} [thisArg] The `this` binding of the created callback.
+   * @param {number} [argCount] The number of arguments the callback accepts.
+   * @returns {Function} Returns a callback function.
+   */
+  function baseCreateCallback(func, thisArg, argCount) {
+    if (typeof func != 'function') {
+      return identity;
+    }
+    // exit early for no `thisArg` or already bound by `Function#bind`
+    if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+      return func;
+    }
+    var bindData = func.__bindData__;
+    if (typeof bindData == 'undefined') {
+      if (support.funcNames) {
+        bindData = !func.name;
+      }
+      bindData = bindData || !support.funcDecomp;
+      if (!bindData) {
+        var source = fnToString.call(func);
+        if (!support.funcNames) {
+          bindData = !reFuncName.test(source);
+        }
+        if (!bindData) {
+          // checks if `func` references the `this` keyword and stores the result
+          bindData = reThis.test(source);
+          setBindData(func, bindData);
+        }
+      }
+    }
+    // exit early if there are no `this` references or `func` is bound
+    if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+      return func;
+    }
+    switch (argCount) {
+      case 1: return function(value) {
+        return func.call(thisArg, value);
+      };
+      case 2: return function(a, b) {
+        return func.call(thisArg, a, b);
+      };
+      case 3: return function(value, index, collection) {
+        return func.call(thisArg, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(thisArg, accumulator, value, index, collection);
+      };
+    }
+    return bind(func, thisArg);
+  }
+
+  /**
+   * The base implementation of `createWrapper` that creates the wrapper and
+   * sets its meta data.
+   *
+   * @private
+   * @param {Array} bindData The bind data array.
+   * @returns {Function} Returns the new function.
+   */
+  function baseCreateWrapper(bindData) {
+    var func = bindData[0],
+        bitmask = bindData[1],
+        partialArgs = bindData[2],
+        partialRightArgs = bindData[3],
+        thisArg = bindData[4],
+        arity = bindData[5];
+
+    var isBind = bitmask & 1,
+        isBindKey = bitmask & 2,
+        isCurry = bitmask & 4,
+        isCurryBound = bitmask & 8,
+        key = func;
+
+    function bound() {
+      var thisBinding = isBind ? thisArg : this;
+      if (partialArgs) {
+        var args = slice(partialArgs);
+        push.apply(args, arguments);
+      }
+      if (partialRightArgs || isCurry) {
+        args || (args = slice(arguments));
+        if (partialRightArgs) {
+          push.apply(args, partialRightArgs);
+        }
+        if (isCurry && args.length < arity) {
+          bitmask |= 16 & ~32;
+          return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+        }
+      }
+      args || (args = arguments);
+      if (isBindKey) {
+        func = thisBinding[key];
+      }
+      if (this instanceof bound) {
+        thisBinding = baseCreate(func.prototype);
+        var result = func.apply(thisBinding, args);
+        return isObject(result) ? result : thisBinding;
+      }
+      return func.apply(thisBinding, args);
+    }
+    setBindData(bound, bindData);
+    return bound;
+  }
+
+  /**
+   * Creates a function that, when called, either curries or invokes `func`
+   * with an optional `this` binding and partially applied arguments.
+   *
+   * @private
+   * @param {Function|string} func The function or method name to reference.
+   * @param {number} bitmask The bitmask of method flags to compose.
+   *  The bitmask may be composed of the following flags:
+   *  1 - `_.bind`
+   *  2 - `_.bindKey`
+   *  4 - `_.curry`
+   *  8 - `_.curry` (bound)
+   *  16 - `_.partial`
+   *  32 - `_.partialRight`
+   * @param {Array} [partialArgs] An array of arguments to prepend to those
+   *  provided to the new function.
+   * @param {Array} [partialRightArgs] An array of arguments to append to those
+   *  provided to the new function.
+   * @param {*} [thisArg] The `this` binding of `func`.
+   * @param {number} [arity] The arity of `func`.
+   * @returns {Function} Returns the new function.
+   */
+  function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+    var isBind = bitmask & 1,
+        isBindKey = bitmask & 2,
+        isCurry = bitmask & 4,
+        isCurryBound = bitmask & 8,
+        isPartial = bitmask & 16,
+        isPartialRight = bitmask & 32;
+
+    if (!isBindKey && !isFunction(func)) {
+      throw new TypeError;
+    }
+    if (isPartial && !partialArgs.length) {
+      bitmask &= ~16;
+      isPartial = partialArgs = false;
+    }
+    if (isPartialRight && !partialRightArgs.length) {
+      bitmask &= ~32;
+      isPartialRight = partialRightArgs = false;
+    }
+    var bindData = func && func.__bindData__;
+    if (bindData && bindData !== true) {
+      // clone `bindData`
+      bindData = slice(bindData);
+      if (bindData[2]) {
+        bindData[2] = slice(bindData[2]);
+      }
+      if (bindData[3]) {
+        bindData[3] = slice(bindData[3]);
+      }
+      // set `thisBinding` is not previously bound
+      if (isBind && !(bindData[1] & 1)) {
+        bindData[4] = thisArg;
+      }
+      // set if previously bound but not currently (subsequent curried functions)
+      if (!isBind && bindData[1] & 1) {
+        bitmask |= 8;
+      }
+      // set curried arity if not yet set
+      if (isCurry && !(bindData[1] & 4)) {
+        bindData[5] = arity;
+      }
+      // append partial left arguments
+      if (isPartial) {
+        push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+      }
+      // append partial right arguments
+      if (isPartialRight) {
+        unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+      }
+      // merge flags
+      bindData[1] |= bitmask;
+      return createWrapper.apply(null, bindData);
+    }
+    // fast path for `_.bind`
+    var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+    return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+  }
+
+  /**
+   * Checks if `value` is a native function.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+   */
+  function isNative(value) {
+    return typeof value == 'function' && reNative.test(value);
+  }
+
+  /**
+   * Sets `this` binding data on a given function.
+   *
+   * @private
+   * @param {Function} func The function to set data on.
+   * @param {Array} value The data array to set.
+   */
+  var setBindData = !defineProperty ? noop : function(func, value) {
+    descriptor.value = value;
+    defineProperty(func, '__bindData__', descriptor);
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A fallback implementation of `Object.keys` which produces an array of the
+   * given object's own enumerable property names.
+   *
+   * @private
+   * @type Function
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns an array of property names.
+   */
+  var shimKeys = function(object) {
+    var index, iterable = object, result = [];
+    if (!iterable) return result;
+    if (!(objectTypes[typeof object])) return result;
+      for (index in iterable) {
+        if (hasOwnProperty.call(iterable, index)) {
+          result.push(index);
+        }
+      }
+    return result
+  };
+
+  /**
+   * Creates an array composed of the own enumerable property names of an object.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns an array of property names.
+   * @example
+   *
+   * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+   * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+   */
+  var keys = !nativeKeys ? shimKeys : function(object) {
+    if (!isObject(object)) {
+      return [];
+    }
+    return nativeKeys(object);
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Assigns own enumerable properties of source object(s) to the destination
+   * object. Subsequent sources will overwrite property assignments of previous
+   * sources. If a callback is provided it will be executed to produce the
+   * assigned values. The callback is bound to `thisArg` and invoked with two
+   * arguments; (objectValue, sourceValue).
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @alias extend
+   * @category Objects
+   * @param {Object} object The destination object.
+   * @param {...Object} [source] The source objects.
+   * @param {Function} [callback] The function to customize assigning values.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns the destination object.
+   * @example
+   *
+   * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+   * // => { 'name': 'fred', 'employer': 'slate' }
+   *
+   * var defaults = _.partialRight(_.assign, function(a, b) {
+   *   return typeof a == 'undefined' ? b : a;
+   * });
+   *
+   * var object = { 'name': 'barney' };
+   * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+   * // => { 'name': 'barney', 'employer': 'slate' }
+   */
+  var assign = function(object, source, guard) {
+    var index, iterable = object, result = iterable;
+    if (!iterable) return result;
+    var args = arguments,
+        argsIndex = 0,
+        argsLength = typeof guard == 'number' ? 2 : args.length;
+    if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
+      var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
+    } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
+      callback = args[--argsLength];
+    }
+    while (++argsIndex < argsLength) {
+      iterable = args[argsIndex];
+      if (iterable && objectTypes[typeof iterable]) {
+      var ownIndex = -1,
+          ownProps = objectTypes[typeof iterable] && keys(iterable),
+          length = ownProps ? ownProps.length : 0;
+
+      while (++ownIndex < length) {
+        index = ownProps[ownIndex];
+        result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
+      }
+      }
+    }
+    return result
+  };
+
+  /**
+   * Checks if `value` is a function.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+   * @example
+   *
+   * _.isFunction(_);
+   * // => true
+   */
+  function isFunction(value) {
+    return typeof value == 'function';
+  }
+
+  /**
+   * Checks if `value` is the language type of Object.
+   * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+   * @example
+   *
+   * _.isObject({});
+   * // => true
+   *
+   * _.isObject([1, 2, 3]);
+   * // => true
+   *
+   * _.isObject(1);
+   * // => false
+   */
+  function isObject(value) {
+    // check if the value is the ECMAScript language type of Object
+    // http://es5.github.io/#x8
+    // and avoid a V8 bug
+    // http://code.google.com/p/v8/issues/detail?id=2291
+    return !!(value && objectTypes[typeof value]);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a function that, when called, invokes `func` with the `this`
+   * binding of `thisArg` and prepends any additional `bind` arguments to those
+   * provided to the bound function.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to bind.
+   * @param {*} [thisArg] The `this` binding of `func`.
+   * @param {...*} [arg] Arguments to be partially applied.
+   * @returns {Function} Returns the new bound function.
+   * @example
+   *
+   * var func = function(greeting) {
+   *   return greeting + ' ' + this.name;
+   * };
+   *
+   * func = _.bind(func, { 'name': 'fred' }, 'hi');
+   * func();
+   * // => 'hi fred'
+   */
+  function bind(func, thisArg) {
+    return arguments.length > 2
+      ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+      : createWrapper(func, 1, null, null, thisArg);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * This method returns the first argument provided to it.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {*} value Any value.
+   * @returns {*} Returns `value`.
+   * @example
+   *
+   * var object = { 'name': 'fred' };
+   * _.identity(object) === object;
+   * // => true
+   */
+  function identity(value) {
+    return value;
+  }
+
+  /**
+   * A no-operation function.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @example
+   *
+   * var object = { 'name': 'fred' };
+   * _.noop(object) === undefined;
+   * // => true
+   */
+  function noop() {
+    // no operation performed
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  lodash.assign = assign;
+  lodash.bind = bind;
+  lodash.keys = keys;
+
+  lodash.extend = assign;
+
+  /*--------------------------------------------------------------------------*/
+
+  lodash.identity = identity;
+  lodash.isFunction = isFunction;
+  lodash.isObject = isObject;
+  lodash.noop = noop;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The semantic version number.
+   *
+   * @static
+   * @memberOf _
+   * @type string
+   */
+  lodash.VERSION = '2.4.1';
+
+  /*--------------------------------------------------------------------------*/
+
+  // some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module
+    define(function() {
+      return lodash;
+    });
+  }
+
+}.call(this));

+ 28 - 0
packages/ckeditor5-utils/src/promise.js

@@ -0,0 +1,28 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals window */
+
+'use strict';
+
+/**
+ * An ES6 compatible Promise class, used for deferred and asynchronous computations.
+ *
+ * @class Promise
+ */
+
+CKEDITOR.define( function() {
+	// For now we're using the native browser implementation of Promise, an ES6 feature. Just IE is not supporting it so
+	// a polyfill will have to be developed for it.
+	//
+	// http://caniuse.com/#feat=promises
+
+	/* istanbul ignore next: we expect this to never happen for now, so we'll not have coverage for this */
+	if ( !window.Promise ) {
+		throw 'The Promise class is not available natively. CKEditor is not compatible with this browser.';
+	}
+
+	return window.Promise;
+} );

+ 38 - 0
packages/ckeditor5-utils/src/utils-lodash.js

@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals module */
+
+'use strict';
+
+// This module returns the list of Lo-Dash methods that will be exported to the main "utils" module. It is coded in a
+// way that it can be used by the CKEditor core code in "utils" as well as from Node.js with the configurations for
+// `grunt lodash`.
+//
+// https://lodash.com/docs
+
+( function() {
+	// The list of Lo-Dash methods to include in "utils".
+	// It is mandatory to execute `grunt lodash` after changes to this list.
+	var lodashInclude = [
+		/**
+		 * See Lo-Dash: https://lodash.com/docs#assign (alias)
+		 *
+		 * @member utils
+		 * @method extend
+		 */
+		'extend'
+	];
+
+	// Make this compatible with CommonJS as well so it can be used in Node (e.g. "grunt lodash").
+	/* istanbul ignore next: we're not able to test the following in bender so ignore it */
+	if ( typeof module == 'object' && module.exports ) {
+		module.exports = lodashInclude;
+	} else {
+		CKEDITOR.define( function() {
+			return lodashInclude;
+		} );
+	}
+} )();

+ 6 - 53
packages/ckeditor5-utils/src/utils.js

@@ -12,61 +12,9 @@
  * @singleton
  */
 
-CKEDITOR.define( function() {
+CKEDITOR.define( [ 'utils-lodash', 'lib/lodash/lodash-ckeditor' ], function( lodashIncludes, lodash ) {
 	var utils = {
 		/**
-		 * Extends one JavaScript object with the properties defined in one or more objects. Existing properties are
-		 * overridden.
-		 *
-		 * @param {Object} target The object to be extended.
-		 * @param {Object} source One or more objects which properties will be copied (by reference) to `target`.
-		 * @returns {Object} The `target` object.
-		 */
-		extend: function( target, source ) {
-			if ( !this.isObject( source ) && !this.isFunction( source ) ) {
-				return target;
-			}
-
-			if ( arguments.length > 2 ) {
-				var args = Array.prototype.splice.call( arguments, 1 );
-
-				while ( args.length ) {
-					this.extend( target, args.shift() );
-				}
-			} else {
-				var keys = Object.keys( source );
-
-				while ( keys.length ) {
-					var key = keys.shift();
-					target[ key ] = source[ key ];
-				}
-			}
-
-			return target;
-		},
-
-		/**
-		 * Checks if the provided object is a JavaScript function.
-		 *
-		 * @param obj The object to be checked.
-		 * @returns {Boolean} `true` if the provided object is a JavaScript function. Otherwise `false`.
-		 */
-		isFunction: function( obj ) {
-			return typeof obj == 'function';
-		},
-
-		/**
-		 * Checks if the provided object is a "pure" JavaScript object. In other words, if it is not any other
-		 * JavaScript native type, like Number or String.
-		 *
-		 * @param obj The object to be checked.
-		 * @returns {Boolean} `true` if the provided object is a "pure" JavaScript object. Otherwise `false`.
-		 */
-		isObject: function( obj ) {
-			return typeof obj === 'object' && !!obj;
-		},
-
-		/**
 		 * A mixin function to be used to implement the static `extend()` method in classes. It allows for easy creation
 		 * of subclasses.
 		 *
@@ -128,6 +76,11 @@ CKEDITOR.define( function() {
 		} )()
 	};
 
+	// Extend "utils" with Lo-Dash methods.
+	for ( var i = 0; i < lodashIncludes.length; i++ ) {
+		utils[ lodashIncludes[ i ] ] = lodash[ lodashIncludes[ i ] ];
+	}
+
 	return utils;
 } );
 

+ 86 - 14
packages/ckeditor5-utils/tests/emitter/emitter.js

@@ -15,9 +15,9 @@ beforeEach( refreshEmitter );
 
 describe( 'fire', function() {
 	it( 'should execute callbacks in the right order without priority', function() {
-		var spy1 = sinon.spy();
-		var spy2 = sinon.spy();
-		var spy3 = sinon.spy();
+		var spy1 = sinon.spy().named( 1 );
+		var spy2 = sinon.spy().named( 2 );
+		var spy3 = sinon.spy().named( 3 );
 
 		emitter.on( 'test', spy1 );
 		emitter.on( 'test', spy2 );
@@ -29,11 +29,11 @@ describe( 'fire', function() {
 	} );
 
 	it( 'should execute callbacks in the right order with priority defined', function() {
-		var spy1 = sinon.spy();
-		var spy2 = sinon.spy();
-		var spy3 = sinon.spy();
-		var spy4 = sinon.spy();
-		var spy5 = sinon.spy();
+		var spy1 = sinon.spy().named( 1 );
+		var spy2 = sinon.spy().named( 2 );
+		var spy3 = sinon.spy().named( 3 );
+		var spy4 = sinon.spy().named( 4 );
+		var spy5 = sinon.spy().named( 5 );
 
 		emitter.on( 'test', spy2, null, 9 );
 		emitter.on( 'test', spy3 );	// Defaults to 10.
@@ -161,6 +161,23 @@ describe( 'on', function() {
 		sinon.assert.calledOnce( spy2 );
 		sinon.assert.calledTwice( spy3 );
 	} );
+
+	it( 'should take the callback off() even after stop()', function() {
+		var spy1 = sinon.spy( function( event ) {
+			event.stop();
+			event.off();
+		} );
+		var spy2 = sinon.spy();
+
+		emitter.on( 'test', spy1 );
+		emitter.on( 'test', spy2 );
+
+		emitter.fire( 'test' );
+		emitter.fire( 'test' );
+
+		sinon.assert.calledOnce( spy1 );
+		sinon.assert.calledOnce( spy2 );
+	} );
 } );
 
 describe( 'once', function() {
@@ -210,7 +227,7 @@ describe( 'once', function() {
 } );
 
 describe( 'off', function() {
-	it( 'should get callbacks off', function() {
+	it( 'should get callbacks off()', function() {
 		var spy1 = sinon.spy();
 		var spy2 = sinon.spy();
 		var spy3 = sinon.spy();
@@ -231,9 +248,49 @@ describe( 'off', function() {
 		sinon.assert.calledThrice( spy3 );
 	} );
 
-	it( 'should no fail with unknown events', function() {
+	it( 'should not fail with unknown events', function() {
 		emitter.off( 'test', function() {} );
 	} );
+
+	it( 'should remove all entries for the same callback', function() {
+		var spy1 = sinon.spy().named( 1 );
+		var spy2 = sinon.spy().named( 2 );
+
+		emitter.on( 'test', spy1 );
+		emitter.on( 'test', spy2 );
+		emitter.on( 'test', spy1 );
+		emitter.on( 'test', spy2 );
+
+		emitter.fire( 'test' );
+
+		emitter.off( 'test', spy1 );
+
+		emitter.fire( 'test' );
+
+		sinon.assert.callCount( spy1, 2 );
+		sinon.assert.callCount( spy2, 4 );
+	} );
+
+	it( 'should remove the callback for a specific context only', function() {
+		var spy = sinon.spy().named( 1 );
+
+		var ctx1 = { ctx: 1 };
+		var ctx2 = { ctx: 2 };
+
+		emitter.on( 'test', spy, ctx1 );
+		emitter.on( 'test', spy, ctx2 );
+
+		emitter.fire( 'test' );
+
+		spy.reset();
+
+		emitter.off( 'test', spy, ctx1 );
+
+		emitter.fire( 'test' );
+
+		sinon.assert.calledOnce( spy );
+		sinon.assert.calledOn( spy, ctx2 );
+	} );
 } );
 
 describe( 'listenTo', function() {
@@ -253,7 +310,7 @@ describe( 'listenTo', function() {
 describe( 'stopListening', function() {
 	beforeEach( refreshListener );
 
-	it( 'should stop listening callback on event', function() {
+	it( 'should stop listening to a specific event callback', function() {
 		var spy1 = sinon.spy();
 		var spy2 = sinon.spy();
 
@@ -272,7 +329,7 @@ describe( 'stopListening', function() {
 		sinon.assert.calledTwice( spy2 );
 	} );
 
-	it( 'should stop listening event', function() {
+	it( 'should stop listening to an specific event', function() {
 		var spy1a = sinon.spy();
 		var spy1b = sinon.spy();
 		var spy2 = sinon.spy();
@@ -294,7 +351,7 @@ describe( 'stopListening', function() {
 		sinon.assert.calledTwice( spy2 );
 	} );
 
-	it( 'should stop listening all events for emitter', function() {
+	it( 'should stop listening to all events from a specific emitter', function() {
 		var spy1 = sinon.spy();
 		var spy2 = sinon.spy();
 
@@ -313,7 +370,7 @@ describe( 'stopListening', function() {
 		sinon.assert.calledOnce( spy2 );
 	} );
 
-	it( 'should stop listening everything', function() {
+	it( 'should stop listening to everything', function() {
 		var spy1 = sinon.spy();
 		var spy2 = sinon.spy();
 
@@ -338,6 +395,21 @@ describe( 'stopListening', function() {
 
 		expect( listener ).to.not.have.property( '_listeningTo' );
 	} );
+
+	it( 'should not stop other emitters when a non-listened emitter is provided', function() {
+		var spy = sinon.spy();
+
+		var emitter1 = getEmitterInstance();
+		var emitter2 = getEmitterInstance();
+
+		listener.listenTo( emitter1, 'test', spy );
+
+		listener.stopListening( emitter2 );
+
+		emitter1.fire( 'test' );
+
+		sinon.assert.called( spy );
+	} );
 } );
 
 describe( 'addParentEmitter', function() {

+ 1 - 1
packages/ckeditor5-utils/tests/eventinfo/eventinfo.js

@@ -21,7 +21,7 @@ describe( 'EventInfo', function() {
 		expect( event.off.called ).to.not.be.true();
 	} );
 
-	it( 'should have cancel() and off() marked', function() {
+	it( 'should have stop() and off() marked', function() {
 		var EventInfo = modules.eventinfo;
 
 		var event = new EventInfo( this, 'test' );

+ 24 - 0
packages/ckeditor5-utils/tests/promise/promise.js

@@ -0,0 +1,24 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals describe, it, expect */
+
+'use strict';
+
+var modules = bender.amd.require( 'promise' );
+
+describe( 'Promise', function() {
+	it( 'should resolve properly', function() {
+		var Promise = modules.promise;
+
+		var promise = new Promise( function( resolve ) {
+			resolve( 1 );
+		} );
+
+		return promise.then( function( value ) {
+			expect( value ).to.equal( 1 );
+		} );
+	} );
+} );

+ 51 - 1
packages/ckeditor5-utils/tests/utils/utils.js

@@ -7,7 +7,37 @@
 
 'use strict';
 
-var modules = bender.amd.require( 'utils' );
+var modules = bender.amd.require( 'utils', 'utils-lodash' );
+
+describe( 'extend()', function() {
+	// 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.
+	it( 'should extend by several params in the correct order', function() {
+		var utils = modules.utils;
+
+		var target = {
+			a: 0,
+			b: 0
+		};
+
+		var ext1 = {
+			b: 1,
+			c: 1
+		};
+
+		var ext2 = {
+			c: 2,
+			d: 2
+		};
+
+		utils.extend( target, ext1, ext2 );
+
+		expect( target ).to.have.property( 'a' ).to.equal( 0 );
+		expect( target ).to.have.property( 'b' ).to.equal( 1 );
+		expect( target ).to.have.property( 'c' ).to.equal( 2 );
+		expect( target ).to.have.property( 'd' ).to.equal( 2 );
+	} );
+} );
 
 describe( 'extendMixin', function() {
 	it( 'should extend classes', function() {
@@ -49,6 +79,14 @@ describe( 'extendMixin', function() {
 } );
 
 describe( 'spy', function() {
+	it( 'should not have `called` after creation', function() {
+		var utils = modules.utils;
+
+		var spy = utils.spy();
+
+		expect( spy.called ).to.not.be.true();
+	} );
+
 	it( 'should register calls', function() {
 		var utils = modules.utils;
 
@@ -75,3 +113,15 @@ describe( 'uid', function() {
 		expect( id3 ).to.be.a( 'number' ).to.not.equal( id1 ).to.not.equal( id2 );
 	} );
 } );
+
+describe( 'Lo-Dash extensions', function() {
+	// Ensures that the required Lo-Dash extensions are available in `utils`.
+	it( 'should be exposed in utils', function() {
+		var utils = modules.utils;
+		var extensions = modules[ 'utils-lodash' ];
+
+		extensions.forEach( function( extension ) {
+			expect( utils ).to.have.property( extension ).to.not.be.undefined();
+		} );
+	} );
+} );