teaspoon 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/MIT.LICENSE +1 -1
  3. data/README.md +46 -377
  4. data/app/assets/javascripts/teaspoon-jasmine.js +200 -194
  5. data/app/assets/javascripts/teaspoon-mocha.js +183 -185
  6. data/app/assets/javascripts/teaspoon-qunit.js +201 -193
  7. data/app/assets/javascripts/teaspoon-teaspoon.js +10 -10
  8. data/app/assets/javascripts/teaspoon/base/fixture.coffee +0 -1
  9. data/app/assets/javascripts/teaspoon/base/hook.coffee +7 -6
  10. data/app/assets/javascripts/teaspoon/qunit.coffee +10 -0
  11. data/app/controllers/teaspoon/suite_controller.rb +3 -4
  12. data/app/views/teaspoon/suite/_boot.html.erb +1 -1
  13. data/app/views/teaspoon/suite/_boot_require_js.html.erb +1 -0
  14. data/bin/teaspoon +1 -1
  15. data/config/routes.rb +4 -14
  16. data/lib/generators/teaspoon/install/install_generator.rb +22 -11
  17. data/lib/generators/teaspoon/install/templates/jasmine/{env_comments.rb → env_comments.rb.tt} +10 -5
  18. data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.coffee +5 -5
  19. data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.js +5 -5
  20. data/lib/generators/teaspoon/install/templates/mocha/{env_comments.rb → env_comments.rb.tt} +10 -5
  21. data/lib/generators/teaspoon/install/templates/mocha/spec_helper.coffee +6 -5
  22. data/lib/generators/teaspoon/install/templates/mocha/spec_helper.js +6 -5
  23. data/lib/generators/teaspoon/install/templates/qunit/{env_comments.rb → env_comments.rb.tt} +10 -5
  24. data/lib/generators/teaspoon/install/templates/qunit/test_helper.coffee +5 -5
  25. data/lib/generators/teaspoon/install/templates/qunit/test_helper.js +5 -5
  26. data/lib/tasks/teaspoon.rake +1 -1
  27. data/lib/teaspoon/command_line.rb +37 -40
  28. data/lib/teaspoon/configuration.rb +27 -30
  29. data/lib/teaspoon/configuration.rb.orig +187 -0
  30. data/lib/teaspoon/console.rb +31 -17
  31. data/lib/teaspoon/coverage.rb +2 -3
  32. data/lib/teaspoon/deprecated.rb +13 -8
  33. data/lib/teaspoon/drivers/base.rb +2 -2
  34. data/lib/teaspoon/drivers/capybara_webkit_driver.rb +33 -0
  35. data/lib/teaspoon/drivers/phantomjs/runner.js +2 -2
  36. data/lib/teaspoon/drivers/phantomjs_driver.rb +13 -4
  37. data/lib/teaspoon/drivers/selenium_driver.rb +3 -5
  38. data/lib/teaspoon/engine.rb +33 -5
  39. data/lib/teaspoon/environment.rb +2 -4
  40. data/lib/teaspoon/exceptions.rb +8 -5
  41. data/lib/teaspoon/formatters/base.rb +39 -27
  42. data/lib/teaspoon/formatters/clean_formatter.rb +0 -1
  43. data/lib/teaspoon/formatters/description.rb +36 -0
  44. data/lib/teaspoon/formatters/documentation_formatter.rb +2 -2
  45. data/lib/teaspoon/formatters/json_formatter.rb +1 -2
  46. data/lib/teaspoon/formatters/junit_formatter.rb +20 -20
  47. data/lib/teaspoon/formatters/modules/report_module.rb +4 -4
  48. data/lib/teaspoon/formatters/pride_formatter.rb +0 -1
  49. data/lib/teaspoon/formatters/rspec_html_formatter.rb +463 -0
  50. data/lib/teaspoon/formatters/snowday_formatter.rb +0 -1
  51. data/lib/teaspoon/formatters/swayze_or_oprah_formatter.rb +5 -4
  52. data/lib/teaspoon/formatters/tap_formatter.rb +2 -3
  53. data/lib/teaspoon/formatters/tap_y_formatter.rb +20 -21
  54. data/lib/teaspoon/formatters/teamcity_formatter.rb +4 -5
  55. data/lib/teaspoon/instrumentation.rb +7 -7
  56. data/lib/teaspoon/result.rb +1 -3
  57. data/lib/teaspoon/runner.rb +1 -2
  58. data/lib/teaspoon/server.rb +2 -1
  59. data/lib/teaspoon/suite.rb +20 -17
  60. data/lib/teaspoon/utility.rb +1 -3
  61. data/lib/teaspoon/version.rb +1 -1
  62. data/spec/dummy/config/application.rb +14 -18
  63. data/spec/dummy/config/boot.rb +2 -6
  64. data/spec/dummy/config/environment.rb +3 -3
  65. data/spec/dummy/config/environments/development.rb +27 -13
  66. data/spec/dummy/config/environments/production.rb +79 -0
  67. data/spec/dummy/config/environments/test.rb +26 -13
  68. data/spec/dummy/config/routes.rb +1 -1
  69. data/spec/dummy/config/secrets.yml +22 -0
  70. data/spec/features/console_reporter_spec.rb +3 -8
  71. data/spec/features/hooks_spec.rb +17 -4
  72. data/spec/features/html_reporter_spec.rb +12 -1
  73. data/spec/features/install_generator_spec.rb +2 -3
  74. data/spec/features/instrumentation_spec.rb +11 -11
  75. data/spec/javascripts/teaspoon/base/teaspoon_spec.coffee +14 -1
  76. data/spec/spec_helper.rb +7 -4
  77. data/spec/teaspoon/command_line_spec.rb +19 -28
  78. data/spec/teaspoon/configuration_spec.rb +22 -14
  79. data/spec/teaspoon/console_spec.rb +79 -63
  80. data/spec/teaspoon/coverage_spec.rb +23 -23
  81. data/spec/teaspoon/drivers/capybara_webkit_driver_spec.rb +39 -0
  82. data/spec/teaspoon/drivers/phantomjs_driver_spec.rb +10 -5
  83. data/spec/teaspoon/drivers/selenium_driver_spec.rb +10 -10
  84. data/spec/teaspoon/environment_spec.rb +28 -20
  85. data/spec/teaspoon/exceptions_spec.rb +4 -4
  86. data/spec/teaspoon/exporter_spec.rb +28 -28
  87. data/spec/teaspoon/formatters/base_spec.rb +29 -29
  88. data/spec/teaspoon/formatters/clean_formatter_spec.rb +1 -1
  89. data/spec/teaspoon/formatters/documentation_formatter_spec.rb +1 -1
  90. data/spec/teaspoon/formatters/dot_formatter_spec.rb +1 -1
  91. data/spec/teaspoon/formatters/json_formatter_spec.rb +7 -7
  92. data/spec/teaspoon/formatters/junit_formatter_spec.rb +10 -10
  93. data/spec/teaspoon/formatters/pride_formatter_spec.rb +1 -1
  94. data/spec/teaspoon/formatters/rspec_html_formatter_spec.rb +107 -0
  95. data/spec/teaspoon/formatters/snowday_formatter_spec.rb +1 -1
  96. data/spec/teaspoon/formatters/tap_formatter_spec.rb +1 -1
  97. data/spec/teaspoon/formatters/tap_y_formatter_spec.rb +1 -1
  98. data/spec/teaspoon/formatters/teamcity_formatter_spec.rb +27 -27
  99. data/spec/teaspoon/instrumentation_spec.rb +35 -29
  100. data/spec/teaspoon/result_spec.rb +40 -36
  101. data/spec/teaspoon/runner_spec.rb +23 -20
  102. data/spec/teaspoon/server_spec.rb +19 -16
  103. data/spec/teaspoon/suite_spec.rb +23 -9
  104. data/spec/teaspoon_env.rb +7 -12
  105. data/test/javascripts/teaspoon/qunit/models_test.coffee +6 -2
  106. data/vendor/assets/javascripts/support/chai-1.10.0.js +4800 -0
  107. data/vendor/assets/javascripts/support/chai-jq-0.0.7.js +524 -0
  108. data/vendor/assets/javascripts/support/chai.js +4435 -4349
  109. metadata +57 -54
  110. data/app/assets/javascripts/teaspoon-angular.js +0 -1299
  111. data/app/assets/javascripts/teaspoon/angular.coffee +0 -55
  112. data/app/assets/javascripts/teaspoon/angular/reporters/console.coffee +0 -11
  113. data/app/assets/javascripts/teaspoon/angular/reporters/html.coffee +0 -21
  114. data/spec/javascripts/angular_helper.coffee +0 -5
  115. data/spec/javascripts/teaspoon/angular/models_aspec.coffee +0 -95
  116. data/spec/javascripts/teaspoon/angular/reporters/html_aspec.coffee +0 -9
  117. data/vendor/assets/javascripts/angular/1.0.5.js +0 -26195
  118. data/vendor/assets/javascripts/angular/MIT-LICENSE +0 -22
@@ -1,28 +1,40 @@
1
+ require "set"
2
+ require "teaspoon/formatters/description"
3
+
1
4
  module Teaspoon
2
5
  module Formatters
6
+ @@known_formatters = SortedSet.new
7
+
8
+ def self.known_formatters
9
+ @@known_formatters
10
+ end
11
+
12
+ def self.register(name, details)
13
+ description = Description.new(name, details)
14
+ @@known_formatters << description
15
+ autoload description.class_name, description.require_path
16
+ end
3
17
 
4
18
  # CONTRIBUTORS:
5
19
  # If you add a formatter you should do the following before it will be considered for merging.
6
20
  # - add it to this list so it can be autoloaded
7
21
  # - write specs for it
8
22
  # - add it to the readme so it's documented
9
- # - add it to the list in command_line.rb so others know it's available
10
- # - add it to the initializers in /lib/generators/install/templates so it's documented there as well
11
-
12
- autoload :CleanFormatter, "teaspoon/formatters/clean_formatter"
13
- autoload :DocumentationFormatter, "teaspoon/formatters/documentation_formatter"
14
- autoload :DotFormatter, "teaspoon/formatters/dot_formatter"
15
- autoload :JsonFormatter, "teaspoon/formatters/json_formatter"
16
- autoload :JunitFormatter, "teaspoon/formatters/junit_formatter"
17
- autoload :PrideFormatter, "teaspoon/formatters/pride_formatter"
18
- autoload :SnowdayFormatter, "teaspoon/formatters/snowday_formatter"
19
- autoload :SwayzeOrOprahFormatter, "teaspoon/formatters/swayze_or_oprah_formatter"
20
- autoload :TapFormatter, "teaspoon/formatters/tap_formatter"
21
- autoload :TapYFormatter, "teaspoon/formatters/tap_y_formatter"
22
- autoload :TeamcityFormatter, "teaspoon/formatters/teamcity_formatter"
23
23
 
24
- class Base
24
+ register :clean, description: "like dots but doesn't log re-run commands"
25
+ register :documentation, description: "descriptive documentation"
26
+ register :dot, description: "dots", default: true
27
+ register :json, description: "json formatter (raw teaspoon)"
28
+ register :junit, description: "junit compatible formatter"
29
+ register :pride, description: "yay rainbows!"
30
+ register :rspec_html, description: "RSpec inspired HTML format"
31
+ register :snowday, description: "makes you feel warm inside"
32
+ register :swayze_or_oprah, description: "quote from either Patrick Swayze or Oprah Winfrey"
33
+ register :tap, description: "test anything protocol formatter"
34
+ register :tap_y, description: "tap_yaml, format used by tapout"
35
+ register :teamcity, description: "teamcity compatible formatter"
25
36
 
37
+ class Base
26
38
  attr_accessor :total_count, :run_count, :passes, :pendings, :failures, :errors
27
39
 
28
40
  def initialize(suite_name = :default, output_file = nil)
@@ -106,9 +118,9 @@ module Teaspoon
106
118
 
107
119
  protected
108
120
 
109
- def log_runner(result); end
121
+ def log_runner(_result); end
110
122
 
111
- def log_suite(result); end
123
+ def log_suite(_result); end
112
124
 
113
125
  def log_spec(result)
114
126
  return log_passing_spec(result) if result.passing?
@@ -116,25 +128,25 @@ module Teaspoon
116
128
  log_failing_spec(result)
117
129
  end
118
130
 
119
- def log_passing_spec(result); end
131
+ def log_passing_spec(_result); end
120
132
 
121
- def log_pending_spec(result); end
133
+ def log_pending_spec(_result); end
122
134
 
123
- def log_failing_spec(result); end
135
+ def log_failing_spec(_result); end
124
136
 
125
- def log_error(result); end
137
+ def log_error(_result); end
126
138
 
127
- def log_exception(result); end
139
+ def log_exception(_result); end
128
140
 
129
- def log_console(message); end
141
+ def log_console(_message); end
130
142
 
131
- def log_result(result); end
143
+ def log_result(_result); end
132
144
 
133
- def log_coverage(message); end
145
+ def log_coverage(_message); end
134
146
 
135
- def log_threshold_failure(message); end
147
+ def log_threshold_failure(_message); end
136
148
 
137
- def log_complete(failure_count); end
149
+ def log_complete(_failure_count); end
138
150
 
139
151
  private
140
152
 
@@ -1,7 +1,6 @@
1
1
  module Teaspoon
2
2
  module Formatters
3
3
  class CleanFormatter < DotFormatter
4
-
5
4
  private
6
5
 
7
6
  def log_failed_examples
@@ -0,0 +1,36 @@
1
+ require "active_support/core_ext/string"
2
+ require "active_support/inflector"
3
+
4
+ module Teaspoon
5
+ module Formatters
6
+ class Description
7
+ attr_reader :name, :description
8
+
9
+ def initialize(name, details)
10
+ @name = name
11
+ @description = details[:description]
12
+ @default = details[:default]
13
+ end
14
+
15
+ def default?
16
+ @default
17
+ end
18
+
19
+ def cli_help
20
+ " #{name}#{' (default)' if default?} - #{description}"
21
+ end
22
+
23
+ def class_name
24
+ "#{name.to_s.camelize}Formatter"
25
+ end
26
+
27
+ def require_path
28
+ "teaspoon/formatters/#{name}_formatter"
29
+ end
30
+
31
+ def <=>(other)
32
+ name <=> other.name
33
+ end
34
+ end
35
+ end
36
+ end
@@ -43,7 +43,7 @@ module Teaspoon
43
43
 
44
44
  def log_intent_stdout(level)
45
45
  return if @stdout.blank?
46
- log_indent_line("# #{@stdout.gsub(/\n$/, "").gsub("\n", "\n# ")}", level, CYAN)
46
+ log_indent_line("# #{@stdout.gsub(/\n$/, '').gsub("\n", "\n# ")}", level, CYAN)
47
47
  end
48
48
 
49
49
  def log_indent_line(str = "", level = nil, color = nil)
@@ -51,7 +51,7 @@ module Teaspoon
51
51
  end
52
52
 
53
53
  def indent(str = "", level = nil)
54
- indent = "#{" " * level}"
54
+ indent = " " * (level * 2)
55
55
  str.gsub!("\n", "\n#{indent}")
56
56
  "#{indent}#{str}"
57
57
  end
@@ -1,7 +1,6 @@
1
1
  module Teaspoon
2
2
  module Formatters
3
3
  class JsonFormatter < Base
4
-
5
4
  protected
6
5
 
7
6
  def log_runner(result)
@@ -25,7 +24,7 @@ module Teaspoon
25
24
  end
26
25
 
27
26
  def log_console(message)
28
- log_line(%Q{{"type":"console","log":"#{message.gsub(/\n$/, "").gsub("\n", "\\n")}"}})
27
+ log_line(%{{"type":"console","log":"#{message.gsub(/\n$/, '').gsub('\n', '\\n')}"}})
29
28
  end
30
29
 
31
30
  def log_result(result)
@@ -3,18 +3,17 @@ require "cgi"
3
3
  module Teaspoon
4
4
  module Formatters
5
5
  class JunitFormatter < Base
6
-
7
6
  protected
8
7
 
9
8
  def log_runner(result)
10
- log_line(%Q{<?xml version="1.0" encoding="UTF-8"?>})
11
- log_line(%Q{<testsuites name="Teaspoon">})
12
- log_line(%Q{<testsuite name="#{@suite_name}" tests="#{@total_count}" time="#{result.start}">})
9
+ log_line(%{<?xml version="1.0" encoding="UTF-8"?>})
10
+ log_line(%{<testsuites name="Teaspoon">})
11
+ log_line(%{<testsuite name="#{escape(@suite_name)}" tests="#{@total_count}" time="#{result.start}">})
13
12
  end
14
13
 
15
14
  def log_suite(result)
16
15
  log_end_suite
17
- log_line(%Q{<testsuite name="#{result.label}">})
16
+ log_line(%{<testsuite name="#{escape(result.label)}">})
18
17
  end
19
18
 
20
19
  def log_passing_spec(result)
@@ -23,47 +22,48 @@ module Teaspoon
23
22
 
24
23
  def log_pending_spec(result)
25
24
  log_junit_spec(suite: result.suite, label: result.label) do
26
- log_line(%Q{ <skipped/>})
25
+ log_line(%{ <skipped/>})
27
26
  end
28
27
  end
29
28
 
30
29
  def log_failing_spec(result)
31
30
  log_junit_spec(suite: result.suite, label: result.label) do
32
- log_line(%Q{ <failure type="AssertionFailed">#{cdata(result.message)}</failure>})
31
+ log_line(%{ <failure type="AssertionFailed">#{cdata(result.message)}</failure>})
33
32
  end
34
33
  end
35
34
 
36
- def log_result(result)
35
+ def log_result(_result)
37
36
  log_end_suite
38
37
  end
39
38
 
40
39
  def log_coverage(message)
41
- log_line(%Q{<testsuite name="Coverage summary" tests="0">\n<properties>#{cdata(message)}<properties>\n</testsuite>})
40
+ properties = "<properties>#{cdata(message)}</properties>"
41
+ log_line(%{<testsuite name="Coverage summary" tests="0">\n#{properties}\n</testsuite>})
42
42
  end
43
43
 
44
44
  def log_threshold_failure(message)
45
- log_line(%Q{<testsuite name="Coverage thresholds" tests="1">})
45
+ log_line(%{<testsuite name="Coverage thresholds" tests="1">})
46
46
  log_junit_spec(suite: "Coverage thresholds", label: "were not met") do
47
- log_line(%Q{ <failure type="AssertionFailed">#{cdata(message)}</failure>})
47
+ log_line(%{ <failure type="AssertionFailed">#{cdata(message)}</failure>})
48
48
  end
49
- log_line(%Q{</testsuite>})
49
+ log_line(%{</testsuite>})
50
50
  end
51
51
 
52
- def log_complete(failure_count)
53
- log_line(%Q{</testsuite>\n</testsuites>})
52
+ def log_complete(_failure_count)
53
+ log_line(%{</testsuite>\n</testsuites>})
54
54
  end
55
55
 
56
56
  private
57
57
 
58
58
  def log_end_suite
59
- log_line(%Q{</testsuite>}) if @last_suite
59
+ log_line(%{</testsuite>}) if @last_suite
60
60
  end
61
61
 
62
- def log_junit_spec(opts, &block)
63
- log_line(%Q{<testcase classname="#{escape(opts[:suite])}" name="#{escape(opts[:label])}">})
62
+ def log_junit_spec(opts, &_block)
63
+ log_line(%{<testcase classname="#{escape(opts[:suite])}" name="#{escape(opts[:label])}">})
64
64
  yield if block_given?
65
- log_line(%Q{<system-out>#{cdata(@stdout)}</system-out>}) unless @stdout.blank?
66
- log_line(%Q{</testcase>})
65
+ log_line(%{<system-out>#{cdata(@stdout)}</system-out>}) unless @stdout.blank?
66
+ log_line(%{</testcase>})
67
67
  end
68
68
 
69
69
  def escape(str)
@@ -71,7 +71,7 @@ module Teaspoon
71
71
  end
72
72
 
73
73
  def cdata(str)
74
- "\n<![CDATA[\n#{str.gsub(/\n$/, "")}\n]]>\n"
74
+ "\n<![CDATA[\n#{str.gsub(/\n$/, '')}\n]]>\n"
75
75
  end
76
76
  end
77
77
  end
@@ -1,7 +1,6 @@
1
1
  module Teaspoon
2
2
  module Formatters
3
3
  module ReportModule
4
-
5
4
  RED = 31
6
5
  GREEN = 32
7
6
  YELLOW = 33
@@ -9,8 +8,9 @@ module Teaspoon
9
8
 
10
9
  def log_error(result)
11
10
  log_line(result.message, RED)
12
- for trace in result.trace || []
13
- log_line(" # #{filename(trace["file"])}:#{trace["line"]}#{trace["function"].present? ? " -- #{trace["function"]}" : ""}", CYAN)
11
+ (result.trace || []).each do |trace|
12
+ function = trace["function"].present? ? " -- #{trace['function']}" : ""
13
+ log_line(" # #{filename(trace['file'])}:#{trace['line']}#{function}", CYAN)
14
14
  end
15
15
  log_line
16
16
  end
@@ -54,7 +54,7 @@ module Teaspoon
54
54
 
55
55
  def log_stats(result)
56
56
  log_line("Finished in #{result.elapsed} seconds")
57
- stats = "#{pluralize("example", run_count)}, #{pluralize("failure", failures.size)}"
57
+ stats = "#{pluralize('example', run_count)}, #{pluralize('failure', failures.size)}"
58
58
  stats << ", #{pendings.size} pending" if pendings.size > 0
59
59
  log_line(stats, stats_color)
60
60
  end
@@ -1,7 +1,6 @@
1
1
  module Teaspoon
2
2
  module Formatters
3
3
  class PrideFormatter < DotFormatter
4
-
5
4
  PI_3 = Math::PI / 3
6
5
 
7
6
  def initialize(*args)
@@ -0,0 +1,463 @@
1
+ require "erb"
2
+
3
+ module Teaspoon
4
+ module Formatters
5
+ class RspecHtmlFormatter < Base
6
+ def initialize(*args)
7
+ super
8
+ @suite_start_template = template Templates::SUITE_START
9
+ @suite_end_template = template Templates::SUITE_END
10
+ @spec_template = template Templates::SPEC
11
+ @current_suite = []
12
+ end
13
+
14
+ def log_runner(result)
15
+ log_template template(Templates::HEADER), result
16
+ end
17
+
18
+ def log_suite(result)
19
+ while !@current_suite.empty? && @current_suite.size > result.level
20
+ log_suite_end
21
+ end
22
+
23
+ log_suite_start result
24
+ end
25
+
26
+ def log_spec(result)
27
+ log_template @spec_template, result
28
+ end
29
+
30
+ def log_result(result)
31
+ while !@current_suite.empty?
32
+ log_suite_end
33
+ end
34
+
35
+ log_template template(Templates::FOOTER), result
36
+ end
37
+
38
+ private
39
+
40
+ def log_suite_start(result)
41
+ @current_suite << result.label
42
+ log_template @suite_start_template, result
43
+ end
44
+
45
+ def log_suite_end
46
+ log_template @suite_end_template, @current_suite.pop
47
+ end
48
+
49
+ def template(contents)
50
+ Template.new contents
51
+ end
52
+
53
+ def log_template(template, object)
54
+ log_str template.render(object)
55
+ end
56
+
57
+ class Template
58
+ include ERB::Util
59
+
60
+ def initialize(contents)
61
+ @template = contents
62
+ end
63
+
64
+ def render(obj)
65
+ @o = obj
66
+ ERB.new(@template).result binding
67
+ end
68
+ end
69
+
70
+ module Templates
71
+ CSS = <<-CSS.strip_heredoc
72
+ body {
73
+ margin: 0;
74
+ padding: 0;
75
+ background: #fff;
76
+ font-size: 80%;
77
+ }
78
+
79
+ #teaspoon-header {
80
+ background: #65C400;
81
+ color: #fff;
82
+ height: 4em;
83
+ }
84
+
85
+ .teaspoon-report h1 {
86
+ margin: 0px 10px 0px 10px;
87
+ padding: 10px;
88
+ font-family: "Lucida Grande", Helvetica, sans-serif;
89
+ font-size: 1.8em;
90
+ position: absolute;
91
+ }
92
+
93
+ #label {
94
+ float: left;
95
+ }
96
+
97
+ #display-filters {
98
+ float: left;
99
+ padding: 28px 0 0 40%;
100
+ font-family: "Lucida Grande", Helvetica, sans-serif;
101
+ }
102
+
103
+ #summary {
104
+ float: right;
105
+ padding: 5px 10px;
106
+ font-family: "Lucida Grande", Helvetica, sans-serif;
107
+ text-align: right;
108
+ }
109
+
110
+ #summary p {
111
+ margin: 0 0 0 2px;
112
+ }
113
+
114
+ #summary #totals {
115
+ font-size: 1.2em;
116
+ }
117
+
118
+ .example_group {
119
+ background: #fff;
120
+ }
121
+
122
+ .results > .example_group {
123
+ margin: 0 10px 5px;
124
+ }
125
+
126
+ dl {
127
+ font: normal 11px "Lucida Grande", Helvetica, sans-serif;
128
+ }
129
+
130
+ .results > .example_group > dl {
131
+ margin: 0;
132
+ padding: 0 0 5px;
133
+ }
134
+
135
+ .results > .example_group > dl dl {
136
+ margin-left: 15px;
137
+ }
138
+
139
+ dt {
140
+ padding: 3px;
141
+ background: #65C400;
142
+ color: #fff;
143
+ font-weight: bold;
144
+ }
145
+
146
+ dd {
147
+ margin: 5px 0 5px 5px;
148
+ padding: 3px 3px 3px 18px;
149
+ }
150
+
151
+ dd .duration {
152
+ padding-left: 5px;
153
+ text-align: right;
154
+ right: 0px;
155
+ float: right;
156
+ }
157
+
158
+ dd.example.passed {
159
+ border-left: 5px solid #65C400;
160
+ border-bottom: 1px solid #65C400;
161
+ background: #DBFFB4; color: #3D7700;
162
+ }
163
+
164
+ dd.example.pending {
165
+ border-left: 5px solid #FAF834;
166
+ border-bottom: 1px solid #FAF834;
167
+ background: #FCFB98; color: #131313;
168
+ }
169
+
170
+ dd.example.failed {
171
+ border-left: 5px solid #C20000;
172
+ border-bottom: 1px solid #C20000;
173
+ color: #C20000; background: #FFFBD3;
174
+ }
175
+
176
+ dt.pending {
177
+ color: #000000; background: #FAF834;
178
+ }
179
+
180
+ dt.failed {
181
+ color: #FFFFFF; background: #C40D0D;
182
+ }
183
+
184
+ #teaspoon-header.pending {
185
+ color: #000000; background: #FAF834;
186
+ }
187
+
188
+ #teaspoon-header.failed {
189
+ color: #FFFFFF; background: #C40D0D;
190
+ }
191
+ CSS
192
+
193
+ JAVASCRIPT = <<-JAVASCRIPT.strip_heredoc
194
+ (function() {
195
+ "use strict";
196
+
197
+ if (!document.querySelectorAll) {
198
+ alert("Warning: Your browser does not support document.querySelectorAll. Your report may not work properly.");
199
+ return;
200
+ }
201
+
202
+ function get(id) {
203
+ return document.getElementById(id);
204
+ }
205
+
206
+ function getAll(scope, selector) {
207
+ if (arguments.length === 1) {
208
+ selector = scope;
209
+ scope = document;
210
+ }
211
+
212
+ return scope.querySelectorAll(selector);
213
+ }
214
+
215
+ function show(element) {
216
+ if (element.oldDisplayValue) {
217
+ element.style.display = element.oldDisplayValue;
218
+ } else {
219
+ element.style.display = "";
220
+ }
221
+ }
222
+
223
+ function hide(element) {
224
+ if (element.style.display === "none") {
225
+ return;
226
+ }
227
+
228
+ if (element.oldDisplayValue === undefined) {
229
+ element.oldDisplayValue = element.style.display;
230
+ }
231
+
232
+ element.style.display = "none";
233
+ }
234
+
235
+ function showAll(elements) {
236
+ for (var i = 0; i < elements.length; i++) {
237
+ show(elements[i]);
238
+ }
239
+ }
240
+
241
+ function hideAll(elements) {
242
+ for (var i = 0; i < elements.length; i++) {
243
+ hide(elements[i]);
244
+ }
245
+ }
246
+
247
+ function toggleAll(elements, display) {
248
+ if (display) {
249
+ showAll(elements);
250
+ } else {
251
+ hideAll(elements);
252
+ }
253
+ }
254
+
255
+ function isHidden(element) {
256
+ return element.style.display === "none";
257
+ }
258
+
259
+ function isAllHidden(elements) {
260
+ var allHidden = true;
261
+
262
+ for (var i = 0; i < elements.length; i++) {
263
+ if (!isHidden(elements[i])) {
264
+ allHidden = false;
265
+ break;
266
+ }
267
+ }
268
+
269
+ return allHidden;
270
+ }
271
+
272
+ function setText(element, text) {
273
+ while (element.firstChild !== null) {
274
+ element.removeChild(element.firstChild);
275
+ }
276
+
277
+ element.appendChild(document.createTextNode(text));
278
+ }
279
+
280
+ function addClass(element, className) {
281
+ element.className += " " + className;
282
+ }
283
+
284
+ function addClassAll(elements, className) {
285
+ for (var i = 0; i < elements.length; i++) {
286
+ addClass(elements[i], className);
287
+ }
288
+ }
289
+
290
+ function hasClass(element, className) {
291
+ return (" " + element.className + " ").replace(/[\\t\\r\\n\\f]/g, " ").indexOf(" " + className + " ") >= 0;
292
+ }
293
+
294
+ function isTag(element, tagName) {
295
+ return element.tagName && element.tagName.toLowerCase() === tagName;
296
+ }
297
+
298
+ function parents(elements, predicate) {
299
+ var results = [];
300
+
301
+ for (var i = 0; i < elements.length; i++) {
302
+ var parent = elements[i].parentNode;
303
+
304
+ while (parent) {
305
+ if (predicate(parent)) {
306
+ results.push(parent);
307
+ }
308
+
309
+ parent = parent.parentNode;
310
+ }
311
+ }
312
+
313
+ return results;
314
+ }
315
+
316
+ function children(elements, predicate) {
317
+ var results = [];
318
+
319
+ for (var i = 0; i < elements.length; i++) {
320
+ if (!elements[i].hasChildNodes()) {
321
+ continue;
322
+ }
323
+
324
+ for (var j = 0; j < elements[i].childNodes.length; j++) {
325
+ if (predicate(elements[i].childNodes[j])) {
326
+ results.push(elements[i].childNodes[j]);
327
+ }
328
+ }
329
+ }
330
+
331
+ return results;
332
+ }
333
+
334
+ var elements = getAll("input[data-class-filter]");
335
+
336
+ function handleClassFilterChange(e) {
337
+ var element = e.target || e.srcElement;
338
+ showAll(getAll(".example_group"));
339
+ toggleAll(getAll(".example." + element.getAttribute("data-type")), element.checked);
340
+
341
+ var groups = getAll(".example_group");
342
+
343
+ for (var i = 0; i < groups.length; i++) {
344
+ if (isAllHidden(getAll(groups[i], ".example"))) {
345
+ hide(groups[i]);
346
+ }
347
+ }
348
+ }
349
+
350
+ for (var i = 0; i < elements.length; i++) {
351
+ elements[i].onchange = handleClassFilterChange;
352
+ }
353
+
354
+ get("duration").innerHTML = "Finished in <strong></strong>";
355
+ setText(getAll("#duration strong")[0], get("duration-value").value + " seconds");
356
+ get("totals").innerHTML = '<span class="total-amount"></span> examples, <span class="failure-amount"></span> failures, <span class="pending-amount"></span> pending';
357
+ var failureAmount = getAll(".example.failed").length;
358
+ var pendingAmount = getAll(".example.pending").length;
359
+ setText(getAll("#totals .total-amount")[0], getAll(".example").length);
360
+ setText(getAll("#totals .failure-amount")[0], failureAmount);
361
+ setText(getAll("#totals .pending-amount")[0], pendingAmount);
362
+
363
+ if (failureAmount > 0) {
364
+ addClass(get("teaspoon-header"), "failed");
365
+ } else if (pendingAmount > 0) {
366
+ addClass(get("teaspoon-header"), "pending");
367
+ }
368
+
369
+ function propagateClass(exampleSelector, groupPredicate, classToAdd) {
370
+ var exampleElements = getAll(exampleSelector);
371
+
372
+ var groupElements = parents(exampleElements, function(p) {
373
+ return hasClass(p, "example_group") && groupPredicate(p);
374
+ });
375
+
376
+ addClassAll(groupElements, classToAdd);
377
+ var dlChildren = children(groupElements, function(c) { return isTag(c, "dl"); });
378
+ var dtChildren = children(dlChildren, function(c) { return isTag(c, "dt"); });
379
+ addClassAll(dtChildren, classToAdd);
380
+ }
381
+
382
+ propagateClass(".example.failed", function() { return true; }, "failed");
383
+ propagateClass(".example.pending", function(p) { return !hasClass(p, "failed"); }, "pending");
384
+ propagateClass(".example", function(p) { return !hasClass(p, "failed") && !hasClass(p, "pending"); }, "passed");
385
+ })();
386
+ JAVASCRIPT
387
+
388
+ HEADER = <<-HTML.strip_heredoc
389
+ <!DOCTYPE html>
390
+ <html>
391
+ <head>
392
+ <title>Teaspoon results</title>
393
+
394
+ <style type="text/css">
395
+ #{CSS}
396
+ </style>
397
+ </head>
398
+
399
+ <body>
400
+ <div class="teaspoon-report">
401
+ <div id="teaspoon-header">
402
+ <div id="label">
403
+ <h1>Teaspoon Code Examples</h1>
404
+ </div>
405
+
406
+ <div id="display-filters">
407
+ <input id="passed-checkbox" data-class-filter data-type="passed" type="checkbox" checked="checked">
408
+ <label for="passed-checkbox">Passed</label>
409
+ <input id="failed-checkbox" data-class-filter data-type="failed" type="checkbox" checked="checked">
410
+ <label for="failed-checkbox">Failed</label>
411
+ <input id="pending-checkbox" data-class-filter data-type="pending" type="checkbox" checked="checked">
412
+ <label for="pending-checkbox">Pending</label>
413
+ </div>
414
+
415
+ <div id="summary">
416
+ <p id="totals">&nbsp;</p>
417
+ <p id="duration">&nbsp;</p>
418
+ </div>
419
+ </div>
420
+
421
+ <div class="results">
422
+ HTML
423
+
424
+ SUITE_START = <<-HTML.strip_heredoc
425
+ <div class="example_group">
426
+ <dl>
427
+ <dt><%= h @o.label %></dt>
428
+ HTML
429
+
430
+ SPEC = <<-HTML.strip_heredoc
431
+ <dd class="example <%= h @o.status %>">
432
+ <span class="spec-name"><%= h @o.label %></span>
433
+ <span class="duration"><%= h "\#{@o.elapsed}s" if @o.elapsed %></span>
434
+
435
+ <% if @o.failing? %>
436
+ <div class="message">
437
+ <pre><%= h @o.trace %></pre>
438
+ </div>
439
+ <% end %>
440
+ </dd>
441
+ HTML
442
+
443
+ SUITE_END = <<-HTML.strip_heredoc
444
+ </dl>
445
+ </div>
446
+ HTML
447
+
448
+ FOOTER = <<-HTML.strip_heredoc
449
+ </div>
450
+ </div>
451
+
452
+ <input type="hidden" id="duration-value" value="<%= h @o.elapsed %>" />
453
+
454
+ <script type="text/javascript">
455
+ #{JAVASCRIPT}
456
+ </script>
457
+ </body>
458
+ </html>
459
+ HTML
460
+ end
461
+ end
462
+ end
463
+ end