cucumber 3.1.2 → 7.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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1780 -1146
  3. data/CONTRIBUTING.md +224 -61
  4. data/README.md +144 -22
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +46 -53
  7. data/lib/cucumber/cli/configuration.rb +28 -6
  8. data/lib/cucumber/cli/main.rb +12 -12
  9. data/lib/cucumber/cli/options.rb +103 -77
  10. data/lib/cucumber/cli/profile_loader.rb +49 -26
  11. data/lib/cucumber/configuration.rb +44 -29
  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 +14 -7
  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 -52
  32. data/lib/cucumber/formatter/ast_lookup.rb +163 -0
  33. data/lib/cucumber/formatter/backtrace_filter.rb +10 -8
  34. data/lib/cucumber/formatter/console.rb +69 -69
  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 +147 -0
  43. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  44. data/lib/cucumber/formatter/interceptor.rb +11 -30
  45. data/lib/cucumber/formatter/io.rb +55 -13
  46. data/lib/cucumber/formatter/json.rb +115 -122
  47. data/lib/cucumber/formatter/junit.rb +72 -55
  48. data/lib/cucumber/formatter/message.rb +23 -0
  49. data/lib/cucumber/formatter/message_builder.rb +255 -0
  50. data/lib/cucumber/formatter/pretty.rb +360 -153
  51. data/lib/cucumber/formatter/progress.rb +30 -32
  52. data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
  53. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  54. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  55. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  56. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  57. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  58. data/lib/cucumber/formatter/rerun.rb +22 -4
  59. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  60. data/lib/cucumber/formatter/steps.rb +8 -6
  61. data/lib/cucumber/formatter/summary.rb +16 -8
  62. data/lib/cucumber/formatter/unicode.rb +15 -17
  63. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  64. data/lib/cucumber/formatter/usage.rb +17 -14
  65. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  66. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  67. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  68. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  69. data/lib/cucumber/glue/dsl.rb +19 -0
  70. data/lib/cucumber/glue/hook.rb +34 -11
  71. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  72. data/lib/cucumber/glue/proto_world.rb +37 -44
  73. data/lib/cucumber/glue/registry_and_more.rb +71 -12
  74. data/lib/cucumber/glue/registry_wrapper.rb +31 -0
  75. data/lib/cucumber/glue/snippet.rb +23 -22
  76. data/lib/cucumber/glue/step_definition.rb +42 -20
  77. data/lib/cucumber/glue/world_factory.rb +1 -1
  78. data/lib/cucumber/hooks.rb +11 -11
  79. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +2 -2
  80. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  81. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  82. data/lib/cucumber/multiline_argument.rb +4 -6
  83. data/lib/cucumber/platform.rb +3 -3
  84. data/lib/cucumber/rake/task.rb +16 -18
  85. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  86. data/lib/cucumber/rspec/doubles.rb +3 -5
  87. data/lib/cucumber/running_test_case.rb +2 -53
  88. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  89. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  90. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  91. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  92. data/lib/cucumber/runtime/support_code.rb +13 -15
  93. data/lib/cucumber/runtime/user_interface.rb +6 -16
  94. data/lib/cucumber/runtime.rb +77 -59
  95. data/lib/cucumber/step_definition_light.rb +4 -3
  96. data/lib/cucumber/step_definitions.rb +2 -2
  97. data/lib/cucumber/step_match.rb +12 -17
  98. data/lib/cucumber/step_match_search.rb +2 -1
  99. data/lib/cucumber/term/ansicolor.rb +9 -9
  100. data/lib/cucumber/term/banner.rb +56 -0
  101. data/lib/cucumber/version +1 -1
  102. data/lib/cucumber.rb +1 -1
  103. metadata +272 -81
  104. data/lib/cucumber/core_ext/string.rb +0 -11
  105. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  106. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  107. data/lib/cucumber/formatter/cucumber.css +0 -286
  108. data/lib/cucumber/formatter/cucumber.sass +0 -247
  109. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  110. data/lib/cucumber/formatter/html_builder.rb +0 -121
  111. data/lib/cucumber/formatter/inline-js.js +0 -30
  112. data/lib/cucumber/formatter/jquery-min.js +0 -154
  113. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  114. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  115. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  116. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  117. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  118. data/lib/cucumber/step_argument.rb +0 -25
@@ -1,253 +1,460 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
+ require 'gherkin/dialect'
4
5
  require 'cucumber/formatter/console'
5
6
  require 'cucumber/formatter/io'
6
7
  require 'cucumber/gherkin/formatter/escaping'
7
8
  require 'cucumber/formatter/console_counts'
8
9
  require 'cucumber/formatter/console_issues'
10
+ require 'cucumber/formatter/duration_extractor'
11
+ require 'cucumber/formatter/backtrace_filter'
12
+ require 'cucumber/formatter/ast_lookup'
9
13
 
10
14
  module Cucumber
11
15
  module Formatter
12
16
  # The formatter used for <tt>--format pretty</tt> (the default formatter).
13
17
  #
14
- # This formatter prints features to plain text - exactly how they were parsed,
15
- # just prettier. That means with proper indentation and alignment of table columns.
18
+ # This formatter prints the result of the feature executions to plain text - exactly how they were parsed.
16
19
  #
17
20
  # If the output is STDOUT (and not a file), there are bright colours to watch too.
18
21
  #
19
- class Pretty
22
+ class Pretty # rubocop:disable Metrics/ClassLength
20
23
  include FileUtils
21
24
  include Console
22
25
  include Io
23
26
  include Cucumber::Gherkin::Formatter::Escaping
24
- attr_writer :indent
25
- attr_reader :runtime
26
-
27
- def initialize(runtime, path_or_io, options)
28
- @runtime, @io, @options = runtime, ensure_io(path_or_io), options
29
- @config = runtime.configuration
30
- @exceptions = []
31
- @indent = 0
32
- @prefixes = options[:prefixes] || {}
33
- @delayed_messages = []
34
- @previous_step_keyword = nil
27
+ attr_reader :config, :options
28
+ private :config, :options
29
+ attr_reader :current_feature_uri, :current_scenario_outline, :current_examples, :current_test_case
30
+ private :current_feature_uri, :current_scenario_outline, :current_examples, :current_test_case
31
+ attr_reader :in_scenario_outline, :print_background_steps
32
+ private :in_scenario_outline, :print_background_steps
33
+
34
+ def initialize(config)
35
+ @io = ensure_io(config.out_stream, config.error_stream)
36
+ @config = config
37
+ @options = config.to_hash
35
38
  @snippets_input = []
36
- @counts = ConsoleCounts.new(runtime.configuration)
37
- @issues = ConsoleIssues.new(runtime.configuration)
39
+ @undefined_parameter_types = []
40
+ @total_duration = 0
41
+ @exceptions = []
42
+ @gherkin_sources = {}
43
+ @step_matches = {}
44
+ @ast_lookup = AstLookup.new(config)
45
+ @counts = ConsoleCounts.new(config)
46
+ @issues = ConsoleIssues.new(config, @ast_lookup)
47
+ @first_feature = true
48
+ @current_feature_uri = ''
49
+ @current_scenario_outline = nil
50
+ @current_examples = nil
51
+ @current_test_case = nil
52
+ @in_scenario_outline = false
53
+ @print_background_steps = false
54
+ @test_step_output = []
55
+ @passed_test_cases = []
56
+ @source_indent = 0
57
+ @next_comment_to_be_printed = 0
58
+
59
+ bind_events(config)
60
+ end
61
+
62
+ def bind_events(config)
63
+ config.on_event :gherkin_source_read, &method(:on_gherkin_source_read)
64
+ config.on_event :step_activated, &method(:on_step_activated)
65
+ config.on_event :test_case_started, &method(:on_test_case_started)
66
+ config.on_event :test_step_started, &method(:on_test_step_started)
67
+ config.on_event :test_step_finished, &method(:on_test_step_finished)
68
+ config.on_event :test_case_finished, &method(:on_test_case_finished)
69
+ config.on_event :test_run_finished, &method(:on_test_run_finished)
70
+ config.on_event :undefined_parameter_type, &method(:collect_undefined_parameter_type_names)
71
+ end
72
+
73
+ def on_gherkin_source_read(event)
74
+ @gherkin_sources[event.path] = event.body
75
+ end
76
+
77
+ def on_step_activated(event)
78
+ test_step, step_match = *event.attributes
79
+ @step_matches[test_step.to_s] = step_match
80
+ end
81
+
82
+ def on_test_case_started(event)
83
+ if !same_feature_as_previous_test_case?(event.test_case.location)
84
+ if first_feature?
85
+ @first_feature = false
86
+ print_profile_information
87
+ else
88
+ print_comments(gherkin_source.split("\n").length, 0)
89
+ @io.puts
90
+ end
91
+ @current_feature_uri = event.test_case.location.file
92
+ @exceptions = []
93
+ print_feature_data
94
+ if feature_has_background?
95
+ print_background_data
96
+ @print_background_steps = true
97
+ @in_scenario_outline = false
98
+ end
99
+ else
100
+ @print_background_steps = false
101
+ end
102
+ @current_test_case = event.test_case
103
+ print_step_header(current_test_case) unless print_background_steps
104
+ end
105
+
106
+ def on_test_step_started(event)
107
+ return if event.test_step.hook?
108
+ print_step_header(current_test_case) if first_step_after_printing_background_steps?(event.test_step)
109
+ end
110
+
111
+ def on_test_step_finished(event)
112
+ collect_snippet_data(event.test_step, @ast_lookup) if event.result.undefined?
113
+ return if in_scenario_outline && !options[:expand]
114
+ exception_to_be_printed = find_exception_to_be_printed(event.result)
115
+ print_step_data(event.test_step, event.result) if print_step_data?(event, exception_to_be_printed)
116
+ print_step_output
117
+ return unless exception_to_be_printed
118
+ print_exception(exception_to_be_printed, event.result.to_sym, 6)
119
+ @exceptions << exception_to_be_printed
120
+ end
121
+
122
+ def on_test_case_finished(event)
123
+ @total_duration += DurationExtractor.new(event.result).result_duration
124
+ @passed_test_cases << event.test_case if config.wip? && event.result.passed?
125
+ if in_scenario_outline && !options[:expand]
126
+ print_row_data(event.test_case, event.result)
127
+ else
128
+ exception_to_be_printed = find_exception_to_be_printed(event.result)
129
+ return unless exception_to_be_printed
130
+ print_exception(exception_to_be_printed, event.result.to_sym, 6)
131
+ @exceptions << exception_to_be_printed
132
+ end
38
133
  end
39
134
 
40
- def before_features(_features)
41
- print_profile_information
135
+ def on_test_run_finished(_event)
136
+ print_comments(gherkin_source.split("\n").length, 0) unless current_feature_uri.empty?
137
+ @io.puts
138
+ print_summary
42
139
  end
43
140
 
44
- def after_features(features)
45
- print_summary(features)
141
+ def attach(src, media_type)
142
+ return unless media_type == 'text/x.cucumber.log+plain'
143
+ @test_step_output.push src
46
144
  end
47
145
 
48
- def before_feature(_feature)
49
- @exceptions = []
50
- @indent = 0
146
+ private
147
+
148
+ def find_exception_to_be_printed(result)
149
+ return nil if result.ok?(options[:strict])
150
+ result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
151
+ exception = result.failed? ? result.exception : result
152
+ return nil if @exceptions.include?(exception)
153
+ exception
51
154
  end
52
155
 
53
- def comment_line(comment_line)
54
- @io.puts(comment_line.indent(@indent))
55
- @io.flush
156
+ def calculate_source_indent(test_case)
157
+ scenario = scenario_source(test_case).scenario
158
+ @source_indent = calculate_source_indent_for_ast_node(scenario)
56
159
  end
57
160
 
58
- def after_tags(_tags)
59
- if @indent == 1
60
- @io.puts
61
- @io.flush
161
+ def calculate_source_indent_for_ast_node(ast_node)
162
+ indent = 4 + ast_node.keyword.length
163
+ indent += 1 + ast_node.name.length
164
+ ast_node.steps.each do |step|
165
+ step_indent = 5 + step.keyword.length + step.text.length
166
+ indent = step_indent if step_indent > indent
62
167
  end
168
+ indent
63
169
  end
64
170
 
65
- def tag_name(tag_name)
66
- tag = format_string(tag_name, :tag).indent(@indent)
67
- @io.print(tag)
68
- @io.flush
69
- @indent = 1
171
+ def calculate_source_indent_for_expanded_test_case(test_case, scenario_keyword, expanded_name)
172
+ indent = 7 + scenario_keyword.length
173
+ indent += 2 + expanded_name.length
174
+ test_case.test_steps.each do |step|
175
+ if !step.hook? && step.location.lines.max >= test_case.location.lines.max
176
+ step_indent = 9 + test_step_keyword(step).length + step.text.length
177
+ indent = step_indent if step_indent > indent
178
+ end
179
+ end
180
+ indent
70
181
  end
71
182
 
72
- def feature_name(keyword, name)
73
- @io.puts("#{keyword}: #{name}")
74
- @io.puts
75
- @io.flush
183
+ def print_step_output
184
+ @test_step_output.each { |message| @io.puts(indent(format_string(message, :tag), 6)) }
185
+ @test_step_output = []
76
186
  end
77
187
 
78
- def before_feature_element(_feature_element)
79
- @indent = 2
80
- @scenario_indent = 2
188
+ def first_feature?
189
+ @first_feature
81
190
  end
82
191
 
83
- def after_feature_element(_feature_element)
84
- print_messages
85
- @io.puts
86
- @io.flush
192
+ def same_feature_as_previous_test_case?(location)
193
+ location.file == current_feature_uri
87
194
  end
88
195
 
89
- def before_background(_background)
90
- @indent = 2
91
- @scenario_indent = 2
92
- @in_background = true
196
+ def feature_has_background?
197
+ feature_children = gherkin_document.feature.children
198
+ return false if feature_children.empty?
199
+ !feature_children.first.background.nil?
93
200
  end
94
201
 
95
- def after_background(_background)
96
- print_messages
97
- @in_background = nil
98
- @io.puts
99
- @io.flush
202
+ def print_step_header(test_case)
203
+ if from_scenario_outline?(test_case)
204
+ @in_scenario_outline = true
205
+ unless same_outline_as_previous_test_case?(test_case)
206
+ @current_scenario_outline = scenario_source(test_case).scenario_outline
207
+ @io.puts
208
+ print_outline_data(current_scenario_outline)
209
+ end
210
+ unless same_examples_as_previous_test_case?(test_case)
211
+ @current_examples = scenario_source(test_case).examples
212
+ @io.puts
213
+ print_examples_data(current_examples)
214
+ end
215
+ print_expanded_row_data(current_test_case) if options[:expand]
216
+ else
217
+ @in_scenario_outline = false
218
+ @current_scenario_outline = nil
219
+ @current_examples = nil
220
+ @io.puts
221
+ @source_indent = calculate_source_indent(current_test_case)
222
+ print_scenario_data(test_case)
223
+ end
100
224
  end
101
225
 
102
- def background_name(keyword, name, file_colon_line, source_indent)
103
- print_feature_element_name(keyword, name, file_colon_line, source_indent)
226
+ def same_outline_as_previous_test_case?(test_case)
227
+ scenario_source(test_case).scenario_outline == current_scenario_outline
104
228
  end
105
229
 
106
- def before_examples_array(_examples_array)
107
- @indent = 4
108
- @io.puts
109
- @visiting_first_example_name = true
230
+ def same_examples_as_previous_test_case?(test_case)
231
+ scenario_source(test_case).examples == current_examples
232
+ end
233
+
234
+ def from_scenario_outline?(test_case)
235
+ scenario = scenario_source(test_case)
236
+ scenario.type != :Scenario
110
237
  end
111
238
 
112
- def examples_name(keyword, name)
113
- @io.puts unless @visiting_first_example_name
114
- @visiting_first_example_name = false
115
- @io.puts(" #{keyword}: #{name}")
239
+ def first_step_after_printing_background_steps?(test_step)
240
+ return false unless print_background_steps
241
+ return false unless test_step.location.lines.max >= current_test_case.location.lines.max
242
+ @print_background_steps = false
243
+ true
244
+ end
245
+
246
+ def print_feature_data
247
+ feature = gherkin_document.feature
248
+ print_language_comment(feature.location.line)
249
+ print_comments(feature.location.line, 0)
250
+ print_tags(feature.tags, 0)
251
+ print_feature_line(feature)
252
+ print_description(feature.description)
116
253
  @io.flush
117
- @indent = 6
118
- @scenario_indent = 6
119
254
  end
120
255
 
121
- def before_outline_table(outline_table)
122
- @table = outline_table
256
+ def print_language_comment(feature_line)
257
+ gherkin_source.split("\n")[0..feature_line].each do |line|
258
+ @io.puts(format_string(line, :comment)) if /# *language *:/ =~ line
259
+ end
123
260
  end
124
261
 
125
- def after_outline_table(_outline_table)
126
- @table = nil
127
- @indent = 4
262
+ def print_comments(up_to_line, indent_amount)
263
+ comments = gherkin_document.comments
264
+ return if comments.empty? || comments.length <= @next_comment_to_be_printed
265
+ comments[@next_comment_to_be_printed..-1].each do |comment|
266
+ if comment.location.line <= up_to_line
267
+ @io.puts(indent(format_string(comment.text.strip, :comment), indent_amount))
268
+ @next_comment_to_be_printed += 1
269
+ end
270
+ break if @next_comment_to_be_printed >= comments.length
271
+ end
128
272
  end
129
273
 
130
- def scenario_name(keyword, name, file_colon_line, source_indent)
131
- print_feature_element_name(keyword, name, file_colon_line, source_indent)
274
+ def print_tags(tags, indent_amount)
275
+ return if !tags || tags.empty?
276
+
277
+ @io.puts(indent(tags.map { |tag| format_string(tag.name, :tag) }.join(' '), indent_amount))
132
278
  end
133
279
 
134
- def before_step(step)
135
- @current_step = step
136
- @indent = 6
137
- print_messages
280
+ def print_feature_line(feature)
281
+ print_keyword_name(feature.keyword, feature.name, 0)
138
282
  end
139
283
 
140
- def before_step_result(_keyword, _step_match, _multiline_arg, status, exception, _source_indent, background, _file_colon_line)
141
- @hide_this_step = false
142
- if exception
143
- if @exceptions.include?(exception)
144
- @hide_this_step = true
145
- return
146
- end
147
- @exceptions << exception
284
+ def print_keyword_name(keyword, name, indent_amount, location = nil)
285
+ line = "#{keyword}:"
286
+ line += " #{name}"
287
+ @io.print(indent(line, indent_amount))
288
+ if location && options[:source]
289
+ line_comment = indent(format_string("# #{location}", :comment), @source_indent - line.length - indent_amount)
290
+ @io.print(line_comment)
148
291
  end
149
- if status != :failed && @in_background ^ background
150
- @hide_this_step = true
151
- return
292
+ @io.puts
293
+ end
294
+
295
+ def print_description(description)
296
+ return unless description
297
+ description.split("\n").each do |line|
298
+ @io.puts(line)
152
299
  end
153
- @status = status
154
300
  end
155
301
 
156
- def step_name(keyword, step_match, status, source_indent, _background, _file_colon_line)
157
- return if @hide_this_step
158
- source_indent = nil unless @options[:source]
159
- name_to_report = format_step(keyword, step_match, status, source_indent)
160
- @io.puts(name_to_report.indent(@scenario_indent + 2))
161
- print_messages
302
+ def print_background_data
303
+ @io.puts
304
+ background = gherkin_document.feature.children.first.background
305
+ @source_indent = calculate_source_indent_for_ast_node(background) if options[:source]
306
+ print_comments(background.location.line, 2)
307
+ print_background_line(background)
308
+ print_description(background.description)
309
+ @io.flush
162
310
  end
163
311
 
164
- def doc_string(string)
165
- return if @options[:no_multiline] || @hide_this_step
166
- s = %{"""\n#{string}\n"""}.indent(@indent)
167
- s = s.split("\n").map { |l| l =~ /^\s+$/ ? '' : l }.join("\n")
168
- @io.puts(format_string(s, @current_step.status))
312
+ def print_background_line(background)
313
+ print_keyword_name(background.keyword, background.name, 2, "#{current_feature_uri}:#{background.location.line}")
314
+ end
315
+
316
+ def print_scenario_data(test_case)
317
+ scenario = scenario_source(test_case).scenario
318
+ print_comments(scenario.location.line, 2)
319
+ print_tags(scenario.tags, 2)
320
+ print_scenario_line(scenario, test_case.location)
321
+ print_description(scenario.description)
169
322
  @io.flush
170
323
  end
171
324
 
172
- def exception(exception, status)
173
- return if @hide_this_step
174
- print_messages
175
- print_exception(exception, status, @indent)
325
+ def print_scenario_line(scenario, location = nil)
326
+ print_keyword_name(scenario.keyword, scenario.name, 2, location)
327
+ end
328
+
329
+ def print_step_data?(event, exception_to_be_printed)
330
+ !event.test_step.hook? && (
331
+ print_background_steps ||
332
+ event.test_step.location.lines.max >= current_test_case.location.lines.max ||
333
+ exception_to_be_printed
334
+ )
335
+ end
336
+
337
+ def print_step_data(test_step, result)
338
+ base_indent = options[:expand] && in_scenario_outline ? 8 : 4
339
+ step_keyword = test_step_keyword(test_step)
340
+ indent = options[:source] ? @source_indent - step_keyword.length - test_step.text.length - base_indent : nil
341
+ print_comments(test_step.location.lines.max, base_indent)
342
+ name_to_report = format_step(step_keyword, @step_matches.fetch(test_step.to_s) { NoStepMatch.new(test_step, test_step.text) }, result.to_sym, indent)
343
+ @io.puts(indent(name_to_report, base_indent))
344
+ print_multiline_argument(test_step, result, base_indent + 2) unless options[:no_multiline]
176
345
  @io.flush
177
346
  end
178
347
 
179
- def before_multiline_arg(multiline_arg)
180
- return if @options[:no_multiline] || @hide_this_step
181
- @table = multiline_arg
348
+ def test_step_keyword(test_step)
349
+ step = step_source(test_step).step
350
+ step.keyword
182
351
  end
183
352
 
184
- def after_multiline_arg(_multiline_arg)
185
- @table = nil
353
+ def step_source(test_step)
354
+ @ast_lookup.step_source(test_step)
186
355
  end
187
356
 
188
- def before_table_row(_table_row)
189
- return if !@table || @hide_this_step
190
- @col_index = 0
191
- @io.print ' |'.indent(@indent - 2)
357
+ def scenario_source(test_case)
358
+ @ast_lookup.scenario_source(test_case)
192
359
  end
193
360
 
194
- def after_table_row(table_row)
195
- return if !@table || @hide_this_step
196
- print_table_row_messages
197
- @io.puts
198
- if table_row.exception && !@exceptions.include?(table_row.exception)
199
- print_exception(table_row.exception, table_row.status, @indent)
200
- end
361
+ def gherkin_source
362
+ @gherkin_sources[current_feature_uri]
201
363
  end
202
364
 
203
- def after_table_cell(_cell)
204
- return unless @table
205
- @col_index += 1
365
+ def gherkin_document
366
+ @ast_lookup.gherkin_document(current_feature_uri)
206
367
  end
207
368
 
208
- def table_cell_value(value, status)
209
- return if !@table || @hide_this_step
210
- status ||= @status || :passed
211
- width = @table.col_width(@col_index)
212
- cell_text = escape_cell(value.to_s || '')
213
- padded = cell_text + (' ' * (width - cell_text.unpack('U*').length))
214
- prefix = cell_prefix(status)
215
- @io.print(' ' + format_string("#{prefix}#{padded}", status) + ::Cucumber::Term::ANSIColor.reset(' |'))
216
- @io.flush
369
+ def print_multiline_argument(test_step, result, indent)
370
+ step = step_source(test_step).step
371
+ if !step.doc_string.nil?
372
+ print_doc_string(step.doc_string.content, result.to_sym, indent)
373
+ elsif !step.data_table.nil?
374
+ print_data_table(step.data_table, result.to_sym, indent)
375
+ end
217
376
  end
218
377
 
219
- def before_test_case(_test_case)
220
- @previous_step_keyword = nil
378
+ def print_data_table(data_table, status, indent_amount)
379
+ data_table.rows.each do |row|
380
+ print_comments(row.location.line, indent_amount)
381
+ @io.puts indent(format_string(gherkin_source.split("\n")[row.location.line - 1].strip, status), indent_amount)
382
+ end
221
383
  end
222
384
 
223
- def after_test_step(test_step, result)
224
- collect_snippet_data(test_step, result)
385
+ def print_outline_data(scenario_outline) # rubocop:disable Metrics/AbcSize
386
+ print_comments(scenario_outline.location.line, 2)
387
+ print_tags(scenario_outline.tags, 2)
388
+ @source_indent = calculate_source_indent_for_ast_node(scenario_outline) if options[:source]
389
+ print_scenario_line(scenario_outline, "#{current_feature_uri}:#{scenario_outline.location.line}")
390
+ print_description(scenario_outline.description)
391
+ scenario_outline.steps.each do |step|
392
+ print_comments(step.location.line, 4)
393
+ step_line = " #{step.keyword}#{step.text}"
394
+ @io.print(format_string(step_line, :skipped))
395
+ if options[:source]
396
+ comment_line = format_string("# #{current_feature_uri}:#{step.location.line}", :comment)
397
+ @io.print(indent(comment_line, @source_indent - step_line.length))
398
+ end
399
+ @io.puts
400
+ next if options[:no_multiline]
401
+ print_doc_string(step.doc_string.content, :skipped, 6) unless step.doc_string.nil?
402
+ print_data_table(step.data_table, :skipped, 6) unless step.data_table.nil?
403
+ end
404
+ @io.flush
225
405
  end
226
406
 
227
- private
407
+ def print_doc_string(content, status, indent_amount)
408
+ s = indent(%("""\n#{content}\n"""), indent_amount)
409
+ s = s.split("\n").map { |l| l =~ /^\s+$/ ? '' : l }.join("\n")
410
+ @io.puts(format_string(s, status))
411
+ end
228
412
 
229
- def print_feature_element_name(keyword, name, file_colon_line, source_indent)
230
- @io.puts if @scenario_indent == 6
231
- names = name.empty? ? [name] : name.split("\n")
232
- line = "#{keyword}: #{names[0]}".indent(@scenario_indent)
233
- @io.print(line)
234
- if @options[:source]
235
- line_comment = "# #{file_colon_line}".indent(source_indent)
236
- @io.print(format_string(line_comment, :comment))
413
+ def print_examples_data(examples)
414
+ print_comments(examples.location.line, 4)
415
+ print_tags(examples.tags, 4)
416
+ print_keyword_name(examples.keyword, examples.name, 4)
417
+ print_description(examples.description)
418
+ unless options[:expand]
419
+ print_comments(examples.table_header.location.line, 6)
420
+ @io.puts(indent(gherkin_source.split("\n")[examples.table_header.location.line - 1].strip, 6))
237
421
  end
422
+ @io.flush
423
+ end
424
+
425
+ def print_row_data(test_case, result)
426
+ print_comments(test_case.location.lines.max, 6)
427
+ @io.print(indent(format_string(gherkin_source.split("\n")[test_case.location.lines.max - 1].strip, result.to_sym), 6))
428
+ @io.print(indent(format_string(@test_step_output.join(', '), :tag), 2)) unless @test_step_output.empty?
429
+ @test_step_output = []
238
430
  @io.puts
239
- names[1..-1].each { |s| @io.puts s.to_s }
431
+ if result.failed? || result.pending?
432
+ result = result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
433
+ exception = result.failed? ? result.exception : result
434
+ unless @exceptions.include?(exception)
435
+ print_exception(exception, result.to_sym, 6)
436
+ @exceptions << exception
437
+ end
438
+ end
240
439
  @io.flush
241
440
  end
242
441
 
243
- def cell_prefix(status)
244
- @prefixes[status]
442
+ def print_expanded_row_data(test_case)
443
+ feature = gherkin_document.feature
444
+ language_code = feature.language || 'en'
445
+ language = ::Gherkin::Dialect.for(language_code)
446
+ scenario_keyword = language.scenario_keywords[0]
447
+ row = scenario_source(test_case).row
448
+ expanded_name = '| ' + row.cells.map(&:value).join(' | ') + ' |'
449
+ @source_indent = calculate_source_indent_for_expanded_test_case(test_case, scenario_keyword, expanded_name)
450
+ @io.puts
451
+ print_keyword_name(scenario_keyword, expanded_name, 6, test_case.location)
245
452
  end
246
453
 
247
- def print_summary(features)
248
- print_statistics(features.duration, @config, @counts, @issues)
249
- print_snippets(@options)
250
- print_passing_wip(@options)
454
+ def print_summary
455
+ print_statistics(@total_duration, config, @counts, @issues)
456
+ print_snippets(options)
457
+ print_passing_wip(config, @passed_test_cases, @ast_lookup)
251
458
  end
252
459
  end
253
460
  end