turnip_formatter 0.0.1

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 (50) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +3 -0
  5. data/Guardfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +54 -0
  8. data/Rakefile +1 -0
  9. data/lib/rspec/core/formatters/turnip_formatter.rb +61 -0
  10. data/lib/turnip_formatter/ext/turnip/builder.rb +46 -0
  11. data/lib/turnip_formatter/ext/turnip/rspec.rb +73 -0
  12. data/lib/turnip_formatter/formatter.css +159 -0
  13. data/lib/turnip_formatter/formatter.scss +225 -0
  14. data/lib/turnip_formatter/scenario/failure.rb +47 -0
  15. data/lib/turnip_formatter/scenario/pass.rb +19 -0
  16. data/lib/turnip_formatter/scenario/pending.rb +37 -0
  17. data/lib/turnip_formatter/scenario.rb +63 -0
  18. data/lib/turnip_formatter/step/failure.rb +21 -0
  19. data/lib/turnip_formatter/step/pending.rb +21 -0
  20. data/lib/turnip_formatter/step.rb +18 -0
  21. data/lib/turnip_formatter/template.rb +243 -0
  22. data/lib/turnip_formatter/version.rb +5 -0
  23. data/lib/turnip_formatter.rb +14 -0
  24. data/spec/examples/README.md +124 -0
  25. data/spec/examples/features/battle.feature +36 -0
  26. data/spec/examples/features/battle2.feature +17 -0
  27. data/spec/examples/features/battle3.feature +16 -0
  28. data/spec/examples/features/songs.feature +10 -0
  29. data/spec/examples/images/background.png +0 -0
  30. data/spec/examples/images/basic_step.png +0 -0
  31. data/spec/examples/images/failed_step.png +0 -0
  32. data/spec/examples/images/multiline.png +0 -0
  33. data/spec/examples/images/outline.png +0 -0
  34. data/spec/examples/images/pending_and_tagged_step.png +0 -0
  35. data/spec/examples/spec_helper.rb +8 -0
  36. data/spec/examples/steps/spell_steps.rb +5 -0
  37. data/spec/examples/steps/steps.rb +50 -0
  38. data/spec/rspec/core/formatters/turnip_formatter_spec.rb +101 -0
  39. data/spec/spec_helper.rb +17 -0
  40. data/spec/support/passed.feature +4 -0
  41. data/spec/support/shared_context_examples.rb +17 -0
  42. data/spec/turnip_formatter/scenario/failure_spec.rb +55 -0
  43. data/spec/turnip_formatter/scenario/pass_spec.rb +73 -0
  44. data/spec/turnip_formatter/scenario/pending_spec.rb +60 -0
  45. data/spec/turnip_formatter/step/failure_spec.rb +41 -0
  46. data/spec/turnip_formatter/step/pending_spec.rb +28 -0
  47. data/spec/turnip_formatter/step_spec.rb +32 -0
  48. data/spec/turnip_formatter/template_spec.rb +161 -0
  49. data/turnip_formatter.gemspec +26 -0
  50. metadata +206 -0
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'turnip_formatter/scenario'
4
+ require 'turnip_formatter/step/failure'
5
+ require 'rspec/core/formatters/helpers'
6
+
7
+ module TurnipFormatter
8
+ module Scenario
9
+ class NotFailedScenarioError < ::StandardError; end
10
+ class NoExistFailedStepInformationError < ::StandardError; end
11
+
12
+ class Failure
13
+ include TurnipFormatter::Scenario
14
+ include RSpec::Core::BacktraceFormatter
15
+
16
+ def steps
17
+ steps = super
18
+ steps[offending_line].tap do |step|
19
+ step.extend TurnipFormatter::Step::Failure
20
+ step.attention(exception, backtrace)
21
+ end
22
+ steps
23
+ end
24
+
25
+ def validation
26
+ raise NotFailedScenarioError if (status != 'failed')
27
+ offending_line
28
+ super
29
+ end
30
+
31
+ private
32
+
33
+ def offending_line
34
+ unless backtrace.last =~ /:in step:(?<stepno>\d+) `/
35
+ raise NoExistFailedStepInformationError
36
+ end
37
+ $~[:stepno].to_i
38
+ end
39
+
40
+ def backtrace
41
+ @backtrace ||= format_backtrace(exception.backtrace, scenario.metadata).map do |b|
42
+ backtrace_line(b)
43
+ end.compact
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'turnip_formatter/scenario'
4
+ require 'turnip_formatter/step/pending'
5
+
6
+ module TurnipFormatter
7
+ module Scenario
8
+ class NotPassedScenarioError < ::StandardError; end
9
+
10
+ class Pass
11
+ include TurnipFormatter::Scenario
12
+
13
+ def validation
14
+ raise NotPassedScenarioError if status != 'passed'
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'turnip_formatter/scenario'
4
+ require 'turnip_formatter/step/pending'
5
+
6
+ module TurnipFormatter
7
+ module Scenario
8
+ class NotPendingScenarioError < ::StandardError; end
9
+ class NoExistPendingStepInformationError < ::StandardError; end
10
+
11
+ class Pending
12
+ include TurnipFormatter::Scenario
13
+
14
+ def steps
15
+ steps = super
16
+ steps[offending_line].tap do |step|
17
+ step.extend TurnipFormatter::Step::Pending
18
+ step.attention(pending_message, scenario.location)
19
+ end
20
+ steps
21
+ end
22
+
23
+ def validation
24
+ raise NotPendingScenarioError if status != 'pending'
25
+ offending_line
26
+ super
27
+ end
28
+
29
+ private
30
+
31
+ def offending_line
32
+ raise NoExistPendingStepInformationError unless pending_message =~ /^No such step\((?<stepno>\d+)\): /
33
+ $~[:stepno].to_i
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'turnip_formatter/step'
4
+
5
+ module TurnipFormatter
6
+ module Scenario
7
+ class NotExistStepsInformationError < ::StandardError; end
8
+ class NoFeatureFileError < ::StandardError; end
9
+
10
+ #
11
+ # @param [RSpec::Core::Example] example
12
+ #
13
+ def initialize(example)
14
+ @scenario = example
15
+ end
16
+
17
+ def validation
18
+ raise NotExistStepsInformationError unless scenario.metadata.member?(:steps)
19
+ raise NoFeatureFileError unless feature_file_path.end_with?('.feature')
20
+ end
21
+
22
+ def steps
23
+ descriptions.map { |desc| TurnipFormatter::Step.new(desc) }
24
+ end
25
+
26
+ def method_missing(name, *args, &block)
27
+ if scenario.execution_result.member?(name.to_sym)
28
+ scenario.execution_result[name.to_sym]
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def name
35
+ scenario.example_group.description
36
+ end
37
+
38
+ def feature_name
39
+ @scenario.example_group.metadata[:example_group][:example_group][:description]
40
+ end
41
+
42
+ def feature_file_path
43
+ scenario.metadata[:file_path]
44
+ end
45
+
46
+ def tags
47
+ scenario.metadata[:steps][:tags]
48
+ end
49
+
50
+ private
51
+
52
+ def scenario
53
+ @scenario
54
+ end
55
+
56
+ def descriptions
57
+ descriptions = scenario.metadata[:steps][:descriptions]
58
+ keywords = scenario.metadata[:steps][:keywords]
59
+ docstrings = scenario.metadata[:steps][:docstrings]
60
+ descriptions.zip(keywords, docstrings)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module TurnipFormatter
4
+ class Step
5
+ module Failure
6
+ def attention?
7
+ true
8
+ end
9
+
10
+ def attention(exception, backtrace)
11
+ exception.set_backtrace(backtrace)
12
+ docs[:source] = backtrace.first
13
+ docs[:exception] = exception
14
+ end
15
+
16
+ def status
17
+ 'failure'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module TurnipFormatter
4
+ class Step
5
+ module Pending
6
+ def attention?
7
+ true
8
+ end
9
+
10
+ def attention(message, location)
11
+ exception = RSpec::Core::Pending::PendingDeclaredInExample.new(message)
12
+ exception.set_backtrace(location)
13
+ docs[:exception] = exception
14
+ end
15
+
16
+ def status
17
+ 'pending'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module TurnipFormatter
4
+ class Step
5
+ attr_reader :name, :docs
6
+
7
+ def initialize(description)
8
+ step_name, keyword, docstring = description
9
+ @name = keyword + step_name
10
+ @docs = {}
11
+ @docs[:extra_args] = docstring unless docstring.empty?
12
+ end
13
+
14
+ def attention?
15
+ false
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,243 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'erb'
4
+ require 'rspec/core/formatters/snippet_extractor'
5
+
6
+ module TurnipFormatter
7
+ class Template
8
+ include ERB::Util
9
+ include RSpec::Core::BacktraceFormatter
10
+
11
+ def print_header
12
+ <<-EOS
13
+ <!DOCTYPE html>
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <style>
17
+ #{File.read(File.dirname(__FILE__) + '/formatter.css')}
18
+ </style>
19
+ <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
20
+ <script>
21
+ $(function() {
22
+ ["passed", "failed", "pending"].forEach(function(status) {
23
+ $('#' + status + '_check').click(function() {
24
+ if (this.checked) {
25
+ $('.scenario.' + status).show();
26
+ } else {
27
+ $('.scenario.' + status).hide();
28
+ }
29
+ });
30
+ });
31
+ });
32
+ </script>
33
+ </head>
34
+ <body>
35
+ #{report_area}
36
+ <div id="main" role="main">
37
+ EOS
38
+ end
39
+
40
+ def print_footer(total_count, failed_count, pending_count, total_time)
41
+ update_report_js_tmp = '<script type="text/javascript">document.getElementById("%s").innerHTML = "%s";</script>'
42
+ update_report_js = ''
43
+
44
+
45
+ %w{ total_count failed_count pending_count total_time }.each do |key|
46
+ update_report_js += update_report_js_tmp % [key, eval(key)]
47
+ end
48
+
49
+ <<-EOS
50
+ </div>
51
+ #{update_report_js}
52
+ </body>
53
+ </html>
54
+ EOS
55
+ end
56
+
57
+ def print_scenario(scenario)
58
+ template_scenario.result(binding)
59
+ end
60
+
61
+ def print_runtime_error(example, exception)
62
+
63
+ if example.exception
64
+ example_backtrace = format_backtrace(example.exception.backtrace[0..14], example.metadata).map do |l|
65
+ RSpec::Core::Metadata::relative_path(l)
66
+ end
67
+ end
68
+
69
+ template_exception.result(binding)
70
+ end
71
+
72
+ private
73
+
74
+ def feature_name(scenario)
75
+ path = RSpec::Core::Metadata::relative_path(scenario.feature_file_path)
76
+ name = scenario.feature_name
77
+ "\"#{name}\" in #{path}"
78
+ end
79
+
80
+ def scenario_tags(scenario)
81
+ return '' if scenario.tags.empty?
82
+ template_scenario_tags.result(binding)
83
+ end
84
+
85
+ def step_attr(step)
86
+ attr = 'step'
87
+ attr += " #{step.status}" if step.attention?
88
+ "class=\"#{h(attr)}\""
89
+ end
90
+
91
+ def step_args(step)
92
+ args = []
93
+ [:extra_args, :source, :exception].each do |k|
94
+ args << send("step_#{k}", step.docs[k]) unless step.docs[k].nil?
95
+ end
96
+ args.join("\n")
97
+ end
98
+
99
+ def step_extra_args(extra_args)
100
+ extra_args.map do |arg|
101
+ if arg.instance_of?(Turnip::Table)
102
+ step_outline(arg)
103
+ else
104
+ step_multiline(arg)
105
+ end
106
+ end.join("\n")
107
+ end
108
+
109
+ def step_outline(table)
110
+ template_step_outline.result(binding)
111
+ end
112
+
113
+ def step_multiline(lines)
114
+ '<pre class="multiline">' + h(lines) + '</pre>'
115
+ end
116
+
117
+ def step_source(location)
118
+ @snippet_extractor ||= ::RSpec::Core::Formatters::SnippetExtractor.new
119
+ '<pre class="source"><code class="ruby">' + @snippet_extractor.snippet([location]) + '</code></pre>'
120
+ end
121
+
122
+ def step_exception(exception)
123
+ template_step_exception.result(binding)
124
+ end
125
+
126
+ def report_area
127
+ <<-EOS
128
+ <div id="report">
129
+ <h1>Turnip Report</h1>
130
+ <section class="checkbox">
131
+ <label for="passed_check">Passed</label><input type="checkbox" checked id="passed_check">
132
+ <label for="failed_check">Failed</label><input type="checkbox" checked id="failed_check">
133
+ <label for="pending_check">Pending</label><input type="checkbox" checked id="pending_check">
134
+ </section>
135
+
136
+ <section class="result">
137
+ <p>
138
+ <span id="total_count"></span> Scenario (<span id="failed_count"></span> failed <span id="pending_count"></span> pending).
139
+ </p>
140
+ <p>Finished in <span id="total_time"></span></p>
141
+ </section>
142
+ </div>
143
+ EOS
144
+ end
145
+
146
+ def template_scenario
147
+ @template_scenario ||= ERB.new(<<-EOS)
148
+ <section class="scenario <%= h(scenario.status) %>">
149
+ <header>
150
+ <span class="permalink">
151
+ <a href="#<%= scenario.object_id %>">&para;</a>
152
+ </span>
153
+ <span class="scenario_name" id="<%= scenario.object_id %>">
154
+ Scenario: <%= h(scenario.name) %>
155
+ </span>
156
+ <span class="feature_name">(Feature: <%= h(feature_name(scenario)) %>)</span>
157
+ </header>
158
+ <%= scenario_tags(scenario) %>
159
+ <ul class="steps">
160
+ <% scenario.steps.each do |step| %>
161
+ <li <%= step_attr(step) %>><span><%= h(step.name) %></span>
162
+ <div class="args"><%= step_args(step) %></div>
163
+ </li>
164
+ <% end %>
165
+ </ul>
166
+ </section>
167
+ EOS
168
+ end
169
+
170
+ def template_scenario_tags
171
+ @template_scenario_tags ||= ERB.new(<<-EOS)
172
+ <ul class="tags">
173
+ <% scenario.tags.each do |tag| %>
174
+ <li>@<%= h(tag) %></li>
175
+ <% end %>
176
+ </ul>
177
+ EOS
178
+ end
179
+
180
+ def template_step_outline
181
+ @template_step_outline ||= ERB.new(<<-EOS)
182
+ <table class="step_outline">
183
+ <% table.each do |tr| %>
184
+ <tr>
185
+ <% tr.each do |td| %>
186
+ <td><%= h(td) %></td>
187
+ <% end %>
188
+ </tr>
189
+ <% end %>
190
+ </table>
191
+ EOS
192
+ end
193
+
194
+ def template_step_exception
195
+ @template_step_exception ||= ERB.new(<<-EOS)
196
+ <div class="step_exception">
197
+ <span>Failure:</span>
198
+ <pre><%= h(exception.to_s) %></pre>
199
+ <span>Backtrace:</span>
200
+ <ol>
201
+ <% exception.backtrace.each do |line| %>
202
+ <li><%= h(line) %></li>
203
+ <% end %>
204
+ </ol>
205
+ </div>
206
+ EOS
207
+ end
208
+
209
+ def template_exception
210
+ @template_exception ||= ERB.new(<<-EOS)
211
+ <section class="exception">
212
+ <h1>TurnipFormatter RuntimeError</h1>
213
+ <dl>
214
+ <dt>Exception</dt>
215
+ <dd><%= h(exception.to_s) %></dd>
216
+
217
+ <dt>Example Full Description</dt>
218
+ <dd><%= h(example.metadata[:full_description]) %></dd>
219
+
220
+ <% if example.exception %>
221
+ <dt>Example Exception</dt>
222
+ <dd><%= h(example.exception.to_s) %></dd>
223
+
224
+ <dt>Backtrace:</dt>
225
+ <dd>
226
+ <ol>
227
+ <% example_backtrace.each do |line| %>
228
+ <li><%= h(line) %></li>
229
+ <% end %>
230
+ </ol>
231
+ </dd>
232
+ <% end %>
233
+
234
+ <% if example.pending %>
235
+ <dt>Example Pending description</dt>
236
+ <dd><%= h(example.metadata[:description]) %></dd>
237
+ <% end %>
238
+ </dl>
239
+ </section>
240
+ EOS
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module TurnipFormatter
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "turnip_formatter/version"
4
+ require 'turnip'
5
+
6
+ module TurnipFormatter
7
+ require 'rspec/core/formatters/turnip_formatter'
8
+ require 'turnip_formatter/template'
9
+ require 'turnip_formatter/ext/turnip/rspec'
10
+ require 'turnip_formatter/ext/turnip/builder'
11
+ end
12
+
13
+ RSpecTurnipFormatter = RSpec::Core::Formatters::TurnipFormatter
14
+
@@ -0,0 +1,124 @@
1
+ # TurnipFormatter Demo
2
+
3
+ ## Usage
4
+
5
+ $ cd /path/to/turnip_formatter
6
+ $ bundle install --path vendor/bundle
7
+ $ bundle exec rspec -r ./spec/examples/spec_helper spec/examples/features
8
+ $ open report.html
9
+
10
+ ## Screenshot
11
+
12
+ ### Basic step
13
+
14
+ ```feature
15
+ Scenario: normal monster
16
+ Given there is a monster
17
+ When I attack it
18
+ Then it should die
19
+ And Fanfare
20
+ ```
21
+
22
+ ![Basic step](https://github.com/gongo/turnip_formatter/raw/master/spec/examples/images/basic_step.png)
23
+
24
+ ### Failed step
25
+
26
+ ```feature
27
+ Scenario: strong monster
28
+
29
+ This scenario will error
30
+ So, fanfare is not...oh...
31
+
32
+ Given there is a strong monster
33
+ When I attack it
34
+ Then it should die
35
+ And Fanfare
36
+ ```
37
+
38
+ ![Failed step](https://github.com/gongo/turnip_formatter/raw/master/spec/examples/images/failed_step.png)
39
+
40
+ ### Pending step and specify tag
41
+
42
+ ```feature
43
+ Scenario: spell magic
44
+
45
+ This scenario will error because he can't cast spell
46
+
47
+ Given there is a strong monster
48
+ When I cast a spell 'fireball'
49
+ And I attack it
50
+ Then it should die
51
+ And Fanfare
52
+
53
+ @magician
54
+ Scenario: spell magic
55
+
56
+ Given there is a strong monster
57
+ When I cast a spell 'fireball'
58
+ And I attack it
59
+ Then it should die
60
+ And Fanfare
61
+ ```
62
+
63
+ ![Pending step](https://github.com/gongo/turnip_formatter/raw/master/spec/examples/images/pending_and_tagged_step.png)
64
+
65
+ ### Background
66
+
67
+ ```feature
68
+ Feature: Battle a monster with weapon
69
+
70
+ Background:
71
+ Given I equip a weapon
72
+
73
+ Scenario: normal monster
74
+ Given there is a monster
75
+ When I attack it
76
+ Then it should die
77
+ And Fanfare
78
+
79
+ Scenario: strong monster
80
+ Given there is a strong monster
81
+ When I attack it
82
+ Then it should die
83
+ And Fanfare
84
+ ```
85
+
86
+ ![Background step](https://github.com/gongo/turnip_formatter/raw/master/spec/examples/images/background.png)
87
+
88
+ ### Outline
89
+
90
+ ```feature
91
+ Feature: Battle monsters
92
+
93
+ Scenario: Escape
94
+ Given there are monsters:
95
+ | gargoyle |
96
+ | Cockatrice |
97
+ When I escape
98
+ Then I was able to escape
99
+
100
+ Scenario: Inescapable
101
+ Given there are monsters:
102
+ | gargoyle |
103
+ | Cockatrice |
104
+ | basilisk |
105
+ When I escape
106
+ Then I could not escape
107
+ ```
108
+
109
+ ![outline](https://github.com/gongo/turnip_formatter/raw/master/spec/examples/images/outline.png)
110
+
111
+ ### Multiline
112
+
113
+ ```feature
114
+ Feature: A feature with multiline strings
115
+ Scenario: This is a feature with multiline strings
116
+ When the monster sings the following song
117
+ """
118
+ Oh here be monsters
119
+ This is cool
120
+ """
121
+ Then the song should have 2 lines
122
+ ```
123
+
124
+ ![Multiline](https://github.com/gongo/turnip_formatter/raw/master/spec/examples/images/multiline.png)
@@ -0,0 +1,36 @@
1
+ Feature: Battle a monster
2
+
3
+ Scenario: normal monster
4
+ Given there is a monster
5
+ When I attack it
6
+ Then it should die
7
+ And Fanfare
8
+
9
+ Scenario: strong monster
10
+
11
+ This scenario will error
12
+ So, fanfare is not...oh...
13
+
14
+ Given there is a strong monster
15
+ When I attack it
16
+ Then it should die
17
+ And Fanfare
18
+
19
+ Scenario: spell magic
20
+
21
+ This scenario will error because he can't cast spell
22
+
23
+ Given there is a strong monster
24
+ When I cast a spell 'fireball'
25
+ And I attack it
26
+ Then it should die
27
+ And Fanfare
28
+
29
+ @magician
30
+ Scenario: spell magic
31
+
32
+ Given there is a strong monster
33
+ When I cast a spell 'fireball'
34
+ And I attack it
35
+ Then it should die
36
+ And Fanfare
@@ -0,0 +1,17 @@
1
+ Feature: Battle a monster with weapon
2
+
3
+ Background:
4
+ Given I equip a weapon
5
+
6
+ Scenario: normal monster
7
+ Given there is a monster
8
+ When I attack it
9
+ Then it should die
10
+ And Fanfare
11
+
12
+ Scenario: strong monster
13
+ Given there is a strong monster
14
+ When I attack it
15
+ Then it should die
16
+ And Fanfare
17
+
@@ -0,0 +1,16 @@
1
+ Feature: Battle monsters
2
+
3
+ Scenario: Escape
4
+ Given there are monsters:
5
+ | gargoyle |
6
+ | Cockatrice |
7
+ When I escape
8
+ Then I was able to escape
9
+
10
+ Scenario: Inescapable
11
+ Given there are monsters:
12
+ | gargoyle |
13
+ | Cockatrice |
14
+ | basilisk |
15
+ When I escape
16
+ Then I could not escape