aslakhellesoy-cucumber 0.1.12 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/History.txt +38 -3
  2. data/Manifest.txt +17 -1
  3. data/README.txt +2 -39
  4. data/bin/cucumber +1 -1
  5. data/examples/calculator_ruby_features/features/addition.rb +16 -0
  6. data/examples/i18n/ar/features/step_definitons/calculator_steps.rb +1 -1
  7. data/examples/i18n/da/features/step_definitons/kalkulator_steps.rb +1 -0
  8. data/examples/i18n/de/features/step_definitons/calculator_steps.rb +1 -0
  9. data/examples/i18n/en/features/step_definitons/calculator_steps.rb +1 -0
  10. data/examples/i18n/es/features/step_definitons/calculador_steps.rb +1 -0
  11. data/examples/i18n/et/features/step_definitions/kalkulaator_steps.rb +1 -0
  12. data/examples/i18n/fr/features/addition.feature +13 -11
  13. data/examples/i18n/fr/features/step_definitions/calculatrice_steps.rb +6 -2
  14. data/examples/i18n/id/features/step_definitons/calculator_steps.rb +1 -0
  15. data/examples/i18n/it/features/step_definitons/calcolatrice_steps.rb +1 -0
  16. data/examples/i18n/ja/features/step_definitons/calculator_steps.rb +2 -0
  17. data/examples/i18n/lt/features/step_definitons/calculator_steps.rb +1 -0
  18. data/examples/i18n/no/features/step_definitons/kalkulator_steps.rb +1 -0
  19. data/examples/i18n/pt/features/step_definitions/calculadora_steps.rb +1 -0
  20. data/examples/i18n/ro/features/step_definitons/calculator_steps.rb +1 -0
  21. data/examples/i18n/se/features/step_definitons/kalkulator_steps.rb +1 -0
  22. data/examples/i18n/zh-CN/features/step_definitons/calculator_steps.rb +1 -0
  23. data/examples/selenium/features/search.feature +1 -1
  24. data/examples/selenium/features/step_definitons/stories_steps.rb +2 -3
  25. data/examples/tickets/features/lib/eatting_machine.rb +18 -0
  26. data/examples/tickets/features/lib/pantry.rb +20 -0
  27. data/examples/tickets/features/scenario_outline.feature +64 -0
  28. data/examples/tickets/features/step_definitons/scenario_outline_steps.rb +34 -0
  29. data/examples/tickets/features/step_definitons/tickets_steps.rb +4 -0
  30. data/gem_tasks/fix_cr_lf.rake +1 -1
  31. data/gem_tasks/yard.rake +8 -0
  32. data/lib/autotest/cucumber_mixin.rb +3 -3
  33. data/lib/cucumber/broadcaster.rb +1 -1
  34. data/lib/cucumber/cli.rb +87 -42
  35. data/lib/cucumber/core_ext/exception.rb +20 -0
  36. data/lib/cucumber/core_ext/string.rb +1 -1
  37. data/lib/cucumber/executor.rb +35 -18
  38. data/lib/cucumber/formatters/ansicolor.rb +65 -74
  39. data/lib/cucumber/formatters/html_formatter.rb +33 -10
  40. data/lib/cucumber/formatters/pretty_formatter.rb +58 -16
  41. data/lib/cucumber/formatters/progress_formatter.rb +3 -0
  42. data/lib/cucumber/formatters/unicode.rb +27 -0
  43. data/lib/cucumber/languages.yml +6 -4
  44. data/lib/cucumber/platform.rb +1 -0
  45. data/lib/cucumber/rails/world.rb +6 -6
  46. data/lib/cucumber/step_mother.rb +3 -0
  47. data/lib/cucumber/tree/feature.rb +28 -2
  48. data/lib/cucumber/tree/scenario.rb +62 -1
  49. data/lib/cucumber/tree/step.rb +32 -1
  50. data/lib/cucumber/treetop_parser/feature.treetop.erb +54 -7
  51. data/lib/cucumber/treetop_parser/feature_ar.rb +377 -18
  52. data/lib/cucumber/treetop_parser/feature_cy.rb +377 -18
  53. data/lib/cucumber/treetop_parser/feature_da.rb +377 -18
  54. data/lib/cucumber/treetop_parser/feature_de.rb +377 -18
  55. data/lib/cucumber/treetop_parser/feature_en-lol.rb +377 -18
  56. data/lib/cucumber/treetop_parser/feature_en-tx.rb +377 -18
  57. data/lib/cucumber/treetop_parser/feature_en.rb +377 -18
  58. data/lib/cucumber/treetop_parser/feature_es.rb +377 -18
  59. data/lib/cucumber/treetop_parser/feature_et.rb +377 -18
  60. data/lib/cucumber/treetop_parser/feature_fr.rb +389 -30
  61. data/lib/cucumber/treetop_parser/feature_id.rb +377 -18
  62. data/lib/cucumber/treetop_parser/feature_it.rb +377 -18
  63. data/lib/cucumber/treetop_parser/feature_ja.rb +377 -18
  64. data/lib/cucumber/treetop_parser/feature_lt.rb +377 -18
  65. data/lib/cucumber/treetop_parser/feature_nl.rb +377 -18
  66. data/lib/cucumber/treetop_parser/feature_no.rb +377 -18
  67. data/lib/cucumber/treetop_parser/feature_pl.rb +377 -18
  68. data/lib/cucumber/treetop_parser/feature_pt.rb +377 -18
  69. data/lib/cucumber/treetop_parser/feature_ro.rb +377 -18
  70. data/lib/cucumber/treetop_parser/feature_ro2.rb +377 -18
  71. data/lib/cucumber/treetop_parser/feature_ru.rb +377 -18
  72. data/lib/cucumber/treetop_parser/feature_se.rb +377 -18
  73. data/lib/cucumber/treetop_parser/feature_zh-CN.rb +377 -18
  74. data/lib/cucumber/version.rb +1 -1
  75. data/lib/cucumber/world/pending.rb +22 -0
  76. data/lib/cucumber/world.rb +1 -0
  77. data/lib/cucumber.rb +2 -0
  78. data/rails_generators/cucumber/templates/env.rb +1 -0
  79. data/rails_generators/feature/feature_generator.rb +22 -2
  80. data/rails_generators/feature/templates/feature.erb +15 -12
  81. data/rails_generators/feature/templates/steps.erb +16 -14
  82. data/spec/cucumber/cli_spec.rb +87 -6
  83. data/spec/cucumber/executor_spec.rb +102 -30
  84. data/spec/cucumber/formatters/ansicolor_spec.rb +10 -10
  85. data/spec/cucumber/formatters/html_formatter_spec.rb +30 -0
  86. data/spec/cucumber/formatters/pretty_formatter_spec.rb +139 -4
  87. data/spec/cucumber/formatters/progress_formatter_spec.rb +16 -0
  88. data/spec/cucumber/tree/feature_spec.rb +84 -5
  89. data/spec/cucumber/tree/row_scenario_outline_spec.rb +73 -0
  90. data/spec/cucumber/tree/row_step_outline_spec.rb +38 -0
  91. data/spec/cucumber/tree/scenario_outline_spec.rb +50 -0
  92. data/spec/cucumber/tree/step_outline_spec.rb +17 -0
  93. data/spec/cucumber/tree/step_spec.rb +9 -0
  94. data/spec/cucumber/treetop_parser/empty_scenario_outline.feature +3 -0
  95. data/spec/cucumber/treetop_parser/feature_parser_spec.rb +22 -0
  96. data/spec/cucumber/treetop_parser/invalid_scenario_outlines.feature +7 -0
  97. data/spec/cucumber/treetop_parser/scenario_outline.feature +16 -0
  98. data/spec/cucumber/world/pending_spec.rb +46 -0
  99. data/spec/spec_helper.rb +2 -1
  100. metadata +19 -4
  101. data/TODO.txt +0 -26
@@ -6,7 +6,6 @@ module Cucumber
6
6
  include ANSIColor
7
7
 
8
8
  INDENT = "\n "
9
- BACKTRACE_FILTER_PATTERNS = [/vendor\/rails/, /vendor\/plugins\/cucumber/, /spec\/expectations/, /spec\/matchers/]
10
9
 
11
10
  def initialize(io, step_mother, options={})
12
11
  @io = (io == STDOUT) ? Kernel : io
@@ -18,6 +17,8 @@ module Cucumber
18
17
  @pending_steps = []
19
18
  @skipped = []
20
19
  @last_executed_was_row = false
20
+ @pending_messages = {}
21
+ @forced_pending_step_count = 0
21
22
  end
22
23
 
23
24
  def feature_executing(feature)
@@ -40,17 +41,23 @@ module Cucumber
40
41
  end
41
42
 
42
43
  def scenario_executing(scenario)
44
+ scenario_or_scenario_outline_keyword = scenario.outline? ? Cucumber.language['scenario_outline'] : Cucumber.language['scenario']
45
+
43
46
  @scenario_failed = false
44
47
  @io.puts if @last_executed_was_row && !scenario.row?
45
48
  if scenario.row?
46
49
  @last_executed_was_row = true
47
50
  @io.print " |"
48
51
  else
52
+ scenario_text = "#{scenario_or_scenario_outline_keyword}: #{scenario.name}"
53
+
49
54
  if scenario.pending?
50
55
  @pending_scenarios << scenario
51
- @io.print pending(" #{Cucumber.language['scenario']}: #{scenario.name}")
56
+ @io.print pending(" #{scenario_text}")
57
+ elsif scenario.outline?
58
+ @io.print skipped(" #{scenario_text}")
52
59
  else
53
- @io.print passed(" #{Cucumber.language['scenario']}: #{scenario.name}")
60
+ @io.print passed(" #{scenario_text}")
54
61
  end
55
62
  @last_executed_was_row = false
56
63
 
@@ -78,6 +85,7 @@ module Cucumber
78
85
 
79
86
  def step_passed(step, regexp, args)
80
87
  if step.row?
88
+ args = step.visible_args if step.outline?
81
89
  @passed << step
82
90
  print_passed_args(args)
83
91
  else
@@ -93,6 +101,7 @@ module Cucumber
93
101
 
94
102
  def step_failed(step, regexp, args)
95
103
  if step.row?
104
+ args = step.visible_args if step.outline?
96
105
  @failed << step
97
106
  @scenario_failed = true
98
107
  print_failed_args(args)
@@ -112,12 +121,17 @@ module Cucumber
112
121
  def step_skipped(step, regexp, args)
113
122
  @skipped << step
114
123
  if step.row?
124
+ args = step.visible_args if step.outline?
115
125
  print_skipped_args(args)
116
126
  else
117
127
  @io.print skipped(" #{step.keyword} #{step.format(regexp){|param| skipped_param(param) << skipped}}")
118
128
  if @options[:source]
119
129
  @io.print padding_spaces(step)
120
- @io.print source_comment(step)
130
+ if step.outline?
131
+ @io.print comment("# #{step.file}:#{step.line}")
132
+ else
133
+ @io.print source_comment(step)
134
+ end
121
135
  end
122
136
  @io.puts
123
137
  end
@@ -125,6 +139,7 @@ module Cucumber
125
139
 
126
140
  def step_pending(step, regexp, args)
127
141
  if step.row?
142
+ args = step.visible_args if step.outline?
128
143
  @pending_steps << step
129
144
  print_pending_args(args)
130
145
  else
@@ -136,38 +151,61 @@ module Cucumber
136
151
  end
137
152
  @io.puts
138
153
  end
154
+ if step.forced_to_pending?
155
+ @pending_messages[regexp.inspect] ||= "#{step.keyword} #{regexp.inspect} (#{step.error.message}) #{source_comment(step)}"
156
+ @forced_pending_step_count += 1
157
+ end
158
+ end
159
+
160
+ def step_traced(step, regexp, args)
161
+ @io.print skipped(" #{step.keyword} #{step.format(regexp){|param| skipped_param(param) << skipped}}")
162
+ if @options[:source]
163
+ @io.print padding_spaces(step)
164
+ @io.print comment("# #{step.file}:#{step.line}")
165
+ end
166
+ @io.puts
139
167
  end
140
168
 
141
169
  def output_failing_step(step)
142
- backtrace = step.error.backtrace || []
143
- clean_backtrace = backtrace.map {|b| b.split("\n") }.flatten.reject do |line|
144
- BACKTRACE_FILTER_PATTERNS.detect{|p| line =~ p}
145
- end.map { |line| line.strip }
146
170
  @io.puts failed(" #{step.error.message.split("\n").join(INDENT)} (#{step.error.class})")
147
- @io.puts failed(" #{clean_backtrace.join(INDENT)}")
171
+ @io.puts failed(" #{step.error.cucumber_backtrace.join(INDENT)}")
148
172
  end
149
173
 
150
174
  def dump
151
175
  @io.puts
152
176
 
177
+ print_pending_messages if @pending_messages.any?
178
+
153
179
  @io.puts pending("#{@pending_scenarios.length} scenarios pending") if @pending_scenarios.any?
154
180
 
155
181
  @io.puts passed("#{@passed.length} steps passed") if @passed.any?
156
182
  @io.puts failed("#{@failed.length} steps failed") if @failed.any?
157
183
  @io.puts skipped("#{@skipped.length} steps skipped") if @skipped.any?
158
- @io.puts pending("#{@pending_steps.length} steps pending") if @pending_steps.any?
184
+ if @pending_steps.any?
185
+ @io.print pending("#{@pending_steps.length} steps pending")
186
+ @io.print pending(" (#{number_of_unimplemented_steps} with no step definition)") if number_of_unimplemented_steps > 0
187
+ @io.puts
188
+ end
159
189
 
160
190
  @io.print reset
161
191
 
162
192
  print_snippets if @options[:snippets]
163
193
  end
164
194
 
195
+ def print_pending_messages
196
+ @io.puts "Pending Notes:"
197
+ @pending_messages.each_value do |message|
198
+ @io.puts message
199
+ end
200
+ @io.puts
201
+ end
202
+
165
203
  def print_snippets
166
204
  snippets = @pending_steps
167
205
  snippets.delete_if {|snippet| snippet.row? || @step_mother.has_step_definition?(snippet.name)}
168
206
 
169
207
  unless snippets.empty?
170
- @io.puts "\nYou can use these snippets to implement pending steps:\n\n"
208
+ @io.puts "\nYou can use these snippets to implement pending steps which have no step definition:\n\n"
171
209
 
172
210
  prev_keyword = nil
173
211
  snippets = snippets.map do |step|
@@ -184,6 +222,10 @@ module Cucumber
184
222
 
185
223
  private
186
224
 
225
+ def number_of_unimplemented_steps
226
+ @pending_steps.length - @forced_pending_step_count
227
+ end
228
+
187
229
  def escape_regexp_characters(string)
188
230
  Regexp.escape(string).gsub('\ ', ' ').gsub('/', '\/') unless string.nil?
189
231
  end
@@ -204,7 +246,7 @@ module Cucumber
204
246
  @current_column
205
247
  end
206
248
 
207
- def print_row row_args, &colorize_proc
249
+ def print_row(row_args, &colorize_proc)
208
250
  colorize_proc = Proc.new{|row_element| row_element} unless colorize_proc
209
251
 
210
252
  row_args.each do |row_arg|
@@ -214,19 +256,19 @@ module Cucumber
214
256
  end
215
257
  end
216
258
 
217
- def print_passed_args args
259
+ def print_passed_args(args)
218
260
  print_row(args) {|arg| passed(arg)}
219
261
  end
220
262
 
221
- def print_skipped_args args
263
+ def print_skipped_args(args)
222
264
  print_row(args) {|arg| skipped(arg)}
223
265
  end
224
266
 
225
- def print_failed_args args
267
+ def print_failed_args(args)
226
268
  print_row(args) {|arg| failed(arg)}
227
269
  end
228
270
 
229
- def print_pending_args args
271
+ def print_pending_args(args)
230
272
  print_row(args) {|arg| pending(arg)}
231
273
  end
232
274
  end
@@ -36,6 +36,9 @@ module Cucumber
36
36
  @io.print skipped('_')
37
37
  end
38
38
 
39
+ def step_traced(step, regexp, args)
40
+ end
41
+
39
42
  def dump
40
43
  @io.puts pending
41
44
  @io.puts "\nPending Scenarios:\n\n" if @pending_scenarios.any?
@@ -0,0 +1,27 @@
1
+ # Require this file if you need Unicode support.
2
+ require 'cucumber/platform'
3
+ require 'cucumber/formatters/ansicolor'
4
+
5
+ $KCODE='u'
6
+
7
+ if $CUCUMBER_WINDOWS_MRI && `chcp` =~ /Active code page: (\d+)/
8
+ codepage = $1.to_i
9
+ codepages = (1251..1252)
10
+
11
+ if codepages.include?(codepage)
12
+ $CUCUMBER_CODEPAGE = "cp#{codepage}"
13
+
14
+ require 'iconv'
15
+ module Kernel
16
+ alias cucumber_print print
17
+ def print(*a)
18
+ cucumber_print *Iconv.iconv($CUCUMBER_CODEPAGE, "UTF-8", *a)
19
+ end
20
+
21
+ alias cucumber_puts puts
22
+ def puts(*a)
23
+ cucumber_puts *Iconv.iconv($CUCUMBER_CODEPAGE, "UTF-8", *a)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -7,6 +7,8 @@
7
7
  "en":
8
8
  feature: Feature
9
9
  scenario: Scenario
10
+ scenario_outline: Scenario Outline
11
+ examples: Examples
10
12
  more_examples: More Examples
11
13
  given_scenario: GivenScenario
12
14
  given: Given
@@ -108,11 +110,11 @@
108
110
  but: Kuid
109
111
  # French
110
112
  "fr":
111
- feature: Fonction
112
- scenario: Scenario
113
+ feature: Fonctionnalité
114
+ scenario: Scénario
113
115
  more_examples: Plus d'exemples
114
- given_scenario: SoitScenario
115
- given: Soit
116
+ given_scenario: Soit le Scénario
117
+ given: Etant donné
116
118
  when: Lorsque
117
119
  then: Alors
118
120
  and: Et
@@ -7,3 +7,4 @@ $CUCUMBER_IRONRUBY = Config::CONFIG['sitedir'] =~ /IronRuby/
7
7
  $CUCUMBER_WINDOWS = Config::CONFIG['host_os'] =~ /mswin|mingw/
8
8
  $CUCUMBER_WINDOWS_MRI = $CUCUMBER_WINDOWS && !$CUCUMBER_JRUBY && !$CUCUMBER_IRONRUBY
9
9
  $CUCUMBER_RAILS = defined?(Rails)
10
+ $CUCUMBER_RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
@@ -11,7 +11,7 @@ require 'test/unit/testresult'
11
11
 
12
12
  # These allow exceptions to come through as opposed to being caught and having non-helpful responses returned.
13
13
  ActionController::Base.class_eval do
14
- def perform_action
14
+ def perform_action_with_rescue
15
15
  perform_action_without_rescue
16
16
  end
17
17
  end
@@ -24,7 +24,7 @@ end
24
24
  # So that Test::Unit doesn't launch at the end - makes it think it has already been run.
25
25
  Test::Unit.run = true if Test::Unit.respond_to?(:run=)
26
26
 
27
- $main = self
27
+ $cucumber_toplevel = self
28
28
 
29
29
  module Cucumber #:nodoc:
30
30
  module Rails
@@ -44,22 +44,22 @@ module Cucumber #:nodoc:
44
44
  def self.use_transactional_fixtures
45
45
  World.use_transactional_fixtures = true
46
46
  if defined?(ActiveRecord::Base)
47
- $main.Before do
47
+ $cucumber_toplevel.Before do
48
48
  if ActiveRecord::Base.connection.respond_to?(:increment_open_transactions)
49
49
  ActiveRecord::Base.connection.increment_open_transactions
50
50
  else
51
- ActiveRecord::Base.send :increment_open_transactions
51
+ ActiveRecord::Base.__send__(:increment_open_transactions)
52
52
  end
53
53
  ActiveRecord::Base.connection.begin_db_transaction
54
54
  ActionMailer::Base.deliveries = [] if defined?(ActionMailer::Base)
55
55
  end
56
56
 
57
- $main.After do
57
+ $cucumber_toplevel.After do
58
58
  ActiveRecord::Base.connection.rollback_db_transaction
59
59
  if ActiveRecord::Base.connection.respond_to?(:decrement_open_transactions)
60
60
  ActiveRecord::Base.connection.decrement_open_transactions
61
61
  else
62
- ActiveRecord::Base.send :decrement_open_transactions
62
+ ActiveRecord::Base.__send__(:decrement_open_transactions)
63
63
  end
64
64
  end
65
65
  end
@@ -4,6 +4,9 @@ module Cucumber
4
4
  class Pending < StandardError
5
5
  end
6
6
 
7
+ class ForcedPending < Pending
8
+ end
9
+
7
10
  class Duplicate < StandardError
8
11
  end
9
12
 
@@ -19,11 +19,23 @@ module Cucumber
19
19
  scenario
20
20
  end
21
21
 
22
+ def add_scenario_outline(name, line, &proc)
23
+ scenario = ScenarioOutline.new(self, name, line, &proc)
24
+ @scenarios << scenario
25
+ scenario
26
+ end
27
+
22
28
  def add_row_scenario(template_scenario, values, line)
23
29
  scenario = RowScenario.new(self, template_scenario, values, line)
24
30
  @scenarios << scenario
25
31
  scenario
26
32
  end
33
+
34
+ def add_row_scenario_outline(template_scenario, values, line)
35
+ scenario = RowScenarioOutline.new(self, template_scenario, values, line)
36
+ @scenarios << scenario
37
+ scenario
38
+ end
27
39
 
28
40
  def scenario_named(name)
29
41
  @scenarios.find {|s| s.name == name}
@@ -38,26 +50,40 @@ module Cucumber
38
50
  add_scenario(name, line, &proc)
39
51
  end
40
52
 
53
+ def ScenarioOutline(name, &proc)
54
+ line = caller[0] =~ /:(\d+)$/ ? $1 : nil
55
+ add_scenario_outline(name, line, &proc)
56
+ end
57
+
41
58
  def Table(matrix = [], &proc)
42
59
  table = Table.new(matrix)
43
60
  proc.call(table)
61
+
44
62
  template_scenario = @scenarios.last
45
63
  template_scenario.table_header = matrix[0]
64
+
46
65
  matrix[1..-1].each do |row|
47
- add_row_scenario(template_scenario, row, row.line)
66
+ if template_scenario.outline?
67
+ add_row_scenario_outline(template_scenario, row, row.line)
68
+ else
69
+ add_row_scenario(template_scenario, row, row.line)
70
+ end
48
71
  end
49
72
  end
50
73
 
51
74
  def accept(visitor)
52
75
  visitor.visit_header(@header)
53
76
  @scenarios.each do |scenario|
54
- if scenario.row?
77
+ if scenario.outline? && !scenario.row?
78
+ visitor.visit_scenario_outline(scenario)
79
+ elsif scenario.row?
55
80
  visitor.visit_row_scenario(scenario)
56
81
  else
57
82
  visitor.visit_regular_scenario(scenario)
58
83
  end
59
84
  end
60
85
  end
86
+
61
87
  end
62
88
  end
63
89
  end
@@ -9,7 +9,9 @@ module Cucumber
9
9
 
10
10
  def accept(visitor)
11
11
  steps.each do |step|
12
- if step.row?
12
+ if step.outline? && !step.row?
13
+ visitor.visit_step_outline(step)
14
+ elsif step.row?
13
15
  visitor.visit_row_step(step)
14
16
  else
15
17
  visitor.visit_regular_step(step)
@@ -31,6 +33,10 @@ module Cucumber
31
33
  steps.empty?
32
34
  end
33
35
 
36
+ def outline?
37
+ false
38
+ end
39
+
34
40
  end
35
41
 
36
42
  class Scenario < BaseScenario
@@ -129,6 +135,22 @@ module Cucumber
129
135
 
130
136
  end
131
137
 
138
+ class ScenarioOutline < Scenario
139
+ def outline?
140
+ true
141
+ end
142
+
143
+ def length
144
+ @length ||= Cucumber.language['scenario_outline'].jlength + 2 + (@name.nil? ? 0 : @name.jlength)
145
+ end
146
+
147
+ def create_step(keyword, name, line)
148
+ step = StepOutline.new(self, keyword, name, line)
149
+ @steps_and_given_scenarios << step
150
+ step
151
+ end
152
+ end
153
+
132
154
  class RowScenario < BaseScenario
133
155
  attr_reader :line
134
156
 
@@ -171,5 +193,44 @@ module Cucumber
171
193
  end
172
194
 
173
195
  end
196
+
197
+ class RowScenarioOutline < RowScenario
198
+ def outline?
199
+ true
200
+ end
201
+
202
+ def steps
203
+ @processed_placeholders = []
204
+ @steps ||= @template_scenario.steps.map do |template_step|
205
+ build_row_step_outline(template_step)
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ def build_row_step_outline(template_step)
212
+ step_name = template_step.name
213
+ placeholder_values = []
214
+
215
+ @template_scenario.table_header.each_with_index do |column_name, column_number|
216
+
217
+ if step_name =~ placeholder_regexp(column_name)
218
+ step_name = step_name.gsub(placeholder_regexp(column_name), @values[column_number])
219
+
220
+ unless @processed_placeholders.include?(column_name)
221
+ placeholder_values << @values[column_number]
222
+ @processed_placeholders << column_name
223
+ end
224
+ end
225
+
226
+ end
227
+ RowStepOutline.new(self, template_step, step_name, placeholder_values, @line)
228
+ end
229
+
230
+ def placeholder_regexp(string)
231
+ /#{Regexp.escape("<#{string}>")}/
232
+ end
233
+ end
234
+
174
235
  end
175
236
  end
@@ -90,6 +90,14 @@ module Cucumber
90
90
  def padding_length
91
91
  @scenario.step_padding_length(self)
92
92
  end
93
+
94
+ def forced_to_pending?
95
+ @error.kind_of?(ForcedPending)
96
+ end
97
+
98
+ def outline?
99
+ false
100
+ end
93
101
  end
94
102
 
95
103
  class Step < BaseStep
@@ -102,7 +110,7 @@ module Cucumber
102
110
 
103
111
  def initialize(scenario, keyword, name, line)
104
112
  @scenario, @keyword, @name, @line = scenario, keyword, name, line
105
- @extra_args = []
113
+ @extra_args ||= []
106
114
  @arity = 0
107
115
  end
108
116
 
@@ -117,6 +125,12 @@ module Cucumber
117
125
  end
118
126
  end
119
127
 
128
+ class StepOutline < Step
129
+ def outline?
130
+ true
131
+ end
132
+ end
133
+
120
134
  class RowStep < BaseStep
121
135
  attr_reader :keyword
122
136
 
@@ -138,5 +152,22 @@ module Cucumber
138
152
  end
139
153
  end
140
154
 
155
+ class RowStepOutline < Step
156
+ attr_reader :visible_args
157
+
158
+ def initialize(scenario, step, name, visible_args, line)
159
+ @visible_args = visible_args
160
+ @extra_args = step.extra_args
161
+ super(scenario, keyword, name, line)
162
+ end
163
+
164
+ def row?
165
+ true
166
+ end
167
+
168
+ def outline?
169
+ true
170
+ end
171
+ end
141
172
  end
142
173
  end
@@ -13,23 +13,27 @@ grammar Feature
13
13
  end
14
14
 
15
15
  rule header
16
- (!(scenario_keyword / comment_to_eol) .)+
16
+ (!(scenario_keyword / scenario_outline_keyword / comment_to_eol) .)+
17
17
  end
18
18
 
19
19
  rule scenario_sequence
20
- head:scenario? tail:(space scenario_or_table)* {
20
+ head:scenario_outline_or_scenario? tail:(space scenario_or_scenario_outline_or_table)* {
21
21
  def compile(feature)
22
- ([head] + tail).each do |scenario_or_table|
23
- scenario_or_table.compile(feature) if scenario_or_table.respond_to?(:compile)
22
+ ([head] + tail).each do |scenario_or_scenario_outline_or_table|
23
+ scenario_or_scenario_outline_or_table.compile(feature) if scenario_or_scenario_outline_or_table.respond_to?(:compile)
24
24
  end
25
25
  end
26
26
 
27
27
  def tail
28
- super.elements.map { |elt| elt.scenario_or_table }
28
+ super.elements.map { |elt| elt.scenario_or_scenario_outline_or_table }
29
29
  end
30
30
  }
31
31
  end
32
32
 
33
+ rule scenario_outline_or_scenario
34
+ scenario_outline / scenario
35
+ end
36
+
33
37
  rule scenario
34
38
  scenario_keyword space? name:line_to_eol steps:(space step_sequence)? {
35
39
  def compile(feature)
@@ -42,9 +46,29 @@ grammar Feature
42
46
  end
43
47
  }
44
48
  end
49
+
50
+ rule scenario_outline
51
+ scenario_outline_keyword space? name:line_to_eol outline_body:(steps_and_optional_examples)? {
52
+ def compile(feature)
53
+ line = input.line_of(interval.first)
54
+ scenario = feature.add_scenario_outline(name.text_value.strip, line)
55
+ Feature.last_scenario = scenario
56
+ outline_body.compile(feature, scenario) if outline_body.respond_to?(:compile)
57
+ end
58
+ }
59
+ end
45
60
 
46
- rule scenario_or_table
47
- scenario / more_examples
61
+ rule scenario_or_scenario_outline_or_table
62
+ scenario_outline / (scenario / more_examples)
63
+ end
64
+
65
+ rule steps_and_optional_examples
66
+ steps:(space step_sequence) table:(space examples)? {
67
+ def compile(feature, scenario)
68
+ steps.step_sequence.compile(scenario) if steps.respond_to?(:step_sequence)
69
+ table.examples.compile(feature, scenario) if table.respond_to?(:examples) && table.examples.respond_to?(:compile)
70
+ end
71
+ }
48
72
  end
49
73
 
50
74
  rule more_examples
@@ -55,6 +79,14 @@ grammar Feature
55
79
  }
56
80
  end
57
81
 
82
+ rule examples
83
+ examples_keyword table {
84
+ def compile(feature, scenario)
85
+ table.compile_examples(feature, scenario)
86
+ end
87
+ }
88
+ end
89
+
58
90
  rule table
59
91
  space head:table_line body:(blank* eol space? table_line)* {
60
92
  def compile(feature)
@@ -64,6 +96,13 @@ grammar Feature
64
96
  end
65
97
  end
66
98
 
99
+ def compile_examples(feature, scenario)
100
+ scenario.table_header = head.cell_values
101
+ body.each do |table_line|
102
+ feature.add_row_scenario_outline(scenario, table_line.cell_values, table_line.line)
103
+ end
104
+ end
105
+
67
106
  def matrix
68
107
  ([head] + body).map do |table_line|
69
108
  table_line.cell_values # We're losing the line - we'll get it back when we make our own class
@@ -192,10 +231,18 @@ grammar Feature
192
231
  "<%= words['scenario'] %>" ":"?
193
232
  end
194
233
 
234
+ rule scenario_outline_keyword
235
+ "<%= words['scenario_outline'] %>" ":"?
236
+ end
237
+
195
238
  rule more_examples_keyword
196
239
  "<%= words['more_examples'] %>" ":"?
197
240
  end
198
241
 
242
+ rule examples_keyword
243
+ "<%= words['examples'] %>" ":"?
244
+ end
245
+
199
246
  rule given_scenario_keyword
200
247
  "<%= words['given_scenario'] %>" ":"?
201
248
  end