evergreen 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/README.rdoc +30 -4
  2. data/config/routes.rb +1 -1
  3. data/lib/evergreen.rb +14 -50
  4. data/lib/evergreen/application.rb +46 -0
  5. data/lib/evergreen/cli.rb +2 -2
  6. data/lib/evergreen/rails.rb +4 -0
  7. data/lib/evergreen/resources/evergreen.css +3 -1
  8. data/lib/evergreen/resources/evergreen.js +3 -0
  9. data/lib/evergreen/runner.rb +124 -36
  10. data/lib/evergreen/server.rb +9 -18
  11. data/lib/evergreen/spec.rb +26 -14
  12. data/lib/evergreen/suite.rb +44 -0
  13. data/lib/evergreen/tasks.rb +6 -0
  14. data/lib/evergreen/template.rb +7 -10
  15. data/lib/evergreen/version.rb +1 -1
  16. data/lib/evergreen/views/layout.erb +1 -0
  17. data/lib/evergreen/views/list.erb +2 -2
  18. data/lib/evergreen/views/spec.erb +18 -10
  19. data/lib/jasmine/Gemfile +6 -0
  20. data/lib/jasmine/MIT.LICENSE +2 -2
  21. data/lib/jasmine/README.markdown +5 -459
  22. data/lib/jasmine/Rakefile +22 -16
  23. data/lib/jasmine/example/SpecRunner.html +1 -1
  24. data/lib/jasmine/jsdoc-template/allclasses.tmpl +17 -0
  25. data/lib/jasmine/jsdoc-template/allfiles.tmpl +56 -0
  26. data/lib/jasmine/jsdoc-template/class.tmpl +646 -0
  27. data/lib/jasmine/jsdoc-template/index.tmpl +39 -0
  28. data/lib/jasmine/jsdoc-template/publish.js +184 -0
  29. data/lib/jasmine/jsdoc-template/static/default.css +162 -0
  30. data/lib/jasmine/jsdoc-template/static/header.html +2 -0
  31. data/lib/jasmine/jsdoc-template/static/index.html +19 -0
  32. data/lib/jasmine/jsdoc-template/symbol.tmpl +35 -0
  33. data/lib/jasmine/lib/jasmine-html.js +12 -6
  34. data/lib/jasmine/lib/jasmine.js +122 -44
  35. data/lib/jasmine/spec/runner.html +0 -1
  36. data/lib/jasmine/spec/suites/CustomMatchersSpec.js +18 -9
  37. data/lib/jasmine/spec/suites/MatchersSpec.js +241 -162
  38. data/lib/jasmine/spec/suites/SpecRunningSpec.js +120 -62
  39. data/lib/jasmine/spec/suites/TrivialReporterSpec.js +3 -0
  40. data/lib/jasmine/spec/suites/WaitsForBlockSpec.js +9 -9
  41. data/lib/jasmine/src/Env.js +1 -0
  42. data/lib/jasmine/src/Matchers.js +35 -17
  43. data/lib/jasmine/src/Queue.js +6 -1
  44. data/lib/jasmine/src/Spec.js +34 -2
  45. data/lib/jasmine/src/WaitsForBlock.js +28 -13
  46. data/lib/jasmine/src/base.js +15 -8
  47. data/lib/jasmine/src/html/TrivialReporter.js +12 -6
  48. data/lib/jasmine/src/version.json +2 -2
  49. data/lib/tasks/evergreen.rake +1 -1
  50. data/spec/meta_spec.rb +21 -2
  51. data/spec/runner_spec.rb +16 -18
  52. data/spec/spec_helper.rb +12 -4
  53. data/spec/spec_spec.rb +4 -11
  54. data/spec/suite1/spec/javascripts/invalid_coffee_spec.coffee +1 -0
  55. data/spec/suite1/spec/javascripts/spec_helper.coffee +1 -1
  56. data/spec/suite2/config/evergreen.rb +5 -0
  57. data/spec/suite2/public_html/foo.js +1 -0
  58. data/spec/suite2/spec/awesome_spec.js +12 -0
  59. data/spec/suite2/spec/failing_spec.js +5 -0
  60. data/spec/suite2/templates/foo.html +1 -0
  61. data/spec/suite_spec.rb +26 -0
  62. data/spec/template_spec.rb +3 -9
  63. metadata +52 -20
  64. data/lib/jasmine/lib/consolex.js +0 -28
@@ -2,13 +2,13 @@
2
2
 
3
3
  "Because green is the new Blue(Ridge)"
4
4
 
5
- Evergreen is a tool to run javascript unit tests for client side JavaScript. It combines a runner which allows you to serve up and run your specs in a browser, as well as a headless runner based on envjs. Evergreen uses the Jasmine unit testing framework for JavaScript.
5
+ Evergreen is a tool to run javascript unit tests for client side JavaScript. It combines a server which allows you to serve up and run your specs in a browser, as well as a runner which uses Capybara and any of its drivers to run your specs. Evergreen uses the Jasmine unit testing framework for JavaScript.
6
6
 
7
7
  http://github.com/jnicklas/evergreen
8
8
 
9
9
  == Philosophy
10
10
 
11
- Evergreen is a unit testing tool. It's purpose is to test JavaScript in isolation from your application. If you need a tool that tests how your JavaScript integrates with your application you should use an integration testing framework, such as {Capybara}[http://github.com/jnicklas/capybara].
11
+ Evergreen is a unit testing tool. Its purpose is to test JavaScript in isolation from your application. If you need a tool that tests how your JavaScript integrates with your application you should use an integration testing framework, such as {Capybara}[http://github.com/jnicklas/capybara].
12
12
 
13
13
  == Installation
14
14
 
@@ -39,19 +39,45 @@ Alternatively you can run the specs headlessly by running:
39
39
 
40
40
  evergreen run
41
41
 
42
-
43
42
  == Integrating with Rails 3
44
43
 
45
44
  Add Evergreen to your Gemfile:
46
45
 
47
46
  gem 'evergreen', :require => 'evergreen/rails'
48
47
 
49
- Start your rails application and navigate to /evergreen/list. You should now see a list of all spec files, click on one to run it.
48
+ Start your rails application and navigate to /evergreen. You should now see a list of all spec files, click on one to run it.
50
49
 
51
50
  There's a rake task provided for you that you can use to run your specs:
52
51
 
53
52
  rake spec:javascripts
54
53
 
54
+ == Integrating with Rails 2
55
+
56
+ Add the following line to your Rakefile:
57
+
58
+ require 'evergreen/tasks'
59
+
60
+ This will give you the `spec:javascripts` rake task. Note that mounting is not possible under Rails 2 and that `require 'evergreen/rails'` will fail.
61
+
62
+ == Configuration
63
+
64
+ By default, Evergreen uses Selenium to run your specs and assumes a certain directory structure. If this standard is fine for you, then you don't need to do anything else. If you need to configure Evergreen to suit your needs, Evergreen will automatically look for and load the following files:
65
+
66
+ config/evergreen.rb
67
+ .evergreen
68
+ ~/.evergreen
69
+
70
+ The content of these files could look like this:
71
+
72
+ require 'capybara/envjs'
73
+
74
+ Evergreen.configure do |config|
75
+ config.driver = :envjs
76
+ config.public_dir = 'public_html'
77
+ config.template_dir = 'templates'
78
+ config.spec_dir = 'spec'
79
+ end
80
+
55
81
  == Transactions
56
82
 
57
83
  One problem often faced when writing unit tests for client side code is that changes to the page are not reverted for the next example, so that successive examples become dependent on each other. Evergreen adds a special div to your page with an id of test. This div is automatically emptied before each example. You should avoid appending markup to the page body and instead append it to this test div:
@@ -1,3 +1,3 @@
1
1
  Rails.application.routes.draw do
2
- mount Evergreen.application(Rails.root), :at => '/evergreen'
2
+ mount Evergreen.rails, :at => '/evergreen'
3
3
  end
@@ -1,71 +1,35 @@
1
1
  require 'rubygems'
2
2
  require 'sinatra/base'
3
3
  require 'capybara'
4
- require 'capybara/wait_until'
5
4
  require 'launchy'
6
5
  require 'evergreen/version'
6
+ require 'evergreen/application'
7
7
  require 'json'
8
8
 
9
9
  module Evergreen
10
10
  autoload :Cli, 'evergreen/cli'
11
11
  autoload :Server, 'evergreen/server'
12
12
  autoload :Runner, 'evergreen/runner'
13
+ autoload :Suite, 'evergreen/suite'
13
14
  autoload :Spec, 'evergreen/spec'
14
15
  autoload :Template, 'evergreen/template'
15
16
 
16
17
  class << self
17
- def application(root, driver=:serve)
18
- Rack::Builder.new do
19
- map "/jasmine" do
20
- use Rack::Static, :urls => ["/"], :root => File.expand_path('jasmine/lib', File.dirname(__FILE__))
21
- run lambda { |env| [404, {}, "No such file"]}
22
- end
18
+ attr_accessor :driver, :public_dir, :template_dir, :spec_dir
23
19
 
24
- map "/resources" do
25
- use Rack::Static, :urls => ["/"], :root => File.expand_path('evergreen/resources', File.dirname(__FILE__))
26
- run lambda { |env| [404, {}, "No such file"]}
27
- end
28
-
29
- map "/" do
30
- app = Class.new(Sinatra::Base).tap do |app|
31
- app.reset!
32
- app.class_eval do
33
- set :static, true
34
- set :root, File.expand_path('evergreen', File.dirname(__FILE__))
35
- set :public, File.expand_path(File.join(root, 'public'), File.dirname(__FILE__))
36
-
37
- helpers do
38
- def url(path)
39
- request.env['SCRIPT_NAME'].to_s + path.to_s
40
- end
41
- end
42
-
43
- get '/' do
44
- @specs = Spec.all(root)
45
- erb :list
46
- end
47
-
48
- get '/list' do
49
- @specs = Spec.all(root)
50
- erb :list
51
- end
52
-
53
- get '/run/*' do |name|
54
- @spec = Spec.new(root, name)
55
- @js_spec_helper = Spec.new(root, 'spec_helper.js')
56
- @coffee_spec_helper = Spec.new(root, 'spec_helper.coffee')
57
- @driver = driver
58
- erb :spec
59
- end
20
+ def configure
21
+ yield self
22
+ end
60
23
 
61
- get '/spec/*' do |name|
62
- Spec.new(root, name).read
63
- end
64
- end
65
- end
66
- run app
67
- end
24
+ def use_defaults!
25
+ configure do |config|
26
+ config.driver = :selenium
27
+ config.public_dir = 'public'
28
+ config.spec_dir = 'spec/javascripts'
29
+ config.template_dir = 'spec/javascripts/templates'
68
30
  end
69
31
  end
70
32
  end
71
33
  end
34
+
35
+ Evergreen.use_defaults!
@@ -0,0 +1,46 @@
1
+ module Evergreen
2
+ def self.application(suite)
3
+ Rack::Builder.new do
4
+ map "/jasmine" do
5
+ use Rack::Static, :urls => ["/"], :root => File.expand_path('../jasmine/lib', File.dirname(__FILE__))
6
+ run lambda { |env| [404, {}, "No such file"]}
7
+ end
8
+
9
+ map "/resources" do
10
+ use Rack::Static, :urls => ["/"], :root => File.expand_path('resources', File.dirname(__FILE__))
11
+ run lambda { |env| [404, {}, "No such file"]}
12
+ end
13
+
14
+ map "/" do
15
+ app = Class.new(Sinatra::Base).tap do |app|
16
+ app.reset!
17
+ app.class_eval do
18
+ set :static, true
19
+ set :root, File.expand_path('.', File.dirname(__FILE__))
20
+ set :public, File.expand_path(File.join(suite.root, Evergreen.public_dir), File.dirname(__FILE__))
21
+
22
+ helpers do
23
+ def url(path)
24
+ request.env['SCRIPT_NAME'].to_s + path.to_s
25
+ end
26
+ end
27
+
28
+ get '/' do
29
+ @suite = suite
30
+ erb :list
31
+ end
32
+
33
+ get '/run/*' do |name|
34
+ @suite = suite
35
+ @spec = suite.get_spec(name)
36
+ @js_spec_helper = suite.get_spec('spec_helper.js')
37
+ @coffee_spec_helper = suite.get_spec('spec_helper.coffee')
38
+ erb :spec
39
+ end
40
+ end
41
+ end
42
+ run app
43
+ end
44
+ end
45
+ end
46
+ end
@@ -10,10 +10,10 @@ module Evergreen
10
10
 
11
11
  case command
12
12
  when "serve"
13
- Evergreen::Server.run(root)
13
+ Evergreen::Suite.new(root).serve
14
14
  return true
15
15
  when "run"
16
- return Evergreen::Runner.run(root)
16
+ return Evergreen::Suite.new(root).run
17
17
  else
18
18
  puts "no such command '#{command}'"
19
19
  return false
@@ -2,6 +2,10 @@ require 'evergreen'
2
2
  require 'rails'
3
3
 
4
4
  module Evergreen
5
+ def self.rails
6
+ Evergreen::Suite.new(Rails.root).application
7
+ end
8
+
5
9
  class Railtie < Rails::Engine
6
10
  end
7
11
  end
@@ -18,6 +18,8 @@ a {
18
18
 
19
19
  #test {
20
20
  min-height: 50px;
21
+ max-height: 300px;
22
+ overflow: auto;
21
23
  margin: 30px auto 30px;
22
24
  }
23
25
 
@@ -59,4 +61,4 @@ a {
59
61
  text-align: center;
60
62
  font-size: 13px;
61
63
  color: #aaa;
62
- }
64
+ }
@@ -2,6 +2,8 @@ if(!this.JSON){this.JSON={};}
2
2
 
3
3
  var Evergreen = {};
4
4
 
5
+ Evergreen.dots = ""
6
+
5
7
  Evergreen.ReflectiveReporter = function() {
6
8
  this.reportRunnerStarting = function(runner) {
7
9
  Evergreen.results = [];
@@ -15,6 +17,7 @@ Evergreen.ReflectiveReporter = function() {
15
17
  message: item.message,
16
18
  trace: item.trace
17
19
  });
20
+ Evergreen.dots += (results.failedCount === 0) ? "." : "F";
18
21
  };
19
22
  this.reportRunnerResults = function(runner) {
20
23
  Evergreen.done = true;
@@ -1,60 +1,148 @@
1
1
  module Evergreen
2
2
  class Runner
3
- attr_reader :spec
4
-
5
- def self.run(root, io=STDOUT)
6
- runners = Spec.all(root).map { |spec| new(spec) }
7
- runners.each do |runner|
8
- if runner.passed?
9
- io.print '.'
10
- else
11
- io.print 'F'
12
- end
3
+ class Example
4
+ def initialize(row)
5
+ @row = row
6
+ end
7
+
8
+ def passed?
9
+ @row['passed']
13
10
  end
14
- io.puts ""
15
11
 
16
- runners.each do |runner|
17
- io.puts runner.failure_message unless runner.passed?
12
+ def failure_message
13
+ unless passed?
14
+ msg = []
15
+ msg << " Failed: #{@row['name']}"
16
+ msg << " #{@row['message']}"
17
+ msg << " in #{@row['trace']['fileName']}:#{@row['trace']['lineNumber']}" if @row['trace']
18
+ msg.join("\n")
19
+ end
18
20
  end
19
- runners.all? { |runner| runner.passed? }
20
21
  end
21
22
 
22
- def initialize(spec)
23
- @spec = spec
23
+ class SpecRunner
24
+ attr_reader :runner, :spec
25
+
26
+ def initialize(runner, spec)
27
+ @runner = runner
28
+ @spec = spec
29
+ end
30
+
31
+ def session
32
+ runner.session
33
+ end
34
+
35
+ def io
36
+ runner.io
37
+ end
38
+
39
+ def run
40
+ io.puts dots
41
+ io.puts failure_messages
42
+ io.puts "\n#{examples.size} examples, #{failed_examples.size} failures"
43
+ passed?
44
+ end
45
+
46
+ def examples
47
+ @results ||= begin
48
+ session.visit(spec.url)
49
+
50
+ previous_results = ""
51
+
52
+ session.wait_until(180) do
53
+ dots = session.evaluate_script('Evergreen.dots')
54
+ io.print dots.sub(/^#{Regexp.escape(previous_results)}/, '')
55
+ io.flush
56
+ previous_results = dots
57
+ session.evaluate_script('Evergreen.done')
58
+ end
59
+
60
+ dots = session.evaluate_script('Evergreen.dots')
61
+ io.print dots.sub(/^#{Regexp.escape(previous_results)}/, '')
62
+
63
+ JSON.parse(session.evaluate_script('Evergreen.getResults()')).map do |row|
64
+ Example.new(row)
65
+ end
66
+ end
67
+ end
68
+
69
+ def failed_examples
70
+ examples.select { |example| not example.passed? }
71
+ end
72
+
73
+ def passed?
74
+ examples.all? { |example| example.passed? }
75
+ end
76
+
77
+ def dots
78
+ examples; ""
79
+ end
80
+
81
+ def failure_messages
82
+ unless passed?
83
+ examples.map { |example| example.failure_message }.compact.join("\n\n")
84
+ end
85
+ end
24
86
  end
25
87
 
26
- def name
27
- spec.name
88
+ attr_reader :suite, :io
89
+
90
+ def initialize(suite, io=STDOUT)
91
+ @suite = suite
92
+ @io = io
28
93
  end
29
94
 
30
- def passed?
31
- failed_examples.empty?
95
+ def spec_runner(spec)
96
+ SpecRunner.new(self, spec)
32
97
  end
33
98
 
34
- def failure_message
35
- failed_examples.map do |row|
36
- msg = []
37
- msg << " Failed: #{row['name']}"
38
- msg << " #{row['message']}"
39
- msg << " in #{row['trace']['fileName']}:#{row['trace']['lineNumber']}" if row['trace']
40
- msg.join("\n")
41
- end.join("\n\n")
99
+ def run
100
+ before = Time.now
101
+
102
+ io.puts ""
103
+ io.puts dots.to_s
104
+ io.puts ""
105
+ if failure_messages
106
+ io.puts failure_messages
107
+ io.puts ""
108
+ end
109
+
110
+ seconds = "%.2f" % (Time.now - before)
111
+ io.puts "Finished in #{seconds} seconds"
112
+ io.puts "#{examples.size} examples, #{failed_examples.size} failures"
113
+ passed?
42
114
  end
43
115
 
44
- protected
116
+ def examples
117
+ spec_runners.map { |spec_runner| spec_runner.examples }.flatten
118
+ end
45
119
 
46
120
  def failed_examples
47
- results.select { |row| !row['passed'] }
121
+ examples.select { |example| not example.passed? }
122
+ end
123
+
124
+ def passed?
125
+ spec_runners.all? { |spec_runner| spec_runner.passed? }
126
+ end
127
+
128
+ def dots
129
+ spec_runners.map { |spec_runner| spec_runner.dots }.join
48
130
  end
49
131
 
50
- def results
51
- @results ||= begin
52
- session = Capybara::Session.new(:selenium, Evergreen.application(spec.root, :selenium))
53
- session.visit(spec.url)
54
- session.wait_until(180) { session.evaluate_script('Evergreen.done') }
55
- JSON.parse(session.evaluate_script('Evergreen.getResults()'))
132
+ def failure_messages
133
+ unless passed?
134
+ spec_runners.map { |spec_runner| spec_runner.failure_messages }.compact.join("\n\n")
56
135
  end
57
136
  end
58
137
 
138
+ def session
139
+ @session ||= Capybara::Session.new(Evergreen.driver, suite.application)
140
+ end
141
+
142
+ protected
143
+
144
+ def spec_runners
145
+ @spec_runners ||= suite.specs.map { |spec| SpecRunner.new(self, spec) }
146
+ end
59
147
  end
60
148
  end
@@ -1,30 +1,21 @@
1
1
  module Evergreen
2
2
  class Server
3
- def self.run(root)
4
- serve = new(root)
5
- serve.boot
6
- serve.launch_browser
7
- end
8
-
9
- def initialize(root)
10
- @root = root
11
- end
3
+ attr_reader :suite
12
4
 
13
- def server
14
- @server ||= Capybara::Server.new(Evergreen.application(@root))
5
+ def initialize(suite)
6
+ @suite = suite
15
7
  end
16
8
 
17
- def boot
9
+ def serve
18
10
  server.boot
11
+ Launchy.open(server.url('/'))
12
+ sleep
19
13
  end
20
14
 
21
- def root_url
22
- server.url('/')
23
- end
15
+ protected
24
16
 
25
- def launch_browser
26
- Launchy.open(root_url)
27
- sleep
17
+ def server
18
+ @server ||= Capybara::Server.new(suite.application)
28
19
  end
29
20
  end
30
21
  end