mimosa-ember-test
Version:
A Mimosa module for testing Ember apps
1,684 lines (1,332 loc) • 1.56 MB
JavaScript
/*!
* @overview Ember - JavaScript Application Framework
* @copyright Copyright 2011-2014 Tilde Inc. and contributors
* Portions Copyright 2006-2011 Strobe Inc.
* Portions Copyright 2008-2011 Apple Inc. All rights reserved.
* @license Licensed under MIT license
* See https://raw.github.com/emberjs/ember.js/master/LICENSE
* @version 1.7.0-beta.4
*/
(function() {
var define, requireModule, require, requirejs, Ember;
(function() {
Ember = this.Ember = this.Ember || {};
if (typeof Ember === 'undefined') { Ember = {} };
if (typeof Ember.__loader === 'undefined') {
var registry = {}, seen = {};
define = function(name, deps, callback) {
registry[name] = { deps: deps, callback: callback };
};
requirejs = require = requireModule = function(name) {
if (seen.hasOwnProperty(name)) { return seen[name]; }
seen[name] = {};
if (!registry[name]) {
throw new Error("Could not find module " + name);
}
var mod = registry[name],
deps = mod.deps,
callback = mod.callback,
reified = [],
exports;
for (var i=0, l=deps.length; i<l; i++) {
if (deps[i] === 'exports') {
reified.push(exports = {});
} else {
reified.push(requireModule(resolve(deps[i])));
}
}
var value = callback.apply(this, reified);
return seen[name] = exports || value;
function resolve(child) {
if (child.charAt(0) !== '.') { return child; }
var parts = child.split("/");
var parentBase = name.split("/").slice(0, -1);
for (var i=0, l=parts.length; i<l; i++) {
var part = parts[i];
if (part === '..') { parentBase.pop(); }
else if (part === '.') { continue; }
else { parentBase.push(part); }
}
return parentBase.join("/");
}
};
requirejs._eak_seen = registry;
Ember.__loader = {define: define, require: require, registry: registry};
} else {
define = Ember.__loader.define;
requirejs = require = requireModule = Ember.__loader.require;
}
})();
define("backburner",
["backburner/utils","backburner/deferred_action_queues","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
var Utils = __dependency1__["default"];
var DeferredActionQueues = __dependency2__.DeferredActionQueues;
var slice = [].slice,
pop = [].pop,
each = Utils.each,
isString = Utils.isString,
isFunction = Utils.isFunction,
isNumber = Utils.isNumber,
timers = [],
global = this,
NUMBER = /\d+/;
// In IE 6-8, try/finally doesn't work without a catch.
// Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug.
// This tests for another broken try/catch behavior that only exhibits in the same versions of IE.
var needsIETryCatchFix = (function(e,x){
try{ x(); }
catch(e) { } // jshint ignore:line
return !!e;
})();
function isCoercableNumber(number) {
return isNumber(number) || NUMBER.test(number);
}
function Backburner(queueNames, options) {
this.queueNames = queueNames;
this.options = options || {};
if (!this.options.defaultQueue) {
this.options.defaultQueue = queueNames[0];
}
this.instanceStack = [];
this._debouncees = [];
this._throttlers = [];
}
Backburner.prototype = {
queueNames: null,
options: null,
currentInstance: null,
instanceStack: null,
begin: function() {
var options = this.options,
onBegin = options && options.onBegin,
previousInstance = this.currentInstance;
if (previousInstance) {
this.instanceStack.push(previousInstance);
}
this.currentInstance = new DeferredActionQueues(this.queueNames, options);
if (onBegin) {
onBegin(this.currentInstance, previousInstance);
}
},
end: function() {
var options = this.options,
onEnd = options && options.onEnd,
currentInstance = this.currentInstance,
nextInstance = null;
// Prevent double-finally bug in Safari 6.0.2 and iOS 6
// This bug appears to be resolved in Safari 6.0.5 and iOS 7
var finallyAlreadyCalled = false;
try {
currentInstance.flush();
} finally {
if (!finallyAlreadyCalled) {
finallyAlreadyCalled = true;
this.currentInstance = null;
if (this.instanceStack.length) {
nextInstance = this.instanceStack.pop();
this.currentInstance = nextInstance;
}
if (onEnd) {
onEnd(currentInstance, nextInstance);
}
}
}
},
run: function(target, method /*, args */) {
var onError = getOnError(this.options);
this.begin();
if (!method) {
method = target;
target = null;
}
if (isString(method)) {
method = target[method];
}
var args = slice.call(arguments, 2);
// guard against Safari 6's double-finally bug
var didFinally = false;
if (onError) {
try {
return method.apply(target, args);
} catch(error) {
onError(error);
} finally {
if (!didFinally) {
didFinally = true;
this.end();
}
}
} else {
try {
return method.apply(target, args);
} finally {
if (!didFinally) {
didFinally = true;
this.end();
}
}
}
},
defer: function(queueName, target, method /* , args */) {
if (!method) {
method = target;
target = null;
}
if (isString(method)) {
method = target[method];
}
var stack = this.DEBUG ? new Error() : undefined,
args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
if (!this.currentInstance) { createAutorun(this); }
return this.currentInstance.schedule(queueName, target, method, args, false, stack);
},
deferOnce: function(queueName, target, method /* , args */) {
if (!method) {
method = target;
target = null;
}
if (isString(method)) {
method = target[method];
}
var stack = this.DEBUG ? new Error() : undefined,
args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
if (!this.currentInstance) { createAutorun(this); }
return this.currentInstance.schedule(queueName, target, method, args, true, stack);
},
setTimeout: function() {
var args = slice.call(arguments),
length = args.length,
method, wait, target,
methodOrTarget, methodOrWait, methodOrArgs;
if (length === 0) {
return;
} else if (length === 1) {
method = args.shift();
wait = 0;
} else if (length === 2) {
methodOrTarget = args[0];
methodOrWait = args[1];
if (isFunction(methodOrWait) || isFunction(methodOrTarget[methodOrWait])) {
target = args.shift();
method = args.shift();
wait = 0;
} else if (isCoercableNumber(methodOrWait)) {
method = args.shift();
wait = args.shift();
} else {
method = args.shift();
wait = 0;
}
} else {
var last = args[args.length - 1];
if (isCoercableNumber(last)) {
wait = args.pop();
} else {
wait = 0;
}
methodOrTarget = args[0];
methodOrArgs = args[1];
if (isFunction(methodOrArgs) || (isString(methodOrArgs) &&
methodOrTarget !== null &&
methodOrArgs in methodOrTarget)) {
target = args.shift();
method = args.shift();
} else {
method = args.shift();
}
}
var executeAt = (+new Date()) + parseInt(wait, 10);
if (isString(method)) {
method = target[method];
}
var onError = getOnError(this.options);
function fn() {
if (onError) {
try {
method.apply(target, args);
} catch (e) {
onError(e);
}
} else {
method.apply(target, args);
}
}
// find position to insert
var i = searchTimer(executeAt, timers);
timers.splice(i, 0, executeAt, fn);
updateLaterTimer(this, executeAt, wait);
return fn;
},
throttle: function(target, method /* , args, wait, [immediate] */) {
var self = this,
args = arguments,
immediate = pop.call(args),
wait,
throttler,
index,
timer;
if (isNumber(immediate) || isString(immediate)) {
wait = immediate;
immediate = true;
} else {
wait = pop.call(args);
}
wait = parseInt(wait, 10);
index = findThrottler(target, method, this._throttlers);
if (index > -1) { return this._throttlers[index]; } // throttled
timer = global.setTimeout(function() {
if (!immediate) {
self.run.apply(self, args);
}
var index = findThrottler(target, method, self._throttlers);
if (index > -1) {
self._throttlers.splice(index, 1);
}
}, wait);
if (immediate) {
self.run.apply(self, args);
}
throttler = [target, method, timer];
this._throttlers.push(throttler);
return throttler;
},
debounce: function(target, method /* , args, wait, [immediate] */) {
var self = this,
args = arguments,
immediate = pop.call(args),
wait,
index,
debouncee,
timer;
if (isNumber(immediate) || isString(immediate)) {
wait = immediate;
immediate = false;
} else {
wait = pop.call(args);
}
wait = parseInt(wait, 10);
// Remove debouncee
index = findDebouncee(target, method, this._debouncees);
if (index > -1) {
debouncee = this._debouncees[index];
this._debouncees.splice(index, 1);
clearTimeout(debouncee[2]);
}
timer = global.setTimeout(function() {
if (!immediate) {
self.run.apply(self, args);
}
var index = findDebouncee(target, method, self._debouncees);
if (index > -1) {
self._debouncees.splice(index, 1);
}
}, wait);
if (immediate && index === -1) {
self.run.apply(self, args);
}
debouncee = [target, method, timer];
self._debouncees.push(debouncee);
return debouncee;
},
cancelTimers: function() {
var clearItems = function(item) {
clearTimeout(item[2]);
};
each(this._throttlers, clearItems);
this._throttlers = [];
each(this._debouncees, clearItems);
this._debouncees = [];
if (this._laterTimer) {
clearTimeout(this._laterTimer);
this._laterTimer = null;
}
timers = [];
if (this._autorun) {
clearTimeout(this._autorun);
this._autorun = null;
}
},
hasTimers: function() {
return !!timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun;
},
cancel: function(timer) {
var timerType = typeof timer;
if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
return timer.queue.cancel(timer);
} else if (timerType === 'function') { // we're cancelling a setTimeout
for (var i = 0, l = timers.length; i < l; i += 2) {
if (timers[i + 1] === timer) {
timers.splice(i, 2); // remove the two elements
return true;
}
}
} else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce
return this._cancelItem(findThrottler, this._throttlers, timer) ||
this._cancelItem(findDebouncee, this._debouncees, timer);
} else {
return; // timer was null or not a timer
}
},
_cancelItem: function(findMethod, array, timer){
var item,
index;
if (timer.length < 3) { return false; }
index = findMethod(timer[0], timer[1], array);
if(index > -1) {
item = array[index];
if(item[2] === timer[2]){
array.splice(index, 1);
clearTimeout(timer[2]);
return true;
}
}
return false;
}
};
Backburner.prototype.schedule = Backburner.prototype.defer;
Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
Backburner.prototype.later = Backburner.prototype.setTimeout;
if (needsIETryCatchFix) {
var originalRun = Backburner.prototype.run;
Backburner.prototype.run = wrapInTryCatch(originalRun);
var originalEnd = Backburner.prototype.end;
Backburner.prototype.end = wrapInTryCatch(originalEnd);
}
function wrapInTryCatch(func) {
return function () {
try {
return func.apply(this, arguments);
} catch (e) {
throw e;
}
};
}
function getOnError(options) {
return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]);
}
function createAutorun(backburner) {
backburner.begin();
backburner._autorun = global.setTimeout(function() {
backburner._autorun = null;
backburner.end();
});
}
function updateLaterTimer(self, executeAt, wait) {
if (!self._laterTimer || executeAt < self._laterTimerExpiresAt) {
self._laterTimer = global.setTimeout(function() {
self._laterTimer = null;
self._laterTimerExpiresAt = null;
executeTimers(self);
}, wait);
self._laterTimerExpiresAt = executeAt;
}
}
function executeTimers(self) {
var now = +new Date(),
time, fns, i, l;
self.run(function() {
i = searchTimer(now, timers);
fns = timers.splice(0, i);
for (i = 1, l = fns.length; i < l; i += 2) {
self.schedule(self.options.defaultQueue, null, fns[i]);
}
});
if (timers.length) {
updateLaterTimer(self, timers[0], timers[0] - now);
}
}
function findDebouncee(target, method, debouncees) {
return findItem(target, method, debouncees);
}
function findThrottler(target, method, throttlers) {
return findItem(target, method, throttlers);
}
function findItem(target, method, collection) {
var item,
index = -1;
for (var i = 0, l = collection.length; i < l; i++) {
item = collection[i];
if (item[0] === target && item[1] === method) {
index = i;
break;
}
}
return index;
}
function searchTimer(time, timers) {
var start = 0,
end = timers.length - 2,
middle, l;
while (start < end) {
// since timers is an array of pairs 'l' will always
// be an integer
l = (end - start) / 2;
// compensate for the index in case even number
// of pairs inside timers
middle = start + l - (l % 2);
if (time >= timers[middle]) {
start = middle + 2;
} else {
end = middle;
}
}
return (time >= timers[start]) ? start + 2 : start;
}
__exports__.Backburner = Backburner;
});
define("backburner/deferred_action_queues",
["backburner/utils","backburner/queue","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
var Utils = __dependency1__["default"];
var Queue = __dependency2__.Queue;
var each = Utils.each,
isString = Utils.isString;
function DeferredActionQueues(queueNames, options) {
var queues = this.queues = {};
this.queueNames = queueNames = queueNames || [];
this.options = options;
each(queueNames, function(queueName) {
queues[queueName] = new Queue(this, queueName, options);
});
}
DeferredActionQueues.prototype = {
queueNames: null,
queues: null,
options: null,
schedule: function(queueName, target, method, args, onceFlag, stack) {
var queues = this.queues,
queue = queues[queueName];
if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); }
if (onceFlag) {
return queue.pushUnique(target, method, args, stack);
} else {
return queue.push(target, method, args, stack);
}
},
invoke: function(target, method, args, _) {
if (args && args.length > 0) {
method.apply(target, args);
} else {
method.call(target);
}
},
invokeWithOnError: function(target, method, args, onError) {
try {
if (args && args.length > 0) {
method.apply(target, args);
} else {
method.call(target);
}
} catch(error) {
onError(error);
}
},
flush: function() {
var queues = this.queues,
queueNames = this.queueNames,
queueName, queue, queueItems, priorQueueNameIndex,
queueNameIndex = 0, numberOfQueues = queueNames.length,
options = this.options,
onError = options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]),
invoke = onError ? this.invokeWithOnError : this.invoke;
outerloop:
while (queueNameIndex < numberOfQueues) {
queueName = queueNames[queueNameIndex];
queue = queues[queueName];
queueItems = queue._queueBeingFlushed = queue._queue.slice();
queue._queue = [];
var queueOptions = queue.options, // TODO: write a test for this
before = queueOptions && queueOptions.before,
after = queueOptions && queueOptions.after,
target, method, args, stack,
queueIndex = 0, numberOfQueueItems = queueItems.length;
if (numberOfQueueItems && before) { before(); }
while (queueIndex < numberOfQueueItems) {
target = queueItems[queueIndex];
method = queueItems[queueIndex+1];
args = queueItems[queueIndex+2];
stack = queueItems[queueIndex+3]; // Debugging assistance
if (isString(method)) { method = target[method]; }
// method could have been nullified / canceled during flush
if (method) {
invoke(target, method, args, onError);
}
queueIndex += 4;
}
queue._queueBeingFlushed = null;
if (numberOfQueueItems && after) { after(); }
if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) {
queueNameIndex = priorQueueNameIndex;
continue outerloop;
}
queueNameIndex++;
}
}
};
function indexOfPriorQueueWithActions(daq, currentQueueIndex) {
var queueName, queue;
for (var i = 0, l = currentQueueIndex; i <= l; i++) {
queueName = daq.queueNames[i];
queue = daq.queues[queueName];
if (queue._queue.length) { return i; }
}
return -1;
}
__exports__.DeferredActionQueues = DeferredActionQueues;
});
define("backburner/queue",
["exports"],
function(__exports__) {
"use strict";
function Queue(daq, name, options) {
this.daq = daq;
this.name = name;
this.globalOptions = options;
this.options = options[name];
this._queue = [];
}
Queue.prototype = {
daq: null,
name: null,
options: null,
onError: null,
_queue: null,
push: function(target, method, args, stack) {
var queue = this._queue;
queue.push(target, method, args, stack);
return {queue: this, target: target, method: method};
},
pushUnique: function(target, method, args, stack) {
var queue = this._queue, currentTarget, currentMethod, i, l;
for (i = 0, l = queue.length; i < l; i += 4) {
currentTarget = queue[i];
currentMethod = queue[i+1];
if (currentTarget === target && currentMethod === method) {
queue[i+2] = args; // replace args
queue[i+3] = stack; // replace stack
return {queue: this, target: target, method: method};
}
}
queue.push(target, method, args, stack);
return {queue: this, target: target, method: method};
},
// TODO: remove me, only being used for Ember.run.sync
flush: function() {
var queue = this._queue,
globalOptions = this.globalOptions,
options = this.options,
before = options && options.before,
after = options && options.after,
onError = globalOptions.onError || (globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]),
target, method, args, stack, i, l = queue.length;
if (l && before) { before(); }
for (i = 0; i < l; i += 4) {
target = queue[i];
method = queue[i+1];
args = queue[i+2];
stack = queue[i+3]; // Debugging assistance
// TODO: error handling
if (args && args.length > 0) {
if (onError) {
try {
method.apply(target, args);
} catch (e) {
onError(e);
}
} else {
method.apply(target, args);
}
} else {
if (onError) {
try {
method.call(target);
} catch(e) {
onError(e);
}
} else {
method.call(target);
}
}
}
if (l && after) { after(); }
// check if new items have been added
if (queue.length > l) {
this._queue = queue.slice(l);
this.flush();
} else {
this._queue.length = 0;
}
},
cancel: function(actionToCancel) {
var queue = this._queue, currentTarget, currentMethod, i, l;
for (i = 0, l = queue.length; i < l; i += 4) {
currentTarget = queue[i];
currentMethod = queue[i+1];
if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
queue.splice(i, 4);
return true;
}
}
// if not found in current queue
// could be in the queue that is being flushed
queue = this._queueBeingFlushed;
if (!queue) {
return;
}
for (i = 0, l = queue.length; i < l; i += 4) {
currentTarget = queue[i];
currentMethod = queue[i+1];
if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
// don't mess with array during flush
// just nullify the method
queue[i+1] = null;
return true;
}
}
}
};
__exports__.Queue = Queue;
});
define("backburner/utils",
["exports"],
function(__exports__) {
"use strict";
__exports__["default"] = {
each: function(collection, callback) {
for (var i = 0; i < collection.length; i++) {
callback(collection[i]);
}
},
isString: function(suspect) {
return typeof suspect === 'string';
},
isFunction: function(suspect) {
return typeof suspect === 'function';
},
isNumber: function(suspect) {
return typeof suspect === 'number';
}
};
});
define("container",
["container/container","exports"],
function(__dependency1__, __exports__) {
"use strict";
/*
Public api for the container is still in flux.
The public api, specified on the application namespace should be considered the stable api.
// @module container
@private
*/
/*
Flag to enable/disable model factory injections (disabled by default)
If model factory injections are enabled, models should not be
accessed globally (only through `container.lookupFactory('model:modelName'))`);
*/
Ember.MODEL_FACTORY_INJECTIONS = false;
if (Ember.ENV && typeof Ember.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') {
Ember.MODEL_FACTORY_INJECTIONS = !!Ember.ENV.MODEL_FACTORY_INJECTIONS;
}
var Container = __dependency1__["default"];
__exports__["default"] = Container;
});
define("container/container",
["container/inheriting_dict","ember-metal/core","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
var InheritingDict = __dependency1__["default"];
var Ember = __dependency2__["default"];
// Ember.assert
// A lightweight container that helps to assemble and decouple components.
// Public api for the container is still in flux.
// The public api, specified on the application namespace should be considered the stable api.
function Container(parent) {
this.parent = parent;
this.children = [];
this.resolver = parent && parent.resolver || function() {};
this.registry = new InheritingDict(parent && parent.registry);
this.cache = new InheritingDict(parent && parent.cache);
this.factoryCache = new InheritingDict(parent && parent.factoryCache);
this.resolveCache = new InheritingDict(parent && parent.resolveCache);
this.typeInjections = new InheritingDict(parent && parent.typeInjections);
this.injections = {};
this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections);
this.factoryInjections = {};
this._options = new InheritingDict(parent && parent._options);
this._typeOptions = new InheritingDict(parent && parent._typeOptions);
}
Container.prototype = {
/**
@property parent
@type Container
@default null
*/
parent: null,
/**
@property children
@type Array
@default []
*/
children: null,
/**
@property resolver
@type function
*/
resolver: null,
/**
@property registry
@type InheritingDict
*/
registry: null,
/**
@property cache
@type InheritingDict
*/
cache: null,
/**
@property typeInjections
@type InheritingDict
*/
typeInjections: null,
/**
@property injections
@type Object
@default {}
*/
injections: null,
/**
@private
@property _options
@type InheritingDict
@default null
*/
_options: null,
/**
@private
@property _typeOptions
@type InheritingDict
*/
_typeOptions: null,
/**
Returns a new child of the current container. These children are configured
to correctly inherit from the current container.
@method child
@return {Container}
*/
child: function() {
var container = new Container(this);
this.children.push(container);
return container;
},
/**
Sets a key-value pair on the current container. If a parent container,
has the same key, once set on a child, the parent and child will diverge
as expected.
@method set
@param {Object} object
@param {String} key
@param {any} value
*/
set: function(object, key, value) {
object[key] = value;
},
/**
Registers a factory for later injection.
Example:
```javascript
var container = new Container();
container.register('model:user', Person, {singleton: false });
container.register('fruit:favorite', Orange);
container.register('communication:main', Email, {singleton: false});
```
@method register
@param {String} fullName
@param {Function} factory
@param {Object} options
*/
register: function(fullName, factory, options) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
if (factory === undefined) {
throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
}
var normalizedName = this.normalize(fullName);
if (this.cache.has(normalizedName)) {
throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
}
this.registry.set(normalizedName, factory);
this._options.set(normalizedName, options || {});
},
/**
Unregister a fullName
```javascript
var container = new Container();
container.register('model:user', User);
container.lookup('model:user') instanceof User //=> true
container.unregister('model:user')
container.lookup('model:user') === undefined //=> true
```
@method unregister
@param {String} fullName
*/
unregister: function(fullName) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
var normalizedName = this.normalize(fullName);
this.registry.remove(normalizedName);
this.cache.remove(normalizedName);
this.factoryCache.remove(normalizedName);
this.resolveCache.remove(normalizedName);
this._options.remove(normalizedName);
},
/**
Given a fullName return the corresponding factory.
By default `resolve` will retrieve the factory from
its container's registry.
```javascript
var container = new Container();
container.register('api:twitter', Twitter);
container.resolve('api:twitter') // => Twitter
```
Optionally the container can be provided with a custom resolver.
If provided, `resolve` will first provide the custom resolver
the opportunity to resolve the fullName, otherwise it will fallback
to the registry.
```javascript
var container = new Container();
container.resolver = function(fullName) {
// lookup via the module system of choice
};
// the twitter factory is added to the module system
container.resolve('api:twitter') // => Twitter
```
@method resolve
@param {String} fullName
@return {Function} fullName's factory
*/
resolve: function(fullName) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
return resolve(this, this.normalize(fullName));
},
/**
A hook that can be used to describe how the resolver will
attempt to find the factory.
For example, the default Ember `.describe` returns the full
class name (including namespace) where Ember's resolver expects
to find the `fullName`.
@method describe
@param {String} fullName
@return {string} described fullName
*/
describe: function(fullName) {
return fullName;
},
/**
A hook to enable custom fullName normalization behaviour
@method normalize
@param {String} fullName
@return {string} normalized fullName
*/
normalize: function(fullName) {
return fullName;
},
/**
@method makeToString
@param {any} factory
@param {string} fullName
@return {function} toString function
*/
makeToString: function(factory, fullName) {
return factory.toString();
},
/**
Given a fullName return a corresponding instance.
The default behaviour is for lookup to return a singleton instance.
The singleton is scoped to the container, allowing multiple containers
to all have their own locally scoped singletons.
```javascript
var container = new Container();
container.register('api:twitter', Twitter);
var twitter = container.lookup('api:twitter');
twitter instanceof Twitter; // => true
// by default the container will return singletons
var twitter2 = container.lookup('api:twitter');
twitter2 instanceof Twitter; // => true
twitter === twitter2; //=> true
```
If singletons are not wanted an optional flag can be provided at lookup.
```javascript
var container = new Container();
container.register('api:twitter', Twitter);
var twitter = container.lookup('api:twitter', { singleton: false });
var twitter2 = container.lookup('api:twitter', { singleton: false });
twitter === twitter2; //=> false
```
@method lookup
@param {String} fullName
@param {Object} options
@return {any}
*/
lookup: function(fullName, options) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
return lookup(this, this.normalize(fullName), options);
},
/**
Given a fullName return the corresponding factory.
@method lookupFactory
@param {String} fullName
@return {any}
*/
lookupFactory: function(fullName) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
return factoryFor(this, this.normalize(fullName));
},
/**
Given a fullName check if the container is aware of its factory
or singleton instance.
@method has
@param {String} fullName
@return {Boolean}
*/
has: function(fullName) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
return has(this, this.normalize(fullName));
},
/**
Allow registering options for all factories of a type.
```javascript
var container = new Container();
// if all of type `connection` must not be singletons
container.optionsForType('connection', { singleton: false });
container.register('connection:twitter', TwitterConnection);
container.register('connection:facebook', FacebookConnection);
var twitter = container.lookup('connection:twitter');
var twitter2 = container.lookup('connection:twitter');
twitter === twitter2; // => false
var facebook = container.lookup('connection:facebook');
var facebook2 = container.lookup('connection:facebook');
facebook === facebook2; // => false
```
@method optionsForType
@param {String} type
@param {Object} options
*/
optionsForType: function(type, options) {
if (this.parent) { illegalChildOperation('optionsForType'); }
this._typeOptions.set(type, options);
},
/**
@method options
@param {String} type
@param {Object} options
*/
options: function(type, options) {
this.optionsForType(type, options);
},
/**
Used only via `injection`.
Provides a specialized form of injection, specifically enabling
all objects of one type to be injected with a reference to another
object.
For example, provided each object of type `controller` needed a `router`.
one would do the following:
```javascript
var container = new Container();
container.register('router:main', Router);
container.register('controller:user', UserController);
container.register('controller:post', PostController);
container.typeInjection('controller', 'router', 'router:main');
var user = container.lookup('controller:user');
var post = container.lookup('controller:post');
user.router instanceof Router; //=> true
post.router instanceof Router; //=> true
// both controllers share the same router
user.router === post.router; //=> true
```
@private
@method typeInjection
@param {String} type
@param {String} property
@param {String} fullName
*/
typeInjection: function(type, property, fullName) {
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
if (this.parent) { illegalChildOperation('typeInjection'); }
var fullNameType = fullName.split(':')[0];
if(fullNameType === type) {
throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s). Register the `' + fullName + '` as a different type and perform the typeInjection.');
}
addTypeInjection(this.typeInjections, type, property, fullName);
},
/**
Defines injection rules.
These rules are used to inject dependencies onto objects when they
are instantiated.
Two forms of injections are possible:
* Injecting one fullName on another fullName
* Injecting one fullName on a type
Example:
```javascript
var container = new Container();
container.register('source:main', Source);
container.register('model:user', User);
container.register('model:post', Post);
// injecting one fullName on another fullName
// eg. each user model gets a post model
container.injection('model:user', 'post', 'model:post');
// injecting one fullName on another type
container.injection('model', 'source', 'source:main');
var user = container.lookup('model:user');
var post = container.lookup('model:post');
user.source instanceof Source; //=> true
post.source instanceof Source; //=> true
user.post instanceof Post; //=> true
// and both models share the same source
user.source === post.source; //=> true
```
@method injection
@param {String} factoryName
@param {String} property
@param {String} injectionName
*/
injection: function(fullName, property, injectionName) {
if (this.parent) { illegalChildOperation('injection'); }
validateFullName(injectionName);
var normalizedInjectionName = this.normalize(injectionName);
if (fullName.indexOf(':') === -1) {
return this.typeInjection(fullName, property, normalizedInjectionName);
}
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
var normalizedName = this.normalize(fullName);
if (this.cache.has(normalizedName)) {
throw new Error("Attempted to register an injection for a type that has already been looked up. ('" + normalizedName + "', '" + property + "', '" + injectionName + "')");
}
addInjection(this.injections, normalizedName, property, normalizedInjectionName);
},
/**
Used only via `factoryInjection`.
Provides a specialized form of injection, specifically enabling
all factory of one type to be injected with a reference to another
object.
For example, provided each factory of type `model` needed a `store`.
one would do the following:
```javascript
var container = new Container();
container.register('store:main', SomeStore);
container.factoryTypeInjection('model', 'store', 'store:main');
var store = container.lookup('store:main');
var UserFactory = container.lookupFactory('model:user');
UserFactory.store instanceof SomeStore; //=> true
```
@private
@method factoryTypeInjection
@param {String} type
@param {String} property
@param {String} fullName
*/
factoryTypeInjection: function(type, property, fullName) {
if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName));
},
/**
Defines factory injection rules.
Similar to regular injection rules, but are run against factories, via
`Container#lookupFactory`.
These rules are used to inject objects onto factories when they
are looked up.
Two forms of injections are possible:
* Injecting one fullName on another fullName
* Injecting one fullName on a type
Example:
```javascript
var container = new Container();
container.register('store:main', Store);
container.register('store:secondary', OtherStore);
container.register('model:user', User);
container.register('model:post', Post);
// injecting one fullName on another type
container.factoryInjection('model', 'store', 'store:main');
// injecting one fullName on another fullName
container.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
var UserFactory = container.lookupFactory('model:user');
var PostFactory = container.lookupFactory('model:post');
var store = container.lookup('store:main');
UserFactory.store instanceof Store; //=> true
UserFactory.secondaryStore instanceof OtherStore; //=> false
PostFactory.store instanceof Store; //=> true
PostFactory.secondaryStore instanceof OtherStore; //=> true
// and both models share the same source instance
UserFactory.store === PostFactory.store; //=> true
```
@method factoryInjection
@param {String} factoryName
@param {String} property
@param {String} injectionName
*/
factoryInjection: function(fullName, property, injectionName) {
if (this.parent) { illegalChildOperation('injection'); }
var normalizedName = this.normalize(fullName);
var normalizedInjectionName = this.normalize(injectionName);
validateFullName(injectionName);
if (fullName.indexOf(':') === -1) {
return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
}
Ember.assert('fullName must be a proper full name', validateFullName(fullName));
if (this.factoryCache.has(normalizedName)) {
throw new Error('Attempted to register a factoryInjection for a type that has already ' +
'been looked up. (\'' + normalizedName + '\', \'' + property + '\', \'' + injectionName + '\')');
}
addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName);
},
/**
A depth first traversal, destroying the container, its descendant containers and all
their managed objects.
@method destroy
*/
destroy: function() {
for (var i = 0, length = this.children.length; i < length; i++) {
this.children[i].destroy();
}
this.children = [];
eachDestroyable(this, function(item) {
item.destroy();
});
this.parent = undefined;
this.isDestroyed = true;
},
/**
@method reset
*/
reset: function() {
for (var i = 0, length = this.children.length; i < length; i++) {
resetCache(this.children[i]);
}
resetCache(this);
}
};
function resolve(container, normalizedName) {
var cached = container.resolveCache.get(normalizedName);
if (cached) { return cached; }
var resolved = container.resolver(normalizedName) || container.registry.get(normalizedName);
container.resolveCache.set(normalizedName, resolved);
return resolved;
}
function has(container, fullName){
if (container.cache.has(fullName)) {
return true;
}
return !!container.resolve(fullName);
}
function lookup(container, fullName, options) {
options = options || {};
if (container.cache.has(fullName) && options.singleton !== false) {
return container.cache.get(fullName);
}
var value = instantiate(container, fullName);
if (value === undefined) { return; }
if (isSingleton(container, fullName) && options.singleton !== false) {
container.cache.set(fullName, value);
}
return value;
}
function illegalChildOperation(operation) {
throw new Error(operation + ' is not currently supported on child containers');
}
function isSingleton(container, fullName) {
var singleton = option(container, fullName, 'singleton');
return singleton !== false;
}
function buildInjections(container, injections) {
var hash = {};
if (!injections) { return hash; }
var injection, injectable;
for (var i = 0, length = injections.length; i < length; i++) {
injection = injections[i];
injectable = lookup(container, injection.fullName);
if (injectable !== undefined) {
hash[injection.property] = injectable;
} else {
throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`');
}
}
return hash;
}
function option(container, fullName, optionName) {
var options = container._options.get(fullName);
if (options && options[optionName] !== undefined) {
return options[optionName];
}
var type = fullName.split(':')[0];
options = container._typeOptions.get(type);
if (options) {
return options[optionName];
}
}
function factoryFor(container, fullName) {
var cache = container.factoryCache;
if (cache.has(fullName)) {
return cache.get(fullName);
}
var factory = container.resolve(fullName);
if (factory === undefined) { return; }
var type = fullName.split(':')[0];
if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) {
// TODO: think about a 'safe' merge style extension
// for now just fallback to create time injection
return factory;
} else {
var injections = injectionsFor(container, fullName);
var factoryInjections = factoryInjectionsFor(container, fullName);
factoryInjections._toString = container.makeToString(factory, fullName);
var injectedFactory = factory.extend(injections);
injectedFactory.reopenClass(factoryInjections);
cache.set(fullName, injectedFactory);
return injectedFactory;
}
}
function injectionsFor(container, fullName) {
var splitName = fullName.split(':'),
type = splitName[0],
injections = [];
injections = injections.concat(container.typeInjections.get(type) || []);
injections = injections.concat(container.injections[fullName] || []);
injections = buildInjections(container, injections);
injections._debugContainerKey = fullName;
injections.container = container;
return injections;
}
function factoryInjectionsFor(container, fullName) {
var splitName = fullName.split(':'),
type = splitName[0],
factoryInjections = [];
factoryInjections = factoryInjections.concat(container.factoryTypeInjections.get(type) || []);
factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []);
factoryInjections = buildInjections(container, factoryInjections);
factoryInjections._debugContainerKey = fullName;
return factoryInjections;
}
function instantiate(container, fullName) {
var factory = factoryFor(container, fullName);
if (option(container, fullName, 'instantiate') === false) {
return factory;
}
if (factory) {
if (typeof factory.create !== 'function') {
throw new Error('Failed to create an instance of \'' + fullName + '\'. ' +
'Most likely an improperly defined class or an invalid module export.');
}
if (typeof factory.extend === 'function') {
// assume the factory was extendable and is already injected
return factory.create();
} else {
// assume the factory was extendable
// to create time injections
// TODO: support new'ing for instantiation and merge injections for pure JS Functions
return factory.create(injectionsFor(container, fullName));
}
}
}
function eachDestroyable(container, callback) {
container.cache.eachLocal(function(key, value) {
if (option(container, key, 'instanti