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.
- 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
|