Files
jasmine/src/core/Env.js
Davis W. Frank 3fc79bac9e * Removed old Queue & Runner in favor of Suite using the new QueueRunner
* New reporter interface across all reporters
* xdescribe & xit now store disabled specs
* Rewrite of HtmlReporter to support new interface and be more performant
2013-02-19 11:45:05 -08:00

472 lines
14 KiB
JavaScript

(function() {
jasmine.Env = function(options) {
options = options || {};
var self = this;
var global = options.global || jasmine.getGlobal();
var catchExceptions = true;
this.clock = new jasmine.Clock(global, new jasmine.DelayedFunctionScheduler());
this.jasmine = jasmine;
this.spies_ = [];
this.currentSpec = null;
this.undefined = jasmine.undefined;
this.reporter = new jasmine.ReportDispatcher([
"jasmineStarted",
"jasmineDone",
"suiteStarted",
"suiteDone",
"specStarted",
"specDone"
]);
this.lastUpdate = 0;
this.specFilter = function() {
return true;
};
this.nextSpecId_ = 0;
this.nextSuiteId_ = 0;
this.equalityTesters_ = [];
// wrap matchers
this.matchersClass = function() {
jasmine.Matchers.apply(this, arguments);
};
jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
var expectationFactory = function(actual, spec) {
var expect = new (self.matchersClass)(self, actual, spec);
expect.not = new (self.matchersClass)(self, actual, spec, true);
return expect;
};
var specStarted = function(spec) {
self.currentSpec = spec;
self.reporter.specStarted(spec.result);
};
var beforeFns = function(currentSuite) {
return function() {
var befores = [];
for (var suite = currentSuite; suite; suite = suite.parentSuite) {
befores = befores.concat(suite.beforeFns)
}
return befores.reverse();
}
};
var afterFns = function(currentSuite) {
return function() {
var afters = [];
for (var suite = currentSuite; suite; suite = suite.parentSuite) {
afters = afters.concat(suite.afterFns)
}
return afters;
}
};
var exceptionFormatter = jasmine.exceptionFormatter;
var specConstructor = jasmine.Spec;
var getSpecName = function(spec, currentSuite) {
return currentSuite.getFullName() + ' ' + spec.description + '.';
};
var buildExpectationResult = jasmine.buildExpectationResult;
var expectationResultFactory = function(attrs) {
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 = 100;
var currentSpecCallbackDepth = 0;
function encourageGarbageCollection(fn) {
currentSpecCallbackDepth++;
if (currentSpecCallbackDepth > maximumSpecCallbackDepth) {
currentSpecCallbackDepth = 0;
global.setTimeout(fn, 0);
} else {
fn();
}
}
var queueRunnerFactory = function(options) {
options.catchingExceptions = self.catchingExceptions;
options.encourageGC = options.encourageGarbageCollection || encourageGarbageCollection;
new jasmine.QueueRunner(options).run(options.fns, 0);
};
var totalSpecsDefined = 0;
this.specFactory = function(description, fn, suite) {
totalSpecsDefined++;
var spec = new specConstructor({
id: self.nextSpecId(),
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,
queueRunner: queueRunnerFactory,
fn: fn
});
if (!self.specFilter(spec)) {
spec.disable();
}
return spec;
function specResultCallback(result) {
self.removeAllSpies();
self.clock.uninstall();
self.currentSpec = null;
self.reporter.specDone(result);
}
};
var suiteStarted = function(suite) {
self.reporter.suiteStarted(suite.result);
};
var suiteConstructor = jasmine.Suite;
this.topSuite = new jasmine.Suite({
env: this,
id: this.nextSuiteId(),
description: 'Jasmine__TopLevel__Suite',
queueRunner: queueRunnerFactory,
completeCallback: function() {}, // TODO - hook this up
resultCallback: function() {} // TODO - hook this up
});
this.currentSuite = this.topSuite;
this.suiteFactory = function(description) {
return new suiteConstructor({
env: self,
id: self.nextSuiteId(),
description: description,
parentSuite: self.currentSuite,
queueRunner: queueRunnerFactory,
onStart: suiteStarted,
resultCallback: function(attrs) {
self.reporter.suiteDone(attrs);
}
});
};
this.execute = function() {
this.reporter.jasmineStarted({
totalSpecsDefined: totalSpecsDefined
});
this.topSuite.execute(this.reporter.jasmineDone);
};
};
//TODO: shim Spec addMatchers behavior into Env. Should be rewritten to remove globals, etc.
jasmine.Env.prototype.addMatchers = function(matchersPrototype) {
var parent = this.matchersClass;
var newMatchersClass = function() {
parent.apply(this, arguments);
};
jasmine.util.inherit(newMatchersClass, parent);
jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
this.matchersClass = newMatchersClass;
};
jasmine.Env.prototype.version = function() {
if (this.jasmine.version_) {
return this.jasmine.version_;
} else {
throw new Error('Version not set');
}
};
jasmine.Env.prototype.expect = function(actual) {
return this.currentSpec.expect(actual);
};
jasmine.Env.prototype.spyOn = function(obj, methodName) {
if (obj == this.undefined) {
throw "spyOn could not find an object to spy upon for " + methodName + "()";
}
if (obj[methodName] === this.undefined) {
throw methodName + '() method does not exist';
}
if (obj[methodName] && obj[methodName].isSpy) {
//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 spyObj = jasmine.createSpy(methodName);
this.spies_.push(spyObj);
spyObj.baseObj = obj;
spyObj.methodName = methodName;
spyObj.originalValue = obj[methodName];
obj[methodName] = spyObj;
return spyObj;
};
// TODO: move this to closure
jasmine.Env.prototype.removeAllSpies = function() {
for (var i = 0; i < this.spies_.length; i++) {
var spy = this.spies_[i];
spy.baseObj[spy.methodName] = spy.originalValue;
}
this.spies_ = [];
};
// TODO: move this to closure
jasmine.Env.prototype.versionString = function() {
if (!this.jasmine.version_) {
return "version unknown";
}
var version = this.version();
var versionString = version.major + "." + version.minor + "." + version.build;
if (version.release_candidate) {
versionString += ".rc" + version.release_candidate;
}
versionString += " revision " + version.revision;
return versionString;
};
// TODO: move this to closure
jasmine.Env.prototype.nextSpecId = function() {
return this.nextSpecId_++;
};
// TODO: move this to closure
jasmine.Env.prototype.nextSuiteId = function() {
return this.nextSuiteId_++;
};
// TODO: move this to closure
jasmine.Env.prototype.addReporter = function(reporter) {
this.reporter.addReporter(reporter);
};
// TODO: move this to closure
jasmine.Env.prototype.describe = function(description, specDefinitions) {
var suite = this.suiteFactory(description, specDefinitions);
var parentSuite = this.currentSuite;
parentSuite.addSuite(suite);
this.currentSuite = suite;
var declarationError = null;
try {
specDefinitions.call(suite);
} catch (e) {
declarationError = e;
}
if (declarationError) {
this.it("encountered a declaration exception", function() {
throw declarationError;
});
}
this.currentSuite = parentSuite;
return suite;
};
// TODO: move this to closure
jasmine.Env.prototype.xdescribe = function(description, specDefinitions) {
var suite = this.describe(description, specDefinitions);
suite.disable();
return suite;
};
// TODO: move this to closure
jasmine.Env.prototype.it = function(description, fn) {
var spec = this.specFactory(description, fn, this.currentSuite);
this.currentSuite.addSpec(spec);
return spec;
};
// TODO: move this to closure
jasmine.Env.prototype.xit = function(description, fn) {
var spec = this.it(description, fn);
spec.disable();
return spec;
};
// TODO: move this to closure
jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
this.currentSuite.beforeEach(beforeEachFunction);
};
// TODO: move this to closure
jasmine.Env.prototype.afterEach = function(afterEachFunction) {
this.currentSuite.afterEach(afterEachFunction);
};
// TODO: Still needed?
jasmine.Env.prototype.currentRunner = function() {
return this.topSuite;
};
jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
if (a.source != b.source)
mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
if (a.ignoreCase != b.ignoreCase)
mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
if (a.global != b.global)
mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
if (a.multiline != b.multiline)
mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
if (a.sticky != b.sticky)
mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
return (mismatchValues.length === 0);
};
jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
return true;
}
a.__Jasmine_been_here_before__ = b;
b.__Jasmine_been_here_before__ = a;
var hasKey = function(obj, keyName) {
return obj !== null && obj[keyName] !== this.undefined;
};
for (var property in b) {
if (!hasKey(a, property) && hasKey(b, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
}
for (property in a) {
if (!hasKey(b, property) && hasKey(a, property)) {
mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
}
}
for (property in b) {
if (property == '__Jasmine_been_here_before__') continue;
if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
}
}
if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
mismatchValues.push("arrays were not the same length");
}
delete a.__Jasmine_been_here_before__;
delete b.__Jasmine_been_here_before__;
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
for (var i = 0; i < this.equalityTesters_.length; i++) {
var equalityTester = this.equalityTesters_[i];
var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
if (result !== this.undefined) return result;
}
if (a === b) return true;
if (a === this.undefined || a === null || b === this.undefined || b === null) {
return (a == this.undefined && b == this.undefined);
}
if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
return a === b;
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() == b.getTime();
}
if (a.jasmineMatches) {
return a.jasmineMatches(b);
}
if (b.jasmineMatches) {
return b.jasmineMatches(a);
}
if (a instanceof jasmine.Matchers.ObjectContaining) {
return a.matches(b);
}
if (b instanceof jasmine.Matchers.ObjectContaining) {
return b.matches(a);
}
if (jasmine.isString_(a) && jasmine.isString_(b)) {
return (a == b);
}
if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
return (a == b);
}
if (a instanceof RegExp && b instanceof RegExp) {
return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
}
if (typeof a === "object" && typeof b === "object") {
return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
}
//Straight check
return (a === b);
};
jasmine.Env.prototype.contains_ = function(haystack, needle) {
if (jasmine.isArray_(haystack)) {
for (var i = 0; i < haystack.length; i++) {
if (this.equals_(haystack[i], needle)) return true;
}
return false;
}
return haystack.indexOf(needle) >= 0;
};
jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
this.equalityTesters_.push(equalityTester);
};
}());