visionmedia-jspec 2.8.4 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +21 -0
- data/Manifest +5 -0
- data/README.rdoc +36 -10
- data/Rakefile +2 -0
- data/bin/jspec +84 -24
- data/jspec.gemspec +9 -3
- data/lib/jspec.jquery.js +3 -3
- data/lib/jspec.js +188 -134
- data/lib/jspec.xhr.js +62 -53
- data/server/browsers.rb +222 -10
- data/server/helpers.rb +82 -0
- data/server/routes.rb +57 -0
- data/server/server.rb +70 -82
- data/spec/server.rb +2 -0
- data/spec/spec.jquery.js +3 -3
- data/spec/spec.matchers.js +1 -0
- data/spec/spec.server.html +5 -5
- data/spec/spec.xhr.js +6 -1
- data/templates/default/spec/server.rb +4 -0
- data/templates/default/spec/spec.server.html +4 -4
- data/templates/rails/server.rb +4 -0
- data/templates/rails/spec.server.html +4 -4
- metadata +27 -2
data/server/browsers.rb
CHANGED
@@ -1,16 +1,228 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
#--
|
5
|
+
# Browser
|
6
|
+
#++
|
7
|
+
|
8
|
+
class Browser
|
9
|
+
|
10
|
+
##
|
11
|
+
# Check if the user agent _string_ matches this browser.
|
12
|
+
|
13
|
+
def self.matches_agent? string; end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Check if the browser matches the name _string_.
|
17
|
+
|
18
|
+
def self.matches_name? string; end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Subclasses.
|
22
|
+
|
23
|
+
def self.subclasses
|
24
|
+
@subclasses ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Stack subclasses.
|
29
|
+
|
30
|
+
def self.inherited subclass
|
31
|
+
subclasses << subclass
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Weither or not the browser is supported.
|
36
|
+
|
37
|
+
def supported?; true end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Server setup.
|
41
|
+
|
42
|
+
def setup; end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Server teardown.
|
46
|
+
|
47
|
+
def teardown; end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Host environment.
|
51
|
+
|
52
|
+
def host
|
53
|
+
Config::CONFIG['host']
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Check if we are using macos.
|
58
|
+
|
59
|
+
def macos?
|
60
|
+
host.include? 'darwin'
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Check if we are using windows.
|
65
|
+
|
66
|
+
def windows?
|
67
|
+
host.include? 'mswin'
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Check if we are using linux.
|
72
|
+
|
73
|
+
def linux?
|
74
|
+
host.include? 'linux'
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Run applescript _code_.
|
79
|
+
|
80
|
+
def applescript code
|
81
|
+
raise "Can't run AppleScript on #{host}" unless macos?
|
82
|
+
system "osascript -e '#{code}' 2>&1 >/dev/null"
|
83
|
+
end
|
84
|
+
|
85
|
+
#--
|
86
|
+
# Firefox
|
87
|
+
#++
|
88
|
+
|
89
|
+
class Firefox < self
|
90
|
+
def self.matches_agent? string
|
91
|
+
string =~ /firefox/i
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.matches_name? string
|
95
|
+
string =~ /ff|firefox|mozilla/i
|
96
|
+
end
|
97
|
+
|
98
|
+
def visit uri
|
99
|
+
system "open -g -a Firefox '#{uri}'" if macos?
|
100
|
+
system "firefox #{uri}" if linux?
|
101
|
+
system "#{File.join(ENV['ProgramFiles'] || 'c:\Program Files', '\Mozilla Firefox\firefox.exe')} #{uri}" if windows?
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
'Firefox'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#--
|
110
|
+
# Safari
|
111
|
+
#++
|
112
|
+
|
113
|
+
class Safari < self
|
114
|
+
def self.matches_agent? string
|
115
|
+
string =~ /safari/i && string !~ /chrome/i
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.matches_name? string
|
119
|
+
string =~ /safari/i
|
120
|
+
end
|
121
|
+
|
122
|
+
def supported?
|
123
|
+
macos?
|
124
|
+
end
|
125
|
+
|
126
|
+
def setup
|
127
|
+
applescript 'tell application "Safari" to make new document'
|
128
|
+
end
|
129
|
+
|
130
|
+
def visit uri
|
131
|
+
applescript 'tell application "Safari" to set URL of front document to "' + uri + '"'
|
132
|
+
end
|
133
|
+
|
134
|
+
def matches_agent? string
|
135
|
+
string =~ /safari/i
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_s
|
139
|
+
'Safari'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
#--
|
144
|
+
# Chrome
|
145
|
+
#++
|
146
|
+
|
147
|
+
class Chrome < self
|
148
|
+
def self.matches_agent? string
|
149
|
+
string =~ /chrome/i
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.matches_name? string
|
153
|
+
string =~ /google|chrome/i
|
154
|
+
end
|
155
|
+
|
156
|
+
def supported?
|
157
|
+
macos?
|
158
|
+
end
|
159
|
+
|
160
|
+
def visit uri
|
161
|
+
system "open -g -a Chromium #{uri}" if macos?
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_s
|
165
|
+
'Chrome'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#--
|
170
|
+
# Internet Explorer
|
171
|
+
#++
|
172
|
+
|
173
|
+
class IE < self
|
174
|
+
def self.matches_agent? string
|
175
|
+
string =~ /microsoft/i
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.matches_name? string
|
179
|
+
string =~ /ie|explorer/i
|
6
180
|
end
|
7
181
|
|
8
|
-
def
|
9
|
-
|
182
|
+
def supported?
|
183
|
+
windows?
|
10
184
|
end
|
11
185
|
|
12
|
-
|
13
|
-
|
14
|
-
|
186
|
+
def setup
|
187
|
+
require 'win32ole'
|
188
|
+
end
|
189
|
+
|
190
|
+
def visit uri
|
191
|
+
ie = WIN32OLE.new 'InternetExplorer.Application'
|
192
|
+
ie.visible = true
|
193
|
+
ie.Navigate uri
|
194
|
+
while ie.ReadyState != 4 do
|
195
|
+
sleep 1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def to_s
|
200
|
+
'Internet Explorer'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
#--
|
205
|
+
# Opera
|
206
|
+
#++
|
207
|
+
|
208
|
+
class Opera < self
|
209
|
+
def self.matches_agent? string
|
210
|
+
string =~ /opera/i
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.matches_name? string
|
214
|
+
string =~ /opera/i
|
215
|
+
end
|
216
|
+
|
217
|
+
def visit uri
|
218
|
+
system "open -g -a Opera #{uri}" if macos?
|
219
|
+
system "c:\Program Files\Opera\Opera.exe #{uri}" if windows?
|
220
|
+
system "opera #{uri}" if linux?
|
221
|
+
end
|
222
|
+
|
223
|
+
def to_s
|
224
|
+
'Opera'
|
225
|
+
end
|
15
226
|
end
|
16
|
-
|
227
|
+
|
228
|
+
end
|
data/server/helpers.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
helpers do
|
3
|
+
|
4
|
+
##
|
5
|
+
# Return dotted assertion graph for _assertions_.
|
6
|
+
|
7
|
+
def assertion_graph_for assertions
|
8
|
+
return if assertions.empty?
|
9
|
+
assertions.map do |assertion|
|
10
|
+
assertion['passed'] ? green('.') : red('.')
|
11
|
+
end.join
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Override Sinatra's #send_file to prevent caching.
|
16
|
+
|
17
|
+
def send_file path, opts = {}
|
18
|
+
stat = File.stat(path)
|
19
|
+
response['Cache-Control'] = 'no-cache'
|
20
|
+
content_type media_type(opts[:type]) ||
|
21
|
+
media_type(File.extname(path)) ||
|
22
|
+
response['Content-Type'] ||
|
23
|
+
'application/octet-stream'
|
24
|
+
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
|
25
|
+
|
26
|
+
if opts[:disposition] == 'attachment' || opts[:filename]
|
27
|
+
attachment opts[:filename] || path
|
28
|
+
elsif opts[:disposition] == 'inline'
|
29
|
+
response['Content-Disposition'] = 'inline'
|
30
|
+
end
|
31
|
+
|
32
|
+
halt ::Sinatra::Application::StaticFile.open(path, 'rb')
|
33
|
+
rescue Errno::ENOENT
|
34
|
+
not_found
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Find the browser name for the current user agent.
|
39
|
+
|
40
|
+
def browser_name
|
41
|
+
Browser.subclasses.find do |browser|
|
42
|
+
browser.matches_agent? env['HTTP_USER_AGENT']
|
43
|
+
end.new
|
44
|
+
rescue
|
45
|
+
'Unknown'
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Wrap _string_ with ansi escape sequence using _code_.
|
50
|
+
|
51
|
+
def color string, code
|
52
|
+
"\e[#{code}m#{string}\e[0m"
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Bold _string_.
|
57
|
+
|
58
|
+
def bold string
|
59
|
+
color string, 1
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Color _string_ red.
|
64
|
+
|
65
|
+
def red string
|
66
|
+
color string, 31
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Color _string_ green.
|
71
|
+
|
72
|
+
def green string
|
73
|
+
color string, 32
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Color _string_ blue.
|
78
|
+
|
79
|
+
def blue string
|
80
|
+
color string, 34
|
81
|
+
end
|
82
|
+
end
|
data/server/routes.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
get '/jspec/*' do |path|
|
3
|
+
send_file JSPEC_ROOT + '/lib/' + path
|
4
|
+
end
|
5
|
+
|
6
|
+
post '/results' do
|
7
|
+
require 'json'
|
8
|
+
data = JSON.parse request.body.read
|
9
|
+
if data['options'].include?('verbose') && data['options']['verbose'] ||
|
10
|
+
data['options'].include?('failuresOnly') && data['options']['failuresOnly']
|
11
|
+
puts "\n\n %s Passes: %s Failures: %s\n\n" % [
|
12
|
+
bold(browser_name),
|
13
|
+
green(data['stats']['passes']),
|
14
|
+
red(data['stats']['failures'])]
|
15
|
+
data['results'].compact.each do |suite|
|
16
|
+
specs = suite['specs'].compact.map do |spec|
|
17
|
+
case spec['status'].to_sym
|
18
|
+
when :pass
|
19
|
+
next if data['options'].include?('failuresOnly') && data['options']['failuresOnly']
|
20
|
+
' ' + green(spec['description']) + assertion_graph_for(spec['assertions']).to_s + "\n"
|
21
|
+
when :fail
|
22
|
+
" #{red(spec['description'])}\n #{spec['message']}\n\n"
|
23
|
+
else
|
24
|
+
" #{blue(spec['description'])}\n"
|
25
|
+
end
|
26
|
+
end.join
|
27
|
+
unless specs.strip.empty?
|
28
|
+
puts "\n " + bold(suite['description'])
|
29
|
+
puts specs
|
30
|
+
end
|
31
|
+
end
|
32
|
+
else
|
33
|
+
puts "%20s Passes: %s Failures: %s" % [
|
34
|
+
bold(browser_name),
|
35
|
+
green(data['stats']['passes']),
|
36
|
+
red(data['stats']['failures'])]
|
37
|
+
end
|
38
|
+
halt 200
|
39
|
+
end
|
40
|
+
|
41
|
+
get '/*' do |path|
|
42
|
+
pass unless File.exists?(path)
|
43
|
+
send_file path
|
44
|
+
end
|
45
|
+
|
46
|
+
#--
|
47
|
+
# Simulation Routes
|
48
|
+
#++
|
49
|
+
|
50
|
+
get '/slow/*' do |seconds|
|
51
|
+
sleep seconds.to_i
|
52
|
+
halt 200
|
53
|
+
end
|
54
|
+
|
55
|
+
get '/status/*' do |code|
|
56
|
+
halt code.to_i
|
57
|
+
end
|
data/server/server.rb
CHANGED
@@ -1,100 +1,88 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
require '
|
2
|
+
$:.unshift File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require 'sinatra'
|
5
|
+
require 'thread'
|
6
|
+
require 'browsers'
|
7
|
+
require 'helpers'
|
8
|
+
require 'routes'
|
5
9
|
|
6
10
|
module JSpec
|
7
11
|
class Server
|
8
|
-
attr_reader :responses, :browsers, :root
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
@browsers = options.delete :browsers
|
13
|
-
@root = options.delete :root
|
14
|
-
end
|
13
|
+
##
|
14
|
+
# Suite HTML.
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
def browser string
|
41
|
-
case string
|
42
|
-
when /Safari/ ; :Safari
|
43
|
-
when /Firefox/ ; :Firefox
|
44
|
-
when /MSIE/ ; :MSIE
|
45
|
-
when /Opera/ ; :Opera
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def bold string
|
50
|
-
color string, 1
|
51
|
-
end
|
52
|
-
|
53
|
-
def red string
|
54
|
-
color string, 31
|
55
|
-
end
|
56
|
-
|
57
|
-
def green string
|
58
|
-
color string, 32
|
59
|
-
end
|
60
|
-
|
61
|
-
def color string, code
|
62
|
-
"\e[#{code}m#{string}\e[m"
|
16
|
+
attr_accessor :suite
|
17
|
+
|
18
|
+
##
|
19
|
+
# Host string.
|
20
|
+
|
21
|
+
attr_reader :host
|
22
|
+
|
23
|
+
##
|
24
|
+
# Port number.
|
25
|
+
|
26
|
+
attr_reader :port
|
27
|
+
|
28
|
+
##
|
29
|
+
# Server instance.
|
30
|
+
|
31
|
+
attr_reader :server
|
32
|
+
|
33
|
+
##
|
34
|
+
# Initialize.
|
35
|
+
|
36
|
+
def initialize suite, port
|
37
|
+
@suite, @port, @host = suite, port, :localhost
|
63
38
|
end
|
64
39
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
40
|
+
##
|
41
|
+
# URI formed by the given host and port.
|
42
|
+
|
43
|
+
def uri
|
44
|
+
'http://%s:%d' % [host, port]
|
70
45
|
end
|
71
46
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
47
|
+
##
|
48
|
+
# Start the server with _browsers_ which defaults to all supported browsers.
|
49
|
+
|
50
|
+
def start browsers = nil
|
51
|
+
browsers ||= Browser.subclasses.map { |browser| browser.new }
|
52
|
+
browsers.map do |browser|
|
53
|
+
Thread.new {
|
54
|
+
sleep 1
|
55
|
+
if browser.supported?
|
56
|
+
browser.setup
|
57
|
+
browser.visit uri + '/' + suite
|
58
|
+
browser.teardown
|
59
|
+
end
|
83
60
|
}
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
self
|
61
|
+
end.push(Thread.new {
|
62
|
+
start!
|
63
|
+
}).reverse.each { |thread| thread.join }
|
88
64
|
end
|
89
65
|
|
90
|
-
|
91
|
-
|
92
|
-
|
66
|
+
private
|
67
|
+
|
68
|
+
#:nodoc:
|
69
|
+
|
70
|
+
def start!
|
71
|
+
Sinatra::Application.class_eval do
|
72
|
+
begin
|
73
|
+
$stderr.puts 'Started JSpec server at http://%s:%d' % [host, port.to_i]
|
74
|
+
detect_rack_handler.run self, :Host => host, :Port => port do |server|
|
75
|
+
trap 'INT' do
|
76
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
77
|
+
end
|
78
|
+
end
|
79
|
+
rescue Errno::EADDRINUSE
|
80
|
+
raise "Port #{port} already in use"
|
81
|
+
rescue Errno::EACCES
|
82
|
+
raise "Permission Denied on port #{port}"
|
83
|
+
end
|
93
84
|
end
|
94
85
|
end
|
95
86
|
|
96
|
-
def self.browser name
|
97
|
-
eval("JSpec::Browser::#{name}").new
|
98
|
-
end
|
99
87
|
end
|
100
88
|
end
|
data/spec/server.rb
ADDED
data/spec/spec.jquery.js
CHANGED
@@ -20,16 +20,16 @@ describe 'jQuery'
|
|
20
20
|
|
21
21
|
describe 'async'
|
22
22
|
it 'should load mah cookies (textfile)'
|
23
|
-
$.
|
23
|
+
$.get('async', function(text){
|
24
24
|
text.should_eql 'cookies!'
|
25
25
|
})
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'should load mah cookies twice (ensure multiple async requests work)'
|
29
|
-
$.
|
29
|
+
$.get('async', function(text){
|
30
30
|
text.should.eql 'cookies!'
|
31
31
|
})
|
32
|
-
$.
|
32
|
+
$.get('async', function(text){
|
33
33
|
text.should.not.eql 'rawr'
|
34
34
|
})
|
35
35
|
end
|
data/spec/spec.matchers.js
CHANGED
data/spec/spec.server.html
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
<html>
|
2
2
|
<head>
|
3
3
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
|
4
|
-
<script src="jspec.js"></script>
|
5
|
-
<script src="jspec.jquery.js"></script>
|
6
|
-
<script src="jspec.xhr.js"></script>
|
4
|
+
<script src="/jspec/jspec.js"></script>
|
5
|
+
<script src="/jspec/jspec.jquery.js"></script>
|
6
|
+
<script src="/jspec/jspec.xhr.js"></script>
|
7
7
|
<script src="modules.js"></script>
|
8
8
|
<script src="spec.grammar-less.js"></script>
|
9
9
|
<script>
|
@@ -19,8 +19,8 @@
|
|
19
19
|
.exec('spec.modules.js')
|
20
20
|
.exec('spec.xhr.js')
|
21
21
|
.exec('spec.jquery.xhr.js')
|
22
|
-
.run()
|
23
|
-
.
|
22
|
+
.run({ formatter : JSpec.formatters.Server })
|
23
|
+
.report()
|
24
24
|
}
|
25
25
|
</script>
|
26
26
|
</head>
|
data/spec/spec.xhr.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
2
|
describe 'JSpec'
|
3
|
-
describe '
|
3
|
+
describe 'Mock XHR'
|
4
4
|
before
|
5
5
|
responseFrom = function(path) {
|
6
6
|
request = new XMLHttpRequest
|
@@ -10,6 +10,11 @@ describe 'JSpec'
|
|
10
10
|
}
|
11
11
|
end
|
12
12
|
|
13
|
+
it 'should provide snake DSL methods'
|
14
|
+
mock_request.should.equal mockRequest
|
15
|
+
unmock_request.should.equal unmockRequest
|
16
|
+
end
|
17
|
+
|
13
18
|
it 'should mock XMLHttpRequests if unmockRequest() is called or the spec block has finished'
|
14
19
|
original = XMLHttpRequest
|
15
20
|
mockRequest().and_return('test')
|
@@ -1,13 +1,13 @@
|
|
1
1
|
<html>
|
2
2
|
<head>
|
3
|
-
<script src="jspec.js"></script>
|
4
|
-
<script src="
|
3
|
+
<script src="/jspec/jspec.js"></script>
|
4
|
+
<script src="/lib/yourlib.core.js"></script>
|
5
5
|
<script>
|
6
6
|
function runSuites() {
|
7
7
|
JSpec
|
8
8
|
.exec('spec.core.js')
|
9
|
-
.run()
|
10
|
-
.
|
9
|
+
.run({ formatter : JSpec.formatters.Server, verbose: true, failuresOnly: true })
|
10
|
+
.report()
|
11
11
|
}
|
12
12
|
</script>
|
13
13
|
</head>
|
@@ -1,13 +1,13 @@
|
|
1
1
|
<html>
|
2
2
|
<head>
|
3
|
-
<script src="jspec.js"></script>
|
4
|
-
<script src="
|
3
|
+
<script src="/jspec/jspec.js"></script>
|
4
|
+
<script src="/public/javascripts/application.js"></script>
|
5
5
|
<script>
|
6
6
|
function runSuites() {
|
7
7
|
JSpec
|
8
8
|
.exec('spec.application.js')
|
9
|
-
.run()
|
10
|
-
.
|
9
|
+
.run({ formatter : JSpec.formatters.Server, verbose: true, failuresOnly: true })
|
10
|
+
.report()
|
11
11
|
}
|
12
12
|
</script>
|
13
13
|
</head>
|