satisfaction-jasmine 0.4.3

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 (33) hide show
  1. data/README.markdown +37 -0
  2. data/bin/jasmine +58 -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-rails-with-src-base-url.yml +23 -0
  9. data/generators/jasmine/templates/spec/javascripts/support/jasmine-rails.yml +20 -0
  10. data/generators/jasmine/templates/spec/javascripts/support/jasmine.yml +20 -0
  11. data/generators/jasmine/templates/spec/javascripts/support/jasmine_config.rb +61 -0
  12. data/generators/jasmine/templates/spec/javascripts/support/jasmine_runner.rb +17 -0
  13. data/jasmine/contrib/ruby/jasmine_runner.rb +334 -0
  14. data/jasmine/contrib/ruby/jasmine_spec_builder.rb +153 -0
  15. data/jasmine/contrib/ruby/run.html +47 -0
  16. data/jasmine/lib/TrivialReporter.js +117 -0
  17. data/jasmine/lib/consolex.js +28 -0
  18. data/jasmine/lib/jasmine-0.10.0.js +2261 -0
  19. data/jasmine/lib/jasmine.css +86 -0
  20. data/jasmine/lib/json2.js +478 -0
  21. data/lib/jasmine.rb +6 -0
  22. data/lib/jasmine/base.rb +63 -0
  23. data/lib/jasmine/config.rb +166 -0
  24. data/lib/jasmine/run.html.erb +43 -0
  25. data/lib/jasmine/selenium_driver.rb +44 -0
  26. data/lib/jasmine/server.rb +125 -0
  27. data/lib/jasmine/spec_builder.rb +152 -0
  28. data/spec/config_spec.rb +138 -0
  29. data/spec/jasmine_self_test_config.rb +15 -0
  30. data/spec/jasmine_self_test_spec.rb +18 -0
  31. data/spec/server_spec.rb +65 -0
  32. data/spec/spec_helper.rb +4 -0
  33. metadata +145 -0
data/lib/jasmine.rb ADDED
@@ -0,0 +1,6 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'jasmine/base'))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'jasmine/config'))
3
+ require File.expand_path(File.join(File.dirname(__FILE__), 'jasmine/server'))
4
+ require File.expand_path(File.join(File.dirname(__FILE__), 'jasmine/selenium_driver'))
5
+
6
+ require File.expand_path(File.join(File.dirname(__FILE__), 'jasmine/spec_builder'))
@@ -0,0 +1,63 @@
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__), '../../jasmine'))
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
+ end
@@ -0,0 +1,166 @@
1
+ module Jasmine
2
+ class Config
3
+ require 'yaml'
4
+
5
+ def initialize(options = {})
6
+ require 'selenium_rc'
7
+ @selenium_jar_path = SeleniumRC::Server.allocate.jar_path
8
+ @options = options
9
+
10
+ @browser = options[:browser] ? options.delete(:browser) : 'firefox'
11
+ @selenium_pid = nil
12
+ @jasmine_server_pid = nil
13
+ end
14
+
15
+ def start_server(port = 8888)
16
+ Jasmine::Server.new(port, self).start
17
+ end
18
+
19
+ def start
20
+ start_servers
21
+ @client = Jasmine::SeleniumDriver.new("localhost", @selenium_server_port, "*#{@browser}", "http://localhost:#{@jasmine_server_port}/")
22
+ @client.connect
23
+ end
24
+
25
+ def stop
26
+ @client.disconnect
27
+ stop_servers
28
+ end
29
+
30
+ def start_servers
31
+ @jasmine_server_port = Jasmine::find_unused_port
32
+ @selenium_server_port = Jasmine::find_unused_port
33
+
34
+ server = Jasmine::Server.new(@jasmine_server_port, self)
35
+
36
+ @selenium_pid = fork do
37
+ Process.setpgrp
38
+ exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1"
39
+ end
40
+ puts "selenium started. pid is #{@selenium_pid}"
41
+
42
+ @jasmine_server_pid = fork do
43
+ Process.setpgrp
44
+ server.start
45
+ exit! 0
46
+ end
47
+ puts "jasmine server started. pid is #{@jasmine_server_pid}"
48
+
49
+ Jasmine::wait_for_listener(@selenium_server_port, "selenium server")
50
+ Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server")
51
+ end
52
+
53
+ def stop_servers
54
+ puts "shutting down the servers..."
55
+ Jasmine::kill_process_group(@selenium_pid) if @selenium_pid
56
+ Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
57
+ end
58
+
59
+ def run
60
+ begin
61
+ start
62
+ puts "servers are listening on their ports -- running the test script..."
63
+ tests_passed = @client.run
64
+ ensure
65
+ stop
66
+ end
67
+ return tests_passed
68
+ end
69
+
70
+ def eval_js(script)
71
+ @client.eval_js(script)
72
+ end
73
+
74
+ def match_files(dir, patterns)
75
+ dir = File.expand_path(dir)
76
+ patterns.collect do |pattern|
77
+ Dir.glob(File.join(dir, pattern)).collect {|f| f.sub("#{dir}/", "/")}.sort
78
+ end.flatten
79
+ end
80
+
81
+ def simple_config
82
+ config = File.exist?(simple_config_file) ? File.open(simple_config_file) { |yf| YAML::load( yf ) } : false
83
+ config || {}
84
+ end
85
+
86
+
87
+ def spec_path
88
+ "/__spec__"
89
+ end
90
+
91
+ def root_path
92
+ "/__root__"
93
+ end
94
+
95
+ def mappings
96
+ {
97
+ spec_path => spec_dir,
98
+ root_path => project_root
99
+ }
100
+ end
101
+
102
+ def js_files
103
+ src_files + spec_files.collect {|f| File.join(spec_path, f) }
104
+ end
105
+
106
+ def css_files
107
+ stylesheets
108
+ end
109
+
110
+ def spec_files_full_paths
111
+ spec_files.collect {|spec_file| File.join(spec_dir, spec_file) }
112
+ end
113
+
114
+ def project_root
115
+ Dir.pwd
116
+ end
117
+
118
+ def simple_config_file
119
+ File.join(project_root, 'spec/javascripts/support/jasmine.yml')
120
+ end
121
+
122
+ def src_dir
123
+ if simple_config['src_dir']
124
+ File.join(project_root, simple_config['src_dir'])
125
+ else
126
+ project_root
127
+ end
128
+ end
129
+
130
+ def spec_dir
131
+ if simple_config['spec_dir']
132
+ File.join(project_root, simple_config['spec_dir'])
133
+ else
134
+ File.join(project_root, 'spec/javascripts')
135
+ end
136
+ end
137
+
138
+ def src_files
139
+ if simple_config['src_files']
140
+ if simple_config['src_base_url']
141
+ return simple_config['src_files'].map {|f| File.join(simple_config['src_base_url'], f)}
142
+ else
143
+ return match_files(src_dir, simple_config['src_files'])
144
+ end
145
+ end
146
+ return []
147
+ end
148
+
149
+ def spec_files
150
+ files = match_files(spec_dir, "**/*.js")
151
+ if simple_config['spec_files']
152
+ files = match_files(spec_dir, simple_config['spec_files'])
153
+ end
154
+ files
155
+ end
156
+
157
+ def stylesheets
158
+ files = []
159
+ if simple_config['stylesheets']
160
+ files = match_files(src_dir, simple_config['stylesheets'])
161
+ end
162
+ files
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2
+ <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta content="text/html;charset=UTF-8" http-equiv="Content-Type"/>
5
+ <title>Jasmine suite</title>
6
+ <% css_files.each do |css_file| %>
7
+ <link rel="stylesheet" href="<%= css_file %>" type="text/css" media="screen"/>
8
+ <% end %>
9
+
10
+ <% jasmine_files.each do |jasmine_file| %>
11
+ <script src="<%= jasmine_file %>" type="text/javascript"></script>
12
+ <% end %>
13
+
14
+ <script type="text/javascript">
15
+ var jsApiReporter;
16
+ (function() {
17
+ var jasmineEnv = jasmine.getEnv();
18
+
19
+ jsApiReporter = new jasmine.JsApiReporter();
20
+ var trivialReporter = new jasmine.TrivialReporter();
21
+
22
+ jasmineEnv.addReporter(jsApiReporter);
23
+ jasmineEnv.addReporter(trivialReporter);
24
+
25
+ jasmineEnv.specFilter = function(spec) {
26
+ return trivialReporter.specFilter(spec);
27
+ };
28
+
29
+ window.onload = function() {
30
+ jasmineEnv.execute();
31
+ };
32
+ })();
33
+ </script>
34
+
35
+ <% js_files.each do |js_file| %>
36
+ <script src="<%= js_file %>" type="text/javascript"></script>
37
+ <% end %>
38
+
39
+ </head>
40
+ <body>
41
+ <div id="jasmine_content"></div>
42
+ </body>
43
+ </html>
@@ -0,0 +1,44 @@
1
+ module Jasmine
2
+ class SeleniumDriver
3
+ def initialize(selenium_host, selenium_port, selenium_browser_start_command, http_address)
4
+ require 'selenium/client'
5
+ @driver = Selenium::Client::Driver.new(
6
+ selenium_host,
7
+ selenium_port,
8
+ selenium_browser_start_command,
9
+ http_address
10
+ )
11
+ @http_address = http_address
12
+ end
13
+
14
+ def tests_have_finished?
15
+ @driver.get_eval("window.jasmine.getEnv().currentRunner.finished") == "true"
16
+ end
17
+
18
+ def connect
19
+ @driver.start
20
+ @driver.open("/")
21
+ end
22
+
23
+ def disconnect
24
+ @driver.stop
25
+ end
26
+
27
+ def run
28
+ until tests_have_finished? do
29
+ sleep 0.1
30
+ end
31
+
32
+ puts @driver.get_eval("window.results()")
33
+ failed_count = @driver.get_eval("window.jasmine.getEnv().currentRunner.results().failedCount").to_i
34
+ failed_count == 0
35
+ end
36
+
37
+ def eval_js(script)
38
+ escaped_script = "'" + script.gsub(/(['\\])/) { '\\' + $1 } + "'"
39
+
40
+ result = @driver.get_eval("try { eval(#{escaped_script}, window); } catch(err) { window.eval(#{escaped_script}); }")
41
+ JSON.parse("{\"result\":#{result}}")["result"]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,125 @@
1
+ module Jasmine
2
+ class RunAdapter
3
+ def initialize(config)
4
+ @config = config
5
+ @jasmine_files = [
6
+ "/__JASMINE_ROOT__/lib/" + File.basename(Dir.glob("#{Jasmine.root}/lib/jasmine*.js").first),
7
+ "/__JASMINE_ROOT__/lib/TrivialReporter.js",
8
+ "/__JASMINE_ROOT__/lib/json2.js",
9
+ "/__JASMINE_ROOT__/lib/consolex.js",
10
+ ]
11
+ @jasmine_stylesheets = ["/__JASMINE_ROOT__/lib/jasmine.css"]
12
+ end
13
+
14
+ def call(env)
15
+ run
16
+ end
17
+
18
+ #noinspection RubyUnusedLocalVariable
19
+ def run
20
+ jasmine_files = @jasmine_files
21
+ css_files = @jasmine_stylesheets + (@config.css_files || [])
22
+ js_files = @config.js_files
23
+
24
+ body = ERB.new(File.read(File.join(File.dirname(__FILE__), "run.html.erb"))).result(binding)
25
+ [
26
+ 200,
27
+ { 'Content-Type' => 'text/html' },
28
+ body
29
+ ]
30
+ end
31
+
32
+
33
+ end
34
+
35
+ class Redirect
36
+ def initialize(url)
37
+ @url = url
38
+ end
39
+
40
+ def call(env)
41
+ [
42
+ 302,
43
+ { 'Location' => @url },
44
+ []
45
+ ]
46
+ end
47
+ end
48
+
49
+ class JsAlert
50
+ def call(env)
51
+ [
52
+ 200,
53
+ { 'Content-Type' => 'application/javascript' },
54
+ "document.write('<p>Couldn\\'t load #{env["PATH_INFO"]}!</p>');"
55
+ ]
56
+ end
57
+ end
58
+
59
+ class FocusedSuite
60
+ def initialize(config)
61
+ @config = config
62
+ # @spec_files_or_proc = spec_files_or_proc || []
63
+ # @options = options
64
+ end
65
+
66
+ def call(env)
67
+ spec_files = @config.spec_files_or_proc
68
+ matching_specs = spec_files.select {|spec_file| spec_file =~ /#{Regexp.escape(env["PATH_INFO"])}/ }.compact
69
+ if !matching_specs.empty?
70
+ run_adapter = Jasmine::RunAdapter.new(matching_specs, @options)
71
+ run_adapter.run
72
+ else
73
+ [
74
+ 200,
75
+ { 'Content-Type' => 'application/javascript' },
76
+ "document.write('<p>Couldn\\'t find any specs matching #{env["PATH_INFO"]}!</p>');"
77
+ ]
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ class Server
84
+ attr_reader :thin
85
+
86
+ def initialize(port, config)
87
+ @port = port
88
+ @config = config
89
+
90
+ require 'thin'
91
+ thin_config = {
92
+ '/__suite__' => Jasmine::FocusedSuite.new(@config),
93
+ '/run.html' => Jasmine::Redirect.new('/'),
94
+ '/' => Jasmine::RunAdapter.new(@config)
95
+ }
96
+
97
+ @config.mappings.each do |from, to|
98
+ thin_config[from] = Rack::File.new(to)
99
+ end
100
+
101
+ thin_config["/__JASMINE_ROOT__"] = Rack::File.new(Jasmine.root)
102
+
103
+ app = Rack::Cascade.new([
104
+ Rack::URLMap.new({'/' => Rack::File.new(@config.src_dir)}),
105
+ Rack::URLMap.new(thin_config),
106
+ JsAlert.new
107
+ ])
108
+
109
+ @thin = Thin::Server.new('0.0.0.0', @port, app)
110
+ end
111
+
112
+ def start
113
+ begin
114
+ thin.start
115
+ rescue RuntimeError => e
116
+ raise e unless e.message == 'no acceptor'
117
+ raise RuntimeError.new("A server is already running on port #{@port}")
118
+ end
119
+ end
120
+
121
+ def stop
122
+ thin.stop
123
+ end
124
+ end
125
+ end