seacucumber 0.3.0

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.
data/README ADDED
@@ -0,0 +1,50 @@
1
+ = SEA CUCUMBER
2
+
3
+ by {Michael Ward}[http://m2ward.blogspot.com] and {Peter Ryan}[http://www.peterryan.net/]
4
+
5
+ == DESCRIPTION:
6
+
7
+ A gem that allows for in browser JavaScript Unit tests to be run as part of your build.
8
+
9
+ == SYNOPSIS:
10
+
11
+ SeaCucumber takes advantage of the JavaScript Unit testing provided by {Script.aculo.us}[http://script.aculo.us/].
12
+
13
+ == INSTALL:
14
+
15
+ You can download SeaCucumber from here[http://rubyforge.org/projects/seacucumber] or install it with the following command.
16
+
17
+ <tt>$ gem install seacucumber</tt>
18
+
19
+ To use as a Rails plug-in unpack the gem into your applications <tt>vendor/plugin</tt> directory:
20
+
21
+ <tt>$ gem unpack seacucumber</tt>
22
+
23
+ == USAGE:
24
+
25
+ todo
26
+
27
+ == LICENSE:
28
+
29
+ (The MIT License)
30
+
31
+ Copyright (c) 2007 The SeaCucumber Team
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining
34
+ a copy of this software and associated documentation files (the
35
+ 'Software'), to deal in the Software without restriction, including
36
+ without limitation the rights to use, copy, modify, merge, publish,
37
+ distribute, sublicense, and/or sell copies of the Software, and to
38
+ permit persons to whom the Software is furnished to do so, subject to
39
+ the following conditions:
40
+
41
+ The above copyright notice and this permission notice shall be
42
+ included in all copies or substantial portions of the Software.
43
+
44
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
45
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
46
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
47
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
48
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
49
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
50
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
1
+ class SeacucumberGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.directory File.join('test/javascripts')
5
+ m.directory File.join('public/javascripts')
6
+
7
+ m.file 'unittest.js', File.join("test", "javascripts", "unittest.js")
8
+ m.file 'test.css', File.join("test", "javascripts", "test.css")
9
+
10
+ m.template 'javascript_template.js', File.join("public", "javascripts", "#{file_name}.js")
11
+ m.template 'seacucumber_template.html', File.join("test", "javascripts", "#{file_name}_test.html")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1 @@
1
+ This is a sample javascript!
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <title><%= name %></title>
6
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
7
+ <script src="../../public/javascripts/prototype.js" type="text/javascript"></script>
8
+ <script src="../../public/javascripts/<%= file_name %>.js" type="text/javascript"></script>
9
+ <script src="unittest.js" type="text/javascript"></script>
10
+ <link rel="stylesheet" href="test.css" type="text/css"/>
11
+ </head>
12
+ <body>
13
+ <h1><%= name %> SeaCucumber Test</h1>
14
+ <!-- Log output -->
15
+ <div id="testlog"></div>
16
+ <!-- Tests follow -->
17
+ <script type="text/javascript" language="javascript" charset="utf-8">
18
+ // <![CDATA[
19
+ new Test.Unit.Runner({
20
+
21
+ testSomething: function() {
22
+ with (this) {
23
+ assertEqual("seacucumber", "seacucumber");
24
+ }
25
+ }
26
+ });
27
+ // ]]>
28
+ </script>
29
+ </body>
30
+ </html>
@@ -0,0 +1,44 @@
1
+ body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li {
2
+ font-family: sans-serif;
3
+ }
4
+
5
+ body {
6
+ font-size:0.8em;
7
+ }
8
+
9
+ #log {
10
+ padding-bottom: 1em;
11
+ border-bottom: 2px solid #000;
12
+ margin-bottom: 2em;
13
+ }
14
+
15
+ #logsummary {
16
+ margin-bottom: 1em;
17
+ padding: 1ex;
18
+ border: 1px solid #000;
19
+ font-weight: bold;
20
+ }
21
+
22
+ #logtable {
23
+ width:100%;
24
+ border-collapse: collapse;
25
+ border: 1px dotted #666;
26
+ }
27
+
28
+ #logtable td, #logtable th {
29
+ text-align: left;
30
+ padding: 3px 8px;
31
+ border: 1px dotted #666;
32
+ }
33
+
34
+ #logtable .passed {
35
+ background-color: #cfc;
36
+ }
37
+
38
+ #logtable .failed, #logtable .error {
39
+ background-color: #fcc;
40
+ }
41
+
42
+ #logtable .nameCell {
43
+ cursor: pointer;
44
+ }
@@ -0,0 +1,566 @@
1
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
3
+ // (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
4
+ //
5
+ // script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+ // For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+ // experimental, Firefox-only
9
+ Event.simulateMouse = function(element, eventName) {
10
+ var options = Object.extend({
11
+ pointerX: 0,
12
+ pointerY: 0,
13
+ buttons: 0,
14
+ ctrlKey: false,
15
+ altKey: false,
16
+ shiftKey: false,
17
+ metaKey: false
18
+ }, arguments[2] || {});
19
+ var oEvent = document.createEvent("MouseEvents");
20
+ oEvent.initMouseEvent(eventName, true, true, document.defaultView,
21
+ options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
22
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
23
+
24
+ if(this.mark) Element.remove(this.mark);
25
+ this.mark = document.createElement('div');
26
+ this.mark.appendChild(document.createTextNode(" "));
27
+ document.body.appendChild(this.mark);
28
+ this.mark.style.position = 'absolute';
29
+ this.mark.style.top = options.pointerY + "px";
30
+ this.mark.style.left = options.pointerX + "px";
31
+ this.mark.style.width = "5px";
32
+ this.mark.style.height = "5px;";
33
+ this.mark.style.borderTop = "1px solid red;"
34
+ this.mark.style.borderLeft = "1px solid red;"
35
+
36
+ if(this.step)
37
+ alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
38
+
39
+ $(element).dispatchEvent(oEvent);
40
+ };
41
+
42
+ // Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
43
+ // You need to downgrade to 1.0.4 for now to get this working
44
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
45
+ Event.simulateKey = function(element, eventName) {
46
+ var options = Object.extend({
47
+ ctrlKey: false,
48
+ altKey: false,
49
+ shiftKey: false,
50
+ metaKey: false,
51
+ keyCode: 0,
52
+ charCode: 0
53
+ }, arguments[2] || {});
54
+
55
+ var oEvent = document.createEvent("KeyEvents");
56
+ oEvent.initKeyEvent(eventName, true, true, window,
57
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
58
+ options.keyCode, options.charCode );
59
+ $(element).dispatchEvent(oEvent);
60
+ };
61
+
62
+ Event.simulateKeys = function(element, command) {
63
+ for(var i=0; i<command.length; i++) {
64
+ Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
65
+ }
66
+ };
67
+
68
+ var Test = {}
69
+ Test.Unit = {};
70
+
71
+ // security exception workaround
72
+ Test.Unit.inspect = Object.inspect;
73
+
74
+ Test.Unit.Logger = Class.create();
75
+ Test.Unit.Logger.prototype = {
76
+ initialize: function(log) {
77
+ this.log = $(log);
78
+ if (this.log) {
79
+ this._createLogTable();
80
+ }
81
+ },
82
+ start: function(testName) {
83
+ if (!this.log) return;
84
+ this.testName = testName;
85
+ this.lastLogLine = document.createElement('tr');
86
+ this.statusCell = document.createElement('td');
87
+ this.nameCell = document.createElement('td');
88
+ this.nameCell.className = "nameCell";
89
+ this.nameCell.appendChild(document.createTextNode(testName));
90
+ this.messageCell = document.createElement('td');
91
+ this.lastLogLine.appendChild(this.statusCell);
92
+ this.lastLogLine.appendChild(this.nameCell);
93
+ this.lastLogLine.appendChild(this.messageCell);
94
+ this.loglines.appendChild(this.lastLogLine);
95
+ },
96
+ finish: function(status, summary) {
97
+ if (!this.log) return;
98
+ this.lastLogLine.className = status;
99
+ this.statusCell.innerHTML = status;
100
+ this.messageCell.innerHTML = this._toHTML(summary);
101
+ this.addLinksToResults();
102
+ },
103
+ message: function(message) {
104
+ if (!this.log) return;
105
+ this.messageCell.innerHTML = this._toHTML(message);
106
+ },
107
+ summary: function(summary) {
108
+ if (!this.log) return;
109
+ this.logsummary.innerHTML = this._toHTML(summary);
110
+ },
111
+ _createLogTable: function() {
112
+ this.log.innerHTML =
113
+ '<div id="logsummary"></div>' +
114
+ '<table id="logtable">' +
115
+ '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
116
+ '<tbody id="loglines"></tbody>' +
117
+ '</table>';
118
+ this.logsummary = $('logsummary')
119
+ this.loglines = $('loglines');
120
+ },
121
+ _toHTML: function(txt) {
122
+ return txt.escapeHTML().replace(/\n/g,"<br/>");
123
+ },
124
+ addLinksToResults: function(){
125
+ $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
126
+ td.title = "Run only this test"
127
+ Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
128
+ });
129
+ $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
130
+ td.title = "Run all tests"
131
+ Event.observe(td, 'click', function(){ window.location.search = "";});
132
+ });
133
+ }
134
+ }
135
+
136
+ Test.Unit.Runner = Class.create();
137
+ Test.Unit.Runner.prototype = {
138
+ initialize: function(testcases) {
139
+ this.options = Object.extend({
140
+ testLog: 'testlog'
141
+ }, arguments[1] || {});
142
+ this.options.resultsURL = this.parseResultsURLQueryParameter();
143
+ this.options.tests = this.parseTestsQueryParameter();
144
+ if (this.options.testLog) {
145
+ this.options.testLog = $(this.options.testLog) || null;
146
+ }
147
+ if(this.options.tests) {
148
+ this.tests = [];
149
+ for(var i = 0; i < this.options.tests.length; i++) {
150
+ if(/^test/.test(this.options.tests[i])) {
151
+ this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
152
+ }
153
+ }
154
+ } else {
155
+ if (this.options.test) {
156
+ this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
157
+ } else {
158
+ this.tests = [];
159
+ for(var testcase in testcases) {
160
+ if(/^test/.test(testcase)) {
161
+ this.tests.push(
162
+ new Test.Unit.Testcase(
163
+ this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
164
+ testcases[testcase], testcases["setup"], testcases["teardown"]
165
+ ));
166
+ }
167
+ }
168
+ }
169
+ }
170
+ this.currentTest = 0;
171
+ this.logger = new Test.Unit.Logger(this.options.testLog);
172
+ setTimeout(this.runTests.bind(this), 1000);
173
+ },
174
+ parseResultsURLQueryParameter: function() {
175
+ return window.location.search.parseQuery()["resultsURL"];
176
+ },
177
+ parseTestsQueryParameter: function(){
178
+ if (window.location.search.parseQuery()["tests"]){
179
+ return window.location.search.parseQuery()["tests"].split(',');
180
+ };
181
+ },
182
+ // Returns:
183
+ // "ERROR" if there was an error,
184
+ // "FAILURE" if there was a failure, or
185
+ // "SUCCESS" if there was neither
186
+ getResult: function() {
187
+ var hasFailure = false;
188
+ for(var i=0;i<this.tests.length;i++) {
189
+ if (this.tests[i].errors > 0) {
190
+ return "ERROR";
191
+ }
192
+ if (this.tests[i].failures > 0) {
193
+ hasFailure = true;
194
+ }
195
+ }
196
+ if (hasFailure) {
197
+ return "FAILURE";
198
+ } else {
199
+ return "SUCCESS";
200
+ }
201
+ },
202
+ postResults: function() {
203
+ if (this.options.resultsURL) {
204
+ new Ajax.Request(this.options.resultsURL,
205
+ { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
206
+ }
207
+ },
208
+ runTests: function() {
209
+ var test = this.tests[this.currentTest];
210
+ if (!test) {
211
+ // finished!
212
+ this.postResults();
213
+ this.logger.summary(this.summary());
214
+ return;
215
+ }
216
+ if(!test.isWaiting) {
217
+ this.logger.start(test.name);
218
+ }
219
+ test.run();
220
+ if(test.isWaiting) {
221
+ this.logger.message("Waiting for " + test.timeToWait + "ms");
222
+ setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
223
+ } else {
224
+ this.logger.finish(test.status(), test.summary());
225
+ this.currentTest++;
226
+ // tail recursive, hopefully the browser will skip the stackframe
227
+ this.runTests();
228
+ }
229
+ },
230
+ summary: function() {
231
+ var assertions = 0;
232
+ var failures = 0;
233
+ var errors = 0;
234
+ var messages = [];
235
+ for(var i=0;i<this.tests.length;i++) {
236
+ assertions += this.tests[i].assertions;
237
+ failures += this.tests[i].failures;
238
+ errors += this.tests[i].errors;
239
+ }
240
+ return (
241
+ (this.options.context ? this.options.context + ': ': '') +
242
+ this.tests.length + " tests, " +
243
+ assertions + " assertions, " +
244
+ failures + " failures, " +
245
+ errors + " errors");
246
+ }
247
+ }
248
+
249
+ Test.Unit.Assertions = Class.create();
250
+ Test.Unit.Assertions.prototype = {
251
+ initialize: function() {
252
+ this.assertions = 0;
253
+ this.failures = 0;
254
+ this.errors = 0;
255
+ this.messages = [];
256
+ },
257
+ summary: function() {
258
+ return (
259
+ this.assertions + " assertions, " +
260
+ this.failures + " failures, " +
261
+ this.errors + " errors" + "\n" +
262
+ this.messages.join("\n"));
263
+ },
264
+ pass: function() {
265
+ this.assertions++;
266
+ },
267
+ fail: function(message) {
268
+ this.failures++;
269
+ this.messages.push("Failure: " + message);
270
+ },
271
+ info: function(message) {
272
+ this.messages.push("Info: " + message);
273
+ },
274
+ error: function(error) {
275
+ this.errors++;
276
+ this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
277
+ },
278
+ status: function() {
279
+ if (this.failures > 0) return 'failed';
280
+ if (this.errors > 0) return 'error';
281
+ return 'passed';
282
+ },
283
+ assert: function(expression) {
284
+ var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
285
+ try { expression ? this.pass() :
286
+ this.fail(message); }
287
+ catch(e) { this.error(e); }
288
+ },
289
+ assertEqual: function(expected, actual) {
290
+ var message = arguments[2] || "assertEqual";
291
+ try { (expected == actual) ? this.pass() :
292
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
293
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
294
+ catch(e) { this.error(e); }
295
+ },
296
+ assertInspect: function(expected, actual) {
297
+ var message = arguments[2] || "assertInspect";
298
+ try { (expected == actual.inspect()) ? this.pass() :
299
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
300
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
301
+ catch(e) { this.error(e); }
302
+ },
303
+ assertEnumEqual: function(expected, actual) {
304
+ var message = arguments[2] || "assertEnumEqual";
305
+ try { $A(expected).length == $A(actual).length &&
306
+ expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
307
+ this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
308
+ ', actual ' + Test.Unit.inspect(actual)); }
309
+ catch(e) { this.error(e); }
310
+ },
311
+ assertNotEqual: function(expected, actual) {
312
+ var message = arguments[2] || "assertNotEqual";
313
+ try { (expected != actual) ? this.pass() :
314
+ this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
315
+ catch(e) { this.error(e); }
316
+ },
317
+ assertIdentical: function(expected, actual) {
318
+ var message = arguments[2] || "assertIdentical";
319
+ try { (expected === actual) ? this.pass() :
320
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
321
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
322
+ catch(e) { this.error(e); }
323
+ },
324
+ assertNotIdentical: function(expected, actual) {
325
+ var message = arguments[2] || "assertNotIdentical";
326
+ try { !(expected === actual) ? this.pass() :
327
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
328
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
329
+ catch(e) { this.error(e); }
330
+ },
331
+ assertNull: function(obj) {
332
+ var message = arguments[1] || 'assertNull'
333
+ try { (obj==null) ? this.pass() :
334
+ this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
335
+ catch(e) { this.error(e); }
336
+ },
337
+ assertMatch: function(expected, actual) {
338
+ var message = arguments[2] || 'assertMatch';
339
+ var regex = new RegExp(expected);
340
+ try { (regex.exec(actual)) ? this.pass() :
341
+ this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
342
+ catch(e) { this.error(e); }
343
+ },
344
+ assertHidden: function(element) {
345
+ var message = arguments[1] || 'assertHidden';
346
+ this.assertEqual("none", element.style.display, message);
347
+ },
348
+ assertNotNull: function(object) {
349
+ var message = arguments[1] || 'assertNotNull';
350
+ this.assert(object != null, message);
351
+ },
352
+ assertType: function(expected, actual) {
353
+ var message = arguments[2] || 'assertType';
354
+ try {
355
+ (actual.constructor == expected) ? this.pass() :
356
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
357
+ '", actual "' + (actual.constructor) + '"'); }
358
+ catch(e) { this.error(e); }
359
+ },
360
+ assertNotOfType: function(expected, actual) {
361
+ var message = arguments[2] || 'assertNotOfType';
362
+ try {
363
+ (actual.constructor != expected) ? this.pass() :
364
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
365
+ '", actual "' + (actual.constructor) + '"'); }
366
+ catch(e) { this.error(e); }
367
+ },
368
+ assertInstanceOf: function(expected, actual) {
369
+ var message = arguments[2] || 'assertInstanceOf';
370
+ try {
371
+ (actual instanceof expected) ? this.pass() :
372
+ this.fail(message + ": object was not an instance of the expected type"); }
373
+ catch(e) { this.error(e); }
374
+ },
375
+ assertNotInstanceOf: function(expected, actual) {
376
+ var message = arguments[2] || 'assertNotInstanceOf';
377
+ try {
378
+ !(actual instanceof expected) ? this.pass() :
379
+ this.fail(message + ": object was an instance of the not expected type"); }
380
+ catch(e) { this.error(e); }
381
+ },
382
+ assertRespondsTo: function(method, obj) {
383
+ var message = arguments[2] || 'assertRespondsTo';
384
+ try {
385
+ (obj[method] && typeof obj[method] == 'function') ? this.pass() :
386
+ this.fail(message + ": object doesn't respond to [" + method + "]"); }
387
+ catch(e) { this.error(e); }
388
+ },
389
+ assertReturnsTrue: function(method, obj) {
390
+ var message = arguments[2] || 'assertReturnsTrue';
391
+ try {
392
+ var m = obj[method];
393
+ if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
394
+ m() ? this.pass() :
395
+ this.fail(message + ": method returned false"); }
396
+ catch(e) { this.error(e); }
397
+ },
398
+ assertReturnsFalse: function(method, obj) {
399
+ var message = arguments[2] || 'assertReturnsFalse';
400
+ try {
401
+ var m = obj[method];
402
+ if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
403
+ !m() ? this.pass() :
404
+ this.fail(message + ": method returned true"); }
405
+ catch(e) { this.error(e); }
406
+ },
407
+ assertRaise: function(exceptionName, method) {
408
+ var message = arguments[2] || 'assertRaise';
409
+ try {
410
+ method();
411
+ this.fail(message + ": exception expected but none was raised"); }
412
+ catch(e) {
413
+ ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e);
414
+ }
415
+ },
416
+ assertElementsMatch: function() {
417
+ var expressions = $A(arguments), elements = $A(expressions.shift());
418
+ if (elements.length != expressions.length) {
419
+ this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
420
+ return false;
421
+ }
422
+ elements.zip(expressions).all(function(pair, index) {
423
+ var element = $(pair.first()), expression = pair.last();
424
+ if (element.match(expression)) return true;
425
+ this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
426
+ }.bind(this)) && this.pass();
427
+ },
428
+ assertElementMatches: function(element, expression) {
429
+ this.assertElementsMatch([element], expression);
430
+ },
431
+ benchmark: function(operation, iterations) {
432
+ var startAt = new Date();
433
+ (iterations || 1).times(operation);
434
+ var timeTaken = ((new Date())-startAt);
435
+ this.info((arguments[2] || 'Operation') + ' finished ' +
436
+ iterations + ' iterations in ' + (timeTaken/1000)+'s' );
437
+ return timeTaken;
438
+ },
439
+ _isVisible: function(element) {
440
+ element = $(element);
441
+ if(!element.parentNode) return true;
442
+ this.assertNotNull(element);
443
+ if(element.style && Element.getStyle(element, 'display') == 'none')
444
+ return false;
445
+
446
+ return this._isVisible(element.parentNode);
447
+ },
448
+ assertNotVisible: function(element) {
449
+ this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
450
+ },
451
+ assertVisible: function(element) {
452
+ this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
453
+ },
454
+ benchmark: function(operation, iterations) {
455
+ var startAt = new Date();
456
+ (iterations || 1).times(operation);
457
+ var timeTaken = ((new Date())-startAt);
458
+ this.info((arguments[2] || 'Operation') + ' finished ' +
459
+ iterations + ' iterations in ' + (timeTaken/1000)+'s' );
460
+ return timeTaken;
461
+ }
462
+ }
463
+
464
+ Test.Unit.Testcase = Class.create();
465
+ Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
466
+ initialize: function(name, test, setup, teardown) {
467
+ Test.Unit.Assertions.prototype.initialize.bind(this)();
468
+ this.name = name;
469
+
470
+ if(typeof test == 'string') {
471
+ test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
472
+ test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
473
+ this.test = function() {
474
+ eval('with(this){'+test+'}');
475
+ }
476
+ } else {
477
+ this.test = test || function() {};
478
+ }
479
+
480
+ this.setup = setup || function() {};
481
+ this.teardown = teardown || function() {};
482
+ this.isWaiting = false;
483
+ this.timeToWait = 1000;
484
+ },
485
+ wait: function(time, nextPart) {
486
+ this.isWaiting = true;
487
+ this.test = nextPart;
488
+ this.timeToWait = time;
489
+ },
490
+ run: function() {
491
+ try {
492
+ try {
493
+ if (!this.isWaiting) this.setup.bind(this)();
494
+ this.isWaiting = false;
495
+ this.test.bind(this)();
496
+ } finally {
497
+ if(!this.isWaiting) {
498
+ this.teardown.bind(this)();
499
+ }
500
+ }
501
+ }
502
+ catch(e) { this.error(e); }
503
+ }
504
+ });
505
+
506
+ // *EXPERIMENTAL* BDD-style testing to please non-technical folk
507
+ // This draws many ideas from RSpec http://rspec.rubyforge.org/
508
+
509
+ Test.setupBDDExtensionMethods = function(){
510
+ var METHODMAP = {
511
+ shouldEqual: 'assertEqual',
512
+ shouldNotEqual: 'assertNotEqual',
513
+ shouldEqualEnum: 'assertEnumEqual',
514
+ shouldBeA: 'assertType',
515
+ shouldNotBeA: 'assertNotOfType',
516
+ shouldBeAn: 'assertType',
517
+ shouldNotBeAn: 'assertNotOfType',
518
+ shouldBeNull: 'assertNull',
519
+ shouldNotBeNull: 'assertNotNull',
520
+
521
+ shouldBe: 'assertReturnsTrue',
522
+ shouldNotBe: 'assertReturnsFalse',
523
+ shouldRespondTo: 'assertRespondsTo'
524
+ };
525
+ var makeAssertion = function(assertion, args, object) {
526
+ this[assertion].apply(this,(args || []).concat([object]));
527
+ }
528
+
529
+ Test.BDDMethods = {};
530
+ $H(METHODMAP).each(function(pair) {
531
+ Test.BDDMethods[pair.key] = function() {
532
+ var args = $A(arguments);
533
+ var scope = args.shift();
534
+ makeAssertion.apply(scope, [pair.value, args, this]); };
535
+ });
536
+
537
+ [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
538
+ function(p){ Object.extend(p, Test.BDDMethods) }
539
+ );
540
+ }
541
+
542
+ Test.context = function(name, spec, log){
543
+ Test.setupBDDExtensionMethods();
544
+
545
+ var compiledSpec = {};
546
+ var titles = {};
547
+ for(specName in spec) {
548
+ switch(specName){
549
+ case "setup":
550
+ case "teardown":
551
+ compiledSpec[specName] = spec[specName];
552
+ break;
553
+ default:
554
+ var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
555
+ var body = spec[specName].toString().split('\n').slice(1);
556
+ if(/^\{/.test(body[0])) body = body.slice(1);
557
+ body.pop();
558
+ body = body.map(function(statement){
559
+ return statement.strip()
560
+ });
561
+ compiledSpec[testName] = body.join('\n');
562
+ titles[testName] = specName;
563
+ }
564
+ }
565
+ new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
566
+ };
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ # Placeholder to satisfy Rails.
2
+ #
3
+ # Do NOT add any require statements to this file. Doing
4
+ # so will cause Rails to load this plugin all of the time.
@@ -0,0 +1,116 @@
1
+ require 'thread'
2
+ require 'webrick'
3
+
4
+ require 'rake'
5
+ require 'rake/tasklib'
6
+
7
+ # Load Custom Classes
8
+ Dir[File.join(File.dirname(__FILE__), 'seacucumber/**/*.rb')].sort.each { |lib| require lib }
9
+
10
+ module SeaCucumber
11
+
12
+ class TestRunner < ::Rake::TaskLib
13
+
14
+ # browsers to be tested
15
+ @browsers = {
16
+ :firefox => FirefoxBrowser.new,
17
+ :ie => IEBrowser.new,
18
+ :konqueror => KonquerorBrowser.new,
19
+ :safari => SafariBrowser.new
20
+ }
21
+
22
+ # configurable options for this task
23
+ @options = {
24
+ :name => :test,
25
+ :host => 'localhost',
26
+ :port => 4711
27
+ }
28
+
29
+ # Make class instance variables accesible
30
+ class << self; attr_accessor :options, :browsers; end
31
+
32
+ def initialize(&block)
33
+ @name = options[:name]
34
+ @host = options[:host]
35
+ @port = options[:port]
36
+ @tests = []
37
+ @queue = Queue.new
38
+
39
+ define(&block)
40
+ end
41
+
42
+ def define
43
+ task @name do
44
+ result = []
45
+
46
+ # Starting the server...
47
+ # NOTE: this must be within the task, otherwise the server will be started regardless of whether the task was called
48
+ puts "Starting WEBrick server (listening on #{@port})..."
49
+ @server = WEBrick::HTTPServer.new(:Port => @port)
50
+ @server.mount_proc("/results") do |req, res|
51
+ @queue.push(req.query['result'])
52
+ res.body = "OK"
53
+ end
54
+
55
+ yield self if block_given?
56
+
57
+ trap("INT") { @server.shutdown }
58
+ thread = Thread.new { @server.start }
59
+
60
+ # run all combinations of browsers and tests
61
+ browsers.values.each do |browser|
62
+ if browser.supported?
63
+ browser.setup
64
+ @tests.each do |test|
65
+ browser.visit("http://#{@host}:#{@port}/#{test}?resultsURL=http://#{@host}:#{@port}/results&t=" + ("%.6f" % Time.now.to_f))
66
+ result = @queue.pop
67
+ puts "Testing '#{test}' on #{browser}: #{result}"
68
+ end
69
+ else
70
+ puts "Skipping #{browser}, not supported on this OS"
71
+ end
72
+
73
+ browser.teardown
74
+ end
75
+
76
+ @server.shutdown
77
+ thread.join
78
+ end
79
+ end
80
+
81
+ def mount(path, dir=nil)
82
+ dir = Dir.pwd + path unless dir
83
+
84
+ @server.mount(path, WEBrick::HTTPServlet::FileHandler, dir)
85
+ end
86
+
87
+ # test should be specified as a url
88
+ def run(test)
89
+ @tests << test
90
+ end
91
+
92
+ private
93
+
94
+ def options
95
+ self.class.options
96
+ end
97
+
98
+ def browsers
99
+ self.class.browsers
100
+ end
101
+
102
+ end
103
+ end
104
+
105
+ #:enddoc:
106
+ # silence webrick
107
+ class ::WEBrick::HTTPServer
108
+ def access_log(config, req, res)
109
+ # nop
110
+ end
111
+ end
112
+ class ::WEBrick::BasicLog
113
+ def log(level, data)
114
+ # nop
115
+ end
116
+ end
@@ -0,0 +1,39 @@
1
+ require 'rbconfig'
2
+
3
+ module SeaCucumber
4
+
5
+ class Browser
6
+ def supported?
7
+ true
8
+ end
9
+
10
+ def setup; end
11
+
12
+ def open(url); end
13
+
14
+ def teardown; end
15
+
16
+ def host
17
+ Config::CONFIG['host']
18
+ end
19
+
20
+ def macos?
21
+ host.include?('darwin')
22
+ end
23
+
24
+ def windows?
25
+ host.include?('mswin')
26
+ end
27
+
28
+ def linux?
29
+ host.include?('linux')
30
+ end
31
+
32
+ def applescript(script)
33
+ raise "Can't run AppleScript on #{host}" unless macos?
34
+ system "osascript -e '#{script}' 2>&1 >/dev/null"
35
+ end
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,22 @@
1
+ module SeaCucumber
2
+
3
+ class FirefoxBrowser < Browser
4
+ def initialize(path='c:\Program Files\Mozilla Firefox')
5
+ @path = path
6
+ end
7
+
8
+ def visit(url)
9
+ applescript('tell application "Firefox" to Get URL "' + url + '"') if macos?
10
+
11
+ system("start /D \"#{@path}\" firefox.exe \"#{url}\"") if windows?
12
+
13
+ system("firefox #{url}") if linux?
14
+ end
15
+
16
+ def to_s
17
+ "Firefox"
18
+ end
19
+ end
20
+
21
+ end
22
+
@@ -0,0 +1,25 @@
1
+ module SeaCucumber
2
+
3
+ class IEBrowser < Browser
4
+ def initialize(path='C:\Program Files\Internet Explorer\IEXPLORE.EXE')
5
+ @path = path
6
+ end
7
+
8
+ def supported?
9
+ windows?
10
+ end
11
+
12
+ def visit(url)
13
+ system("start \"#{@path}\" \"#{url}\"") if windows?
14
+ end
15
+
16
+ def teardown
17
+ system("TASKKILL /F /IM IEXPLORE.EXE")
18
+ end
19
+
20
+ def to_s
21
+ "Internet Explorer"
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,18 @@
1
+ module SeaCucumber
2
+
3
+ class KonquerorBrowser < Browser
4
+ def supported?
5
+ linux?
6
+ end
7
+
8
+ def visit(url)
9
+ system("kfmclient openURL #{url}")
10
+ end
11
+
12
+ def to_s
13
+ "Konqueror"
14
+ end
15
+ end
16
+
17
+ end
18
+
@@ -0,0 +1,27 @@
1
+ module SeaCucumber
2
+
3
+ # TODO need to update this to support Safari on Windows
4
+ class SafariBrowser < Browser
5
+ def supported?
6
+ macos?
7
+ end
8
+
9
+ def setup
10
+ applescript('tell application "Safari" to make new document')
11
+ end
12
+
13
+ def visit(url)
14
+ applescript('tell application "Safari" to set URL of front document to "' + url + '"')
15
+ end
16
+
17
+ def teardown
18
+ #applescript('tell application "Safari" to close front document')
19
+ end
20
+
21
+ def to_s
22
+ "Safari"
23
+ end
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,8 @@
1
+ module SeaCucumber # :nodoc:
2
+ module VERSION # :nodoc:
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ TINY = 0
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe SeaCucumber::Browser do
4
+
5
+ it "should be able to determine OS" do
6
+ browser = SeaCucumber::Browser.new
7
+
8
+ browser.stub!(:host).and_return("fake")
9
+ browser.macos?.should == false
10
+ browser.windows?.should == false
11
+ browser.linux?.should == false
12
+
13
+ browser.stub!(:host).and_return("i686-apple-darwin8.9.1")
14
+ browser.macos?.should == true
15
+
16
+ browser.stub!(:host).and_return("xxx_mswin_xxx1")
17
+ browser.windows?.should == true
18
+
19
+ browser.stub!(:host).and_return("xxx_linux_xxx1")
20
+ browser.linux?.should == true
21
+ end
22
+
23
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe SeaCucumber::FirefoxBrowser do
4
+
5
+ it "visit(url) should be specific depending on host OS" do
6
+ firefox = SeaCucumber::FirefoxBrowser.new
7
+
8
+ # OSX
9
+ firefox.stub!(:macos?).and_return(true)
10
+ firefox.stub!(:windows?).and_return(false)
11
+ firefox.stub!(:linux?).and_return(false)
12
+ firefox.should_receive(:applescript).with("tell application \"Firefox\" to Get URL \"osx_url\"")
13
+ firefox.visit("osx_url")
14
+
15
+ # Windows
16
+ firefox.stub!(:windows?).and_return(true)
17
+ firefox.stub!(:macos?).and_return(false)
18
+ firefox.stub!(:linux?).and_return(false)
19
+ firefox.should_receive(:system).with("start /D \"c:\\Program Files\\Mozilla Firefox\" firefox.exe \"windows_url\"")
20
+ firefox.visit("windows_url")
21
+
22
+ # Linux
23
+ firefox.stub!(:linux?).and_return(true)
24
+ firefox.stub!(:windows?).and_return(false)
25
+ firefox.stub!(:macos?).and_return(false)
26
+ firefox.should_receive(:system).with("firefox linux_url")
27
+ firefox.visit("linux_url")
28
+ end
29
+
30
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe SeaCucumber::IEBrowser do
4
+
5
+ it "visit(url) should make correct system call" do
6
+ ie = SeaCucumber::IEBrowser.new
7
+ ie.should_receive(:windows?).and_return(true)
8
+ ie.should_receive(:system).with(%{start "C:\\Program Files\\Internet Explorer\\IEXPLORE.EXE" "my url"})
9
+
10
+ ie.visit("my url")
11
+ end
12
+
13
+ it "teardown should make correct system call" do
14
+ ie = SeaCucumber::IEBrowser.new
15
+ ie.should_receive(:system).with(%{TASKKILL /F /IM IEXPLORE.EXE})
16
+
17
+ ie.teardown
18
+ end
19
+
20
+ end
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe SeaCucumber::TestRunner do
4
+
5
+ it "should handle all browsers" do
6
+ browsers = SeaCucumber::TestRunner.browsers
7
+
8
+ browsers.length.should == 4
9
+ browsers[:firefox].class.should == SeaCucumber::FirefoxBrowser
10
+ browsers[:ie].class.should == SeaCucumber::IEBrowser
11
+ browsers[:konqueror].class.should == SeaCucumber::KonquerorBrowser
12
+ browsers[:safari].class.should == SeaCucumber::SafariBrowser
13
+ end
14
+
15
+ it "should initialize WEBrick server and exexute all browsers" do
16
+ SeaCucumber::TestRunner.browsers.clear
17
+
18
+ # Adding two tests to be run
19
+ runner = SeaCucumber::TestRunner.new do |t|
20
+ t.run("test_one")
21
+ t.run("test_two")
22
+ end
23
+
24
+ runner.instance_variable_get(:@queue) << "result for test one"
25
+ runner.instance_variable_get(:@queue) << "result for test two"
26
+
27
+ Time.stub!(:now).and_return(99)
28
+
29
+ # Supported browser
30
+ supported = mock('supported browser')
31
+ supported.should_receive(:supported?).and_return(true)
32
+ supported.should_receive(:setup)
33
+ supported.should_receive(:visit).with("http://localhost:4711/test_one?resultsURL=http://localhost:4711/results&t=99.000000")
34
+ supported.should_receive(:visit).with("http://localhost:4711/test_two?resultsURL=http://localhost:4711/results&t=99.000000")
35
+ supported.should_receive(:teardown)
36
+
37
+ # Unsupported browser
38
+ unsupported = mock('unsupported browser')
39
+ unsupported.should_receive(:supported?).and_return(false)
40
+ unsupported.should_receive(:teardown)
41
+
42
+ SeaCucumber::TestRunner.browsers[:supported] = supported
43
+ SeaCucumber::TestRunner.browsers[:unsupported] = unsupported
44
+
45
+ webrick = mock('webrick server')
46
+ webrick.should_receive(:mount_proc).with('/results')
47
+ webrick.should_receive(:start)
48
+ webrick.should_receive(:shutdown)
49
+
50
+ WEBrick::HTTPServer.should_receive(:new).with(:Port => 4711).and_return(webrick)
51
+
52
+ # Assert output messages
53
+ runner.should_receive(:puts).with("Starting WEBrick server (listening on 4711)...")
54
+ runner.should_receive(:puts).with(%r{^Testing 'test_one' on #{supported}: result for test one})
55
+ runner.should_receive(:puts).with(%r{^Testing 'test_two' on #{supported}: result for test two})
56
+ runner.should_receive(:puts).with(%r{Skipping #{unsupported}, not supported on this OS})
57
+
58
+ Rake::Task[:test].invoke
59
+ end
60
+
61
+ it "should provide default values for name, host and port" do
62
+ runner = SeaCucumber::TestRunner.new
63
+
64
+ runner.instance_variable_get(:@name).should == :test
65
+ runner.instance_variable_get(:@host).should == 'localhost'
66
+ runner.instance_variable_get(:@port).should == 4711
67
+ end
68
+
69
+ # This test needs to be run last because it changes the values for the class instance variables
70
+ it "should be able to override default options" do
71
+ SeaCucumber::TestRunner.options[:name] = :foobar
72
+ SeaCucumber::TestRunner.options[:host] = 'www.thoughtworks.com'
73
+ SeaCucumber::TestRunner.options[:port] = 8080
74
+
75
+ runner = SeaCucumber::TestRunner.new
76
+
77
+ runner.instance_variable_get(:@name).should == :foobar
78
+ runner.instance_variable_get(:@host).should == 'www.thoughtworks.com'
79
+ runner.instance_variable_get(:@port).should == 8080
80
+ end
81
+
82
+ end
83
+
84
+
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require File.dirname(__FILE__) + '/../lib/seacucumber'
@@ -0,0 +1,23 @@
1
+ require 'seacucumber'
2
+
3
+ namespace :js do
4
+
5
+ desc "Run and collect results for all JavaScript unit tests"
6
+ ::SeaCucumber::TestRunner.new do |task|
7
+
8
+ # Mount all directories under public
9
+ FileList['public/**/**'].each do |file|
10
+ task.mount("/#{file}") if File.directory?(file)
11
+ end
12
+
13
+ # Mount javascript test directory
14
+ task.mount("/test/javascripts")
15
+
16
+ # Find all tests and run them
17
+ FileList['test/javascripts/*_test.html'].each do |file|
18
+ task.run(file)
19
+ end
20
+
21
+ end
22
+
23
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: seacucumber
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.3.0
7
+ date: 2008-01-22 00:00:00 -06:00
8
+ summary: allows for in browser JavaScript Unit testing directly from your Rake script
9
+ require_paths:
10
+ - lib
11
+ email: seacucumber-developer@rubyforge.org
12
+ homepage: http://seacucumber.rubyforge.org
13
+ rubyforge_project: seacucumber
14
+ description:
15
+ autorequire: seacucumber
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Michael Ward, Peter Ryan
31
+ files:
32
+ - lib/seacucumber/browser.rb
33
+ - lib/seacucumber/firefox_browser.rb
34
+ - lib/seacucumber/ie_browser.rb
35
+ - lib/seacucumber/konqueror_browser.rb
36
+ - lib/seacucumber/safari_browser.rb
37
+ - lib/seacucumber/version.rb
38
+ - lib/seacucumber.rb
39
+ - spec/browser_spec.rb
40
+ - spec/firefox_browser_spec.rb
41
+ - spec/ie_browser_spec.rb
42
+ - spec/seacucumber_spec.rb
43
+ - spec/spec_helper.rb
44
+ - tasks/seacucumber.rake
45
+ - generators/seacucumber
46
+ - generators/seacucumber/seacucumber_generator.rb
47
+ - generators/seacucumber/templates
48
+ - generators/seacucumber/templates/javascript_template.js
49
+ - generators/seacucumber/templates/seacucumber_template.html
50
+ - generators/seacucumber/templates/test.css
51
+ - generators/seacucumber/templates/unittest.js
52
+ - init.rb
53
+ - README
54
+ test_files: []
55
+
56
+ rdoc_options: []
57
+
58
+ extra_rdoc_files:
59
+ - README
60
+ executables: []
61
+
62
+ extensions: []
63
+
64
+ requirements: []
65
+
66
+ dependencies:
67
+ - !ruby/object:Gem::Dependency
68
+ name: rake
69
+ version_requirement:
70
+ version_requirements: !ruby/object:Gem::Version::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 0.7.0
75
+ version: