/* YUI 3.17.2 (build 9c3c78e) Copyright 2014 Yahoo! Inc. All rights reserved. Licensed under the BSD License. http://yuilibrary.com/license/ */ YUI.add('test', function (Y, NAME) { /** * YUI Test Framework * @module test * @main test */ /* * The root namespace for YUI Test. */ //So we only ever have one YUITest object that's shared if (YUI.YUITest) { Y.Test = YUI.YUITest; } else { //Ends after the YUITest definitions //Make this global for back compat YUITest = { version: "3.17.2", guid: function(pre) { return Y.guid(pre); } }; Y.namespace('Test'); //Using internal YUI methods here YUITest.Object = Y.Object; YUITest.Array = Y.Array; YUITest.Util = { mix: Y.mix, JSON: Y.JSON }; /** * Simple custom event implementation. * @namespace Test * @module test * @class EventTarget * @constructor */ YUITest.EventTarget = function(){ /** * Event handlers for the various events. * @type Object * @private * @property _handlers * @static */ this._handlers = {}; }; YUITest.EventTarget.prototype = { //restore prototype constructor: YUITest.EventTarget, //------------------------------------------------------------------------- // Event Handling //------------------------------------------------------------------------- /** * Adds a listener for a given event type. * @param {String} type The type of event to add a listener for. * @param {Function} listener The function to call when the event occurs. * @method attach */ attach: function(type, listener){ if (typeof this._handlers[type] == "undefined"){ this._handlers[type] = []; } this._handlers[type].push(listener); }, /** * Adds a listener for a given event type. * @param {String} type The type of event to add a listener for. * @param {Function} listener The function to call when the event occurs. * @method subscribe * @deprecated */ subscribe: function(type, listener){ this.attach.apply(this, arguments); }, /** * Fires an event based on the passed-in object. * @param {Object|String} event An object with at least a 'type' attribute * or a string indicating the event name. * @method fire */ fire: function(event){ if (typeof event == "string"){ event = { type: event }; } if (!event.target){ event.target = this; } if (!event.type){ throw new Error("Event object missing 'type' property."); } if (this._handlers[event.type] instanceof Array){ var handlers = this._handlers[event.type]; for (var i=0, len=handlers.length; i < len; i++){ handlers[i].call(this, event); } } }, /** * Removes a listener for a given event type. * @param {String} type The type of event to remove a listener from. * @param {Function} listener The function to remove from the event. * @method detach */ detach: function(type, listener){ if (this._handlers[type] instanceof Array){ var handlers = this._handlers[type]; for (var i=0, len=handlers.length; i < len; i++){ if (handlers[i] === listener){ handlers.splice(i, 1); break; } } } }, /** * Removes a listener for a given event type. * @param {String} type The type of event to remove a listener from. * @param {Function} listener The function to remove from the event. * @method unsubscribe * @deprecated */ unsubscribe: function(type, listener){ this.detach.apply(this, arguments); } }; /** * A test suite that can contain a collection of TestCase and TestSuite objects. * @param {String||Object} data The name of the test suite or an object containing * a name property as well as setUp and tearDown methods. * @namespace Test * @module test * @class TestSuite * @constructor */ YUITest.TestSuite = function (data) { /** * The name of the test suite. * @type String * @property name */ this.name = ""; /** * Array of test suites and test cases. * @type Array * @property items * @private */ this.items = []; //initialize the properties if (typeof data == "string"){ this.name = data; } else if (data instanceof Object){ for (var prop in data){ if (data.hasOwnProperty(prop)){ this[prop] = data[prop]; } } } //double-check name if (this.name === "" || !this.name) { this.name = YUITest.guid("testSuite_"); } }; YUITest.TestSuite.prototype = { //restore constructor constructor: YUITest.TestSuite, /** * Adds a test suite or test case to the test suite. * @param {Test.TestSuite||YUITest.TestCase} testObject The test suite or test case to add. * @method add */ add : function (testObject) { if (testObject instanceof YUITest.TestSuite || testObject instanceof YUITest.TestCase) { this.items.push(testObject); } return this; }, //------------------------------------------------------------------------- // Stub Methods //------------------------------------------------------------------------- /** * Function to run before each test is executed. * @method setUp */ setUp : function () { }, /** * Function to run after each test is executed. * @method tearDown */ tearDown: function () { } }; /** * Test case containing various tests to run. * @param template An object containing any number of test methods, other methods, * an optional name, and anything else the test case needs. * @module test * @class TestCase * @namespace Test * @constructor */ YUITest.TestCase = function (template) { /* * Special rules for the test case. Possible subobjects * are fail, for tests that should fail, and error, for * tests that should throw an error. */ this._should = {}; //copy over all properties from the template to this object for (var prop in template) { this[prop] = template[prop]; } //check for a valid name if (typeof this.name != "string") { this.name = YUITest.guid("testCase_"); } }; /** Default delay for a test failure when `wait()` is called without a _delay_. @property DEFAULT_WAIT @type {Number} @default 10000 @static **/ YUITest.TestCase.DEFAULT_WAIT = 10000; /** Calls `YUITest.Assert.fail()` with a message indicating `wait()` was called, but `resume()` was never called. @method _waitTimeout @static @protected **/ YUITest.TestCase._waitTimeout = function () { YUITest.Assert.fail("Timeout: wait() called but resume() never called."); }; YUITest.TestCase.prototype = { //restore constructor constructor: YUITest.TestCase, /** * Method to call from an async init method to * restart the test case. When called, returns a function * that should be called when tests are ready to continue. * @method callback * @return {Function} The function to call as a callback. */ callback: function(){ return YUITest.TestRunner.callback.apply(YUITest.TestRunner,arguments); }, /** * Resumes a paused test and runs the given function. * @param {Function} segment (Optional) The function to run. * If omitted, the test automatically passes. * @method resume */ resume : function (segment) { YUITest.TestRunner.resume(segment); }, /** * Causes the test case to wait a specified amount of time and then * continue executing the given code. * @param {Function} segment (Optional) The function to run after the delay. * If omitted, the TestRunner will wait until resume() is called. * @param {Number} delay (Optional) The number of milliseconds to wait before running * the function. If omitted, defaults to `DEFAULT_WAIT` ms (10s). * @method wait */ wait : function (segment, delay){ delay = (typeof segment === 'number') ? segment : (typeof delay === 'number') ? delay : YUITest.TestCase.DEFAULT_WAIT; if (typeof segment !== 'function') { segment = YUITest.TestCase._waitTimeout; } throw new YUITest.Wait(segment, delay); }, /** Creates a callback that automatically resumes the test. Parameters as passed on to the callback. @method next @param {Function} callback Callback to call after resuming the test. @param {Object} [context] The value of `this` inside the callback. If not given, the original context of the function will be used. @return {Function} wrapped callback that resumes the test. @example ``` // using test.resume() Y.jsonp(uri, function (response) { test.resume(function () { Y.Assert.isObject(response); }); }); test.wait(); // using test.next() Y.jsonp(uri, test.next(function (response) { Y.Assert.isObject(response); })); test.wait(); ``` **/ next: function (callback, context) { var self = this; context = arguments.length >= 2 ? arguments[1] : undefined; return function () { var args = arguments; if (context === undefined) { context = this; } self.resume(function () { callback.apply(context, args); }); }; }, /** Delays the current test until _condition_ returns a truthy value. If _condition_ fails to return a truthy value before _timeout_ milliseconds have passed, the test fails. Default _timeout_ is 10s. _condition_ will be executed every _increment_ milliseconds (default 100). @method waitFor @param {Function} condition Function executed to indicate whether to execute _segment_ @param {Function} segment Function to check the success or failure of this test @param {Number} [timeout=10000] Maximum number of milliseconds to wait for _condition_ to return true @param {Number} [increment=100] Milliseconds to wait before checking _condition_ **/ waitFor: function (condition, segment, timeout, increment) { var self = this, endTime; if ((typeof condition !== 'function') || (typeof segment !== 'function')) { self.fail('waitFor() called with invalid parameters.'); } if (typeof timeout !== 'number') { timeout = YUITest.TestCase.DEFAULT_WAIT; } endTime = (+new Date()) + timeout; if (typeof increment !== 'number') { increment = 100; } self.wait(function () { var now; if (condition.call(self)) { segment.call(self); } else { now = (+new Date()); if (now > endTime) { YUITest.TestCase._waitTimeout(); } else { self.waitFor(condition, segment, endTime - now, increment); } } }, increment); }, //------------------------------------------------------------------------- // Assertion Methods //------------------------------------------------------------------------- /** * Asserts that a given condition is true. If not, then a YUITest.AssertionError object is thrown * and the test fails. * @method assert * @param {Boolean} condition The condition to test. * @param {String} message The message to display if the assertion fails. */ assert : function (condition, message){ YUITest.Assert._increment(); if (!condition){ throw new YUITest.AssertionError(YUITest.Assert._formatMessage(message, "Assertion failed.")); } }, /** * Forces an assertion error to occur. Shortcut for YUITest.Assert.fail(). * @method fail * @param {String} message (Optional) The message to display with the failure. */ fail: function (message) { YUITest.Assert.fail(message); }, //------------------------------------------------------------------------- // Stub Methods //------------------------------------------------------------------------- /** * Function to run once before tests start to run. * This executes before the first call to setUp(). * @method init */ init: function(){ //noop }, /** * Function to run once after tests finish running. * This executes after the last call to tearDown(). * @method destroy */ destroy: function(){ //noop }, /** * Function to run before each test is executed. * @method setUp */ setUp : function () { //noop }, /** * Function to run after each test is executed. * @method tearDown */ tearDown: function () { //noop } }; /** * An object object containing test result formatting methods. * @namespace Test * @module test * @class TestFormat * @static */ YUITest.TestFormat = function(){ /* (intentionally not documented) * Basic XML escaping method. Replaces quotes, less-than, greater-than, * apostrophe, and ampersand characters with their corresponding entities. * @param {String} text The text to encode. * @return {String} The XML-escaped text. */ function xmlEscape(text){ return text.replace(/[<>"'&]/g, function(value){ switch(value){ case "<": return "<"; case ">": return ">"; case "\"": return """; case "'": return "'"; case "&": return "&"; } }); } return { /** * Returns test results formatted as a JSON string. Requires JSON utility. * @param {Object} result The results object created by TestRunner. * @return {String} A JSON-formatted string of results. * @method JSON * @static */ JSON: function(results) { return YUITest.Util.JSON.stringify(results); }, /** * Returns test results formatted as an XML string. * @param {Object} result The results object created by TestRunner. * @return {String} An XML-formatted string of results. * @method XML * @static */ XML: function(results) { function serializeToXML(results){ var xml = "<" + results.type + " name=\"" + xmlEscape(results.name) + "\""; if (typeof(results.duration)=="number"){ xml += " duration=\"" + results.duration + "\""; } if (results.type == "test"){ xml += " result=\"" + results.result + "\" message=\"" + xmlEscape(results.message) + "\">"; } else { xml += " passed=\"" + results.passed + "\" failed=\"" + results.failed + "\" ignored=\"" + results.ignored + "\" total=\"" + results.total + "\">"; for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ xml += serializeToXML(results[prop]); } } } } xml += ""; return xml; } return "" + serializeToXML(results); }, /** * Returns test results formatted in JUnit XML format. * @param {Object} result The results object created by TestRunner. * @return {String} An XML-formatted string of results. * @method JUnitXML * @static */ JUnitXML: function(results) { function serializeToJUnitXML(results){ var xml = ""; switch (results.type){ //equivalent to testcase in JUnit case "test": if (results.result != "ignore"){ xml = ""; if (results.result == "fail"){ xml += ""; } xml+= ""; } break; //equivalent to testsuite in JUnit case "testcase": xml = ""; for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ xml += serializeToJUnitXML(results[prop]); } } } xml += ""; break; //no JUnit equivalent, don't output anything case "testsuite": for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ xml += serializeToJUnitXML(results[prop]); } } } break; //top-level, equivalent to testsuites in JUnit case "report": xml = ""; for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ xml += serializeToJUnitXML(results[prop]); } } } xml += ""; //no default } return xml; } return "" + serializeToJUnitXML(results); }, /** * Returns test results formatted in TAP format. * For more information, see Test Anything Protocol. * @param {Object} result The results object created by TestRunner. * @return {String} A TAP-formatted string of results. * @method TAP * @static */ TAP: function(results) { var currentTestNum = 1; function serializeToTAP(results){ var text = ""; switch (results.type){ case "test": if (results.result != "ignore"){ text = "ok " + (currentTestNum++) + " - " + results.name; if (results.result == "fail"){ text = "not " + text + " - " + results.message; } text += "\n"; } else { text = "#Ignored test " + results.name + "\n"; } break; case "testcase": text = "#Begin testcase " + results.name + "(" + results.failed + " failed of " + results.total + ")\n"; for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ text += serializeToTAP(results[prop]); } } } text += "#End testcase " + results.name + "\n"; break; case "testsuite": text = "#Begin testsuite " + results.name + "(" + results.failed + " failed of " + results.total + ")\n"; for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ text += serializeToTAP(results[prop]); } } } text += "#End testsuite " + results.name + "\n"; break; case "report": for (var prop in results){ if (results.hasOwnProperty(prop)){ if (results[prop] && typeof results[prop] == "object" && !(results[prop] instanceof Array)){ text += serializeToTAP(results[prop]); } } } //no default } return text; } return "1.." + results.total + "\n" + serializeToTAP(results); } }; }(); /** * An object capable of sending test results to a server. * @param {String} url The URL to submit the results to. * @param {Function} format (Optiona) A function that outputs the results in a specific format. * Default is YUITest.TestFormat.XML. * @constructor * @namespace Test * @module test * @class Reporter */ YUITest.Reporter = function(url, format) { /** * The URL to submit the data to. * @type String * @property url */ this.url = url; /** * The formatting function to call when submitting the data. * @type Function * @property format */ this.format = format || YUITest.TestFormat.XML; /** * Extra fields to submit with the request. * @type Object * @property _fields * @private */ this._fields = new Object(); /** * The form element used to submit the results. * @type HTMLFormElement * @property _form * @private */ this._form = null; /** * Iframe used as a target for form submission. * @type HTMLIFrameElement * @property _iframe * @private */ this._iframe = null; }; YUITest.Reporter.prototype = { //restore missing constructor constructor: YUITest.Reporter, /** * Adds a field to the form that submits the results. * @param {String} name The name of the field. * @param {Any} value The value of the field. * @method addField */ addField : function (name, value){ this._fields[name] = value; }, /** * Removes all previous defined fields. * @method clearFields */ clearFields : function(){ this._fields = new Object(); }, /** * Cleans up the memory associated with the TestReporter, removing DOM elements * that were created. * @method destroy */ destroy : function() { if (this._form){ this._form.parentNode.removeChild(this._form); this._form = null; } if (this._iframe){ this._iframe.parentNode.removeChild(this._iframe); this._iframe = null; } this._fields = null; }, /** * Sends the report to the server. * @param {Object} results The results object created by TestRunner. * @method report */ report : function(results){ //if the form hasn't been created yet, create it if (!this._form){ this._form = document.createElement("form"); this._form.method = "post"; this._form.style.visibility = "hidden"; this._form.style.position = "absolute"; this._form.style.top = 0; document.body.appendChild(this._form); //IE won't let you assign a name using the DOM, must do it the hacky way try { this._iframe = document.createElement("