microsoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemail
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.
3623 lines
99 KiB
3623 lines
99 KiB
9 years ago
|
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, '&')
|
||
|
.replace(/</g, '<')
|
||
|
.replace(/>/g, '>');
|
||
|
};
|
||
|
|
||
|
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;
|
||
|
};
|