newjs 1.7.2 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/Manifest.txt +13 -2
  2. data/Rakefile +11 -20
  3. data/app_generators/newjs/templates/test/assets/jsunittest.js +173 -135
  4. data/app_generators/newjs_iphone/templates/Html/test/assets/jsunittest.js +173 -135
  5. data/app_generators/newjs_screwunit/USAGE +5 -0
  6. data/app_generators/newjs_screwunit/newjs_screwunit_generator.rb +61 -0
  7. data/app_generators/newjs_screwunit/templates/Rakefile.erb +34 -0
  8. data/bin/newjs_screwunit +17 -0
  9. data/features/development.feature +5 -5
  10. data/features/imported_files_for_generators.feature +2 -2
  11. data/features/newjs_screwunit.feature +18 -0
  12. data/features/step_definitions/cli_steps.rb +9 -0
  13. data/features/{steps/common.rb → step_definitions/common_steps.rb} +39 -81
  14. data/features/step_definitions/file_comparison_steps.rb +10 -0
  15. data/features/support/common.rb +29 -0
  16. data/features/support/env.rb +15 -0
  17. data/features/support/matchers.rb +11 -0
  18. data/lib/newjs.rb +1 -1
  19. data/rack_generators/javascript_test/templates/assets/jsunittest.js +173 -135
  20. data/rails_generators/javascript_test/templates/assets/jsunittest.js +173 -135
  21. data/test/test_newjs_screwunit_generator.rb +43 -0
  22. data/vendor/jsunittest/History.txt +9 -0
  23. data/vendor/jsunittest/Rakefile +1 -1
  24. data/vendor/jsunittest/dist/jsunittest-0.7.3.js +1042 -0
  25. data/vendor/jsunittest/dist/jsunittest.js +173 -135
  26. data/vendor/jsunittest/src/ajax.js +2 -1
  27. data/vendor/jsunittest/src/assertions.js +49 -78
  28. data/vendor/jsunittest/src/common.js +83 -24
  29. data/vendor/jsunittest/src/jsunittest.js +7 -998
  30. data/vendor/jsunittest/src/logger.js +6 -6
  31. data/vendor/jsunittest/src/message_template.js +1 -1
  32. data/vendor/jsunittest/src/prototype/event.js +2 -2
  33. data/vendor/jsunittest/src/prototype/template.js +7 -6
  34. data/vendor/jsunittest/src/runner.js +13 -12
  35. data/vendor/jsunittest/src/test_case.js +11 -6
  36. data/vendor/jsunittest/test/unit/assertions_test.html +9 -1
  37. data/vendor/jsunittest/test/unit/common_test.html +9 -1
  38. data/vendor/jsunittest/test/unit/logger_test.html +2 -2
  39. data/vendor/jsunittest/test/unit/message_template_test.html +2 -2
  40. data/vendor/jsunittest/test/unit/runner_test.html +2 -2
  41. data/vendor/jsunittest/test/unit/template_test.html +2 -2
  42. data/vendor/jsunittest/test/unit/test_case_test.html +24 -3
  43. data/vendor/jsunittest/website/images/logo_bundle.png +0 -0
  44. data/vendor/jsunittest/website/index.html +3 -3
  45. data/vendor/jsunittest/website/index.txt +1 -1
  46. data/vendor/jsunittest/website/stylesheets/screen.css +3 -0
  47. data/vendor/jsunittest/website/tmbundle/JavaScript Unit Testing.tmbundle.tar.gz +0 -0
  48. metadata +20 -14
  49. data/features/steps/env.rb +0 -6
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), "test_generator_helper.rb")
2
+
3
+ class TestNewjsScrewunitGenerator < Test::Unit::TestCase
4
+ include RubiGen::GeneratorTestHelper
5
+
6
+ def setup
7
+ bare_setup
8
+ end
9
+
10
+ def teardown
11
+ bare_teardown
12
+ end
13
+
14
+ # Some generator-related assertions:
15
+ # assert_generated_file(name, &block) # block passed the file contents
16
+ # assert_directory_exists(name)
17
+ # assert_generated_class(name, &block)
18
+ # assert_generated_module(name, &block)
19
+ # assert_generated_test_for(name, &block)
20
+ # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file
21
+ # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet)
22
+ #
23
+ # Other helper methods are:
24
+ # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files)
25
+ # bare_setup - place this in setup method to create the APP_ROOT folder for each test
26
+ # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test
27
+
28
+ def test_generator_without_options
29
+ run_generator('newjs_screwunit', [APP_ROOT], sources)
30
+ assert_directory_exists "path/to/included/folder"
31
+ assert_generated_file "path/to/included/folder/some_file"
32
+ end
33
+
34
+ private
35
+ def sources
36
+ [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path))
37
+ ]
38
+ end
39
+
40
+ def generator_path
41
+ "app_generators"
42
+ end
43
+ end
@@ -1,3 +1,12 @@
1
+ == 0.7.4 2009-07-27
2
+
3
+ * updated jsunittest.js against edge
4
+
5
+ == 0.7.3 2009-03-21
6
+
7
+ * New assertions: assertNotHasClass(element, class)
8
+ * Fixed algorithm for assertHasClass
9
+
1
10
  == 0.7.2 2008-10-08
2
11
 
3
12
  * AJAX callback from IE6 working [choan]
@@ -12,7 +12,7 @@ require 'rake/packagetask'
12
12
 
13
13
  $:.unshift File.dirname(__FILE__) + "/lib"
14
14
 
15
- APP_VERSION = '0.7.2'
15
+ APP_VERSION = '0.7.3'
16
16
  APP_NAME = 'jsunittest'
17
17
  RUBYFORGE_PROJECT = 'drnicjavascript'
18
18
  APP_FILE_NAME= "#{APP_NAME}.js"
@@ -0,0 +1,1042 @@
1
+ /* Jsunittest, version 0.7.3
2
+ * (c) 2008 Dr Nic Williams
3
+ *
4
+ * Jsunittest is freely distributable under
5
+ * the terms of an MIT-style license.
6
+ * For details, see the web site: http://jsunittest.rubyforge.org
7
+ *
8
+ *--------------------------------------------------------------------------*/
9
+
10
+ var JsUnitTest = {
11
+ Unit: {},
12
+ inspect: function(object) {
13
+ try {
14
+ if (typeof object == "undefined") {return 'undefined';}
15
+ if (object === null) {return 'null';}
16
+ if (typeof object == "string") {
17
+ var useDoubleQuotes = arguments[1];
18
+ var escapedString = this.gsub(object, /[\x00-\x1f\\]/, function(match) {
19
+ var character = {
20
+ '\b': '\\b',
21
+ '\t': '\\t',
22
+ '\n': '\\n',
23
+ '\f': '\\f',
24
+ '\r': '\\r',
25
+ '\\': '\\\\'
26
+ }[match[0]];
27
+ return character ? character : '\\u00' + JsUnitTest.toHexString(match[0].charCodeAt());
28
+ });
29
+ if (useDoubleQuotes) {return '"' + escapedString.replace(/"/g, '\\"') + '"';}
30
+ return "'" + escapedString.replace(/'/g, '\\\'') + "'";
31
+ }
32
+ if (JsUnitTest.getClass(object) === 'Object') {
33
+ var keys_values = new Array(), prefix = 'Object: { ';
34
+ for (property in object) {
35
+ keys_values.push(property + ': ' + object[property]);
36
+ }
37
+ return (prefix + keys_values.join(', ') + ' }');
38
+ }
39
+ return String(object);
40
+ } catch (e) {
41
+ if (e instanceof RangeError) {return '...';}
42
+ throw e;
43
+ }
44
+ },
45
+
46
+ getClass: function(object) {
47
+ return Object.prototype.toString.call(object)
48
+ .match(/^\[object\s(.*)\]$/)[1];
49
+ },
50
+
51
+ $: function(element) {
52
+ if (arguments.length > 1) {
53
+ for (var i = 0, elements = [], length = arguments.length; i < length; i++) {
54
+ elements.push(this.$(arguments[i]));
55
+ }
56
+ return elements;
57
+ }
58
+ if (typeof element == "string") {
59
+ element = document.getElementById(element);
60
+ }
61
+ return element;
62
+ },
63
+
64
+ gsub: function(source, pattern, replacement) {
65
+ var result = '', match;
66
+ replacement = arguments.callee.prepareReplacement(replacement);
67
+
68
+ while (source.length > 0) {
69
+ if (match = source.match(pattern)) {
70
+ result += source.slice(0, match.index);
71
+ result += JsUnitTest.String.interpret(replacement(match));
72
+ source = source.slice(match.index + match[0].length);
73
+ } else {
74
+ result += source, source = '';
75
+ }
76
+ }
77
+ return result;
78
+ },
79
+ scan: function(source, pattern, iterator) {
80
+ this.gsub(source, pattern, iterator);
81
+ return String(source);
82
+ },
83
+ escapeHTML: function(data) {
84
+ return data.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
85
+ },
86
+ toHexString : function(n) {
87
+ var string = n.toString(16);
88
+ return '00'.substring(string.length) + string;
89
+ },
90
+ arrayfromargs: function(args) {
91
+ var myarray = new Array();
92
+ var i;
93
+
94
+ for (i=0;i<args.length;i++) {
95
+ myarray[i] = args[i];
96
+ }
97
+
98
+ return myarray;
99
+ },
100
+
101
+ // from now we recursively zip & compare nested arrays
102
+ areArraysEqual: function(expected, actual) {
103
+ var expected_array = JsUnitTest.flattenArray(expected);
104
+ var actual_array = JsUnitTest.flattenArray(actual);
105
+ if (expected_array.length == actual_array.length) {
106
+ for (var i=0; i < expected_array.length; i++) {
107
+ if (expected_array[i] != actual_array[i]) {return false;}
108
+ }
109
+ return true;
110
+ }
111
+ return false;
112
+ },
113
+
114
+ areArraysNotEqual: function(expected, actual) {
115
+ return !this.areArraysEqual(expected, actual);
116
+ },
117
+
118
+ areHashesEqual: function(expected, actual) {
119
+ var expected_array = JsUnitTest.hashToSortedArray(expected);
120
+ var actual_array = JsUnitTest.hashToSortedArray(actual);
121
+ return this.areArraysEqual(expected_array, actual_array);
122
+ },
123
+
124
+ areHashesNotEqual: function(expected, actual) {
125
+ return !this.areHashesEqual(expected, actual);
126
+ },
127
+
128
+ hashToSortedArray: function(hash) {
129
+ var results = [];
130
+ for (key in hash) {
131
+ results.push([key, hash[key]]);
132
+ }
133
+ return results.sort();
134
+ },
135
+ flattenArray: function(array) {
136
+ var results = arguments[1] || [];
137
+ for (var i=0; i < array.length; i++) {
138
+ var object = array[i];
139
+ if (object != null && typeof object == "object" &&
140
+ 'splice' in object && 'join' in object) {
141
+ this.flattenArray(object, results);
142
+ } else {
143
+ results.push(object);
144
+ }
145
+ }
146
+ return results;
147
+ },
148
+ selectorMatch: function(expression, element) {
149
+ var tokens = [];
150
+ var patterns = {
151
+ // combinators must be listed first
152
+ // (and descendant needs to be last combinator)
153
+ laterSibling: /^\s*~\s*/,
154
+ child: /^\s*>\s*/,
155
+ adjacent: /^\s*\+\s*/,
156
+ descendant: /^\s/,
157
+
158
+ // selectors follow
159
+ tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
160
+ id: /^#([\w\-\*]+)(\b|$)/,
161
+ className: /^\.([\w\-\*]+)(\b|$)/,
162
+ pseudo:
163
+ /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
164
+ attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
165
+ attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
166
+ };
167
+
168
+ var assertions = {
169
+ tagName: function(element, matches) {
170
+ return matches[1].toUpperCase() == element.tagName.toUpperCase();
171
+ },
172
+
173
+ className: function(element, matches) {
174
+ return Element.hasClassName(element, matches[1]);
175
+ },
176
+
177
+ id: function(element, matches) {
178
+ return element.id === matches[1];
179
+ },
180
+
181
+ attrPresence: function(element, matches) {
182
+ return Element.hasAttribute(element, matches[1]);
183
+ },
184
+
185
+ attr: function(element, matches) {
186
+ var nodeValue = Element.readAttribute(element, matches[1]);
187
+ return nodeValue && operators[matches[2]](nodeValue, matches[5] || matches[6]);
188
+ }
189
+ };
190
+ var e = this.expression, ps = patterns, as = assertions;
191
+ var le, p, m;
192
+
193
+ while (e && le !== e && (/\S/).test(e)) {
194
+ le = e;
195
+ for (var i in ps) {
196
+ p = ps[i];
197
+ if (m = e.match(p)) {
198
+ // use the Selector.assertions methods unless the selector
199
+ // is too complex.
200
+ if (as[i]) {
201
+ tokens.push([i, Object.clone(m)]);
202
+ e = e.replace(m[0], '');
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ var match = true, name, matches;
209
+ for (var i = 0, token; token = tokens[i]; i++) {
210
+ name = token[0]; matches = token[1];
211
+ if (!assertions[name](element, matches)) {
212
+ match = false; break;
213
+ }
214
+ }
215
+
216
+ return match;
217
+ },
218
+
219
+ toQueryParams: function(query, separator) {
220
+ var query = query || window.location.search;
221
+ var match = query.replace(/^\s+/, '').replace(/\s+$/, '').match(/([^?#]*)(#.*)?$/);
222
+ if (!match) {return { };}
223
+
224
+ var hash = {};
225
+ var parts = match[1].split(separator || '&');
226
+ for (var i=0; i < parts.length; i++) {
227
+ var pair = parts[i].split('=');
228
+ if (pair[0]) {
229
+ var key = decodeURIComponent(pair.shift());
230
+ var value = pair.length > 1 ? pair.join('=') : pair[0];
231
+ if (value != undefined) {value = decodeURIComponent(value);}
232
+
233
+ if (key in hash) {
234
+ var object = hash[key];
235
+ var isArray = object != null && typeof object == "object" &&
236
+ 'splice' in object && 'join' in object;
237
+ if (!isArray) {hash[key] = [hash[key]];}
238
+ hash[key].push(value);
239
+ }
240
+ else {
241
+ hash[key] = value;
242
+ }
243
+ }
244
+ }
245
+ return hash;
246
+ },
247
+
248
+ String: {
249
+ interpret: function(value) {
250
+ return value == null ? '' : String(value);
251
+ }
252
+ }
253
+ };
254
+
255
+ JsUnitTest.gsub.prepareReplacement = function(replacement) {
256
+ if (typeof replacement == "function") {return replacement;}
257
+ var template = new Template(replacement);
258
+ return function(match) { return template.evaluate(match); };
259
+ };
260
+
261
+ JsUnitTest.Version = '0.7.3';
262
+
263
+ JsUnitTest.Template = function(template, pattern) {
264
+ this.template = template; //template.toString();
265
+ this.pattern = pattern || JsUnitTest.Template.Pattern;
266
+ };
267
+
268
+ JsUnitTest.Template.prototype.evaluate = function(object) {
269
+ if (typeof object.toTemplateReplacements == "function") {
270
+ object = object.toTemplateReplacements();
271
+ }
272
+
273
+ return JsUnitTest.gsub(this.template, this.pattern, function(match) {
274
+ if (object == null) {return '';}
275
+
276
+ var before = match[1] || '';
277
+ if (before == '\\') {return match[2];}
278
+
279
+ var ctx = object, expr = match[3];
280
+ var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
281
+ match = pattern.exec(expr);
282
+ if (match == null) {return before;}
283
+
284
+ while (match != null) {
285
+ var comp = (match[1].indexOf('[]') === 0) ? match[2].gsub('\\\\]', ']') : match[1];
286
+ ctx = ctx[comp];
287
+ if (null == ctx || '' == match[3]) {break;}
288
+ expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
289
+ match = pattern.exec(expr);
290
+ }
291
+
292
+ return before + JsUnitTest.String.interpret(ctx);
293
+ });
294
+ };
295
+
296
+ JsUnitTest.Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
297
+ JsUnitTest.Event = {};
298
+ // written by Dean Edwards, 2005
299
+ // with input from Tino Zijdel, Matthias Miller, Diego Perini
300
+ // namespaced by Dr Nic Williams 2008
301
+
302
+ // http://dean.edwards.name/weblog/2005/10/add-event/
303
+ // http://dean.edwards.name/weblog/2005/10/add-event2/
304
+ JsUnitTest.Event.addEvent = function(element, type, handler) {
305
+ if (element.addEventListener) {
306
+ element.addEventListener(type, handler, false);
307
+ } else {
308
+ // assign each event handler a unique ID
309
+ if (!handler.$$guid) {handler.$$guid = JsUnitTest.Event.addEvent.guid++;}
310
+ // create a hash table of event types for the element
311
+ if (!element.events) {element.events = {};}
312
+ // create a hash table of event handlers for each element/event pair
313
+ var handlers = element.events[type];
314
+ if (!handlers) {
315
+ handlers = element.events[type] = {};
316
+ // store the existing event handler (if there is one)
317
+ if (element["on" + type]) {
318
+ handlers[0] = element["on" + type];
319
+ }
320
+ }
321
+ // store the event handler in the hash table
322
+ handlers[handler.$$guid] = handler;
323
+ // assign a global event handler to do all the work
324
+ element["on" + type] = this.handleEvent;
325
+ }
326
+ };
327
+ // a counter used to create unique IDs
328
+ JsUnitTest.Event.addEvent.guid = 1;
329
+
330
+ JsUnitTest.Event.removeEvent = function(element, type, handler) {
331
+ if (element.removeEventListener) {
332
+ element.removeEventListener(type, handler, false);
333
+ } else {
334
+ // delete the event handler from the hash table
335
+ if (element.events && element.events[type]) {
336
+ delete element.events[type][handler.$$guid];
337
+ }
338
+ }
339
+ };
340
+
341
+ JsUnitTest.Event.handleEvent = function(event) {
342
+ var returnValue = true;
343
+ // grab the event object (IE uses a global event object)
344
+ event = event || JsUnitTest.Event.fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
345
+ // get a reference to the hash table of event handlers
346
+ var handlers = this.events[event.type];
347
+ // execute each event handler
348
+ for (var i in handlers) {
349
+ this.$$handleEvent = handlers[i];
350
+ if (this.$$handleEvent(event) === false) {
351
+ returnValue = false;
352
+ }
353
+ }
354
+ return returnValue;
355
+ };
356
+
357
+ JsUnitTest.Event.fixEvent = function(event) {
358
+ // add W3C standard event methods
359
+ event.preventDefault = this.fixEvent.preventDefault;
360
+ event.stopPropagation = this.fixEvent.stopPropagation;
361
+ return event;
362
+ };
363
+ JsUnitTest.Event.fixEvent.preventDefault = function() {
364
+ this.returnValue = false;
365
+ };
366
+ JsUnitTest.Event.fixEvent.stopPropagation = function() {
367
+ this.cancelBubble = true;
368
+ };
369
+
370
+ JsUnitTest.Unit.Logger = function(element) {
371
+ this.element = JsUnitTest.$(element);
372
+ if (this.element) {this._createLogTable();}
373
+ };
374
+
375
+ JsUnitTest.Unit.Logger.prototype.start = function(testName) {
376
+ if (!this.element) {return;}
377
+ var tbody = this.element.getElementsByTagName('tbody')[0];
378
+
379
+ var tr = document.createElement('tr');
380
+ var td;
381
+
382
+ //testname
383
+ td = document.createElement('td');
384
+ td.appendChild(document.createTextNode(testName));
385
+ tr.appendChild(td);
386
+
387
+ tr.appendChild(document.createElement('td'));//status
388
+ tr.appendChild(document.createElement('td'));//message
389
+
390
+ tbody.appendChild(tr);
391
+ };
392
+
393
+ JsUnitTest.Unit.Logger.prototype.setStatus = function(status) {
394
+ var logline = this.getLastLogLine();
395
+ logline.className = status;
396
+ var statusCell = logline.getElementsByTagName('td')[1];
397
+ statusCell.appendChild(document.createTextNode(status));
398
+ };
399
+
400
+ JsUnitTest.Unit.Logger.prototype.finish = function(status, summary) {
401
+ if (!this.element) {return;}
402
+ this.setStatus(status);
403
+ this.message(summary);
404
+ };
405
+
406
+ JsUnitTest.Unit.Logger.prototype.message = function(message) {
407
+ if (!this.element) {return;}
408
+ var cell = this.getMessageCell();
409
+
410
+ // cell.appendChild(document.createTextNode(this._toHTML(message)));
411
+ cell.innerHTML = this._toHTML(message);
412
+ };
413
+
414
+ JsUnitTest.Unit.Logger.prototype.summary = function(summary) {
415
+ if (!this.element) {return;}
416
+ var div = this.element.getElementsByTagName('div')[0];
417
+ div.innerHTML = this._toHTML(summary);
418
+ };
419
+
420
+ JsUnitTest.Unit.Logger.prototype.getLastLogLine = function() {
421
+ var tbody = this.element.getElementsByTagName('tbody')[0];
422
+ var loglines = tbody.getElementsByTagName('tr');
423
+ return loglines[loglines.length - 1];
424
+ };
425
+
426
+ JsUnitTest.Unit.Logger.prototype.getMessageCell = function() {
427
+ var logline = this.getLastLogLine();
428
+ return logline.getElementsByTagName('td')[2];
429
+ };
430
+
431
+ JsUnitTest.Unit.Logger.prototype._createLogTable = function() {
432
+ var html = '<div class="logsummary">running...</div>' +
433
+ '<table class="logtable">' +
434
+ '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
435
+ '<tbody class="loglines"></tbody>' +
436
+ '</table>';
437
+ this.element.innerHTML = html;
438
+ };
439
+
440
+ JsUnitTest.Unit.Logger.prototype.appendActionButtons = function(actions) {
441
+ // actions = $H(actions);
442
+ // if (!actions.any()) return;
443
+ // var div = new Element("div", {className: 'action_buttons'});
444
+ // actions.inject(div, function(container, action) {
445
+ // var button = new Element("input").setValue(action.key).observe("click", action.value);
446
+ // button.type = "button";
447
+ // return container.insert(button);
448
+ // });
449
+ // this.getMessageCell().insert(div);
450
+ };
451
+
452
+ JsUnitTest.Unit.Logger.prototype._toHTML = function(txt) {
453
+ return JsUnitTest.escapeHTML(txt).replace(/\n/g,"<br/>");
454
+ };
455
+ JsUnitTest.Unit.MessageTemplate = function(string) {
456
+ var parts = [];
457
+ var str = JsUnitTest.scan((string || ''), /(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
458
+ parts.push(part[0]);
459
+ });
460
+ this.parts = parts;
461
+ };
462
+
463
+ JsUnitTest.Unit.MessageTemplate.prototype.evaluate = function(params) {
464
+ var results = [];
465
+ for (var i=0; i < this.parts.length; i++) {
466
+ var part = this.parts[i];
467
+ var result = (part == '?') ? JsUnitTest.inspect(params.shift()) : part.replace(/\\\?/, '?');
468
+ results.push(result);
469
+ }
470
+ return results.join('');
471
+ };
472
+ // A generic function for performming AJAX requests
473
+ // It takes one argument, which is an object that contains a set of options
474
+ // All of which are outline in the comments, below
475
+ // From John Resig's book Pro JavaScript Techniques
476
+ // published by Apress, 2006-8
477
+ JsUnitTest.ajax = function( options ) {
478
+
479
+ // Load the options object with defaults, if no
480
+ // values were provided by the user
481
+ options = {
482
+ // The type of HTTP Request
483
+ type: options.type || "POST",
484
+
485
+ // The URL the request will be made to
486
+ url: options.url || "",
487
+
488
+ // How long to wait before considering the request to be a timeout
489
+ timeout: options.timeout || 5000,
490
+
491
+ // Functions to call when the request fails, succeeds,
492
+ // or completes (either fail or succeed)
493
+ onComplete: options.onComplete || function(){},
494
+ onError: options.onError || function(){},
495
+ onSuccess: options.onSuccess || function(){},
496
+
497
+ // The data type that'll be returned from the server
498
+ // the default is simply to determine what data was returned from the
499
+ // and act accordingly.
500
+ data: options.data || ""
501
+ };
502
+
503
+ // Create the request object
504
+ var xml = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
505
+
506
+ // Open the asynchronous POST request
507
+ xml.open(options.type, options.url, true);
508
+
509
+ // We're going to wait for a request for 5 seconds, before giving up
510
+ var timeoutLength = 5000;
511
+
512
+ // Keep track of when the request has been succesfully completed
513
+ var requestDone = false;
514
+
515
+ // Initalize a callback which will fire 5 seconds from now, cancelling
516
+ // the request (if it has not already occurred).
517
+ setTimeout(function(){
518
+ requestDone = true;
519
+ }, timeoutLength);
520
+
521
+ // Watch for when the state of the document gets updated
522
+ xml.onreadystatechange = function(){
523
+ // Wait until the data is fully loaded,
524
+ // and make sure that the request hasn't already timed out
525
+ if ( xml.readyState == 4 && !requestDone ) {
526
+
527
+ // Check to see if the request was successful
528
+ if ( httpSuccess( xml ) ) {
529
+
530
+ // Execute the success callback with the data returned from the server
531
+ options.onSuccess( httpData( xml, options.type ) );
532
+
533
+ // Otherwise, an error occurred, so execute the error callback
534
+ } else {
535
+ options.onError();
536
+ }
537
+
538
+ // Call the completion callback
539
+ options.onComplete();
540
+
541
+ // Clean up after ourselves, to avoid memory leaks
542
+ xml = null;
543
+ }
544
+ };
545
+
546
+ // Establish the connection to the server
547
+ xml.send(null);
548
+
549
+ // Determine the success of the HTTP response
550
+ function httpSuccess(r) {
551
+ try {
552
+ // If no server status is provided, and we're actually
553
+ // requesting a local file, then it was successful
554
+ return !r.status && location.protocol == "file:" ||
555
+
556
+ // Any status in the 200 range is good
557
+ ( r.status >= 200 && r.status < 300 ) ||
558
+
559
+ // Successful if the document has not been modified
560
+ r.status == 304 ||
561
+
562
+ // Safari returns an empty status if the file has not been modified
563
+ navigator.userAgent.indexOf("Safari") >= 0 && typeof r.status == "undefined";
564
+ } catch(e){}
565
+
566
+ // If checking the status failed, then assume that the request failed too
567
+ return false;
568
+ }
569
+
570
+ // Extract the correct data from the HTTP response
571
+ function httpData(r,type) {
572
+ // Get the content-type header
573
+ var ct = r.getResponseHeader("content-type");
574
+
575
+ // If no default type was provided, determine if some
576
+ // form of XML was returned from the server
577
+ var data = !type && ct && ct.indexOf("xml") >= 0;
578
+
579
+ // Get the XML Document object if XML was returned from
580
+ // the server, otherwise return the text contents returned by the server
581
+ data = type == "xml" || data ? r.responseXML : r.responseText;
582
+
583
+ // If the specified type is "script", execute the returned text
584
+ // response as if it was JavaScript
585
+ if ( type == "script" ) {
586
+ eval.call( window, data );
587
+ }
588
+
589
+ // Return the response data (either an XML Document or a text string)
590
+ return data;
591
+ }
592
+
593
+ };
594
+ JsUnitTest.Unit.Assertions = {
595
+ buildMessage: function(message, template) {
596
+ var args = JsUnitTest.arrayfromargs(arguments).slice(2);
597
+ return (message ? message + '\n' : '') +
598
+ new JsUnitTest.Unit.MessageTemplate(template).evaluate(args);
599
+ },
600
+
601
+ flunk: function(message) {
602
+ this.assertBlock(message || 'Flunked', function() { return false; });
603
+ },
604
+
605
+ assertBlock: function(message, block) {
606
+ try {
607
+ block.call(this) ? this.pass() : this.fail(message);
608
+ } catch(e) { this.error(e); }
609
+ },
610
+
611
+ assert: function(expression, message) {
612
+ message = this.buildMessage(message || 'assert', 'got <?>', expression);
613
+ this.assertBlock(message, function() { return expression; });
614
+ },
615
+
616
+ assertEqual: function(expected, actual, message) {
617
+ message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual);
618
+ this.assertBlock(message, function() { return expected == actual; });
619
+ },
620
+
621
+ assertNotEqual: function(expected, actual, message) {
622
+ message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual);
623
+ this.assertBlock(message, function() { return expected != actual; });
624
+ },
625
+
626
+ assertEnumEqual: function(expected, actual, message) {
627
+ message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual);
628
+ this.assertBlock(message, function() { return JsUnitTest.areArraysEqual(expected, actual); });
629
+ },
630
+
631
+ assertEnumNotEqual: function(expected, actual, message) {
632
+ message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual);
633
+ this.assertBlock(message, function() { return JsUnitTest.areArraysNotEqual(expected, actual); });
634
+ },
635
+
636
+ assertHashEqual: function(expected, actual, message) {
637
+ message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', JsUnitTest.inspect(expected), JsUnitTest.inspect(actual));
638
+ this.assertBlock(message, function() { return JsUnitTest.areHashesEqual(expected, actual); });
639
+ },
640
+
641
+ assertHashNotEqual: function(expected, actual, message) {
642
+ message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', JsUnitTest.inspect(expected), JsUnitTest.inspect(actual));
643
+ this.assertBlock(message, function() { return JsUnitTest.areHashesNotEqual(expected, actual); });
644
+ },
645
+
646
+ assertIdentical: function(expected, actual, message) {
647
+ message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual);
648
+ this.assertBlock(message, function() { return expected === actual; });
649
+ },
650
+
651
+ assertNotIdentical: function(expected, actual, message) {
652
+ message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual);
653
+ this.assertBlock(message, function() { return expected !== actual; });
654
+ },
655
+
656
+ assertNull: function(obj, message) {
657
+ message = this.buildMessage(message || 'assertNull', 'got <?>', obj);
658
+ this.assertBlock(message, function() { return obj === null; });
659
+ },
660
+
661
+ assertNotNull: function(obj, message) {
662
+ message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj);
663
+ this.assertBlock(message, function() { return obj !== null; });
664
+ },
665
+
666
+ assertUndefined: function(obj, message) {
667
+ message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj);
668
+ this.assertBlock(message, function() { return typeof obj == "undefined"; });
669
+ },
670
+
671
+ assertNotUndefined: function(obj, message) {
672
+ message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
673
+ this.assertBlock(message, function() { return typeof obj != "undefined"; });
674
+ },
675
+
676
+ assertNullOrUndefined: function(obj, message) {
677
+ message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
678
+ this.assertBlock(message, function() { return obj == null; });
679
+ },
680
+
681
+ assertNotNullOrUndefined: function(obj, message) {
682
+ message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
683
+ this.assertBlock(message, function() { return obj != null; });
684
+ },
685
+
686
+ assertMatch: function(expected, actual, message) {
687
+ message = this.buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual);
688
+ this.assertBlock(message, function() { return new RegExp(expected).exec(actual); });
689
+ },
690
+
691
+ assertNoMatch: function(expected, actual, message) {
692
+ message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
693
+ this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)); });
694
+ },
695
+
696
+ assertHasClass: function(element, klass, message) {
697
+ element = JsUnitTest.$(element);
698
+ message = this.buildMessage(message || 'assertHasClass', '? doesn\'t have class <?>.', element, klass);
699
+ this.assertBlock(message, function() {
700
+ var elementClassName = element.className;
701
+ return (elementClassName.length > 0 && (elementClassName == klass ||
702
+ new RegExp("(^|\\s)" + klass + "(\\s|$)").test(elementClassName)));
703
+ // return !!element.className.match(new RegExp(klass))
704
+ });
705
+ },
706
+
707
+ assertNotHasClass: function(element, klass, message) {
708
+ element = JsUnitTest.$(element);
709
+ message = this.buildMessage(message || 'assertNotHasClass', '? does have class <?>.', element, klass);
710
+ this.assertBlock(message, function() {
711
+ var elementClassName = element.className;
712
+ return !(elementClassName.length > 0 && (elementClassName == klass ||
713
+ new RegExp("(^|\\s)" + klass + "(\\s|$)").test(elementClassName)));
714
+ });
715
+ },
716
+
717
+ assertHidden: function(element, message) {
718
+ element = JsUnitTest.$(element);
719
+ message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
720
+ this.assertBlock(message, function() { return !element.style.display || element.style.display == 'none'; });
721
+ },
722
+
723
+ assertInstanceOf: function(expected, actual, message) {
724
+ message = this.buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual);
725
+ this.assertBlock(message, function() { return actual instanceof expected; });
726
+ },
727
+
728
+ assertNotInstanceOf: function(expected, actual, message) {
729
+ message = this.buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual);
730
+ this.assertBlock(message, function() { return !(actual instanceof expected); });
731
+ },
732
+
733
+ assertRespondsTo: function(method, obj, message) {
734
+ message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method);
735
+ this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function'); });
736
+ },
737
+
738
+ assertRaise: function(exceptionName, method, message) {
739
+ message = this.buildMessage(message || 'assertRaise', '<?> exception expected but none was raised', exceptionName);
740
+ var block = function() {
741
+ try {
742
+ method();
743
+ return false;
744
+ } catch(e) {
745
+ if (e.name == exceptionName) {return true;}
746
+ else {throw e;}
747
+ }
748
+ };
749
+ this.assertBlock(message, block);
750
+ },
751
+
752
+ assertNothingRaised: function(method, message) {
753
+ try {
754
+ method();
755
+ this.assert(true, "Expected nothing to be thrown");
756
+ } catch(e) {
757
+ message = this.buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e);
758
+ this.flunk(message);
759
+ }
760
+ },
761
+
762
+ _isVisible: function(element) {
763
+ element = JsUnitTest.$(element);
764
+ if(!element.parentNode) {return true;}
765
+ this.assertNotNull(element);
766
+ if(element.style && (element.style.display == 'none')) {
767
+ return false;
768
+ }
769
+
770
+ return arguments.callee.call(this, element.parentNode);
771
+ },
772
+
773
+ assertVisible: function(element, message) {
774
+ message = this.buildMessage(message, '? was not visible.', element);
775
+ this.assertBlock(message, function() { return this._isVisible(element); });
776
+ },
777
+
778
+ assertNotVisible: function(element, message) {
779
+ message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
780
+ this.assertBlock(message, function() { return !this._isVisible(element); });
781
+ },
782
+
783
+ assertElementsMatch: function() {
784
+ var pass = true, expressions = JsUnitTest.arrayfromargs(arguments);
785
+ var elements = expressions.shift();
786
+ if (elements.length != expressions.length) {
787
+ message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions);
788
+ this.flunk(message);
789
+ pass = false;
790
+ }
791
+ for (var i=0; i < expressions.length; i++) {
792
+ var expression = expressions[i];
793
+ var element = JsUnitTest.$(elements[i]);
794
+ if (JsUnitTest.selectorMatch(expression, element)) {
795
+ pass = true;
796
+ break;
797
+ }
798
+ message = this.buildMessage('assertElementsMatch', 'In index <?>: expected <?> but got ?', index, expression, element);
799
+ this.flunk(message);
800
+ pass = false;
801
+ }
802
+ this.assert(pass, "Expected all elements to match.");
803
+ },
804
+
805
+ assertElementMatches: function(element, expression, message) {
806
+ this.assertElementsMatch([element], expression);
807
+ }
808
+ };
809
+ JsUnitTest.Unit.Runner = function(testcases) {
810
+ var argumentOptions = arguments[1] || {};
811
+ var options = this.options = {};
812
+ options.testLog = ('testLog' in argumentOptions) ? argumentOptions.testLog : 'testlog';
813
+ options.resultsURL = this.queryParams.resultsURL;
814
+ options.testLog = JsUnitTest.$(options.testLog);
815
+
816
+ this.tests = this.getTests(testcases);
817
+ this.currentTest = 0;
818
+ this.logger = new JsUnitTest.Unit.Logger(options.testLog);
819
+
820
+ var self = this;
821
+ JsUnitTest.Event.addEvent(window, "load", function() {
822
+ setTimeout(function() {
823
+ self.runTests();
824
+ }, 0.1);
825
+ });
826
+ };
827
+
828
+ JsUnitTest.Unit.Runner.prototype.queryParams = JsUnitTest.toQueryParams();
829
+
830
+ JsUnitTest.Unit.Runner.prototype.portNumber = function() {
831
+ if (window.location.search.length > 0) {
832
+ var matches = window.location.search.match(/\:(\d{3,5})\//);
833
+ if (matches) {
834
+ return parseInt(matches[1], 10);
835
+ }
836
+ }
837
+ return null;
838
+ };
839
+
840
+ JsUnitTest.Unit.Runner.prototype.getTests = function(testcases) {
841
+ var tests = [], options = this.options;
842
+ if (this.queryParams.tests) {tests = this.queryParams.tests.split(',');}
843
+ else if (options.tests) {tests = options.tests;}
844
+ else if (options.test) {tests = [option.test];}
845
+ else {
846
+ for (testname in testcases) {
847
+ if (testname.match(/^test/)) {tests.push(testname);}
848
+ }
849
+ }
850
+ var results = [];
851
+ for (var i=0; i < tests.length; i++) {
852
+ var test = tests[i];
853
+ if (testcases[test]) {
854
+ results.push(
855
+ new JsUnitTest.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown)
856
+ );
857
+ }
858
+ }
859
+ return results;
860
+ };
861
+
862
+ JsUnitTest.Unit.Runner.prototype.getResult = function() {
863
+ var results = {
864
+ tests: this.tests.length,
865
+ assertions: 0,
866
+ failures: 0,
867
+ errors: 0,
868
+ warnings: 0
869
+ };
870
+
871
+ for (var i=0; i < this.tests.length; i++) {
872
+ var test = this.tests[i];
873
+ results.assertions += test.assertions;
874
+ results.failures += test.failures;
875
+ results.errors += test.errors;
876
+ results.warnings += test.warnings;
877
+ }
878
+ return results;
879
+ };
880
+
881
+ JsUnitTest.Unit.Runner.prototype.postResults = function() {
882
+ if (this.options.resultsURL) {
883
+ // new Ajax.Request(this.options.resultsURL,
884
+ // { method: 'get', parameters: this.getResult(), asynchronous: false });
885
+ var results = this.getResult();
886
+ var url = this.options.resultsURL + "?";
887
+ url += "tests="+ this.tests.length + "&";
888
+ url += "assertions="+ results.assertions + "&";
889
+ url += "warnings=" + results.warnings + "&";
890
+ url += "failures=" + results.failures + "&";
891
+ url += "errors=" + results.errors;
892
+ JsUnitTest.ajax({
893
+ url: url,
894
+ type: 'GET'
895
+ });
896
+ }
897
+ };
898
+
899
+ JsUnitTest.Unit.Runner.prototype.runTests = function() {
900
+ var test = this.tests[this.currentTest], actions;
901
+
902
+ if (!test) {return this.finish();}
903
+ if (!test.isWaiting) {this.logger.start(test.name);}
904
+ test.run();
905
+ var self = this;
906
+ if(test.isWaiting) {
907
+ this.logger.message("Waiting for " + test.timeToWait + "ms");
908
+ // setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
909
+ setTimeout(function() {
910
+ self.runTests();
911
+ }, test.timeToWait || 1000);
912
+ return;
913
+ }
914
+
915
+ this.logger.finish(test.status(), test.summary());
916
+ if (actions = test.actions) {this.logger.appendActionButtons(actions);}
917
+ this.currentTest++;
918
+ // tail recursive, hopefully the browser will skip the stackframe
919
+ this.runTests();
920
+ };
921
+
922
+ JsUnitTest.Unit.Runner.prototype.finish = function() {
923
+ this.postResults();
924
+ this.logger.summary(this.summary());
925
+ };
926
+
927
+ JsUnitTest.Unit.Runner.prototype.summary = function() {
928
+ return new JsUnitTest.Template('#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings').evaluate(this.getResult());
929
+ };
930
+ JsUnitTest.Unit.Testcase = function(name, test, setup, teardown) {
931
+ this.name = name;
932
+ this.test = test || function() {};
933
+ this.setup = setup || function() {};
934
+ this.teardown = teardown || function() {};
935
+ this.messages = [];
936
+ this.actions = {};
937
+ };
938
+ // import JsUnitTest.Unit.Assertions
939
+
940
+ for (method in JsUnitTest.Unit.Assertions) {
941
+ JsUnitTest.Unit.Testcase.prototype[method] = JsUnitTest.Unit.Assertions[method];
942
+ }
943
+
944
+ JsUnitTest.Unit.Testcase.prototype.isWaiting = false;
945
+ JsUnitTest.Unit.Testcase.prototype.timeToWait = 1000;
946
+ JsUnitTest.Unit.Testcase.prototype.assertions = 0;
947
+ JsUnitTest.Unit.Testcase.prototype.failures = 0;
948
+ JsUnitTest.Unit.Testcase.prototype.errors = 0;
949
+ JsUnitTest.Unit.Testcase.prototype.warnings = 0;
950
+ JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port;
951
+
952
+ // JsUnitTest.Unit.Testcase.prototype.isRunningFromRake = window.location.port == 4711;
953
+
954
+ JsUnitTest.Unit.Testcase.prototype.wait = function(time, nextPart) {
955
+ this.isWaiting = true;
956
+ this.test = nextPart;
957
+ this.timeToWait = time;
958
+ };
959
+
960
+ JsUnitTest.Unit.Testcase.prototype.run = function(rethrow) {
961
+ try {
962
+ try {
963
+ if (!this.isWaiting) {this.setup();}
964
+ this.isWaiting = false;
965
+ this.test();
966
+ } finally {
967
+ if(!this.isWaiting) {
968
+ this.teardown();
969
+ }
970
+ }
971
+ }
972
+ catch(e) {
973
+ if (rethrow) {throw e;}
974
+ this.error(e, this);
975
+ }
976
+ };
977
+
978
+ JsUnitTest.Unit.Testcase.prototype.summary = function() {
979
+ var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors, #{warnings} warnings\n';
980
+ return new JsUnitTest.Template(msg).evaluate(this) +
981
+ this.messages.join("\n");
982
+ };
983
+
984
+ JsUnitTest.Unit.Testcase.prototype.pass = function() {
985
+ this.assertions++;
986
+ };
987
+
988
+ JsUnitTest.Unit.Testcase.prototype.fail = function(message) {
989
+ this.failures++;
990
+ var line = "";
991
+ try {
992
+ throw new Error("stack");
993
+ } catch(e){
994
+ line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
995
+ }
996
+ this.messages.push("Failure: " + message + (line ? " Line #" + line : ""));
997
+ };
998
+
999
+ JsUnitTest.Unit.Testcase.prototype.warning = function(message) {
1000
+ this.warnings++;
1001
+ var line = "";
1002
+ try {
1003
+ throw new Error("stack");
1004
+ } catch(e){
1005
+ line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
1006
+ }
1007
+ this.messages.push("Warning: " + message + (line ? " Line #" + line : ""));
1008
+ };
1009
+ JsUnitTest.Unit.Testcase.prototype.warn = JsUnitTest.Unit.Testcase.prototype.warning;
1010
+
1011
+ JsUnitTest.Unit.Testcase.prototype.info = function(message) {
1012
+ this.messages.push("Info: " + message);
1013
+ };
1014
+
1015
+ JsUnitTest.Unit.Testcase.prototype.error = function(error, test) {
1016
+ this.errors++;
1017
+ this.actions['retry with throw'] = function() { test.run(true); };
1018
+ this.messages.push(error.name + ": "+ error.message + "(" + JsUnitTest.inspect(error) + ")");
1019
+ if( typeof console != "undefined" && console.error) {
1020
+ console.error("Test '" + test.name + "' died, exception and test follows");
1021
+ console.error(error);
1022
+ console.error(test.test.toString());
1023
+ }
1024
+ };
1025
+
1026
+ JsUnitTest.Unit.Testcase.prototype.status = function() {
1027
+ if (this.failures > 0) {return 'failed';}
1028
+ if (this.errors > 0) {return 'error';}
1029
+ if (this.warnings > 0) {return 'warning';}
1030
+ return 'passed';
1031
+ };
1032
+
1033
+ JsUnitTest.Unit.Testcase.prototype.benchmark = function(operation, iterations) {
1034
+ var startAt = new Date();
1035
+ (iterations || 1).times(operation);
1036
+ var timeTaken = ((new Date())-startAt);
1037
+ this.info((arguments[2] || 'Operation') + ' finished ' +
1038
+ iterations + ' iterations in ' + (timeTaken/1000)+'s' );
1039
+ return timeTaken;
1040
+ };
1041
+
1042
+ Test = JsUnitTest