teaspoon 0.8.0 → 0.9.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 (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