extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,798 lines (1,571 loc) • 102 kB
JavaScript
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 = sou