UNPKG

extjs-gpl

Version:

GPL licensed version of Sencha Ext JS

1,798 lines (1,571 loc) 102 kB
var Test = {};var isCommonJS = typeof window == "undefined" && typeof exports == "object"; /** * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. * * @namespace */ var jasmine = {}; if (isCommonJS) exports.jasmine = jasmine; /** * @private */ jasmine.unimplementedMethod_ = function() { throw new Error("unimplemented method"); }; /** * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just * a plain old variable and may be redefined by somebody else. * * @private */ jasmine.undefined = jasmine.___undefined___; /** * Show diagnostic messages in the console if set to true * */ jasmine.VERBOSE = false; /** * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. * */ jasmine.DEFAULT_UPDATE_INTERVAL = 250; /** * Maximum levels of nesting that will be included when an object is pretty-printed */ jasmine.MAX_PRETTY_PRINT_DEPTH = 40; /** * Default timeout interval in milliseconds for waitsFor() blocks. */ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; /** * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. * Set to false to let the exception bubble up in the browser. * */ jasmine.CATCH_EXCEPTIONS = true; jasmine.getGlobal = function() { function getGlobal() { return this; } return getGlobal(); }; /** * Allows for bound functions to be compared. Internal use only. * * @ignore * @private * @param base {Object} bound 'this' for the function * @param name {Function} function to find */ jasmine.bindOriginal_ = function(base, name) { var original = base[name]; if (original.apply) { return function() { return original.apply(base, arguments); }; } else { // IE support return jasmine.getGlobal()[name]; } }; jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); jasmine.MessageResult = function(values) { this.type = 'log'; this.values = values; this.trace = new Error(); // todo: test better }; jasmine.MessageResult.prototype.toString = function() { var text = ""; for (var i = 0; i < this.values.length; i++) { if (i > 0) text += " "; if (jasmine.isString_(this.values[i])) { text += this.values[i]; } else { text += jasmine.pp(this.values[i]); } } return text; }; jasmine.ExpectationResult = function(params) { this.type = 'expect'; this.matcherName = params.matcherName; this.passed_ = params.passed; this.expected = params.expected; this.actual = params.actual; this.message = this.passed_ ? 'Passed.' : params.message; var trace = (params.trace || new Error(this.message)); this.trace = this.passed_ ? '' : trace; }; jasmine.ExpectationResult.prototype.toString = function () { return this.message; }; jasmine.ExpectationResult.prototype.passed = function () { return this.passed_; }; /** * Getter for the Jasmine environment. Ensures one gets created */ jasmine.getEnv = function() { var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); return env; }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isArray_ = function(value) { return jasmine.isA_("Array", value); }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isString_ = function(value) { return jasmine.isA_("String", value); }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isNumber_ = function(value) { return jasmine.isA_("Number", value); }; /** * @ignore * @private * @param {String} typeName * @param value * @returns {Boolean} */ jasmine.isA_ = function(typeName, value) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; }; /** * Pretty printer for expecations. Takes any object and turns it into a human-readable string. * * @param value {Object} an object to be outputted * @returns {String} */ jasmine.pp = function(value) { var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); stringPrettyPrinter.format(value); return stringPrettyPrinter.string; }; /** * Returns true if the object is a DOM Node. * * @param {Object} obj object to check * @returns {Boolean} */ jasmine.isDomNode = function(obj) { return obj.nodeType > 0; }; /** * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. * * @example * // don't care about which function is passed in, as long as it's a function * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); * * @param {Class} clazz * @returns matchable object of the type clazz */ jasmine.any = function(clazz) { return new jasmine.Matchers.Any(clazz); }; /** * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the * attributes on the object. * * @example * // don't care about any other attributes than foo. * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); * * @param sample {Object} sample * @returns matchable object for the sample */ jasmine.objectContaining = function (sample) { return new jasmine.Matchers.ObjectContaining(sample); }; /** * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. * * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine * expectation syntax. Spies can be checked if they were called or not and what the calling params were. * * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). * * Spies are torn down at the end of every spec. * * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. * * @example * // a stub * var myStub = jasmine.createSpy('myStub'); // can be used anywhere * * // spy example * var foo = { * not: function(bool) { return !bool; } * } * * // actual foo.not will not be called, execution stops * spyOn(foo, 'not'); // foo.not spied upon, execution will continue to implementation * spyOn(foo, 'not').andCallThrough(); * * // fake example * var foo = { * not: function(bool) { return !bool; } * } * * // foo.not(val) will return val * spyOn(foo, 'not').andCallFake(function(value) {return value;}); * * // mock example * foo.not(7 == 7); * expect(foo.not).toHaveBeenCalled(); * expect(foo.not).toHaveBeenCalledWith(true); * * @constructor * @see spyOn, jasmine.createSpy, jasmine.createSpyObj * @param {String} name */ jasmine.Spy = function(name) { /** * The name of the spy, if provided. */ this.identity = name || 'unknown'; /** * Is this Object a spy? */ this.isSpy = true; /** * The actual function this spy stubs. */ this.plan = function() { }; /** * Tracking of the most recent call to the spy. * @example * var mySpy = jasmine.createSpy('foo'); * mySpy(1, 2); * mySpy.mostRecentCall.args = [1, 2]; */ this.mostRecentCall = {}; /** * Holds arguments for each call to the spy, indexed by call count * @example * var mySpy = jasmine.createSpy('foo'); * mySpy(1, 2); * mySpy(7, 8); * mySpy.mostRecentCall.args = [7, 8]; * mySpy.argsForCall[0] = [1, 2]; * mySpy.argsForCall[1] = [7, 8]; */ this.argsForCall = []; this.calls = []; }; /** * Tells a spy to call through to the actual implemenatation. * * @example * var foo = { * bar: function() { // do some stuff } * } * * // defining a spy on an existing property: foo.bar * spyOn(foo, 'bar').andCallThrough(); */ jasmine.Spy.prototype.andCallThrough = function() { this.plan = this.originalValue; return this; }; /** * For setting the return value of a spy. * * @example * // defining a spy from scratch: foo() returns 'baz' * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); * * // defining a spy on an existing property: foo.bar() returns 'baz' * spyOn(foo, 'bar').andReturn('baz'); * * @param {Object} value */ jasmine.Spy.prototype.andReturn = function(value) { this.plan = function() { return value; }; return this; }; /** * For throwing an exception when a spy is called. * * @example * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); * * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' * spyOn(foo, 'bar').andThrow('baz'); * * @param {String} exceptionMsg */ jasmine.Spy.prototype.andThrow = function(exceptionMsg) { this.plan = function() { throw exceptionMsg; }; return this; }; /** * Calls an alternate implementation when a spy is called. * * @example * var baz = function() { * // do some stuff, return something * } * // defining a spy from scratch: foo() calls the function baz * var foo = jasmine.createSpy('spy on foo').andCall(baz); * * // defining a spy on an existing property: foo.bar() calls an anonymnous function * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); * * @param {Function} fakeFunc */ jasmine.Spy.prototype.andCallFake = function(fakeFunc) { this.plan = fakeFunc; return this; }; /** * Resets all of a spy's the tracking variables so that it can be used again. * * @example * spyOn(foo, 'bar'); * * foo.bar(); * * expect(foo.bar.callCount).toEqual(1); * * foo.bar.reset(); * * expect(foo.bar.callCount).toEqual(0); */ jasmine.Spy.prototype.reset = function() { this.wasCalled = false; this.callCount = 0; this.argsForCall = []; this.calls = []; this.mostRecentCall = {}; }; jasmine.createSpy = function(name) { var spyObj = function() { spyObj.wasCalled = true; spyObj.callCount++; var args = jasmine.util.argsToArray(arguments); spyObj.mostRecentCall.object = this; spyObj.mostRecentCall.args = args; spyObj.argsForCall.push(args); spyObj.calls.push({object: this, args: args}); return spyObj.plan.apply(this, arguments); }; var spy = new jasmine.Spy(name); for (var prop in spy) { spyObj[prop] = spy[prop]; } spyObj.reset(); return spyObj; }; /** * Determines whether an object is a spy. * * @param {jasmine.Spy|Object} putativeSpy * @returns {Boolean} */ jasmine.isSpy = function(putativeSpy) { return putativeSpy && putativeSpy.isSpy; }; /** * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something * large in one call. * * @param {String} baseName name of spy class * @param {Array} methodNames array of names of methods to make spies */ jasmine.createSpyObj = function(baseName, methodNames) { if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); } var obj = {}; for (var i = 0; i < methodNames.length; i++) { obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); } return obj; }; /** * All parameters are pretty-printed and concatenated together, then written to the current spec's output. * * Be careful not to leave calls to <code>jasmine.log</code> in production code. */ jasmine.log = function() { var spec = jasmine.getEnv().currentSpec; spec.log.apply(spec, arguments); }; /** * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. * * @example * // spy example * var foo = { * not: function(bool) { return !bool; } * } * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops * * @see jasmine.createSpy * @param obj * @param methodName * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods */ var spyOn = function(obj, methodName) { return jasmine.getEnv().currentSpec.spyOn(obj, methodName); }; if (isCommonJS) exports.spyOn = spyOn; /** * Creates a Jasmine spec that will be added to the current suite. * * // TODO: pending tests * * @example * it('should be true', function() { * expect(true).toEqual(true); * }); * * @param {String} desc description of this specification * @param {Function} func defines the preconditions and expectations of the spec */ var it = function(desc, func) { return jasmine.getEnv().it(desc, func); }; if (isCommonJS) exports.it = it; /** * Creates a <em>disabled</em> Jasmine spec. * * A convenience method that allows existing specs to be disabled temporarily during development. * * @param {String} desc description of this specification * @param {Function} func defines the preconditions and expectations of the spec */ var xit = function(desc, func) { return jasmine.getEnv().xit(desc, func); }; if (isCommonJS) exports.xit = xit; /** * Starts a chain for a Jasmine expectation. * * It is passed an Object that is the actual value and should chain to one of the many * jasmine.Matchers functions. * * @param {Object} actual Actual value to test against and expected value * @return {jasmine.Matchers} */ var expect = function(actual) { return jasmine.getEnv().currentSpec.expect(actual); }; if (isCommonJS) exports.expect = expect; /** * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. * * @param {Function} func Function that defines part of a jasmine spec. */ var runs = function(func) { jasmine.getEnv().currentSpec.runs(func); }; if (isCommonJS) exports.runs = runs; /** * Waits a fixed time period before moving to the next block. * * @deprecated Use waitsFor() instead * @param {Number} timeout milliseconds to wait */ var waits = function(timeout) { jasmine.getEnv().currentSpec.waits(timeout); }; if (isCommonJS) exports.waits = waits; /** * Waits for the latchFunction to return true before proceeding to the next block. * * @param {Function} latchFunction * @param {String} optional_timeoutMessage * @param {Number} optional_timeout */ var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); }; if (isCommonJS) exports.waitsFor = waitsFor; /** * A function that is called before each spec in a suite. * * Used for spec setup, including validating assumptions. * * @param {Function} beforeEachFunction */ var beforeEach = function(beforeEachFunction) { jasmine.getEnv().beforeEach(beforeEachFunction); }; if (isCommonJS) exports.beforeEach = beforeEach; /** * A function that is called after each spec in a suite. * * Used for restoring any state that is hijacked during spec execution. * * @param {Function} afterEachFunction */ var afterEach = function(afterEachFunction) { jasmine.getEnv().afterEach(afterEachFunction); }; if (isCommonJS) exports.afterEach = afterEach; /** * Defines a suite of specifications. * * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization * of setup in some tests. * * @example * // TODO: a simple suite * * // TODO: a simple suite with a nested describe block * * @param {String} description A string, usually the class under test. * @param {Function} specDefinitions function that defines several specs. */ var describe = function(description, specDefinitions) { return jasmine.getEnv().describe(description, specDefinitions); }; if (isCommonJS) exports.describe = describe; /** * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. * * @param {String} description A string, usually the class under test. * @param {Function} specDefinitions function that defines several specs. */ var xdescribe = function(description, specDefinitions) { return jasmine.getEnv().xdescribe(description, specDefinitions); }; if (isCommonJS) exports.xdescribe = xdescribe; // Provide the XMLHttpRequest class for IE 5.x-6.x: jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { function tryIt(f) { try { return f(); } catch(e) { } return null; } var xhr = tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }) || tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }) || tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP"); }) || tryIt(function() { return new ActiveXObject("Microsoft.XMLHTTP"); }); if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); return xhr; } : XMLHttpRequest; /** * @namespace */ jasmine.util = {}; /** * Declare that a child class inherit it's prototype from the parent class. * * @private * @param {Function} childClass * @param {Function} parentClass */ jasmine.util.inherit = function(childClass, parentClass) { /** * @private */ var subclass = function() { }; subclass.prototype = parentClass.prototype; childClass.prototype = new subclass(); }; jasmine.util.formatException = function(e) { var lineNumber; if (e.line) { lineNumber = e.line; } else if (e.lineNumber) { lineNumber = e.lineNumber; } var file; if (e.sourceURL) { file = e.sourceURL; } else if (e.fileName) { file = e.fileName; } var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); if (file && lineNumber) { message += ' in ' + file + ' (line ' + lineNumber + ')'; } return message; }; jasmine.util.htmlEscape = function(str) { if (!str) return str; return str.replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;'); }; jasmine.util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); return arrayOfArgs; }; jasmine.util.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; /** * Base class for pretty printing for expectation results. */ jasmine.PrettyPrinter = function() { this.ppNestLevel_ = 0; }; /** * Formats a value in a nice, human-readable string. * * @param value */ jasmine.PrettyPrinter.prototype.format = function(value) { this.ppNestLevel_++; try { if (value === jasmine.undefined) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === jasmine.getGlobal()) { this.emitScalar('<global>'); } else if (value.jasmineToString) { this.emitScalar(value.jasmineToString()); } else if (typeof value === 'string') { this.emitString(value); } else if (jasmine.isSpy(value)) { this.emitScalar("spy on " + value.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (value.__Jasmine_been_here_before__) { this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); } else if (jasmine.isArray_(value) || typeof value == 'object') { value.__Jasmine_been_here_before__ = true; if (jasmine.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } delete value.__Jasmine_been_here_before__; } else { this.emitScalar(value.toString()); } } finally { this.ppNestLevel_--; } }; jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { for (var property in obj) { if (!obj.hasOwnProperty(property)) continue; if (property == '__Jasmine_been_here_before__') continue; fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && obj.__lookupGetter__(property) !== null) : false); } }; jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; jasmine.StringPrettyPrinter = function() { jasmine.PrettyPrinter.call(this); this.string = ''; }; jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { this.append(value); }; jasmine.StringPrettyPrinter.prototype.emitString = function(value) { this.append("'" + value + "'"); }; jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { this.append("Array"); return; } this.append('[ '); for (var i = 0; i < array.length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } this.append(' ]'); }; jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { this.append("Object"); return; } var self = this; this.append('{ '); var first = true; this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.append(property); self.append(' : '); if (isGetter) { self.append('<getter>'); } else { self.format(obj[property]); } }); this.append(' }'); }; jasmine.StringPrettyPrinter.prototype.append = function(value) { this.string += value; }; /** * Formats a value in a nice, human-readable string. * * @param value */ jasmine.PrettyPrinter.prototype.format = function(value) { if (this.ppNestLevel_ > 40) { throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); } this.ppNestLevel_++; try { if (value === jasmine.undefined) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === jasmine.getGlobal()) { this.emitScalar('<global>'); } else if (value.expectedClass) { //override of value instanceof jasmine.Matchers.Any this.emitScalar(value.toString()); } else if (typeof value === 'string') { this.emitString(value); } else if (jasmine.isSpy(value)) { this.emitScalar("spy on " + value.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (value.__Jasmine_been_here_before__) { this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); } else if (jasmine.isArray_(value) || typeof value == 'object') { value.__Jasmine_been_here_before__ = true; if (jasmine.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } delete value.__Jasmine_been_here_before__; } else { this.emitScalar(value.toString()); } } catch (e) { } finally { this.ppNestLevel_--; } }; // Extend: creates whitespaces indent jasmine.StringPrettyPrinter.prototype.getIndent = function () { var whiteSpaces = "", i; for (i = 0; i < this.ws; i++) { whiteSpaces += " "; } return whiteSpaces; }; // Override: pre-format object jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { var self = this, first = true, indent; this.append('{\n'); if(!this.ws) { this.ws = 0; } this.ws += 4; indent = this.getIndent(); var i = 0; this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(',\n'); } self.append(indent + property); self.append(' : '); if (isGetter) { self.append('<getter>'); } else { if (typeof obj[property] !== "object") { self.format(obj[property]); } else { self.append("<Object>"); } } }); this.ws -= 4; indent = this.getIndent(); this.append(indent + '\n'+ indent +'}'); }; /** * Basic browsers detection. */ jasmine.browser = {}; jasmine.browser.isIE = !!window.ActiveXObject; jasmine.browser.isIE6 = jasmine.browser.isIE && !window.XMLHttpRequest; jasmine.browser.isIE7 = jasmine.browser.isIE && !!window.XMLHttpRequest && !document.documentMode; jasmine.browser.isIE8 = jasmine.browser.isIE && !!window.XMLHttpRequest && !!document.documentMode && !window.performance; jasmine.browser.isIE9 = jasmine.browser.isIE && !!window.performance; jasmine.browser.isSafari3 = /safari/.test(navigator.userAgent.toLowerCase()) && /version\/3/.test(navigator.userAgent.toLowerCase()); jasmine.browser.isOpera = !!window.opera; jasmine.browser.isOpera11 = jasmine.browser.isOpera && parseInt(window.opera.version(), 10) > 10; jasmine.array = {}; /** * Checks whether or not the specified item exists in the array. * Array.prototype.indexOf is missing in Internet Explorer, unfortunately. * We always have to use this static method instead for consistency * @param {Array} array The array to check * @param {Mixed} item The item to look for * @param {Number} from (Optional) The index at which to begin the search * @return {Number} The index of item in the array (or -1 if it is not found) */ jasmine.array.indexOf = function(array, item, from){ if (array.indexOf) { return array.indexOf(item, from); } var i, length = array.length; for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){ if (array[i] === item) { return i; } } return -1; }; /** * Removes the specified item from the array. If the item is not found nothing happens. * @param {Array} array The array * @param {Mixed} item The item to remove * @return {Array} The passed array itself */ jasmine.array.remove = function(array, item) { var index = this.indexOf(array, item); if (index !== -1) { array.splice(index, 1); } return array; };/** * Creates an HTMLElement. * @param {Object/HTMLElement} config Ext DomHelper style element config object. * If no tag is specified (e.g., {tag:'input'}) then a div will be automatically generated with the specified attributes. * @return {HTMLElement} The created HTMLElement */ jasmine.Dom = function(config) { var element, children, length, child, i, property; config = config || {}; if (config.tagName) { return config; } element = document.createElement(config.tag || "div"); children = config.children || []; length = children.length; delete config.tag; for (i = 0; i < length; i++) { child = children[i]; element.appendChild(new jasmine.Dom(child)); } delete config.children; if (config.cls) { jasmine.Dom.setCls(element, config.cls); delete config.cls; } if (config.html) { jasmine.Dom.setHTML(element, config.html); delete config.html; } if (config.style) { jasmine.Dom.setStyle(element, config.style); delete config.style; } for (property in config) { if (!config.hasOwnProperty(property)) { continue; } element[property] = config[property]; } return element; }; /** * Adds className to an HTMLElement. * @param {HTMLElement} element The HTMLElement * @param {String} cls The className string */ jasmine.Dom.addCls = function (element, cls) { var split, length, i; if (!element.className) { jasmine.Dom.setCls(element, cls); return; } split = element.className.split(" "); length = split.length; for (i = 0; i < length; i++) { if (split[i] == cls) { return; } } element.className = element.className + " " + cls; }; /** * Removes className to HTMLElement. * @param {HTMLElement} element The HTMLElement * @param {String} cls The className string */ jasmine.Dom.removeCls = function(element, cls) { var split, length, classArray, i; if (!element.className) { return; } classArray = []; split = element.className.split(" "); length = split.length; for (i = 0; i < length; i++) { if (split[i] !== cls) { classArray.push(split[i]); } } element.className = classArray.join(" "); }; /** * Checks if a dom element has a className. * @param {HTMLElement} element The HTMLElement * @param {String} cls The className string * @return {Boolean} */ jasmine.Dom.hasCls = function(element, cls) { var split, length, classArray, i; if (!element.className) { return; } split = element.className.split(" "); length = split.length; for (i = 0; i < length; i++) { if (split[i] === cls) { return true; } } return false; }; /** * Sets HTMLElement className. * @param {HTMLElement} element The HTMLElement * @param {String} cls The className string */ jasmine.Dom.setCls = function(element, cls) { element.className = cls; }; /** * Sets HTMLElement innerHTML * @param {HTMLElement} element The HTMLElement * @param {String} html The innerHTML text */ jasmine.Dom.setHTML = function(element, html) { element.innerHTML = html; }; /** * Sets HTMLElement style * @param {HTMLElement} element The HTMLElement * @param {String} style The style property to set */ jasmine.Dom.setStyle = function(element, style) { var property; for (property in style) { if (style.hasOwnProperty(property)) { element.style[property] = style[property]; } } }; Test.OptionsImpl = function() { this.optionCheckBoxesEl = {}; this.options = this.urlDecode(window.location.search.substring(1)); this.options.remote = window.location.toString().search("http:") !== -1; this.startAutoReloadTask(); }; Test.OptionsImpl.prototype.get = function() { return this.options; }; /** * Takes an object and converts it to an encoded URL. * @param {Object} o The object to encode * @return {String} */ Test.OptionsImpl.prototype.urlEncode = function(object) { var buf = [], e = encodeURIComponent, value, property, length, i; for (property in object) { if(!object.hasOwnProperty(property)) { continue; } value = object[property]; if (jasmine.isArray_(value)) { length = value.length; for (i = 0; i < length; i++) { buf.push(property + '=' + e(value[i])); } } else { buf.push(property + '=' + e(value)); } } return buf.join('&'); }; Test.hashString = function (s, hash) { hash = hash || 0; // see http://www.cse.yorku.ca/~oz/hash.html for (var c, i = 0, n = s.length; i < n; ++i) { c = s.charCodeAt(i); hash = c + (hash << 6) + (hash << 16) - hash; } return hash; }; /** * Takes an encoded URL and and converts it to an object. Example: * @param {String} string * @return {Object} A literal with members */ Test.OptionsImpl.prototype.urlDecode = function(string) { var obj = {}, pairs, d, name, value, pair, i, length; if (string != "") { pairs = string.split('&'); d = decodeURIComponent; length = pairs.length; for (i = 0; i < length; i++) { pair = pairs[i].split('='); name = d(pair[0]); value = d(pair[1]); obj[name] = !obj[name] ? value : [].concat(obj[name]).concat(value); } } function parseStringOrId (str) { var id = parseInt(str, 10); if (String(id) !== str) { id = Test.hashString(str); } return id; } if (obj.specs) { obj.specs = jasmine.isArray_(obj.specs) ? obj.specs : [obj.specs]; length = obj.specs.length; for (i = 0; i < length; i++) { obj.specs[i] = parseStringOrId(obj.specs[i]); } } else { obj.specs = []; } if (obj.suites) { obj.suites = jasmine.isArray_(obj.suites) ? obj.suites : [obj.suites]; length = obj.suites.length; for (i = 0; i < length; i++) { obj.suites[i] = parseStringOrId(obj.suites[i]); } } else { obj.suites = []; } return obj; }; /** * Renders option checkbox and label. * @param {String} name The option name. * @param {String} labelText The label text. * @return {HTMLElement} The option HTMLElement */ Test.OptionsImpl.prototype.renderCheckbox = function(name, labelText) { var me = this, checkbox = new jasmine.Dom({ tag: "input", cls: "option " + name, type: "checkbox", onclick: function() { me.onCheckboxClick.apply(me, arguments); } }); me.optionCheckBoxesEl[name] = checkbox; return new jasmine.Dom({ tag: "span", cls: "show", children: [checkbox,{ tag: "label", html: labelText }] }); }; /** * Checks options checkboxs if needed. */ Test.OptionsImpl.prototype.check = function() { var property, checkbox; for (property in this.options) { if (!this.options.hasOwnProperty(property)) { continue; } checkbox = this.optionCheckBoxesEl[property]; if (checkbox) { checkbox.checked = this.options[property]; } } }; /** * Options checkbox check/uncked handler. * @param {HTMLElement} el The checkbox HTMLElement */ Test.OptionsImpl.prototype.onCheckboxClick = function(event) { var el, opt, row, length, i; event = event || window.event; el = event.target || event.srcElement; opt = el.className.split(" ")[1]; if (el.checked) { this.options[opt] = true; } else { delete this.options[opt]; } }; /** * Reloads current page with reporter options. */ Test.OptionsImpl.prototype.reloadWindow = function(reset) { if (reset) { this.options.specs = []; this.options.suites = []; } window.location.search = this.urlEncode(this.options); }; /** * Starts autoReload task. */ Test.OptionsImpl.prototype.startAutoReloadTask = function() { var me = this; if (me.options.autoReload) { var interval = setInterval(function() { if (Test.SandBox.isRunning()) { clearInterval(interval); setTimeout(function() { me.reloadWindow(); }, 2000); } }, 1500); } }; Test.OptionsImpl.prototype.isChecked = function(o) { var specs = this.options.specs, suites = this.options.suites, id = o.id; if (o.suite) { return specs && jasmine.array.indexOf(specs, id) !== -1; } else { return suites && jasmine.array.indexOf(suites, id) !== -1; } return false; }; Test.Options = new Test.OptionsImpl();Test.SandBoxImpl = function(){}; Test.SandBoxImpl.prototype.domReady = function(fn) { if (document.addEventListener) { window.addEventListener('load', fn, false); } else { window.attachEvent('onload', fn, false); } }; Test.SandBoxImpl.prototype.setup = function(config) { var me = this; me.requires = config.requires; me.domReady(function() { me.reporter = new Test.Reporter(); me.createIframe(); }); }; Test.SandBoxImpl.prototype.createIframe = function() { var me = this, iframe, win, doc; me.options = Test.Options.get(); var src = me.options.quirksMode ? 'iframe-quirks.html?loadSpecs=true' : 'iframe.html?loadSpecs=true'; src += '&compiled=' + !!me.options.compiled; if (me.options.specsset) { src += '&specsset=' + me.options.specsset; } iframe = new jasmine.Dom({ tag: "iframe", cls: "sandboxIframe", name: "sandbox", frameBorder: 0, src: src }); me.reporter.getIframeContainer().appendChild(iframe); win = iframe.contentWindow || window.frames[iframe.name]; doc = iframe.contentDocument || win.document; this.iframe = iframe; this.win = win; this.doc = doc; }; Test.SandBoxImpl.prototype.getIframe = function() { return this.iframe; }; Test.SandBoxImpl.prototype.getWin = function() { return this.win; }; Test.SandBoxImpl.prototype.getDoc = function() { return this.doc; }; Test.SandBoxImpl.prototype.getBody = function() { return this.getDoc().body; }; Test.SandBoxImpl.prototype.getHead = function() { return this.getDoc().getElementsByTagName("head")[0]; }; Test.SandBoxImpl.prototype.save = function(spec) { var doc = this.getDoc(), sb = doc.createElement("div"), body = this.getBody(), children = body && body.childNodes || [], length = children.length, i = 0, child, lwas = this.lengthWas || (this.lengthWas = 0); if (!this.options || !this.options.disableBodyClean) { //this.clearComponents(); //this.clearDomElements(); } if (length != lwas) { if (!window.headless) { this.reporter.log(">> Warning the document.body dom element contains childNodes after spec execution !<br/>" + "Spec : " + jasmine.util.htmlEscape(spec.getFullName()) + ' <a href="?' + Test.Options.urlEncode({specs: [spec.id], suites:[], disableBodyClean: true}) + '">Load this spec only and disable body autoclean</a><br/>', "warning"); } else { this.reporter.log("Warning: " + spec.getFullName() + "doesn't clean properly the document.body."); } this.lengthWas = length; } }; Test.SandBoxImpl.prototype.clearDomElements = function() { var doc = this.getDoc(), bd = this.getBody(), children = bd.childNodes, length = children.length, i, child; if (!this.options.disableBodyClean) { for (i = 0; i < length; i++) { child = children[i]; if (child) { bd.removeChild(child); } } } } Test.SandBoxImpl.prototype.clearComponents = function() { var me = this, win = me.getWin(), comps, c, len, i; if(win.Ext && win.Ext.ComponentManager) { comps = win.Ext.ComponentManager.all.getArray(); len = comps.length; for(i=0; i<len; i++) { c = comps[i]; c.destroy(); } } }; Test.SandBoxImpl.prototype.isRunning = function() { return !this.getWin().jasmine.getEnv().currentRunner_.queue.isRunning(); }; Test.SandBoxImpl.prototype.iScope = function(o) { if (typeof o === "function") { o = "(" + o.toString() + ")();"; } return Test.SandBox.getWin().eval(o); }; Test.SandBox = new Test.SandBoxImpl(); var iScope = Test.SandBox.iScope; /** * @class Test.CodeHighLighter * A javascript simple source code higlighter and beautifier (optional). */ Test.CodeHighLighter = function(config) { /** * @cfg {String} source The source string to process. */ this.source = config.source; this.lineNumber = config.lineNumber; this.linesFromJsCoverage = config.linesFromJsCoverage; this.beautify = config.beautify || this.lineNumber === undefined; this.highLightCode = config.highLightCode === false ? false : true; this.matchedComments = []; this.matchedStrings = []; }; /** * Regular expressions. */ Test.CodeHighLighter.prototype.regExps = { strings: /"([^\\"\n]|\\.)*"|'([^\\'\n]|\\.)*'|"([^\\"\n]|\\\n)*"|'([^\\'\n]|\\\n)*'/gm, comments: /\/\/.*$|\/\*[\s\S]*?\*\//gm, operators: /([\+\-\*\/=\?!]{1,3}|[\-\+]{1,2})/g, numbers: /\b([0-9]+)\b/g, keywords: [/\b(break)\b/g, /\b(case)\b/g, /\b(catch)\b/g, /\b(continue)\b/g, /\b(default)\b/g, /\b(delete)\b/g, /\b(do)\b/g, /\b(else)\b/g, /\b(false)\b/g, /\b(for)\b/g, /\b(function)\b/g, /\b(if)\b/g, /\b(in)\b/g, /\b(instanceof)\b/g, /\b(new)\b/g, /\b(null)\b/g, /\b(return)\b/g, /\b(switch)\b/g, /\b(this)\b/g, /\b(throw)\b/g, /\b(true)\b/g, /\b(try)\b/g,/\b(typeof)\b/g, /\b(var)\b/g, /\b(while)\b/g, /\b(with)\b/g], commasInsideParenthesis: /\(([^\(\)\{\}])+\)/g, arrayWithOneElement: /\[\n([^,\]]*)\n\]/g, commaBracket: /,\n\s*\{/g, multipleWhiteSpaces: /(\s+)/g, semiColon: /;/g, comma: /,/g, openedBrackets: /([\{\[])/g, closedBrackets: /([\}\]])/g, emptyObject: /\{\n\s*\n\}/g, openedBracketsWithNewLine: /[\{\[]$/g, closedBracketsWithNewLine: /^\s*[\}\]]/g, unwantedNewLines: /\n([\n,;\)])/g, newLine: /\n/g, firstSpaces: /^(\s)+/ }; /** * Populates an array of matched objects. * @param {String} value The match result. * @param {Number} index The index of the match. * @param {Array} matchedObjects The array of matches to populate. * @param {String} css The css to apply to the match. * @return {Boolean} Returns <tt>true</tt> is the match is inside another. */ Test.CodeHighLighter.prototype.matchObjects = function(value, index, matchedObjects, css) { matchedObjects.push({ origValue: value, value: '<span class="jsHl'+ css +'">' + jasmine.util.htmlEscape(value).replace("$","$\b") + '</span>', start: index, end: index + value.length }); }; /** * Checks if a match is inside another matches. * @param {Object} matchedObject The checked match. * @param {Array} matchedOthers The array that contains other matches. * @return {Boolean} Returns <tt>true</tt> is the match is inside another. */ Test.CodeHighLighter.prototype.isInside = function(matchedObject, matchedOthers) { var start = matchedObject.start, end = matchedObject.end, length = matchedOthers.length, matchedOther, i; for (i = 0; i < length; i++) { matchedOther = matchedOthers[i]; if (matchedOther.start < start && start < matchedOther.end) { return true; } } return false; }; /** * This function get rid of any matches that are inside of other matches. * If a match isn't inside another it is replaced by a string in {@link #source} * in order to protect it from {@link #processOperatorsNumbersKeywords} replace tricks. * @param {Array} matchedObjects The array of matches to check. * @param {Array} matchedOthers The array that contains other matches. * @param {String} protect The replacement string */ Test.CodeHighLighter.prototype.fixOverlaps = function(matchedObjects, matchedOthers, protect) { var result = [], length = matchedObjects.length, matchedObject, i; for (i = 0; i < length; i++) { matchedObject = matchedObjects[i]; if (!this.isInside(matchedObject, matchedOthers)) { this.source = this.source.replace(matchedObject.origValue, protect); result.push(matchedObject); } } return result; }; /** * Replaces Strings and Comments in javascript source code. */ Test.CodeHighLighter.prototype.saveStringsAndComments = function() { var commentsRe = this.regExps.comments, stringsRe = this.regExps.strings, exec; while((exec = commentsRe.exec(this.source))) { this.matchObjects(exec[0], exec.index, this.matchedComments, "Comment"); } while((exec = stringsRe.exec(this.source))) { this.matchObjects(exec[0], exec.index, this.matchedStrings, "String"); } this.matchedComments = this.fixOverlaps(this.matchedComments, this.matchedStrings, "%%%%comment%%%%"); this.matchedStrings = this.fixOverlaps(this.matchedStrings, this.matchedComments, '%%%%string%%%%'); }; /** * Process strings and comments saved by {@link #saveStringsAndComments}. */ Test.CodeHighLighter.prototype.processStringsAndComments = function() { var matches = this.matchedComments, length = matches ? matches.length : 0, value, i; for (i = 0; i < length; i++) { value = matches[i].value; this.source = this.source.replace("%%%%comment%%%%", value); } matches = this.matchedStrings; length = matches ? matches.length : 0; for (i = 0; i < length; i++) { value = matches[i].value; this.source = this.source.replace('%%%%string%%%%', value); } }; /** * Highlight operators, numbers and keywords. */ Test.CodeHighLighter.prototype.processOperatorsNumbersKeywords = function() { var regexps = this.regExps, keywords = regexps.keywords, length = keywords.length, i; this.source = jasmine.util.htmlEscape(this.source).replace( regexps.operators, '<span class="jsHlOperator">$1</span>').replace( regexps.numbers, '<span class="jsHlNumber">$1</span>'); for (i = 0; i < length; i++) { this.source = this.source.replace(keywords[i], '<span class="jsHlKeyword">$1</span>'); } }; /** * Format and highligth javascript sources. * @return The HTML formatted and highlighted code */ Test.CodeHighLighter.prototype.process = function() { this.saveStringsAndComments(); if (this.beautify) { this.prepareIndent(); this.doIndent(); } this.processOperatorsNumbersKeywords(); this.processStringsAndComments(); return this.source; }; /** * Render sources with line numbers. * @return The HTML formatted and highlighted code */ Test.CodeHighLighter.prototype.renderJsSources = function() { var result = 'No code found.', linesFromJsCoverage = this.linesFromJsCoverage, lineNumber = this.lineNumber, source = this.source, lines, line, i, errorCls, length, lineNumberCls; if (source) { source = this.highLightCode ? this.process() : source; lines = source.split("\n"); length = lines.length; result = '<table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="lineNumbers">'; for (i = 0; i < length; i++) { errorCls = ""; lineNumberCls = ""; if (lineNumber) { errorCls = i === (lineNumber - 1) ? " error" : ""; } if (linesFromJsCoverage) { lineNumberCls = !isNaN(linesFromJsCoverage[i + 1]) ? " lineNumberGreen" : ""; lineNumberCls = linesFromJsCoverage[i + 1] === 0 ? " lineNumberRed" : lineNumberCls; } result += '<div class="lineNumber' + errorCls + lineNumberCls + '">' + (i + 1) +'</div>'; } result += '</td><td><pre class="code">'+ source +'</pre></td></tr></tbody></table>'; } this.source = result; return this.source; }; /** * Prepares source code. It crops double whitespace and append new lines. * This function is used generally to preformat the code that come from a * Function.prototype.toString. */ Test.CodeHighLighter.prototype.prepareIndent = function() { var regexps = this.regExps, matches, length, i, m; this.source = this.source.replace( regexps.multipleWhiteSpaces, " ").replace( regexps.semiColon, ";\n").replace( regexps.comma, ",\n").replace( regexps.openedBrackets, "$1\n").replace( regexps.closedBrackets, "\n$1\n"); // remove newline after commas inside code parenthesis matches = this.source.match(regexps.commasInsideParenthesis); length = matches ? matches.length : 0; for (i = 0; i < length; i++) { m = matches[i]; this.source = this.source.replace(m, m.replace(regexps.newLine, "")); } // fixes various bad formatting this.source = this.source.replace(regexps.arrayWithOneElement, "[$1]").replace( regexps.emptyObject, "{}").replace( regexps.commaBracket, ", {").replace( regexps.unwantedNewLines, "$1"); }; /** * Creates a string composed of n whitespaces * @param {Number} number The number of white spaces. * @return {String} A multiple whitespace string. */ Test.CodeHighLighter.prototype.addWhiteSpaces = function (number) { var whiteSpaces = "", i; for (i = 0; i < number; i++) { whiteSpaces += " "; } return whiteSpaces; }; /** * Indents pre-formatted source code. */ Test.CodeHighLighter.prototype.doIndent = function() { var regexps = this.regExps, results = [], indent = 0, sources = this.source.split("\n"), length = sour