teaspoon 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT.LICENSE +1 -1
- data/README.md +46 -377
- data/app/assets/javascripts/teaspoon-jasmine.js +200 -194
- data/app/assets/javascripts/teaspoon-mocha.js +183 -185
- data/app/assets/javascripts/teaspoon-qunit.js +201 -193
- data/app/assets/javascripts/teaspoon-teaspoon.js +10 -10
- data/app/assets/javascripts/teaspoon/base/fixture.coffee +0 -1
- data/app/assets/javascripts/teaspoon/base/hook.coffee +7 -6
- data/app/assets/javascripts/teaspoon/qunit.coffee +10 -0
- data/app/controllers/teaspoon/suite_controller.rb +3 -4
- data/app/views/teaspoon/suite/_boot.html.erb +1 -1
- data/app/views/teaspoon/suite/_boot_require_js.html.erb +1 -0
- data/bin/teaspoon +1 -1
- data/config/routes.rb +4 -14
- data/lib/generators/teaspoon/install/install_generator.rb +22 -11
- data/lib/generators/teaspoon/install/templates/jasmine/{env_comments.rb → env_comments.rb.tt} +10 -5
- data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.coffee +5 -5
- data/lib/generators/teaspoon/install/templates/jasmine/spec_helper.js +5 -5
- data/lib/generators/teaspoon/install/templates/mocha/{env_comments.rb → env_comments.rb.tt} +10 -5
- data/lib/generators/teaspoon/install/templates/mocha/spec_helper.coffee +6 -5
- data/lib/generators/teaspoon/install/templates/mocha/spec_helper.js +6 -5
- data/lib/generators/teaspoon/install/templates/qunit/{env_comments.rb → env_comments.rb.tt} +10 -5
- data/lib/generators/teaspoon/install/templates/qunit/test_helper.coffee +5 -5
- data/lib/generators/teaspoon/install/templates/qunit/test_helper.js +5 -5
- data/lib/tasks/teaspoon.rake +1 -1
- data/lib/teaspoon/command_line.rb +37 -40
- data/lib/teaspoon/configuration.rb +27 -30
- data/lib/teaspoon/configuration.rb.orig +187 -0
- data/lib/teaspoon/console.rb +31 -17
- data/lib/teaspoon/coverage.rb +2 -3
- data/lib/teaspoon/deprecated.rb +13 -8
- data/lib/teaspoon/drivers/base.rb +2 -2
- data/lib/teaspoon/drivers/capybara_webkit_driver.rb +33 -0
- data/lib/teaspoon/drivers/phantomjs/runner.js +2 -2
- data/lib/teaspoon/drivers/phantomjs_driver.rb +13 -4
- data/lib/teaspoon/drivers/selenium_driver.rb +3 -5
- data/lib/teaspoon/engine.rb +33 -5
- data/lib/teaspoon/environment.rb +2 -4
- data/lib/teaspoon/exceptions.rb +8 -5
- data/lib/teaspoon/formatters/base.rb +39 -27
- data/lib/teaspoon/formatters/clean_formatter.rb +0 -1
- data/lib/teaspoon/formatters/description.rb +36 -0
- data/lib/teaspoon/formatters/documentation_formatter.rb +2 -2
- data/lib/teaspoon/formatters/json_formatter.rb +1 -2
- data/lib/teaspoon/formatters/junit_formatter.rb +20 -20
- data/lib/teaspoon/formatters/modules/report_module.rb +4 -4
- data/lib/teaspoon/formatters/pride_formatter.rb +0 -1
- data/lib/teaspoon/formatters/rspec_html_formatter.rb +463 -0
- data/lib/teaspoon/formatters/snowday_formatter.rb +0 -1
- data/lib/teaspoon/formatters/swayze_or_oprah_formatter.rb +5 -4
- data/lib/teaspoon/formatters/tap_formatter.rb +2 -3
- data/lib/teaspoon/formatters/tap_y_formatter.rb +20 -21
- data/lib/teaspoon/formatters/teamcity_formatter.rb +4 -5
- data/lib/teaspoon/instrumentation.rb +7 -7
- data/lib/teaspoon/result.rb +1 -3
- data/lib/teaspoon/runner.rb +1 -2
- data/lib/teaspoon/server.rb +2 -1
- data/lib/teaspoon/suite.rb +20 -17
- data/lib/teaspoon/utility.rb +1 -3
- data/lib/teaspoon/version.rb +1 -1
- data/spec/dummy/config/application.rb +14 -18
- data/spec/dummy/config/boot.rb +2 -6
- data/spec/dummy/config/environment.rb +3 -3
- data/spec/dummy/config/environments/development.rb +27 -13
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +26 -13
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/features/console_reporter_spec.rb +3 -8
- data/spec/features/hooks_spec.rb +17 -4
- data/spec/features/html_reporter_spec.rb +12 -1
- data/spec/features/install_generator_spec.rb +2 -3
- data/spec/features/instrumentation_spec.rb +11 -11
- data/spec/javascripts/teaspoon/base/teaspoon_spec.coffee +14 -1
- data/spec/spec_helper.rb +7 -4
- data/spec/teaspoon/command_line_spec.rb +19 -28
- data/spec/teaspoon/configuration_spec.rb +22 -14
- data/spec/teaspoon/console_spec.rb +79 -63
- data/spec/teaspoon/coverage_spec.rb +23 -23
- data/spec/teaspoon/drivers/capybara_webkit_driver_spec.rb +39 -0
- data/spec/teaspoon/drivers/phantomjs_driver_spec.rb +10 -5
- data/spec/teaspoon/drivers/selenium_driver_spec.rb +10 -10
- data/spec/teaspoon/environment_spec.rb +28 -20
- data/spec/teaspoon/exceptions_spec.rb +4 -4
- data/spec/teaspoon/exporter_spec.rb +28 -28
- data/spec/teaspoon/formatters/base_spec.rb +29 -29
- data/spec/teaspoon/formatters/clean_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/documentation_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/dot_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/json_formatter_spec.rb +7 -7
- data/spec/teaspoon/formatters/junit_formatter_spec.rb +10 -10
- data/spec/teaspoon/formatters/pride_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/rspec_html_formatter_spec.rb +107 -0
- data/spec/teaspoon/formatters/snowday_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/tap_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/tap_y_formatter_spec.rb +1 -1
- data/spec/teaspoon/formatters/teamcity_formatter_spec.rb +27 -27
- data/spec/teaspoon/instrumentation_spec.rb +35 -29
- data/spec/teaspoon/result_spec.rb +40 -36
- data/spec/teaspoon/runner_spec.rb +23 -20
- data/spec/teaspoon/server_spec.rb +19 -16
- data/spec/teaspoon/suite_spec.rb +23 -9
- data/spec/teaspoon_env.rb +7 -12
- data/test/javascripts/teaspoon/qunit/models_test.coffee +6 -2
- data/vendor/assets/javascripts/support/chai-1.10.0.js +4800 -0
- data/vendor/assets/javascripts/support/chai-jq-0.0.7.js +524 -0
- data/vendor/assets/javascripts/support/chai.js +4435 -4349
- metadata +57 -54
- data/app/assets/javascripts/teaspoon-angular.js +0 -1299
- data/app/assets/javascripts/teaspoon/angular.coffee +0 -55
- data/app/assets/javascripts/teaspoon/angular/reporters/console.coffee +0 -11
- data/app/assets/javascripts/teaspoon/angular/reporters/html.coffee +0 -21
- data/spec/javascripts/angular_helper.coffee +0 -5
- data/spec/javascripts/teaspoon/angular/models_aspec.coffee +0 -95
- data/spec/javascripts/teaspoon/angular/reporters/html_aspec.coffee +0 -9
- data/vendor/assets/javascripts/angular/1.0.5.js +0 -26195
- 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
|
-
|
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(
|
121
|
+
def log_runner(_result); end
|
110
122
|
|
111
|
-
def log_suite(
|
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(
|
131
|
+
def log_passing_spec(_result); end
|
120
132
|
|
121
|
-
def log_pending_spec(
|
133
|
+
def log_pending_spec(_result); end
|
122
134
|
|
123
|
-
def log_failing_spec(
|
135
|
+
def log_failing_spec(_result); end
|
124
136
|
|
125
|
-
def log_error(
|
137
|
+
def log_error(_result); end
|
126
138
|
|
127
|
-
def log_exception(
|
139
|
+
def log_exception(_result); end
|
128
140
|
|
129
|
-
def log_console(
|
141
|
+
def log_console(_message); end
|
130
142
|
|
131
|
-
def log_result(
|
143
|
+
def log_result(_result); end
|
132
144
|
|
133
|
-
def log_coverage(
|
145
|
+
def log_coverage(_message); end
|
134
146
|
|
135
|
-
def log_threshold_failure(
|
147
|
+
def log_threshold_failure(_message); end
|
136
148
|
|
137
|
-
def log_complete(
|
149
|
+
def log_complete(_failure_count); end
|
138
150
|
|
139
151
|
private
|
140
152
|
|
@@ -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$/,
|
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 = "
|
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(%
|
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(%
|
11
|
-
log_line(%
|
12
|
-
log_line(%
|
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(%
|
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(%
|
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(%
|
31
|
+
log_line(%{ <failure type="AssertionFailed">#{cdata(result.message)}</failure>})
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
|
-
def log_result(
|
35
|
+
def log_result(_result)
|
37
36
|
log_end_suite
|
38
37
|
end
|
39
38
|
|
40
39
|
def log_coverage(message)
|
41
|
-
|
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(%
|
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(%
|
47
|
+
log_line(%{ <failure type="AssertionFailed">#{cdata(message)}</failure>})
|
48
48
|
end
|
49
|
-
log_line(%
|
49
|
+
log_line(%{</testsuite>})
|
50
50
|
end
|
51
51
|
|
52
|
-
def log_complete(
|
53
|
-
log_line(%
|
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(%
|
59
|
+
log_line(%{</testsuite>}) if @last_suite
|
60
60
|
end
|
61
61
|
|
62
|
-
def log_junit_spec(opts, &
|
63
|
-
log_line(%
|
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(%
|
66
|
-
log_line(%
|
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$/,
|
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
|
-
|
13
|
-
|
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(
|
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
|
@@ -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"> </p>
|
417
|
+
<p id="duration"> </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
|