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 jasmine.undefined instead of undefined, since undefined 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 not 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 jasmine.log 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 disabled 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, '>'); }; 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(''); } 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(''); } 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(''); } 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(''); } 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(''); } 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(''); } else { if (typeof obj[property] !== "object") { self.format(obj[property]); } else { self.append(""); } } }); 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 !
" + "Spec : " + jasmine.util.htmlEscape(spec.getFullName()) + ' Load this spec only and disable body autoclean
', "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; itrue is the match is inside another. */ Test.CodeHighLighter.prototype.matchObjects = function(value, index, matchedObjects, css) { matchedObjects.push({ origValue: value, value: '' + jasmine.util.htmlEscape(value).replace("$","$\b") + '', 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 true 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, '$1').replace( regexps.numbers, '$1'); for (i = 0; i < length; i++) { this.source = this.source.replace(keywords[i], '$1'); } }; /** * 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 = '
'; 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 += '
' + (i + 1) +'
'; } result += '
'+ source +'
'; } 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") + "
" + 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 = ''; result += ''; result += ''; result += ''; result += '"; result += '"; result += '"; result += ''; for (filename in coverage) { if (!coverage.hasOwnProperty(filename)) { continue; } result += ''; result += '"; result += '"; result += '"; result += '"; result += ''; } result += '
FileStatementsExecutedPercentage
Total' + Test.jsCoverage.statements + "' + Test.jsCoverage.executed + "' + this.renderPercentage(Test.jsCoverage.percentage) + "
' + filename + "' + coverage[filename].statements + "' + coverage[filename].executed + "' + this.renderPercentage(coverage[filename].percentage) + "
'; 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 + '%
'; result += '
'; result += '
'; 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; };