From 8c8fd7b72b55bf59c2f90f3df687ae2797b0f39f Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sat, 14 Jun 2014 22:00:38 +0200 Subject: [PATCH 01/16] First steps towards integrating Jasmine --- js/common/modules/jasmine.js | 95 + js/common/modules/jasmine/core.js | 2402 +++++++++++++++++++++++++ js/common/modules/jasmine/reporter.js | 42 + 3 files changed, 2539 insertions(+) create mode 100755 js/common/modules/jasmine.js create mode 100755 js/common/modules/jasmine/core.js create mode 100644 js/common/modules/jasmine/reporter.js diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js new file mode 100755 index 0000000000..a891db89a0 --- /dev/null +++ b/js/common/modules/jasmine.js @@ -0,0 +1,95 @@ +/** 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'), + Reporter = require('jasmine/reporter').Reporter; + +jasmine = jasmine.core(jasmine); + +var env = jasmine.getEnv(); + +/** + * ## The Global Interface + */ +exports.describe = function(description, specDefinitions) { + return env.describe(description, specDefinitions); +}; + +exports.xdescribe = function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); +}; + +exports.it = function(desc, func) { + return env.it(desc, func); +}; + +exports.xit = function(desc, func) { + return env.xit(desc, func); +}; + +exports.beforeEach = function(beforeEachFunction) { + return env.beforeEach(beforeEachFunction); +}; + +exports.afterEach = function(afterEachFunction) { + return env.afterEach(afterEachFunction); +}; + +exports.expect = function(actual) { + return env.expect(actual); +}; + +exports.pending = function() { + return env.pending(); +}; + +exports.spyOn = function(obj, methodName) { + return env.spyOn(obj, methodName); +}; + +exports.execute = function() { + env.execute(); +}; + +/** + * Expose the interface for adding custom equality testers. + */ +jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); +}; + +/** + * Expose the interface for adding custom expectation matchers + */ +jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); +}; + +/** + * Expose the mock interface for the JavaScript timeout functions + */ +jasmine.clock = function() { + return env.clock; +}; + +/** + * 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() +}); + +env.addReporter(jsApiReporter); + +/** + * The `arangoReporter` does the reporting to the console + */ +var arangoReporter = new Reporter(); + +env.addReporter(arangoReporter); 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..aa6d891e23 --- /dev/null +++ b/js/common/modules/jasmine/reporter.js @@ -0,0 +1,42 @@ +var Reporter, + _ = require('underscore'), + log = require('console').log, + inspect = require('internal').inspect, + p = function (x) { log(inspect(x)); }; + +Reporter = function () { +}; + +_.extend(Reporter.prototype, { + jasmineStarted: function(options) { + p(options); + this.totalSpecsDefined = options.totalSpecsDefined || 0; + log('Jasmine booting up, Number of Specs: %s', this.totalSpecsDefined); + }, + + jasmineDone: function() { + log('Jasmine done'); + }, + + suiteStarted: function(result) { + log('Started Suite "%s"', result.description); + p(result); + }, + + suiteDone: function(result) { + log('Done with Suite "%s"', result.description); + p(result); + }, + + specStarted: function(result) { + log('Started Spec "%s"', result.description); + p(result); + }, + + specDone: function(result) { + log('Done with Spec "%s"', result.description); + p(result); + } +}); + +exports.Reporter = Reporter; From eade04cb17701c9be1d9775ecec3aa6d3b5bb290 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sat, 14 Jun 2014 23:30:00 +0200 Subject: [PATCH 02/16] Cleaned up and prettified the reporter --- js/common/modules/jasmine/reporter.js | 89 ++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/js/common/modules/jasmine/reporter.js b/js/common/modules/jasmine/reporter.js index aa6d891e23..2e78058cb0 100644 --- a/js/common/modules/jasmine/reporter.js +++ b/js/common/modules/jasmine/reporter.js @@ -1,41 +1,96 @@ +/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true */ +/*global module, require, exports, print */ + +// Reporter +// [p]rogress (default - dots) +// [d]ocumentation (group and example names) + var Reporter, _ = require('underscore'), log = require('console').log, - inspect = require('internal').inspect, + internal = require('internal'), + inspect = internal.inspect, p = function (x) { log(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 printIndented = function(message, indentation) { + var lines = message.split("\n"); + print(_.map(lines, indenter(indentation)).join("\n")); +}; + Reporter = function () { + this.failures = []; }; _.extend(Reporter.prototype, { jasmineStarted: function(options) { - p(options); - this.totalSpecsDefined = options.totalSpecsDefined || 0; - log('Jasmine booting up, Number of Specs: %s', this.totalSpecsDefined); + this.totalSpecs = options.totalSpecsDefined || 0; + this.failedSpecs = 0; + this.start = new Date(); + print(); }, jasmineDone: function() { - log('Jasmine done'); + if (this.failures.length > 0) { + this.printFailureInfo(); + } + this.printFooter(); }, suiteStarted: function(result) { - log('Started Suite "%s"', result.description); - p(result); + print(result.description); }, - suiteDone: function(result) { - log('Done with Suite "%s"', result.description); - p(result); - }, - - specStarted: function(result) { - log('Started Spec "%s"', result.description); - p(result); + suiteDone: function() { + print(); }, specDone: function(result) { - log('Done with Spec "%s"', result.description); - p(result); + if (_.isEqual(result.status, 'passed')) { + this.pass(result.description); + } else { + this.fail(result.description, result); + } + }, + + pass: function (testName) { + print(internal.COLORS.COLOR_GREEN + " " + testName + internal.COLORS.COLOR_RESET); + }, + + fail: function (testName, result) { + var failedExpectations = result.failedExpectations; + print(internal.COLORS.COLOR_RED + " " + testName + " [FAILED]" + internal.COLORS.COLOR_RESET); + _.each(failedExpectations, function(failedExpectation) { + this.failures.push({ fullName: result.fullName, failedExpectation: failedExpectation }); + }, this); + }, + + printFailureInfo: function () { + print("\nFailures:\n"); + _.each(this.failures, function(failure, index) { + var failedExpectation = failure.failedExpectation; + + print(" " + index + ") " + failure.fullName); + printIndented(failedExpectation.stack, 6); + }); + }, + + printFooter: function () { + var end = new Date(), + timeInMilliseconds = end - this.start; + print(); + print('Finished in ' + (timeInMilliseconds / 1000) + ' seconds'); + print(this.totalSpecs + ' example, ' + this.failedSpecs + ' failures'); + print(); } }); From 7ca09cbf85d6db6b1b9182b81036d9b3e11287fb Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sun, 15 Jun 2014 10:50:59 +0200 Subject: [PATCH 03/16] Really fancy reporter --- js/common/modules/jasmine/reporter.js | 58 +++++++++++++++++++++------ 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/js/common/modules/jasmine/reporter.js b/js/common/modules/jasmine/reporter.js index 2e78058cb0..868dd050d5 100644 --- a/js/common/modules/jasmine/reporter.js +++ b/js/common/modules/jasmine/reporter.js @@ -7,10 +7,13 @@ var Reporter, _ = require('underscore'), - log = require('console').log, internal = require('internal'), inspect = internal.inspect, - p = function (x) { log(inspect(x)); }; + failureColor = internal.COLORS.COLOR_RED, + successColor = internal.COLORS.COLOR_GREEN, + commentColor = internal.COLORS.COLOR_BLUE, + resetColor = internal.COLORS.COLOR_RESET, + p = function (x) { print(inspect(x)); }; var repeatString = function(str, num) { return new Array(num + 1).join(str); @@ -22,9 +25,20 @@ var indenter = function(indentation) { }; }; -var printIndented = function(message, indentation) { +var indent = function(message, indentation) { var lines = message.split("\n"); - print(_.map(lines, indenter(indentation)).join("\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 () { @@ -34,7 +48,7 @@ Reporter = function () { _.extend(Reporter.prototype, { jasmineStarted: function(options) { this.totalSpecs = options.totalSpecsDefined || 0; - this.failedSpecs = 0; + this.failedSpecs = []; this.start = new Date(); print(); }, @@ -44,6 +58,10 @@ _.extend(Reporter.prototype, { this.printFailureInfo(); } this.printFooter(); + if (this.failures.length > 0) { + this.printFailedExamples(); + } + print(); }, suiteStarted: function(result) { @@ -63,34 +81,48 @@ _.extend(Reporter.prototype, { }, pass: function (testName) { - print(internal.COLORS.COLOR_GREEN + " " + testName + internal.COLORS.COLOR_RESET); + print(successColor + " " + testName + resetColor); }, fail: function (testName, result) { var failedExpectations = result.failedExpectations; - print(internal.COLORS.COLOR_RED + " " + testName + " [FAILED]" + internal.COLORS.COLOR_RESET); + this.failedSpecs.push(result.fullName); + print(failureColor + " " + testName + " [FAILED]" + resetColor); _.each(failedExpectations, function(failedExpectation) { - this.failures.push({ fullName: result.fullName, failedExpectation: failedExpectation }); + this.failures.push({ + fullName: result.fullName, + failedExpectation: failedExpectation, + fileName: parseFileName(failedExpectation.stack) + }); }, this); }, printFailureInfo: function () { - print("\nFailures:\n"); + print("Failures:\n"); _.each(this.failures, function(failure, index) { var failedExpectation = failure.failedExpectation; print(" " + index + ") " + failure.fullName); - printIndented(failedExpectation.stack, 6); + print(failureColor + indent(failedExpectation.stack, 6) + resetColor); }); }, printFooter: function () { var end = new Date(), - timeInMilliseconds = end - this.start; + timeInMilliseconds = end - this.start, + color = this.failedSpecs.length > 0 ? failureColor : successColor; + print(); print('Finished in ' + (timeInMilliseconds / 1000) + ' seconds'); - print(this.totalSpecs + ' example, ' + this.failedSpecs + ' failures'); - print(); + print(color + this.totalSpecs + ' example, ' + this.failedSpecs.length + ' failures' + 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); + }); } }); From bf34e5f870ba2c3980758e4f05dc1c3e6b965840 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sun, 15 Jun 2014 12:16:05 +0200 Subject: [PATCH 04/16] Reporter can now also have a dot format --- js/common/modules/jasmine.js | 2 +- js/common/modules/jasmine/reporter.js | 35 +++++++++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index a891db89a0..6b985dafe3 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -90,6 +90,6 @@ env.addReporter(jsApiReporter); /** * The `arangoReporter` does the reporting to the console */ -var arangoReporter = new Reporter(); +var arangoReporter = new Reporter({ format: 'progress' }); env.addReporter(arangoReporter); diff --git a/js/common/modules/jasmine/reporter.js b/js/common/modules/jasmine/reporter.js index 868dd050d5..c7b53c76cd 100644 --- a/js/common/modules/jasmine/reporter.js +++ b/js/common/modules/jasmine/reporter.js @@ -2,8 +2,8 @@ /*global module, require, exports, print */ // Reporter -// [p]rogress (default - dots) -// [d]ocumentation (group and example names) +// progress [default]: Dots +// documentation: Group and example names var Reporter, _ = require('underscore'), @@ -41,7 +41,9 @@ var parseFileName = function(stack) { return fileInfoPattern.exec(parsedStack)[1]; }; -Reporter = function () { +Reporter = function (options) { + options = options || {}; + this.format = options.format || 'progress'; this.failures = []; }; @@ -54,6 +56,9 @@ _.extend(Reporter.prototype, { }, jasmineDone: function() { + if (this.format === 'progress') { + print('\n'); + } if (this.failures.length > 0) { this.printFailureInfo(); } @@ -65,11 +70,15 @@ _.extend(Reporter.prototype, { }, suiteStarted: function(result) { - print(result.description); + if (this.format === 'documentation') { + print(result.description); + } }, suiteDone: function() { - print(); + if (this.format === 'documentation') { + print(); + } }, specDone: function(result) { @@ -81,13 +90,25 @@ _.extend(Reporter.prototype, { }, pass: function (testName) { - print(successColor + " " + testName + resetColor); + 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); - print(failureColor + " " + testName + " [FAILED]" + resetColor); + this.printFailureMessage(testName); _.each(failedExpectations, function(failedExpectation) { this.failures.push({ fullName: result.fullName, From 4b1097a110c3468b8e9ff63da95db0e2377200b2 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sun, 15 Jun 2014 12:44:44 +0200 Subject: [PATCH 05/16] Status Code --- js/common/modules/jasmine.js | 10 ++++++++++ js/common/modules/jasmine/reporter.js | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index 6b985dafe3..07d50172ef 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -92,4 +92,14 @@ env.addReporter(jsApiReporter); */ var arangoReporter = new Reporter({ format: 'progress' }); +exports.status = function() { + var status; + if (arangoReporter.hasErrors()) { + status = 1; + } else { + status = 0; + } + return status; +}; + env.addReporter(arangoReporter); diff --git a/js/common/modules/jasmine/reporter.js b/js/common/modules/jasmine/reporter.js index c7b53c76cd..463779f020 100644 --- a/js/common/modules/jasmine/reporter.js +++ b/js/common/modules/jasmine/reporter.js @@ -55,6 +55,10 @@ _.extend(Reporter.prototype, { print(); }, + hasErrors: function() { + return this.failedSpecs.length > 0; + }, + jasmineDone: function() { if (this.format === 'progress') { print('\n'); From 67f05c4d6270f5a563911607668868f5bd662581 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sun, 15 Jun 2014 13:16:05 +0200 Subject: [PATCH 06/16] Spec Executor --- scripts/execute-spec.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 scripts/execute-spec.js diff --git a/scripts/execute-spec.js b/scripts/execute-spec.js new file mode 100644 index 0000000000..8c4e4f9118 --- /dev/null +++ b/scripts/execute-spec.js @@ -0,0 +1,34 @@ +/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true, evil: true */ +/*global module, require, exports, print */ + +/** Usage + * + * ./bin/arangod --log.level warning --javascript.script scripts/execute-spec.js --javascript.script-parameter js/server/tests/shell-example-spec.js --javascript.script-parameter js/server/tests/shell-example-spec-2.js /tmp/tests + * + */ + +function main(argv) { + var jasmine = require('jasmine'), + _ = require('underscore'), + describe = jasmine.describe, + it = jasmine.it, + expect = jasmine.expect, + fs = require('fs'), + file, + status; + + if (argv.length >= 2) { + _.each(argv.slice(1), function (fileName) { + file = fs.read(fileName); + eval(file); + }); + + jasmine.execute(); + status = jasmine.status(); + } else { + print('Provide exactly one filename'); + status = 1; + } + + return status; +} From 61fb82bb4586ae497bc5dbcf9d48b6d503a85dc3 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sun, 15 Jun 2014 15:35:26 +0200 Subject: [PATCH 07/16] Refactoring of Jasmine functionality --- js/common/modules/jasmine.js | 52 +++++++++++------------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index 07d50172ef..d5d54d412c 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -8,6 +8,7 @@ */ var jasmine = require('jasmine/core'), + _ = require('underscore'), Reporter = require('jasmine/reporter').Reporter; jasmine = jasmine.core(jasmine); @@ -17,45 +18,24 @@ var env = jasmine.getEnv(); /** * ## The Global Interface */ -exports.describe = function(description, specDefinitions) { - return env.describe(description, specDefinitions); -}; -exports.xdescribe = function(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); -}; +var exposedFunctionality = [ + 'describe', + 'xdescribe', + 'it', + 'xit', + 'beforeEach', + 'afterEach', + 'expect', + 'pending', + 'spyOn', + 'execute' +]; -exports.it = function(desc, func) { - return env.it(desc, func); -}; +_.each(exposedFunctionality, function(name) { + exports[name] = env[name]; +}); -exports.xit = function(desc, func) { - return env.xit(desc, func); -}; - -exports.beforeEach = function(beforeEachFunction) { - return env.beforeEach(beforeEachFunction); -}; - -exports.afterEach = function(afterEachFunction) { - return env.afterEach(afterEachFunction); -}; - -exports.expect = function(actual) { - return env.expect(actual); -}; - -exports.pending = function() { - return env.pending(); -}; - -exports.spyOn = function(obj, methodName) { - return env.spyOn(obj, methodName); -}; - -exports.execute = function() { - env.execute(); -}; /** * Expose the interface for adding custom equality testers. From b0ce561546afe40f3210099b2ba8c74e3a859815 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Sun, 15 Jun 2014 16:02:12 +0200 Subject: [PATCH 08/16] Implemented `pending` --- js/common/modules/jasmine/reporter.js | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/js/common/modules/jasmine/reporter.js b/js/common/modules/jasmine/reporter.js index 463779f020..bdd19b4d23 100644 --- a/js/common/modules/jasmine/reporter.js +++ b/js/common/modules/jasmine/reporter.js @@ -13,6 +13,7 @@ var Reporter, 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) { @@ -51,6 +52,7 @@ _.extend(Reporter.prototype, { jasmineStarted: function(options) { this.totalSpecs = options.totalSpecsDefined || 0; this.failedSpecs = []; + this.pendingSpecs = []; this.start = new Date(); print(); }, @@ -66,6 +68,9 @@ _.extend(Reporter.prototype, { if (this.failures.length > 0) { this.printFailureInfo(); } + if (this.pendingSpecs.length > 0) { + this.printPendingInfo(); + } this.printFooter(); if (this.failures.length > 0) { this.printFailedExamples(); @@ -85,7 +90,15 @@ _.extend(Reporter.prototype, { } }, + 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 { @@ -93,6 +106,15 @@ _.extend(Reporter.prototype, { } }, + 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); @@ -132,14 +154,31 @@ _.extend(Reporter.prototype, { }); }, + 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 = this.failedSpecs.length > 0 ? failureColor : successColor; + 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 + this.totalSpecs + ' example, ' + this.failedSpecs.length + ' failures' + resetColor); + print(color + message + resetColor); }, printFailedExamples: function () { From db5470ae6cf23e1ce88ba6c9882310cc6f357bd2 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 13:56:54 +0200 Subject: [PATCH 09/16] Execute Jasmine specs via arangod --javascript.unit-test --- js/common/modules/jasmine.js | 26 +++++++++++++++------ js/common/modules/jsunity.js | 45 ++++++++++++++++++++++++++++++++---- scripts/execute-spec.js | 34 --------------------------- 3 files changed, 59 insertions(+), 46 deletions(-) delete mode 100644 scripts/execute-spec.js diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index d5d54d412c..dd45544ffc 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -73,13 +73,25 @@ env.addReporter(jsApiReporter); var arangoReporter = new Reporter({ format: 'progress' }); exports.status = function() { - var status; - if (arangoReporter.hasErrors()) { - status = 1; - } else { - status = 0; - } - return status; + return !arangoReporter.hasErrors(); +}; + +exports.executeTestSuite = function (specs) { + var jasmine = require('jasmine'), + _ = require('underscore'), + describe = jasmine.describe, + it = jasmine.it, + expect = jasmine.expect, + fs = require('fs'), + file, + status; + + _.each(specs, function (spec) { + eval(spec); + }); + + jasmine.execute(); + return jasmine.status(); }; env.addReporter(arangoReporter); diff --git a/js/common/modules/jsunity.js b/js/common/modules/jsunity.js index 14cea39579..5753246f9e 100644 --- a/js/common/modules/jsunity.js +++ b/js/common/modules/jsunity.js @@ -28,6 +28,7 @@ var internal = require("internal"); var fs = require("fs"); var console = require("console"); +var _ = require("underscore"); var TOTAL = 0; var PASSED = 0; @@ -395,15 +396,14 @@ function RunCoverage (path, files) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief runs tests from command-line +/// @brief runs all jsunity tests //////////////////////////////////////////////////////////////////////////////// -function RunCommandLineTests () { +function RunJSUnityTests (tests) { var result = true; - var unitTests = internal.unitTests(); - for (var i = 0; i < unitTests.length; ++i) { - var file = unitTests[i]; + for (var i = 0; i < tests.length; ++i) { + var file = tests[i]; print(); print("running tests from file '" + file + "'"); @@ -421,6 +421,41 @@ function RunCommandLineTests () { internal.wait(0); // force GC } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief runs all jsunity tests +//////////////////////////////////////////////////////////////////////////////// + +function RunJasmineTests (testFiles) { + if (testFiles.length > 0) { + var tests = _.map(testFiles, function (x) { return fs.read(x); }), + jasmine = require('jasmine'); + + print('\nRunning Jasmine Tests: ' + testFiles.join(', ')); + return jasmine.executeTestSuite(tests); + } else { + return true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief runs tests from command-line +//////////////////////////////////////////////////////////////////////////////// + +function RunCommandLineTests () { + var result = true, + 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); + internal.setUnitTestsResult(result); } diff --git a/scripts/execute-spec.js b/scripts/execute-spec.js deleted file mode 100644 index 8c4e4f9118..0000000000 --- a/scripts/execute-spec.js +++ /dev/null @@ -1,34 +0,0 @@ -/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true, evil: true */ -/*global module, require, exports, print */ - -/** Usage - * - * ./bin/arangod --log.level warning --javascript.script scripts/execute-spec.js --javascript.script-parameter js/server/tests/shell-example-spec.js --javascript.script-parameter js/server/tests/shell-example-spec-2.js /tmp/tests - * - */ - -function main(argv) { - var jasmine = require('jasmine'), - _ = require('underscore'), - describe = jasmine.describe, - it = jasmine.it, - expect = jasmine.expect, - fs = require('fs'), - file, - status; - - if (argv.length >= 2) { - _.each(argv.slice(1), function (fileName) { - file = fs.read(fileName); - eval(file); - }); - - jasmine.execute(); - status = jasmine.status(); - } else { - print('Provide exactly one filename'); - status = 1; - } - - return status; -} From f939c8de0873b70625491f55afe0923458d12b0a Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 14:08:55 +0200 Subject: [PATCH 10/16] Removed the unused Coverage functionality --- .../Books/Users/UsingJsUnity/README.mdpp | 14 - Documentation/RefManual/JSModuleJSUnity.md | 14 - js/common/modules/jsunity.js | 243 +----------------- 3 files changed, 1 insertion(+), 270 deletions(-) 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/js/common/modules/jsunity.js b/js/common/modules/jsunity.js index 5753246f9e..36b16864b9 100644 --- a/js/common/modules/jsunity.js +++ b/js/common/modules/jsunity.js @@ -58,235 +58,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 // ----------------------------------------------------------------------------- @@ -384,17 +155,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 all jsunity tests //////////////////////////////////////////////////////////////////////////////// @@ -475,8 +235,7 @@ function RunCommandLineTests () { exports.jsUnity = jsUnity; exports.run = Run; exports.done = Done; -exports.runTest = RunTest; -exports.runCoverage = RunCoverage; +exports.runTest = RunTest; exports.runCommandLineTests = RunCommandLineTests; //////////////////////////////////////////////////////////////////////////////// From b5764513d9f033fa9ecae05f8e015d78c19157f6 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 14:27:09 +0200 Subject: [PATCH 11/16] Extracted the test runner from jsunity --- arangod/RestServer/ArangoServer.cpp | 2 +- arangosh/V8Client/arangosh.cpp | 2 +- js/common/modules/jsunity.js | 66 ------------------------- js/common/modules/test_runner.js | 76 +++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 68 deletions(-) create mode 100644 js/common/modules/test_runner.js 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/jsunity.js b/js/common/modules/jsunity.js index 36b16864b9..408ba95958 100644 --- a/js/common/modules/jsunity.js +++ b/js/common/modules/jsunity.js @@ -28,7 +28,6 @@ var internal = require("internal"); var fs = require("fs"); var console = require("console"); -var _ = require("underscore"); var TOTAL = 0; var PASSED = 0; @@ -155,70 +154,6 @@ function RunTest (path) { return f(); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief runs all jsunity tests -//////////////////////////////////////////////////////////////////////////////// - -function RunJSUnityTests (tests) { - var result = true; - - for (var i = 0; i < tests.length; ++i) { - var file = tests[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 - } - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief runs all jsunity tests -//////////////////////////////////////////////////////////////////////////////// - -function RunJasmineTests (testFiles) { - if (testFiles.length > 0) { - var tests = _.map(testFiles, function (x) { return fs.read(x); }), - jasmine = require('jasmine'); - - print('\nRunning Jasmine Tests: ' + testFiles.join(', ')); - return jasmine.executeTestSuite(tests); - } else { - return true; - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief runs tests from command-line -//////////////////////////////////////////////////////////////////////////////// - -function RunCommandLineTests () { - var result = true, - 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); - - internal.setUnitTestsResult(result); -} - //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// @@ -236,7 +171,6 @@ exports.jsUnity = jsUnity; exports.run = Run; exports.done = Done; exports.runTest = RunTest; -exports.runCommandLineTests = RunCommandLineTests; //////////////////////////////////////////////////////////////////////////////// /// @} diff --git a/js/common/modules/test_runner.js b/js/common/modules/test_runner.js new file mode 100644 index 0000000000..debe6ade95 --- /dev/null +++ b/js/common/modules/test_runner.js @@ -0,0 +1,76 @@ +/*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true */ +/*global module, require, exports, print */ +var RunTest = require('jsunity').runTest; +var _ = require("underscore"); +var internal = require('internal'); +var fs = require('fs'); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief runs all jsunity tests +//////////////////////////////////////////////////////////////////////////////// + +function RunJSUnityTests (tests) { + var result = true, + i, + file, + ok; + + for (i = 0; i < tests.length; ++i) { + file = tests[i]; + print(); + print("running tests from file '" + file + "'"); + + try { + 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 + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief runs all jsunity tests +//////////////////////////////////////////////////////////////////////////////// + +function RunJasmineTests (testFiles) { + var result = true; + + if (testFiles.length > 0) { + var tests = _.map(testFiles, function (x) { return fs.read(x); }), + jasmine = require('jasmine'); + + print('\nRunning Jasmine Tests: ' + testFiles.join(', ')); + result = jasmine.executeTestSuite(tests); + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief runs tests from command-line +//////////////////////////////////////////////////////////////////////////////// + +function runCommandLineTests () { + var result = true, + 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); + + internal.setUnitTestsResult(result); +} + +exports.runCommandLineTests = runCommandLineTests; From 403cdd80b619f26cbea944bd674c5010581589de Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 14:38:29 +0200 Subject: [PATCH 12/16] Refactor Test Runner --- js/common/modules/test_runner.js | 58 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/js/common/modules/test_runner.js b/js/common/modules/test_runner.js index debe6ade95..17a8a2f2ed 100644 --- a/js/common/modules/test_runner.js +++ b/js/common/modules/test_runner.js @@ -1,76 +1,78 @@ /*jslint indent: 2, nomen: true, maxlen: 120, regexp: true, todo: true */ /*global module, require, exports, print */ -var RunTest = require('jsunity').runTest; -var _ = require("underscore"); -var internal = require('internal'); -var fs = require('fs'); + +var runTest = require('jsunity').runTest, + _ = require('underscore'), + internal = require('internal'), + fs = require('fs'), + runJSUnityTests, + runJasmineTests, + runCommandLineTests; //////////////////////////////////////////////////////////////////////////////// /// @brief runs all jsunity tests //////////////////////////////////////////////////////////////////////////////// -function RunJSUnityTests (tests) { - var result = true, - i, - file, - ok; +runJSUnityTests = function (tests) { + 'use strict'; + var result = true; - for (i = 0; i < tests.length; ++i) { - file = tests[i]; - print(); - print("running tests from file '" + file + "'"); + _.each(tests, function (file) { + print("\nRunning JSUnity test from file '" + file + "'"); try { - ok = RunTest(file); - result = result && ok; - } - catch (err) { + 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 //////////////////////////////////////////////////////////////////////////////// -function RunJasmineTests (testFiles) { - var result = true; +runJasmineTests = function (testFiles) { + 'use strict'; + var result = true, + tests, + jasmine; if (testFiles.length > 0) { - var tests = _.map(testFiles, function (x) { return fs.read(x); }), - jasmine = require('jasmine'); + tests = _.map(testFiles, function (x) { return fs.read(x); }); + jasmine = require('jasmine'); print('\nRunning Jasmine Tests: ' + testFiles.join(', ')); result = jasmine.executeTestSuite(tests); } return result; -} +}; //////////////////////////////////////////////////////////////////////////////// /// @brief runs tests from command-line //////////////////////////////////////////////////////////////////////////////// -function runCommandLineTests () { +runCommandLineTests = function () { + 'use strict'; var result = true, unitTests = internal.unitTests(), isSpecRegEx = /.+spec.js/, - isSpec = function(unitTest) { + isSpec = function (unitTest) { return isSpecRegEx.test(unitTest); }, jasmine = _.filter(unitTests, isSpec), jsUnity = _.reject(unitTests, isSpec); - result = RunJSUnityTests(jsUnity) && RunJasmineTests(jasmine); + result = runJSUnityTests(jsUnity) && runJasmineTests(jasmine); internal.setUnitTestsResult(result); -} +}; exports.runCommandLineTests = runCommandLineTests; From 912b2cfd93403ee0b82df11af62104476d5c140f Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 15:13:23 +0200 Subject: [PATCH 13/16] Jasmine: Execute in sandbox --- js/common/modules/jasmine.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index dd45544ffc..fd306bad7f 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -79,15 +79,17 @@ exports.status = function() { exports.executeTestSuite = function (specs) { var jasmine = require('jasmine'), _ = require('underscore'), - describe = jasmine.describe, - it = jasmine.it, - expect = jasmine.expect, - fs = require('fs'), - file, - status; + internal = require('internal'); + + var sandbox = { + require: require, + describe: jasmine.describe, + it: jasmine.it, + expect: jasmine.expect, + }; _.each(specs, function (spec) { - eval(spec); + internal.executeScript(spec, sandbox, 'rhababer'); }); jasmine.execute(); From 8e22d4ef42d233da4d19ae2da56823bbf0a313b0 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 15:18:53 +0200 Subject: [PATCH 14/16] Refactoring Jasmine Wrapper --- js/common/modules/jasmine.js | 113 ++++++++++--------------------- js/common/modules/test_runner.js | 10 +-- 2 files changed, 36 insertions(+), 87 deletions(-) diff --git a/js/common/modules/jasmine.js b/js/common/modules/jasmine.js index fd306bad7f..617c64e25b 100755 --- a/js/common/modules/jasmine.js +++ b/js/common/modules/jasmine.js @@ -1,3 +1,6 @@ +/*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, @@ -9,91 +12,43 @@ var jasmine = require('jasmine/core'), _ = require('underscore'), + fs = require('fs'), Reporter = require('jasmine/reporter').Reporter; jasmine = jasmine.core(jasmine); -var env = jasmine.getEnv(); +exports.executeTestSuite = function (specFileNames, options) { + 'use strict'; + var sandbox = jasmine.getEnv(), + format = options.format || 'progress'; -/** - * ## The Global Interface - */ + // Explicitly add require + sandbox.require = require; -var exposedFunctionality = [ - 'describe', - 'xdescribe', - 'it', - 'xit', - 'beforeEach', - 'afterEach', - 'expect', - 'pending', - 'spyOn', - 'execute' -]; - -_.each(exposedFunctionality, function(name) { - exports[name] = env[name]; -}); - - -/** - * Expose the interface for adding custom equality testers. - */ -jasmine.addCustomEqualityTester = function(tester) { - env.addCustomEqualityTester(tester); -}; - -/** - * Expose the interface for adding custom expectation matchers - */ -jasmine.addMatchers = function(matchers) { - return env.addMatchers(matchers); -}; - -/** - * Expose the mock interface for the JavaScript timeout functions - */ -jasmine.clock = function() { - return env.clock; -}; - -/** - * 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() -}); - -env.addReporter(jsApiReporter); - -/** - * The `arangoReporter` does the reporting to the console - */ -var arangoReporter = new Reporter({ format: 'progress' }); - -exports.status = function() { - return !arangoReporter.hasErrors(); -}; - -exports.executeTestSuite = function (specs) { - var jasmine = require('jasmine'), - _ = require('underscore'), - internal = require('internal'); - - var sandbox = { - require: require, - describe: jasmine.describe, - it: jasmine.it, - expect: jasmine.expect, - }; - - _.each(specs, function (spec) { - internal.executeScript(spec, sandbox, 'rhababer'); + /** + * 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() }); - jasmine.execute(); - return jasmine.status(); -}; + sandbox.addReporter(jsApiReporter); -env.addReporter(arangoReporter); + /** + * 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/test_runner.js b/js/common/modules/test_runner.js index 17a8a2f2ed..9e38553887 100644 --- a/js/common/modules/test_runner.js +++ b/js/common/modules/test_runner.js @@ -4,7 +4,6 @@ var runTest = require('jsunity').runTest, _ = require('underscore'), internal = require('internal'), - fs = require('fs'), runJSUnityTests, runJasmineTests, runCommandLineTests; @@ -40,16 +39,11 @@ runJSUnityTests = function (tests) { runJasmineTests = function (testFiles) { 'use strict'; - var result = true, - tests, - jasmine; + var result = true; if (testFiles.length > 0) { - tests = _.map(testFiles, function (x) { return fs.read(x); }); - jasmine = require('jasmine'); - print('\nRunning Jasmine Tests: ' + testFiles.join(', ')); - result = jasmine.executeTestSuite(tests); + result = require('jasmine').executeTestSuite(testFiles, { format: 'progress' }); } return result; From 5abece1642b956609827033bd74b6da3d58825af Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Mon, 16 Jun 2014 15:59:49 +0200 Subject: [PATCH 15/16] Make the output format configurable from the outside --- js/common/modules/test_runner.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/common/modules/test_runner.js b/js/common/modules/test_runner.js index 9e38553887..fbab84883c 100644 --- a/js/common/modules/test_runner.js +++ b/js/common/modules/test_runner.js @@ -37,13 +37,13 @@ runJSUnityTests = function (tests) { /// @brief runs all jsunity tests //////////////////////////////////////////////////////////////////////////////// -runJasmineTests = function (testFiles) { +runJasmineTests = function (testFiles, options) { 'use strict'; var result = true; if (testFiles.length > 0) { print('\nRunning Jasmine Tests: ' + testFiles.join(', ')); - result = require('jasmine').executeTestSuite(testFiles, { format: 'progress' }); + result = require('jasmine').executeTestSuite(testFiles, options); } return result; @@ -53,9 +53,11 @@ runJasmineTests = function (testFiles) { /// @brief runs tests from command-line //////////////////////////////////////////////////////////////////////////////// -runCommandLineTests = function () { +runCommandLineTests = function (opts) { 'use strict'; var result = true, + options = opts || {}, + jasmineReportFormat = options.jasmineReportFormat || 'progress', unitTests = internal.unitTests(), isSpecRegEx = /.+spec.js/, isSpec = function (unitTest) { @@ -64,7 +66,7 @@ runCommandLineTests = function () { jasmine = _.filter(unitTests, isSpec), jsUnity = _.reject(unitTests, isSpec); - result = runJSUnityTests(jsUnity) && runJasmineTests(jasmine); + result = runJSUnityTests(jsUnity) && runJasmineTests(jasmine, { format: jasmineReportFormat }); internal.setUnitTestsResult(result); }; From 6fe06843fe171517161b96414c410ef221d145ca Mon Sep 17 00:00:00 2001 From: gschwab Date: Mon, 16 Jun 2014 16:22:49 +0200 Subject: [PATCH 16/16] manually merged docu into mdpp --- .../General-Graphs/GeneralGraphFunctions.mdpp | 441 +++++++++++++++++- .../modules/org/arangodb/general-graph.js | 42 +- 2 files changed, 445 insertions(+), 38 deletions(-) diff --git a/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp b/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp index 01793a6c5a..3d53411b11 100644 --- a/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp +++ b/Documentation/Books/Users/General-Graphs/GeneralGraphFunctions.mdpp @@ -42,11 +42,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 @@ -81,20 +157,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 @@ -126,47 +256,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/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();