cuke_modeler 1.3.0 → 2.1.0

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 (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