square-cucumber 0.3.12.2 → 0.3.93.1

Sign up to get free protection for your applications and to get access to all the features.
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