cucumber 3.2.0 → 4.1.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -19
  3. data/CONTRIBUTING.md +2 -18
  4. data/README.md +4 -5
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +34 -39
  7. data/lib/cucumber/cli/configuration.rb +5 -5
  8. data/lib/cucumber/cli/main.rb +12 -12
  9. data/lib/cucumber/cli/options.rb +62 -71
  10. data/lib/cucumber/cli/profile_loader.rb +49 -26
  11. data/lib/cucumber/configuration.rb +31 -23
  12. data/lib/cucumber/constantize.rb +2 -5
  13. data/lib/cucumber/deprecate.rb +31 -7
  14. data/lib/cucumber/errors.rb +5 -7
  15. data/lib/cucumber/events/envelope.rb +9 -0
  16. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  17. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  18. data/lib/cucumber/events/step_activated.rb +2 -1
  19. data/lib/cucumber/events/test_case_created.rb +13 -0
  20. data/lib/cucumber/events/test_case_ready.rb +12 -0
  21. data/lib/cucumber/events/test_step_created.rb +13 -0
  22. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  23. data/lib/cucumber/events.rb +13 -6
  24. data/lib/cucumber/file_specs.rb +6 -6
  25. data/lib/cucumber/filters/activate_steps.rb +5 -3
  26. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  27. data/lib/cucumber/filters/prepare_world.rb +5 -9
  28. data/lib/cucumber/filters/quit.rb +1 -3
  29. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  30. data/lib/cucumber/filters.rb +1 -0
  31. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  32. data/lib/cucumber/formatter/ast_lookup.rb +165 -0
  33. data/lib/cucumber/formatter/backtrace_filter.rb +9 -8
  34. data/lib/cucumber/formatter/console.rb +58 -66
  35. data/lib/cucumber/formatter/console_counts.rb +4 -9
  36. data/lib/cucumber/formatter/console_issues.rb +6 -3
  37. data/lib/cucumber/formatter/duration.rb +1 -1
  38. data/lib/cucumber/formatter/duration_extractor.rb +3 -1
  39. data/lib/cucumber/formatter/errors.rb +6 -0
  40. data/lib/cucumber/formatter/fanout.rb +2 -0
  41. data/lib/cucumber/formatter/html.rb +11 -598
  42. data/lib/cucumber/formatter/http_io.rb +1 -1
  43. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  44. data/lib/cucumber/formatter/interceptor.rb +8 -28
  45. data/lib/cucumber/formatter/io.rb +1 -1
  46. data/lib/cucumber/formatter/json.rb +101 -115
  47. data/lib/cucumber/formatter/junit.rb +56 -56
  48. data/lib/cucumber/formatter/message.rb +22 -0
  49. data/lib/cucumber/formatter/message_builder.rb +255 -0
  50. data/lib/cucumber/formatter/pretty.rb +359 -153
  51. data/lib/cucumber/formatter/progress.rb +30 -32
  52. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  53. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  54. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  55. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  56. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  57. data/lib/cucumber/formatter/rerun.rb +22 -4
  58. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  59. data/lib/cucumber/formatter/steps.rb +2 -3
  60. data/lib/cucumber/formatter/summary.rb +16 -8
  61. data/lib/cucumber/formatter/unicode.rb +15 -17
  62. data/lib/cucumber/formatter/usage.rb +11 -10
  63. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  64. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  65. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  66. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  67. data/lib/cucumber/glue/dsl.rb +1 -1
  68. data/lib/cucumber/glue/hook.rb +34 -11
  69. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  70. data/lib/cucumber/glue/proto_world.rb +42 -33
  71. data/lib/cucumber/glue/registry_and_more.rb +42 -12
  72. data/lib/cucumber/glue/snippet.rb +23 -22
  73. data/lib/cucumber/glue/step_definition.rb +42 -19
  74. data/lib/cucumber/glue/world_factory.rb +1 -1
  75. data/lib/cucumber/hooks.rb +11 -11
  76. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  77. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  78. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  79. data/lib/cucumber/multiline_argument.rb +4 -6
  80. data/lib/cucumber/platform.rb +3 -3
  81. data/lib/cucumber/rake/task.rb +16 -16
  82. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  83. data/lib/cucumber/running_test_case.rb +2 -53
  84. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  85. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  86. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  87. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  88. data/lib/cucumber/runtime/support_code.rb +13 -15
  89. data/lib/cucumber/runtime/user_interface.rb +6 -16
  90. data/lib/cucumber/runtime.rb +34 -58
  91. data/lib/cucumber/step_definition_light.rb +4 -3
  92. data/lib/cucumber/step_definitions.rb +2 -2
  93. data/lib/cucumber/step_match.rb +12 -11
  94. data/lib/cucumber/step_match_search.rb +2 -1
  95. data/lib/cucumber/term/ansicolor.rb +9 -9
  96. data/lib/cucumber/version +1 -1
  97. data/lib/cucumber.rb +1 -1
  98. metadata +251 -81
  99. data/lib/cucumber/formatter/cucumber.css +0 -286
  100. data/lib/cucumber/formatter/cucumber.sass +0 -247
  101. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  102. data/lib/cucumber/formatter/html_builder.rb +0 -121
  103. data/lib/cucumber/formatter/inline-js.js +0 -30
  104. data/lib/cucumber/formatter/jquery-min.js +0 -154
  105. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  106. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  107. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  108. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  109. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  110. data/lib/cucumber/step_argument.rb +0 -25
@@ -1,610 +1,23 @@
1
- # frozen_string_literal: true
2
-
3
- require 'erb'
4
- require 'cucumber/formatter/duration'
5
1
  require 'cucumber/formatter/io'
6
- require 'cucumber/formatter/html_builder'
7
- require 'pathname'
2
+ require 'cucumber/html_formatter'
3
+ require 'cucumber/formatter/message_builder'
8
4
 
9
5
  module Cucumber
10
6
  module Formatter
11
- class Html
12
- # TODO: remove coupling to types
13
- AST_CLASSES = {
14
- Cucumber::Core::Ast::Scenario => 'scenario',
15
- Cucumber::Core::Ast::ScenarioOutline => 'scenario outline'
16
- }
17
-
18
- AST_DATA_TABLE = LegacyApi::Ast::MultilineArg::DataTable
19
-
20
- include ERB::Util # for the #h method
21
- include Duration
7
+ class HTML < MessageBuilder
22
8
  include Io
23
9
 
24
- attr_reader :builder
25
- private :builder
26
-
27
- def initialize(runtime, path_or_io, options)
28
- @io = ensure_io(path_or_io)
29
- @runtime = runtime
30
- @options = options
31
- @buffer = {}
32
- @builder = HtmlBuilder.new(target: @io, indent: 0)
33
- @feature_number = 0
34
- @scenario_number = 0
35
- @step_number = 0
36
- @header_red = nil
37
- @delayed_messages = []
38
- @inside_outline = false
39
- @previous_step_keyword = nil
40
- end
41
-
42
- def embed(src, mime_type, label)
43
- if image?(mime_type)
44
- src = src_is_file_or_data?(src) ? src : "data:#{standardize_mime_type(mime_type)},#{src}"
45
- builder.embed(type: :image, src: path(src), label: label, id: next_id(:img))
46
- else
47
- builder.embed(type: :text, src: src, label: label, id: next_id(:text))
48
- end
49
- end
50
-
51
- def path(src)
52
- if @io.respond_to?(:path) && File.file?(src)
53
- out_dir = Pathname.new(File.dirname(File.absolute_path(@io.path)))
54
- src = Pathname.new(File.absolute_path(src)).relative_path_from(out_dir)
55
- end
56
-
57
- src
58
- end
59
-
60
- def standardize_mime_type(mime_type)
61
- mime_type =~ /;base[0-9]+$/ ? mime_type : mime_type + ';base64'
62
- end
63
-
64
- def src_is_file_or_data?(src)
65
- File.file?(src) || src =~ /^data:image\/(png|gif|jpg|jpeg);base64,/
66
- end
67
-
68
- def image?(mime_type)
69
- mime_type =~ /^image\/(png|gif|jpg|jpeg)/
70
- end
71
-
72
- def before_features(features)
73
- @step_count = features && features.step_count || 0 # TODO: Make this work with core!
74
-
75
- builder.build_document!
76
- builder.format_features! features
77
- end
78
-
79
- def after_features(features)
80
- print_stats(features)
81
- builder << '</div>'
82
- builder << '</body>'
83
- builder << '</html>'
84
- end
85
-
86
- def before_feature(_feature)
87
- @exceptions = []
88
- builder << '<div class="feature">'
89
- end
90
-
91
- def after_feature(_feature)
92
- builder << '</div>'
93
- end
94
-
95
- def before_comment(_comment)
96
- builder << '<pre class="comment">'
97
- end
98
-
99
- def after_comment(_comment)
100
- builder << '</pre>'
101
- end
10
+ def initialize(config)
11
+ @io = ensure_io(config.out_stream)
12
+ @html_formatter = Cucumber::HTMLFormatter::Formatter.new(@io)
13
+ @html_formatter.write_pre_message
102
14
 
103
- def comment_line(comment_line)
104
- builder.text!(comment_line)
105
- builder.br
15
+ super(config)
106
16
  end
107
17
 
108
- def after_tags(_tags)
109
- @tag_spacer = nil
110
- end
111
-
112
- def tag_name(tag_name)
113
- builder.text!(@tag_spacer) if @tag_spacer
114
- @tag_spacer = ' '
115
- builder.span(tag_name, :class => 'tag')
116
- end
117
-
118
- def feature_name(keyword, name)
119
- lines = name.split(/\r?\n/)
120
- return if lines.empty?
121
- builder.h2 do |h2|
122
- builder.span(keyword + ': ' + lines[0], :class => 'val')
123
- end
124
- builder.p(:class => 'narrative') do
125
- lines[1..-1].each do |line|
126
- builder.text!(line.strip)
127
- builder.br
128
- end
129
- end
130
- end
131
-
132
- def before_test_case(_test_case)
133
- @previous_step_keyword = nil
134
- end
135
-
136
- def before_background(_background)
137
- @in_background = true
138
- builder << '<div class="background">'
139
- end
140
-
141
- def after_background(_background)
142
- @in_background = nil
143
- builder << '</div>'
144
- end
145
-
146
- def background_name(keyword, name, _file_colon_line, _source_indent)
147
- @listing_background = true
148
- builder.h3(:id => "background_#{@scenario_number}") do |h3|
149
- builder.span(keyword, :class => 'keyword')
150
- builder.text!(' ')
151
- builder.span(name, :class => 'val')
152
- end
153
- end
154
-
155
- def before_feature_element(feature_element)
156
- @scenario_number += 1
157
- @scenario_red = false
158
- css_class = AST_CLASSES[feature_element.class]
159
- builder << "<div class='#{css_class}'>"
160
- @in_scenario_outline = feature_element.class == Cucumber::Core::Ast::ScenarioOutline
161
- end
162
-
163
- def after_feature_element(_feature_element)
164
- unless @in_scenario_outline
165
- print_messages
166
- builder << '</ol>'
167
- end
168
- builder << '</div>'
169
- @in_scenario_outline = nil
170
- end
171
-
172
- def scenario_name(keyword, name, file_colon_line, _source_indent)
173
- builder.span(:class => 'scenario_file') do
174
- builder << file_colon_line
175
- end
176
- @listing_background = false
177
- scenario_id = "scenario_#{@scenario_number}"
178
- if @inside_outline
179
- @outline_row += 1
180
- scenario_id += "_#{@outline_row}"
181
- @scenario_red = false
182
- end
183
- builder.h3(:id => scenario_id) do
184
- builder.span(keyword + ':', :class => 'keyword')
185
- builder.text!(' ')
186
- builder.span(name, :class => 'val')
187
- end
188
- end
189
-
190
- def before_outline_table(_outline_table)
191
- @inside_outline = true
192
- @outline_row = 0
193
- builder << '<table>'
194
- end
195
-
196
- def after_outline_table(_outline_table)
197
- builder << '</table>'
198
- @outline_row = nil
199
- @inside_outline = false
200
- end
201
-
202
- def before_examples(_examples)
203
- builder << '<div class="examples">'
204
- end
205
-
206
- def after_examples(_examples)
207
- builder << '</div>'
208
- end
209
-
210
- def examples_name(keyword, name)
211
- builder.h4 do
212
- builder.span(keyword, :class => 'keyword')
213
- builder.text!(' ')
214
- builder.span(name, :class => 'val')
215
- end
216
- end
217
-
218
- def before_steps(_steps)
219
- builder << '<ol>'
220
- end
221
-
222
- def after_steps(_steps)
223
- print_messages
224
- builder << '</ol>' if @in_background || @in_scenario_outline
225
- end
226
-
227
- def before_step(step)
228
- print_messages
229
- @step_id = step.dom_id
230
- @step_number += 1
231
- @step = step
232
- end
233
-
234
- def after_step(_step)
235
- move_progress
236
- end
237
-
238
- def before_step_result(_keyword, step_match, _multiline_arg, status, exception, _source_indent, background, _file_colon_line)
239
- @step_match = step_match
240
- @hide_this_step = false
241
- if exception
242
- if @exceptions.include?(exception)
243
- @hide_this_step = true
244
- return
245
- end
246
- @exceptions << exception
247
- end
248
- if status != :failed && @in_background ^ background
249
- @hide_this_step = true
250
- return
251
- end
252
- @status = status
253
- return if @hide_this_step
254
- scenario_color(status)
255
- builder << "<li id='#{@step_id}' class='step #{status}'>"
256
- end
257
-
258
- def after_step_result(keyword, step_match, _multiline_arg, status, _exception, _source_indent, _background, _file_colon_line)
259
- return if @hide_this_step
260
- # print snippet for undefined steps
261
- unless outline_step?(@step)
262
- keyword = @step.actual_keyword(@previous_step_keyword)
263
- @previous_step_keyword = keyword
264
- end
265
- if status == :undefined
266
- builder.pre do |pre|
267
- # TODO: snippet text should be an event sent to the formatter so we don't
268
- # have this couping to the runtime.
269
- pre << @runtime.snippet_text(keyword, step_match.instance_variable_get('@name') || '', @step.multiline_arg)
270
- end
271
- end
272
- builder << '</li>'
273
- print_messages
274
- end
275
-
276
- def step_name(keyword, step_match, status, _source_indent, background, _file_colon_line)
277
- background_in_scenario = background && !@listing_background
278
- @skip_step = background_in_scenario
279
-
280
- unless @skip_step
281
- build_step(keyword, step_match, status)
282
- end
283
- end
284
-
285
- def exception(exception, _status)
286
- return if @hide_this_step
287
- print_messages
288
- build_exception_detail(exception)
289
- end
290
-
291
- def extra_failure_content(file_colon_line)
292
- @snippet_extractor ||= SnippetExtractor.new
293
- "<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
294
- end
295
-
296
- def before_multiline_arg(multiline_arg)
297
- return if @hide_this_step || @skip_step
298
- if AST_DATA_TABLE === multiline_arg
299
- builder << '<table>'
300
- end
301
- end
302
-
303
- def after_multiline_arg(multiline_arg)
304
- return if @hide_this_step || @skip_step
305
- if AST_DATA_TABLE === multiline_arg
306
- builder << '</table>'
307
- end
308
- end
309
-
310
- def doc_string(string)
311
- return if @hide_this_step
312
- builder.pre(:class => 'val') do |pre|
313
- builder << h(string).gsub("\n", '&#x000A;')
314
- end
315
- end
316
-
317
- def before_table_row(table_row)
318
- @row_id = table_row.dom_id
319
- @col_index = 0
320
- return if @hide_this_step
321
- builder << "<tr class='step' id='#{@row_id}'>"
322
- end
323
-
324
- def after_table_row(table_row)
325
- return if @hide_this_step
326
- print_table_row_messages
327
- builder << '</tr>'
328
- if table_row.exception
329
- builder.tr do
330
- builder.td(:colspan => @col_index.to_s, :class => 'failed') do
331
- builder.pre do |pre|
332
- pre << h(format_exception(table_row.exception))
333
- end
334
- end
335
- end
336
- if table_row.exception.is_a? ::Cucumber::Pending
337
- set_scenario_color_pending
338
- else
339
- set_scenario_color_failed
340
- end
341
- end
342
- if @outline_row
343
- @outline_row += 1
344
- end
345
- @step_number += 1
346
- move_progress
347
- end
348
-
349
- def table_cell_value(value, status)
350
- return if @hide_this_step
351
-
352
- @cell_type = @outline_row == 0 ? :th : :td
353
- attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
354
- attributes[:class] += " #{status}" if status
355
- build_cell(@cell_type, value, attributes)
356
- scenario_color(status) if @inside_outline
357
- @col_index += 1
358
- end
359
-
360
- def puts(message)
361
- @delayed_messages << message
362
- end
363
-
364
- def print_messages
365
- return if @delayed_messages.empty?
366
-
367
- @delayed_messages.each do |ann|
368
- builder.li(:class => 'step message') do
369
- builder << ann
370
- end
371
- end
372
- empty_messages
373
- end
374
-
375
- def print_table_row_messages
376
- return if @delayed_messages.empty?
377
-
378
- builder.td(:class => 'message') do
379
- builder << @delayed_messages.join(', ')
380
- end
381
- empty_messages
382
- end
383
-
384
- def empty_messages
385
- @delayed_messages = []
386
- end
387
-
388
- def after_test_case(_test_case, result)
389
- if result.failed? && !@scenario_red
390
- set_scenario_color_failed
391
- end
392
- end
393
-
394
- protected
395
-
396
- def next_id(type)
397
- @indices ||= Hash.new { 0 }
398
- @indices[type] += 1
399
- "#{type}_#{@indices[type]}"
400
- end
401
-
402
- def build_exception_detail(exception)
403
- backtrace = Array.new
404
-
405
- builder.div(:class => 'message') do
406
- message = exception.message
407
-
408
- if defined?(RAILS_ROOT) && message.include?('Exception caught')
409
- matches = message.match(/Showing <i>(.+)<\/i>(?:.+) #(\d+)/)
410
- backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"] if matches
411
- matches = message.match(/<code>([^(\/)]+)<\//m)
412
- message = matches ? matches[1] : ''
413
- end
414
-
415
- unless exception.instance_of?(RuntimeError)
416
- message = "#{message} (#{exception.class})"
417
- end
418
-
419
- builder.pre do
420
- builder.text!(message)
421
- end
422
- end
423
-
424
- builder.div(:class => 'backtrace') do
425
- builder.pre do
426
- backtrace = exception.backtrace
427
- backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
428
- builder << backtrace_line(backtrace.join("\n"))
429
- end
430
- end
431
-
432
- extra = extra_failure_content(backtrace)
433
- builder << extra unless extra == ''
434
- end
435
-
436
- def scenario_color(status)
437
- if status.nil? || status == :undefined || status == :pending
438
- set_scenario_color_pending
439
- end
440
- if status == :failed
441
- set_scenario_color_failed
442
- end
443
- end
444
-
445
- def set_scenario_color_failed
446
- builder.script do
447
- builder.text!("makeRed('cucumber-header');") unless @header_red
448
- @header_red = true
449
- scenario_or_background = @in_background ? 'background' : 'scenario'
450
- builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
451
- @scenario_red = true
452
- if @options[:expand] && @inside_outline
453
- builder.text!("makeRed('#{scenario_or_background}_#{@scenario_number}_#{@outline_row}');")
454
- end
455
- end
456
- end
457
-
458
- def set_scenario_color_pending
459
- builder.script do
460
- builder.text!("makeYellow('cucumber-header');") unless @header_red
461
- scenario_or_background = @in_background ? 'background' : 'scenario'
462
- builder.text!("makeYellow('#{scenario_or_background}_#{@scenario_number}');") unless @scenario_red
463
- end
464
- end
465
-
466
- def build_step(keyword, step_match, _status)
467
- step_name = step_match.format_args(lambda { |param| %{<span class="param">#{param}</span>} })
468
- builder.div(:class => 'step_name') do |div|
469
- builder.span(keyword, :class => 'keyword')
470
- builder.span(:class => 'step val') do |name|
471
- name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
472
- end
473
- end
474
-
475
- step_file = step_match.file_colon_line
476
- step_file.gsub(/^([^:]*\.rb):(\d*)/) do
477
- if ENV['TM_PROJECT_DIRECTORY']
478
- step_file = "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
479
- end
480
- end
481
-
482
- builder.div(:class => 'step_file') do |div|
483
- builder.span do
484
- builder << step_file
485
- end
486
- end
487
- end
488
-
489
- def build_cell(cell_type, value, attributes)
490
- builder.__send__(cell_type, attributes) do
491
- builder.div do
492
- builder.span(value, :class => 'step param')
493
- end
494
- end
495
- end
496
-
497
- def move_progress
498
- builder << " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
499
- end
500
-
501
- def percent_done
502
- result = 100.0
503
- if @step_count != 0
504
- result = ((@step_number).to_f / @step_count.to_f * 1000).to_i / 10.0
505
- end
506
- result
507
- end
508
-
509
- def format_exception(exception)
510
- ([exception.message.to_s] + exception.backtrace).join("\n")
511
- end
512
-
513
- def backtrace_line(line)
514
- if ENV['TM_PROJECT_DIRECTORY']
515
- line.gsub(/^([^:]*\.(?:rb|feature|haml)):(\d*).*$/) do
516
- "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
517
- end
518
- else
519
- line
520
- end
521
- end
522
-
523
- def print_stats(features)
524
- builder << "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{format_duration(features.duration)} seconds</strong>\";</script>"
525
- builder << "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
526
- end
527
-
528
- def print_stat_string(_features)
529
- string = String.new
530
- string << dump_count(@runtime.scenarios.length, 'scenario')
531
- scenario_count = print_status_counts { |status| @runtime.scenarios(status) }
532
- string << scenario_count if scenario_count
533
- string << '<br />'
534
- string << dump_count(@runtime.steps.length, 'step')
535
- step_count = print_status_counts { |status| @runtime.steps(status) }
536
- string << step_count if step_count
537
- end
538
-
539
- def print_status_counts
540
- counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
541
- elements = yield status
542
- elements.any? ? "#{elements.length} #{status}" : nil
543
- end.compact
544
- return " (#{counts.join(', ')})" if counts.any?
545
- end
546
-
547
- def dump_count(count, what, state = nil)
548
- [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(' ')
549
- end
550
-
551
- def outline_step?(_step)
552
- not @step.step.respond_to?(:actual_keyword)
553
- end
554
-
555
- class SnippetExtractor #:nodoc:
556
- class NullConverter; def convert(code, _pre); code; end; end #:nodoc:
557
-
558
- begin
559
- require 'syntax/convertors/html'
560
- @@converter = Syntax::Convertors::HTML.for_syntax 'ruby'
561
- rescue LoadError
562
- @@converter = NullConverter.new
563
- end
564
-
565
- def snippet(error)
566
- raw_code, line = snippet_for(error[0])
567
- highlighted = @@converter.convert(raw_code, false)
568
- highlighted += "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
569
- post_process(highlighted, line)
570
- end
571
-
572
- def snippet_for(error_line)
573
- if error_line =~ /(.*):(\d+)/
574
- file = $1
575
- line = $2.to_i
576
- [lines_around(file, line), line]
577
- else
578
- ["# Couldn't get snippet for #{error_line}", 1]
579
- end
580
- end
581
-
582
- def lines_around(file, line)
583
- if File.file?(file)
584
- begin
585
- lines = File.open(file).read.split("\n")
586
- rescue ArgumentError
587
- return "# Couldn't get snippet for #{file}"
588
- end
589
- min = [0, line - 3].max
590
- max = [line + 1, lines.length - 1].min
591
- selected_lines = []
592
- selected_lines.join("\n")
593
- lines[min..max].join("\n")
594
- else
595
- "# Couldn't get snippet for #{file}"
596
- end
597
- end
598
-
599
- def post_process(highlighted, offending_line)
600
- new_lines = []
601
- highlighted.split("\n").each_with_index do |line, i|
602
- new_line = "<span class=\"linenum\">#{offending_line + i - 2}</span>#{line}"
603
- new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
604
- new_lines << new_line
605
- end
606
- new_lines.join("\n")
607
- end
18
+ def output_envelope(envelope)
19
+ @html_formatter.write_message(envelope)
20
+ @html_formatter.write_post_message if envelope.test_run_finished
608
21
  end
609
22
  end
610
23
  end
@@ -54,7 +54,7 @@ module Cucumber
54
54
 
55
55
  headers.scan(str_scanner) do |header|
56
56
  header = header.compact!
57
- hash_headers[header[0]] = header[1] ? header[1].strip : header[1]
57
+ hash_headers[header[0]] = header[1]&.strip
58
58
  end
59
59
 
60
60
  hash_headers
@@ -8,7 +8,7 @@ module Cucumber
8
8
  end
9
9
 
10
10
  def method_missing(message, *args)
11
- @receiver.send(message, *args) if @receiver.respond_to?(message)
11
+ @receiver.respond_to?(message) ? @receiver.send(message, *args) : super
12
12
  end
13
13
 
14
14
  def respond_to_missing?(name, include_private = false)