cuke_modeler 1.3.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +60 -17
  3. data/CHANGELOG.md +312 -0
  4. data/Gemfile +19 -3
  5. data/LICENSE.txt +1 -1
  6. data/README.md +17 -7
  7. data/Rakefile +45 -28
  8. data/appveyor.yml +57 -17
  9. data/cuke_modeler.gemspec +6 -3
  10. data/lib/cuke_modeler/adapters/gherkin_2_adapter.rb +1 -0
  11. data/lib/cuke_modeler/adapters/gherkin_3_adapter.rb +1 -0
  12. data/lib/cuke_modeler/adapters/gherkin_4_adapter.rb +2 -1
  13. data/lib/cuke_modeler/adapters/gherkin_5_adapter.rb +12 -0
  14. data/lib/cuke_modeler/adapters/gherkin_6_adapter.rb +310 -0
  15. data/lib/cuke_modeler/adapters/gherkin_7_adapter.rb +307 -0
  16. data/lib/cuke_modeler/adapters/gherkin_8_adapter.rb +12 -0
  17. data/lib/cuke_modeler/adapters/gherkin_9_adapter.rb +12 -0
  18. data/lib/cuke_modeler/containing.rb +16 -0
  19. data/lib/cuke_modeler/described.rb +1 -0
  20. data/lib/cuke_modeler/models/step.rb +31 -2
  21. data/lib/cuke_modeler/named.rb +1 -0
  22. data/lib/cuke_modeler/nested.rb +1 -0
  23. data/lib/cuke_modeler/parsed.rb +1 -0
  24. data/lib/cuke_modeler/parsing.rb +116 -68
  25. data/lib/cuke_modeler/sourceable.rb +1 -0
  26. data/lib/cuke_modeler/stepped.rb +1 -0
  27. data/lib/cuke_modeler/taggable.rb +1 -0
  28. data/lib/cuke_modeler/version.rb +1 -1
  29. data/testing/cucumber/features/analysis/step_comparison.feature +25 -0
  30. data/testing/cucumber/features/analysis/test_comparison.feature +1 -1
  31. data/testing/cucumber/step_definitions/feature_file_steps.rb +1 -1
  32. data/testing/cucumber/step_definitions/modeling_steps.rb +7 -2
  33. data/testing/cucumber/step_definitions/verification_steps.rb +11 -2
  34. data/testing/file_helper.rb +3 -0
  35. data/testing/gemfiles/gherkin2.gemfile +8 -0
  36. data/testing/gemfiles/gherkin3.gemfile +6 -0
  37. data/testing/gemfiles/gherkin4.gemfile +7 -0
  38. data/testing/gemfiles/gherkin5.gemfile +7 -0
  39. data/testing/gemfiles/gherkin6.gemfile +10 -0
  40. data/testing/gemfiles/gherkin7.gemfile +9 -0
  41. data/testing/gemfiles/gherkin8.gemfile +9 -0
  42. data/testing/gemfiles/gherkin9.gemfile +9 -0
  43. data/testing/helper_methods.rb +23 -0
  44. data/testing/rspec/spec/integration/{gherkin_2_adapter_spec.rb → adapters/gherkin_2_adapter_spec.rb} +13 -13
  45. data/testing/rspec/spec/integration/{gherkin_3_adapter_spec.rb → adapters/gherkin_3_adapter_spec.rb} +13 -13
  46. data/testing/rspec/spec/integration/{gherkin_4_adapter_spec.rb → adapters/gherkin_4_adapter_spec.rb} +13 -13
  47. data/testing/rspec/spec/integration/adapters/gherkin_5_adapter_spec.rb +165 -0
  48. data/testing/rspec/spec/integration/adapters/gherkin_6_adapter_spec.rb +159 -0
  49. data/testing/rspec/spec/integration/adapters/gherkin_7_adapter_spec.rb +162 -0
  50. data/testing/rspec/spec/integration/adapters/gherkin_8_adapter_spec.rb +162 -0
  51. data/testing/rspec/spec/integration/adapters/gherkin_9_adapter_spec.rb +162 -0
  52. data/testing/rspec/spec/integration/{background_integration_spec.rb → models/background_integration_spec.rb} +90 -86
  53. data/testing/rspec/spec/integration/{cell_integration_spec.rb → models/cell_integration_spec.rb} +49 -38
  54. data/testing/rspec/spec/integration/{comment_integration_spec.rb → models/comment_integration_spec.rb} +31 -20
  55. data/testing/rspec/spec/integration/{directory_integration_spec.rb → models/directory_integration_spec.rb} +3 -3
  56. data/testing/rspec/spec/integration/{doc_string_integration_spec.rb → models/doc_string_integration_spec.rb} +39 -35
  57. data/testing/rspec/spec/integration/{example_integration_spec.rb → models/example_integration_spec.rb} +109 -83
  58. data/testing/rspec/spec/integration/{feature_file_integration_spec.rb → models/feature_file_integration_spec.rb} +52 -38
  59. data/testing/rspec/spec/integration/{feature_integration_spec.rb → models/feature_integration_spec.rb} +125 -112
  60. data/testing/rspec/spec/integration/{model_integration_spec.rb → models/model_integration_spec.rb} +1 -1
  61. data/testing/rspec/spec/integration/{outline_integration_spec.rb → models/outline_integration_spec.rb} +138 -129
  62. data/testing/rspec/spec/integration/{row_integration_spec.rb → models/row_integration_spec.rb} +55 -35
  63. data/testing/rspec/spec/integration/{scenario_integration_spec.rb → models/scenario_integration_spec.rb} +92 -88
  64. data/testing/rspec/spec/integration/models/step_integration_spec.rb +573 -0
  65. data/testing/rspec/spec/integration/{table_integration_spec.rb → models/table_integration_spec.rb} +38 -34
  66. data/testing/rspec/spec/integration/{tag_integration_spec.rb → models/tag_integration_spec.rb} +56 -36
  67. data/testing/rspec/spec/integration/parsing_integration_spec.rb +45 -7
  68. data/testing/rspec/spec/spec_helper.rb +79 -43
  69. data/testing/rspec/spec/unit/cuke_modeler_unit_spec.rb +25 -0
  70. data/testing/rspec/spec/unit/{background_unit_spec.rb → models/background_unit_spec.rb} +1 -1
  71. data/testing/rspec/spec/unit/{cell_unit_spec.rb → models/cell_unit_spec.rb} +1 -1
  72. data/testing/rspec/spec/unit/{comment_unit_spec.rb → models/comment_unit_spec.rb} +1 -1
  73. data/testing/rspec/spec/unit/{directory_unit_spec.rb → models/directory_unit_spec.rb} +1 -1
  74. data/testing/rspec/spec/unit/{doc_string_unit_spec.rb → models/doc_string_unit_spec.rb} +1 -1
  75. data/testing/rspec/spec/unit/{example_unit_spec.rb → models/example_unit_spec.rb} +1 -1
  76. data/testing/rspec/spec/unit/{feature_file_unit_spec.rb → models/feature_file_unit_spec.rb} +1 -1
  77. data/testing/rspec/spec/unit/{feature_unit_spec.rb → models/feature_unit_spec.rb} +1 -1
  78. data/testing/rspec/spec/unit/{model_unit_spec.rb → models/model_unit_spec.rb} +1 -1
  79. data/testing/rspec/spec/unit/{outline_unit_spec.rb → models/outline_unit_spec.rb} +1 -1
  80. data/testing/rspec/spec/unit/{row_unit_spec.rb → models/row_unit_spec.rb} +1 -1
  81. data/testing/rspec/spec/unit/{scenario_unit_spec.rb → models/scenario_unit_spec.rb} +1 -1
  82. data/testing/rspec/spec/unit/{step_unit_spec.rb → models/step_unit_spec.rb} +2 -2
  83. data/testing/rspec/spec/unit/{table_unit_spec.rb → models/table_unit_spec.rb} +1 -1
  84. data/testing/rspec/spec/unit/{tag_unit_spec.rb → models/tag_unit_spec.rb} +1 -1
  85. data/testing/rspec/spec/unit/shared/containing_models_unit_specs.rb +102 -0
  86. data/todo.txt +5 -2
  87. metadata +80 -47
  88. data/History.md +0 -186
  89. data/testing/cucumber/support/transforms.rb +0 -3
  90. data/testing/rspec/spec/integration/step_integration_spec.rb +0 -459
@@ -0,0 +1,307 @@
1
+ module CukeModeler
2
+
3
+ # NOT A PART OF THE PUBLIC API
4
+ # An adapter that can convert the output of version 7.x of the *gherkin* gem into input that is consumable by this gem.
5
+
6
+ class Gherkin7Adapter
7
+
8
+ # Adapts the given AST into the shape that this gem expects
9
+ def adapt(parsed_ast)
10
+ # Saving off the original data
11
+ parsed_ast['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_ast))
12
+
13
+ # Removing parsed data for child elements in order to avoid duplicating data
14
+ parsed_ast['cuke_modeler_parsing_data'][:feature] = nil
15
+ parsed_ast['cuke_modeler_parsing_data'][:comments] = nil
16
+
17
+ parsed_ast['comments'] = []
18
+ parsed_ast[:comments].each do |comment|
19
+ adapt_comment!(comment)
20
+ end
21
+ parsed_ast['comments'].concat(parsed_ast.delete(:comments))
22
+
23
+ adapt_feature!(parsed_ast[:feature]) if parsed_ast[:feature]
24
+ parsed_ast['feature'] = parsed_ast.delete(:feature)
25
+
26
+ [parsed_ast]
27
+ end
28
+
29
+ # Adapts the AST sub-tree that is rooted at the given feature node.
30
+ def adapt_feature!(parsed_feature)
31
+ # Saving off the original data
32
+ parsed_feature['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_feature))
33
+
34
+ # Removing parsed data for child elements in order to avoid duplicating data
35
+ parsed_feature['cuke_modeler_parsing_data'][:tags] = nil
36
+ parsed_feature['cuke_modeler_parsing_data'][:children] = nil
37
+
38
+ parsed_feature['keyword'] = parsed_feature.delete(:keyword)
39
+ parsed_feature['name'] = parsed_feature.delete(:name)
40
+ parsed_feature['description'] = parsed_feature.delete(:description)
41
+ parsed_feature['line'] = parsed_feature.delete(:location)[:line]
42
+
43
+ parsed_feature['elements'] = []
44
+ adapt_child_elements!(parsed_feature[:children])
45
+ parsed_feature['elements'].concat(parsed_feature.delete(:children))
46
+
47
+ parsed_feature['tags'] = []
48
+ parsed_feature[:tags].each do |tag|
49
+ adapt_tag!(tag)
50
+ end
51
+ parsed_feature['tags'].concat(parsed_feature.delete(:tags))
52
+ end
53
+
54
+ # Adapts the AST sub-tree that is rooted at the given background node.
55
+ def adapt_background!(parsed_background)
56
+ # Saving off the original data
57
+ parsed_background['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_background))
58
+
59
+ # Removing parsed data for child elements in order to avoid duplicating data
60
+ parsed_background['cuke_modeler_parsing_data'][:background][:steps] = nil
61
+
62
+ parsed_background['type'] = 'Background'
63
+ parsed_background['keyword'] = parsed_background[:background].delete(:keyword)
64
+ parsed_background['name'] = parsed_background[:background].delete(:name)
65
+ parsed_background['description'] = parsed_background[:background].delete(:description)
66
+ parsed_background['line'] = parsed_background[:background].delete(:location)[:line]
67
+
68
+ parsed_background['steps'] = []
69
+ parsed_background[:background][:steps].each do |step|
70
+ adapt_step!(step)
71
+ end
72
+ parsed_background['steps'].concat(parsed_background[:background].delete(:steps))
73
+ end
74
+
75
+ # Adapts the AST sub-tree that is rooted at the given scenario node.
76
+ def adapt_scenario!(parsed_test)
77
+ # Removing parsed data for child elements in order to avoid duplicating data
78
+ parsed_test['cuke_modeler_parsing_data'][:scenario][:tags] = nil
79
+ parsed_test['cuke_modeler_parsing_data'][:scenario][:steps] = nil
80
+
81
+ parsed_test['type'] = 'Scenario'
82
+ parsed_test['keyword'] = parsed_test[:scenario].delete(:keyword)
83
+ parsed_test['name'] = parsed_test[:scenario].delete(:name)
84
+ parsed_test['description'] = parsed_test[:scenario].delete(:description)
85
+ parsed_test['line'] = parsed_test[:scenario].delete(:location)[:line]
86
+
87
+ parsed_test['tags'] = []
88
+ parsed_test[:scenario][:tags].each do |tag|
89
+ adapt_tag!(tag)
90
+ end
91
+ parsed_test['tags'].concat(parsed_test[:scenario].delete(:tags))
92
+
93
+ parsed_test['steps'] = []
94
+ parsed_test[:scenario][:steps].each do |step|
95
+ adapt_step!(step)
96
+ end
97
+ parsed_test['steps'].concat(parsed_test[:scenario].delete(:steps))
98
+ end
99
+
100
+ # Adapts the AST sub-tree that is rooted at the given outline node.
101
+ def adapt_outline!(parsed_test)
102
+ # Removing parsed data for child elements in order to avoid duplicating data
103
+ parsed_test['cuke_modeler_parsing_data'][:scenario][:tags] = nil
104
+ parsed_test['cuke_modeler_parsing_data'][:scenario][:steps] = nil
105
+ parsed_test['cuke_modeler_parsing_data'][:scenario][:examples] = nil
106
+
107
+ parsed_test['type'] = 'ScenarioOutline'
108
+ parsed_test['keyword'] = parsed_test[:scenario].delete(:keyword)
109
+ parsed_test['name'] = parsed_test[:scenario].delete(:name)
110
+ parsed_test['description'] = parsed_test[:scenario].delete(:description)
111
+ parsed_test['line'] = parsed_test[:scenario].delete(:location)[:line]
112
+
113
+ parsed_test['tags'] = []
114
+ parsed_test[:scenario][:tags].each do |tag|
115
+ adapt_tag!(tag)
116
+ end
117
+ parsed_test['tags'].concat(parsed_test[:scenario].delete(:tags))
118
+
119
+ parsed_test['steps'] = []
120
+ parsed_test[:scenario][:steps].each do |step|
121
+ adapt_step!(step)
122
+ end
123
+ parsed_test['steps'].concat(parsed_test[:scenario].delete(:steps))
124
+
125
+ parsed_test['examples'] = []
126
+ parsed_test[:scenario][:examples].each do |step|
127
+ adapt_example!(step)
128
+ end
129
+ parsed_test['examples'].concat(parsed_test[:scenario].delete(:examples))
130
+ end
131
+
132
+ # Adapts the AST sub-tree that is rooted at the given example node.
133
+ def adapt_example!(parsed_example)
134
+ # Saving off the original data
135
+ parsed_example['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_example))
136
+
137
+ # Removing parsed data for child elements in order to avoid duplicating data
138
+ parsed_example['cuke_modeler_parsing_data'][:tags] = nil
139
+ parsed_example['cuke_modeler_parsing_data'][:table_header] = nil
140
+ parsed_example['cuke_modeler_parsing_data'][:table_body] = nil
141
+
142
+ parsed_example['keyword'] = parsed_example.delete(:keyword)
143
+ parsed_example['name'] = parsed_example.delete(:name)
144
+ parsed_example['line'] = parsed_example.delete(:location)[:line]
145
+ parsed_example['description'] = parsed_example.delete(:description)
146
+
147
+ parsed_example['rows'] = []
148
+
149
+ if parsed_example[:table_header]
150
+ adapt_table_row!(parsed_example[:table_header])
151
+ parsed_example['rows'] << parsed_example.delete(:table_header)
152
+ end
153
+
154
+ if parsed_example[:table_body]
155
+ parsed_example[:table_body].each do |row|
156
+ adapt_table_row!(row)
157
+ end
158
+ parsed_example['rows'].concat(parsed_example.delete(:table_body))
159
+ end
160
+
161
+ parsed_example['tags'] = []
162
+ parsed_example[:tags].each do |tag|
163
+ adapt_tag!(tag)
164
+ end
165
+ parsed_example['tags'].concat(parsed_example.delete(:tags))
166
+ end
167
+
168
+ # Adapts the AST sub-tree that is rooted at the given tag node.
169
+ def adapt_tag!(parsed_tag)
170
+ # Saving off the original data
171
+ parsed_tag['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_tag))
172
+
173
+ parsed_tag['name'] = parsed_tag.delete(:name)
174
+ parsed_tag['line'] = parsed_tag.delete(:location)[:line]
175
+ end
176
+
177
+ # Adapts the AST sub-tree that is rooted at the given comment node.
178
+ def adapt_comment!(parsed_comment)
179
+ # Saving off the original data
180
+ parsed_comment['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_comment))
181
+
182
+ parsed_comment['text'] = parsed_comment.delete(:text)
183
+ parsed_comment['line'] = parsed_comment.delete(:location)[:line]
184
+ end
185
+
186
+ # Adapts the AST sub-tree that is rooted at the given step node.
187
+ def adapt_step!(parsed_step)
188
+ # Saving off the original data
189
+ parsed_step['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_step))
190
+
191
+ # Removing parsed data for child elements in order to avoid duplicating data
192
+ parsed_step['cuke_modeler_parsing_data'][:data_table] = nil
193
+ parsed_step['cuke_modeler_parsing_data'][:doc_string] = nil
194
+
195
+ parsed_step['keyword'] = parsed_step.delete(:keyword)
196
+ parsed_step['name'] = parsed_step.delete(:text)
197
+ parsed_step['line'] = parsed_step.delete(:location)[:line]
198
+
199
+
200
+ case
201
+ when parsed_step[:doc_string]
202
+ adapt_doc_string!(parsed_step[:doc_string])
203
+ parsed_step['doc_string'] = parsed_step.delete(:doc_string)
204
+ when parsed_step[:data_table]
205
+ adapt_step_table!(parsed_step[:data_table])
206
+ parsed_step['table'] = parsed_step.delete(:data_table)
207
+ else
208
+ # Step has no extra argument
209
+ end
210
+ end
211
+
212
+ # Adapts the AST sub-tree that is rooted at the given doc string node.
213
+ def adapt_doc_string!(parsed_doc_string)
214
+ # Saving off the original data
215
+ parsed_doc_string['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_doc_string))
216
+
217
+ parsed_doc_string['value'] = parsed_doc_string.delete(:content)
218
+ parsed_doc_string['content_type'] = parsed_doc_string.delete(:content_type).strip # TODO: fix bug in Gherkin so that this whitespace is already trimmed off
219
+ parsed_doc_string['line'] = parsed_doc_string.delete(:location)[:line]
220
+ end
221
+
222
+ # Adapts the AST sub-tree that is rooted at the given table node.
223
+ def adapt_step_table!(parsed_step_table)
224
+ # Saving off the original data
225
+ parsed_step_table['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_step_table))
226
+
227
+ # Removing parsed data for child elements in order to avoid duplicating data
228
+ parsed_step_table['cuke_modeler_parsing_data'][:rows] = nil
229
+
230
+ parsed_step_table['rows'] = []
231
+ parsed_step_table[:rows].each do |row|
232
+ adapt_table_row!(row)
233
+ end
234
+ parsed_step_table['rows'].concat(parsed_step_table.delete(:rows))
235
+ parsed_step_table['line'] = parsed_step_table.delete(:location)[:line]
236
+ end
237
+
238
+ # Adapts the AST sub-tree that is rooted at the given row node.
239
+ def adapt_table_row!(parsed_table_row)
240
+ # Saving off the original data
241
+ parsed_table_row['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_table_row))
242
+
243
+ # Removing parsed data for child elements in order to avoid duplicating data which the child elements will themselves include
244
+ parsed_table_row['cuke_modeler_parsing_data'][:cells] = nil
245
+
246
+
247
+ parsed_table_row['line'] = parsed_table_row.delete(:location)[:line]
248
+
249
+ parsed_table_row['cells'] = []
250
+ parsed_table_row[:cells].each do |row|
251
+ adapt_table_cell!(row)
252
+ end
253
+ parsed_table_row['cells'].concat(parsed_table_row.delete(:cells))
254
+ end
255
+
256
+ # Adapts the AST sub-tree that is rooted at the given cell node.
257
+ def adapt_table_cell!(parsed_cell)
258
+ # Saving off the original data
259
+ parsed_cell['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_cell))
260
+
261
+ parsed_cell['value'] = parsed_cell.delete(:value)
262
+ parsed_cell['line'] = parsed_cell.delete(:location)[:line]
263
+ end
264
+
265
+
266
+ private
267
+
268
+
269
+ def adapt_child_elements!(parsed_children)
270
+ return if parsed_children.empty?
271
+
272
+ background_child = parsed_children.find { |child| child[:background] }
273
+
274
+ if background_child
275
+ adapt_background!(background_child)
276
+
277
+ remaining_children = parsed_children.reject { |child| child[:background] }
278
+ end
279
+
280
+ adapt_tests!(remaining_children || parsed_children)
281
+ end
282
+
283
+ def adapt_tests!(parsed_tests)
284
+ return unless parsed_tests
285
+
286
+ parsed_tests.each do |test|
287
+ adapt_test!(test)
288
+ end
289
+ end
290
+
291
+ def adapt_test!(parsed_test)
292
+ # Saving off the original data
293
+ parsed_test['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_test))
294
+
295
+
296
+ case
297
+ when parsed_test[:scenario] && parsed_test[:scenario][:examples].any?
298
+ adapt_outline!(parsed_test)
299
+ when parsed_test[:scenario]
300
+ adapt_scenario!(parsed_test)
301
+ else
302
+ raise(ArgumentError, "Unknown test type with keys: #{parsed_test.keys}")
303
+ end
304
+ end
305
+
306
+ end
307
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'gherkin_7_adapter'
2
+
3
+ module CukeModeler
4
+
5
+ # NOT A PART OF THE PUBLIC API
6
+ # An adapter that can convert the output of version 8.x of the *gherkin* gem into input that is consumable by this gem.
7
+
8
+ class Gherkin8Adapter < Gherkin7Adapter
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'gherkin_7_adapter'
2
+
3
+ module CukeModeler
4
+
5
+ # NOT A PART OF THE PUBLIC API
6
+ # An adapter that can convert the output of version 9.x of the *gherkin* gem into input that is consumable by this gem.
7
+
8
+ class Gherkin9Adapter < Gherkin7Adapter
9
+
10
+ end
11
+
12
+ end
@@ -1,9 +1,25 @@
1
1
  module CukeModeler
2
2
 
3
+ # NOT A PART OF THE PUBLIC API
3
4
  # A mix-in module containing methods used by models that contain other models.
4
5
 
5
6
  module Containing
6
7
 
8
+ # Executes the given code block with every model that is a child of this model.
9
+ def each_descendant(&block)
10
+ children.each do |child_model|
11
+ block.call(child_model)
12
+ child_model.each_descendant(&block) if child_model.respond_to?(:each_descendant)
13
+ end
14
+ end
15
+
16
+ # Executes the given code block with this model and every model that is a child of this model.
17
+ def each_model(&block)
18
+ block.call(self)
19
+
20
+ each_descendant(&block)
21
+ end
22
+
7
23
 
8
24
  private
9
25
 
@@ -1,5 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
+ # NOT A PART OF THE PUBLIC API
3
4
  # A mix-in module containing methods used by models that represent an element that has a description.
4
5
 
5
6
  module Described
@@ -33,9 +33,11 @@ module CukeModeler
33
33
  # Returns *true* if the two steps have the same base text (i.e. minus any keyword,
34
34
  # table, or doc string and *false* otherwise.
35
35
  def ==(other_step)
36
- return false unless other_step.respond_to?(:text)
36
+ return false unless other_step.is_a?(CukeModeler::Step)
37
37
 
38
- text == other_step.text
38
+ text_matches?(other_step) &&
39
+ table_matches?(other_step) &&
40
+ doc_string_matches?(other_step)
39
41
  end
40
42
 
41
43
  # Returns the model objects that belong to this model.
@@ -65,5 +67,32 @@ module CukeModeler
65
67
  parsed_file.first['feature']['elements'].first['steps'].first
66
68
  end
67
69
 
70
+ def text_matches?(other_step)
71
+ text == other_step.text
72
+ end
73
+
74
+ def table_matches?(other_step)
75
+ return false if (!block.is_a?(CukeModeler::Table) || !other_step.block.is_a?(CukeModeler::Table)) && (block.is_a?(CukeModeler::Table) || other_step.block.is_a?(CukeModeler::Table))
76
+ return true unless block.is_a?(CukeModeler::Table) && other_step.block.is_a?(CukeModeler::Table)
77
+
78
+ first_step_values = block.rows.collect { |table_row| table_row.cells.map(&:value) }
79
+ second_step_values = other_step.block.rows.collect { |table_row| table_row.cells.map(&:value) }
80
+
81
+ first_step_values == second_step_values
82
+ end
83
+
84
+ def doc_string_matches?(other_step)
85
+ return false if (!block.is_a?(CukeModeler::DocString) || !other_step.block.is_a?(CukeModeler::DocString)) && (block.is_a?(CukeModeler::DocString) || other_step.block.is_a?(CukeModeler::DocString))
86
+ return true unless block.is_a?(CukeModeler::DocString) && other_step.block.is_a?(CukeModeler::DocString)
87
+
88
+ first_content = block.content
89
+ first_content_type = block.content_type
90
+ second_content = other_step.block.content
91
+ second_content_type = other_step.block.content_type
92
+
93
+ (first_content == second_content) &&
94
+ (first_content_type == second_content_type)
95
+ end
96
+
68
97
  end
69
98
  end
@@ -1,5 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
+ # NOT A PART OF THE PUBLIC API
3
4
  # A mix-in module containing methods used by models that represent an element that has a name.
4
5
 
5
6
  module Named
@@ -1,5 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
+ # NOT A PART OF THE PUBLIC API
3
4
  # A mix-in module containing methods used by models that are nested inside
4
5
  # of other models.
5
6
 
@@ -1,5 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
+ # NOT A PART OF THE PUBLIC API
3
4
  # A mix-in module containing methods used by models that are parsed from source text.
4
5
 
5
6
  module Parsed
@@ -1,75 +1,48 @@
1
- module CukeModeler
2
-
3
- # A module providing source text parsing functionality.
4
-
5
- module Parsing
6
-
7
-
8
- # Have to at least load some version of the gem before which version of the gem has been loaded can
9
- # be determined and the rest of the needed files can be loaded. Try the old one first and then the
10
- # new one.
11
- begin
12
- require 'gherkin'
13
- rescue LoadError
14
- require 'gherkin/parser'
15
- end
16
-
17
-
18
- # The *gherkin* gem loads differently and has different grammar rules across major versions. Parsing
19
- # will be done with an 'adapter' appropriate to the version of the *gherkin* gem that has been activated.
20
-
21
- case Gem.loaded_specs['gherkin'].version.version
22
- when /^[54]\./
23
- require 'gherkin/parser'
24
- require 'cuke_modeler/adapters/gherkin_4_adapter'
25
-
26
-
27
- # todo - make these methods private?
28
- def self.parsing_method(source_text, _filename)
29
- Gherkin::Parser.new.parse(source_text)
30
- end
31
-
32
- def self.adapter_class
33
- CukeModeler::Gherkin4Adapter
34
- end
35
-
36
- when /^3\./
37
- require 'gherkin/parser'
38
- require 'cuke_modeler/adapters/gherkin_3_adapter'
1
+ # Have to at least load some version of the gem before which version of the gem has been loaded can
2
+ # be determined and the rest of the needed files can be loaded. The entry points vary across versions,
3
+ # so try them all until one of them works.
4
+ begin
5
+ # Gherkin 2.x, 8.x, 9.x
6
+ require 'gherkin'
7
+ rescue LoadError
8
+ begin
9
+ require 'gherkin/parser'
10
+ rescue LoadError
11
+ # Gherkin 6.x, 7.x
12
+ require 'gherkin/gherkin'
13
+ end
14
+ end
39
15
 
40
16
 
41
- def self.parsing_method(source_text, _filename)
42
- Gherkin::Parser.new.parse(source_text)
43
- end
17
+ # The *gherkin* gem loads differently and has different grammar rules across major versions. Parsing
18
+ # will be done with an 'adapter' appropriate to the version of the *gherkin* gem that has been activated.
19
+
20
+ gherkin_version = Gem.loaded_specs['gherkin'].version.version
21
+ gherkin_major_version = gherkin_version.match(/^(\d+)\./)[1].to_i
22
+
23
+ case gherkin_major_version
24
+ when 6, 7, 8, 9
25
+ require 'gherkin/dialect'
26
+ when 3, 4, 5
27
+ require 'gherkin/parser'
28
+ when 2
29
+ require 'stringio'
30
+ require 'gherkin/formatter/json_formatter'
31
+ require 'gherkin'
32
+ require 'json'
33
+ require 'multi_json'
34
+ else
35
+ raise("Unknown Gherkin version: '#{gherkin_version}'")
36
+ end
44
37
 
45
- def self.adapter_class
46
- CukeModeler::Gherkin3Adapter
47
- end
38
+ require "cuke_modeler/adapters/gherkin_#{gherkin_major_version}_adapter"
48
39
 
49
- else # Assume version 2.x
50
- require 'stringio'
51
- require 'gherkin/formatter/json_formatter'
52
- require 'gherkin'
53
- require 'json'
54
- require 'multi_json'
55
- require 'cuke_modeler/adapters/gherkin_2_adapter'
56
-
57
-
58
- def self.parsing_method(source_text, filename)
59
- io = StringIO.new
60
- formatter = Gherkin::Formatter::JSONFormatter.new(io)
61
- parser = Gherkin::Parser::Parser.new(formatter)
62
- parser.parse(source_text, filename, 0)
63
- formatter.done
64
- MultiJson.load(io.string)
65
- end
66
40
 
67
- def self.adapter_class
68
- CukeModeler::Gherkin2Adapter
69
- end
41
+ module CukeModeler
70
42
 
71
- end
43
+ # A module providing source text parsing functionality.
72
44
 
45
+ module Parsing
73
46
 
74
47
  class << self
75
48
 
@@ -96,17 +69,92 @@ module CukeModeler
96
69
  def parse_text(source_text, filename = 'cuke_modeler_fake_file.feature')
97
70
  raise(ArgumentError, "Text to parse must be a String but got #{source_text.class}") unless source_text.is_a?(String)
98
71
 
99
-
100
72
  begin
101
73
  parsed_result = parsing_method(source_text, filename)
102
74
  rescue => e
103
75
  raise(ArgumentError, "Error encountered while parsing '#{filename}'\n#{e.class} - #{e.message}")
104
76
  end
105
77
 
106
- adapted_result = adapter_class.new.adapt(parsed_result)
78
+ adapter_class.new.adapt(parsed_result)
79
+ end
80
+
107
81
 
82
+ gherkin_version = Gem.loaded_specs['gherkin'].version.version
83
+ gherkin_major_version = gherkin_version.match(/^(\d+)\./)[1].to_i
84
+
85
+ case gherkin_major_version
86
+ when 9
87
+ # NOT A PART OF THE PUBLIC API
88
+ # The method to use for parsing Gherkin text
89
+ def parsing_method(source_text, filename)
90
+ messages = Gherkin.from_source(filename, source_text, { :include_gherkin_document => true }).to_a.map(&:to_hash)
91
+
92
+ potential_error_message = messages.find { |message| message[:attachment] }
93
+ gherkin_ast_message = messages.find { |message| message[:gherkin_document] }
94
+
95
+ if potential_error_message
96
+ raise potential_error_message[:attachment][:data] if potential_error_message[:attachment][:data] =~ /expected.*got/
97
+ end
98
+
99
+ gherkin_ast_message[:gherkin_document]
100
+ end
101
+ when 8
102
+ # NOT A PART OF THE PUBLIC API
103
+ # The method to use for parsing Gherkin text
104
+ def parsing_method(source_text, filename)
105
+ messages = Gherkin.from_source(filename, source_text, { :include_gherkin_document => true }).to_a.map(&:to_hash)
106
+
107
+ potential_error_message = messages.find { |message| message[:attachment] }
108
+ gherkin_ast_message = messages.find { |message| message[:gherkinDocument] }
109
+
110
+ if potential_error_message
111
+ raise potential_error_message[:attachment][:data] if potential_error_message[:attachment][:data] =~ /expected.*got/
112
+ end
113
+
114
+ gherkin_ast_message[:gherkinDocument]
115
+ end
116
+ when 6, 7
117
+ # NOT A PART OF THE PUBLIC API
118
+ # The method to use for parsing Gherkin text
119
+ def parsing_method(source_text, filename)
120
+ messages = Gherkin::Gherkin.from_source(filename, source_text).to_a.map(&:to_hash)
121
+
122
+ potential_error_message = messages.find { |message| message[:attachment] }
123
+ gherkin_ast_message = messages.find { |message| message[:gherkinDocument] }
124
+
125
+ if potential_error_message
126
+ raise potential_error_message[:attachment][:data] if potential_error_message[:attachment][:data] =~ /expected.*got/
127
+ end
128
+
129
+ gherkin_ast_message[:gherkinDocument]
130
+ end
131
+ when 3, 4, 5
132
+ # todo - make these methods private?
133
+ # NOT A PART OF THE PUBLIC API
134
+ # The method to use for parsing Gherkin text
135
+ # Filename isn't used by this version of Gherkin but keeping the parameter so that the calling method only has to know one method signature
136
+ def parsing_method(source_text, _filename)
137
+ Gherkin::Parser.new.parse(source_text)
138
+ end
139
+ when 2
140
+ # NOT A PART OF THE PUBLIC API
141
+ # The method to use for parsing Gherkin text
142
+ def parsing_method(source_text, filename)
143
+ io = StringIO.new
144
+ formatter = Gherkin::Formatter::JSONFormatter.new(io)
145
+ parser = Gherkin::Parser::Parser.new(formatter)
146
+ parser.parse(source_text, filename, 0)
147
+ formatter.done
148
+ MultiJson.load(io.string)
149
+ end
150
+ else
151
+ raise("Unknown Gherkin version: '#{gherkin_version}'")
152
+ end
108
153
 
109
- adapted_result
154
+ # NOT A PART OF THE PUBLIC API
155
+ # The adapter to use when converting an AST to a standard internal shape
156
+ define_method('adapter_class') do
157
+ CukeModeler.const_get("Gherkin#{gherkin_major_version}Adapter")
110
158
  end
111
159
 
112
160
  end