diff --git a/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp b/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp
index 062e96028e..ea193d3c7b 100644
--- a/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp
+++ b/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp
@@ -38,11 +38,87 @@ To add further edge definitions to the array one must call:
!SUBSUBSECTION Undirected Relation
-
+
+
+`general-graph._undirectedRelationDefinition(relationName, vertexCollections)`
+*Define an undirected relation.*
+
+Defines an undirected relation with the name *relationName* using the
+list of *vertexCollections*. This relation allows the user to store
+edges in any direction between any pair of vertices within the
+*vertexCollections*.
+
+@EXAMPLES
+
+To define simple relation with only one vertex collection:
+
+
+```
+arangosh> var graph = require("org/arangodb/general-graph");
+arangosh> graph._undirectedRelationDefinition("friend", "user");
+{
+ "collection" : "friend",
+ "from" : [
+ "user"
+ ],
+ "to" : [
+ "user"
+ ]
+}
+```
+
+To define a relation between several vertex collections:
+
+
+```
+arangosh> var graph = require("org/arangodb/general-graph");
+arangosh> graph._undirectedRelationDefinition("marriage", ["female", "male"]);
+{
+ "collection" : "marriage",
+ "from" : [
+ "female",
+ "male"
+ ],
+ "to" : [
+ "female",
+ "male"
+ ]
+}
+```
+
!SUBSUBSECTION Directed Relation
-
+
+
+`general-graph._directedRelationDefinition(relationName, fromVertexCollections, toVertexCollections)`
+*Define a directed relation.*
+
+The *relationName* defines the name of this relation and references to the underlying edge collection.
+The *fromVertexCollections* is an Array of document collections holding the start vertices.
+The *toVertexCollections* is an Array of document collections holding the target vertices.
+Relations are only allowed in the direction from any collection in *fromVertexCollections*
+to any collection in *toVertexCollections*.
+
+@EXAMPLES
+
+
+```
+arangosh> var graph = require("org/arangodb/general-graph");
+arangosh> graph._directedRelationDefinition("has_bought", ["Customer", "Company"], ["Groceries", "Electronics"]);
+{
+ "collection" : "has_bought",
+ "from" : [
+ "Customer",
+ "Company"
+ ],
+ "to" : [
+ "Groceries",
+ "Electronics"
+ ]
+}
+```
+
!SUBSUBSECTION Complete Example to create a graph
@@ -77,20 +153,74 @@ alternative call:
!SUBSECTION Orphan Collections
Each graph has an orphan collection. It consists of arbitrary many vertex collection (type *document*), that are not
-used in an edge definition of the graph. If the graph is extended with an edge definition, which is part of the orphan
-collection, it will be removed from the orphan collection automatically.
+used in an edge definition of the graph. If the graph is extended with an edge definition using one of the orphans,
+it will be removed from the orphan collection automatically.
!SUBSUBSECTION Add
-
+
+Adds a vertex collection to the set of orphan collections of the graph. If the
+collection does not exist, it will be created.
+
+`general-graph._addOrphanCollection(orphanCollectionName, createCollection)`
+
+*orphanCollectionName* - string : name of vertex collection.
+*createCollection* - bool : if true the collection will be created if it does not exist. Default: true.
+
+@EXAMPLES
+
+
+```
+arangosh> var graph = require("org/arangodb/general-graph")
+arangosh> var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+arangosh> var g = graph._create("myGraph", [ed1, ed2]);
+ReferenceError: ed2 is not defined
+```
+
+
!SUBSUBSECTION Read
-
+
+Returns all vertex collections of the graph, that are not used in an edge definition.
+
+`general-graph._getOrphanCollections()`
+
+@EXAMPLES
+
+
+```
+arangosh> var graph = require("org/arangodb/general-graph")
+arangosh> var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+arangosh> var g = graph._create("myGraph", [ed1]);
+[ArangoError 1925: graph already exists]
+```
+
+
!SUBSUBSECTION Remove
-
+
+Removes an orphan collection from the graph and deletes the collection, if it is not
+used in any graph.
+
+`general-graph._removeOrphanCollection()`
+
+*orphanCollectionName* - string : name of vertex collection.
+*dropCollection* - bool : if true the collection will be dropped if it is not used in any graph.
+Default: true.
+
+@EXAMPLES
+
+
+```
+arangosh> var graph = require("org/arangodb/general-graph")
+arangosh> var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+arangosh> var g = graph._create("myGraph", [ed1]);
+[ArangoError 1925: graph already exists]
+```
+
+
!SUBSECTION Read a graph
@@ -122,47 +252,324 @@ dropCollections: bool - optional. *true* all collections of the graph will be de
!SUBSECTION Save
-
+
+Creates and saves a new vertex in collection *vertexCollectionName*
+
+`general-graph.vertexCollectionName.save(data)`
+
+*data*: json - data of vertex
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.male.save({name: "Floyd", _key: "floyd"});
+{
+ "error" : false,
+ "_id" : "male/floyd",
+ "_rev" : "91260521",
+ "_key" : "floyd"
+}
+```
+
+
!SUBSECTION Replace
-
+
+Replaces the data of a vertex in collection *vertexCollectionName*
+
+`general-graph.vertexCollectionName.replace(vertexId, data, options)`
+
+*vertexId*: string - id of the vertex
+*data*: json - data of vertex
+*options*: json - (optional) - see collection documentation
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.male.save({neym: "Jon", _key: "john"});
+{
+ "error" : false,
+ "_id" : "male/john",
+ "_rev" : "31360617",
+ "_key" : "john"
+}
+arangosh> g.male.replace("male/john", {name: "John"});
+{
+ "error" : false,
+ "_id" : "male/john",
+ "_rev" : "31557225",
+ "_key" : "john"
+}
+```
+
+
!SUBSECTION Update
-
+
+Updates the data of a vertex in collection *vertexCollectionName*
+
+`general-graph.vertexCollectionName.update(vertexId, data, options)`
+
+*vertexId*: string - id of the vertex
+*data*: json - data of vertex
+*options*: json - (optional) - see collection documentation
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.female.save({name: "Lynda", _key: "linda"});
+{
+ "error" : false,
+ "_id" : "female/linda",
+ "_rev" : "79201897",
+ "_key" : "linda"
+}
+arangosh> g.female.update({name: "Linda", _key: "linda"});
+TypeError: Object # has no method 'split'
+```
+
+
!SUBSECTION Remove
-
+
+Removes a vertex in collection *vertexCollectionName*
+
+`general-graph.vertexCollectionName.remove(vertexId, options)`
+
+Additionally removes all ingoing and outgoing edges of the vertex recursively
+(see [edge remove](#edge.remove)).
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.male.save({name: "Kermit", _key: "kermit"});
+{
+ "error" : false,
+ "_id" : "male/kermit",
+ "_rev" : "83068521",
+ "_key" : "kermit"
+}
+arangosh> db._exists("male/kermit")
+true
+arangosh> g.male.remove("male/kermit")
+true
+arangosh> db._exists("male/kermit")
+false
+```
+
+
!SECTION Edge
!SUBSECTION Save
-
+
+Creates and saves a new edge from vertex *from* to vertex *to* in
+collection *edgeCollectionName*
+
+`general-graph.edgeCollectionName.save(from, to, data)`
+
+*from*: string - id of outgoing vertex
+*to*: string - of ingoing vertex
+*data*: json - data of edge
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.relation.save("male/bob", "female/alice", {type: "married", _key: "bobAndAlice"});
+{
+ "error" : false,
+ "_id" : "relation/bobAndAlice",
+ "_rev" : "45909609",
+ "_key" : "bobAndAlice"
+}
+```
+
+If the collections of *from* and *to* are not defined in an edgeDefinition of the graph,
+the edge will not be stored.
+
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.relation.save("relation/aliceAndBob", "female/alice", {type: "married", _key: "bobAndAlice"});
+Edge is not allowed between relation/aliceAndBob and female/alice.
+```
+
!SUBSECTION Replace
-
+
+Replaces the data of an edge in collection *edgeCollectionName*
+
+`general-graph.edgeCollectionName.replace(edgeId, data, options)`
+
+*edgeId*: string - id of the edge
+*data*: json - data of edge
+*options*: json - (optional) - see collection documentation
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.relation.save("female/alice", "female/diana", {typo: "nose", _key: "aliceAndDiana"});
+{
+ "error" : false,
+ "_id" : "relation/aliceAndDiana",
+ "_rev" : "27362921",
+ "_key" : "aliceAndDiana"
+}
+arangosh> g.relation.replace("relation/aliceAndDiana", {type: "knows"});
+{
+ "error" : false,
+ "_id" : "relation/aliceAndDiana",
+ "_rev" : "27559529",
+ "_key" : "aliceAndDiana"
+}
+```
+
+
!SUBSECTION Update
-
+
+Updates the data of an edge in collection *edgeCollectionName*
+
+`general-graph.edgeCollectionName.update(edgeId, data, options)`
+
+*edgeId*: string - id of the edge
+*data*: json - data of edge
+*options*: json - (optional) - see collection documentation
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.relation.save("female/alice", "female/diana", {type: "knows", _key: "aliceAndDiana"});
+{
+ "error" : false,
+ "_id" : "relation/aliceAndDiana",
+ "_rev" : "127108713",
+ "_key" : "aliceAndDiana"
+}
+arangosh> g.relation.update("relation/aliceAndDiana", {type: "quarrelled", _key: "aliceAndDiana"});
+{
+ "error" : false,
+ "_id" : "relation/aliceAndDiana",
+ "_rev" : "127305321",
+ "_key" : "aliceAndDiana"
+}
+```
+
+
!SUBSECTION Remove
-
+
+Removes an edge in collection *edgeCollectionName*
+
+`general-graph.edgeCollectionName.remove(edgeId, options)`
+
+If this edge is used as a vertex by another edge, the other edge will be removed (recursively).
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g.relation.save("female/alice", "female/diana", {_key: "aliceAndDiana"});
+{
+ "error" : false,
+ "_id" : "relation/aliceAndDiana",
+ "_rev" : "141657705",
+ "_key" : "aliceAndDiana"
+}
+arangosh> db._exists("relation/aliceAndDiana")
+true
+arangosh> g.relation.remove("relation/aliceAndDiana")
+true
+arangosh> db._exists("relation/aliceAndDiana")
+false
+```
+
+
!SECTION Vertices
!SUBSECTION Get vertex *from* of an edge
-
+
+Get the vertex of an edge defined as *_from*
+
+`general-graph._getFromVertex(edgeId)`
+
+Returns the vertex defined with the attribute *_from* of the edge with *edgeId* as its *_id*.
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g._getFromVertex("relation/aliceAndBob")
+{
+ "name" : "Alice",
+ "_id" : "female/alice",
+ "_rev" : "175670889",
+ "_key" : "alice"
+}
+```
+
+
!SUBSECTION Get vertex *to* of an edge
-
+
+Get the vertex of an edge defined as *_to*
+
+`general-graph._getToVertex(edgeId)`
+
+Returns the vertex defined with the attribute *_to* of the edge with *edgeId* as its *_id*.
+
+@EXAMPLES
+
+
+```
+arangosh> var examples = require("org/arangodb/graph-examples/example-graph.js");
+arangosh> var g = examples.loadGraph("social");
+arangosh> g._getToVertex("relation/aliceAndBob")
+{
+ "name" : "Bob",
+ "_id" : "male/bob",
+ "_rev" : "40666729",
+ "_key" : "bob"
+}
+```
+
diff --git a/Documentation/Books/Users/UsingJsUnity/README.mdpp b/Documentation/Books/Users/UsingJsUnity/README.mdpp
index 89ae42bdb1..99d0b09384 100644
--- a/Documentation/Books/Users/UsingJsUnity/README.mdpp
+++ b/Documentation/Books/Users/UsingJsUnity/README.mdpp
@@ -30,17 +30,3 @@ Then you can run the test suite using *jsunity.runTest*
2012-01-28T19:10:23Z [10671] INFO 1 test passed
2012-01-28T19:10:23Z [10671] INFO 0 tests failed
2012-01-28T19:10:23Z [10671] INFO 1 millisecond elapsed
-
-!SUBSECTION Running jsUnity Tests with Coverage
-
-You can use the coverage tool
-[node-jscoverage](https://github.com/visionmedia/node-jscoverage)
-
-Assume that your file live in a directory called `lib`. Use
-
- node-jscoverage lib lib-cov
-
-to create a copy of the JavaScript files with coverage information. Start the
-ArangoDB with these files and use *jsunity.runCoverage* instead of
-*jsunity.runTest*.
-
diff --git a/Documentation/RefManual/JSModuleJSUnity.md b/Documentation/RefManual/JSModuleJSUnity.md
index 8cb5c5883b..2fe5abe1c0 100644
--- a/Documentation/RefManual/JSModuleJSUnity.md
+++ b/Documentation/RefManual/JSModuleJSUnity.md
@@ -36,18 +36,4 @@ Then you can run the test suite using @FN{jsunity.runTest}
2012-01-28T19:10:23Z [10671] INFO 0 tests failed
2012-01-28T19:10:23Z [10671] INFO 1 millisecond elapsed
-Running jsUnity Tests with Coverage{#jsUnityRunningCoverage}
-============================================================
-
-You can use the coverage tool @LIT{node-jscoverage} .
-
-Assume that your file live in a directory called `lib`. Use
-
- node-jscoverage lib lib-cov
-
-to create a copy of the JavaScript files with coverage information. Start the
-ArangoDB with these files and use @FN{jsunity.runCoverage} instead of
-@FN{jsunity.runTest}.
-
@BNAVIGATE_jsUnity
diff --git a/arangod/RestServer/ArangoServer.cpp b/arangod/RestServer/ArangoServer.cpp
index be97d85457..17575e8f03 100644
--- a/arangod/RestServer/ArangoServer.cpp
+++ b/arangod/RestServer/ArangoServer.cpp
@@ -1017,7 +1017,7 @@ int ArangoServer::runUnitTests (TRI_vocbase_t* vocbase) {
context->_context->Global()->Set(v8::String::New("SYS_UNIT_TESTS_RESULT"), v8::True());
// run tests
- char const* input = "require(\"jsunity\").runCommandLineTests();";
+ char const* input = "require(\"test_runner\").runCommandLineTests();";
TRI_ExecuteJavaScriptString(context->_context, v8::String::New(input), name, true);
if (tryCatch.HasCaught()) {
diff --git a/arangosh/V8Client/arangosh.cpp b/arangosh/V8Client/arangosh.cpp
index 60562feb89..657682db10 100644
--- a/arangosh/V8Client/arangosh.cpp
+++ b/arangosh/V8Client/arangosh.cpp
@@ -1555,7 +1555,7 @@ static bool RunUnitTests (v8::Handle context) {
context->Global()->Set(v8::String::New("SYS_UNIT_TESTS_RESULT"), v8::True());
// run tests
- char const* input = "require(\"jsunity\").runCommandLineTests();";
+ char const* input = "require(\"test_runner\").runCommandLineTests();";
v8::Local name(v8::String::New("(arangosh)"));
TRI_ExecuteJavaScriptString(context, v8::String::New(input), name, true);
diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js
new file mode 100755
index 0000000000..617c64e25b
--- /dev/null
+++ b/js/common/modules/jasmine.js
@@ -0,0 +1,54 @@
+/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true */
+/*global module, require, exports, print */
+
+/** Jasmine Wrapper
+ *
+ * This file is based upon Jasmine's boot.js,
+ * but adjusted to work with ArangoDB
+ *
+ * jasmine/core: This is an unmodified copy of Jasmine's Standalone Version
+ * jasmine/reporter: A reporter written for ArangoDB
+ */
+
+var jasmine = require('jasmine/core'),
+ _ = require('underscore'),
+ fs = require('fs'),
+ Reporter = require('jasmine/reporter').Reporter;
+
+jasmine = jasmine.core(jasmine);
+
+exports.executeTestSuite = function (specFileNames, options) {
+ 'use strict';
+ var sandbox = jasmine.getEnv(),
+ format = options.format || 'progress';
+
+ // Explicitly add require
+ sandbox.require = require;
+
+ /**
+ * The `jsApiReporter` also receives spec results, and is used by any environment
+ * that needs to extract the results from JavaScript.
+ */
+ var jsApiReporter = new jasmine.JsApiReporter({
+ timer: new jasmine.Timer()
+ });
+
+ sandbox.addReporter(jsApiReporter);
+
+ /**
+ * The `arangoReporter` does the reporting to the console
+ */
+ var arangoReporter = new Reporter({ format: format });
+ sandbox.addReporter(arangoReporter);
+
+ var _ = require('underscore'),
+ internal = require('internal');
+
+ _.each(specFileNames, function (specFileName) {
+ var spec = fs.read(specFileName);
+ internal.executeScript(spec, sandbox, specFileName);
+ });
+
+ sandbox.execute();
+ return !arangoReporter.hasErrors();
+};
diff --git a/js/common/modules/jasmine/core.js b/js/common/modules/jasmine/core.js
new file mode 100755
index 0000000000..24463ecb83
--- /dev/null
+++ b/js/common/modules/jasmine/core.js
@@ -0,0 +1,2402 @@
+/*
+Copyright (c) 2008-2013 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+function getJasmineRequireObj() {
+ if (typeof module !== "undefined" && module.exports) {
+ return exports;
+ } else {
+ window.jasmineRequire = window.jasmineRequire || {};
+ return window.jasmineRequire;
+ }
+}
+
+getJasmineRequireObj().core = function(jRequire) {
+ var j$ = {};
+
+ jRequire.base(j$);
+ j$.util = jRequire.util();
+ j$.Any = jRequire.Any();
+ j$.CallTracker = jRequire.CallTracker();
+ j$.Clock = jRequire.Clock();
+ j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+ j$.Env = jRequire.Env(j$);
+ j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+ j$.Expectation = jRequire.Expectation();
+ j$.buildExpectationResult = jRequire.buildExpectationResult();
+ j$.JsApiReporter = jRequire.JsApiReporter();
+ j$.matchersUtil = jRequire.matchersUtil(j$);
+ j$.ObjectContaining = jRequire.ObjectContaining(j$);
+ j$.pp = jRequire.pp(j$);
+ j$.QueueRunner = jRequire.QueueRunner();
+ j$.ReportDispatcher = jRequire.ReportDispatcher();
+ j$.Spec = jRequire.Spec(j$);
+ j$.SpyStrategy = jRequire.SpyStrategy();
+ j$.Suite = jRequire.Suite();
+ j$.Timer = jRequire.Timer();
+ j$.version = jRequire.version();
+
+ j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+ return j$;
+};
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+ var availableMatchers = [
+ "toBe",
+ "toBeCloseTo",
+ "toBeDefined",
+ "toBeFalsy",
+ "toBeGreaterThan",
+ "toBeLessThan",
+ "toBeNaN",
+ "toBeNull",
+ "toBeTruthy",
+ "toBeUndefined",
+ "toContain",
+ "toEqual",
+ "toHaveBeenCalled",
+ "toHaveBeenCalledWith",
+ "toMatch",
+ "toThrow",
+ "toThrowError"
+ ],
+ matchers = {};
+
+ for (var i = 0; i < availableMatchers.length; i++) {
+ var name = availableMatchers[i];
+ matchers[name] = jRequire[name](j$);
+ }
+
+ return matchers;
+};
+
+getJasmineRequireObj().base = function(j$) {
+ j$.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+ };
+
+ j$.MAX_PRETTY_PRINT_DEPTH = 40;
+ j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+ j$.getGlobal = (function() {
+ var jasmineGlobal = eval.call(null, "this");
+ return function() {
+ return jasmineGlobal;
+ };
+ })();
+
+ j$.getEnv = function(options) {
+ var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+ //jasmine. singletons in here (setTimeout blah blah).
+ return env;
+ };
+
+ j$.isArray_ = function(value) {
+ return j$.isA_("Array", value);
+ };
+
+ j$.isString_ = function(value) {
+ return j$.isA_("String", value);
+ };
+
+ j$.isNumber_ = function(value) {
+ return j$.isA_("Number", value);
+ };
+
+ j$.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+ };
+
+ j$.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+ };
+
+ j$.any = function(clazz) {
+ return new j$.Any(clazz);
+ };
+
+ j$.objectContaining = function(sample) {
+ return new j$.ObjectContaining(sample);
+ };
+
+ j$.createSpy = function(name, originalFn) {
+
+ var spyStrategy = new j$.SpyStrategy({
+ name: name,
+ fn: originalFn,
+ getSpy: function() { return spy; }
+ }),
+ callTracker = new j$.CallTracker(),
+ spy = function() {
+ callTracker.track({
+ object: this,
+ args: Array.prototype.slice.apply(arguments)
+ });
+ return spyStrategy.exec.apply(this, arguments);
+ };
+
+ for (var prop in originalFn) {
+ if (prop === 'and' || prop === 'calls') {
+ throw new Error("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon");
+ }
+
+ spy[prop] = originalFn[prop];
+ }
+
+ spy.and = spyStrategy;
+ spy.calls = callTracker;
+
+ return spy;
+ };
+
+ j$.isSpy = function(putativeSpy) {
+ if (!putativeSpy) {
+ return false;
+ }
+ return putativeSpy.and instanceof j$.SpyStrategy &&
+ putativeSpy.calls instanceof j$.CallTracker;
+ };
+
+ j$.createSpyObj = function(baseName, methodNames) {
+ if (!j$.isArray_(methodNames) || methodNames.length === 0) {
+ throw "createSpyObj requires a non-empty array of method names to create spies for";
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+ };
+};
+
+getJasmineRequireObj().util = function() {
+
+ var util = {};
+
+ util.inherit = function(childClass, parentClass) {
+ var Subclass = function() {
+ };
+ Subclass.prototype = parentClass.prototype;
+ childClass.prototype = new Subclass();
+ };
+
+ util.htmlEscape = function(str) {
+ if (!str) {
+ return str;
+ }
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+ };
+
+ util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) {
+ arrayOfArgs.push(args[i]);
+ }
+ return arrayOfArgs;
+ };
+
+ util.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+ function Spec(attrs) {
+ this.expectationFactory = attrs.expectationFactory;
+ this.resultCallback = attrs.resultCallback || function() {};
+ this.id = attrs.id;
+ this.description = attrs.description || '';
+ this.fn = attrs.fn;
+ this.beforeFns = attrs.beforeFns || function() { return []; };
+ this.afterFns = attrs.afterFns || function() { return []; };
+ this.onStart = attrs.onStart || function() {};
+ this.exceptionFormatter = attrs.exceptionFormatter || function() {};
+ this.getSpecName = attrs.getSpecName || function() { return ''; };
+ this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+ this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+ this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+
+ this.timer = attrs.timer || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+
+ if (!this.fn) {
+ this.pend();
+ }
+
+ this.result = {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ failedExpectations: []
+ };
+ }
+
+ Spec.prototype.addExpectationResult = function(passed, data) {
+ if (passed) {
+ return;
+ }
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ };
+
+ Spec.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
+ Spec.prototype.execute = function(onComplete) {
+ var self = this,
+ timeout;
+
+ this.onStart(this);
+
+ if (this.markedPending || this.disabled) {
+ complete();
+ return;
+ }
+
+ function timeoutable(fn) {
+ return function(done) {
+ timeout = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() {
+ onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'));
+ done();
+ }, j$.DEFAULT_TIMEOUT_INTERVAL]]);
+
+ var callDone = function() {
+ clearTimeoutable();
+ done();
+ };
+
+ fn.call(this, callDone); //TODO: do we care about more than 1 arg?
+ };
+ }
+
+ function clearTimeoutable() {
+ Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeout]]);
+ timeout = void 0;
+ }
+
+ var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()),
+ allTimeoutableFns = [];
+ for (var i = 0; i < allFns.length; i++) {
+ var fn = allFns[i];
+ allTimeoutableFns.push(fn.length > 0 ? timeoutable(fn) : fn);
+ }
+
+ this.queueRunnerFactory({
+ fns: allTimeoutableFns,
+ onException: onException,
+ onComplete: complete
+ });
+
+ function onException(e) {
+ clearTimeoutable();
+ if (Spec.isPendingSpecException(e)) {
+ self.pend();
+ return;
+ }
+
+ self.addExpectationResult(false, {
+ matcherName: "",
+ passed: false,
+ expected: "",
+ actual: "",
+ error: e
+ });
+ }
+
+ function complete() {
+ self.result.status = self.status();
+ self.resultCallback(self.result);
+
+ if (onComplete) {
+ onComplete();
+ }
+ }
+ };
+
+ Spec.prototype.disable = function() {
+ this.disabled = true;
+ };
+
+ Spec.prototype.pend = function() {
+ this.markedPending = true;
+ };
+
+ Spec.prototype.status = function() {
+ if (this.disabled) {
+ return 'disabled';
+ }
+
+ if (this.markedPending) {
+ return 'pending';
+ }
+
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'passed';
+ }
+ };
+
+ Spec.prototype.getFullName = function() {
+ return this.getSpecName(this);
+ };
+
+ Spec.pendingSpecExceptionMessage = "=> marked Pending";
+
+ Spec.isPendingSpecException = function(e) {
+ return e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1;
+ };
+
+ return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == "object") {
+ exports.Spec = jasmineRequire.Spec;
+}
+
+getJasmineRequireObj().Env = function(j$) {
+ function Env(options) {
+ options = options || {};
+
+ var self = this;
+ var global = options.global || j$.getGlobal();
+
+ var totalSpecsDefined = 0;
+
+ var catchExceptions = true;
+
+ var realSetTimeout = j$.getGlobal().setTimeout;
+ var realClearTimeout = j$.getGlobal().clearTimeout;
+ this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler());
+
+ var runnableLookupTable = {};
+
+ var spies = [];
+
+ var currentSpec = null;
+ var currentSuite = null;
+
+ var reporter = new j$.ReportDispatcher([
+ "jasmineStarted",
+ "jasmineDone",
+ "suiteStarted",
+ "suiteDone",
+ "specStarted",
+ "specDone"
+ ]);
+
+ this.specFilter = function() {
+ return true;
+ };
+
+ var equalityTesters = [];
+
+ var customEqualityTesters = [];
+ this.addCustomEqualityTester = function(tester) {
+ customEqualityTesters.push(tester);
+ };
+
+ j$.Expectation.addCoreMatchers(j$.matchers);
+
+ var nextSpecId = 0;
+ var getNextSpecId = function() {
+ return 'spec' + nextSpecId++;
+ };
+
+ var nextSuiteId = 0;
+ var getNextSuiteId = function() {
+ return 'suite' + nextSuiteId++;
+ };
+
+ var expectationFactory = function(actual, spec) {
+ return j$.Expectation.Factory({
+ util: j$.matchersUtil,
+ customEqualityTesters: customEqualityTesters,
+ actual: actual,
+ addExpectationResult: addExpectationResult
+ });
+
+ function addExpectationResult(passed, result) {
+ return spec.addExpectationResult(passed, result);
+ }
+ };
+
+ var specStarted = function(spec) {
+ currentSpec = spec;
+ reporter.specStarted(spec.result);
+ };
+
+ var beforeFns = function(suite) {
+ return function() {
+ var befores = [];
+ while(suite) {
+ befores = befores.concat(suite.beforeFns);
+ suite = suite.parentSuite;
+ }
+ return befores.reverse();
+ };
+ };
+
+ var afterFns = function(suite) {
+ return function() {
+ var afters = [];
+ while(suite) {
+ afters = afters.concat(suite.afterFns);
+ suite = suite.parentSuite;
+ }
+ return afters;
+ };
+ };
+
+ var getSpecName = function(spec, suite) {
+ return suite.getFullName() + ' ' + spec.description;
+ };
+
+ // TODO: we may just be able to pass in the fn instead of wrapping here
+ var buildExpectationResult = j$.buildExpectationResult,
+ exceptionFormatter = new j$.ExceptionFormatter(),
+ expectationResultFactory = function(attrs) {
+ attrs.messageFormatter = exceptionFormatter.message;
+ attrs.stackFormatter = exceptionFormatter.stack;
+
+ return buildExpectationResult(attrs);
+ };
+
+ // TODO: fix this naming, and here's where the value comes in
+ this.catchExceptions = function(value) {
+ catchExceptions = !!value;
+ return catchExceptions;
+ };
+
+ this.catchingExceptions = function() {
+ return catchExceptions;
+ };
+
+ var maximumSpecCallbackDepth = 20;
+ var currentSpecCallbackDepth = 0;
+
+ function clearStack(fn) {
+ currentSpecCallbackDepth++;
+ if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) {
+ currentSpecCallbackDepth = 0;
+ realSetTimeout(fn, 0);
+ } else {
+ fn();
+ }
+ }
+
+ var catchException = function(e) {
+ return j$.Spec.isPendingSpecException(e) || catchExceptions;
+ };
+
+ var queueRunnerFactory = function(options) {
+ options.catchException = catchException;
+ options.clearStack = options.clearStack || clearStack;
+
+ new j$.QueueRunner(options).execute();
+ };
+
+ var topSuite = new j$.Suite({
+ env: this,
+ id: getNextSuiteId(),
+ description: 'Jasmine__TopLevel__Suite',
+ queueRunner: queueRunnerFactory,
+ resultCallback: function() {} // TODO - hook this up
+ });
+ runnableLookupTable[topSuite.id] = topSuite;
+ currentSuite = topSuite;
+
+ this.topSuite = function() {
+ return topSuite;
+ };
+
+ this.execute = function(runnablesToRun) {
+ runnablesToRun = runnablesToRun || [topSuite.id];
+
+ var allFns = [];
+ for(var i = 0; i < runnablesToRun.length; i++) {
+ var runnable = runnableLookupTable[runnablesToRun[i]];
+ allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable));
+ }
+
+ reporter.jasmineStarted({
+ totalSpecsDefined: totalSpecsDefined
+ });
+
+ queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone});
+ };
+
+ this.addReporter = function(reporterToAdd) {
+ reporter.addReporter(reporterToAdd);
+ };
+
+ this.addMatchers = function(matchersToAdd) {
+ j$.Expectation.addMatchers(matchersToAdd);
+ };
+
+ this.spyOn = function(obj, methodName) {
+ if (j$.util.isUndefined(obj)) {
+ throw new Error("spyOn could not find an object to spy upon for " + methodName + "()");
+ }
+
+ if (j$.util.isUndefined(obj[methodName])) {
+ throw new Error(methodName + '() method does not exist');
+ }
+
+ if (obj[methodName] && j$.isSpy(obj[methodName])) {
+ //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spy = j$.createSpy(methodName, obj[methodName]);
+
+ spies.push({
+ spy: spy,
+ baseObj: obj,
+ methodName: methodName,
+ originalValue: obj[methodName]
+ });
+
+ obj[methodName] = spy;
+
+ return spy;
+ };
+
+ var suiteFactory = function(description) {
+ var suite = new j$.Suite({
+ env: self,
+ id: getNextSuiteId(),
+ description: description,
+ parentSuite: currentSuite,
+ queueRunner: queueRunnerFactory,
+ onStart: suiteStarted,
+ resultCallback: function(attrs) {
+ reporter.suiteDone(attrs);
+ }
+ });
+
+ runnableLookupTable[suite.id] = suite;
+ return suite;
+ };
+
+ this.describe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+
+ var parentSuite = currentSuite;
+ parentSuite.addChild(suite);
+ currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch (e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ currentSuite = parentSuite;
+
+ return suite;
+ };
+
+ this.xdescribe = function(description, specDefinitions) {
+ var suite = this.describe(description, specDefinitions);
+ suite.disable();
+ return suite;
+ };
+
+ var specFactory = function(description, fn, suite) {
+ totalSpecsDefined++;
+
+ var spec = new j$.Spec({
+ id: getNextSpecId(),
+ beforeFns: beforeFns(suite),
+ afterFns: afterFns(suite),
+ expectationFactory: expectationFactory,
+ exceptionFormatter: exceptionFormatter,
+ resultCallback: specResultCallback,
+ getSpecName: function(spec) {
+ return getSpecName(spec, suite);
+ },
+ onStart: specStarted,
+ description: description,
+ expectationResultFactory: expectationResultFactory,
+ queueRunnerFactory: queueRunnerFactory,
+ fn: fn,
+ timer: {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}
+ });
+
+ runnableLookupTable[spec.id] = spec;
+
+ if (!self.specFilter(spec)) {
+ spec.disable();
+ }
+
+ return spec;
+
+ function removeAllSpies() {
+ for (var i = 0; i < spies.length; i++) {
+ var spyEntry = spies[i];
+ spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
+ }
+ spies = [];
+ }
+
+ function specResultCallback(result) {
+ removeAllSpies();
+ j$.Expectation.resetMatchers();
+ customEqualityTesters = [];
+ currentSpec = null;
+ reporter.specDone(result);
+ }
+ };
+
+ var suiteStarted = function(suite) {
+ reporter.suiteStarted(suite.result);
+ };
+
+ this.it = function(description, fn) {
+ var spec = specFactory(description, fn, currentSuite);
+ currentSuite.addChild(spec);
+ return spec;
+ };
+
+ this.xit = function(description, fn) {
+ var spec = this.it(description, fn);
+ spec.pend();
+ return spec;
+ };
+
+ this.expect = function(actual) {
+ return currentSpec.expect(actual);
+ };
+
+ this.beforeEach = function(beforeEachFunction) {
+ currentSuite.beforeEach(beforeEachFunction);
+ };
+
+ this.afterEach = function(afterEachFunction) {
+ currentSuite.afterEach(afterEachFunction);
+ };
+
+ this.pending = function() {
+ throw j$.Spec.pendingSpecExceptionMessage;
+ };
+ }
+
+ return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+ var noopTimer = {
+ start: function(){},
+ elapsed: function(){ return 0; }
+ };
+
+ function JsApiReporter(options) {
+ var timer = options.timer || noopTimer,
+ status = "loaded";
+
+ this.started = false;
+ this.finished = false;
+
+ this.jasmineStarted = function() {
+ this.started = true;
+ status = 'started';
+ timer.start();
+ };
+
+ var executionTime;
+
+ this.jasmineDone = function() {
+ this.finished = true;
+ executionTime = timer.elapsed();
+ status = 'done';
+ };
+
+ this.status = function() {
+ return status;
+ };
+
+ var suites = {};
+
+ this.suiteStarted = function(result) {
+ storeSuite(result);
+ };
+
+ this.suiteDone = function(result) {
+ storeSuite(result);
+ };
+
+ function storeSuite(result) {
+ suites[result.id] = result;
+ }
+
+ this.suites = function() {
+ return suites;
+ };
+
+ var specs = [];
+ this.specStarted = function(result) { };
+
+ this.specDone = function(result) {
+ specs.push(result);
+ };
+
+ this.specResults = function(index, length) {
+ return specs.slice(index, index + length);
+ };
+
+ this.specs = function() {
+ return specs;
+ };
+
+ this.executionTime = function() {
+ return executionTime;
+ };
+
+ }
+
+ return JsApiReporter;
+};
+
+getJasmineRequireObj().Any = function() {
+
+ function Any(expectedObject) {
+ this.expectedObject = expectedObject;
+ }
+
+ Any.prototype.jasmineMatches = function(other) {
+ if (this.expectedObject == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedObject == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedObject == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedObject == Object) {
+ return typeof other == 'object';
+ }
+
+ if (this.expectedObject == Boolean) {
+ return typeof other == 'boolean';
+ }
+
+ return other instanceof this.expectedObject;
+ };
+
+ Any.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Any;
+};
+
+getJasmineRequireObj().CallTracker = function() {
+
+ function CallTracker() {
+ var calls = [];
+
+ this.track = function(context) {
+ calls.push(context);
+ };
+
+ this.any = function() {
+ return !!calls.length;
+ };
+
+ this.count = function() {
+ return calls.length;
+ };
+
+ this.argsFor = function(index) {
+ var call = calls[index];
+ return call ? call.args : [];
+ };
+
+ this.all = function() {
+ return calls;
+ };
+
+ this.allArgs = function() {
+ var callArgs = [];
+ for(var i = 0; i < calls.length; i++){
+ callArgs.push(calls[i].args);
+ }
+
+ return callArgs;
+ };
+
+ this.first = function() {
+ return calls[0];
+ };
+
+ this.mostRecent = function() {
+ return calls[calls.length - 1];
+ };
+
+ this.reset = function() {
+ calls = [];
+ };
+ }
+
+ return CallTracker;
+};
+
+getJasmineRequireObj().Clock = function() {
+ function Clock(global, delayedFunctionScheduler) {
+ var self = this,
+ realTimingFunctions = {
+ setTimeout: global.setTimeout,
+ clearTimeout: global.clearTimeout,
+ setInterval: global.setInterval,
+ clearInterval: global.clearInterval
+ },
+ fakeTimingFunctions = {
+ setTimeout: setTimeout,
+ clearTimeout: clearTimeout,
+ setInterval: setInterval,
+ clearInterval: clearInterval
+ },
+ installed = false,
+ timer;
+
+ self.install = function() {
+ replace(global, fakeTimingFunctions);
+ timer = fakeTimingFunctions;
+ installed = true;
+ };
+
+ self.uninstall = function() {
+ delayedFunctionScheduler.reset();
+ replace(global, realTimingFunctions);
+ timer = realTimingFunctions;
+ installed = false;
+ };
+
+ self.setTimeout = function(fn, delay, params) {
+ if (legacyIE()) {
+ if (arguments.length > 2) {
+ throw new Error("IE < 9 cannot support extra params to setTimeout without a polyfill");
+ }
+ return timer.setTimeout(fn, delay);
+ }
+ return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+ };
+
+ self.setInterval = function(fn, delay, params) {
+ if (legacyIE()) {
+ if (arguments.length > 2) {
+ throw new Error("IE < 9 cannot support extra params to setInterval without a polyfill");
+ }
+ return timer.setInterval(fn, delay);
+ }
+ return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+ };
+
+ self.clearTimeout = function(id) {
+ return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+ };
+
+ self.clearInterval = function(id) {
+ return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+ };
+
+ self.tick = function(millis) {
+ if (installed) {
+ delayedFunctionScheduler.tick(millis);
+ } else {
+ throw new Error("Mock clock is not installed, use jasmine.clock().install()");
+ }
+ };
+
+ return self;
+
+ function legacyIE() {
+ //if these methods are polyfilled, apply will be present
+ return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+ }
+
+ function replace(dest, source) {
+ for (var prop in source) {
+ dest[prop] = source[prop];
+ }
+ }
+
+ function setTimeout(fn, delay) {
+ return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+ }
+
+ function clearTimeout(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function setInterval(fn, interval) {
+ return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+ }
+
+ function clearInterval(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function argSlice(argsObj, n) {
+ return Array.prototype.slice.call(argsObj, 2);
+ }
+ }
+
+ return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+ function DelayedFunctionScheduler() {
+ var self = this;
+ var scheduledLookup = [];
+ var scheduledFunctions = {};
+ var currentTime = 0;
+ var delayedFnCount = 0;
+
+ self.tick = function(millis) {
+ millis = millis || 0;
+ var endTime = currentTime + millis;
+
+ runScheduledFunctions(endTime);
+ currentTime = endTime;
+ };
+
+ self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+ var f;
+ if (typeof(funcToCall) === 'string') {
+ /* jshint evil: true */
+ f = function() { return eval(funcToCall); };
+ /* jshint evil: false */
+ } else {
+ f = funcToCall;
+ }
+
+ millis = millis || 0;
+ timeoutKey = timeoutKey || ++delayedFnCount;
+ runAtMillis = runAtMillis || (currentTime + millis);
+
+ var funcToSchedule = {
+ runAtMillis: runAtMillis,
+ funcToCall: f,
+ recurring: recurring,
+ params: params,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+
+ if (runAtMillis in scheduledFunctions) {
+ scheduledFunctions[runAtMillis].push(funcToSchedule);
+ } else {
+ scheduledFunctions[runAtMillis] = [funcToSchedule];
+ scheduledLookup.push(runAtMillis);
+ scheduledLookup.sort(function (a, b) {
+ return a - b;
+ });
+ }
+
+ return timeoutKey;
+ };
+
+ self.removeFunctionWithId = function(timeoutKey) {
+ for (var runAtMillis in scheduledFunctions) {
+ var funcs = scheduledFunctions[runAtMillis];
+ var i = indexOfFirstToPass(funcs, function (func) {
+ return func.timeoutKey === timeoutKey;
+ });
+
+ if (i > -1) {
+ if (funcs.length === 1) {
+ delete scheduledFunctions[runAtMillis];
+ deleteFromLookup(runAtMillis);
+ } else {
+ funcs.splice(i, 1);
+ }
+
+ // intervals get rescheduled when executed, so there's never more
+ // than a single scheduled function with a given timeoutKey
+ break;
+ }
+ }
+ };
+
+ self.reset = function() {
+ currentTime = 0;
+ scheduledLookup = [];
+ scheduledFunctions = {};
+ delayedFnCount = 0;
+ };
+
+ return self;
+
+ function indexOfFirstToPass(array, testFn) {
+ var index = -1;
+
+ for (var i = 0; i < array.length; ++i) {
+ if (testFn(array[i])) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function deleteFromLookup(key) {
+ var value = Number(key);
+ var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+ return millis === value;
+ });
+
+ if (i > -1) {
+ scheduledLookup.splice(i, 1);
+ }
+ }
+
+ function reschedule(scheduledFn) {
+ self.scheduleFunction(scheduledFn.funcToCall,
+ scheduledFn.millis,
+ scheduledFn.params,
+ true,
+ scheduledFn.timeoutKey,
+ scheduledFn.runAtMillis + scheduledFn.millis);
+ }
+
+ function runScheduledFunctions(endTime) {
+ if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+ return;
+ }
+
+ do {
+ currentTime = scheduledLookup.shift();
+
+ var funcsToRun = scheduledFunctions[currentTime];
+ delete scheduledFunctions[currentTime];
+
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ var funcToRun = funcsToRun[i];
+ funcToRun.funcToCall.apply(null, funcToRun.params || []);
+
+ if (funcToRun.recurring) {
+ reschedule(funcToRun);
+ }
+ }
+ } while (scheduledLookup.length > 0 &&
+ // checking first if we're out of time prevents setTimeout(0)
+ // scheduled in a funcToRun from forcing an extra iteration
+ currentTime !== endTime &&
+ scheduledLookup[0] <= endTime);
+ }
+ }
+
+ return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().ExceptionFormatter = function() {
+ function ExceptionFormatter() {
+ this.message = function(error) {
+ var message = error.name +
+ ': ' +
+ error.message;
+
+ if (error.fileName || error.sourceURL) {
+ message += " in " + (error.fileName || error.sourceURL);
+ }
+
+ if (error.line || error.lineNumber) {
+ message += " (line " + (error.line || error.lineNumber) + ")";
+ }
+
+ return message;
+ };
+
+ this.stack = function(error) {
+ return error ? error.stack : null;
+ };
+ }
+
+ return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+ var matchers = {};
+
+ function Expectation(options) {
+ this.util = options.util || { buildFailureMessage: function() {} };
+ this.customEqualityTesters = options.customEqualityTesters || [];
+ this.actual = options.actual;
+ this.addExpectationResult = options.addExpectationResult || function(){};
+ this.isNot = options.isNot;
+
+ for (var matcherName in matchers) {
+ this[matcherName] = matchers[matcherName];
+ }
+ }
+
+ Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ expected = args.slice(0),
+ message = "";
+
+ args.unshift(this.actual);
+
+ var matcher = matcherFactory(this.util, this.customEqualityTesters),
+ matcherCompare = matcher.compare;
+
+ function defaultNegativeCompare() {
+ var result = matcher.compare.apply(null, args);
+ result.pass = !result.pass;
+ return result;
+ }
+
+ if (this.isNot) {
+ matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+ }
+
+ var result = matcherCompare.apply(null, args);
+
+ if (!result.pass) {
+ if (!result.message) {
+ args.unshift(this.isNot);
+ args.unshift(name);
+ message = this.util.buildFailureMessage.apply(null, args);
+ } else {
+ message = result.message;
+ }
+ }
+
+ if (expected.length == 1) {
+ expected = expected[0];
+ }
+
+ // TODO: how many of these params are needed?
+ this.addExpectationResult(
+ result.pass,
+ {
+ matcherName: name,
+ passed: result.pass,
+ message: message,
+ actual: this.actual,
+ expected: expected // TODO: this may need to be arrayified/sliced
+ }
+ );
+ };
+ };
+
+ Expectation.addCoreMatchers = function(matchers) {
+ var prototype = Expectation.prototype;
+ for (var matcherName in matchers) {
+ var matcher = matchers[matcherName];
+ prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+ }
+ };
+
+ Expectation.addMatchers = function(matchersToAdd) {
+ for (var name in matchersToAdd) {
+ var matcher = matchersToAdd[name];
+ matchers[name] = Expectation.prototype.wrapCompare(name, matcher);
+ }
+ };
+
+ Expectation.resetMatchers = function() {
+ for (var name in matchers) {
+ delete matchers[name];
+ }
+ };
+
+ Expectation.Factory = function(options) {
+ options = options || {};
+
+ var expect = new Expectation(options);
+
+ // TODO: this would be nice as its own Object - NegativeExpectation
+ // TODO: copy instead of mutate options
+ options.isNot = true;
+ expect.not = new Expectation(options);
+
+ return expect;
+ };
+
+ return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+ function buildExpectationResult(options) {
+ var messageFormatter = options.messageFormatter || function() {},
+ stackFormatter = options.stackFormatter || function() {};
+
+ return {
+ matcherName: options.matcherName,
+ expected: options.expected,
+ actual: options.actual,
+ message: message(),
+ stack: stack(),
+ passed: options.passed
+ };
+
+ function message() {
+ if (options.passed) {
+ return "Passed.";
+ } else if (options.message) {
+ return options.message;
+ } else if (options.error) {
+ return messageFormatter(options.error);
+ }
+ return "";
+ }
+
+ function stack() {
+ if (options.passed) {
+ return "";
+ }
+
+ var error = options.error;
+ if (!error) {
+ try {
+ throw new Error(message());
+ } catch (e) {
+ error = e;
+ }
+ }
+ return stackFormatter(error);
+ }
+ }
+
+ return buildExpectationResult;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+ function ObjectContaining(sample) {
+ this.sample = sample;
+ }
+
+ ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+ if (typeof(this.sample) !== "object") { throw new Error("You must provide an object to objectContaining, not '"+this.sample+"'."); }
+
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && !j$.util.isUndefined(obj[keyName]);
+ };
+
+ for (var property in this.sample) {
+ if (!hasKey(other, property) && hasKey(this.sample, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ else if (!j$.matchersUtil.equals(this.sample[property], other[property])) {
+ mismatchValues.push("'" + property + "' was '" + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + "' in actual, but was '" + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in expected.");
+ }
+ }
+
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+ };
+
+ ObjectContaining.prototype.jasmineToString = function() {
+ return "";
+ };
+
+ return ObjectContaining;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+ function PrettyPrinter() {
+ this.ppNestLevel_ = 0;
+ }
+
+ PrettyPrinter.prototype.format = function(value) {
+ this.ppNestLevel_++;
+ try {
+ if (j$.util.isUndefined(value)) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === j$.getGlobal()) {
+ this.emitScalar('');
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (j$.isSpy(value)) {
+ this.emitScalar("spy on " + value.and.identity());
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('');
+ } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+ value.__Jasmine_been_here_before__ = true;
+ if (j$.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+ };
+
+ PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (!obj.hasOwnProperty(property)) { continue; }
+ if (property == '__Jasmine_been_here_before__') { continue; }
+ fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+ };
+
+ PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+ function StringPrettyPrinter() {
+ PrettyPrinter.call(this);
+
+ this.string = '';
+ }
+
+ j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+ StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+ };
+
+ StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+ };
+
+ StringPrettyPrinter.prototype.emitArray = function(array) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append("Array");
+ return;
+ }
+
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+ };
+
+ StringPrettyPrinter.prototype.emitObject = function(obj) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append("Object");
+ return;
+ }
+
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+ };
+
+ StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+ };
+
+ return function(value) {
+ var stringPrettyPrinter = new StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+ };
+};
+
+getJasmineRequireObj().QueueRunner = function() {
+
+ function QueueRunner(attrs) {
+ this.fns = attrs.fns || [];
+ this.onComplete = attrs.onComplete || function() {};
+ this.clearStack = attrs.clearStack || function(fn) {fn();};
+ this.onException = attrs.onException || function() {};
+ this.catchException = attrs.catchException || function() { return true; };
+ this.userContext = {};
+ }
+
+ QueueRunner.prototype.execute = function() {
+ this.run(this.fns, 0);
+ };
+
+ QueueRunner.prototype.run = function(fns, recursiveIndex) {
+ var length = fns.length,
+ self = this,
+ iterativeIndex;
+
+ for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+ var fn = fns[iterativeIndex];
+ if (fn.length > 0) {
+ return attemptAsync(fn);
+ } else {
+ attemptSync(fn);
+ }
+ }
+
+ var runnerDone = iterativeIndex >= length;
+
+ if (runnerDone) {
+ this.clearStack(this.onComplete);
+ }
+
+ function attemptSync(fn) {
+ try {
+ fn.call(self.userContext);
+ } catch (e) {
+ handleException(e);
+ }
+ }
+
+ function attemptAsync(fn) {
+ var next = function () { self.run(fns, iterativeIndex + 1); };
+
+ try {
+ fn.call(self.userContext, next);
+ } catch (e) {
+ handleException(e);
+ next();
+ }
+ }
+
+ function handleException(e) {
+ self.onException(e);
+ if (!self.catchException(e)) {
+ //TODO: set a var when we catch an exception and
+ //use a finally block to close the loop in a nice way..
+ throw e;
+ }
+ }
+ };
+
+ return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+ function ReportDispatcher(methods) {
+
+ var dispatchedMethods = methods || [];
+
+ for (var i = 0; i < dispatchedMethods.length; i++) {
+ var method = dispatchedMethods[i];
+ this[method] = (function(m) {
+ return function() {
+ dispatch(m, arguments);
+ };
+ }(method));
+ }
+
+ var reporters = [];
+
+ this.addReporter = function(reporter) {
+ reporters.push(reporter);
+ };
+
+ return this;
+
+ function dispatch(method, args) {
+ for (var i = 0; i < reporters.length; i++) {
+ var reporter = reporters[i];
+ if (reporter[method]) {
+ reporter[method].apply(reporter, args);
+ }
+ }
+ }
+ }
+
+ return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().SpyStrategy = function() {
+
+ function SpyStrategy(options) {
+ options = options || {};
+
+ var identity = options.name || "unknown",
+ originalFn = options.fn || function() {},
+ getSpy = options.getSpy || function() {},
+ plan = function() {};
+
+ this.identity = function() {
+ return identity;
+ };
+
+ this.exec = function() {
+ return plan.apply(this, arguments);
+ };
+
+ this.callThrough = function() {
+ plan = originalFn;
+ return getSpy();
+ };
+
+ this.returnValue = function(value) {
+ plan = function() {
+ return value;
+ };
+ return getSpy();
+ };
+
+ this.throwError = function(something) {
+ var error = (something instanceof Error) ? something : new Error(something);
+ plan = function() {
+ throw error;
+ };
+ return getSpy();
+ };
+
+ this.callFake = function(fn) {
+ plan = fn;
+ return getSpy();
+ };
+
+ this.stub = function(fn) {
+ plan = function() {};
+ return getSpy();
+ };
+ }
+
+ return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function() {
+ function Suite(attrs) {
+ this.env = attrs.env;
+ this.id = attrs.id;
+ this.parentSuite = attrs.parentSuite;
+ this.description = attrs.description;
+ this.onStart = attrs.onStart || function() {};
+ this.resultCallback = attrs.resultCallback || function() {};
+ this.clearStack = attrs.clearStack || function(fn) {fn();};
+
+ this.beforeFns = [];
+ this.afterFns = [];
+ this.queueRunner = attrs.queueRunner || function() {};
+ this.disabled = false;
+
+ this.children = [];
+
+ this.result = {
+ id: this.id,
+ status: this.disabled ? 'disabled' : '',
+ description: this.description,
+ fullName: this.getFullName()
+ };
+ }
+
+ Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ if (parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ }
+ return fullName;
+ };
+
+ Suite.prototype.disable = function() {
+ this.disabled = true;
+ };
+
+ Suite.prototype.beforeEach = function(fn) {
+ this.beforeFns.unshift(fn);
+ };
+
+ Suite.prototype.afterEach = function(fn) {
+ this.afterFns.unshift(fn);
+ };
+
+ Suite.prototype.addChild = function(child) {
+ this.children.push(child);
+ };
+
+ Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ if (this.disabled) {
+ complete();
+ return;
+ }
+
+ var allFns = [];
+
+ for (var i = 0; i < this.children.length; i++) {
+ allFns.push(wrapChildAsAsync(this.children[i]));
+ }
+
+ this.onStart(this);
+
+ this.queueRunner({
+ fns: allFns,
+ onComplete: complete
+ });
+
+ function complete() {
+ self.resultCallback(self.result);
+
+ if (onComplete) {
+ onComplete();
+ }
+ }
+
+ function wrapChildAsAsync(child) {
+ return function(done) { child.execute(done); };
+ }
+ };
+
+ return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == "object") {
+ exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+ function Timer(options) {
+ options = options || {};
+
+ var now = options.now || function() { return new Date().getTime(); },
+ startTime;
+
+ this.start = function() {
+ startTime = now();
+ };
+
+ this.elapsed = function() {
+ return now() - startTime;
+ };
+ }
+
+ return Timer;
+};
+
+getJasmineRequireObj().matchersUtil = function(j$) {
+ // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+ return {
+ equals: function(a, b, customTesters) {
+ customTesters = customTesters || [];
+
+ return eq(a, b, [], [], customTesters);
+ },
+
+ contains: function(haystack, needle, customTesters) {
+ customTesters = customTesters || [];
+
+ if (Object.prototype.toString.apply(haystack) === "[object Array]") {
+ for (var i = 0; i < haystack.length; i++) {
+ if (eq(haystack[i], needle, [], [], customTesters)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+ },
+
+ buildFailureMessage: function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ matcherName = args[0],
+ isNot = args[1],
+ actual = args[2],
+ expected = args.slice(3),
+ englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+ var message = "Expected " +
+ j$.pp(actual) +
+ (isNot ? " not " : " ") +
+ englishyPredicate;
+
+ if (expected.length > 0) {
+ for (var i = 0; i < expected.length; i++) {
+ if (i > 0) {
+ message += ",";
+ }
+ message += " " + j$.pp(expected[i]);
+ }
+ }
+
+ return message + ".";
+ }
+ };
+
+ // Equality function lovingly adapted from isEqual in
+ // [Underscore](http://underscorejs.org)
+ function eq(a, b, aStack, bStack, customTesters) {
+ var result = true;
+
+ for (var i = 0; i < customTesters.length; i++) {
+ var customTesterResult = customTesters[i](a, b);
+ if (!j$.util.isUndefined(customTesterResult)) {
+ return customTesterResult;
+ }
+ }
+
+ if (a instanceof j$.Any) {
+ result = a.jasmineMatches(b);
+ if (result) {
+ return true;
+ }
+ }
+
+ if (b instanceof j$.Any) {
+ result = b.jasmineMatches(a);
+ if (result) {
+ return true;
+ }
+ }
+
+ if (b instanceof j$.ObjectContaining) {
+ result = b.jasmineMatches(a);
+ if (result) {
+ return true;
+ }
+ }
+
+ if (a instanceof Error && b instanceof Error) {
+ return a.message == b.message;
+ }
+
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+ if (a === b) { return a !== 0 || 1 / a == 1 / b; }
+ // A strict comparison is necessary because `null == undefined`.
+ if (a === null || b === null) { return a === b; }
+ var className = Object.prototype.toString.call(a);
+ if (className != Object.prototype.toString.call(b)) { return false; }
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') { return false; }
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] == a) { return bStack[length] == b; }
+ }
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+ var size = 0;
+ // Recursively compare objects and arrays.
+ if (className == '[object Array]') {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ size = a.length;
+ result = size == b.length;
+ if (result) {
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (size--) {
+ if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; }
+ }
+ }
+ } else {
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) &&
+ isFunction(bCtor) && (bCtor instanceof bCtor))) {
+ return false;
+ }
+ // Deep compare objects.
+ for (var key in a) {
+ if (has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; }
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (has(b, key) && !(size--)) { break; }
+ }
+ result = !size;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+
+ return result;
+
+ function has(obj, key) {
+ return obj.hasOwnProperty(key);
+ }
+
+ function isFunction(obj) {
+ return typeof obj === 'function';
+ }
+ }
+};
+
+getJasmineRequireObj().toBe = function() {
+ function toBe() {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: actual === expected
+ };
+ }
+ };
+ }
+
+ return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+
+ function toBeCloseTo() {
+ return {
+ compare: function(actual, expected, precision) {
+ if (precision !== 0) {
+ precision = precision || 2;
+ }
+
+ return {
+ pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+ };
+ }
+ };
+ }
+
+ return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+ function toBeDefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: (void 0 !== actual)
+ };
+ }
+ };
+ }
+
+ return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+ function toBeFalsy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!!actual
+ };
+ }
+ };
+ }
+
+ return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+
+ function toBeGreaterThan() {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: actual > expected
+ };
+ }
+ };
+ }
+
+ return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeLessThan = function() {
+ function toBeLessThan() {
+ return {
+
+ compare: function(actual, expected) {
+ return {
+ pass: actual < expected
+ };
+ }
+ };
+ }
+
+ return toBeLessThan;
+};
+getJasmineRequireObj().toBeNaN = function(j$) {
+
+ function toBeNaN() {
+ return {
+ compare: function(actual) {
+ var result = {
+ pass: (actual !== actual)
+ };
+
+ if (result.pass) {
+ result.message = "Expected actual not to be NaN.";
+ } else {
+ result.message = "Expected " + j$.pp(actual) + " to be NaN.";
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+
+ function toBeNull() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: actual === null
+ };
+ }
+ };
+ }
+
+ return toBeNull;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+
+ function toBeTruthy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!actual
+ };
+ }
+ };
+ }
+
+ return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+
+ function toBeUndefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: void 0 === actual
+ };
+ }
+ };
+ }
+
+ return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+ function toContain(util, customEqualityTesters) {
+ customEqualityTesters = customEqualityTesters || [];
+
+ return {
+ compare: function(actual, expected) {
+
+ return {
+ pass: util.contains(actual, expected, customEqualityTesters)
+ };
+ }
+ };
+ }
+
+ return toContain;
+};
+
+getJasmineRequireObj().toEqual = function() {
+
+ function toEqual(util, customEqualityTesters) {
+ customEqualityTesters = customEqualityTesters || [];
+
+ return {
+ compare: function(actual, expected) {
+ var result = {
+ pass: false
+ };
+
+ result.pass = util.equals(actual, expected, customEqualityTesters);
+
+ return result;
+ }
+ };
+ }
+
+ return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+ function toHaveBeenCalled() {
+ return {
+ compare: function(actual) {
+ var result = {};
+
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ if (arguments.length > 1) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ result.pass = actual.calls.any();
+
+ result.message = result.pass ?
+ "Expected spy " + actual.and.identity() + " not to have been called." :
+ "Expected spy " + actual.and.identity() + " to have been called.";
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+ function toHaveBeenCalledWith(util) {
+ return {
+ compare: function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ actual = args[0],
+ expectedArgs = args.slice(1),
+ result = { pass: false };
+
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ if (!actual.calls.any()) {
+ result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but it was never called.";
+ return result;
+ }
+
+ if (util.contains(actual.calls.allArgs(), expectedArgs)) {
+ result.pass = true;
+ result.message = "Expected spy " + actual.and.identity() + " not to have been called with " + j$.pp(expectedArgs) + " but it was.";
+ } else {
+ result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but actual calls were " + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + ".";
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function() {
+
+ function toMatch() {
+ return {
+ compare: function(actual, expected) {
+ var regexp = new RegExp(expected);
+
+ return {
+ pass: regexp.test(actual)
+ };
+ }
+ };
+ }
+
+ return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+ function toThrow(util) {
+ return {
+ compare: function(actual, expected) {
+ var result = { pass: false },
+ threw = false,
+ thrown;
+
+ if (typeof actual != "function") {
+ throw new Error("Actual is not a Function");
+ }
+
+ try {
+ actual();
+ } catch (e) {
+ threw = true;
+ thrown = e;
+ }
+
+ if (!threw) {
+ result.message = "Expected function to throw an exception.";
+ return result;
+ }
+
+ if (arguments.length == 1) {
+ result.pass = true;
+ result.message = "Expected function not to throw, but it threw " + j$.pp(thrown) + ".";
+
+ return result;
+ }
+
+ if (util.equals(thrown, expected)) {
+ result.pass = true;
+ result.message = "Expected function not to throw " + j$.pp(expected) + ".";
+ } else {
+ result.message = "Expected function to throw " + j$.pp(expected) + ", but it threw " + j$.pp(thrown) + ".";
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+ function toThrowError (util) {
+ return {
+ compare: function(actual) {
+ var threw = false,
+ thrown,
+ errorType,
+ message,
+ regexp,
+ name,
+ constructorName;
+
+ if (typeof actual != "function") {
+ throw new Error("Actual is not a Function");
+ }
+
+ extractExpectedParams.apply(null, arguments);
+
+ try {
+ actual();
+ } catch (e) {
+ threw = true;
+ thrown = e;
+ }
+
+ if (!threw) {
+ return fail("Expected function to throw an Error.");
+ }
+
+ if (!(thrown instanceof Error)) {
+ return fail("Expected function to throw an Error, but it threw " + thrown + ".");
+ }
+
+ if (arguments.length == 1) {
+ return pass("Expected function not to throw an Error, but it threw " + fnNameFor(thrown) + ".");
+ }
+
+ if (errorType) {
+ name = fnNameFor(errorType);
+ constructorName = fnNameFor(thrown.constructor);
+ }
+
+ if (errorType && message) {
+ if (thrown.constructor == errorType && util.equals(thrown.message, message)) {
+ return pass("Expected function not to throw " + name + " with message \"" + message + "\".");
+ } else {
+ return fail("Expected function to throw " + name + " with message \"" + message +
+ "\", but it threw " + constructorName + " with message \"" + thrown.message + "\".");
+ }
+ }
+
+ if (errorType && regexp) {
+ if (thrown.constructor == errorType && regexp.test(thrown.message)) {
+ return pass("Expected function not to throw " + name + " with message matching " + regexp + ".");
+ } else {
+ return fail("Expected function to throw " + name + " with message matching " + regexp +
+ ", but it threw " + constructorName + " with message \"" + thrown.message + "\".");
+ }
+ }
+
+ if (errorType) {
+ if (thrown.constructor == errorType) {
+ return pass("Expected function not to throw " + name + ".");
+ } else {
+ return fail("Expected function to throw " + name + ", but it threw " + constructorName + ".");
+ }
+ }
+
+ if (message) {
+ if (thrown.message == message) {
+ return pass("Expected function not to throw an exception with message " + j$.pp(message) + ".");
+ } else {
+ return fail("Expected function to throw an exception with message " + j$.pp(message) +
+ ", but it threw an exception with message " + j$.pp(thrown.message) + ".");
+ }
+ }
+
+ if (regexp) {
+ if (regexp.test(thrown.message)) {
+ return pass("Expected function not to throw an exception with a message matching " + j$.pp(regexp) + ".");
+ } else {
+ return fail("Expected function to throw an exception with a message matching " + j$.pp(regexp) +
+ ", but it threw an exception with message " + j$.pp(thrown.message) + ".");
+ }
+ }
+
+ function fnNameFor(func) {
+ return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
+ }
+
+ function pass(notMessage) {
+ return {
+ pass: true,
+ message: notMessage
+ };
+ }
+
+ function fail(message) {
+ return {
+ pass: false,
+ message: message
+ };
+ }
+
+ function extractExpectedParams() {
+ if (arguments.length == 1) {
+ return;
+ }
+
+ if (arguments.length == 2) {
+ var expected = arguments[1];
+
+ if (expected instanceof RegExp) {
+ regexp = expected;
+ } else if (typeof expected == "string") {
+ message = expected;
+ } else if (checkForAnErrorType(expected)) {
+ errorType = expected;
+ }
+
+ if (!(errorType || message || regexp)) {
+ throw new Error("Expected is not an Error, string, or RegExp.");
+ }
+ } else {
+ if (checkForAnErrorType(arguments[1])) {
+ errorType = arguments[1];
+ } else {
+ throw new Error("Expected error type is not an Error.");
+ }
+
+ if (arguments[2] instanceof RegExp) {
+ regexp = arguments[2];
+ } else if (typeof arguments[2] == "string") {
+ message = arguments[2];
+ } else {
+ throw new Error("Expected error message is not a string or RegExp.");
+ }
+ }
+ }
+
+ function checkForAnErrorType(type) {
+ if (typeof type !== "function") {
+ return false;
+ }
+
+ var Surrogate = function() {};
+ Surrogate.prototype = type.prototype;
+ return (new Surrogate()) instanceof Error;
+ }
+ }
+ };
+ }
+
+ return toThrowError;
+};
+
+getJasmineRequireObj().version = function() {
+ return "2.0.0";
+};
diff --git a/js/common/modules/jasmine/reporter.js b/js/common/modules/jasmine/reporter.js
new file mode 100644
index 0000000000..bdd19b4d23
--- /dev/null
+++ b/js/common/modules/jasmine/reporter.js
@@ -0,0 +1,193 @@
+/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true */
+/*global module, require, exports, print */
+
+// Reporter
+// progress [default]: Dots
+// documentation: Group and example names
+
+var Reporter,
+ _ = require('underscore'),
+ internal = require('internal'),
+ inspect = internal.inspect,
+ failureColor = internal.COLORS.COLOR_RED,
+ successColor = internal.COLORS.COLOR_GREEN,
+ commentColor = internal.COLORS.COLOR_BLUE,
+ resetColor = internal.COLORS.COLOR_RESET,
+ pendingColor = internal.COLORS.COLOR_YELLOW
+ p = function (x) { print(inspect(x)); };
+
+var repeatString = function(str, num) {
+ return new Array(num + 1).join(str);
+};
+
+var indenter = function(indentation) {
+ return function (str) {
+ return repeatString(" ", indentation) + str;
+ };
+};
+
+var indent = function(message, indentation) {
+ var lines = message.split("\n");
+ return _.map(lines, indenter(indentation)).join("\n");
+};
+
+ // "at Function.main (test.js:19:11)"
+
+var fileInfoPattern = /\(([^:]+):[^)]+\)/;
+
+var parseFileName = function(stack) {
+ var parsedStack = _.last(_.filter(stack.split("\n"), function(line) {
+ return fileInfoPattern.test(line);
+ }));
+ return fileInfoPattern.exec(parsedStack)[1];
+};
+
+Reporter = function (options) {
+ options = options || {};
+ this.format = options.format || 'progress';
+ this.failures = [];
+};
+
+_.extend(Reporter.prototype, {
+ jasmineStarted: function(options) {
+ this.totalSpecs = options.totalSpecsDefined || 0;
+ this.failedSpecs = [];
+ this.pendingSpecs = [];
+ this.start = new Date();
+ print();
+ },
+
+ hasErrors: function() {
+ return this.failedSpecs.length > 0;
+ },
+
+ jasmineDone: function() {
+ if (this.format === 'progress') {
+ print('\n');
+ }
+ if (this.failures.length > 0) {
+ this.printFailureInfo();
+ }
+ if (this.pendingSpecs.length > 0) {
+ this.printPendingInfo();
+ }
+ this.printFooter();
+ if (this.failures.length > 0) {
+ this.printFailedExamples();
+ }
+ print();
+ },
+
+ suiteStarted: function(result) {
+ if (this.format === 'documentation') {
+ print(result.description);
+ }
+ },
+
+ suiteDone: function() {
+ if (this.format === 'documentation') {
+ print();
+ }
+ },
+
+ specStarted: function(result) {
+ if (!_.isUndefined(this.currentSpec)) {
+ this.pending(this.currentSpec.description, this.currentSpec);
+ }
+ this.currentSpec = result;
+ },
+
+ specDone: function(result) {
+ this.currentSpec = undefined;
+ if (_.isEqual(result.status, 'passed')) {
+ this.pass(result.description);
+ } else {
+ this.fail(result.description, result);
+ }
+ },
+
+ pending: function (testName, result) {
+ this.pendingSpecs.push(result);
+ if (this.format === 'progress') {
+ printf("%s", pendingColor + "*" + resetColor);
+ } else if (this.format === 'documentation') {
+ print(pendingColor + " " + testName + " [PENDING]" + resetColor);
+ }
+ },
+
+ pass: function (testName) {
+ if (this.format === 'progress') {
+ printf("%s", successColor + "." + resetColor);
+ } else if (this.format === 'documentation') {
+ print(successColor + " " + testName + resetColor);
+ }
+ },
+
+ printFailureMessage: function(testName) {
+ if (this.format === 'progress') {
+ printf("%s", failureColor + "F" + resetColor);
+ } else if (this.format === 'documentation') {
+ print(failureColor + " " + testName + " [FAILED]" + resetColor);
+ }
+ },
+
+ fail: function (testName, result) {
+ var failedExpectations = result.failedExpectations;
+ this.failedSpecs.push(result.fullName);
+ this.printFailureMessage(testName);
+ _.each(failedExpectations, function(failedExpectation) {
+ this.failures.push({
+ fullName: result.fullName,
+ failedExpectation: failedExpectation,
+ fileName: parseFileName(failedExpectation.stack)
+ });
+ }, this);
+ },
+
+ printFailureInfo: function () {
+ print("Failures:\n");
+ _.each(this.failures, function(failure, index) {
+ var failedExpectation = failure.failedExpectation;
+
+ print(" " + index + ") " + failure.fullName);
+ print(failureColor + indent(failedExpectation.stack, 6) + resetColor);
+ });
+ },
+
+ printPendingInfo: function () {
+ print("Pending:\n");
+ _.each(this.pendingSpecs, function(pending) {
+ print(pendingColor + " " + pending.fullName + resetColor);
+ });
+ },
+
+ printFooter: function () {
+ var end = new Date(),
+ timeInMilliseconds = end - this.start,
+ color,
+ message = this.totalSpecs + ' example, ' + this.failedSpecs.length + ' failures';
+
+ if (this.failedSpecs.length > 0) {
+ color = failureColor;
+ } else if (this.pendingSpecs.length > 0) {
+ color = pendingColor;
+ message += ', ' + this.pendingSpecs.length + ' pending';
+ } else {
+ color = successColor;
+ }
+
+ print();
+ print('Finished in ' + (timeInMilliseconds / 1000) + ' seconds');
+ print(color + message + resetColor);
+ },
+
+ printFailedExamples: function () {
+ print("\nFailed examples:\n");
+ _.each(this.failures, function(failure) {
+ var repeatAction = "arangod --javascript.unit-tests " + failure.fileName + " /tmp/arangodb_test";
+ print(failureColor + repeatAction + commentColor + " # " + failure.fullName + resetColor);
+ });
+ }
+});
+
+exports.Reporter = Reporter;
diff --git a/js/common/modules/jsunity.js b/js/common/modules/jsunity.js
index 14cea39579..408ba95958 100644
--- a/js/common/modules/jsunity.js
+++ b/js/common/modules/jsunity.js
@@ -57,235 +57,6 @@ jsUnity.results.end = function (passed, failed, duration) {
print();
};
-// -----------------------------------------------------------------------------
-// --SECTION-- private functions
-// -----------------------------------------------------------------------------
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief pad the given string to the maximum width provided
-///
-/// From: http://www.bennadel.com/blog/1927-Faking-Context-In-Javascript-s-Function-Constructor.htm
-////////////////////////////////////////////////////////////////////////////////
-
-function FunctionContext (func) {
- var body = " for (var __i in context) {"
- + " eval('var ' + __i + ' = context[__i];');"
- + " }"
- + " return " + func + ";";
-
- return new Function("context", body);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @addtogroup Expresso
-/// @{
-///
-/// based on:
-///
-/// Expresso
-/// Copyright(c) TJ Holowaychuk
-/// (MIT Licensed)
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief pad the given string to the maximum width provided
-////////////////////////////////////////////////////////////////////////////////
-
-function lpad (str, width) {
- str = String(str);
-
- var n = width - str.length;
-
- if (n < 1) {
- return str;
- }
-
- while (n--) {
- str = ' ' + str;
- }
-
- return str;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief pad the given string to the maximum width provided
-////////////////////////////////////////////////////////////////////////////////
-
-function rpad (str, width) {
- str = String(str);
-
- var n = width - str.length;
-
- if (n < 1) {
- return str;
- }
-
- while (n--) {
- str = str + ' ';
- }
-
- return str;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// Total coverage for the given file data.
-////////////////////////////////////////////////////////////////////////////////
-
-function coverage (data, val) {
- var n = 0;
-
- for (var i = 0, len = data.length; i < len; ++i) {
- if (data[i] !== undefined && data[i] == val) {
- ++n;
- }
- }
-
- return n;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief populate code coverage data
-////////////////////////////////////////////////////////////////////////////////
-
-function populateCoverage (cov) {
- var boring = false;
-
- cov.LOC = 0;
- cov.SLOC = 0;
- cov.totalFiles = 0;
- cov.totalHits = 0;
- cov.totalMisses = 0;
- cov.coverage = 0;
-
- for (var name in cov) {
- var file = cov[name];
-
- if (Array.isArray(file)) {
- ++cov.totalFiles;
-
- if (! file.source) {
- file.source = [];
- }
-
- cov.totalHits += file.totalHits = coverage(file, true);
- cov.totalMisses += file.totalMisses = coverage(file, false);
- file.totalLines = file.totalHits + file.totalMisses;
- cov.SLOC += file.SLOC = file.totalLines;
- cov.LOC += file.LOC = file.source.length;
- file.coverage = (file.totalHits / file.totalLines) * 100;
-
- var width = file.source.length.toString().length;
-
- file.sourceLines = file.source.map(function(line, i) {
- ++i;
- var hits = lpad(file[i] === 0 ? 0 : (file[i] || ' '), 3);
-
- if (! boring) {
- if (file[i] === 0) {
- hits = '\x1b[31m' + hits + '\x1b[0m';
- line = '\x1b[41m' + line + '\x1b[0m';
- }
- else {
- hits = '\x1b[32m' + hits + '\x1b[0m';
- }
- }
-
- return '\n ' + lpad(i, width) + ' | ' + hits + ' | ' + line;
- }).join('');
- }
- }
-
- cov.coverage = (cov.totalHits / cov.SLOC) * 100;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief report test coverage in tabular format
-////////////////////////////////////////////////////////////////////////////////
-
-function reportCoverage (cov, files) {
- print('\n Test Coverage\n');
-
- var sep = ' +------------------------------------------+----------+------+------+--------+';
- var lastSep = ' +----------+------+------+--------+';
- var line;
-
- print(sep);
- print(' | filename | coverage | LOC | SLOC | missed |');
- print(sep);
-
- for (var name in cov) {
- var file = cov[name];
-
- if (Array.isArray(file)) {
- line = '';
- line += ' | ' + rpad(name, 40);
- line += ' | ' + lpad(file.coverage.toFixed(2), 8);
- line += ' | ' + lpad(file.LOC, 4);
- line += ' | ' + lpad(file.SLOC, 4);
- line += ' | ' + lpad(file.totalMisses, 6);
- line += ' |';
-
- print(line);
- }
- }
-
- print(sep);
-
- line = '';
- line += ' ' + rpad('', 40);
- line += ' | ' + lpad(cov.coverage.toFixed(2), 8);
- line += ' | ' + lpad(cov.LOC, 4);
- line += ' | ' + lpad(cov.SLOC, 4);
- line += ' | ' + lpad(cov.totalMisses, 6);
- line += ' |';
-
- print(line);
-
- print(lastSep);
-
- var match = null;
-
- if (files == true) {
- match = function(name) {
- return name.match(/\.js$/);
- }
- }
- else if (files != null) {
- var fl = {};
-
- if (typeof files == "string") {
- fl[files] = true;
- }
- else {
- for (var i = 0; i < files.length; ++i) {
- fl[files[i]] = true;
- }
- }
-
- match = function(name) {
- return name in fl;
- }
- }
-
- if (match != null) {
- for (var name in cov) {
- if (match(name)) {
- var file = cov[name];
-
- if (file.coverage < 100) {
- print('\n ' + name + ':');
- print(file.sourceLines);
- print('\n');
- }
- }
- }
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @}
-////////////////////////////////////////////////////////////////////////////////
-
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
@@ -383,47 +154,6 @@ function RunTest (path) {
return f();
}
-////////////////////////////////////////////////////////////////////////////////
-/// @brief runs a JSUnity test file with coverage
-////////////////////////////////////////////////////////////////////////////////
-
-function RunCoverage (path, files) {
- RunTest(path);
-
- populateCoverage(_$jscoverage);
- reportCoverage(_$jscoverage, files);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/// @brief runs tests from command-line
-////////////////////////////////////////////////////////////////////////////////
-
-function RunCommandLineTests () {
- var result = true;
- var unitTests = internal.unitTests();
-
- for (var i = 0; i < unitTests.length; ++i) {
- var file = unitTests[i];
- print();
- print("running tests from file '" + file + "'");
-
- try {
- var ok = RunTest(file);
-
- result = result && ok;
- }
- catch (err) {
- print("cannot run test file '" + file + "': " + err);
- print(err.stack);
- result = false;
- }
-
- internal.wait(0); // force GC
- }
-
- internal.setUnitTestsResult(result);
-}
-
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
@@ -440,9 +170,7 @@ function RunCommandLineTests () {
exports.jsUnity = jsUnity;
exports.run = Run;
exports.done = Done;
-exports.runTest = RunTest;
-exports.runCoverage = RunCoverage;
-exports.runCommandLineTests = RunCommandLineTests;
+exports.runTest = RunTest;
////////////////////////////////////////////////////////////////////////////////
/// @}
diff --git a/js/common/modules/org/arangodb/general-graph.js b/js/common/modules/org/arangodb/general-graph.js
index 3efb908704..ba278623d5 100644
--- a/js/common/modules/org/arangodb/general-graph.js
+++ b/js/common/modules/org/arangodb/general-graph.js
@@ -2508,10 +2508,10 @@ Graph.prototype._amountCommonProperties = function(vertex1Example, vertex2Exampl
/// @EXAMPLES
///
/// @EXAMPLE_ARANGOSH_OUTPUT{general_graph__extendEdgeDefinitions}
-/// var examples = require("org/arangodb/graph-examples/example-graph.js");
-/// var ed1 = examples._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
-/// var ed2 = examples._directedRelationDefinition("myEC2", ["myVC1"], ["myVC3"]);
-/// var g = examples._create("myGraph", [ed1]);
+/// var graph = require("org/arangodb/general-graph")
+/// var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+/// var ed2 = graph._directedRelationDefinition("myEC2", ["myVC1"], ["myVC3"]);
+/// var g = graph._create("myGraph", [ed1]);
/// g._extendEdgeDefinitions(ed2);
/// @END_EXAMPLE_ARANGOSH_OUTPUT
///
@@ -2661,10 +2661,10 @@ var changeEdgeDefinitionsForGraph = function(graph, edgeDefinition, newCollectio
/// @EXAMPLES
///
/// @EXAMPLE_ARANGOSH_OUTPUT{general_graph__editEdgeDefinition}
-/// var examples = require("org/arangodb/graph-examples/example-graph.js");
-/// var ed1 = examples._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
-/// var ed2 = examples._directedRelationDefinition("myEC1", ["myVC2"], ["myVC3"]);
-/// var g = examples._create("myGraph", [ed1, ed2]);
+/// var graph = require("org/arangodb/general-graph")
+/// var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+/// var ed2 = graph._directedRelationDefinition("myEC1", ["myVC2"], ["myVC3"]);
+/// var g = graph._create("myGraph", [ed1, ed2]);
/// g._editEdgeDefinition(ed2, true);
/// @END_EXAMPLE_ARANGOSH_OUTPUT
///
@@ -2730,10 +2730,10 @@ Graph.prototype._editEdgeDefinitions = function(edgeDefinition) {
/// @EXAMPLES
///
/// @EXAMPLE_ARANGOSH_OUTPUT{general_graph__deleteEdgeDefinition}
-/// var examples = require("org/arangodb/graph-examples/example-graph.js");
-/// var ed1 = examples._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
-/// var ed2 = examples._directedRelationDefinition("myEC2", ["myVC1"], ["myVC3"]);
-/// var g = examples._create("myGraph", [ed1, ed2]);
+/// var graph = require("org/arangodb/general-graph")
+/// var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+/// var ed2 = graph._directedRelationDefinition("myEC2", ["myVC1"], ["myVC3"]);
+/// var g = graph._create("myGraph", [ed1, ed2]);
/// g._deleteEdgeDefinition("myEC1");
/// @END_EXAMPLE_ARANGOSH_OUTPUT
///
@@ -2792,9 +2792,9 @@ Graph.prototype._deleteEdgeDefinition = function(edgeCollection) {
/// @EXAMPLES
///
/// @EXAMPLE_ARANGOSH_OUTPUT{general_graph__addOrphanCollection}
-/// var examples = require("org/arangodb/graph-examples/example-graph.js");
-/// var ed1 = examples._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
-/// var g = examples._create("myGraph", [ed1, ed2]);
+/// var graph = require("org/arangodb/general-graph")
+/// var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+/// var g = graph._create("myGraph", [ed1]);
/// g._addOrphanCollection("myVC3", true);
/// @END_EXAMPLE_ARANGOSH_OUTPUT
///
@@ -2842,9 +2842,9 @@ Graph.prototype._addOrphanCollection = function(orphanCollectionName, createColl
/// @EXAMPLES
///
/// @EXAMPLE_ARANGOSH_OUTPUT{general_graph__getOrphanCollections}
-/// var examples = require("org/arangodb/graph-examples/example-graph.js");
-/// var ed1 = examples._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
-/// var g = examples._create("myGraph", [ed1]);
+/// var graph = require("org/arangodb/general-graph")
+/// var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+/// var g = graph._create("myGraph", [ed1]);
/// g._addOrphanCollection("myVC3", true);
/// g._getOrphanCollections();
/// @END_EXAMPLE_ARANGOSH_OUTPUT
@@ -2871,9 +2871,9 @@ Graph.prototype._getOrphanCollections = function() {
/// @EXAMPLES
///
/// @EXAMPLE_ARANGOSH_OUTPUT{general_graph__removeOrphanCollections}
-/// var examples = require("org/arangodb/graph-examples/example-graph.js");
-/// var ed1 = examples._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
-/// var g = examples._create("myGraph", [ed1]);
+/// var graph = require("org/arangodb/general-graph")
+/// var ed1 = graph._directedRelationDefinition("myEC1", ["myVC1"], ["myVC2"]);
+/// var g = graph._create("myGraph", [ed1]);
/// g._addOrphanCollection("myVC3", true);
/// g._addOrphanCollection("myVC4", true);
/// g._getOrphanCollections();
diff --git a/js/common/modules/test_runner.js b/js/common/modules/test_runner.js
new file mode 100644
index 0000000000..fbab84883c
--- /dev/null
+++ b/js/common/modules/test_runner.js
@@ -0,0 +1,74 @@
+/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true */
+/*global module, require, exports, print */
+
+var runTest = require('jsunity').runTest,
+ _ = require('underscore'),
+ internal = require('internal'),
+ runJSUnityTests,
+ runJasmineTests,
+ runCommandLineTests;
+
+////////////////////////////////////////////////////////////////////////////////
+/// @brief runs all jsunity tests
+////////////////////////////////////////////////////////////////////////////////
+
+runJSUnityTests = function (tests) {
+ 'use strict';
+ var result = true;
+
+ _.each(tests, function (file) {
+ print("\nRunning JSUnity test from file '" + file + "'");
+
+ try {
+ result = result && runTest(file);
+ } catch (err) {
+ print("cannot run test file '" + file + "': " + err);
+ print(err.stack);
+ result = false;
+ }
+
+ internal.wait(0); // force GC
+ });
+
+ return result;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/// @brief runs all jsunity tests
+////////////////////////////////////////////////////////////////////////////////
+
+runJasmineTests = function (testFiles, options) {
+ 'use strict';
+ var result = true;
+
+ if (testFiles.length > 0) {
+ print('\nRunning Jasmine Tests: ' + testFiles.join(', '));
+ result = require('jasmine').executeTestSuite(testFiles, options);
+ }
+
+ return result;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/// @brief runs tests from command-line
+////////////////////////////////////////////////////////////////////////////////
+
+runCommandLineTests = function (opts) {
+ 'use strict';
+ var result = true,
+ options = opts || {},
+ jasmineReportFormat = options.jasmineReportFormat || 'progress',
+ unitTests = internal.unitTests(),
+ isSpecRegEx = /.+spec.js/,
+ isSpec = function (unitTest) {
+ return isSpecRegEx.test(unitTest);
+ },
+ jasmine = _.filter(unitTests, isSpec),
+ jsUnity = _.reject(unitTests, isSpec);
+
+ result = runJSUnityTests(jsUnity) && runJasmineTests(jasmine, { format: jasmineReportFormat });
+
+ internal.setUnitTestsResult(result);
+};
+
+exports.runCommandLineTests = runCommandLineTests;