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