UNPKG

joii-unit

Version:

UnitTest Framework for JOII-based applications

388 lines (351 loc) 12.9 kB
/* JavaScript Unit Testing Framework _ _ _ - Powered by JOII (_)___ (_|_) .__ __ / / __ \/ / / __ __ ____ |__|/ |_ (c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\ Released under the MIT license. __/ /\____/_/_/ | | / | \ || | --------------------------------- /___/ ------------ |____/|___| /__||__| --- */ (function(g, namespace, Class, undefined) { var ns = namespace('DependencyInjection'); /** * Dependency Injection Container * * @author Harold Iedema <harold@iedema.me> */ ns.Container = Class({ 'private object definitions' : {}, 'private object parameters' : {}, 'private object loading' : {}, 'private object passes' : [], 'private boolean is_frozen' : false, 'private boolean is_compiling' : false, 'private __construct': function() { return this.api; }, /** * Creates a new Definition based on the passed name and function * and returns it. * * @param string name * @param mixed fn Function or string referencing the function. */ 'public function register': function(name, fn) { if (this.is_frozen) { throw new Error('Unable to register a new Definition on a frozen container.'); } this.definitions[name] = new ns.Definition(fn); return this.definitions[name]; }, /** * Adds a CompilerPass to this container. * * A compiler pass must have a compile() method which accepts the * Container as its one and only argument. The CompilerPass may * add, alter or remove service definitions as it sees fit. * * @param CompilerPass compiler_pass */ 'public function addCompilerPass': function(compiler_pass) { if (typeof(compiler_pass) === 'function') { compiler_pass = new compiler_pass(); } if (typeof(compiler_pass.compile) !== 'function') { throw new Error('The CompilerPass doesn\'t have a compile function.'); } this.passes.push(compiler_pass); }, /** * Runs all compiler passes. After this process is complete, the * container is marked as frozen and no more definitions may be * added. */ 'public function compile': function() { if (this.is_compiling) { throw new Error('The container is already compiling.'); } this.is_compiling = true; if (this.is_frozen) { throw new Error('Unable to compile a container which is already compiled.'); } for (var i in this.passes) { this.passes[i].compile(this); } this.is_compiling = false; this.is_frozen = true; }, /** * Returns true if this container is already compiled. * * @return bool */ 'public function isFrozen': function() { return this.is_frozen; }, /** * Returns the service with the associated id. * * @param string id * @return Object */ 'public function get': function(id) { // Compile the container first if it hasn't been compiled yet. if (!this.is_frozen) { this.compile(); } if (typeof(this.definitions[id]) === 'undefined') { throw new Error('Service ' + id + ' does not exist.'); } var definition = this.definitions[id]; // Do we already have an instance of this definition? if (definition.hasInstance()) { return definition.getInstance(); } // Circular reference check if (this.loading[id] === true) { throw new Error('Service ' + id + ' has a circular reference to itself.'); } this.loading[id] = true; // Create the service var service = this.createService(definition); // Remove the circular reference check delete this.loading[id]; return service; }, /** * Initializes the service definition and returns its function * instance. * * @access private * @return Object */ 'public function createService': function(definition) { if (definition.hasInstance()) { throw new Error('Attempt to create a service that already has an instance.'); } // Build up an array of arguments to pass to the constructor. var c_args = this.getParameterArray(definition.getArguments()); // Construct function to use '.apply' on 'new' objects. var construct = function(c, args) { var cc = function() { return c.apply(this, args); }; cc.prototype = c.prototype; return new cc(); }; var fn = definition.getFunction(), instance = construct(fn, c_args); definition.setInstance(instance); var calls = definition.getMethodCalls(); for (var i in calls) { if (!calls.hasOwnProperty(i)) { continue; } var method = calls[i][0]; var args = this.getParameterArray(calls[i][1] || []); if (typeof(instance[method]) !== 'function') { throw new Error('Method ' + method + ' does not exist.'); } instance[method].apply(instance, args); } return instance; }, 'public function getServiceIds': function() { var result = []; for (var i in this.definitions) { if (this.definitions.hasOwnProperty(i)) { result.push(i); } } return result; }, /** * Sets the service definitions. * * @param DependencyInjection.Definition[] An array of Definitions. * @return DependencyInjection.Container */ 'public function setDefinitions': function(definitions) { if (this.is_frozen) { throw new Error('Unable to register a new Definition on a frozen container.'); } this.definitions = {}; this.addDefinitions(definitions); return this.api; }, /** * Adds the service definitions. * * @param Definition[] definitions An array of service definitions. * @return DependencyInjection.Container */ 'public function addDefinitions': function(definitions) { for (var i in definitions) { if (definitions.hasOwnProperty(i)) { var def = definitions[i]; this.setDefinition(i, def); } } return this.api; }, /** * Sets a service definition. * * @param string id The id of the service * @param DepedencyInjection.Definition definition * @return DepedencyInjection.Container */ 'public function setDefinition': function(id, definition) { if (this.is_frozen) { throw new Error('Unable to register a new Definition on a frozen container.'); } this.definitions[id] = definition; return this.api; }, /** * Returns true if a service definition exists under the * given identifier. * * @return bool */ 'public function hasDefinition': function(id) { return typeof(this.definitions[id]) !== 'undefined'; }, /** * Gets a service definition. * * @param string id The service identifier * @return DependencyInjection.Definition */ 'public function getDefinition': function(id) { if (typeof(this.definitions[id]) === 'undefined') { throw new Error('The service definition ' + id + ' does not exist.'); } return this.definitions[id]; }, /** * Returns an array of tag attributes indexed by service id. * * @param string name * @return array */ 'public function findTaggedServiceIds': function(name) { var result = {}; for (var i in this.definitions) { if (!this.definitions.hasOwnProperty(i)) { continue; } if (this.definitions[i].hasTag(name)) { result[i] = this.definitions[i].getTag(name); } } return result; }, /** * Sets the parameters array. * * @param Object parameters * @return DepedencyInjection.Container */ 'public function setParameters': function(parameters) { if (this.is_frozen) { throw new Error('Unable to update parameters on a frozen container.'); } this.parameters = parameters; return this.api; }, /** * Sets a parameter. * * @param string name * @param mixed value * @return DepedencyInjection.Container */ 'public function setParameter': function(name, value) { if (this.is_frozen) { throw new Error('Unable to update parameters on a frozen container.'); } this.parameters[name] = value; return this.api; }, /** * Returns true if a parameter with the given name exists. * * @param string name * @return bool */ 'public function hasParameter': function(name) { return typeof(this.parameters[name]) !== 'undefined'; }, /** * Returns the value of the parameter with the given name. * * @param string name * @return mixed */ 'public function getParameter': function(name) { if (typeof(this.parameters[name]) !== 'undefined') { return this.parameters[name]; } throw new Error('Parameter ' + name + ' does not exist.'); }, /** * Returns a parsed parameter array. * * @access private * @param array arr * @return arr */ 'private function getParameterArray': function(arr) { var args = []; for (var i in arr) { var arg = arr[i]; if (typeof(arg) === 'string') { arg = this.resolveParameter(arg); } args.push(arg); } return args; }, /** * Resolves a parameter. * * If the value starts with an @, a service is referenced. * If the value is omitted with %, a parameter is referenced. * * @access private * @param string value * @return mixed */ 'private function resolveParameter': function(value) { if (typeof(value) !== 'string') { return value; } if (value.charAt(0) === '@') { return this.get(value.slice(1, value.length)); } if (value.charAt(0) === '%' && value.charAt(value.length - 1) === '%') { return this.getParameter(value.slice(1, value.length - 1)); } return value; } }); } ((typeof window !== 'undefined' ? window : global), (typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace, (typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));