Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

3622 lines
99 KiB

var Test = {};var isCommonJS = typeof window == "undefined" && typeof exports == "object";
/**
* Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
*
* @namespace
*/
var jasmine = {};
if (isCommonJS) exports.jasmine = jasmine;
/**
* @private
*/
jasmine.unimplementedMethod_ = function() {
throw new Error("unimplemented method");
};
/**
* Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
* a plain old variable and may be redefined by somebody else.
*
* @private
*/
jasmine.undefined = jasmine.___undefined___;
/**
* Show diagnostic messages in the console if set to true
*
*/
jasmine.VERBOSE = false;
/**
* Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
*
*/
jasmine.DEFAULT_UPDATE_INTERVAL = 250;
/**
* Maximum levels of nesting that will be included when an object is pretty-printed
*/
jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
/**
* Default timeout interval in milliseconds for waitsFor() blocks.
*/
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
/**
* By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
* Set to false to let the exception bubble up in the browser.
*
*/
jasmine.CATCH_EXCEPTIONS = true;
jasmine.getGlobal = function() {
function getGlobal() {
return this;
}
return getGlobal();
};
/**
* Allows for bound functions to be compared. Internal use only.
*
* @ignore
* @private
* @param base {Object} bound 'this' for the function
* @param name {Function} function to find
*/
jasmine.bindOriginal_ = function(base, name) {
var original = base[name];
if (original.apply) {
return function() {
return original.apply(base, arguments);
};
} else {
// IE support
return jasmine.getGlobal()[name];
}
};
jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
jasmine.MessageResult = function(values) {
this.type = 'log';
this.values = values;
this.trace = new Error(); // todo: test better
};
jasmine.MessageResult.prototype.toString = function() {
var text = "";
for (var i = 0; i < this.values.length; i++) {
if (i > 0) text += " ";
if (jasmine.isString_(this.values[i])) {
text += this.values[i];
} else {
text += jasmine.pp(this.values[i]);
}
}
return text;
};
jasmine.ExpectationResult = function(params) {
this.type = 'expect';
this.matcherName = params.matcherName;
this.passed_ = params.passed;
this.expected = params.expected;
this.actual = params.actual;
this.message = this.passed_ ? 'Passed.' : params.message;
var trace = (params.trace || new Error(this.message));
this.trace = this.passed_ ? '' : trace;
};
jasmine.ExpectationResult.prototype.toString = function () {
return this.message;
};
jasmine.ExpectationResult.prototype.passed = function () {
return this.passed_;
};
/**
* Getter for the Jasmine environment. Ensures one gets created
*/
jasmine.getEnv = function() {
var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
return env;
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isArray_ = function(value) {
return jasmine.isA_("Array", value);
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isString_ = function(value) {
return jasmine.isA_("String", value);
};
/**
* @ignore
* @private
* @param value
* @returns {Boolean}
*/
jasmine.isNumber_ = function(value) {
return jasmine.isA_("Number", value);
};
/**
* @ignore
* @private
* @param {String} typeName
* @param value
* @returns {Boolean}
*/
jasmine.isA_ = function(typeName, value) {
return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
};
/**
* Pretty printer for expecations. Takes any object and turns it into a human-readable string.
*
* @param value {Object} an object to be outputted
* @returns {String}
*/
jasmine.pp = function(value) {
var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
stringPrettyPrinter.format(value);
return stringPrettyPrinter.string;
};
/**
* Returns true if the object is a DOM Node.
*
* @param {Object} obj object to check
* @returns {Boolean}
*/
jasmine.isDomNode = function(obj) {
return obj.nodeType > 0;
};
/**
* Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
*
* @example
* // don't care about which function is passed in, as long as it's a function
* expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
*
* @param {Class} clazz
* @returns matchable object of the type clazz
*/
jasmine.any = function(clazz) {
return new jasmine.Matchers.Any(clazz);
};
/**
* Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
* attributes on the object.
*
* @example
* // don't care about any other attributes than foo.
* expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
*
* @param sample {Object} sample
* @returns matchable object for the sample
*/
jasmine.objectContaining = function (sample) {
return new jasmine.Matchers.ObjectContaining(sample);
};
/**
* Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
*
* Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
* expectation syntax. Spies can be checked if they were called or not and what the calling params were.
*
* A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
*
* Spies are torn down at the end of every spec.
*
* Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
*
* @example
* // a stub
* var myStub = jasmine.createSpy('myStub'); // can be used anywhere
*
* // spy example
* var foo = {
* not: function(bool) { return !bool; }
* }
*
* // actual foo.not will not be called, execution stops
* spyOn(foo, 'not');
// foo.not spied upon, execution will continue to implementation
* spyOn(foo, 'not').andCallThrough();
*
* // fake example
* var foo = {
* not: function(bool) { return !bool; }
* }
*
* // foo.not(val) will return val
* spyOn(foo, 'not').andCallFake(function(value) {return value;});
*
* // mock example
* foo.not(7 == 7);
* expect(foo.not).toHaveBeenCalled();
* expect(foo.not).toHaveBeenCalledWith(true);
*
* @constructor
* @see spyOn, jasmine.createSpy, jasmine.createSpyObj
* @param {String} name
*/
jasmine.Spy = function(name) {
/**
* The name of the spy, if provided.
*/
this.identity = name || 'unknown';
/**
* Is this Object a spy?
*/
this.isSpy = true;
/**
* The actual function this spy stubs.
*/
this.plan = function() {
};
/**
* Tracking of the most recent call to the spy.
* @example
* var mySpy = jasmine.createSpy('foo');
* mySpy(1, 2);
* mySpy.mostRecentCall.args = [1, 2];
*/
this.mostRecentCall = {};
/**
* Holds arguments for each call to the spy, indexed by call count
* @example
* var mySpy = jasmine.createSpy('foo');
* mySpy(1, 2);
* mySpy(7, 8);
* mySpy.mostRecentCall.args = [7, 8];
* mySpy.argsForCall[0] = [1, 2];
* mySpy.argsForCall[1] = [7, 8];
*/
this.argsForCall = [];
this.calls = [];
};
/**
* Tells a spy to call through to the actual implemenatation.
*
* @example
* var foo = {
* bar: function() { // do some stuff }
* }
*
* // defining a spy on an existing property: foo.bar
* spyOn(foo, 'bar').andCallThrough();
*/
jasmine.Spy.prototype.andCallThrough = function() {
this.plan = this.originalValue;
return this;
};
/**
* For setting the return value of a spy.
*
* @example
* // defining a spy from scratch: foo() returns 'baz'
* var foo = jasmine.createSpy('spy on foo').andReturn('baz');
*
* // defining a spy on an existing property: foo.bar() returns 'baz'
* spyOn(foo, 'bar').andReturn('baz');
*
* @param {Object} value
*/
jasmine.Spy.prototype.andReturn = function(value) {
this.plan = function() {
return value;
};
return this;
};
/**
* For throwing an exception when a spy is called.
*
* @example
* // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
* var foo = jasmine.createSpy('spy on foo').andThrow('baz');
*
* // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
* spyOn(foo, 'bar').andThrow('baz');
*
* @param {String} exceptionMsg
*/
jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
this.plan = function() {
throw exceptionMsg;
};
return this;
};
/**
* Calls an alternate implementation when a spy is called.
*
* @example
* var baz = function() {
* // do some stuff, return something
* }
* // defining a spy from scratch: foo() calls the function baz
* var foo = jasmine.createSpy('spy on foo').andCall(baz);
*
* // defining a spy on an existing property: foo.bar() calls an anonymnous function
* spyOn(foo, 'bar').andCall(function() { return 'baz';} );
*
* @param {Function} fakeFunc
*/
jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
this.plan = fakeFunc;
return this;
};
/**
* Resets all of a spy's the tracking variables so that it can be used again.
*
* @example
* spyOn(foo, 'bar');
*
* foo.bar();
*
* expect(foo.bar.callCount).toEqual(1);
*
* foo.bar.reset();
*
* expect(foo.bar.callCount).toEqual(0);
*/
jasmine.Spy.prototype.reset = function() {
this.wasCalled = false;
this.callCount = 0;
this.argsForCall = [];
this.calls = [];
this.mostRecentCall = {};
};
jasmine.createSpy = function(name) {
var spyObj = function() {
spyObj.wasCalled = true;
spyObj.callCount++;
var args = jasmine.util.argsToArray(arguments);
spyObj.mostRecentCall.object = this;
spyObj.mostRecentCall.args = args;
spyObj.argsForCall.push(args);
spyObj.calls.push({object: this, args: args});
return spyObj.plan.apply(this, arguments);
};
var spy = new jasmine.Spy(name);
for (var prop in spy) {
spyObj[prop] = spy[prop];
}
spyObj.reset();
return spyObj;
};
/**
* Determines whether an object is a spy.
*
* @param {jasmine.Spy|Object} putativeSpy
* @returns {Boolean}
*/
jasmine.isSpy = function(putativeSpy) {
return putativeSpy && putativeSpy.isSpy;
};
/**
* Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
* large in one call.
*
* @param {String} baseName name of spy class
* @param {Array} methodNames array of names of methods to make spies
*/
jasmine.createSpyObj = function(baseName, methodNames) {
if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
throw new Error('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]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
}
return obj;
};
/**
* All parameters are pretty-printed and concatenated together, then written to the current spec's output.
*
* Be careful not to leave calls to <code>jasmine.log</code> in production code.
*/
jasmine.log = function() {
var spec = jasmine.getEnv().currentSpec;
spec.log.apply(spec, arguments);
};
/**
* Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
*
* @example
* // spy example
* var foo = {
* not: function(bool) { return !bool; }
* }
* spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
*
* @see jasmine.createSpy
* @param obj
* @param methodName
* @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
*/
var spyOn = function(obj, methodName) {
return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
};
if (isCommonJS) exports.spyOn = spyOn;
/**
* Creates a Jasmine spec that will be added to the current suite.
*
* // TODO: pending tests
*
* @example
* it('should be true', function() {
* expect(true).toEqual(true);
* });
*
* @param {String} desc description of this specification
* @param {Function} func defines the preconditions and expectations of the spec
*/
var it = function(desc, func) {
return jasmine.getEnv().it(desc, func);
};
if (isCommonJS) exports.it = it;
/**
* Creates a <em>disabled</em> Jasmine spec.
*
* A convenience method that allows existing specs to be disabled temporarily during development.
*
* @param {String} desc description of this specification
* @param {Function} func defines the preconditions and expectations of the spec
*/
var xit = function(desc, func) {
return jasmine.getEnv().xit(desc, func);
};
if (isCommonJS) exports.xit = xit;
/**
* Starts a chain for a Jasmine expectation.
*
* It is passed an Object that is the actual value and should chain to one of the many
* jasmine.Matchers functions.
*
* @param {Object} actual Actual value to test against and expected value
* @return {jasmine.Matchers}
*/
var expect = function(actual) {
return jasmine.getEnv().currentSpec.expect(actual);
};
if (isCommonJS) exports.expect = expect;
/**
* Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
*
* @param {Function} func Function that defines part of a jasmine spec.
*/
var runs = function(func) {
jasmine.getEnv().currentSpec.runs(func);
};
if (isCommonJS) exports.runs = runs;
/**
* Waits a fixed time period before moving to the next block.
*
* @deprecated Use waitsFor() instead
* @param {Number} timeout milliseconds to wait
*/
var waits = function(timeout) {
jasmine.getEnv().currentSpec.waits(timeout);
};
if (isCommonJS) exports.waits = waits;
/**
* Waits for the latchFunction to return true before proceeding to the next block.
*
* @param {Function} latchFunction
* @param {String} optional_timeoutMessage
* @param {Number} optional_timeout
*/
var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
};
if (isCommonJS) exports.waitsFor = waitsFor;
/**
* A function that is called before each spec in a suite.
*
* Used for spec setup, including validating assumptions.
*
* @param {Function} beforeEachFunction
*/
var beforeEach = function(beforeEachFunction) {
jasmine.getEnv().beforeEach(beforeEachFunction);
};
if (isCommonJS) exports.beforeEach = beforeEach;
/**
* A function that is called after each spec in a suite.
*
* Used for restoring any state that is hijacked during spec execution.
*
* @param {Function} afterEachFunction
*/
var afterEach = function(afterEachFunction) {
jasmine.getEnv().afterEach(afterEachFunction);
};
if (isCommonJS) exports.afterEach = afterEach;
/**
* Defines a suite of specifications.
*
* Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
* are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
* of setup in some tests.
*
* @example
* // TODO: a simple suite
*
* // TODO: a simple suite with a nested describe block
*
* @param {String} description A string, usually the class under test.
* @param {Function} specDefinitions function that defines several specs.
*/
var describe = function(description, specDefinitions) {
return jasmine.getEnv().describe(description, specDefinitions);
};
if (isCommonJS) exports.describe = describe;
/**
* Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
*
* @param {String} description A string, usually the class under test.
* @param {Function} specDefinitions function that defines several specs.
*/
var xdescribe = function(description, specDefinitions) {
return jasmine.getEnv().xdescribe(description, specDefinitions);
};
if (isCommonJS) exports.xdescribe = xdescribe;
// Provide the XMLHttpRequest class for IE 5.x-6.x:
jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
function tryIt(f) {
try {
return f();
} catch(e) {
}
return null;
}
var xhr = tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
}) ||
tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
}) ||
tryIt(function() {
return new ActiveXObject("Msxml2.XMLHTTP");
}) ||
tryIt(function() {
return new ActiveXObject("Microsoft.XMLHTTP");
});
if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
return xhr;
} : XMLHttpRequest;
/**
* @namespace
*/
jasmine.util = {};
/**
* Declare that a child class inherit it's prototype from the parent class.
*
* @private
* @param {Function} childClass
* @param {Function} parentClass
*/
jasmine.util.inherit = function(childClass, parentClass) {
/**
* @private
*/
var subclass = function() {
};
subclass.prototype = parentClass.prototype;
childClass.prototype = new subclass();
};
jasmine.util.formatException = function(e) {
var lineNumber;
if (e.line) {
lineNumber = e.line;
}
else if (e.lineNumber) {
lineNumber = e.lineNumber;
}
var file;
if (e.sourceURL) {
file = e.sourceURL;
}
else if (e.fileName) {
file = e.fileName;
}
var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
if (file && lineNumber) {
message += ' in ' + file + ' (line ' + lineNumber + ')';
}
return message;
};
jasmine.util.htmlEscape = function(str) {
if (!str) return str;
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
jasmine.util.argsToArray = function(args) {
var arrayOfArgs = [];
for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
return arrayOfArgs;
};
jasmine.util.extend = function(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
};
/**
* Base class for pretty printing for expectation results.
*/
jasmine.PrettyPrinter = function() {
this.ppNestLevel_ = 0;
};
/**
* Formats a value in a nice, human-readable string.
*
* @param value
*/
jasmine.PrettyPrinter.prototype.format = function(value) {
this.ppNestLevel_++;
try {
if (value === jasmine.undefined) {
this.emitScalar('undefined');
} else if (value === null) {
this.emitScalar('null');
} else if (value === jasmine.getGlobal()) {
this.emitScalar('<global>');
} else if (value.jasmineToString) {
this.emitScalar(value.jasmineToString());
} else if (typeof value === 'string') {
this.emitString(value);
} else if (jasmine.isSpy(value)) {
this.emitScalar("spy on " + value.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('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
} else if (jasmine.isArray_(value) || typeof value == 'object') {
value.__Jasmine_been_here_before__ = true;
if (jasmine.isArray_(value)) {
this.emitArray(value);
} else {
this.emitObject(value);
}
delete value.__Jasmine_been_here_before__;
} else {
this.emitScalar(value.toString());
}
} finally {
this.ppNestLevel_--;
}
};
jasmine.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__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
obj.__lookupGetter__(property) !== null) : false);
}
};
jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
jasmine.StringPrettyPrinter = function() {
jasmine.PrettyPrinter.call(this);
this.string = '';
};
jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
this.append(value);
};
jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
this.append("'" + value + "'");
};
jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
if (this.ppNestLevel_ > jasmine.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(' ]');
};
jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
if (this.ppNestLevel_ > jasmine.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('<getter>');
} else {
self.format(obj[property]);
}
});
this.append(' }');
};
jasmine.StringPrettyPrinter.prototype.append = function(value) {
this.string += value;
};
/**
* Formats a value in a nice, human-readable string.
*
* @param value
*/
jasmine.PrettyPrinter.prototype.format = function(value) {
if (this.ppNestLevel_ > 40) {
throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
}
this.ppNestLevel_++;
try {
if (value === jasmine.undefined) {
this.emitScalar('undefined');
} else if (value === null) {
this.emitScalar('null');
} else if (value === jasmine.getGlobal()) {
this.emitScalar('<global>');
} else if (value.expectedClass) { //override of value instanceof jasmine.Matchers.Any
this.emitScalar(value.toString());
} else if (typeof value === 'string') {
this.emitString(value);
} else if (jasmine.isSpy(value)) {
this.emitScalar("spy on " + value.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('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
} else if (jasmine.isArray_(value) || typeof value == 'object') {
value.__Jasmine_been_here_before__ = true;
if (jasmine.isArray_(value)) {
this.emitArray(value);
} else {
this.emitObject(value);
}
delete value.__Jasmine_been_here_before__;
} else {
this.emitScalar(value.toString());
}
} catch (e) {
} finally {
this.ppNestLevel_--;
}
};
// Extend: creates whitespaces indent
jasmine.StringPrettyPrinter.prototype.getIndent = function () {
var whiteSpaces = "",
i;
for (i = 0; i < this.ws; i++) {
whiteSpaces += " ";
}
return whiteSpaces;
};
// Override: pre-format object
jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
var self = this,
first = true,
indent;
this.append('{\n');
if(!this.ws) {
this.ws = 0;
}
this.ws += 4;
indent = this.getIndent();
var i = 0;
this.iterateObject(obj, function(property, isGetter) {
if (first) {
first = false;
} else {
self.append(',\n');
}
self.append(indent + property);
self.append(' : ');
if (isGetter) {
self.append('<getter>');
} else {
if (typeof obj[property] !== "object") {
self.format(obj[property]);
} else {
self.append("<Object>");
}
}
});
this.ws -= 4;
indent = this.getIndent();
this.append(indent + '\n'+ indent +'}');
};
/**
* Basic browsers detection.
*/
jasmine.browser = {};
jasmine.browser.isIE = !!window.ActiveXObject;
jasmine.browser.isIE6 = jasmine.browser.isIE && !window.XMLHttpRequest;
jasmine.browser.isIE7 = jasmine.browser.isIE && !!window.XMLHttpRequest && !document.documentMode;
jasmine.browser.isIE8 = jasmine.browser.isIE && !!window.XMLHttpRequest && !!document.documentMode && !window.performance;
jasmine.browser.isIE9 = jasmine.browser.isIE && !!window.performance;
jasmine.browser.isSafari3 = /safari/.test(navigator.userAgent.toLowerCase()) && /version\/3/.test(navigator.userAgent.toLowerCase());
jasmine.browser.isOpera = !!window.opera;
jasmine.browser.isOpera11 = jasmine.browser.isOpera && parseInt(window.opera.version(), 10) > 10;
jasmine.array = {};
/**
* Checks whether or not the specified item exists in the array.
* Array.prototype.indexOf is missing in Internet Explorer, unfortunately.
* We always have to use this static method instead for consistency
* @param {Array} array The array to check
* @param {Mixed} item The item to look for
* @param {Number} from (Optional) The index at which to begin the search
* @return {Number} The index of item in the array (or -1 if it is not found)
*/
jasmine.array.indexOf = function(array, item, from){
if (array.indexOf) {
return array.indexOf(item, from);
}
var i, length = array.length;
for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
if (array[i] === item) {
return i;
}
}
return -1;
};
/**
* Removes the specified item from the array. If the item is not found nothing happens.
* @param {Array} array The array
* @param {Mixed} item The item to remove
* @return {Array} The passed array itself
*/
jasmine.array.remove = function(array, item) {
var index = this.indexOf(array, item);
if (index !== -1) {
array.splice(index, 1);
}
return array;
};/**
* Creates an HTMLElement.
* @param {Object/HTMLElement} config Ext DomHelper style element config object.
* If no tag is specified (e.g., {tag:'input'}) then a div will be automatically generated with the specified attributes.
* @return {HTMLElement} The created HTMLElement
*/
jasmine.Dom = function(config) {
var element, children, length, child, i, property;
config = config || {};
if (config.tagName) {
return config;
}
element = document.createElement(config.tag || "div");
children = config.children || [];
length = children.length;
delete config.tag;
for (i = 0; i < length; i++) {
child = children[i];
element.appendChild(new jasmine.Dom(child));
}
delete config.children;
if (config.cls) {
jasmine.Dom.setCls(element, config.cls);
delete config.cls;
}
if (config.html) {
jasmine.Dom.setHTML(element, config.html);
delete config.html;
}
if (config.style) {
jasmine.Dom.setStyle(element, config.style);
delete config.style;
}
for (property in config) {
if (!config.hasOwnProperty(property)) {
continue;
}
element[property] = config[property];
}
return element;
};
/**
* Adds className to an HTMLElement.
* @param {HTMLElement} element The HTMLElement
* @param {String} cls The className string
*/
jasmine.Dom.addCls = function (element, cls) {
var split, length, i;
if (!element.className) {
jasmine.Dom.setCls(element, cls);
return;
}
split = element.className.split(" ");
length = split.length;
for (i = 0; i < length; i++) {
if (split[i] == cls) {
return;
}
}
element.className = element.className + " " + cls;
};
/**
* Removes className to HTMLElement.
* @param {HTMLElement} element The HTMLElement
* @param {String} cls The className string
*/
jasmine.Dom.removeCls = function(element, cls) {
var split, length, classArray, i;
if (!element.className) {
return;
}
classArray = [];
split = element.className.split(" ");
length = split.length;
for (i = 0; i < length; i++) {
if (split[i] !== cls) {
classArray.push(split[i]);
}
}
element.className = classArray.join(" ");
};
/**
* Checks if a dom element has a className.
* @param {HTMLElement} element The HTMLElement
* @param {String} cls The className string
* @return {Boolean}
*/
jasmine.Dom.hasCls = function(element, cls) {
var split, length, classArray, i;
if (!element.className) {
return;
}
split = element.className.split(" ");
length = split.length;
for (i = 0; i < length; i++) {
if (split[i] === cls) {
return true;
}
}
return false;
};
/**
* Sets HTMLElement className.
* @param {HTMLElement} element The HTMLElement
* @param {String} cls The className string
*/
jasmine.Dom.setCls = function(element, cls) {
element.className = cls;
};
/**
* Sets HTMLElement innerHTML
* @param {HTMLElement} element The HTMLElement
* @param {String} html The innerHTML text
*/
jasmine.Dom.setHTML = function(element, html) {
element.innerHTML = html;
};
/**
* Sets HTMLElement style
* @param {HTMLElement} element The HTMLElement
* @param {String} style The style property to set
*/
jasmine.Dom.setStyle = function(element, style) {
var property;
for (property in style) {
if (style.hasOwnProperty(property)) {
element.style[property] = style[property];
}
}
};
Test.OptionsImpl = function() {
this.optionCheckBoxesEl = {};
this.options = this.urlDecode(window.location.search.substring(1));
this.options.remote = window.location.toString().search("http:") !== -1;
this.startAutoReloadTask();
};
Test.OptionsImpl.prototype.get = function() {
return this.options;
};
/**
* Takes an object and converts it to an encoded URL.
* @param {Object} o The object to encode
* @return {String}
*/
Test.OptionsImpl.prototype.urlEncode = function(object) {
var buf = [],
e = encodeURIComponent,
value, property, length, i;
for (property in object) {
if(!object.hasOwnProperty(property)) {
continue;
}
value = object[property];
if (jasmine.isArray_(value)) {
length = value.length;
for (i = 0; i < length; i++) {
buf.push(property + '=' + e(value[i]));
}
} else {
buf.push(property + '=' + e(value));
}
}
return buf.join('&');
};
Test.hashString = function (s, hash) {
hash = hash || 0;
// see http://www.cse.yorku.ca/~oz/hash.html
for (var c, i = 0, n = s.length; i < n; ++i) {
c = s.charCodeAt(i);
hash = c + (hash << 6) + (hash << 16) - hash;
}
return hash;
};
/**
* Takes an encoded URL and and converts it to an object. Example:
* @param {String} string
* @return {Object} A literal with members
*/
Test.OptionsImpl.prototype.urlDecode = function(string) {
var obj = {},
pairs, d, name, value, pair, i, length;
if (string != "") {
pairs = string.split('&');
d = decodeURIComponent;
length = pairs.length;
for (i = 0; i < length; i++) {
pair = pairs[i].split('=');
name = d(pair[0]);
value = d(pair[1]);
obj[name] = !obj[name] ? value : [].concat(obj[name]).concat(value);
}
}
function parseStringOrId (str) {
var id = parseInt(str, 10);
if (String(id) !== str) {
id = Test.hashString(str);
}
return id;
}
if (obj.specs) {
obj.specs = jasmine.isArray_(obj.specs) ? obj.specs : [obj.specs];
length = obj.specs.length;
for (i = 0; i < length; i++) {
obj.specs[i] = parseStringOrId(obj.specs[i]);
}
} else {
obj.specs = [];
}
if (obj.suites) {
obj.suites = jasmine.isArray_(obj.suites) ? obj.suites : [obj.suites];
length = obj.suites.length;
for (i = 0; i < length; i++) {
obj.suites[i] = parseStringOrId(obj.suites[i]);
}
} else {
obj.suites = [];
}
return obj;
};
/**
* Renders option checkbox and label.
* @param {String} name The option name.
* @param {String} labelText The label text.
* @return {HTMLElement} The option HTMLElement
*/
Test.OptionsImpl.prototype.renderCheckbox = function(name, labelText) {
var me = this,
checkbox = new jasmine.Dom({
tag: "input",
cls: "option " + name,
type: "checkbox",
onclick: function() {
me.onCheckboxClick.apply(me, arguments);
}
});
me.optionCheckBoxesEl[name] = checkbox;
return new jasmine.Dom({
tag: "span",
cls: "show",
children: [checkbox,{
tag: "label",
html: labelText
}]
});
};
/**
* Checks options checkboxs if needed.
*/
Test.OptionsImpl.prototype.check = function() {
var property, checkbox;
for (property in this.options) {
if (!this.options.hasOwnProperty(property)) {
continue;
}
checkbox = this.optionCheckBoxesEl[property];
if (checkbox) {
checkbox.checked = this.options[property];
}
}
};
/**
* Options checkbox check/uncked handler.
* @param {HTMLElement} el The checkbox HTMLElement
*/
Test.OptionsImpl.prototype.onCheckboxClick = function(event) {
var el, opt, row, length, i;
event = event || window.event;
el = event.target || event.srcElement;
opt = el.className.split(" ")[1];
if (el.checked) {
this.options[opt] = true;
} else {
delete this.options[opt];
}
};
/**
* Reloads current page with reporter options.
*/
Test.OptionsImpl.prototype.reloadWindow = function(reset) {
if (reset) {
this.options.specs = [];
this.options.suites = [];
}
window.location.search = this.urlEncode(this.options);
};
/**
* Starts autoReload task.
*/
Test.OptionsImpl.prototype.startAutoReloadTask = function() {
var me = this;
if (me.options.autoReload) {
var interval = setInterval(function() {
if (Test.SandBox.isRunning()) {
clearInterval(interval);
setTimeout(function() {
me.reloadWindow();
}, 2000);
}
}, 1500);
}
};
Test.OptionsImpl.prototype.isChecked = function(o) {
var specs = this.options.specs,
suites = this.options.suites,
id = o.id;
if (o.suite) {
return specs && jasmine.array.indexOf(specs, id) !== -1;
} else {
return suites && jasmine.array.indexOf(suites, id) !== -1;
}
return false;
};
Test.Options = new Test.OptionsImpl();Test.SandBoxImpl = function(){};
Test.SandBoxImpl.prototype.domReady = function(fn) {
if (document.addEventListener) {
window.addEventListener('load', fn, false);
} else {
window.attachEvent('onload', fn, false);
}
};
Test.SandBoxImpl.prototype.setup = function(config) {
var me = this;
me.requires = config.requires;
me.domReady(function() {
me.reporter = new Test.Reporter();
me.createIframe();
});
};
Test.SandBoxImpl.prototype.createIframe = function() {
var me = this,
iframe,
win,
doc;
me.options = Test.Options.get();
var src = me.options.quirksMode ? 'iframe-quirks.html?loadSpecs=true' : 'iframe.html?loadSpecs=true';
src += '&compiled=' + !!me.options.compiled;
if (me.options.specsset) {
src += '&specsset=' + me.options.specsset;
}
iframe = new jasmine.Dom({
tag: "iframe",
cls: "sandboxIframe",
name: "sandbox",
frameBorder: 0,
src: src
});
me.reporter.getIframeContainer().appendChild(iframe);
win = iframe.contentWindow || window.frames[iframe.name];
doc = iframe.contentDocument || win.document;
this.iframe = iframe;
this.win = win;
this.doc = doc;
};
Test.SandBoxImpl.prototype.getIframe = function() {
return this.iframe;
};
Test.SandBoxImpl.prototype.getWin = function() {
return this.win;
};
Test.SandBoxImpl.prototype.getDoc = function() {
return this.doc;
};
Test.SandBoxImpl.prototype.getBody = function() {
return this.getDoc().body;
};
Test.SandBoxImpl.prototype.getHead = function() {
return this.getDoc().getElementsByTagName("head")[0];
};
Test.SandBoxImpl.prototype.save = function(spec) {
var doc = this.getDoc(),
sb = doc.createElement("div"),
body = this.getBody(),
children = body && body.childNodes || [],
length = children.length,
i = 0,
child,
lwas = this.lengthWas || (this.lengthWas = 0);
if (!this.options || !this.options.disableBodyClean) {
//this.clearComponents();
//this.clearDomElements();
}
if (length != lwas) {
if (!window.headless) {
this.reporter.log(">> Warning the document.body dom element contains childNodes after spec execution !<br/>" +
"Spec : " + jasmine.util.htmlEscape(spec.getFullName()) + ' <a href="?' +
Test.Options.urlEncode({specs: [spec.id], suites:[], disableBodyClean: true}) + '">Load this spec only and disable body autoclean</a><br/>',
"warning");
} else {
this.reporter.log("Warning: " + spec.getFullName() + "doesn't clean properly the document.body.");
}
this.lengthWas = length;
}
};
Test.SandBoxImpl.prototype.clearDomElements = function() {
var doc = this.getDoc(),
bd = this.getBody(),
children = bd.childNodes,
length = children.length,
i, child;
if (!this.options.disableBodyClean) {
for (i = 0; i < length; i++) {
child = children[i];
if (child) {
bd.removeChild(child);
}
}
}
};
Test.SandBoxImpl.prototype.clearComponents = function() {
var me = this,
win = me.getWin(),
comps, c, len, i;
if(win.Ext && win.Ext.ComponentManager) {
comps = win.Ext.ComponentManager.all.getArray();
len = comps.length;
for(i=0; i<len; i++) {
c = comps[i];
c.destroy();
}
}
};
Test.SandBoxImpl.prototype.isRunning = function() {
return !this.getWin().jasmine.getEnv().currentRunner_.queue.isRunning();
};
Test.SandBoxImpl.prototype.iScope = function(o) {
if (typeof o === "function") {
o = "(" + o.toString() + ")();";
}
return Test.SandBox.getWin().eval(o);
};
Test.SandBox = new Test.SandBoxImpl();
var iScope = Test.SandBox.iScope; /**
* @class Test.CodeHighLighter
* A javascript simple source code higlighter and beautifier (optional).
*/
Test.CodeHighLighter = function(config) {
/**
* @cfg {String} source The source string to process.
*/
this.source = config.source;
this.lineNumber = config.lineNumber;
this.linesFromJsCoverage = config.linesFromJsCoverage;
this.beautify = config.beautify || this.lineNumber === undefined;
this.highLightCode = config.highLightCode === false ? false : true;
this.matchedComments = [];
this.matchedStrings = [];
};
/**
* Regular expressions.
*/
Test.CodeHighLighter.prototype.regExps = {
strings: /"([^\\"\n]|\\.)*"|'([^\\'\n]|\\.)*'|"([^\\"\n]|\\\n)*"|'([^\\'\n]|\\\n)*'/gm,
comments: /\/\/.*$|\/\*[\s\S]*?\*\//gm,
operators: /([\+\-\*\/=\?!]{1,3}|[\-\+]{1,2})/g,
numbers: /\b([0-9]+)\b/g,
keywords: [/\b(break)\b/g, /\b(case)\b/g, /\b(catch)\b/g, /\b(continue)\b/g, /\b(default)\b/g,
/\b(delete)\b/g, /\b(do)\b/g, /\b(else)\b/g, /\b(false)\b/g, /\b(for)\b/g, /\b(function)\b/g,
/\b(if)\b/g, /\b(in)\b/g, /\b(instanceof)\b/g, /\b(new)\b/g, /\b(null)\b/g,
/\b(return)\b/g, /\b(switch)\b/g, /\b(this)\b/g, /\b(throw)\b/g, /\b(true)\b/g,
/\b(try)\b/g,/\b(typeof)\b/g, /\b(var)\b/g, /\b(while)\b/g, /\b(with)\b/g],
commasInsideParenthesis: /\(([^\(\)\{\}])+\)/g,
arrayWithOneElement: /\[\n([^,\]]*)\n\]/g,
commaBracket: /,\n\s*\{/g,
multipleWhiteSpaces: /(\s+)/g,
semiColon: /;/g,
comma: /,/g,
openedBrackets: /([\{\[])/g,
closedBrackets: /([\}\]])/g,
emptyObject: /\{\n\s*\n\}/g,
openedBracketsWithNewLine: /[\{\[]$/g,
closedBracketsWithNewLine: /^\s*[\}\]]/g,
unwantedNewLines: /\n([\n,;\)])/g,
newLine: /\n/g,
firstSpaces: /^(\s)+/
};
/**
* Populates an array of matched objects.
* @param {String} value The match result.
* @param {Number} index The index of the match.
* @param {Array} matchedObjects The array of matches to populate.
* @param {String} css The css to apply to the match.
* @return {Boolean} Returns <tt>true</tt> is the match is inside another.
*/
Test.CodeHighLighter.prototype.matchObjects = function(value, index, matchedObjects, css) {
matchedObjects.push({
origValue: value,
value: '<span class="jsHl'+ css +'">' + jasmine.util.htmlEscape(value).replace("$","$\b") + '</span>',
start: index,
end: index + value.length
});
};
/**
* Checks if a match is inside another matches.
* @param {Object} matchedObject The checked match.
* @param {Array} matchedOthers The array that contains other matches.
* @return {Boolean} Returns <tt>true</tt> is the match is inside another.
*/
Test.CodeHighLighter.prototype.isInside = function(matchedObject, matchedOthers) {
var start = matchedObject.start,
end = matchedObject.end,
length = matchedOthers.length,
matchedOther, i;
for (i = 0; i < length; i++) {
matchedOther = matchedOthers[i];
if (matchedOther.start < start && start < matchedOther.end) {
return true;
}
}
return false;
};
/**
* This function get rid of any matches that are inside of other matches.
* If a match isn't inside another it is replaced by a string in {@link #source}
* in order to protect it from {@link #processOperatorsNumbersKeywords} replace tricks.
* @param {Array} matchedObjects The array of matches to check.
* @param {Array} matchedOthers The array that contains other matches.
* @param {String} protect The replacement string
*/
Test.CodeHighLighter.prototype.fixOverlaps = function(matchedObjects, matchedOthers, protect) {
var result = [],
length = matchedObjects.length,
matchedObject,
i;
for (i = 0; i < length; i++) {
matchedObject = matchedObjects[i];
if (!this.isInside(matchedObject, matchedOthers)) {
this.source = this.source.replace(matchedObject.origValue, protect);
result.push(matchedObject);
}
}
return result;
};
/**
* Replaces Strings and Comments in javascript source code.
*/
Test.CodeHighLighter.prototype.saveStringsAndComments = function() {
var commentsRe = this.regExps.comments,
stringsRe = this.regExps.strings,
exec;
while((exec = commentsRe.exec(this.source))) {
this.matchObjects(exec[0], exec.index, this.matchedComments, "Comment");
}
while((exec = stringsRe.exec(this.source))) {
this.matchObjects(exec[0], exec.index, this.matchedStrings, "String");
}
this.matchedComments = this.fixOverlaps(this.matchedComments, this.matchedStrings, "%%%%comment%%%%");
this.matchedStrings = this.fixOverlaps(this.matchedStrings, this.matchedComments, '%%%%string%%%%');
};
/**
* Process strings and comments saved by {@link #saveStringsAndComments}.
*/
Test.CodeHighLighter.prototype.processStringsAndComments = function() {
var matches = this.matchedComments,
length = matches ? matches.length : 0,
value, i;
for (i = 0; i < length; i++) {
value = matches[i].value;
this.source = this.source.replace("%%%%comment%%%%", value);
}
matches = this.matchedStrings;
length = matches ? matches.length : 0;
for (i = 0; i < length; i++) {
value = matches[i].value;
this.source = this.source.replace('%%%%string%%%%', value);
}
};
/**
* Highlight operators, numbers and keywords.
*/
Test.CodeHighLighter.prototype.processOperatorsNumbersKeywords = function() {
var regexps = this.regExps,
keywords = regexps.keywords,
length = keywords.length,
i;
this.source = jasmine.util.htmlEscape(this.source).replace(
regexps.operators, '<span class="jsHlOperator">$1</span>').replace(
regexps.numbers, '<span class="jsHlNumber">$1</span>');
for (i = 0; i < length; i++) {
this.source = this.source.replace(keywords[i], '<span class="jsHlKeyword">$1</span>');
}
};
/**
* Format and highligth javascript sources.
* @return The HTML formatted and highlighted code
*/
Test.CodeHighLighter.prototype.process = function() {
this.saveStringsAndComments();
if (this.beautify) {
this.prepareIndent();
this.doIndent();
}
this.processOperatorsNumbersKeywords();
this.processStringsAndComments();
return this.source;
};
/**
* Render sources with line numbers.
* @return The HTML formatted and highlighted code
*/
Test.CodeHighLighter.prototype.renderJsSources = function() {
var result = 'No code found.',
linesFromJsCoverage = this.linesFromJsCoverage,
lineNumber = this.lineNumber,
source = this.source,
lines, line, i, errorCls, length, lineNumberCls;
if (source) {
source = this.highLightCode ? this.process() : source;
lines = source.split("\n");
length = lines.length;
result = '<table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="lineNumbers">';
for (i = 0; i < length; i++) {
errorCls = "";
lineNumberCls = "";
if (lineNumber) {
errorCls = i === (lineNumber - 1) ? " error" : "";
}
if (linesFromJsCoverage) {
lineNumberCls = !isNaN(linesFromJsCoverage[i + 1]) ? " lineNumberGreen" : "";
lineNumberCls = linesFromJsCoverage[i + 1] === 0 ? " lineNumberRed" : lineNumberCls;
}
result += '<div class="lineNumber' + errorCls + lineNumberCls + '">' + (i + 1) +'</div>';
}
result += '</td><td><pre class="code">'+ source +'</pre></td></tr></tbody></table>';
}
this.source = result;
return this.source;
};
/**
* Prepares source code. It crops double whitespace and append new lines.
* This function is used generally to preformat the code that come from a
* Function.prototype.toString.
*/
Test.CodeHighLighter.prototype.prepareIndent = function() {
var regexps = this.regExps,
matches, length, i, m;
this.source = this.source.replace(
regexps.multipleWhiteSpaces, " ").replace(
regexps.semiColon, ";\n").replace(
regexps.comma, ",\n").replace(
regexps.openedBrackets, "$1\n").replace(
regexps.closedBrackets, "\n$1\n");
// remove newline after commas inside code parenthesis
matches = this.source.match(regexps.commasInsideParenthesis);
length = matches ? matches.length : 0;
for (i = 0; i < length; i++) {
m = matches[i];
this.source = this.source.replace(m, m.replace(regexps.newLine, ""));
}
// fixes various bad formatting
this.source = this.source.replace(regexps.arrayWithOneElement, "[$1]").replace(
regexps.emptyObject, "{}").replace(
regexps.commaBracket, ", {").replace(
regexps.unwantedNewLines, "$1");
};
/**
* Creates a string composed of n whitespaces
* @param {Number} number The number of white spaces.
* @return {String} A multiple whitespace string.
*/
Test.CodeHighLighter.prototype.addWhiteSpaces = function (number) {
var whiteSpaces = "",
i;
for (i = 0; i < number; i++) {
whiteSpaces += " ";
}
return whiteSpaces;
};
/**
* Indents pre-formatted source code.
*/
Test.CodeHighLighter.prototype.doIndent = function() {
var regexps = this.regExps,
results = [],
indent = 0,
sources = this.source.split("\n"),
length = sources.length,
whiteSpaces = "",
source, i;
for (i = 0; i < length; i++) {
source = sources[i].replace(regexps.firstSpaces, '');
if (source !== "") {
if (source.search(regexps.closedBracketsWithNewLine) !== -1) {
indent = Math.max(indent - 4, 0);
whiteSpaces = this.addWhiteSpaces(indent);
}
results.push(whiteSpaces + source);
if (source.search(regexps.openedBracketsWithNewLine) !== -1) {
indent += 4;
whiteSpaces = this.addWhiteSpaces(indent);
}
}
}
this.source = results.join("\n");
};
/**
* Init allowedGlobals array.
*/
Test.BadGlobalsImpl = function(reporter) {
this.results = [];
};
Test.BadGlobalsImpl.prototype.setup = function() {
var me = this,
win = Test.SandBox.getWin(),
property;
// whitelist support
win.addGlobal = function() {
me.addGlobal.apply(me, arguments);
};
me.allowedGlobals = {};
for (property in win) {
me.allowedGlobals[property] = true;
}
// add firebug globals variables to white list
me.allowedGlobals._firebug = true;
me.allowedGlobals._createFirebugConsole = true;
me.allowedGlobals.loadFirebugConsole = true;
me.allowedGlobals.console = true;
};
/**
* Append to suite HTMLElement warning messages if improper global variables are found.
* @param {HTMLElement} suiteEl The suite HTMLElement.
*/
Test.BadGlobalsImpl.prototype.report = function(info, suite) {
var allowedGlobals = this.allowedGlobals,
win = Test.SandBox.getWin(),
property, message, value;
for (property in win) {
if (!allowedGlobals[property]) {
value = jasmine.pp(win[property]);
message = ">> Bad global variable found in " + (suite ? suite.description : "global scope") + "<br/>" + property + " = " + value;
info.log(message, "warning");
this.results[property] = {
where: (suite ? ('in suite' + suite.description) : "global scope"),
value: value
};
allowedGlobals[property] = true;
}
}
};
Test.BadGlobalsImpl.prototype.addGlobal = function(property) {
this.allowedGlobals[property] = true;
};
if (!jasmine.browser.isIE && !jasmine.browser.isOpera) {
Test.BadGlobals = new Test.BadGlobalsImpl();
}/**
* @singleton Test.jsCoverage
* The jscoverage manager.
*/
Test.jsCoverage = {
executed: 0,
coverage: {},
isEnabled: function() {
return !!Test.SandBox.getWin()._$jscoverage;
},
getCoverage: function() {
return this.coverage;
},
getSandBoxCoverage: function() {
return Test.SandBox.getWin()._$jscoverage;
},
/**
* Adds suite to the jscoverage manager.
* @param {jasmine.Suite} The jasmine suite.
*/
add: function(suite) {
var coverage = this.getSandBoxCoverage(),
filename, file, property, statement;
if (!coverage) {
return;
}
filename = this.getFileName(suite.coverageFile);
file = coverage[filename];
if (coverage && file) {
for (property in file) {
if (!file.hasOwnProperty(property)) {
continue;
}
statement = file[property];
}
}
},
/**
* This methods try to find the corresponding javascript source file.
* @param {String} The filename.
*/
getFileName: function(filename) {
var coverage = this.getSandBoxCoverage(),
property;
if (!coverage || !filename) {
return;
}
if (coverage[filename]) {
return filename;
}
for (property in coverage) {
if (property.search(filename) !== -1) {
return property;
}
}
},
/**
* Updates suite coverage results after execution.
* @param {jasmine.Suite} The jasmine suite.
*/
update: function(suite) {
var coverage = this.getSandBoxCoverage(),
statements = 0,
executed = 0,
property, statement, filename, file;
if (!coverage) {
return;
}
filename = this.getFileName(suite.coverageFile);
file = coverage[filename];
if (file) {
suite.jscoverage = {
file: []
};
for (property in file) {
if (!file.hasOwnProperty(property)) {
continue;
}
statement = file[property];
suite.jscoverage.file[property] = statement;
if (!isNaN(property) && statement !== undefined) {
statements = statements + 1;
if (statement !== 0) {
this.executed = this.executed + 1;
executed = executed + 1;
}
}
}
suite.jscoverage.percentage = ((executed/statements) * 100).toFixed(2);
suite.jscoverage.statements = statements;
suite.jscoverage.executed = executed;
this.coverage[filename] = suite.jscoverage.file;
this.coverage[filename].percentage = suite.jscoverage.percentage;
this.coverage[filename].statements = suite.jscoverage.statements;
this.coverage[filename].executed = suite.jscoverage.executed;
}
},
/**
* Returns suite coverage text.
* @param {jasmine.Suite} The jasmine suite.
* @return {String} The Code coverage text<
*/
getSuiteCoverage: function(suite) {
if (suite.jscoverage) {
return " - Code coverage: " + suite.jscoverage.percentage + "%";
}
return '';
},
/**
* Gets total code coverage.
* @return {String} A string with total code coverage.
*/
getTotal: function() {
if (this.percentage) {
return " - Code coverage: " + this.percentage + "%";
}
return '';
},
updateTotal: function() {
var coverage = this.getSandBoxCoverage(),
statements = 0,
file, filename, statement, property, fstatements, fexecuted, create;
if(!coverage) {
return "";
}
for (filename in coverage) {
if (!coverage.hasOwnProperty(filename)) {
continue;
}
file = coverage[filename];
fstatements = 0;
fexecuted = 0;
create = !this.coverage[filename];
if (create) {
this.coverage[filename] = [];
}
for (property in file) {
if (!file.hasOwnProperty(property)) {
continue;
}
statement = file[property];
if (!isNaN(property)) {
if (statement !== undefined) {
statements = statements + 1;
fstatements = fstatements + 1;
}
if (create) {
this.coverage[filename][property] = 0;
}
}
}
if (create) {
this.coverage[filename].source = file.source;
this.coverage[filename].statements = fstatements;
this.coverage[filename].executed = fexecuted;
this.coverage[filename].percentage = ((fexecuted/fstatements) * 100).toFixed(2);
}
}
this.statements = statements;
this.percentage = ((this.executed/statements) * 100).toFixed(2);
}
};Test.panel = {};
/**
* Renders Jasmine Blocks executed by spec.
* @param {Jasmine.spec} spec The spec.
* @param {HTMLElement} panelsEl The HTMLElement which encapsulate the tools panels.
*/
Test.panel.Blocks = function(config) {
var blocks = config.spec.queue.blocks,
length = blocks.length,
cls = "panel blocks",
children = [],
i, block, codeHighLighter;
for (i = 0; i < length; i++) {
block = blocks[i];
if (block.func) {
children.push({
cls: "blockTitle " + (block.func.typeName || "specSources"),
html: block.func.typeName || 'it("' + jasmine.util.htmlEscape(config.spec.description) + '")'
});
codeHighLighter = new Test.CodeHighLighter({
source: block.func.toString()
});
children.push({
cls: "sources",
html: codeHighLighter.renderJsSources()
});
}
}
this.el = new jasmine.Dom({
cls: cls,
children: children
});
return this;
};
Test.panel.Blocks.prototype.remove = function() {
this.el.parentNode.removeChild(this.el);
};/**
* Renders spec dom sandbox tool.
* @param {Jasmine.spec} spec The spec.
* @param {HTMLElement} panelsEl The HTMLElement which encapsulate the tools panels.
*/
Test.panel.Sandbox = function(config) {
this.persist = true;
this.render();
return this;
};
/**
* Renders spec dom sandbox innerHTML.
* @return {HTMElement} The formatted dom sandbox innerHTML.
*/
Test.panel.Sandbox.prototype.render = function() {
this.el = new jasmine.Dom({
cls: "panel sandbox hideMe"
});
};/**
* Renders infos panel.
*/
Test.panel.Infos = function() {
this.el = new jasmine.Dom({
tag: "div",
cls: "panel infos",
children: [{
cls: "logs"
}]
});
this.logs = this.el.childNodes[0];
this.persist = true;
return this;
};
/**
* Print a message into console.
* @param {String} message The message.
* @param {String} cls (optional) an extra cls to add to the message.
*/
Test.panel.Infos.prototype.log = function(message, cls) {
var log = this.logs.appendChild(new jasmine.Dom({
cls: "infoMessage",
html: message
}));
if (cls) {
jasmine.Dom.addCls(log, cls);
}
};/**
* @class jasmine.panel.jsCoverage
* Creates and renders a per spec jscoverage panel.
* @param {Object} config The configuration object.
*/
Test.panel.jsCoverage = function(config) {
this.el = new jasmine.Dom({
tag: "div",
cls: "panel jsCoverage",
children: [{
cls: "sources",
html: new Test.CodeHighLighter({
source: config.suite.jscoverage.file.source.join("\n"),
linesFromJsCoverage: config.suite.jscoverage.file,
highLightCode: false
}).renderJsSources()
}]
});
return this;
};
Test.panel.jsCoverage.prototype.remove = function() {
this.el.parentNode.removeChild(this.el);
};/**
* @class jasmine.panel.jsCoverageSummary
* Creates and renders the persistant jscoverage summary panel.
* @param {Object} config The configuration object.
*/
Test.panel.jsCoverageSummary = function(config) {
var me = this;
me.el = new jasmine.Dom({
tag: "div",
cls: "panel jsCoverageSummary hideMe",
onclick: function() {
me.onClick.apply(me, arguments);
},
children: [{
cls: "sbody"
}]
});
me.body = me.el.childNodes[0];
me.persist = true;
this.renderSummary();
return me;
};
/**
* Renders summary view.
*/
Test.panel.jsCoverageSummary.prototype.renderSummary = function() {
var coverage = Test.jsCoverage.getCoverage(),
filename, result;
if (!this.summary) {
result = '<table class="summary" border="0" cellpadding="0" cellspacing="0"><tbody>';
result += '<tr class="line header"><td class="fileName">File</td><td class="statements">Statements</td><td class="executed">Executed</td><td class="percentage">Percentage</td></tr>';
result += '<tr class="line total">';
result += '<td class="fileName">Total</td>';
result += '<td class="statements">' + Test.jsCoverage.statements + "</td>";
result += '<td class="executed">' + Test.jsCoverage.executed + "</td>";
result += '<td class="percentage">' + this.renderPercentage(Test.jsCoverage.percentage) + "</td>";
result += '</tr>';
for (filename in coverage) {
if (!coverage.hasOwnProperty(filename)) {
continue;
}
result += '<tr class="line">';
result += '<td class="fileName"><a>' + filename + "</a></td>";
result += '<td class="statements">' + coverage[filename].statements + "</td>";
result += '<td class="executed">' + coverage[filename].executed + "</td>";
result += '<td class="percentage">' + this.renderPercentage(coverage[filename].percentage) + "</td>";
result += '</tr>';
}
result += '</tbody></table>';
this.summary = result;
}
this.body.innerHTML = this.summary;
};
/**
* Renders percentage progress bar.
* @return {String} The progressbar html.
*/
Test.panel.jsCoverageSummary.prototype.renderPercentage = function(percent) {
var result = percent + '%<div class="limit" style="width:300px;">';
result += '<div class="result" style="width:' + 3 * percent + 'px;"></div>';
result += '</div>';
return result;
};
/**
* Renders percentage progress bar.
* @return {String} The progressbar html.
*/
Test.panel.jsCoverageSummary.prototype.onClick = function(event) {
var el;
event = event || window.event;
el = event.target || event.srcElement;
if (el.tagName === "A") {
this.renderSource(Test.jsCoverage.getCoverage()[el.innerHTML]);
}
if (jasmine.Dom.hasCls(el,"back")) {
this.renderSummary();
}
};
/**
* Renders file source.
*/
Test.panel.jsCoverageSummary.prototype.renderSource = function(coverage) {
this.body.innerHTML = "";
this.body.appendChild(new jasmine.Dom({
cls: "back",
html: "Back"
}));
this.body.appendChild(new jasmine.Dom({
cls: "sources",
html: new Test.CodeHighLighter({
source: coverage.source.join("\n"),
linesFromJsCoverage: coverage,
highLightCode: false
}).renderJsSources()
}));
};/**
* Renders stack trace tool.
* @param {Jasmine.spec} The jasmine spec.
* @return {HTMLElement} The created HTMLElement.
*/
Test.panel.StackTrace = function(config) {
this.spec = config.spec;
this.badLinesEls = [];
var resultItems = this.spec.results().getItems(),
length = resultItems.length,
result,
error,
lines,
i;
if (jasmine.browser.isIE || !this.spec.hasError) {
return this;
}
for (i = 0; i < length; i++) {
result = resultItems[i];
if (result.type == "expect" && result.passed && !result.passed()) {
if (result.error) {
error = result.error;
break;
}
}
}
if (error) {
lines = this.extractStackTrace(error);
this.el = new jasmine.Dom({
tag: "div",
cls: "panel stackTrace",
children: this.renderStackLines(lines)
});
}
return this;
};
/**
* Extracts error stack trace.
* @param {Error} e The javascript error object.
* @return {Array} An array which contains all stack trace files and lineNumbers.
*/
Test.panel.StackTrace.prototype.extractStackTrace = function(error) {
var stack = error.stack || error.stackTrace,
results = [],
lines, line, length, i, extract, file, lineNumber;
if (stack) {
lines = stack.split("\n");
length = lines.length;
for(i = 0; i < length; i++) {
line = lines[i];
if (line.search("jasmine.js") === -1) {
extract = this.extractFileAndLine(line);
if (extract) {
results.push(extract);
}
}
}
} else {
file = error.sourceURL || error.fileName;
lineNumber = error.line || error.lineNumber;
if (file && lineNumber) {
results.push({
file: file,
lineNumber: lineNumber
});
}
}
return results;
};
/**
* Extracts filename and line number from a stack trace line.
* @param {String} line The stack trace line.
* @return {Object} An object containing the filename and the line number or null.
*/
Test.panel.StackTrace.prototype.extractRe = /((http:\/\/|file:\/\/\/).*\.js)[^:]*:(\d*)/;
Test.panel.StackTrace.prototype.extractFileAndLine = function(line) {
var result = line.match(this.extractRe);
if (!result) {
return null;
}
return {
file: result[1],
lineNumber: result[3]
};
};
/**
* Render stack trace lines.
* @param {String} file The filename.
* @param {String/Number} lineNumber The line number.
* @return {Array} An array containing all strace trace HTMLElements.
*/
Test.panel.StackTrace.prototype.renderStackLines = function(lines) {
var els = [],
length = lines.length,
el, line, i, file, lineNumber;
for (i = 0; i < length; i++) {
line = lines[i];
file = line.file;
lineNumber = parseInt(line.lineNumber, 0);
el = new jasmine.Dom({
cls: "stackTraceLine",
children: [{
cls: "fileName",
html: "File: "+ file + " (line " + lineNumber + ")"
},{
cls: "sources",
html: this.renderTraceFileSource(file, lineNumber)
}]
});
this.badLinesEls.push({
el: el.childNodes[1],
line: lineNumber
});
els.push(el);
}
return els;
};
/**
* Downloads source file.
* @param {String} url The filename url.
* @return {String} The file source or null.
*/
Test.panel.StackTrace.prototype.getFile = function(file) {
var request;
if (jasmine.browser.isIE || Test.Options.remote) {
return null;
}
this.downloadedFiles = this.downloadedFiles || {};
if (!this.downloadedFiles[file]) {
request = new XMLHttpRequest();
if (!request) {
return null;
}
request.open("GET", file + "?" + (new Date()).getTime(), false);
request.send("");
this.downloadedFiles[file] = request.responseText;
}
return this.downloadedFiles[file];
};
/**
* Renders stack trace source file.
* @param {String} file The filename.
* @param {String/Number} lineNumber The line number.
* @return {HTMLElement} The javascript source file HTMLElement.
*/
Test.panel.StackTrace.prototype.jscoverageFileRe = /(http:\/\/|file:\/\/\/)[^\/]*/;
Test.panel.StackTrace.prototype.renderTraceFileSource = function (file, lineNumber) {
var highLightCode = true,
source, instrumented_file, i, length, line;
if (Test.SandBox.getWin()._$jscoverage) {
instrumented_file = SandBox.getWin()._$jscoverage[file.replace(this.jscoverageFileRe, "")];
if (instrumented_file) {
highLightCode = false;
source = instrumented_file.source.join("\n");
linesFromJsCoverage = {};
length = instrumented_file.length;
for (i = 0; i < length; i++) {
line = instrumented_file[i];
if (line === 0) {
linesFromJsCoverage[i-1] = true;
}
}
}
}
source = source || this.getFile(file);
return new Test.CodeHighLighter({
source: source,
highLightCode: highLightCode,
lineNumber: lineNumber
}).renderJsSources();
};
/**
* Ensure that line which contains the error is visible without scroll.
*/
Test.panel.StackTrace.prototype.afterRender = function() {
var length = this.badLinesEls.length,
badLine, firstChild, el, i, lineHeigth, visiblesLines;
for (i = 0; i < length; i++) {
badLine = this.badLinesEls[i];
el = badLine.el;
lineHeigth = 16;
visiblesLines = el.clientHeight/lineHeigth;
el.scrollTop = Math.max(badLine.line - visiblesLines/2, 0) * lineHeigth;
}
this.badLinesEls = [];
};
Test.panel.StackTrace.prototype.remove = function() {
this.el.parentNode.removeChild(this.el);
};/**
* @class Test.panel.TabPanel
* Renders inspection tools htmlElement.
* @param {Object} config The configuration object.
*/
Test.panel.TabPanel = function(config) {
var me = this;
me.options = Test.Options.get();
me.spec = config.spec;
me.container = config.container;
me.el = new jasmine.Dom({
cls: "tabpanel",
onclick: function() {
me.onTabPanelClick.apply(me, arguments);
},
children: [{
cls: "toolBar"
},{
cls: "panels"
}]
});
me.toolbar = me.el.childNodes[0];
me.body = me.el.childNodes[1];
me.children = [];
me.tabs = [];
me.container.appendChild(me.el);
me.renderToolBar();
me.add(new Test.panel.Infos({}));
me.add(new Test.panel.Sandbox({}));
if (me.options.panel) {
me.activatePanel(me.options.panel);
}
return me;
};
/**
* Adds a panel.
* @param {Object} panel the panel to be added to this tabPanel.
*/
Test.panel.TabPanel.prototype.add = function(panel) {
if (panel.el) {
this.body.appendChild(panel.el);
}
if (panel.afterRender) {
panel.afterRender();
}
this.children.push(panel);
if (panel.afterRender) {
panel.afterRender();
}
};
/**
* Adds a tab
* @param {Object} panel the panel to be added to this tabPanel.
*/
Test.panel.TabPanel.prototype.addTab = function(cls, name, persist) {
var el = this.toolbar.appendChild(new jasmine.Dom({
tag: "span",
cls: "toolbarTab " + cls,
html: name
}));
this.tabs.push({
el: el,
persist: persist
});
};
/**
* Activate a tool panel and render it if needed.
* @param {String} cls The panel className.
*/
Test.panel.TabPanel.prototype.activatePanel = function(cls) {
var children = this.children,
length = children.length,
rendered = false,
child, i;
for(i = 0; i < length; i++) {
child = children[i].el;
jasmine.Dom.addCls(child, "hideMe");
if (jasmine.Dom.hasCls(child, cls)) {
jasmine.Dom.removeCls(child, "hideMe");
if (children[i].persist && cls !== "jsCoverageSummary") {
this.options.panel = cls;
} else {
delete this.options.panel;
}
rendered = true;
}
}
if (rendered) {
return;
}
if (this.spec) {
if (cls === "blocks") {
this.add(new Test.panel.Blocks({
spec: this.spec
}));
}
if (cls === "stackTrace") {
this.add(new Test.panel.StackTrace({
spec: this.spec
}));
}
}
if (this.suite && this.suite.jscoverage) {
if (cls === "jsCoverage") {
this.add(new Test.panel.jsCoverage({
suite: this.suite
}));
}
}
};
/**
* Reporter HTMLElement click dispatcher.
* @param {Event} event The event
*/
Test.panel.TabPanel.prototype.onTabPanelClick = function(event) {
var el;
event = event || window.event;
el = event.target || event.srcElement;
if (jasmine.Dom.hasCls(el, "toolbarTab")) {
this.onTabClick(el);
}
};
/**
* Handle spec tools tab click.
* @param {HTMLElement} el The tab HTMLElement.
*/
Test.panel.TabPanel.prototype.onTabClick = function(el) {
var tools, panels, length, child, i;
jasmine.Dom.addCls(el, "selected");
tools = this.toolbar.childNodes;
panels = this.body.childNodes;
length = tools.length;
for(i = 0; i < length; i++) {
child = tools[i];
if (child != el) {
jasmine.Dom.removeCls(child, "selected");
}
}
this.activatePanel(el.className.split(" ")[1]);
};
/**
* Renders inspection tabpanel toolbar which contain tabs.
* @param {jasmine.Spec} spec The jasmine spec.
* @param {HTMLElement} toolBarEl The toolbar HTMLElement
*/
Test.panel.TabPanel.prototype.renderToolBar = function() {
var spec = this.spec,
suite = this.suite,
toolbar = this.toolbar;
if (this.tabs.length === 0) {
this.addTab("infos selected", "Console", true);
this.addTab("sandbox", "Iframe", true);
} else {
jasmine.Dom.addCls(this.tabs[0].el, "selected");
}
if (spec) {
this.addTab("blocks", "Blocks");
if (!jasmine.browser.isIE && !jasmine.browser.isOpera && this.spec.hasError) {
this.addTab("stackTrace", "Stack Trace");
}
}
if (suite && suite.jscoverage) {
this.addTab("jsCoverage", "Suite Coverage");
}
};
/**
* Removes all non-persistant tabs.
*/
Test.panel.TabPanel.prototype.resetToolBar = function() {
var children = this.tabs,
length = children.length,
child, i;
for (i = length - 1; i >= 0; i--) {
child = children[i];
if (!child.persist) {
this.toolbar.removeChild(child.el);
jasmine.array.remove(children, child);
}
jasmine.Dom.removeCls(child.el, "selected");
}
this.renderToolBar();
};
/**
* Removes all non-persistant panels.
*/
Test.panel.TabPanel.prototype.resetPanels = function() {
var children = this.children,
length = children.length,
child, i;
for (i = length - 1; i >= 0; i--) {
child = children[i];
if (!child.persist) {
child.remove();
jasmine.array.remove(children, child);
}
jasmine.Dom.addCls(child.el, "hideMe");
}
if (children[0]) {
jasmine.Dom.removeCls(children[0].el, "hideMe");
}
};
/**
* Sets TabPanel current spec.
*/
Test.panel.TabPanel.prototype.setSpec = function(spec) {
this.spec = spec;
delete this.suite;
this.resetToolBar();
this.resetPanels();
};
/**
* Sets TabPanel current suite.
*/
Test.panel.TabPanel.prototype.setSuite = function(suite) {
this.suite = suite;
delete this.spec;
this.resetToolBar();
this.resetPanels();
};
/**
* Resize TabPanel dom element.
*/
Test.panel.TabPanel.prototype.resize = function(val) {
this.el.style.height = val + "px";
this.body.style.height = val - 40 + "px";
};
/**
* Adds jscoverage persistant panel.
*/
Test.panel.TabPanel.prototype.addCoverageSummary = function() {
this.addTab("jsCoverageSummary", "Coverage Summary", true);
this.add(new Test.panel.jsCoverageSummary({}));
};/**
* @class Test.panel.TreeGrid
* Creates and renders reporter treegrid.
* @param {Object} config The configuration object.
*/
Test.panel.TreeGrid = function(config) {
var me = this;
me.options = Test.Options.get();
me.el = document.body.appendChild(new jasmine.Dom({
tag: "div",
cls: "treegrid",
onmousedown: function() {
me.onMouseDown.apply(me, arguments);
},
onmouseup: function() {
me.onMouseUp.apply(me, arguments);
},
onmousemove: function() {
me.onMouseMove.apply(me, arguments);
},
children: [{
cls: "header",
children: [{
cls: "logo",
html: "Sencha"
},{
cls: "statusMessage"
},{
cls: "toolBar",
children: [{
tag: "span",
cls: "options",
children: [
Test.Options.renderCheckbox("showPassed", "Show passed"),
Test.Options.renderCheckbox("showDisabled", "Show disabled"),
Test.Options.renderCheckbox("collapseAll", "Collapse all"),
Test.Options.renderCheckbox("disableBodyClean", "Disable Body Autoclean"),
Test.Options.renderCheckbox("disableCacheBuster", "Disable CacheBuster"),
Test.Options.renderCheckbox("showTimings", "Show Timings"),
Test.Options.renderCheckbox("verbose", "Show jasmine logs"),
Test.Options.renderCheckbox("autoReload", "Automatic reload"),
Test.Options.renderCheckbox("quirksMode", "Quirks Mode")
]
},{
tag: "a",
cls: "actionLink",
html: "Run checked",
onclick: function() {
Test.Options.reloadWindow();
}
},{
tag: "a",
cls: "actionLink",
html: "Run all",
onclick: function() {
Test.Options.reloadWindow(true);
}
}]
}]
},{
tag: "div",
cls: "tbody",
onclick: function() {
me.onBodyClick.apply(me, arguments);
}
}, {
cls: "resizer",
html: "......"
}]
}));
me.tabPanel = new Test.panel.TabPanel({
container: me.el
});
Test.Options.check();
me.header = me.el.childNodes[0];
me.statusMessage = me.header.childNodes[1];
me.toolBar = me.header.childNodes[2];
me.body = me.el.childNodes[1];
me.resizer = me.el.childNodes[2];
me.suites = {};
me.specs = {};
me.suitesEls = {};
me.specsEls = {};
if (me.options.resizer) {
me.tabPanel.resize(parseInt(me.options.resizer, 10));
}
me.resizeBody();
window.onresize = function() {
me.resizeBody();
};
};
/**
* Renders suite htmlElement.
* @param {jasmine.Suite} suite The jasmine suite.
* @return {HTMLElement} The suite HTMLElement
*/
Test.panel.TreeGrid.prototype.addSuite = function(suite) {
var options = {},
parent = suite.parentSuite,
padding = 18,
prefix = suite.isDisabled() ? "xdescribe :" : "describe: ",
cls = "noexpand",
row, property;
if (suite.children_.length !== 0) {
cls = this.options.collapseAll ? "expand" : "collapse";
}
if (parent) {
this.suitesEls[parent.id] || this.addSuite(parent);
while(parent) {
padding += 18;
parent = parent.parentSuite;
}
}
row = this.createRow(this.options.collapseAll && suite.parentSuite, suite);
for (property in this.options) {
if (!this.options.hasOwnProperty(property)) {
continue;
}
options[property] = this.options[property];
}
options.suite = suite.id;
delete options.spec;
this.suitesEls[suite.id] = new jasmine.Dom({
tag: "div",
id: "suite-" + suite.id,
cls: "suite " + (suite.isDisabled() ? "disabled" : ""),
style: {
"paddingLeft": padding + "px"
},
children: [{
cls: cls
},{
tag: "span",
cls: "description",
html: prefix + suite.description
}]
});
row.appendChild(this.suitesEls[suite.id]);
var clear = new jasmine.Dom({ tag: 'div' });
clear.style.clear = 'both';
row.appendChild(clear);
this.suites[suite.id] = suite;
return this.suitesEls[suite.id];
};
/**
* Updates suite dom element by adding a code coverage percentage to it's description.
* @param {HTMLElement} The suite dom element.
* @param {jasmine.Suite} The jasmine suite.
*/
Test.panel.TreeGrid.prototype.updateSuiteEl = function(suite, text) {
var description = this.suitesEls[suite.id].childNodes[1];
jasmine.Dom.setHTML(description, description.innerHTML + text);
};
/**
* Renders spec htmlElement.
* @param {jasmine.Spec} spec The jasmine spec.
* @return {HTMLElement} The spec HTMLElement
*/
Test.panel.TreeGrid.prototype.addSpec = function(spec) {
var options = {},
padding = 18,
suite = spec.suite,
suffix = spec.time ? " (" + spec.time + "s)" : "",
row, prefix, status, property, specEl, resultPanel;
if (spec.isEnabled()) {
prefix = "it ";
status = spec.results().passed() ? "passed" : "failed";
} else {
prefix = "xit ";
status = "disabled";
}
if (suite) {
this.suitesEls[suite.id] || this.addSuite(suite);
while(suite) {
padding += 18;
suite = suite.parentSuite;
}
}
row = this.createRow(this.options.collapseAll, spec);
for (property in this.options) {
if (this.options.hasOwnProperty(property)) {
options[property] = this.options[property];
}
}
options.spec = spec.id;
delete options.suite;
specEl = {
id: "spec-" + spec.id,
cls: "spec " + status,
style: {
"paddingLeft": padding + "px"
},
children: [{
cls: this.options.collapseAll ? "expand" : "collapse"
},{
tag: "span",
cls: "description",
html: prefix + spec.description + suffix
}]
};
resultPanel = this.renderSpecResults(spec);
if (this.options.collapseAll) {
resultPanel.style.display = "none";
}
if (resultPanel.innerHTML === "") {
specEl.children[0].cls = "noexpand";
}
specEl.children.push(resultPanel);
specEl = new jasmine.Dom(specEl);
this.specsEls[spec.id] = specEl;
this.specs[spec.id] = spec;
row.appendChild(specEl);
jasmine.Dom.addCls(row, status);
var clear = new jasmine.Dom({ tag: 'div' });
clear.style.clear = 'both';
row.appendChild(clear);
};
/**
* Returns a suite by id.
* @param {String/Number} id The suite id.
* @return {jasmine.Suite} The jasmine suite.
*/
Test.panel.TreeGrid.prototype.getSuite = function(id) {
return this.suites[parseInt(id, 10)];
};
/**
* Returns a spec by id.
* @param {String/Number} id The spec id.
* @return {jasmine.Spec} The jasmine spec.
*/
Test.panel.TreeGrid.prototype.getSpec = function(id) {
return this.specs[parseInt(id, 10)];
};
/**
* Body elements click event dispatcher.
*/
Test.panel.TreeGrid.prototype.onBodyClick = function(event) {
event = event || window.event;
var el = event.target || event.srcElement,
cls = el.className,
i;
if (cls) {
if (jasmine.Dom.hasCls(el, "collapse")) {
this.onCollapse(el);
return;
}
if (jasmine.Dom.hasCls(el, "expand")) {
this.onExpand(el);
return;
}
if (jasmine.Dom.hasCls(el, "select-checkbox")) {
this.onCheck(el);
return;
}
for (i = 0; i < 6; i++) {
if (cls && jasmine.Dom.hasCls(el, "row")) {
this.onRowClick(el);
return;
}
el = el.parentNode;
if (!el) {
break;
}
cls = el.className;
}
}
};
/**
* Checkboxes listener.
*/
Test.panel.TreeGrid.prototype.onCheck = function(el) {
var next = el.parentNode.nextSibling,
id;
if (jasmine.Dom.hasCls(next,"spec")) {
id = parseInt(next.id.replace("spec-", ""), 10);
if (el.checked) {
if (jasmine.array.indexOf(this.options.specs, id) === -1) {
this.options.specs.push(id);
}
} else {
jasmine.array.remove(this.options.specs, id);
}
} else {
id = parseInt(next.id.replace("suite-", ""), 10);
if (el.checked) {
if (jasmine.array.indexOf(this.options.suites, id) === -1) {
this.options.suites.push(id);
}
} else {
jasmine.array.remove(this.options.suites, id);
}
}
};
/**
* Returns row dom element by spec or suite.
* @param {jasmine.Suite/jasmine.Spec} o A suite or a spec.
* @return {HTMLElement} The row dom element.
*/
Test.panel.TreeGrid.prototype.getRow = function(o) {
if (!o.suite && this.suitesEls[o.id]) {
return this.suitesEls[o.id].parentNode;
} else if (this.specsEls[o.id]) {
return this.specsEls[o.id].parentNode;
}
};
/**
* Iterates nested rows calling the supplied function.
* @param {HTMLElement} row The row.
* @param {Function} fn The function.
* @param {Boolean} recursive recurse in all children suite (default to true)
*/
Test.panel.TreeGrid.prototype.onEachRow = function(row, fn, recursive) {
var me = this,
id = row.childNodes[1].id,
traverse = function(s, func) {
var children = s.children_,
i, child, length, r;
if (children) {
length = children.length;
for (i = 0; i < length; i++) {
child = children[i];
r = me.getRow(child);
if (r) {
func.call(me, r, child);
if (child.children_ && recursive !== false) {
traverse(child, func);
}
}
}
}
},
spec, suite;
if (id.search("suite") !== -1) {
suite = this.getSuite(id.replace("suite-", ""));
traverse(suite, fn);
} else {
spec = this.getSpec(id.replace("spec-", ""));
traverse(spec, fn);
}
};
/**
* Collapse click handler.
*/
Test.panel.TreeGrid.prototype.onCollapse = function(el) {
el = el.parentNode;
jasmine.Dom.setCls(el.childNodes[0], "expand");
if (jasmine.Dom.hasCls(el, "suite")) {
this.onEachRow(el.parentNode, function(row, o) {
var childNode = row.childNodes[1],
icon = childNode.childNodes[0],
content = childNode.childNodes[2];
row.style.display = "none";
if (jasmine.Dom.hasCls(icon, "collapse")) {
jasmine.Dom.setCls(icon, "expand");
}
if (o.suite) {
content.style.display = "none";
}
});
} else {
el.childNodes[2].style.display = "none";
}
};
/**
* Expand click handler.
*/
Test.panel.TreeGrid.prototype.onExpand = function(el) {
el = el.parentNode;
jasmine.Dom.setCls(el.childNodes[0], "collapse");
if (jasmine.Dom.hasCls(el, "suite")) {
this.onEachRow(el.parentNode, function(row, o) {
row.style.display = "block";
}, false);
} else {
el.childNodes[2].style.display = "block";
}
};
/**
* Row click click handler.
*/
Test.panel.TreeGrid.prototype.onRowClick = function(el) {
var rows = el.parentNode.childNodes,
length = rows.length,
id, i;
for (i = 0; i < length; i++) {
jasmine.Dom.removeCls(rows[i], "selected");
}
jasmine.Dom.addCls(el, "row selected");
id = el.childNodes[1].id;
if (id.search("spec") !== -1) {
this.tabPanel.setSpec(this.getSpec(id.replace("spec-", "")));
}
if (id.search("suite") !== -1) {
this.tabPanel.setSuite(this.getSuite(id.replace("suite-", "")));
}
};
/**
* Creates row dom element.
* @param {Boolean} hide Sets the row visibility.
* @param {jasmine.Suite/jasmine.Spec} The suite or the spec.
* @return {HTMLElement} The row.
*/
Test.panel.TreeGrid.prototype.createRow = function(hide, o) {
var row = this.body.appendChild(new jasmine.Dom({
tag: "div",
cls: "row",
style: {
display: hide ? "none" : "block"
},
children: [{
cls: "checkbox-col",
children: [{
tag: "input",
cls: "select-checkbox",
type: "checkbox"
}]
}]
}));
if (Test.Options.isChecked(o)) {
row.childNodes[0].childNodes[0].checked = true;
}
return row;
};
/**
* Resizer
*/
/**
* MouseDown event listener. (resizing starts)
*/
Test.panel.TreeGrid.prototype.onMouseDown = function(event) {
var el;
event = event || window.event;
el = event.target || event.srcElement;
if (jasmine.Dom.hasCls(el, "resizer")) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
this.pageY = event.pageY || event.clientY;
this.startHeight = this.tabPanel.el.clientHeight;
document.body.style.cursor = "row-resize";
}
};
/**
* MouseDown event listener. (resize in progress)
*/
Test.panel.TreeGrid.prototype.onMouseMove = function(event) {
var el, diff;
if (this.pageY) {
event = event || window.event;
el = event.target || event.srcElement;
diff = Math.max(200, this.startHeight - ((event.pageY || event.clientY)- this.pageY));
diff = Math.min(diff, document.body.clientHeight - 200);
this.tabPanel.resize(diff);
this.options.resizer = diff;
this.resizeBody();
}
};
/**
* MouseUp event listener. (resize ends)
*/
Test.panel.TreeGrid.prototype.onMouseUp = function(event) {
document.body.style.cursor = "auto";
delete this.pageY;
};
/**
* Returns treegrid innerHeight.
* @return {Number} The innerHeight.
*/
Test.panel.TreeGrid.prototype.getInnerHeight = function() {
return (window.innerHeight || document.documentElement.clientHeight) - this.header.offsetTop * 2;
};
/**
* Resizes treegrid.
*/
Test.panel.TreeGrid.prototype.resizeBody = function() {
var height = this.getInnerHeight();
height -= this.resizer.offsetHeight + this.tabPanel.el.offsetHeight + this.header.offsetHeight;
height -= 2;
height = Math.max(30, height);
this.body.style.height = height + 'px';
};
/**
* End of Resizer
*/
/**
* Renders specs results.
* @param {jasmine.Spec} spec The spec.
* @return {HTMLElement} The spec results dom element.
*/
Test.panel.TreeGrid.prototype.renderSpecResults = function(spec) {
var resultItems = spec.results().getItems(),
length = resultItems.length,
resultsEl,
resultEl,
result,
i;
resultsEl = new jasmine.Dom({
cls: "results"
});
for (i = 0; i < length; i++) {
result = resultItems[i];
if (result.type === "expect" && result.passed) {
if (!result.passed()) {
resultEl = this.renderFailedResult(result);
} else {
resultEl = this.renderPassedResult(result);
}
if (i === 0) {
jasmine.Dom.addCls(resultEl, "first");
}
resultsEl.appendChild(resultEl);
if (result.error) {
break;
}
}
}
return resultsEl;
};
/**
* Renders failed spec result.
* @param {Object} result The spec result.
* @return {HTMLElement} The spec result message HTMLElement
*/
Test.panel.TreeGrid.prototype.renderFailedResult = function(result) {
var message = result.message,
children;
children = [{
cls: "prettyPrint",
html: jasmine.util.htmlEscape(message)
}];
return new jasmine.Dom({
cls: "resultMessage fail",
children: children
});
};
/**
* Renders failed spec result.
* @param {Object} result The spec result.
* @return {HTMLElement} The spec result message HTMLElement
*/
Test.panel.TreeGrid.prototype.renderPassedResult = function(result) {
var children = [{
cls: "prettyPrint",
html: "Actual: " + jasmine.pp(result.actual) + "\nExpected: " + jasmine.pp(result.expected) + "\nMatcher: " + result.matcherName + "."
}];
return new jasmine.Dom({
cls: "resultMessage pass",
children: children
});
};
/**
* Returns tabPanel console.
*/
Test.panel.TreeGrid.prototype.getInfoPanel = function() {
return this.tabPanel.children[0];
};
/**
* Print a message into info console.
* @param {String} message The message.
* @param {String} cls (optional) an extra cls to add to the message.
*/
Test.panel.TreeGrid.prototype.log = function(message, cls) {
this.getInfoPanel().log(message, cls);
};
/**
* Sets statubar message, this method can also add a className.
* @param {String} message The message.
* @param {String} cls The className (optional).
*/
Test.panel.TreeGrid.prototype.setStatus = function(message, cls) {
jasmine.Dom.setHTML(this.statusMessage, message);
if (cls) {
jasmine.Dom.addCls(this.statusMessage, cls);
}
};/**
* @class Test.Reporter
* The Sencha Unit Tests Reporter
*/
Test.Reporter = function(config) {
config = config || {};
this.options = Test.Options.get();
this.runnedSpecsCount = 0;
this.failedSpecsCount = 0;
this.disabledSpecsCount = 0;
this.optionCheckBoxesEl = {};
this.treeGrid = new Test.panel.TreeGrid({});
};
/**
* Called before runner execution.
* @param {jasmine.Runner} runner The Jasmine Runner
*/
Test.Reporter.prototype.reportRunnerStarting = function(runner) {
this.runner = runner;
this.startedAt = new Date();
if (Test.BadGlobals) {
Test.BadGlobals.setup();
}
this.logger = this.treeGrid;
this.log(">> Started at " + this.startedAt.toString(), "info");
if (!this.options.remote) {
this.log(">> Warning! Because you access TestReporter locally, stack trace report isn't available.", "warning");
}
this.runner.filter(this.options.suites, this.options.specs);
if (Test.BadGlobals) {
Test.BadGlobals.report(this.logger);
}
};
/**
* Called after Jasmine runner execution ends.
* @param {jasmine.Runner} runner The Jasmine Runner
*/
Test.Reporter.prototype.reportRunnerResults = function(runner) {
Test.jsCoverage.updateTotal();
this.renderResults(runner);
};
/**
* Called before spec execution.
* @param {jasmine.Runner} suite The Jasmine spec
*/
Test.Reporter.prototype.reportSuiteStarting = function(suite) {
if (this.options.showTimings) {
suite.startedAt = new Date();
}
if (Test.jsCoverage.isEnabled()) {
Test.jsCoverage.add(suite);
}
};
/**
* Called after suite execution ends.
* @param {jasmine.Runner} suite A Jasmine suite
*/
Test.Reporter.prototype.reportSuiteResults = function(suite) {
var suiteEl = this.treeGrid ? this.treeGrid.suitesEls[suite.id] : undefined,
status;
if (suite.isEnabled()) {
if (this.options.showTimings) {
suite.time = (((new Date()).getTime() - suite.startedAt.getTime())/ 1000).toFixed(3);
}
Test.jsCoverage.update(suite);
if (!suite.parentSuite && Test.BadGlobals) {
Test.BadGlobals.report(this.logger, suite);
}
if (this.treeGrid && this.options.showPassed && !suiteEl) {
suiteEl = this.treeGrid.addSuite(suite);
}
if (suiteEl) {
status = suite.results().passed() ? "passed" : "failed";
jasmine.Dom.addCls(suiteEl, status);
jasmine.Dom.addCls(suiteEl.parentNode, status);
if (Test.jsCoverage.isEnabled()) {
this.treeGrid.updateSuiteEl(suite, Test.jsCoverage.getSuiteCoverage(suite));
}
if (suite.time) {
this.treeGrid.updateSuiteEl(suite, " (" + suite.time + "s)");
}
}
} else if (this.treeGrid && this.options.showDisabled && !suiteEl) {
this.treeGrid.addSuite(suite);
}
};
/**
* Called before spec execution.
* @param {jasmine.Runner} suite The Jasmine spec
*/
Test.Reporter.prototype.reportSpecStarting = function(spec) {
this.currentSpec = spec;
if (spec.isEnabled()) {
if (this.options.showTimings) {
spec.startedAt = new Date();
}
this.treeGrid.setStatus("Running: " + jasmine.util.htmlEscape(spec.getFullName()));
}
};
/**
* Called after spec execution.
* @param {jasmine.Runner} suite The Jasmine spec
*/
Test.Reporter.prototype.reportSpecResults = function(spec) {
var results, status;
if (spec.isEnabled()) {
if (this.options.showTimings) {
spec.time = (((new Date()).getTime() - spec.startedAt.getTime())/ 1000).toFixed(3);
}
results = spec.results();
status = results.passed() ? "passed" : "failed";
if(status === "failed") {
this.failedSpecsCount = this.failedSpecsCount + 1;
}
if ((status === "failed" || this.options.showPassed) && spec.isEnabled() && this.treeGrid) {
this.treeGrid.addSpec(spec);
}
Test.SandBox.save(spec);
this.runnedSpecsCount = this.runnedSpecsCount + 1;
} else {
this.disabledSpecsCount = this.disabledSpecsCount + 1;
if (this.treeGrid && this.options.showDisabled) {
this.treeGrid.addSpec(spec);
}
}
};
/**
* Updates runner message with failed and passed specs
* @param {jasmine.Runner} runner The jasmine runner.
*/
Test.Reporter.prototype.renderResults = function(runner) {
var cls = (this.failedSpecsCount > 0) ? "failed" : "passed",
runTime,
message;
runTime = (new Date().getTime() - this.startedAt.getTime()) / 1000;
message = this.runnedSpecsCount + " spec" +
(this.runnedSpecsCount === 1 ? "" : "s" ) + " ran, " +
this.failedSpecsCount + " failure" +
(this.failedSpecsCount === 1 ? "" : "s") +
" and " + this.disabledSpecsCount + " disabled";
message += " in " + runTime + "s";
message += Test.jsCoverage.getTotal() + ".";
if (this.treeGrid) {
if (Test.SandBox.getWin()._$jscoverage) {
this.treeGrid.tabPanel.addCoverageSummary();
}
this.treeGrid.setStatus(message, cls);
}
this.log(">> Finished at " + new Date().toString(), "info");
};
Test.Reporter.prototype.log = function() {
if (this.options.verbose || arguments.length === 2) {
this.logger.log.apply(this.logger, arguments);
}
};
Test.Reporter.prototype.getIframeContainer = function() {
if (this.treeGrid) {
return this.treeGrid.tabPanel.children[1].el;
}
return document.body;
};