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