/*jshint browser:true */
/*global define:false, require:false */
define(function () {
"use strict";
var
Jasq v0.4.2 - AMD dependency injector integrated with Jasmine
https://github.com/biril/jasq
Licensed and freely distributed under the MIT License
Copyright (c) 2013-2014 Alex Lambiris
/*jshint browser:true */
/*global define:false, require:false */
define(function () {
"use strict";
var
Helpers
noOp = function () {},
isString = function (s) {
return Object.prototype.toString.call(s) === "[object String]";
},
isFunction = function (f) {
return Object.prototype.toString.call(f) === "[object Function]";
},
isStrictlyObject = function (o) {
return Object.prototype.toString.call(o) === "[object Object]";
},
each = function (obj, iterator) {
var i, l, key;
if (!obj) { return; }
if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
obj.forEach(iterator);
return;
}
if (obj.length === +obj.length) {
for (i = 0, l = obj.length; i < l; i++) { iterator(obj[i], i, obj); }
return;
}
for (key in obj) {
if (obj.hasOwnProperty(key)) { iterator(obj[key], key, obj); }
}
},
extend = function () {
var target = {};
each(arguments, function (source) {
each(source, function (v, k) { target[k] = v; });
});
return target;
},
jasmineApiNames = ["describe", "xdescribe", "it", "xit"],
Jasmine’s native (non-jasq-patched) global API
jasmineNativeApi = {},
jasmineEnv = null,
jasq = {},
Get a value indicating whether Jasmine is available on the global scope
isJasmineInGlobalScope = function () {
return window.jasmine && isFunction(window.jasmine.getEnv);
},
Generate a context-id for given suiteDescription
/ specDescription
pair
createContextId = (function () {
var uid = 0;
return function (suiteDescription, specDescription) {
return suiteDescription + " " + specDescription + " " + (uid++);
};
}()),
Re-configure require for context of given id, getting a loader-function. All requirejs
configuration options, except for the
context itself, are copied over from the default context _
configRequireForContext = function (contextId) {
var c = {};
each(require.s.contexts._.config, function (val, key) {
if (key !== "deps") { c[key] = val; }
});
c.context = contextId;
return require.config(c);
},
A stack of suite configs, the topmost being the ‘current’. For each jasq-describe
call
(i.e. those that define a module and only those) a config is pushed to the stack which
includes the (name of the) module under test and optionally a mocking function. The module
will be made available to all specs defined within that (or any nested) suite. The
mocking function, if present in the configuration, will be invoked on every spec to
instantiate mocks. (Mocks defined on the spec itself (in the specConfig provided during the
invocation of it
) will override those defined in the suiteConfig)
suiteConfigs = (function () {
var sc = [];
Get the current suite-config. Or a falsy value if no such thing
sc.getCurrent = function () {
return sc[sc.length - 1];
};
Get the path of the current suite-config. Or an empty array if no such thing A suite’s path is defined as an array of suite descriptions where
path[0]
: descr. of the top-level suite == (n-1)th parent of current suitepath[1]
: descr. of (n-2)th parent of current suitepath[path.length - 1]
: descr. of current suite sc.getCurrentPath = function () {
var p = [];
each(sc, function () {
p.push(sc.description);
});
return p;
};
return sc;
}()),
Create a function to execute the spec of given specDescription
and specConfig
after
(re)loading the tested module and mocking its dependencies as specified at the (current)
suite and (given) spec level
createJasqSpec = function (specDescription, specConfig) {
var contextId, load, suiteConfig, mock;
Mods will load in a new requirejs context, specific to this spec. This is its id
contextId = createContextId(suiteConfigs.getCurrentPath(), specDescription);
Create the context, configuring require appropriately and obtaining a loader
load = configRequireForContext(contextId);
Configuration of current suite (name of module to load & mock function)
suiteConfig = suiteConfigs.getCurrent();
Modules to mock, as specified at the suite level as well as the spec level
mock = extend(suiteConfig.mock ? suiteConfig.mock() : {}, specConfig.mock);
return function (done) {
Re-define modules using given mocks (if any), before they’re loaded
each(mock, function (mod, modName) { define(modName, mod); });
And require the tested module
load(suiteConfig.moduleName ? [suiteConfig.moduleName] : [], function (module) {
After module & deps are loaded, just run the original spec’s expectations.
Dependencies (mocked and non-mocked) should be available through the
dependencies
hash. (Note that a (shallow) copy of dependencies is passed, to
avoid exposing the original hash that require maintains)
specConfig.expect(module, extend(require.s.contexts[contextId].defined), done);
In the event that the expectation-function is not meant to complete
asynchronously (<=> the expectation-function did not ‘request’ a done
argument) then it’s already completed. Invoke done
if (specConfig.expect.length < 3) {
done();
}
});
};
},
getJasqDescribe = function (isX) {
var jasmineDescribe = jasmineNativeApi[isX ? "xdescribe" : "describe"];
(x)describe
, Jasq version
suiteDescription
: Description of this suite, as in Jasmine’s native describe
moduleName
: Name of the module to which this test suite refersspecify
: The function to execute the suite’s specs, as in Jasmine’s describe
OR
suiteDescription
: Description of this suite, as in Jasmine’s native describe
suiteConfig
: Configuration of the suite. A hash containingmoduleName
: Name of the module to which this test suite refersmock
: Optionally a function that returns a hash of mocksspecify
: The function to execute the suite’s specs return function (suiteDescription) {
var args, suite;
Parse given arguments as if they were suitable for the jasq-version of describe
.
args
will contain the expected moduleName
, mock
and specify
properties if they
are, or will be falsy if they’re not. In the latter case, just delegate to the native
jasmine version
args = (function (args) {
Either suiteDescription
, moduleName
, specify
..
if (isString(args[0]) && isString(args[1]) && isFunction(args[2])) {
return { moduleName: args[1], specify: args[2] };
}
.. or suiteDescription
, suiteConfig
if (isString(args[0]) && isStrictlyObject(args[1])) {
return args[1];
}
}(arguments));
if (!args) { return jasmineDescribe.apply(null, arguments); }
Push the current suite-config onto the stack of suite-configs, making it the current
suite-config. All specs (and nested suites) will make use of this configuration. (if
this is an xdescribe
call, it makes no difference as the suite’s specs will never
execute anyway. However, it’s simpler to always push
here and always pop
later,
avoiding an extra layer of logic)
suiteConfigs.push({
description: suiteDescription,
moduleName: args.moduleName,
mock: args.mock
});
Ultimately, the native Jasmine version is run. The crucial step was setting a suite-config for further use in specs and nested suites
suite = jasmineDescribe(suiteDescription, args.specify);
Pop the current suite-config
suiteConfigs.pop();
return suite;
};
},
getJasqIt = function (isX) {
var jasmineIt = jasmineNativeApi[isX ? "xit" : "it"];
(x)it
, Jasq version
specDescription
: Description of this spec, as in Jasmine’s native it
specConfig
: Configuration of the spec. A hash containing:store
: An array of neames of the modules to ‘store’: These will be
exposed in the spec through dependencies.store
- a hash of modulesmock
: A hash of mocks, mapping module (name) to mock. These will be
exposed in the spec through dependencies.mocks
- a hash of modulesexpect
: The expectation function: A callback to be invoked with
module
and dependencies
arguments return function (specDescription, specConfig) {
In the event that there’s no current suite-config (no module to pass to the spec) then
just run the native Jasmine version - this will avoid forcing spec to run
asynchronously. Also run the native version in the case the the caller invoked xit
-
the spec will not execute so there’s no reason to incur the module (re)loading overhead
if (!suiteConfigs.getCurrent() || isX) {
We tolerate the caller passing an expectation-hash into a spec which is not nested within a jasq-suite - in this case we’re only interested in the expectation-function
if (isStrictlyObject(specConfig)) {
specConfig = specConfig.expect;
}
return jasmineIt.call(null, specDescription, specConfig);
}
Create a specConfig, in case the caller passed an expectation-function instead
if (!isStrictlyObject(specConfig)) {
specConfig = { expect: specConfig };
}
Execute Jasmine’s (x)it
on an appropriately modified asynchronous spec
return jasmineIt(specDescription, createJasqSpec(specDescription, specConfig));
};
},
Ensure that jasmineEnv
and jasmineNativeApi
have been set and create the
patched version of Jasmine’s API. Will only run once
init = function () {
if (!isJasmineInGlobalScope()) {
throw "Jasmine is not available in global scope (not loaded?)";
}
Store Jasmine’s globals
jasmineEnv = window.jasmine.getEnv();
each(jasmineApiNames, function (name) { jasmineNativeApi[name] = window[name]; });
Create patched version of Jasmine’s API
jasq.describe = getJasqDescribe();
jasq.xdescribe = getJasqDescribe(true);
jasq.it = getJasqIt();
jasq.xit = getJasqIt(true);
each(jasmineApiNames, function (name) { jasq[name].isJasq = true; });
Don’t init
more than once
init = noOp;
};
jasq.applyGlobals = function () {
init();
each(jasmineApiNames, function (name) { window[name] = jasq[name]; });
};
jasq.resetGlobals = function () {
init();
each(jasmineApiNames, function (name) { window[name] = jasmineNativeApi[name]; });
};
If Jasmine is already in global scope then go ahead and apply globals - this will also initialize jasq
if (isJasmineInGlobalScope()) { jasq.applyGlobals(); }
return jasq;
});