|
1 var _config = { |
|
2 fixture: null, |
|
3 Test: [], |
|
4 stats: { |
|
5 all: 0, |
|
6 bad: 0 |
|
7 }, |
|
8 queue: [], |
|
9 blocking: true, |
|
10 timeout: null, |
|
11 expected: null, |
|
12 currentModule: null, |
|
13 asyncTimeout: 2 // seconds for async timeout |
|
14 }; |
|
15 |
|
16 _config.filters = location.search.length > 1 && //restrict modules/tests by get parameters |
|
17 $.map( location.search.slice(1).split('&'), decodeURIComponent ); |
|
18 |
|
19 var isLocal = !!(window.location.protocol == 'file:'); |
|
20 |
|
21 $(function() { |
|
22 $('#userAgent').html(navigator.userAgent); |
|
23 runTest(); |
|
24 }); |
|
25 |
|
26 function synchronize(callback) { |
|
27 _config.queue[_config.queue.length] = callback; |
|
28 if(!_config.blocking) { |
|
29 process(); |
|
30 } |
|
31 } |
|
32 |
|
33 function process() { |
|
34 while(_config.queue.length && !_config.blocking) { |
|
35 var call = _config.queue[0]; |
|
36 _config.queue = _config.queue.slice(1); |
|
37 call(); |
|
38 } |
|
39 } |
|
40 |
|
41 function stop(allowFailure) { |
|
42 _config.blocking = true; |
|
43 var handler = allowFailure ? start : function() { |
|
44 ok( false, "Test timed out" ); |
|
45 start(); |
|
46 }; |
|
47 // Disabled, caused too many random errors |
|
48 //_config.timeout = setTimeout(handler, _config.asyncTimeout * 1000); |
|
49 } |
|
50 function start() { |
|
51 // A slight delay, to avoid any current callbacks |
|
52 setTimeout(function(){ |
|
53 if(_config.timeout) |
|
54 clearTimeout(_config.timeout); |
|
55 _config.blocking = false; |
|
56 process(); |
|
57 }, 13); |
|
58 } |
|
59 |
|
60 function validTest( name ) { |
|
61 var filters = _config.filters; |
|
62 if( !filters ) |
|
63 return true; |
|
64 |
|
65 var i = filters.length, |
|
66 run = false; |
|
67 while( i-- ){ |
|
68 var filter = filters[i], |
|
69 not = filter.charAt(0) == '!'; |
|
70 if( not ) |
|
71 filter = filter.slice(1); |
|
72 if( name.indexOf(filter) != -1 ) |
|
73 return !not; |
|
74 if( not ) |
|
75 run = true; |
|
76 } |
|
77 return run; |
|
78 } |
|
79 |
|
80 function runTest() { |
|
81 _config.blocking = false; |
|
82 var time = new Date(); |
|
83 _config.fixture = document.getElementById('main').innerHTML; |
|
84 _config.ajaxSettings = $.ajaxSettings; |
|
85 synchronize(function() { |
|
86 time = new Date() - time; |
|
87 $("<div>").html(['<p class="result">Tests completed in ', |
|
88 time, ' milliseconds.<br/>', |
|
89 _config.stats.bad, ' tests of ', _config.stats.all, ' failed.</p>'] |
|
90 .join('')) |
|
91 .appendTo("body"); |
|
92 $("#banner").addClass(_config.stats.bad ? "fail" : "pass"); |
|
93 if ( parent.runAJAXTest ) |
|
94 parent.runAJAXTest(); |
|
95 |
|
96 }); |
|
97 } |
|
98 |
|
99 function test(name, callback, nowait) { |
|
100 if(_config.currentModule) |
|
101 name = _config.currentModule + " module: " + name; |
|
102 |
|
103 if ( !validTest(name) ) |
|
104 return; |
|
105 |
|
106 synchronize(function() { |
|
107 _config.Test = []; |
|
108 try { |
|
109 callback(); |
|
110 } catch(e) { |
|
111 if( typeof console != "undefined" && console.error && console.warn ) { |
|
112 console.error("Test " + name + " died, exception and test follows"); |
|
113 console.error(e); |
|
114 console.warn(callback.toString()); |
|
115 } |
|
116 _config.Test.push( [ false, "Died on test #" + (_config.Test.length+1) + ": " + e.message ] ); |
|
117 } |
|
118 }); |
|
119 synchronize(function() { |
|
120 reset(); |
|
121 |
|
122 // don't output pause tests |
|
123 if(nowait) return; |
|
124 |
|
125 if(_config.expected && _config.expected != _config.Test.length) { |
|
126 _config.Test.push( [ false, "Expected " + _config.expected + " assertions, but " + _config.Test.length + " were run" ] ); |
|
127 } |
|
128 _config.expected = null; |
|
129 |
|
130 var good = 0, bad = 0; |
|
131 var ol = document.createElement("ol"); |
|
132 ol.style.display = "none"; |
|
133 var li = "", state = "pass"; |
|
134 for ( var i = 0; i < _config.Test.length; i++ ) { |
|
135 var li = document.createElement("li"); |
|
136 li.className = _config.Test[i][0] ? "pass" : "fail"; |
|
137 li.innerHTML = _config.Test[i][1]; |
|
138 ol.appendChild( li ); |
|
139 |
|
140 _config.stats.all++; |
|
141 if ( !_config.Test[i][0] ) { |
|
142 state = "fail"; |
|
143 bad++; |
|
144 _config.stats.bad++; |
|
145 } else good++; |
|
146 |
|
147 if ( parent.SimpleTest ){ |
|
148 parent.SimpleTest.ok( _config.Test[i][0], name, _config.Test[i][1] );} |
|
149 } |
|
150 |
|
151 var li = document.createElement("li"); |
|
152 li.className = state; |
|
153 |
|
154 var b = document.createElement("strong"); |
|
155 b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + _config.Test.length + ")</b>"; |
|
156 b.onclick = function(){ |
|
157 var n = this.nextSibling; |
|
158 if ( jQuery.css( n, "display" ) == "none" ) |
|
159 n.style.display = "block"; |
|
160 else |
|
161 n.style.display = "none"; |
|
162 }; |
|
163 $(b).dblclick(function(event) { |
|
164 var target = jQuery(event.target).filter("strong").clone(); |
|
165 if ( target.length ) { |
|
166 target.children().remove(); |
|
167 location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent($.trim(target.text())); |
|
168 } |
|
169 }); |
|
170 li.appendChild( b ); |
|
171 li.appendChild( ol ); |
|
172 |
|
173 document.getElementById("tests").appendChild( li ); |
|
174 }); |
|
175 } |
|
176 |
|
177 // call on start of module test to prepend name to all tests |
|
178 function module(moduleName) { |
|
179 _config.currentModule = moduleName; |
|
180 } |
|
181 |
|
182 /** |
|
183 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. |
|
184 */ |
|
185 function expect(asserts) { |
|
186 _config.expected = asserts; |
|
187 } |
|
188 |
|
189 /** |
|
190 * Resets the test setup. Useful for tests that modify the DOM. |
|
191 */ |
|
192 function reset() { |
|
193 $("#main").html( _config.fixture ); |
|
194 $.event.global = {}; |
|
195 $.ajaxSettings = $.extend({}, _config.ajaxSettings); |
|
196 } |
|
197 |
|
198 /** |
|
199 * Asserts true. |
|
200 * @example ok( $("a").size() > 5, "There must be at least 5 anchors" ); |
|
201 */ |
|
202 function ok(a, msg) { |
|
203 _config.Test.push( [ !!a, msg ] ); |
|
204 } |
|
205 |
|
206 /** |
|
207 * Asserts that two arrays are the same |
|
208 */ |
|
209 function isSet(a, b, msg) { |
|
210 var ret = true; |
|
211 if ( a && b && a.length != undefined && a.length == b.length ) { |
|
212 for ( var i = 0; i < a.length; i++ ) |
|
213 if ( a[i] != b[i] ) |
|
214 ret = false; |
|
215 } else |
|
216 ret = false; |
|
217 if ( !ret ) |
|
218 _config.Test.push( [ ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) ] ); |
|
219 else |
|
220 _config.Test.push( [ ret, msg ] ); |
|
221 } |
|
222 |
|
223 /** |
|
224 * Asserts that two objects are equivalent |
|
225 */ |
|
226 function isObj(a, b, msg) { |
|
227 var ret = true; |
|
228 |
|
229 if ( a && b ) { |
|
230 for ( var i in a ) |
|
231 if ( a[i] != b[i] ) |
|
232 ret = false; |
|
233 |
|
234 for ( i in b ) |
|
235 if ( a[i] != b[i] ) |
|
236 ret = false; |
|
237 } else |
|
238 ret = false; |
|
239 |
|
240 _config.Test.push( [ ret, msg ] ); |
|
241 } |
|
242 |
|
243 function serialArray( a ) { |
|
244 var r = []; |
|
245 |
|
246 if ( a && a.length ) |
|
247 for ( var i = 0; i < a.length; i++ ) { |
|
248 var str = a[i].nodeName; |
|
249 if ( str ) { |
|
250 str = str.toLowerCase(); |
|
251 if ( a[i].id ) |
|
252 str += "#" + a[i].id; |
|
253 } else |
|
254 str = a[i]; |
|
255 r.push( str ); |
|
256 } |
|
257 |
|
258 return "[ " + r.join(", ") + " ]"; |
|
259 } |
|
260 |
|
261 /** |
|
262 * Returns an array of elements with the given IDs, eg. |
|
263 * @example q("main", "foo", "bar") |
|
264 * @result [<div id="main">, <span id="foo">, <input id="bar">] |
|
265 */ |
|
266 function q() { |
|
267 var r = []; |
|
268 for ( var i = 0; i < arguments.length; i++ ) |
|
269 r.push( document.getElementById( arguments[i] ) ); |
|
270 return r; |
|
271 } |
|
272 |
|
273 /** |
|
274 * Asserts that a select matches the given IDs |
|
275 * @example t("Check for something", "//[a]", ["foo", "baar"]); |
|
276 * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar' |
|
277 */ |
|
278 function t(a,b,c) { |
|
279 var f = jQuery(b); |
|
280 var s = ""; |
|
281 for ( var i = 0; i < f.length; i++ ) |
|
282 s += (s && ",") + '"' + f[i].id + '"'; |
|
283 isSet(f, q.apply(q,c), a + " (" + b + ")"); |
|
284 } |
|
285 |
|
286 /** |
|
287 * Add random number to url to stop IE from caching |
|
288 * |
|
289 * @example url("data/test.html") |
|
290 * @result "data/test.html?10538358428943" |
|
291 * |
|
292 * @example url("data/test.php?foo=bar") |
|
293 * @result "data/test.php?foo=bar&10538358345554" |
|
294 */ |
|
295 function url(value) { |
|
296 return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); |
|
297 } |
|
298 |
|
299 /** |
|
300 * Checks that the first two arguments are equal, with an optional message. |
|
301 * Prints out both expected and actual values on failure. |
|
302 * |
|
303 * Prefered to ok( expected == actual, message ) |
|
304 * |
|
305 * @example equals( "Expected 2 characters.", v.formatMessage("Expected {0} characters.", 2) ); |
|
306 * |
|
307 * @param Object actual |
|
308 * @param Object expected |
|
309 * @param String message (optional) |
|
310 */ |
|
311 function equals(actual, expected, message) { |
|
312 var result = expected == actual; |
|
313 message = message || (result ? "okay" : "failed"); |
|
314 _config.Test.push( [ result, result ? message + ": " + expected : message + " expected: " + expected + " actual: " + actual ] ); |
|
315 } |
|
316 |
|
317 /** |
|
318 * Trigger an event on an element. |
|
319 * |
|
320 * @example triggerEvent( document.body, "click" ); |
|
321 * |
|
322 * @param DOMElement elem |
|
323 * @param String type |
|
324 */ |
|
325 function triggerEvent( elem, type, event ) { |
|
326 if ( jQuery.browser.mozilla || jQuery.browser.opera ) { |
|
327 event = document.createEvent("MouseEvents"); |
|
328 event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, |
|
329 0, 0, 0, 0, 0, false, false, false, false, 0, null); |
|
330 elem.dispatchEvent( event ); |
|
331 } else if ( jQuery.browser.msie ) { |
|
332 elem.fireEvent("on"+type); |
|
333 } |
|
334 } |