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 +50 -0
- data/generators/seacucumber/seacucumber_generator.rb +14 -0
- data/generators/seacucumber/templates/javascript_template.js +1 -0
- data/generators/seacucumber/templates/seacucumber_template.html +30 -0
- data/generators/seacucumber/templates/test.css +44 -0
- data/generators/seacucumber/templates/unittest.js +566 -0
- data/init.rb +4 -0
- data/lib/seacucumber.rb +116 -0
- data/lib/seacucumber/browser.rb +39 -0
- data/lib/seacucumber/firefox_browser.rb +22 -0
- data/lib/seacucumber/ie_browser.rb +25 -0
- data/lib/seacucumber/konqueror_browser.rb +18 -0
- data/lib/seacucumber/safari_browser.rb +27 -0
- data/lib/seacucumber/version.rb +8 -0
- data/spec/browser_spec.rb +23 -0
- data/spec/firefox_browser_spec.rb +30 -0
- data/spec/ie_browser_spec.rb +20 -0
- data/spec/seacucumber_spec.rb +84 -0
- data/spec/spec_helper.rb +4 -0
- data/tasks/seacucumber.rake +23 -0
- metadata +75 -0
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
data/lib/seacucumber.rb
ADDED
@@ -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,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,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
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|