cucumber 0.3.11 → 0.3.90

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/History.txt +101 -0
  2. data/Manifest.txt +14 -5
  3. data/config/hoe.rb +1 -2
  4. data/cucumber.yml +1 -0
  5. data/examples/i18n/ar/Rakefile +1 -1
  6. data/examples/i18n/ar/features/addition.feature +1 -0
  7. data/examples/i18n/bg/Rakefile +1 -1
  8. data/examples/i18n/bg/features/addition.feature +1 -0
  9. data/examples/i18n/bg/features/consecutive_calculations.feature +1 -0
  10. data/examples/i18n/bg/features/division.feature +1 -0
  11. data/examples/i18n/cat/Rakefile +1 -1
  12. data/examples/i18n/cat/features/suma.feature +1 -0
  13. data/examples/i18n/da/Rakefile +1 -1
  14. data/examples/i18n/da/features/summering.feature +1 -0
  15. data/examples/i18n/de/Rakefile +1 -1
  16. data/examples/i18n/de/features/addition.feature +1 -0
  17. data/examples/i18n/de/features/division.feature +1 -0
  18. data/examples/i18n/en-lol/features/stuffing.feature +1 -1
  19. data/examples/i18n/en/Rakefile +1 -1
  20. data/examples/i18n/en/features/addition.feature +1 -0
  21. data/examples/i18n/en/features/division.feature +1 -0
  22. data/examples/i18n/es/Rakefile +1 -1
  23. data/examples/i18n/es/features/adicion.feature +1 -0
  24. data/examples/i18n/et/Rakefile +1 -1
  25. data/examples/i18n/et/features/jagamine.feature +1 -0
  26. data/examples/i18n/et/features/liitmine.feature +1 -0
  27. data/examples/i18n/fi/Rakefile +1 -1
  28. data/examples/i18n/fi/features/jakolasku.feature +1 -0
  29. data/examples/i18n/fr/Rakefile +1 -1
  30. data/examples/i18n/fr/features/addition.feature +2 -1
  31. data/examples/i18n/he/Rakefile +1 -1
  32. data/examples/i18n/he/features/addition.feature +1 -0
  33. data/examples/i18n/he/features/division.feature +1 -0
  34. data/examples/i18n/hu/Rakefile +1 -1
  35. data/examples/i18n/hu/features/addition.feature +1 -0
  36. data/examples/i18n/hu/features/division.feature +1 -0
  37. data/examples/i18n/id/Rakefile +1 -1
  38. data/examples/i18n/id/features/addition.feature +1 -0
  39. data/examples/i18n/id/features/division.feature +1 -0
  40. data/examples/i18n/it/Rakefile +1 -1
  41. data/examples/i18n/it/features/somma.feature +1 -0
  42. data/examples/i18n/ja/Rakefile +1 -1
  43. data/examples/i18n/ja/features/addition.feature +1 -0
  44. data/examples/i18n/ja/features/division.feature +1 -0
  45. data/examples/i18n/ko/Rakefile +1 -1
  46. data/examples/i18n/ko/features/addition.feature +6 -5
  47. data/examples/i18n/ko/features/division.feature +1 -0
  48. data/examples/i18n/ko/features/step_definitons/calculator_steps.rb +1 -1
  49. data/examples/i18n/lt/Rakefile +1 -1
  50. data/examples/i18n/lt/features/addition.feature +1 -0
  51. data/examples/i18n/lt/features/division.feature +1 -0
  52. data/examples/i18n/lv/Rakefile +1 -1
  53. data/examples/i18n/lv/features/addition.feature +1 -0
  54. data/examples/i18n/lv/features/division.feature +1 -0
  55. data/examples/i18n/no/Rakefile +1 -1
  56. data/examples/i18n/no/features/step_definitons/kalkulator_steps.rb +1 -1
  57. data/examples/i18n/no/features/summering.feature +1 -0
  58. data/examples/i18n/pl/Rakefile +1 -1
  59. data/examples/i18n/pl/features/addition.feature +1 -0
  60. data/examples/i18n/pl/features/division.feature +1 -0
  61. data/examples/i18n/pt/Rakefile +1 -1
  62. data/examples/i18n/pt/features/adicao.feature +1 -0
  63. data/examples/i18n/ro/Rakefile +1 -1
  64. data/examples/i18n/ro/features/suma.feature +1 -0
  65. data/examples/i18n/ru/Rakefile +1 -1
  66. data/examples/i18n/ru/features/addition.feature +1 -0
  67. data/examples/i18n/ru/features/consecutive_calculations.feature +1 -0
  68. data/examples/i18n/ru/features/division.feature +1 -0
  69. data/examples/i18n/se/Rakefile +1 -1
  70. data/examples/i18n/se/features/summering.feature +1 -0
  71. data/examples/i18n/sk/Rakefile +1 -1
  72. data/examples/i18n/sk/features/addition.feature +1 -0
  73. data/examples/i18n/sk/features/division.feature +1 -0
  74. data/examples/i18n/zh-CN/features/addition.feature +1 -0
  75. data/examples/i18n/zh-TW/features/addition.feature +1 -0
  76. data/examples/i18n/zh-TW/features/division.feature +1 -0
  77. data/examples/self_test/features/support/env.rb +2 -1
  78. data/examples/sinatra/features/step_definitions/add_steps.rb +1 -1
  79. data/examples/sinatra/features/support/env.rb +13 -5
  80. data/examples/steps_library/features/step_definitions/steps_lib1.rb +8 -0
  81. data/examples/steps_library/features/step_definitions/steps_lib2.rb +8 -0
  82. data/examples/tickets/features/step_definitons/tickets_steps.rb +15 -0
  83. data/examples/tickets/features/table_diffing.feature +13 -0
  84. data/features/after_block_exceptions.feature +4 -1
  85. data/features/after_step_block_exceptions.feature +4 -1
  86. data/features/background.feature +6 -0
  87. data/features/bug_371.feature +32 -0
  88. data/features/cucumber_cli.feature +11 -1
  89. data/features/cucumber_cli_diff_disabled.feature +9 -2
  90. data/features/drb_server_integration.feature +28 -5
  91. data/features/expand.feature +2 -1
  92. data/features/html_formatter/a.html +129 -1614
  93. data/features/junit_formatter.feature +4 -4
  94. data/features/language_from_header.feature +30 -0
  95. data/features/rake_task.feature +28 -0
  96. data/features/step_definitions/cucumber_steps.rb +2 -2
  97. data/features/steps_formatter.feature +25 -0
  98. data/features/support/env.rb +13 -5
  99. data/features/table_diffing.feature +45 -0
  100. data/features/unicode_table.feature +35 -0
  101. data/features/work_in_progress.feature +3 -0
  102. data/gem_tasks/sass.rake +4 -0
  103. data/lib/cucumber.rb +0 -57
  104. data/lib/cucumber/ast/comment.rb +1 -1
  105. data/lib/cucumber/ast/feature.rb +2 -2
  106. data/lib/cucumber/ast/feature_element.rb +4 -0
  107. data/lib/cucumber/ast/outline_table.rb +2 -1
  108. data/lib/cucumber/ast/py_string.rb +0 -1
  109. data/lib/cucumber/ast/scenario.rb +5 -3
  110. data/lib/cucumber/ast/scenario_outline.rb +6 -1
  111. data/lib/cucumber/ast/step.rb +8 -1
  112. data/lib/cucumber/ast/step_invocation.rb +6 -1
  113. data/lib/cucumber/ast/table.rb +287 -49
  114. data/lib/cucumber/ast/visitor.rb +2 -1
  115. data/lib/cucumber/cli/configuration.rb +45 -28
  116. data/lib/cucumber/cli/drb_client.rb +8 -9
  117. data/lib/cucumber/cli/language_help_formatter.rb +9 -7
  118. data/lib/cucumber/cli/main.rb +8 -9
  119. data/lib/cucumber/feature_file.rb +53 -0
  120. data/lib/cucumber/filter.rb +50 -0
  121. data/lib/cucumber/formatter/console.rb +12 -0
  122. data/lib/cucumber/formatter/cucumber.css +106 -48
  123. data/lib/cucumber/formatter/cucumber.sass +121 -31
  124. data/lib/cucumber/formatter/html.rb +6 -5
  125. data/lib/cucumber/formatter/junit.rb +3 -6
  126. data/lib/cucumber/formatter/pretty.rb +22 -9
  127. data/lib/cucumber/formatter/profile.rb +1 -1
  128. data/lib/cucumber/formatter/progress.rb +1 -1
  129. data/lib/cucumber/formatter/steps.rb +49 -0
  130. data/lib/cucumber/languages.yml +3 -3
  131. data/lib/cucumber/parser.rb +1 -33
  132. data/lib/cucumber/parser/feature.rb +39 -16
  133. data/lib/cucumber/parser/feature.tt +1 -3
  134. data/lib/cucumber/parser/i18n.tt +30 -23
  135. data/lib/cucumber/parser/i18n/language.rb +83 -0
  136. data/lib/cucumber/parser/treetop_ext.rb +12 -83
  137. data/lib/cucumber/platform.rb +6 -0
  138. data/lib/cucumber/rake/task.rb +6 -0
  139. data/lib/cucumber/step_definition.rb +6 -0
  140. data/lib/cucumber/step_match.rb +1 -1
  141. data/lib/cucumber/step_mother.rb +3 -3
  142. data/lib/cucumber/version.rb +1 -1
  143. data/lib/cucumber/webrat/table_locator.rb +66 -0
  144. data/rails_generators/cucumber/templates/cucumber.rake +4 -0
  145. data/rails_generators/cucumber/templates/env.rb +1 -0
  146. data/rails_generators/cucumber/templates/spork_env.rb +1 -1
  147. data/rails_generators/cucumber/templates/webrat_steps.rb +17 -0
  148. data/rails_generators/feature/templates/feature.erb +1 -1
  149. data/rails_generators/feature/templates/steps.erb +2 -8
  150. data/spec/cucumber/ast/step_collection_spec.rb +5 -4
  151. data/spec/cucumber/ast/table_spec.rb +145 -0
  152. data/spec/cucumber/cli/configuration_spec.rb +55 -9
  153. data/spec/cucumber/cli/drb_client_spec.rb +5 -4
  154. data/spec/cucumber/cli/main_spec.rb +9 -12
  155. data/spec/cucumber/formatter/progress_spec.rb +2 -2
  156. data/spec/cucumber/parser/feature_parser_spec.rb +11 -9
  157. data/spec/cucumber/parser/table_parser_spec.rb +1 -1
  158. data/spec/spec_helper.rb +2 -1
  159. metadata +21 -20
  160. data/spec/cucumber/formatter/html/cucumber.css +0 -37
  161. data/spec/cucumber/formatter/html/cucumber.js +0 -13
  162. data/spec/cucumber/formatter/html/index.html +0 -45
  163. data/spec/cucumber/formatter/html/jquery-1.3.min.js +0 -19
  164. data/spec/cucumber/formatter/html/jquery.uitableedit.js +0 -100
@@ -88,7 +88,8 @@ module Cucumber
88
88
  end
89
89
 
90
90
  def actual_keyword
91
- if [Cucumber.keyword_hash['and'], Cucumber.keyword_hash['but']].index(@step.keyword) && previous
91
+ repeat_keywords = [language.but_keywords, language.and_keywords].flatten
92
+ if repeat_keywords.index(@step.keyword) && previous
92
93
  previous.actual_keyword
93
94
  else
94
95
  keyword
@@ -123,6 +124,10 @@ module Cucumber
123
124
  @step.backtrace_line
124
125
  end
125
126
 
127
+ def language
128
+ @step.language
129
+ end
130
+
126
131
  def to_sexp
127
132
  [:step_invocation, @step.line, @step.keyword, @name, (@multiline_arg.nil? ? nil : @multiline_arg.to_sexp)].compact
128
133
  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
@@ -62,7 +65,7 @@ module Cucumber
62
65
  def hashes
63
66
  @hashes ||= cells_rows[1..-1].map do |row|
64
67
  row.to_hash
65
- end
68
+ end.freeze
66
69
  end
67
70
 
68
71
  # Converts this table into a Hash where the first column is
@@ -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)
@@ -117,7 +125,7 @@ module Cucumber
117
125
  [:table, *cells_rows.map{|row| row.to_sexp}]
118
126
  end
119
127
 
120
- # Returns a new Table where the headers are redefined. This makes it
128
+ # Redefines the table headers. This makes it
121
129
  # possible to use prettier header names in the features. Example:
122
130
  #
123
131
  # | Phone Number | Address |
@@ -130,6 +138,18 @@ module Cucumber
130
138
  # hashes = mapped_table.hashes
131
139
  # # => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
132
140
  #
141
+ def map_headers!(mappings)
142
+ header_cells = cell_matrix[0]
143
+ mappings.each_pair do |pre, post|
144
+ header_cell = header_cells.detect{|cell| cell.value == pre}
145
+ header_cell.value = post
146
+ if @conversion_procs.has_key?(pre)
147
+ @conversion_procs[post] = @conversion_procs.delete(pre)
148
+ end
149
+ end
150
+ end
151
+
152
+ # Returns a new Table where the headers are redefined. See #map_headers!
133
153
  def map_headers(mappings)
134
154
  table = self.dup
135
155
  table.map_headers!(mappings)
@@ -153,11 +173,110 @@ module Cucumber
153
173
  @conversion_procs[column_name] = conversion_proc
154
174
  end
155
175
 
176
+ # Compares +other_table+ to self. If +other_table+ contains columns
177
+ # and/or rows that are not in self, new columns/rows are added at the
178
+ # relevant positions, marking the cells in those rows/columns as
179
+ # <tt>surplus</tt>. Likewise, if +other_table+ lacks columns and/or
180
+ # rows that are present in self, these are marked as <tt>missing</tt>.
181
+ #
182
+ # <tt>surplus</tt> and <tt>missing</tt> cells are recognised by formatters
183
+ # and displayed so that it's easy to read the differences.
184
+ #
185
+ # Cells that are different, but <em>look</em> identical (for example the
186
+ # boolean true and the string "true") are converted to their Object#inspect
187
+ # representation and preceded with (i) - to make it easier to identify
188
+ # where the difference actually is.
189
+ #
190
+ # Since all tables that are passed to StepDefinitions always have String
191
+ # objects in their cells, you may want to use #map_column! before calling
192
+ # #diff!
193
+ #
194
+ # An exception is raised if there are missing rows or columns, or
195
+ # surplus rows. An error is <em>not</em> raised for surplus columns.
196
+ # Whether to raise or not raise can be changed by setting values in
197
+ # +options+ to true or false:
198
+ #
199
+ # * <tt>missing_row</tt>: Raise on missing rows (defaults to true)
200
+ # * <tt>surplus_row</tt>: Raise on surplus rows (defaults to true)
201
+ # * <tt>missing_col</tt>: Raise on missing columns (defaults to true)
202
+ # * <tt>surplus_col</tt>: Raise on surplus columns (defaults to false)
203
+ #
204
+ # The +other_table+ argument can be another Table, an Array of Array or
205
+ # an Array of Hash (similar to the structure returned by #hashes).
206
+ #
207
+ # Calling this method is particularly useful in <tt>Then</tt> steps that take
208
+ # a Table argument, if you want to compare that table to some actual values.
209
+ #
210
+ def diff!(other_table, options={})
211
+ options = {:missing_row => true, :surplus_row => true, :missing_col => true, :surplus_col => false}.merge(options)
212
+
213
+ other_table = ensure_table(other_table)
214
+ ensure_green!
215
+
216
+ original_width = cell_matrix[0].length
217
+ other_table_cell_matrix = pad!(other_table.cell_matrix)
218
+ padded_width = cell_matrix[0].length
219
+
220
+ missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined}
221
+ surplus_col = padded_width > original_width
222
+
223
+ require_diff_lcs
224
+ cell_matrix.extend(Diff::LCS)
225
+ convert_columns!
226
+ changes = cell_matrix.diff(other_table_cell_matrix).flatten
227
+
228
+ inserted = 0
229
+ missing = 0
230
+
231
+ row_indices = Array.new(other_table_cell_matrix.length) {|n| n}
232
+
233
+ last_change = nil
234
+ missing_row_pos = nil
235
+ insert_row_pos = nil
236
+
237
+ changes.each do |change|
238
+ if(change.action == '-')
239
+ missing_row_pos = change.position + inserted
240
+ cell_matrix[missing_row_pos].each{|cell| cell.status = :undefined}
241
+ row_indices.insert(missing_row_pos, nil)
242
+ missing += 1
243
+ else # '+'
244
+ insert_row_pos = change.position + missing
245
+ inserted_row = change.element
246
+ inserted_row.each{|cell| cell.status = :comment}
247
+ cell_matrix.insert(insert_row_pos, inserted_row)
248
+ row_indices[insert_row_pos] = nil
249
+ inspect_rows(cell_matrix[missing_row_pos], inserted_row) if last_change && last_change.action == '-'
250
+ inserted += 1
251
+ end
252
+ last_change = change
253
+ end
254
+
255
+ other_table_cell_matrix.each_with_index do |other_row, i|
256
+ row_index = row_indices.index(i)
257
+ row = cell_matrix[row_index] if row_index
258
+ if row
259
+ (original_width..padded_width).each do |col_index|
260
+ surplus_cell = other_row[col_index]
261
+ row[col_index].value = surplus_cell.value if row[col_index]
262
+ end
263
+ end
264
+ end
265
+
266
+ clear_cache!
267
+ should_raise =
268
+ missing_row_pos && options[:missing_row] ||
269
+ insert_row_pos && options[:surplus_row] ||
270
+ missing_col && options[:missing_col] ||
271
+ surplus_col && options[:surplus_col]
272
+ raise 'Tables were not identical' if should_raise
273
+ end
274
+
156
275
  def to_hash(cells) #:nodoc:
157
276
  hash = Hash.new do |hash, key|
158
277
  hash[key.to_s] if key.is_a?(Symbol)
159
278
  end
160
- @raw[0].each_with_index do |column_name, column_index|
279
+ raw[0].each_with_index do |column_name, column_index|
161
280
  value = @conversion_procs[column_name].call(cells.value(column_index))
162
281
  hash[column_name] = value
163
282
  end
@@ -169,11 +288,11 @@ module Cucumber
169
288
  end
170
289
 
171
290
  def verify_column(column_name)
172
- raise %{The column named "#{column_name}" does not exist} unless @raw[0].include?(column_name)
291
+ raise %{The column named "#{column_name}" does not exist} unless raw[0].include?(column_name)
173
292
  end
174
293
 
175
294
  def verify_table_width(width)
176
- raise %{The table must have exactly #{width} columns} unless @raw[0].size == width
295
+ raise %{The table must have exactly #{width} columns} unless raw[0].size == width
177
296
  end
178
297
 
179
298
  def arguments_replaced(arguments) #:nodoc:
@@ -202,47 +321,158 @@ module Cucumber
202
321
  end
203
322
 
204
323
  def headers
205
- @raw.first
324
+ raw.first
206
325
  end
207
326
 
208
327
  def header_cell(col)
209
328
  cells_rows[0][col]
210
329
  end
211
330
 
331
+ def cell_matrix
332
+ @cell_matrix
333
+ end
334
+
335
+ def col_width(col)
336
+ columns[col].__send__(:width)
337
+ end
338
+
339
+ def to_s(options = {})
340
+ options = {:color => true, :indent => 2, :prefixes => TO_S_PREFIXES}.merge(options)
341
+ io = StringIO.new
342
+
343
+ c = Term::ANSIColor.coloring?
344
+ Term::ANSIColor.coloring = options[:color]
345
+ f = Formatter::Pretty.new(nil, io, options)
346
+ f.instance_variable_set('@indent', options[:indent])
347
+ f.visit_multiline_arg(self)
348
+ Term::ANSIColor.coloring = c
349
+
350
+ io.rewind
351
+ s = "\n" + io.read + (" " * (options[:indent] - 2))
352
+ s
353
+ end
354
+
355
+ private
356
+
357
+ TO_S_PREFIXES = Hash.new(' ')
358
+ TO_S_PREFIXES[:comment] = ['(+) ']
359
+ TO_S_PREFIXES[:undefined] = ['(-) ']
360
+
212
361
  protected
213
362
 
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)
363
+ def inspect_rows(missing_row, inserted_row)
364
+ missing_row.each_with_index do |missing_cell, col|
365
+ inserted_cell = inserted_row[col]
366
+ if(missing_cell.value != inserted_cell.value && (missing_cell.value.to_s == inserted_cell.value.to_s))
367
+ missing_cell.inspect!
368
+ inserted_cell.inspect!
220
369
  end
221
370
  end
222
371
  end
223
372
 
224
- private
373
+ def create_cell_matrix(raw)
374
+ @cell_matrix = raw.map do |raw_row|
375
+ line = raw_row.line rescue -1
376
+ raw_row.map do |raw_cell|
377
+ new_cell(raw_cell, line)
378
+ end
379
+ end
380
+ end
225
381
 
226
- def col_width(col)
227
- columns[col].__send__(:width)
382
+ def convert_columns!
383
+ cell_matrix.transpose.each do |col|
384
+ conversion_proc = @conversion_procs[col[0].value]
385
+ col[1..-1].each do |cell|
386
+ cell.value = conversion_proc.call(cell.value)
387
+ end
388
+ end
389
+ end
390
+
391
+ def require_diff_lcs
392
+ begin
393
+ require 'diff/lcs'
394
+ rescue LoadError => e
395
+ e.message << "\n Please gem install diff-lcs\n"
396
+ raise e
397
+ end
398
+ end
399
+
400
+ def clear_cache!
401
+ @hashes = @rows_hash = @rows = @columns = nil
228
402
  end
229
403
 
230
404
  def columns
231
405
  @columns ||= cell_matrix.transpose.map do |cell_row|
232
406
  @cells_class.new(self, cell_row)
233
- end
407
+ end.freeze
234
408
  end
235
409
 
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)
410
+ def new_cell(raw_cell, line)
411
+ @cell_class.new(raw_cell, self, line)
412
+ end
413
+
414
+ # Pads our own cell_matrix and returns a cell matrix of same
415
+ # column width that can be used for diffing
416
+ def pad!(other_cell_matrix)
417
+ clear_cache!
418
+ cols = cell_matrix.transpose
419
+ unmapped_cols = other_cell_matrix.transpose
420
+
421
+ mapped_cols = []
422
+
423
+ cols.each_with_index do |col, col_index|
424
+ header = col[0]
425
+ candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col|
426
+ other_col[0] == header
245
427
  end
428
+ raise "More than one column has the header #{header}" if candidate_cols.size > 2
429
+
430
+ other_padded_col = if candidate_cols.size == 1
431
+ # Found a matching column
432
+ candidate_cols[0]
433
+ else
434
+ mark_as_missing(cols[col_index])
435
+ (0...other_cell_matrix.length).map do |row|
436
+ val = row == 0 ? header.value : nil
437
+ SurplusCell.new(val, self, -1)
438
+ end
439
+ end
440
+ mapped_cols.insert(col_index, other_padded_col)
441
+ end
442
+
443
+ unmapped_cols.each_with_index do |col, col_index|
444
+ empty_col = (0...cell_matrix.length).map do |row|
445
+ SurplusCell.new(nil, self, -1)
446
+ end
447
+ cols << empty_col
448
+ end
449
+
450
+ @cell_matrix = cols.transpose
451
+ (mapped_cols + unmapped_cols).transpose
452
+ end
453
+
454
+ def ensure_table(table_or_array)
455
+ return table_or_array if Table === table_or_array
456
+ table_or_array = hashes_to_array(table_or_array) if Hash === table_or_array[0]
457
+ Table.new(table_or_array)
458
+ end
459
+
460
+ def hashes_to_array(hashes)
461
+ header = hashes[0].keys
462
+ [header] + hashes.map{|hash| header.map{|key| hash[key]}}
463
+ end
464
+
465
+ def ensure_green!
466
+ each_cell{|cell| cell.status = :passed}
467
+ end
468
+
469
+ def each_cell(&proc)
470
+ cell_matrix.each{|row| row.each(&proc)}
471
+ end
472
+
473
+ def mark_as_missing(col)
474
+ col.each do |cell|
475
+ cell.status = :undefined
246
476
  end
247
477
  end
248
478
 
@@ -268,7 +498,7 @@ module Cucumber
268
498
  end
269
499
 
270
500
  def to_hash #:nodoc:
271
- @to_hash ||= @table.to_hash(self)
501
+ @to_hash ||= @table.to_hash(self).freeze
272
502
  end
273
503
 
274
504
  def value(n) #:nodoc:
@@ -303,30 +533,38 @@ module Cucumber
303
533
  end
304
534
 
305
535
  class Cell
306
- attr_reader :value, :line
307
- attr_writer :status
536
+ attr_reader :line, :table
537
+ attr_accessor :status, :value
308
538
 
309
- def initialize(value, table, row, col, line)
310
- @value, @table, @row, @col, @line = value, table, row, col, line
539
+ def initialize(value, table, line)
540
+ @value, @table, @line = value, table, line
311
541
  end
312
542
 
313
543
  def accept(visitor)
314
- visitor.visit_table_cell_value(@value, col_width, @status)
544
+ visitor.visit_table_cell_value(value, status)
545
+ end
546
+
547
+ def inspect!
548
+ @value = "(i) #{value.inspect}"
315
549
  end
316
550
 
317
- def header_cell
318
- @table.header_cell(@col)
551
+ def ==(o)
552
+ SurplusCell === o || value == o.value
319
553
  end
320
554
 
321
555
  # For testing only
322
556
  def to_sexp #:nodoc:
323
557
  [:cell, @value]
324
558
  end
559
+ end
560
+
561
+ class SurplusCell < Cell
562
+ def status
563
+ :comment
564
+ end
325
565
 
326
- private
327
-
328
- def col_width
329
- @col_width ||= @table.__send__(:col_width, @col)
566
+ def ==(o)
567
+ true
330
568
  end
331
569
  end
332
570
  end