square-cucumber 0.3.12.2 → 0.3.93.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. data/History.txt +117 -4
  2. data/Manifest.txt +11 -0
  3. data/Rakefile +1 -1
  4. data/config/hoe.rb +3 -2
  5. data/cucumber.yml +2 -2
  6. data/examples/i18n/ko/features/addition.feature +5 -5
  7. data/examples/i18n/ko/features/step_definitons/calculator_steps.rb +1 -1
  8. data/examples/i18n/no/features/step_definitons/kalkulator_steps.rb +1 -1
  9. data/examples/i18n/pt/features/adicao.feature +4 -4
  10. data/examples/self_test/features/support/env.rb +2 -1
  11. data/examples/sinatra/features/support/env.rb +7 -1
  12. data/examples/steps_library/features/step_definitions/steps_lib1.rb +8 -0
  13. data/examples/steps_library/features/step_definitions/steps_lib2.rb +8 -0
  14. data/examples/tickets/features/step_definitons/tickets_steps.rb +15 -0
  15. data/examples/tickets/features/table_diffing.feature +13 -0
  16. data/examples/watir/features/step_definitons/search_steps.rb +5 -1
  17. data/features/cucumber_cli_diff_disabled.feature +2 -1
  18. data/features/html_formatter/a.html +5 -7
  19. data/features/junit_formatter.feature +21 -14
  20. data/features/profiles.feature +99 -0
  21. data/features/rake_task.feature +28 -0
  22. data/features/step_definitions/cucumber_steps.rb +28 -15
  23. data/features/steps_formatter.feature +25 -0
  24. data/features/support/env.rb +9 -5
  25. data/features/table_diffing.feature +45 -0
  26. data/features/unicode_table.feature +35 -0
  27. data/features/work_in_progress.feature +1 -0
  28. data/gem_tasks/contributors.rake +4 -0
  29. data/lib/cucumber/ast/background.rb +1 -0
  30. data/lib/cucumber/ast/comment.rb +1 -0
  31. data/lib/cucumber/ast/examples.rb +1 -0
  32. data/lib/cucumber/ast/feature.rb +10 -0
  33. data/lib/cucumber/ast/features.rb +6 -1
  34. data/lib/cucumber/ast/outline_table.rb +4 -1
  35. data/lib/cucumber/ast/py_string.rb +1 -1
  36. data/lib/cucumber/ast/scenario.rb +1 -0
  37. data/lib/cucumber/ast/scenario_outline.rb +2 -0
  38. data/lib/cucumber/ast/step.rb +5 -1
  39. data/lib/cucumber/ast/step_collection.rb +1 -0
  40. data/lib/cucumber/ast/step_invocation.rb +1 -0
  41. data/lib/cucumber/ast/table.rb +306 -52
  42. data/lib/cucumber/ast/tags.rb +1 -0
  43. data/lib/cucumber/ast/visitor.rb +2 -1
  44. data/lib/cucumber/cli/configuration.rb +28 -278
  45. data/lib/cucumber/cli/drb_client.rb +3 -1
  46. data/lib/cucumber/cli/language_help_formatter.rb +9 -7
  47. data/lib/cucumber/cli/main.rb +16 -2
  48. data/lib/cucumber/cli/options.rb +370 -0
  49. data/lib/cucumber/cli/profile_loader.rb +65 -0
  50. data/lib/cucumber/core_ext/instance_exec.rb +8 -5
  51. data/lib/cucumber/feature_file.rb +7 -1
  52. data/lib/cucumber/filter.rb +2 -2
  53. data/lib/cucumber/formatter/ansicolor.rb +42 -9
  54. data/lib/cucumber/formatter/console.rb +1 -1
  55. data/lib/cucumber/formatter/html.rb +12 -10
  56. data/lib/cucumber/formatter/junit.rb +63 -26
  57. data/lib/cucumber/formatter/pretty.rb +20 -5
  58. data/lib/cucumber/formatter/progress.rb +1 -1
  59. data/lib/cucumber/formatter/steps.rb +49 -0
  60. data/lib/cucumber/languages.yml +6 -6
  61. data/lib/cucumber/parser/feature.rb +90 -63
  62. data/lib/cucumber/parser/feature.tt +28 -1
  63. data/lib/cucumber/parser/i18n/language.rb +12 -5
  64. data/lib/cucumber/parser/table.rb +25 -25
  65. data/lib/cucumber/rake/task.rb +9 -3
  66. data/lib/cucumber/step_definition.rb +1 -1
  67. data/lib/cucumber/step_match.rb +1 -1
  68. data/lib/cucumber/step_mother.rb +3 -1
  69. data/lib/cucumber/version.rb +2 -2
  70. data/lib/cucumber/webrat/table_locator.rb +66 -0
  71. data/rails_generators/cucumber/cucumber_generator.rb +5 -1
  72. data/rails_generators/cucumber/templates/cucumber +3 -2
  73. data/rails_generators/cucumber/templates/cucumber.rake +18 -6
  74. data/rails_generators/cucumber/templates/cucumber_environment.rb +7 -4
  75. data/rails_generators/cucumber/templates/env.rb +1 -0
  76. data/rails_generators/cucumber/templates/spork_env.rb +1 -0
  77. data/rails_generators/cucumber/templates/webrat_steps.rb +22 -0
  78. data/rails_generators/feature/templates/feature.erb +1 -1
  79. data/rails_generators/feature/templates/steps.erb +2 -8
  80. data/spec/cucumber/ast/table_spec.rb +169 -0
  81. data/spec/cucumber/cli/configuration_spec.rb +144 -101
  82. data/spec/cucumber/cli/main_spec.rb +14 -5
  83. data/spec/cucumber/cli/options_spec.rb +311 -0
  84. data/spec/cucumber/cli/profile_loader_spec.rb +10 -0
  85. data/spec/cucumber/core_ext/proc_spec.rb +16 -2
  86. data/spec/cucumber/formatter/html_spec.rb +18 -0
  87. data/spec/cucumber/formatter/progress_spec.rb +2 -2
  88. data/spec/cucumber/parser/table_parser_spec.rb +1 -1
  89. data/spec/spec.opts +3 -1
  90. metadata +18 -4
  91. data/lib/cucumber/webrat/mechanize_world.rb +0 -82
@@ -6,6 +6,7 @@ module Cucumber
6
6
  end
7
7
 
8
8
  def accept(visitor)
9
+ return if $cucumber_interrupted
9
10
  visitor.visit_examples_name(@keyword, @name)
10
11
  visitor.visit_outline_table(@outline_table)
11
12
  end
@@ -16,6 +16,7 @@ module Cucumber
16
16
  end
17
17
 
18
18
  def accept(visitor)
19
+ return if $cucumber_interrupted
19
20
  visitor.visit_comment(@comment) unless @comment.empty?
20
21
  visitor.visit_tags(@tags)
21
22
  visitor.visit_feature_name(@name)
@@ -43,6 +44,15 @@ module Cucumber
43
44
  "#{@file}:#{line}"
44
45
  end
45
46
 
47
+ def short_name
48
+ first_line = name.split(/\n/)[0]
49
+ if first_line =~ /#{language.keywords('feature', true)}:(.*)/
50
+ $1.strip
51
+ else
52
+ first_line
53
+ end
54
+ end
55
+
46
56
  def to_sexp
47
57
  sexp = [:feature, @file, @name]
48
58
  comment = @comment.to_sexp
@@ -9,6 +9,10 @@ module Cucumber
9
9
  @features = []
10
10
  end
11
11
 
12
+ def [](index)
13
+ @features[index]
14
+ end
15
+
12
16
  def each(&proc)
13
17
  @features.each(&proc)
14
18
  end
@@ -19,8 +23,9 @@ module Cucumber
19
23
  end
20
24
 
21
25
  def accept(visitor)
26
+ return if $cucumber_interrupted
22
27
  start = Time.now
23
- @features.each do |feature|
28
+ self.each do |feature|
24
29
  visitor.visit_feature(feature)
25
30
  end
26
31
  @duration = Time.now - start
@@ -9,6 +9,7 @@ module Cucumber
9
9
  end
10
10
 
11
11
  def accept(visitor)
12
+ return if $cucumber_interrupted
12
13
  cells_rows.each_with_index do |row, n|
13
14
  if(visitor.options[:expand])
14
15
  row.accept(visitor)
@@ -44,7 +45,8 @@ module Cucumber
44
45
  end
45
46
 
46
47
  class ExampleRow < Cells
47
-
48
+ attr_reader :scenario_outline # https://rspec.lighthouseapp.com/projects/16211/tickets/342
49
+
48
50
  def create_step_invocations!(scenario_outline)
49
51
  @scenario_outline = scenario_outline
50
52
  @step_invocations = scenario_outline.step_invocations(self)
@@ -57,6 +59,7 @@ module Cucumber
57
59
  end
58
60
 
59
61
  def accept(visitor)
62
+ return if $cucumber_interrupted
60
63
  visitor.options[:expand] ? accept_expand(visitor) : accept_plain(visitor)
61
64
  end
62
65
 
@@ -17,7 +17,6 @@ module Cucumber
17
17
  # Note how the indentation from the source is stripped away.
18
18
  #
19
19
  class PyString
20
-
21
20
  def self.default_arg_name
22
21
  "string"
23
22
  end
@@ -32,6 +31,7 @@ module Cucumber
32
31
  end
33
32
 
34
33
  def accept(visitor)
34
+ return if $cucumber_interrupted
35
35
  visitor.visit_py_string(to_s)
36
36
  end
37
37
 
@@ -21,6 +21,7 @@ module Cucumber
21
21
  end
22
22
 
23
23
  def accept(visitor)
24
+ return if $cucumber_interrupted
24
25
  visitor.visit_comment(@comment) unless @comment.empty?
25
26
  visitor.visit_tags(@tags)
26
27
  visitor.visit_scenario_name(@keyword, @name, file_colon_line(@line), source_indent(first_line_length))
@@ -5,6 +5,7 @@ module Cucumber
5
5
 
6
6
  module ExamplesArray
7
7
  def accept(visitor)
8
+ return if $cucumber_interrupted
8
9
  each do |examples|
9
10
  visitor.visit_examples(examples)
10
11
  end
@@ -37,6 +38,7 @@ module Cucumber
37
38
  end
38
39
 
39
40
  def accept(visitor)
41
+ return if $cucumber_interrupted
40
42
  visitor.visit_comment(@comment) unless @comment.empty?
41
43
  visitor.visit_tags(@tags)
42
44
  visitor.visit_scenario_name(@keyword, @name, file_colon_line(@line), source_indent(first_line_length))
@@ -33,6 +33,7 @@ module Cucumber
33
33
  end
34
34
 
35
35
  def accept(visitor)
36
+ return if $cucumber_interrupted
36
37
  # The only time a Step is visited is when it is in a ScenarioOutline.
37
38
  # Otherwise it's always StepInvocation that gets visited instead.
38
39
  visit_step_result(visitor, first_match(visitor), @multiline_arg, :skipped, nil, nil)
@@ -85,8 +86,11 @@ module Cucumber
85
86
  private
86
87
 
87
88
  def matched_cells(cells)
89
+ col_index = 0
88
90
  cells.select do |cell|
89
- delimited = delimited(cell.header_cell.value)
91
+ header_cell = cell.table.header_cell(col_index)
92
+ col_index += 1
93
+ delimited = delimited(header_cell.value)
90
94
  @name.index(delimited) || (@multiline_arg && @multiline_arg.has_text?(delimited))
91
95
  end
92
96
  end
@@ -10,6 +10,7 @@ module Cucumber
10
10
  end
11
11
 
12
12
  def accept(visitor, &proc)
13
+ return if $cucumber_interrupted
13
14
  @steps.each do |step|
14
15
  visitor.visit_step(step) if proc.nil? || proc.call(step)
15
16
  end
@@ -19,6 +19,7 @@ module Cucumber
19
19
  end
20
20
 
21
21
  def accept(visitor)
22
+ return if $cucumber_interrupted
22
23
  invoke(visitor.step_mother, visitor.options)
23
24
  visit_step_result(visitor)
24
25
  end
@@ -7,7 +7,9 @@ module Cucumber
7
7
  #
8
8
  # This gets parsed into a Table holding the values <tt>[['a', 'b'], ['c', 'd']]</tt>
9
9
  #
10
- class Table
10
+ class Table
11
+ include Enumerable
12
+
11
13
  NULL_CONVERSIONS = Hash.new(lambda{ |cell_value| cell_value }).freeze
12
14
 
13
15
  attr_accessor :file
@@ -16,18 +18,19 @@ module Cucumber
16
18
  "table"
17
19
  end
18
20
 
19
- def initialize(raw, conversions = NULL_CONVERSIONS.dup)
20
- # Verify that it's square
21
- raw.transpose
22
- @raw = raw
21
+ def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup)
23
22
  @cells_class = Cells
24
23
  @cell_class = Cell
25
- @conversion_procs = conversions
24
+
25
+ # Verify that it's square
26
+ transposed = raw.transpose
27
+ create_cell_matrix(raw)
28
+ @conversion_procs = conversion_procs
26
29
  end
27
30
 
28
31
  # Creates a copy of this table, inheriting the column mappings.
29
32
  def dup
30
- self.class.new(@raw.dup, @conversion_procs.dup)
33
+ self.class.new(raw.dup, @conversion_procs.dup)
31
34
  end
32
35
 
33
36
  # Returns a new, transposed table. Example:
@@ -42,7 +45,7 @@ module Cucumber
42
45
  # | 4 | 2 |
43
46
  #
44
47
  def transpose
45
- self.class.new(@raw.transpose, @conversion_procs.dup)
48
+ self.class.new(raw.transpose, @conversion_procs.dup)
46
49
  end
47
50
 
48
51
  # Converts this table into an Array of Hash where the keys of each
@@ -78,8 +81,9 @@ module Cucumber
78
81
  # The table must be exactly two columns wide
79
82
  #
80
83
  def rows_hash
84
+ return @rows_hash if @rows_hash
81
85
  verify_table_width(2)
82
- @rows_hash = self.transpose.hashes[0]
86
+ @rows_hash = self.transpose.hashes[0].freeze
83
87
  end
84
88
 
85
89
  # Gets the raw data of this table. For example, a Table built from
@@ -88,17 +92,21 @@ module Cucumber
88
92
  # | a | b |
89
93
  # | c | d |
90
94
  #
91
- # Get converted into the following:
95
+ # gets converted into the following:
92
96
  #
93
97
  # [['a', 'b], ['c', 'd']]
94
98
  #
95
99
  def raw
96
- @raw
100
+ cell_matrix.map do |row|
101
+ row.map do |cell|
102
+ cell.value
103
+ end
104
+ end
97
105
  end
98
106
 
99
107
  # Same as #raw, but skips the first (header) row
100
108
  def rows
101
- @raw[1..-1]
109
+ raw[1..-1]
102
110
  end
103
111
 
104
112
  def each_cells_row(&proc)
@@ -106,6 +114,7 @@ module Cucumber
106
114
  end
107
115
 
108
116
  def accept(visitor)
117
+ return if $cucumber_interrupted
109
118
  cells_rows.each do |row|
110
119
  visitor.visit_table_row(row)
111
120
  end
@@ -117,19 +126,38 @@ module Cucumber
117
126
  [:table, *cells_rows.map{|row| row.to_sexp}]
118
127
  end
119
128
 
120
- # Returns a new Table where the headers are redefined. This makes it
121
- # possible to use prettier header names in the features. Example:
129
+ # Redefines the table headers. This makes it possible to use
130
+ # prettier and more flexible header names in the features. The
131
+ # keys of +mappings+ are Strings or regular expressions
132
+ # (anything that responds to #=== will work) that may match
133
+ # column headings in the table. The values of +mappings+ are
134
+ # desired names for the columns.
135
+ #
136
+ # Example:
122
137
  #
123
138
  # | Phone Number | Address |
124
139
  # | 123456 | xyz |
125
140
  # | 345678 | abc |
126
141
  #
127
- # A StepDefinition receiving this table can then map the columns:
142
+ # A StepDefinition receiving this table can then map the columns
143
+ # with both Regexp and String:
128
144
  #
129
- # mapped_table = table.map_columns('Phone Number' => :phone, 'Address' => :address)
130
- # hashes = mapped_table.hashes
145
+ # table.map_headers!(/phone( number)?/i => :phone, 'Address' => :address)
146
+ # table.hashes
131
147
  # # => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
132
148
  #
149
+ def map_headers!(mappings)
150
+ header_cells = cell_matrix[0]
151
+ mappings.each_pair do |pre, post|
152
+ header_cell = header_cells.detect{|cell| pre === cell.value}
153
+ header_cell.value = post
154
+ if @conversion_procs.has_key?(pre)
155
+ @conversion_procs[post] = @conversion_procs.delete(pre)
156
+ end
157
+ end
158
+ end
159
+
160
+ # Returns a new Table where the headers are redefined. See #map_headers!
133
161
  def map_headers(mappings)
134
162
  table = self.dup
135
163
  table.map_headers!(mappings)
@@ -153,11 +181,111 @@ module Cucumber
153
181
  @conversion_procs[column_name] = conversion_proc
154
182
  end
155
183
 
184
+ # Compares +other_table+ to self. If +other_table+ contains columns
185
+ # and/or rows that are not in self, new columns/rows are added at the
186
+ # relevant positions, marking the cells in those rows/columns as
187
+ # <tt>surplus</tt>. Likewise, if +other_table+ lacks columns and/or
188
+ # rows that are present in self, these are marked as <tt>missing</tt>.
189
+ #
190
+ # <tt>surplus</tt> and <tt>missing</tt> cells are recognised by formatters
191
+ # and displayed so that it's easy to read the differences.
192
+ #
193
+ # Cells that are different, but <em>look</em> identical (for example the
194
+ # boolean true and the string "true") are converted to their Object#inspect
195
+ # representation and preceded with (i) - to make it easier to identify
196
+ # where the difference actually is.
197
+ #
198
+ # Since all tables that are passed to StepDefinitions always have String
199
+ # objects in their cells, you may want to use #map_column! before calling
200
+ # #diff!. You can use #map_column! on either of the tables.
201
+ #
202
+ # An exception is raised if there are missing rows or columns, or
203
+ # surplus rows. An error is <em>not</em> raised for surplus columns.
204
+ # Whether to raise or not raise can be changed by setting values in
205
+ # +options+ to true or false:
206
+ #
207
+ # * <tt>missing_row</tt>: Raise on missing rows (defaults to true)
208
+ # * <tt>surplus_row</tt>: Raise on surplus rows (defaults to true)
209
+ # * <tt>missing_col</tt>: Raise on missing columns (defaults to true)
210
+ # * <tt>surplus_col</tt>: Raise on surplus columns (defaults to false)
211
+ #
212
+ # The +other_table+ argument can be another Table, an Array of Array or
213
+ # an Array of Hash (similar to the structure returned by #hashes).
214
+ #
215
+ # Calling this method is particularly useful in <tt>Then</tt> steps that take
216
+ # a Table argument, if you want to compare that table to some actual values.
217
+ #
218
+ def diff!(other_table, options={})
219
+ options = {:missing_row => true, :surplus_row => true, :missing_col => true, :surplus_col => false}.merge(options)
220
+
221
+ other_table = ensure_table(other_table)
222
+ other_table.convert_columns!
223
+ ensure_green!
224
+
225
+ original_width = cell_matrix[0].length
226
+ other_table_cell_matrix = pad!(other_table.cell_matrix)
227
+ padded_width = cell_matrix[0].length
228
+
229
+ missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined}
230
+ surplus_col = padded_width > original_width
231
+
232
+ require_diff_lcs
233
+ cell_matrix.extend(Diff::LCS)
234
+ convert_columns!
235
+ changes = cell_matrix.diff(other_table_cell_matrix).flatten
236
+
237
+ inserted = 0
238
+ missing = 0
239
+
240
+ row_indices = Array.new(other_table_cell_matrix.length) {|n| n}
241
+
242
+ last_change = nil
243
+ missing_row_pos = nil
244
+ insert_row_pos = nil
245
+
246
+ changes.each do |change|
247
+ if(change.action == '-')
248
+ missing_row_pos = change.position + inserted
249
+ cell_matrix[missing_row_pos].each{|cell| cell.status = :undefined}
250
+ row_indices.insert(missing_row_pos, nil)
251
+ missing += 1
252
+ else # '+'
253
+ insert_row_pos = change.position + missing
254
+ inserted_row = change.element
255
+ inserted_row.each{|cell| cell.status = :comment}
256
+ cell_matrix.insert(insert_row_pos, inserted_row)
257
+ row_indices[insert_row_pos] = nil
258
+ inspect_rows(cell_matrix[missing_row_pos], inserted_row) if last_change && last_change.action == '-'
259
+ inserted += 1
260
+ end
261
+ last_change = change
262
+ end
263
+
264
+ other_table_cell_matrix.each_with_index do |other_row, i|
265
+ row_index = row_indices.index(i)
266
+ row = cell_matrix[row_index] if row_index
267
+ if row
268
+ (original_width..padded_width).each do |col_index|
269
+ surplus_cell = other_row[col_index]
270
+ row[col_index].value = surplus_cell.value if row[col_index]
271
+ end
272
+ end
273
+ end
274
+
275
+ clear_cache!
276
+ should_raise =
277
+ missing_row_pos && options[:missing_row] ||
278
+ insert_row_pos && options[:surplus_row] ||
279
+ missing_col && options[:missing_col] ||
280
+ surplus_col && options[:surplus_col]
281
+ raise 'Tables were not identical' if should_raise
282
+ end
283
+
156
284
  def to_hash(cells) #:nodoc:
157
285
  hash = Hash.new do |hash, key|
158
286
  hash[key.to_s] if key.is_a?(Symbol)
159
287
  end
160
- @raw[0].each_with_index do |column_name, column_index|
288
+ raw[0].each_with_index do |column_name, column_index|
161
289
  value = @conversion_procs[column_name].call(cells.value(column_index))
162
290
  hash[column_name] = value
163
291
  end
@@ -169,11 +297,11 @@ module Cucumber
169
297
  end
170
298
 
171
299
  def verify_column(column_name)
172
- raise %{The column named "#{column_name}" does not exist} unless @raw[0].include?(column_name)
300
+ raise %{The column named "#{column_name}" does not exist} unless raw[0].include?(column_name)
173
301
  end
174
302
 
175
303
  def verify_table_width(width)
176
- raise %{The table must have exactly #{width} columns} unless @raw[0].size == width
304
+ raise %{The table must have exactly #{width} columns} unless raw[0].size == width
177
305
  end
178
306
 
179
307
  def arguments_replaced(arguments) #:nodoc:
@@ -198,33 +326,88 @@ module Cucumber
198
326
  def cells_rows
199
327
  @rows ||= cell_matrix.map do |cell_row|
200
328
  @cells_class.new(self, cell_row)
201
- end.freeze
329
+ end
202
330
  end
203
331
 
204
332
  def headers
205
- @raw.first
333
+ raw.first
206
334
  end
207
335
 
208
336
  def header_cell(col)
209
337
  cells_rows[0][col]
210
338
  end
211
339
 
340
+ def cell_matrix
341
+ @cell_matrix
342
+ end
343
+
344
+ def col_width(col)
345
+ columns[col].__send__(:width)
346
+ end
347
+
348
+ def to_s(options = {})
349
+ options = {:color => true, :indent => 2, :prefixes => TO_S_PREFIXES}.merge(options)
350
+ io = StringIO.new
351
+
352
+ c = Term::ANSIColor.coloring?
353
+ Term::ANSIColor.coloring = options[:color]
354
+ f = Formatter::Pretty.new(nil, io, options)
355
+ f.instance_variable_set('@indent', options[:indent])
356
+ f.visit_multiline_arg(self)
357
+ Term::ANSIColor.coloring = c
358
+
359
+ io.rewind
360
+ s = "\n" + io.read + (" " * (options[:indent] - 2))
361
+ s
362
+ end
363
+
364
+ private
365
+
366
+ TO_S_PREFIXES = Hash.new(' ')
367
+ TO_S_PREFIXES[:comment] = ['(+) ']
368
+ TO_S_PREFIXES[:undefined] = ['(-) ']
369
+
212
370
  protected
213
371
 
214
- def map_headers!(mappings)
215
- headers = @raw[0]
216
- mappings.each_pair do |pre, post|
217
- headers[headers.index(pre)] = post
218
- if @conversion_procs.has_key?(pre)
219
- @conversion_procs[post] = @conversion_procs.delete(pre)
372
+ def inspect_rows(missing_row, inserted_row)
373
+ missing_row.each_with_index do |missing_cell, col|
374
+ inserted_cell = inserted_row[col]
375
+ if(missing_cell.value != inserted_cell.value && (missing_cell.value.to_s == inserted_cell.value.to_s))
376
+ missing_cell.inspect!
377
+ inserted_cell.inspect!
220
378
  end
221
379
  end
222
380
  end
223
381
 
224
- private
382
+ def create_cell_matrix(raw)
383
+ @cell_matrix = raw.map do |raw_row|
384
+ line = raw_row.line rescue -1
385
+ raw_row.map do |raw_cell|
386
+ new_cell(raw_cell, line)
387
+ end
388
+ end
389
+ end
225
390
 
226
- def col_width(col)
227
- columns[col].__send__(:width)
391
+ def convert_columns!
392
+ cell_matrix.transpose.each do |col|
393
+ conversion_proc = @conversion_procs[col[0].value]
394
+ col[1..-1].each do |cell|
395
+ cell.value = conversion_proc.call(cell.value)
396
+ end
397
+ end
398
+ end
399
+
400
+ def require_diff_lcs
401
+ begin
402
+ require 'diff/lcs'
403
+ rescue LoadError => e
404
+ e.message << "\n Please gem install diff-lcs\n"
405
+ raise e
406
+ end
407
+ end
408
+
409
+ def clear_cache!
410
+ @hashes = @rows_hash = @rows = @columns = nil
228
411
  end
229
412
 
230
413
  def columns
@@ -233,17 +416,78 @@ module Cucumber
233
416
  end.freeze
234
417
  end
235
418
 
236
- def cell_matrix
237
- row = -1
238
- @cell_matrix ||= @raw.map do |raw_row|
239
- line = raw_row.line rescue -1
240
- row += 1
241
- col = -1
242
- raw_row.map do |raw_cell|
243
- col += 1
244
- @cell_class.new(raw_cell, self, row, col, line)
419
+ def new_cell(raw_cell, line)
420
+ @cell_class.new(raw_cell, self, line)
421
+ end
422
+
423
+ # Pads our own cell_matrix and returns a cell matrix of same
424
+ # column width that can be used for diffing
425
+ def pad!(other_cell_matrix)
426
+ clear_cache!
427
+ cols = cell_matrix.transpose
428
+ unmapped_cols = other_cell_matrix.transpose
429
+
430
+ mapped_cols = []
431
+
432
+ cols.each_with_index do |col, col_index|
433
+ header = col[0]
434
+ candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col|
435
+ other_col[0] == header
245
436
  end
246
- end.freeze
437
+ raise "More than one column has the header #{header}" if candidate_cols.size > 2
438
+
439
+ other_padded_col = if candidate_cols.size == 1
440
+ # Found a matching column
441
+ candidate_cols[0]
442
+ else
443
+ mark_as_missing(cols[col_index])
444
+ (0...other_cell_matrix.length).map do |row|
445
+ val = row == 0 ? header.value : nil
446
+ SurplusCell.new(val, self, -1)
447
+ end
448
+ end
449
+ mapped_cols.insert(col_index, other_padded_col)
450
+ end
451
+
452
+ unmapped_cols.each_with_index do |col, col_index|
453
+ empty_col = (0...cell_matrix.length).map do |row|
454
+ SurplusCell.new(nil, self, -1)
455
+ end
456
+ cols << empty_col
457
+ end
458
+
459
+ @cell_matrix = cols.transpose
460
+ (mapped_cols + unmapped_cols).transpose
461
+ end
462
+
463
+ def ensure_table(table_or_array)
464
+ return table_or_array if Table === table_or_array
465
+ table_or_array = hashes_to_array(table_or_array) if Hash === table_or_array[0]
466
+ table_or_array = enumerable_to_array(table_or_array) unless Array == table_or_array[0]
467
+ Table.new(table_or_array)
468
+ end
469
+
470
+ def hashes_to_array(hashes)
471
+ header = hashes[0].keys
472
+ [header] + hashes.map{|hash| header.map{|key| hash[key]}}
473
+ end
474
+
475
+ def enumerable_to_array(rows)
476
+ rows.map{|row| row.map{|cell| cell}}
477
+ end
478
+
479
+ def ensure_green!
480
+ each_cell{|cell| cell.status = :passed}
481
+ end
482
+
483
+ def each_cell(&proc)
484
+ cell_matrix.each{|row| row.each(&proc)}
485
+ end
486
+
487
+ def mark_as_missing(col)
488
+ col.each do |cell|
489
+ cell.status = :undefined
490
+ end
247
491
  end
248
492
 
249
493
  # Represents a row of cells or columns of cells
@@ -256,6 +500,7 @@ module Cucumber
256
500
  end
257
501
 
258
502
  def accept(visitor)
503
+ return if $cucumber_interrupted
259
504
  each do |cell|
260
505
  visitor.visit_table_cell(cell)
261
506
  end
@@ -303,30 +548,39 @@ module Cucumber
303
548
  end
304
549
 
305
550
  class Cell
306
- attr_reader :value, :line
307
- attr_writer :status
551
+ attr_reader :line, :table
552
+ attr_accessor :status, :value
308
553
 
309
- def initialize(value, table, row, col, line)
310
- @value, @table, @row, @col, @line = value, table, row, col, line
554
+ def initialize(value, table, line)
555
+ @value, @table, @line = value, table, line
311
556
  end
312
557
 
313
558
  def accept(visitor)
314
- visitor.visit_table_cell_value(@value, col_width, @status)
559
+ return if $cucumber_interrupted
560
+ visitor.visit_table_cell_value(value, status)
561
+ end
562
+
563
+ def inspect!
564
+ @value = "(i) #{value.inspect}"
315
565
  end
316
566
 
317
- def header_cell
318
- @table.header_cell(@col)
567
+ def ==(o)
568
+ SurplusCell === o || value == o.value
319
569
  end
320
570
 
321
571
  # For testing only
322
572
  def to_sexp #:nodoc:
323
573
  [:cell, @value]
324
574
  end
575
+ end
576
+
577
+ class SurplusCell < Cell
578
+ def status
579
+ :comment
580
+ end
325
581
 
326
- private
327
-
328
- def col_width
329
- @col_width ||= @table.__send__(:col_width, @col).freeze
582
+ def ==(o)
583
+ true
330
584
  end
331
585
  end
332
586
  end