joii-unit
Version:
UnitTest Framework for JOII-based applications
2,450 lines • 88.1 kB
JavaScript
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, Class, undefined) {
/**
* Bootstrapper
*/
g.JOII.Unit = Class({
/**
* Configuration object to be passed to the DI container.
*/
'private object config' : {
'parameters' : {
'environment' : undefined, // Overwritten by Unit.
'loader' : undefined, // Loader class
'reporter' : undefined, // Reporter class
'dependencies' : [], // An array of dependency files
'tests' : [], // An array of test files
'verbose' : true // Use verbose output (for debugging purposes)
},
'services' : {
// JOII.Unit.ReporterInterface
'joii.unit.reporter' : {
'class' : undefined, // Manually specified
'arguments' : ['%environment%', '%verbose%']
},
// JOII.Unit.LoaderInterface
'joii.unit.loader' : {
'class' : undefined, // Manually specified
'arguments' : ['%dependencies%', '%tests%', '@joii.unit.reporter']
},
// JOII.Unit.AnnotationReader.AnnotationReader
'joii.unit.annotation_reader' : {
'class' : 'JOII.Unit.AnnotationReader.AnnotationReader',
'arguments' : [
'@joii.unit.reporter',
'@joii.unit.loader'
]
},
// JOII.Unit.TestCaseManager
'joii.unit.test_manager' : {
'class' : 'JOII.Unit.TestCaseManager',
'arguments' : [
'@joii.unit.annotation_reader',
'@joii.unit.reporter',
'@joii.unit.loader'
]
}
}
},
/**
* @var JOII.Unit.DependencyInjection.Container
*/
'public immutable object container' : null,
/**
* Constructor
*
* @param object parameters
* @param object services (optional)
*/
'private __construct' : function (parameters, services) {
// First and foremost, JOII.Unit may only exist once.
if (typeof JOII.Unit.instance !== 'undefined') {
throw 'Another instance of JOII.Unit is already active!';
}
// Register this instance.
JOII.Unit.instance = this.__api__;
// Arguments are optional.
parameters = parameters = parameters || {};
services = services = services || {};
// Merge the given options and services with the defaults
var e = JOII.Compat.extend,
c = this.config,
b;
this.config.parameters = e(true, c.parameters, parameters);
this.config.services = e(true, c.services, services);
// Store the environment as a parameters, usable by services.
this.config.parameters.environment = this.determineEnvironemnt();
// If no reporter was specified, we'll define one.
var reporter_class = this.config.parameters.reporter;
if (reporter_class === undefined) {
e = this.config.parameters.environment;
e = e.charAt(0).toUpperCase() + e.slice(1) + 'Reporter';
this.config.services['joii.unit.reporter']['class'] = 'JOII.Unit.Reporter.' + e;
} else {
this.config.services['joii.unit.reporter']['class'] = reporter_class;
}
// If no loader was specified, we'll create one.
var loader_class = this.config.parameters.loader;
if (loader_class === undefined) {
e = this.config.parameters.environment;
e = e.charAt(0).toUpperCase() + e.slice(1) + 'Loader';
this.config.services['joii.unit.loader']['class'] = 'JOII.Unit.Loader.' + e;
} else {
this.config.services['joii.unit.loader']['class'] = loader_class;
}
// Create the service container
var di = new JOII.Unit.DependencyInjection.ServiceContainer();
di.loadConfiguration(this.config);
this.container = di.getContainer();
// Validate and initialize the reporter.
var reporter = this.container.get('joii.unit.reporter');
if (!JOII.isInstance(reporter) || !reporter.instanceOf(JOII.Unit.ReporterInterface)) {
throw new Error('Reporter is not an instance of JOII.Unit.ReporterInterface');
}
reporter.init();
var loader = this.container.get('joii.unit.loader');
loader.load(JOII.Compat.Bind(this.runTests, this));
},
'private function runTests' : function()
{
var tests = this.container.get('joii.unit.test_manager').getTests(),
result, testcase, filename, name, summary = {
testcases : [],
assertions : { passed : 0, failed : 0, total : 0 },
testcount : 0,
runtime : 0
};
for (var i in tests) {
testcase = tests[i].getTestCase();
result = tests[i].run();
filename = testcase.filename;
name = testcase.reflector.getName();
if (typeof summary.testcases[filename] === 'undefined') {
summary.testcases[filename] = {};
}
summary.testcases[filename][name] = result;
summary.assertions.passed += result.assertions.passed;
summary.assertions.failed += result.assertions.failed;
summary.assertions.total += (result.assertions.passed + result.assertions.failed);
summary.testcount ++;
}
this.container.get('joii.unit.reporter').reportSummary(summary);
},
/**
* Determine the environment and return it as a string.
*
* @return string
*/
'private function determineEnvironemnt' : function () {
if (typeof window !== 'undefined') {
return 'browser';
}
if (typeof global !== 'undefined' && module && require) {
return 'node';
}
throw new Error('JOII-Unit is unable to determine the current environment.');
}
});
/**
* Declare a namespace if it doesn't exist already.
*
* @param string ns
* @return object
*/
g.JOII.Unit.Namespace = function(identifier) {
var ns = g.JOII.Unit;
if (identifier !== '') {
var parts = identifier.split('.');
for (var i = 0; i < parts.length; i++) {
if (!ns[parts[i]]) {
ns[parts[i]] = {};
}
ns = ns[parts[i]];
}
}
return ns;
}
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('');
ns.AbstractAnnotation = Class({ 'abstract': true }, {
'final public immutable name' : null,
'final protected immutable definition' : null,
'final protected immutable method' : null,
'final protected immutable data' : null,
'final private __construct' : function (definition, method, name, data) {
this.definition = definition;
this.method = method;
this.name = name;
this.data = data;
},
'final public function toString' : function () {
return '[Annotation ' + this.getName() + ': ' + this.getData() + ']';
},
/**
* Returns true if this Annotation accepts the given name.
*
* @param string name
* @return bool
*/
'abstract public function accepts' : function (name) {}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Interface, undefined) {
/**
* This interface defines all methods a Loader must implement.
*
* @author Harold Iedema <harold@iedema.me>
*/
namespace('').LoaderInterface = Interface('JOII.Unit.Loader', {
/**
* Constructor
*
* @param array dep_files List of dependency files
* @param array test_files List of test files containing TestCases
* @param JOII.Unit.ReporterInterface reporter
*/
'private function __construct' : function (dep_files, test_files, reporter) {},
/**
* Loads all the files specified through the constructor. Once the
* loading process is finished, the given callback is executed.
*
* @param function callback
*/
'public function load' : function (callback) {},
/**
* Returns the name of the last loaded file.
*
* The variable needs to be defined _before_ the file is actually
* loaded. The TestCaseManager uses the return value of this method to
* identifier which TestCase belongs to wich file in order to use the
* AnnotationReader.
*
* @return string
*/
'public function getLastLoadedFile' : function() {},
/**
* Returns the source of the given file.
*
* @param string file
* @return string
*/
'public function getSource' : function (file) {}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.InterfaceBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Interface, undefined) {
/**
* This interface defines all methods a Reporter must implement.
*
* @author Harold Iedema <harold@iedema.me>
*/
namespace('').ReporterInterface = Interface('JOII.Unit.Reporter', {
/**
* Constructor
*
* @param string env
* @param boolean verbose
*/
'private __construct' : function (env, verbose) {},
/**
* Initializes the reporter.
*
* Use this method to show a welcome screen, print a header or build
* the skeleton of a webpage.
*/
'public function init' : function () {},
/**
* Reports a passed assertion.
*
* @param JOII.Unit.TestCase testcase
* @param string method
* @param string message
* @param mixed actual
* @param mixed expected
*/
'public function reportAssertPass' : function (testcase, method, message, actual, expected) {},
/**
* Reports a failed assertion.
*
* @param JOII.Unit.TestCase testcase
* @param string method
* @param string message
* @param mixed actual
* @param mixed expected
*/
'public function reportAssertFail' : function (testcase, method, message, actual, expected) {},
/**
* Reports a critical error message.
*
* Exception messages are usually rendered with this.
* This will terminate the process.
*
* @param string message.
*/
'public function critical' : function (message) {},
/**
* Reports an error message. Multiple error messages may be displayed.
*
* @param string message
*/
'public function error' : function (message) {},
/**
* Reports a debug message.
*
* @param string message
*/
'public function debug' : function (message) {}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.InterfaceBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('');
/**
* JOII-Unit Reporter suitable for a NodeJS environment.
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.TestCaseManager = Class({
'private annotation_reader' : null,
'private reporter' : null,
'private loader' : null,
'private tests' : [],
'private __construct' : function (annotation_reader, reporter, loader) {
this.annotation_reader = annotation_reader;
this.reporter = reporter;
this.loader = loader;
},
'public function addTest' : function(testcase) {
testcase.reflector = new JOII.Reflection.Class(testcase);
testcase.filename = this.loader.getLastLoadedFile();
testcase.annotations = this.annotation_reader.get(testcase);
// Build a list of all testable method names
var methods = [], m = testcase.reflector.getMethods();
for (var i in m) {
if (m[i].isPublic() && m[i].getName().substr(0, 4) === 'test') {
methods.push(m[i].getName());
}
}
testcase.methods = methods;
this.tests.push(new JOII.Unit.TestCase.Process(testcase, this.reporter));
this.reporter.debug('TestCaseManager::addTest - TestCase: "' + testcase.reflector.getName() + '" (from: ' + testcase.filename + '), ' + methods.length + ' test(s).');
},
'public function getTests' : function() {
return this.tests;
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('AnnotationReader');
/**
* JOII-Unit AnnotationMethod
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.AnnotationMethod = Class({
'private string method' : null,
'private object annotations' : null,
/**
* @param object annotations
*/
'private __construct' : function (method, annotations) {
this.annotations = annotations || [];
this.method = method;
},
/**
* Returns the annotation by the given name or throws an Error if
* there is no such annotation.
*
* @param string name
* @return bool
*/
'public function get' : function (name) {
for (var i in this.annotations) {
if (this.annotations[i].instanceOf(name) ||
this.annotations[i].getName() === name) {
return this.annotations[i];
}
}
throw new Error('Annotation "' + name + '" not found on method "' + this.method + '"');
},
/**
* Returns true if this method has an annotation by the given name.
*
* @param string name
* @return bool
*/
'public function has' : function (name) {
try {
this.get(name);
return true;
} catch (e) {
return false;
}
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('AnnotationReader');
/**
* JOII-Unit AnnotationParser
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.AnnotationParser = Class({
'private number pointer' : 0,
'private string source' : '',
'public annotations' : {},
/**
* @param string filename
* @param string scope
*/
'private __construct' : function (source, scope) {
// Replace ' with ", then remove subsequent ".
this.source = this.trim(source.replace(/\'/g, '"').replace(/\"+/g, '"'));
// Do something dirty... 'Guess' the scope starting point and reset
// the pointer.
scope = '"' + scope.substr(0, scope.length - 4); // Remove "Test".
var start = this.seek(scope);
var end = this.seek('JOII.Unit.TestCase');
end = (end === false ? this.source.length : end);
this.source = this.source.substring(start, end);
this.pointer = 0;
var doc_a, doc_b, fn;
while (false !== (this.seek('/*'))) {
doc_a = this.pointer;
doc_b = this.seek('*/');
fn = this.seek(':');
var docblock = this.parseDocblock(this.source.substring(doc_a, doc_b));
var declaration = this.parseDeclaration(this.source.substring(doc_b + 2, fn));
this.annotations[declaration] = docblock;
}
},
/**
* Parses a declaration line.
*/
'private function parseDeclaration' : function (str) {
str = str.replace(/\"/g, '');
str = this.trim(str.replace(/\n/g, '').replace(/\r/g, '').replace(/\s+/g, ' '));
var tmp = str.split(' ');
return tmp[tmp.length - 1];
},
/**
* Parses a dockblock.
*/
'private function parseDocblock' : function (str) {
var buff = [], index = 0, i, q = false, c;
str = this.trim(str.replace(/\n/g, '').replace(/\r/g, '').replace(/\s+/g, ' '));
for (i = 0; i < str.length; i++) {
c = str.charAt(i);
if (c === '"' && q === false) {
q = true;
} else if (c === '"' && q === true) {
q = false;
}
if (q === false && c === '*') {
continue;
}
if (c !== '@') {
if (typeof buff[index] === 'undefined') { buff[index] = ''; }
buff[index] += c;
} else if (q === false && c === '@') {
buff[index] = this.trim(buff[index]);
index++;
}
}
buff.shift();
return buff;
},
/**
* Moves the pointer until {str} is reached and returns the new pointer
* location if {str} is actually found, false otherwise.
*
* @param string str
* @return number|false
*/
'private function seek' : function (str) {
for (var i = this.pointer - 1; i < this.source.length; i++) {
var fc = 0;
for (var s = 0; s < str.length; s++) {
if (i + s > this.source.length) {
return false;
}
if (str.charAt(s) === this.source.charAt(i + s)) {
fc++;
} else {
break;
}
}
if (fc === str.length) {
this.pointer = i;
return this.pointer;
}
}
return false;
},
'private trim' : function(str) {
str = str.replace(/^\s+/, '');
for (var i = str.length - 1; i >= 0; i--) {
if (/\S/.test(str.charAt(i))) {
str = str.substring(0, i + 1);
break;
}
}
return str;
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('AnnotationReader');
/**
* JOII-Unit AnnotationReader (service)
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.AnnotationReader = Class({
'private object cache' : {},
'private JOII.Unit.Reporter reporter' : null,
'private JOII.Unit.Loader loader' : null,
/**
* @param JOII.Unit.Reporter reporter
* @param JOII.Unit.Loader loader
*/
'private __construct' : function(reporter, loader) {
this.reporter = reporter;
this.loader = loader;
},
/**
* Returns an Annotations object for the given filename and scope.
*
* @param string filename
* @param string scope
* @return Annotations
*/
'public function get' : function (definition) {
var filename = definition.filename,
scope = definition.reflector.getName(),
cache_id = filename + ':' + scope;
// Do we have a cached version of this and scope?
if (typeof this.cache[cache_id] !== 'undefined') {
return this.cache[cache_id];
}
var r = JOII.Unit.AnnotationReader.AnnotationParser,
a = JOII.Unit.AnnotationReader.Annotations,
p = new r(this.loader.getSource(filename), scope);
this.reporter.debug('AnnotationReader::get - Parsing annotations for ' + filename + ', scope: ' + scope);
this.cache[cache_id] = new a(this.hydrateAnnotations(definition, p));
return this.cache[cache_id];
},
/**
* Returns a hydrated list of annotation objects indexed by method.
*
* @param AnnotationParser parser
*/
'private hydrateAnnotations' : function(definition, parser) {
var col = parser.getAnnotations(), method, result = {};
for (method in col) {
result[method] = [];
for (i in col[method]) {
result[method].push(this.parseAnnotation(definition, method, col[method][i]));
}
}
return result;
},
'private parseAnnotation' : function(definition, method, data) {
var tmp = data.split(' '),
name = tmp[0],
data = '';
tmp.shift(); data = tmp.join(' ');
for (var i in JOII.Unit.Annotation) {
if (typeof JOII.Unit.Annotation[i] !== 'function') {
continue;
}
var a = new JOII.Unit.Annotation[i](definition, method, name, data);
if (typeof a.accepts === 'function' && a.accepts(name)) {
a.init();
return a;
}
delete a;
}
throw new Error('Unknown Annotation: "' + name + '"');
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('AnnotationReader');
/**
* JOII-Unit Annotations
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.Annotations = Class({
'private object annotations' : null,
'private object cache' : {},
/**
* @param JOII.Unit.AnnotationReader.AnnotationParser parser
*/
'private __construct' : function (annotations) {
this.annotations = annotations;
},
'public function getMethod' : function(method) {
if (typeof this.cache[method] === 'undefined') {
var m = JOII.Unit.AnnotationReader.AnnotationMethod,
p = this.annotations[method];
this.cache[method] = new m(method, p);
}
return this.cache[method];
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Interface, undefined) {
var ns = namespace('Annotation.Behavior');
/**
* When this interface is implemented on an annotation, the annotation will
* be responsible for executing the test.
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.ExceptionBehavior = Interface('UAB_Exception', {
/**
* Returns true if the given Error matches the expected exception.
*
* @param object|string e
* @return bool
*/
'public function isValid' : function (e) {},
/**
* Returns the message from the given Error object.
* @return string
*/
'public function getExceptionString' : function (e) {},
/**
* Returns the expected exception.
* @return string
*/
'public function getExpectedExceptionString' : function () {}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.InterfaceBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Interface, undefined) {
var ns = namespace('Annotation.Behavior');
/**
* When this interface is implemented on an annotation, the annotation will
* be responsible for executing the test.
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.TestExecutorBehavior = Interface('UAB_TestExecutor', {
/**
* Executes the function passed in argument #1
*/
'public function execute' : function (fn) {}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.InterfaceBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('Annotation');
ns.DataProvider = Class('JOII.Unit.Annotation.DataProvider', {
'extends' : JOII.Unit.AbstractAnnotation,
'implements' : ['UAB_TestExecutor']
}, {
'private string provider' : null,
/** @override */
'public function accepts' : function(name) {
return name.toLowerCase() === 'dataprovider';
},
/** @override */
'public function init' : function() {
this.provider = JSON.parse(this.getData());
if (!this.getDefinition().reflector.hasMethod(this.provider)) {
throw new Error('DataProvider \'' + this.provider + '\' for method \'' + this.getMethod() + '\' in TestCase \'' + this.getDefinition().reflector.getName() + '\' does not exist.');
}
},
/**
* @see UA_TestExecutorBehavior.execute
*/
'public function execute' : function(fn) {
var inst = new this.definition;
var args_col = inst[this.provider]();
for (var i in args_col) {
fn.apply(undefined, args_col[i]);
}
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('Annotation');
ns.ExpectedException = Class('JOII.Unit.Annotation.ExpectedException', {
'extends' : JOII.Unit.AbstractAnnotation,
'implements' : ['UAB_Exception']
}, {
/** @override */
'public function accepts' : function(name) {
return name.toLowerCase() === 'expectedexception';
},
/** @override */
'public function init' : function() {
var message = JSON.parse(this.getData());
},
/**
* @see UAB_Exception.isValid
*/
'public function isValid' : function(e) {
return this.getExceptionString(e) === this.getExpectedExceptionString();
},
/**
* @see UAB_Exception.getExceptionString
*/
'public function getExceptionString' : function (e) {
if (typeof e === 'object') {
return e.message;
}
return e;
},
/**
* @see UAB_Exception.getExpectedExceptionString
*/
'public function getExpectedExceptionString' : function () {
return JSON.parse(this.getData());
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('Loader');
/**
* JOII-Unit Reporter suitable for a NodeJS environment.
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.NodeLoader = Class({ 'implements' : JOII.Unit.LoaderInterface }, {
'protected object files' : [],
'protected object source' : {},
'protected object reporter' : null,
'protected string last_file' : '',
/**
* Constructor
*/
'private __construct' : function (dep_files, test_files, reporter) {
var i, f = [];
for (i in dep_files) { f.push(dep_files[i]); }
for (i in test_files) { f.push(test_files[i]); }
this.setFiles(f);
this.setReporter(reporter);
},
/**
* @see JOII.Unit.LoaderInterface.load()
*/
'public function load' : function (callback) {
// Is there anything to load?
if (this.files.length === 0) {
this.reporter.critical('No test files registered.');
return;
}
// Load files
var file, src, i, loaded = [], fs = require('fs');
for (i in this.files) {
file = this.absPath(this.files[i]);
this.last_file = this.files[i];
this.reporter.debug('NodeLoader::load - Loading file ' + this.files[i] + ' (' + file + ')');
this.source[this.files[i]] = fs.readFileSync(file, 'utf8');
require(file);
loaded.push(this.files[i]);
}
callback(loaded);
},
'public function getLastLoadedFile' : function () {
return this.last_file;
},
/**
* Make an absolute path from relative
*
* @param string file
* @return string
*/
'private function absPath' : function (file) {
if (file.charAt(0) != '/') {
file = require('path').resolve(process.cwd(), file);
}
return file;
},
/**
* Returns the contents of the given file as text.
*
* @param string file
* @return string
*/
'public getSource' : function(file)
{
if (typeof this.source[file] === 'undefined') {
throw new Error('Source code for file "' + file + '" is unavailable.');
}
return this.source[file];
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('Reporter');
/**
* JOII-Unit Reporter suitable for a NodeJS environment.
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.NodeReporter = Class({ 'implements' : JOII.Unit.ReporterInterface }, {
'private boolean verbose' : false,
'private object output' : null,
'private object color' : null,
'private number lc' : 0,
'private number ac' : 0,
/**
* Constructor
*/
'private __construct' : function (env, verbose) {
if (env !== 'node') {
throw new Error('NodeReporter requires a NodeJS environment!');
}
this.verbose = verbose;
this.output = process.stdout;
this.color = require('cli-color');
},
/**
* @see JOII.Unit.ReporterInterface.init()
*/
'public function init' : function () {
// Load package.json to build a nice "banner".
var title, subtitle;
try {
var p = JSON.parse(require('fs').readFileSync(__dirname + '/../package.json', 'utf8'));
title = this.color.cyan.bold(p.name) + this.color.cyan(' (module) ') + this.color.cyan.bold(p.version) + ', by ' + p.author + '\n',
subtitle = p.homepage + '\n\n';
} catch (e) {
// Probably unable to load package.json.
title = this.color.cyan.bold('joii-unit') + this.color.cyan(' (standalone)') + ', by Harold Iedema\n';
subtitle = 'https://github.com/haroldiedema/joii-unit/\n\n';
}
process.stdout.write('\n' + title + subtitle);
},
/**
* @see JOII.Unit.ReporterInterface.reportAssertPass
*/
'public function reportAssertPass' : function (testcase, method, message, actual, expected) {
this.output.write('.');
this.lc ++;
this.ac ++;
if (this.lc > 70) {
this.output.write(' (' + this.ac + ')\n');
this.lc = 0;
}
},
/**
* @see JOII.Unit.ReporterInterface.reportAssertFail
*/
'public function reportAssertFail' : function (testcase, method, message, actual, expected) {
this.output.write(this.color.bgRed.white.bold('F'));
this.lc ++;
this.ac ++;
if (this.lc > 70) {
this.output.write(' (' + this.ac + ')\n');
this.lc = 0;
}
},
'public function reportSummary' : function(report)
{
this.output.write('\n\n');
// Draw a nice header indicating some tests failed or not.
if (report.assertions.failed > 0) {
this.output.write(this.color.bgRed.white.bold('FAILURES! Tests: ' + report.testcount + ', Assertions: ' + report.assertions.total + ', Failures: ' + report.assertions.failed) + '\n\n');
this.reportSummaryFailed(report);
} else {
this.output.write(this.color.bgGreen.black('OK (' + report.testcount + ' tests, ' + report.assertions.total + ' assertions)') + '\n\n');
}
},
'private function reportSummaryFailed' : function(report) {
var file, testcase_name, test_name, tests, method, i, res, trace;
for (file in report.testcases) {
for (testcase_name in report.testcases[file]) {
tests = report.testcases[file][testcase_name].tests;
for (method in tests) {
for (i in tests[method]) {
if (tests[method][i].passed !== false) {
continue;
}
res = tests[method][i];
trace = res.trace.split('\n');
this.output.write(file + ' (' + testcase_name + ')\n');
this.output.write(' Assertion : "' + res.message + '"\n');
this.output.write(' Expected : ' + JSON.stringify(res.expected) + "\n");
this.output.write(' Actual : ' + JSON.stringify(res.actual) + "\n");
for (var t in trace) {
trace[t] = trace[t].replace(/\\/g, '/');
if (trace[t].indexOf(file) !== -1 && trace[t].indexOf(testcase_name) !== -1) {
this.output.write(this.color.blackBright(trace[t]) + "\n");
}
}
this.output.write("\n");
}
}
}
}
},
/**
* @see JOII.Unit.ReporterInterface.critical()
*/
'public function critical' : function (message) {
process.stdout.write('\n' + this.color.bgRed.white.bold('CRITICAL: ' + message) + '\n\n');
},
/**
* @see JOII.Unit.ReporterInterface.error()
*/
'public function error' : function (message) {
process.stdout.write('\n' + this.color.bgRed.white.bold('ERROR: ' + message) + '\n\n');
},
/**
* @see JOII.Unit.ReporterInterface.debug()
*/
'public function debug' : function (message) {
if (!this.verbose) {
return;
}
process.stdout.write(this.color.blackBright(message) + '\n');
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
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));
/*
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 Definition
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.Definition = Class({
'private object name' : null,
'private object fn' : null,
'private object instance' : null,
'private object api' : null,
'private object is_public' : true,
'private object args' : [],
'private object calls' : [],
'private object tags' : {},
/**
* @param string name The name of this definition.
* @param mixed fn Function or string referencing the function.
*/
'private __construct': function(fn)
{
if (typeof(fn) !== 'function') {
this.fn = this.findFunctionFromString(fn);
} else {
this.fn = fn;
}
},
/**
* Adds an argument to pass to the service constructor.
*
* @param mixed value
* @return DependencyInjection.Definition
*/
'public function addArgument': function(argument)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.args.push(argument);
return this;
},
/**
* Sets the arguments to pass to the service constructor.
*
* @param mixed value
* @return DependencyInjection.Definition
*/
'public function setArguments': function(args)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.args = args;
return this;
},
/**
* Gets the arguments to pass to the service constructor.
*
* @return array
*/
'public function getArguments': function()
{
return this.args;
},
/**
* Sets the methods to call after service initialization.
*
* @param array calls
* @return DependencyInjection.Definition
*/
'public function setMethodCalls': function(calls)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.calls = [];
for (var i in calls) {
if (calls.hasOwnProperty(i)) {
this.calls[i] = calls[i];
}
}
return this;
},
/**
* Adds a method to call after service initialization.
*
* @param string method
* @param array args
* @return DependencyInjection.Definition
*/
'public function addMethodCall': function(method, args)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
args = args || [];
this.calls.push([method, args]);
return this;
},
/**
* Removes a method call from this definition by the given name.
*
* @param string method
* @return DependencyInjection.Definition
*/
'public function removeMethodCall': function(method)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
for (var i in this.calls) {
if (this.calls[i][0] === method) {
delete this.calls[i];
}
}
return this;
},
/**
* Check if the current definition has a given method to call after
* service initialization.
*
* @return bool
*/
'public function hasMethodCall': function(method)
{
for (var i in this.calls) {
if (this.calls[i][0] === method) {
return true;
}
}
return false;
},
/**
* Gets the methods to call after service initialization.
*
* @return array
*/
'public function getMethodCalls': function()
{
return this.calls;
},
/**
* Sets tags for this definition.
*
* @param array tags
* @return DependencyInjection.Definition
*/
'public function setTags': function(tags)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.tags = tags;
return this;
},
/**
* Returns all tags.
*
* @return array
*/
'public function getTags': function()
{
return this.tags;
},
/**
* Gets a tag by name.
*
* @param string name The tag name
* @return array An array of attributes
*/
'public function getTag': function(name)
{
return typeof(this.tags[name]) !== 'undefined'
? this.tags[name] : [];
},
/**
* Add a tag for this definition.
*
* @param string name
* @param array attributes
* @return DependencyInjection.Definition
*/
'public function addTag': function(name, attributes)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
if (typeof(this.tags[name]) === 'undefined') {
this.tags[name] = [];
}
this.tags[name].push(attributes);
return this;
},
/**
* Returns true if this definition has a tag with the given name.
*
* @return bool
*/
'public function hasTag': function(name)
{
return typeof(this.tags[name]) !== 'undefined';
},
/**
* Clears tags with the given name.
*
* @return DependencyInjection.Definition
*/
'public function clearTag': function(name)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
if (typeof(this.tags[name]) !== 'undefined') {
delete this.tags[name];
}
return this;
},
/**
* @return DependencyInjection.Definition
*/
'public function clearTags': function()
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.tags = {};
return this;
},
/**
* Sets the visibility of this service.
*
* @return DependencyInjection.Definition
*/
'public function setPublic': function(flag)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.is_public = flag;
return this;
},
/**
* Returns true if this definition is public.
*
* Being public means it's retrievable from the container. A private
* service is only usable as a dependency on other services.
*
* @return bool
*/
'public function isPublic': function()
{
return this.is_public;
},
/**
* Returns true if this definition has an instance of the function
* associated with it.
*
* @return bool
*/
'public function hasInstance': function()
{
return !!this.instance;
},
/**
* Returns the associated instance.
*
* @return Object
*/
'public function getInstance': function()
{
if (typeof(this.instance) !== 'undefined') {
return this.instance;
}
throw new Error('Definition is not initialized.');
},
/**
* Sets the instance for this definition.
*
* @param object instance
* @return DependencyInjection.Definition
*/
'public function setInstance': function(instance)
{
if (this.hasInstance()) {
throw new Error('Unable to update a definition that is already initialized.');
}
this.instance = instance;
return this;
},
/**
* Returns the function associated with this Definition.
*
* @return Function
*/
'public function getFunction': function()
{
return this.fn;
},
/**
* Finds the given function by string reference.
*
* @access private
* @return function
*/
'private function findFunctionFromString': function(str)
{
if (str.indexOf('.') === -1) {
// There are no namespace separators, just return it.
if (typeof(g[str]) === 'function') {
return g[str];
}
throw new Error(str + ' is undefined or not a function.');
}
var chunks = str.split('.'),
result = g,
str_rep = '',
current;
while ((current = chunks.shift())) {
str_rep += current;
if (typeof(result[current]) === 'undefined' || (
typeof(result[current]) !== 'object' &&
typeof(result[current]) !== 'function')) {
throw new Error(str_rep + ' is undefined or not iterable.');
}
result = result[current];
str_rep += '.';
}
return result;
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
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 ServiceContainer
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.ServiceContainer = Class({
'private object container' : null,
'private __construct': function(container)
{
if (typeof(container) === 'undefined' || !container) {
container = new ns.Container();
}
this.container = container;
},
/**
* Returns the container.
*
* @return DependencyInjection.Container
*/
'public function getContainer': function()
{
return this.container;
},
/**
* Loads configuration which will be parsed and injected into
* the Container.
*
* @return DependencyInjection.ContainerBuilder
*/
'public function loadConfiguration': function(config)
{
if (typeof(config) !== 'object') {
throw new Error('loadConfiguration expectes an object, ' + typeof(config) + ' given.');
}
if (typeof(config.parameters) === 'undefined' &&
typeof(config.services) === 'undefined') {
throw new Error("The configuration object must have a 'parameters' and/or 'services' element.");
}
// If we have 'parameters' object in the config...
if (typeof(config.parameters) === 'object') {
this.loadParameters(config.parameters);
}
// If we have a 'services' object in the config...
if (typeof(config.services) === 'object') {
for (var i in config.services) {
if (!config.services.hasOwnProperty(i)) {
continue;
}
this.loadService(i, config.services[i]);
}
}
},
/**
* Iterates over the parameters object and injects them into the
* Container being built.
*
* @access private
*/
'private function loadParameters': function(parameters)
{
for (var i in parameters) {
if (!parameters.hasOwnProperty(i)) {
continue;
}
this.container.setParameter(i, parameters[i]);
}
},
/**
* Creates a service definition.
*
* @access private
* @param string id
* @param object config
*/
'private function loadService': function(id, config)
{
var def = this.container.register(id, config['class']);
// Set constructor arguments
if (typeof(config.arguments) !== 'undefined') {
def.setArguments(config.arguments);
}
// Set method calls
if (typeof(config.calls) !== 'undefined') {
def.setMethodCalls(config.calls);
}
// Set tags
if (typeof(config.tags) !== 'undefined') {
def.setTags(config.tags);
}
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('');
/**
* JOII-Unit TestCase
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.TestCase = Class({
/**
* Allow executing this class as a function and delegate the call to
* TestCase.Register
*/
__call: function() {
ns.TestCase.Register.apply(undefined, arguments);
},
// Callbacks
'final public object on_pass' : null,
'final public object on_fail' : null,
'final private __construct' : function (on_pass, on_fail) {
this.on_pass = on_pass || function (m, a, e, t) {};
this.on_fail = on_fail || function (m, a, e, t) {
throw new Error(m + "\n Expected : " + JSON.stringify(e) + "\n Actual : " + JSON.stringify(a) + "\n");
};
},
// Registers a passed test.
'final private function pass' : function(message, actual, expected) {
try { throw new Error(message); } catch (e) { trace = e.stack; }
this.on_pass(message, actual, expected, trace);
},
// Registers a failed test.
'final private function fail' : function(message, actual, expected) {
// Throw an Error to generate a trace
var trace = [];
try { throw new Error(message); } catch (e) { trace = e.stack; }
this.on_fail(message, actual, expected, trace);
}
});
ns.TestCase.Register = function(name, body) {
var unit = JOII.Unit.instance;
if (typeof unit === 'undefined') {
throw new Error('Unable to register a TestCase without an active JOII.Unit instance.');
}
// Append "Test" to the TestCase name.
if (name.substr(name.length - 4) !== 'Test') {
name += 'Test';
}
// Gather and apply traits
var i, traits = [];
for (i in JOII.Unit.TestCase.Traits) {
traits.push(JOII.Unit.TestCase.Traits[i]);
}
var testcase = Class(name, { 'extends': JOII.Unit.TestCase, uses: traits }, body)
unit.getContainer().get('joii.unit.test_manager').addTest(testcase);
}
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('TestCase');
/**
* JOII-Unit TestCase Process
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.Process = Class({
'private testcase' : null,
'private reporter' : null,
'private current_test' : null,
'private result' : {},
/**
* Constructor
*
* @param JOII.Unit.TestCase testcase
*/
'private __construct' : function (testcase, reporter) {
this.testcase = testcase;
this.reporter = reporter;
this.result.testcase = testcase;
this.result.tests = {};
this.result.assertions = { passed: 0, failed: 0 };
this.result.runtime = 0;
},
'public function getTestCase' : function () {
return this.testcase;
},
/**
* Runs the TestCase and returns an object with test results.
*
* @return object
*/
'public function run' : function () {
for (var i in this.testcase.methods) {
var method = this.testcase.methods[i],
annotations = this.testcase.annotations.getMethod(method);
this.current_method = method;
this.result.tests[method] = [];
// Does the method implement annotation "UAB_TestExecutor" ?
if (annotations.has('UAB_TestExecutor')) {
// Grab it and let it execute the test.
var annotation = annotations.get('UAB_TestExecutor');
this.dataset = 0;
annotation.execute(JOII.Compat.Bind(function() {
this.dataset ++;
this.runTest(method, arguments);
}, this));
} else {
this.dataset = undefined;
this.runTest(this.testcase.methods[i], []);
}
}
return this.result;
},
/**
* Runs one test.
*/
'private function runTest' : function(method, args) {
var on_pass = JOII.Compat.Bind(this.registerPass, this);
var on_fail = JOII.Compat.Bind(this.registerFail, this);
var instance = new this.testcase(on_pass, on_fail),
annotations = this.testcase.annotations.getMethod(method);
if (this.testcase.reflector.hasMethod('setup')) {
instance.setup();
}
this.current_test = method;
if (annotations.has('UAB_Exception')) {
var ex = annotations.get('UAB_Exception').getExpectedExceptionString();
try {
instance[method].apply(undefined, args);
on_fail('The expected exception "' + ex + '" was never thrown.', undefined, ex);
} catch (e) {
if (! annotations.get('UAB_Exception').isValid(e)) {
var m = 'Thrown exception did not match the expected exception.',
o = on_fail;
} else {
var m = 'The expected exception was thrown.',
o = on_pass;
}
e = annotations.get('UAB_Exception').getExceptionString(e);
o(m, e, ex);
}
} else {
instance[method].apply(undefined, args);
}
if (this.testcase.reflector.hasMethod('teardown')) {
instance.teardown();
}
},
'private function registerPass' : function (message, actual, expected, trace) {
if (this.dataset !== undefined) {
message = '[dataset #' + this.dataset + '] ' + message;
}
this.result.assertions.passed++;
this.result.tests[this.current_method].push({
passed: true, message: message, actual: actual, expected: expected, trace: trace
});
this.reporter.reportAssertPass(this.testcase, this.current_test, message, actual, expected, trace);
},
'private function registerFail' : function (message, actual, expected) {
if (this.dataset !== undefined) {
message = '[dataset #' + this.dataset + '] ' + message;
}
this.result.assertions.failed++;
this.result.tests[this.current_method].push({
passed: false, message: message, actual: actual, expected: expected, trace: trace
});
this.reporter.reportAssertFail(this.testcase, this.current_test, message, actual, expected, trace);
}
});
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));
/*
JavaScript Unit Testing Framework _ _ _
- Powered by JOII (_)___ (_|_) .__ __
/ / __ \/ / / __ __ ____ |__|/ |_
(c)2014, <harold@iedema.me> / / /_/ / / / | | \/ \| \ __\
Released under the MIT license. __/ /\____/_/_/ | | / | \ || |
--------------------------------- /___/ ------------ |____/|___| /__||__| ---
*/
(function(g, namespace, Class, undefined) {
// Namespace declaration
var ns = namespace('TestCase.Traits');
/**
* Contains assertion methods to be used in a TestCase.
*
* @author Harold Iedema <harold@iedema.me>
*/
ns.AssertScalar = {
'final protected assert' : function (p, m, a, e) {
this[(p === true) ? 'pass' : 'fail'](m, a, e);
},
/**
* Assert the given value equals true in a strict comparison.
*
* @param mixed a
* @param string message
*/
'final protected function assertTrue' : function(a, message) {
this.assert(a === true, message, a, true);
},
/**
* Assert the given value equals false in a strict comparison.
*
* @param mixed a
* @param string message
*/
'final protected function assertFalse' : function(a, message) {
this.assert(a === false, message, a, false);
},
/**
* Perform a non-strict equals comparison on the given values.
*
* @param mixed a
* @param mixed b
* @param string message
*/
'final protected function assertEqual' : function(a, b, message) {
this.assert(a == b, message, a, b);
},
/**
* Perform a non-strict not-equals comparison on the given values.
*
* @param mixed a
* @param mixed b
* @param string message
*/
'final protected function assertNotEqual' : function(a, b, message) {
this.assert(a != b, message, a, b);
},
/**
* Perform a strict equals comparison on the given values.
*
* @param mixed a
* @param mixed b
* @param string message
*/
'final protected function assertEqualStrict' : function(a, b, message) {
this.assert(a === b, message, a, b);
},
/**
* Perform a strict not-equals comparison on the given values.
*
* @param mixed a
* @param mixed b
* @param string message
*/
'final protected function assertNotEqualStrict' : function(a, b, message) {
this.assert(a !== b, message, a, b);
}
};
} ((typeof window !== 'undefined' ? window : global),
(typeof window !== 'undefined' ? window : global).JOII.Unit.Namespace,
(typeof window !== 'undefined' ? window : global).JOII.ClassBuilder));