1 /*global window, equals, ok, test, module, jQuery, deepEqDiag, asyncTest*/ 2 var jqUnit = jqUnit || {}; 3 4 // A function to load the testswarm agent if running in the testswarm environment 5 // This code was derived from testsuite.js ( http://code.google.com/p/jquery-ui/source/browse/trunk/tests/unit/testsuite.js ) 6 (function () { 7 var param = "swarmURL="; 8 var url = window.location.search; 9 url = decodeURIComponent(url.slice(url.indexOf(param) + param.length)); 10 11 if (url && url.indexOf("http") === 0) { 12 var injectPath = window.location.protocol + "//" + window.location.host + "/js/inject.js"; 13 document.write("<scr" + "ipt src='" + injectPath + "?" + (new Date()).getTime() + "'></scr" + "ipt>"); 14 } 15 })(); 16 17 (function ($) { 18 19 /************************ 20 * Deep equality assert * 21 ************************/ 22 23 function path(el) { 24 return el ? "path " + el: "root path"; 25 } 26 27 function reportType(obj) { 28 var type = typeof(obj); 29 return type === "string" || type === "number" || type === "boolean" ? type + " (" + obj + ")" 30 : type; 31 } 32 33 function deepEqImpl(thing1, thing2, basename) { 34 basename = basename || ""; 35 36 if (thing1 === thing2) { 37 return null; 38 } 39 40 if (typeof(thing1) !== typeof(thing2)) { 41 return "Type mismatch at " + path(basename) + ": " + reportType(thing1) + " to " + reportType(thing2); 42 } 43 44 if (thing1 === null ^ thing2 === null) { 45 return "Unexpected null value at " + path(basename); 46 } 47 48 if (thing1 === undefined ^ thing2 === undefined) { 49 return "Unexpected undefined value at " + path(basename); 50 } 51 52 if (typeof(thing1) === "function") { 53 return null; // compare all functions as equal 54 } 55 56 if (typeof(thing1) !== 'object') { 57 if (thing1 !== thing2) { 58 return "Primitive mismatch at " + path(basename) + ": " + thing1 + " to " + thing2; 59 } 60 } else { 61 var length1 = thing1.length; 62 var length2 = thing2.length; 63 if (length1 !== length2) { 64 return "Array length mismatch at " + path(basename) + ": " + length1 + " to " + length2; 65 } 66 for (var name in thing1) { 67 var n1 = thing1[name]; 68 var n2 = thing2[name]; 69 var neq = deepEqDiag(n1, n2, (basename ? basename + ".": "") + name); 70 if (neq) { 71 return neq; 72 } 73 } 74 } 75 76 return null; 77 } 78 79 function deepEqDiag(thing1, thing2, basename) { 80 var diag1 = deepEqImpl(thing1, thing2, basename); 81 if (diag1) { 82 return diag1; 83 } 84 85 var diag2 = deepEqImpl(thing2, thing1, basename); 86 if (diag2) { 87 return diag2; 88 } 89 90 return null; 91 } 92 93 jqUnit.deepEq = function (thing1, thing2) { 94 return !deepEqImpl(thing1, thing2) && !deepEqImpl(thing2, thing1); 95 }; 96 97 jqUnit.deepEqDiag = function (thing1, thing2) { 98 return deepEqDiag(thing1, thing2); 99 }; 100 101 /** 102 * Keeps track of the order of function invocations. The transcript contains information about 103 * each invocation, including its name and the arguments that were supplied to it. 104 */ 105 jqUnit.invocationTracker = function (options) { 106 var that = {}; 107 that.runTestsOnFunctionNamed = options ? options.runTestsOnFunctionNamed : undefined; 108 that.testBody = options ? options.testBody : undefined; 109 110 /** 111 * An array containing an ordered list of details about each function invocation. 112 */ 113 that.transcript = []; 114 115 /** 116 * Called to listen for a function's invocation and record its details in the transcript. 117 * 118 * @param {Object} fnName the function name to listen for 119 * @param {Object} onObject the object on which to invoke the method 120 */ 121 that.intercept = function (fnName, onObject) { 122 onObject = onObject || window; 123 124 var wrappedFn = onObject[fnName]; 125 onObject[fnName] = function () { 126 that.transcript.push({ 127 name: fnName, 128 args: arguments 129 }); 130 wrappedFn.apply(onObject, arguments); 131 132 if (fnName === that.runTestsOnFunctionNamed) { 133 that.testBody(that.transcript); 134 } 135 }; 136 }; 137 138 /** 139 * Intercepts all the functions on the specified object. 140 * 141 * @param {Object} obj 142 */ 143 that.interceptAll = function (obj) { 144 for (var fnName in obj) { 145 that.intercept(fnName, obj); 146 } 147 }; 148 149 that.clearTranscript = function () { 150 that.transcript = []; 151 }; 152 153 return that; 154 }; 155 156 157 /*********************** 158 * xUnit Compatibility * 159 ***********************/ 160 161 var jsUnitCompat = { 162 assertEquals: function (msg, expected, actual) { 163 equals(actual, expected, msg); 164 }, 165 166 assertNotEquals: function (msg, value1, value2) { 167 ok(value1 !== value2, msg); 168 }, 169 170 assertTrue: function (msg, value) { 171 ok(value, msg); 172 }, 173 174 assertFalse: function (msg, value) { 175 ok(!value, msg); 176 }, 177 178 assertUndefined: function (msg, value) { 179 ok(typeof value === 'undefined', msg); 180 }, 181 182 assertNotUndefined: function (msg, value) { 183 ok(typeof value !== 'undefined', msg); 184 }, 185 186 assertValue: function (msg, value) { 187 ok(value !== null && value !== undefined, msg); 188 }, 189 190 assertNull: function (msg, value) { 191 equals(value, null, msg); 192 }, 193 194 assertNotNull: function (msg, value) { 195 ok(value !== null, msg); 196 }, 197 198 assertDeepEq: function (msg, expected, actual) { 199 var diag = deepEqDiag(expected, actual); 200 ok(diag === null, msg + (diag === null ? "" : ": " + diag)); 201 }, 202 203 assertDeepNeq: function (msg, unexpected, actual) { 204 var diag = deepEqDiag(unexpected, actual); 205 ok(diag !== null, msg); 206 } 207 }; 208 209 // Mix these compatibility functions into the jqUnit namespace. 210 $.extend(jqUnit, jsUnitCompat); 211 212 213 /*************************** 214 * Other helpful functions * 215 ***************************/ 216 217 var testFns = { 218 isVisible: function (msg, selector) { 219 ok($(selector).is(':visible'), msg); 220 }, 221 222 notVisible: function (msg, selector) { 223 ok($(selector).is(':hidden'), msg); 224 }, 225 226 exists: function (msg, selector) { 227 ok($(selector)[0], msg); 228 }, 229 230 notExists: function (msg, selector) { 231 ok(!$(selector)[0], msg); 232 }, 233 234 // Overrides jQuery's animation routines to be synchronous. Careful! 235 subvertAnimations: function () { 236 $.fn.fadeIn = function (speed, callback) { 237 this.show(); 238 if (callback) { 239 callback(); 240 } 241 }; 242 243 $.fn.fadeOut = function (speed, callback) { 244 this.hide(); 245 if (callback) { 246 callback(); 247 } 248 }; 249 } 250 }; 251 252 // Mix these test functions into the jqUnit namespace. 253 $.extend(jqUnit, testFns); 254 255 /** 256 * Synchronously loads an HTML document via Ajax. This is roughly similar to jQuery.load but without the asynchrony. 257 * 258 * @param {jQueryable} container the element into which you want put the loaded HTML 259 * @param {String} url the location of the HTML document. This can include a selector after the URL, separated by a space 260 * @param {Function} callback the callback function to run upon successful load 261 */ 262 var loadSync = function (container, url, callback) { 263 var idx = url.indexOf(" "); 264 var sel = "body"; 265 if (idx >= 0) { 266 sel = url.slice(idx, url.length); 267 url = url.slice(0, idx); 268 } 269 270 var injectFragment = function (container, sel, docTxt) { 271 var docFrag = $("<div/>").append(docTxt.replace(/<script(.|\s)*?\/script>/g, "")); 272 container.empty().append($(sel, docFrag)); 273 }; 274 275 $.ajax({ 276 url: url, 277 type: "GET", 278 dataType: "html", 279 async: false, 280 complete: function (res, status) { 281 if (status === "success" || status === "notmodified") { 282 injectFragment(container, sel, res.responseText); 283 callback.apply(null, [container, res.responseText, status, res]); 284 } 285 } 286 }); 287 }; 288 289 /*************************************************** 290 * TestCase constructor for backward compatibility * 291 ***************************************************/ 292 293 jqUnit.TestCase = function (moduleName, setUpFn, tearDownFn) { 294 return jqUnit.testCase(moduleName, setUpFn, tearDownFn); 295 }; 296 297 /****************************** 298 * TestCase creator function 299 * @param {Object} moduleName 300 * @param {Object} setUpFn 301 * @param {Object} tearDownFn 302 */ 303 jqUnit.testCase = function (moduleName, setUpFn, tearDownFn) { 304 var that = {}; 305 that.fetchedTemplates = []; 306 307 /** 308 * Fetches a template synchronously using AJAX if it was never fetched before and stores it in that.fetchedTemplates 309 * @param {Object} templateURL URL to the document to be fetched 310 * @param {Object} selector A selector which finds the piece of the document to be fetched 311 * @param {Object} container The container where the fetched content will be appended - default to the element with the id 'main' 312 */ 313 that.fetchTemplate = function (templateURL, selector, container) { 314 container = container || $("#main"); 315 var selectorToFetch = templateURL + " " + selector; 316 317 if (!that.fetchedTemplates[selectorToFetch]) { 318 loadSync(container, selectorToFetch, function () { 319 that.fetchedTemplates[selectorToFetch] = $(container).clone(); 320 }); 321 } else { 322 container.append($(selector, that.fetchedTemplates[selectorToFetch].clone())); 323 } 324 }; 325 326 that.test = function (string, testFn) { 327 test(string, testFn); 328 }; 329 330 that.asyncTest = function (string, testFn) { 331 asyncTest(string, testFn); 332 }; 333 334 module(moduleName, { 335 setup: setUpFn || function () {}, 336 teardown: tearDownFn || function () {} 337 }); 338 339 return that; 340 }; 341 342 })(jQuery); 343