jasmine 0.4.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.
Files changed (32) hide show
  1. data/README.markdown +21 -0
  2. data/bin/jasmine +55 -0
  3. data/generators/jasmine/jasmine_generator.rb +25 -0
  4. data/generators/jasmine/templates/INSTALL +9 -0
  5. data/generators/jasmine/templates/lib/tasks/jasmine.rake +23 -0
  6. data/generators/jasmine/templates/spec/javascripts/ExampleSpec.js +11 -0
  7. data/generators/jasmine/templates/spec/javascripts/SpecHelper.js +1 -0
  8. data/generators/jasmine/templates/spec/javascripts/support/jasmine_config.rb +23 -0
  9. data/generators/jasmine/templates/spec/javascripts/support/jasmine_spec.rb +17 -0
  10. data/generators/jasmine/templates/spec/javascripts/support/sources-rails.yaml +8 -0
  11. data/generators/jasmine/templates/spec/javascripts/support/sources.yaml +5 -0
  12. data/jasmine/contrib/ruby/jasmine_runner.rb +334 -0
  13. data/jasmine/contrib/ruby/jasmine_spec_builder.rb +153 -0
  14. data/jasmine/contrib/ruby/run.html +47 -0
  15. data/jasmine/lib/TrivialReporter.js +117 -0
  16. data/jasmine/lib/consolex.js +28 -0
  17. data/jasmine/lib/jasmine-0.10.0.js +2261 -0
  18. data/jasmine/lib/jasmine.css +86 -0
  19. data/jasmine/lib/json2.js +478 -0
  20. data/lib/jasmine.rb +6 -0
  21. data/lib/jasmine/base.rb +63 -0
  22. data/lib/jasmine/config.rb +151 -0
  23. data/lib/jasmine/run.html.erb +43 -0
  24. data/lib/jasmine/selenium_driver.rb +44 -0
  25. data/lib/jasmine/server.rb +125 -0
  26. data/lib/jasmine/spec_builder.rb +152 -0
  27. data/spec/config_spec.rb +77 -0
  28. data/spec/jasmine_self_test_config.rb +15 -0
  29. data/spec/jasmine_self_test_spec.rb +18 -0
  30. data/spec/server_spec.rb +65 -0
  31. data/spec/spec_helper.rb +3 -0
  32. metadata +143 -0
data/README.markdown ADDED
@@ -0,0 +1,21 @@
1
+ jasmine-ruby
2
+ ============
3
+
4
+ git clone git://github.com/ragaskar/jasmine-ruby.git
5
+ cd jasmine-ruby
6
+ rake jeweler:build
7
+ rake jeweler:install
8
+ cd ..
9
+ mkdir test-jasmine
10
+ cd test-jasmine
11
+ jasmine init
12
+ rake jasmine
13
+
14
+ then
15
+
16
+ http://localhost:8888
17
+
18
+ and go!
19
+
20
+ After a few more touch-ups, I'll update the gemcutter.org gem ...
21
+
data/bin/jasmine ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rake'
4
+
5
+ def cwd
6
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
7
+ end
8
+
9
+ def expand(*paths)
10
+ File.expand_path(File.join(*paths))
11
+ end
12
+
13
+ def template_path(filepath)
14
+ expand(cwd, File.join("generators/jasmine/templates", filepath))
15
+ end
16
+
17
+ def dest_path(filepath)
18
+ expand(Dir.pwd, filepath)
19
+ end
20
+
21
+ def copy_unless_exists(relative_path, dest_path = nil)
22
+ unless File.exist?(dest_path(relative_path))
23
+ File.copy(template_path(relative_path), dest_path(dest_path || relative_path))
24
+ end
25
+ end
26
+
27
+ if ARGV[0] == 'init'
28
+ require 'ftools'
29
+ File.makedirs('spec/javascripts')
30
+ File.makedirs('spec/javascripts/support')
31
+
32
+ copy_unless_exists('spec/javascripts/SpecHelper.js')
33
+ copy_unless_exists('spec/javascripts/ExampleSpec.js')
34
+ copy_unless_exists('spec/javascripts/support/jasmine_config.rb')
35
+ copy_unless_exists('spec/javascripts/support/jasmine_spec.rb')
36
+
37
+ rails_tasks_dir = dest_path('lib/tasks')
38
+ if File.exist?(rails_tasks_dir)
39
+ copy_unless_exists('lib/tasks/jasmine.rake')
40
+ copy_unless_exists('spec/javascripts/support/sources-rails.yaml', 'spec/javascripts/support/sources.yaml')
41
+ else
42
+ copy_unless_exists('spec/javascripts/support/sources.yaml')
43
+ if File.exist?(dest_path('Rakefile'))
44
+ load dest_path('Rakefile')
45
+ end
46
+ write_mode = Rake::Task.task_defined?('jasmine') ? 'a' : 'w'
47
+ File.open(dest_path('Rakefile'), write_mode) do |f|
48
+ f.write(File.read(template_path('lib/tasks/jasmine.rake')))
49
+ end
50
+ end
51
+ File.open(template_path('INSTALL'), 'r').each_line do |line|
52
+ puts line
53
+ end
54
+ end
55
+
@@ -0,0 +1,25 @@
1
+ class JasmineGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+
5
+ m.directory "spec/javascripts"
6
+ m.file "spec/javascripts/SpecHelper.js", "spec/javascripts/SpecHelper.js"
7
+ m.file "spec/javascripts/ExampleSpec.js", "spec/javascripts/ExampleSpec.js"
8
+
9
+ m.directory "spec/javascripts/support"
10
+ m.file "spec/javascripts/support/jasmine_config.rb", "spec/javascripts/support/jasmine_config.rb"
11
+ m.file "spec/javascripts/support/jasmine_spec.rb", "spec/javascripts/support/jasmine_spec.rb"
12
+ m.file "spec/javascripts/support/sources-rails.yaml", "spec/javascripts/support/sources.yaml"
13
+
14
+ m.directory "lib/tasks"
15
+ m.file "lib/tasks/jasmine.rake", "lib/tasks/jasmine.rake"
16
+
17
+ m.readme "INSTALL"
18
+ end
19
+ end
20
+
21
+ def file_name
22
+ "create_blog"
23
+ end
24
+
25
+ end
@@ -0,0 +1,9 @@
1
+ Jasmine has been installed with example specs.
2
+
3
+ To run the server:
4
+
5
+ rake jasmine
6
+
7
+ To run the automated CI task with Selenium:
8
+
9
+ rake jasmine:ci
@@ -0,0 +1,23 @@
1
+ namespace :jasmine do
2
+ require 'jasmine'
3
+
4
+ desc "Run continuous integration tests"
5
+ require "spec"
6
+ require 'spec/rake/spectask'
7
+ Spec::Rake::SpecTask.new(:ci) do |t|
8
+ t.spec_opts = ["--color", "--format", "specdoc"]
9
+ t.verbose = true
10
+ t.spec_files = ['spec/javascripts/support/jasmine_spec.rb']
11
+ end
12
+ task :server do
13
+ require 'spec/javascripts/support/jasmine_config'
14
+
15
+ puts "your tests are here:"
16
+ puts " http://localhost:8888/run.html"
17
+
18
+ Jasmine::Config.new.start_server
19
+ end
20
+ end
21
+
22
+ desc "Run specs via server"
23
+ task :jasmine => ['jasmine:server']
@@ -0,0 +1,11 @@
1
+ describe('Example', function () {
2
+ it('should have a passing test', function() {
3
+ expect(true).toEqual(true);
4
+ });
5
+
6
+ describe('nested describe', function () {
7
+ it('should also have a passing test', function () {
8
+ expect(true).toEqual(true);
9
+ });
10
+ });
11
+ });
@@ -0,0 +1 @@
1
+ //You may load required files here, or create test-runner-wide environment settings.
@@ -0,0 +1,23 @@
1
+ require 'jasmine'
2
+
3
+ class Jasmine::Config
4
+
5
+ def project_root
6
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "..", ".."))
7
+ end
8
+
9
+ # Return an array of files to include before jasmine specs. Override if needed.
10
+ # def src_files
11
+ # match_files(src_dir, "**/*.js")
12
+ # end
13
+
14
+ # Path to your JavaScript source files
15
+ # def src_dir
16
+ # File.join(project_root, "public")
17
+ # end
18
+
19
+ # Path to your JavaScript specs
20
+ # def spec_dir
21
+ # File.join(project_root, 'spec/javascripts')
22
+ # end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config'))
3
+
4
+ jasmine_config = Jasmine::Config.new
5
+ spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
6
+
7
+ should_stop = false
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.after(:suite) do
11
+ spec_builder.stop if should_stop
12
+ end
13
+ end
14
+
15
+ spec_builder.start
16
+ should_stop = true
17
+ spec_builder.declare_suites
@@ -0,0 +1,8 @@
1
+ sources:
2
+ - javascripts/prototype.js
3
+ - javascripts/effects.js
4
+ - javascripts/controls.js
5
+ - javascripts/dragdrop.js
6
+ - javascripts/application.js
7
+ src_dir: public
8
+ spec_dir: spec/javascripts
@@ -0,0 +1,5 @@
1
+ #sources:
2
+ # - lib/source1.js
3
+ # - lib/source2.js
4
+ #src_dir:
5
+ #spec_dir: spec/javascripts
@@ -0,0 +1,334 @@
1
+ require 'socket'
2
+ require 'erb'
3
+ require 'json'
4
+
5
+ module Jasmine
6
+ def self.root
7
+ File.expand_path(File.join(File.dirname(__FILE__), '../..'))
8
+ end
9
+
10
+ # this seemingly-over-complex method is necessary to get an open port on at least some of our Macs
11
+ def self.open_socket_on_unused_port
12
+ infos = Socket::getaddrinfo("localhost", nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
13
+ families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
14
+
15
+ return TCPServer.open('0.0.0.0', 0) if families.has_key?('AF_INET')
16
+ return TCPServer.open('::', 0) if families.has_key?('AF_INET6')
17
+ return TCPServer.open(0)
18
+ end
19
+
20
+ def self.find_unused_port
21
+ socket = open_socket_on_unused_port
22
+ port = socket.addr[1]
23
+ socket.close
24
+ port
25
+ end
26
+
27
+ def self.server_is_listening_on(hostname, port)
28
+ require 'socket'
29
+ begin
30
+ socket = TCPSocket.open(hostname, port)
31
+ rescue Errno::ECONNREFUSED
32
+ return false
33
+ end
34
+ socket.close
35
+ true
36
+ end
37
+
38
+ def self.wait_for_listener(port, name = "required process", seconds_to_wait = 10)
39
+ time_out_at = Time.now + seconds_to_wait
40
+ until server_is_listening_on "localhost", port
41
+ sleep 0.1
42
+ puts "Waiting for #{name} on #{port}..."
43
+ raise "#{name} didn't show up on port #{port} after #{seconds_to_wait} seconds." if Time.now > time_out_at
44
+ end
45
+ end
46
+
47
+ def self.kill_process_group(process_group_id, signal="TERM")
48
+ Process.kill signal, -process_group_id # negative pid means kill process group. (see man 2 kill)
49
+ end
50
+
51
+ def self.cachebust(files, root_dir="", replace=nil, replace_with=nil)
52
+ require 'digest/md5'
53
+ files.collect do |file_name|
54
+ real_file_name = replace && replace_with ? file_name.sub(replace, replace_with) : file_name
55
+ begin
56
+ digest = Digest::MD5.hexdigest(File.read("#{root_dir}#{real_file_name}"))
57
+ rescue
58
+ digest = "MISSING-FILE"
59
+ end
60
+ "#{file_name}?cachebust=#{digest}"
61
+ end
62
+ end
63
+
64
+ class RunAdapter
65
+ def initialize(spec_files_or_proc, options = {})
66
+ @spec_files_or_proc = Jasmine.files(spec_files_or_proc) || []
67
+ @jasmine_files = Jasmine.files(options[:jasmine_files]) || [
68
+ "/__JASMINE_ROOT__/lib/" + File.basename(Dir.glob("#{Jasmine.root}/lib/jasmine*.js").first),
69
+ "/__JASMINE_ROOT__/lib/TrivialReporter.js",
70
+ "/__JASMINE_ROOT__/lib/json2.js",
71
+ "/__JASMINE_ROOT__/lib/consolex.js",
72
+ ]
73
+ @stylesheets = ["/__JASMINE_ROOT__/lib/jasmine.css"] + (Jasmine.files(options[:stylesheets]) || [])
74
+ @spec_helpers = Jasmine.files(options[:spec_helpers]) || []
75
+ end
76
+
77
+ def call(env)
78
+ run
79
+ end
80
+
81
+ def run
82
+ stylesheets = @stylesheets
83
+ spec_helpers = @spec_helpers
84
+ spec_files = @spec_files_or_proc
85
+
86
+ jasmine_files = @jasmine_files
87
+ jasmine_files = jasmine_files.call if jasmine_files.respond_to?(:call)
88
+
89
+ css_files = @stylesheets
90
+
91
+
92
+ body = ERB.new(File.read(File.join(File.dirname(__FILE__), "run.html"))).result(binding)
93
+ [
94
+ 200,
95
+ { 'Content-Type' => 'text/html' },
96
+ body
97
+ ]
98
+ end
99
+
100
+
101
+ end
102
+
103
+ class Redirect
104
+ def initialize(url)
105
+ @url = url
106
+ end
107
+
108
+ def call(env)
109
+ [
110
+ 302,
111
+ { 'Location' => @url },
112
+ []
113
+ ]
114
+ end
115
+ end
116
+
117
+ class JsAlert
118
+ def call(env)
119
+ [
120
+ 200,
121
+ { 'Content-Type' => 'application/javascript' },
122
+ "document.write('<p>Couldn\\'t load #{env["PATH_INFO"]}!</p>');"
123
+ ]
124
+ end
125
+ end
126
+
127
+ class FocusedSuite
128
+ def initialize(spec_files_or_proc, options)
129
+ @spec_files_or_proc = Jasmine.files(spec_files_or_proc) || []
130
+ @options = options
131
+ end
132
+
133
+ def call(env)
134
+ spec_files = @spec_files_or_proc
135
+ matching_specs = spec_files.select {|spec_file| spec_file =~ /#{Regexp.escape(env["PATH_INFO"])}/ }.compact
136
+ if !matching_specs.empty?
137
+ run_adapter = Jasmine::RunAdapter.new(matching_specs, @options)
138
+ run_adapter.run
139
+ else
140
+ [
141
+ 200,
142
+ { 'Content-Type' => 'application/javascript' },
143
+ "document.write('<p>Couldn\\'t find any specs matching #{env["PATH_INFO"]}!</p>');"
144
+ ]
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ class SimpleServer
151
+ def self.start(port, spec_files_or_proc, mappings, options = {})
152
+ require 'thin'
153
+ config = {
154
+ '/__suite__' => Jasmine::FocusedSuite.new(spec_files_or_proc, options),
155
+ '/run.html' => Jasmine::Redirect.new('/'),
156
+ '/' => Jasmine::RunAdapter.new(spec_files_or_proc, options)
157
+ }
158
+ mappings.each do |from, to|
159
+ config[from] = Rack::File.new(to)
160
+ end
161
+
162
+ config["/__JASMINE_ROOT__"] = Rack::File.new(Jasmine.root)
163
+
164
+ app = Rack::Cascade.new([
165
+ Rack::URLMap.new(config),
166
+ JsAlert.new
167
+ ])
168
+
169
+ begin
170
+ Thin::Server.start('0.0.0.0', port, app)
171
+ rescue RuntimeError => e
172
+ raise e unless e.message == 'no acceptor'
173
+ raise RuntimeError.new("A server is already running on port #{port}")
174
+ end
175
+ end
176
+ end
177
+
178
+ class SimpleClient
179
+ def initialize(selenium_host, selenium_port, selenium_browser_start_command, http_address)
180
+ require 'selenium/client'
181
+ @driver = Selenium::Client::Driver.new(
182
+ selenium_host,
183
+ selenium_port,
184
+ selenium_browser_start_command,
185
+ http_address
186
+ )
187
+ @http_address = http_address
188
+ end
189
+
190
+ def tests_have_finished?
191
+ @driver.get_eval("window.jasmine.getEnv().currentRunner.finished") == "true"
192
+ end
193
+
194
+ def connect
195
+ @driver.start
196
+ @driver.open("/")
197
+ end
198
+
199
+ def disconnect
200
+ @driver.stop
201
+ end
202
+
203
+ def run
204
+ until tests_have_finished? do
205
+ sleep 0.1
206
+ end
207
+
208
+ puts @driver.get_eval("window.results()")
209
+ failed_count = @driver.get_eval("window.jasmine.getEnv().currentRunner.results().failedCount").to_i
210
+ failed_count == 0
211
+ end
212
+
213
+ def eval_js(script)
214
+ escaped_script = "'" + script.gsub(/(['\\])/) { '\\' + $1 } + "'"
215
+
216
+ result = @driver.get_eval(" try { eval(#{escaped_script}, window); } catch(err) { window.eval(#{escaped_script}); }")
217
+ JSON.parse("[#{result}]")[0]
218
+ end
219
+ end
220
+
221
+ class Runner
222
+ def initialize(selenium_jar_path, spec_files, dir_mappings, options={})
223
+ @selenium_jar_path = selenium_jar_path
224
+ @spec_files = spec_files
225
+ @dir_mappings = dir_mappings
226
+ @options = options
227
+
228
+ @browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox'
229
+ @selenium_pid = nil
230
+ @jasmine_server_pid = nil
231
+ @selenium_host = 'localhost'
232
+ @jasmine_server_port = Jasmine::find_unused_port
233
+ @selenium_server_port = Jasmine::find_unused_port
234
+ end
235
+
236
+ def start
237
+ start_jasmine_server
238
+ start_selenium_server
239
+ @client = Jasmine::SimpleClient.new(@selenium_host, @selenium_server_port, "*#{@browser}", "http://localhost:#{@jasmine_server_port}/")
240
+ @client.connect
241
+ end
242
+
243
+ def stop
244
+ @client.disconnect
245
+ stop_selenium_server
246
+ stop_jasmine_server
247
+ end
248
+
249
+ def start_jasmine_server
250
+ @jasmine_server_pid = fork do
251
+ Process.setpgrp
252
+ Jasmine::SimpleServer.start(@jasmine_server_port, @spec_files, @dir_mappings, @options)
253
+ exit! 0
254
+ end
255
+ puts "jasmine server started. pid is #{@jasmine_server_pid}"
256
+ Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server")
257
+ end
258
+
259
+ def start_selenium_server
260
+ @selenium_pid = fork do
261
+ Process.setpgrp
262
+ exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1"
263
+ end
264
+ puts "selenium started. pid is #{@selenium_pid}"
265
+ Jasmine::wait_for_listener(@selenium_server_port, "selenium server")
266
+ end
267
+
268
+ def stop_jasmine_server
269
+ puts "shutting down Jasmine server..."
270
+ Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
271
+ end
272
+
273
+ def stop_selenium_server
274
+ puts "shutting down Selenium server..."
275
+ Jasmine::kill_process_group(@selenium_pid) if @selenium_pid
276
+ end
277
+
278
+ def run
279
+ begin
280
+ start
281
+ puts "servers are listening on their ports -- running the test script..."
282
+ tests_passed = @client.run
283
+ ensure
284
+ stop
285
+ end
286
+ return tests_passed
287
+ end
288
+
289
+ def eval_js(script)
290
+ @client.eval_js(script)
291
+ end
292
+ end
293
+
294
+ class SauceLabsRunner < Runner
295
+ def initialize(spec_files, dir_mappings, options={})
296
+ @spec_files = spec_files
297
+ @dir_mappings = dir_mappings
298
+ @options = options
299
+
300
+ @browser = options[:browser] ? options[:browser].delete(:browser) : 'firefox'
301
+ @jasmine_server_pid = nil
302
+ @jasmine_server_port = Jasmine::find_unused_port
303
+ @saucelabs_config = SeleniumConfig.new(options[:saucelabs_config], options[:saucelabs_config_file], @jasmine_server_port)
304
+ end
305
+
306
+ def start_selenium_server
307
+ @sauce_tunnel = SauceTunnel.new(@saucelabs_config)
308
+ end
309
+
310
+ def start
311
+ start_jasmine_server
312
+ start_selenium_server
313
+ @client = Jasmine::SimpleClient.new(@saucelabs_config['selenium_server_address'],
314
+ 4444,
315
+ @saucelabs_config['selenium_browser_key'],
316
+ "http://#{@saucelabs_config['application_address']}")
317
+ @client.connect
318
+ end
319
+
320
+ def stop
321
+ @client.disconnect
322
+ @sauce_tunnel.shutdown
323
+ stop_jasmine_server
324
+ end
325
+
326
+ end
327
+
328
+ def self.files(f)
329
+ result = f
330
+ result = result.call if result.respond_to?(:call)
331
+ result
332
+ end
333
+
334
+ end