benhamill-gherkin 2.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. data/.gitattributes +2 -0
  2. data/.gitignore +11 -0
  3. data/.mailmap +2 -0
  4. data/.rspec +1 -0
  5. data/.rvmrc +1 -0
  6. data/Gemfile +7 -0
  7. data/History.txt +363 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +149 -0
  10. data/Rakefile +19 -0
  11. data/VERSION +1 -0
  12. data/build_native_gems.sh +8 -0
  13. data/cucumber.yml +3 -0
  14. data/features/escaped_pipes.feature +8 -0
  15. data/features/feature_parser.feature +237 -0
  16. data/features/json_formatter.feature +377 -0
  17. data/features/json_parser.feature +318 -0
  18. data/features/native_lexer.feature +19 -0
  19. data/features/parser_with_native_lexer.feature +205 -0
  20. data/features/pretty_formatter.feature +15 -0
  21. data/features/step_definitions/eyeball_steps.rb +3 -0
  22. data/features/step_definitions/gherkin_steps.rb +29 -0
  23. data/features/step_definitions/json_formatter_steps.rb +28 -0
  24. data/features/step_definitions/json_parser_steps.rb +20 -0
  25. data/features/step_definitions/pretty_formatter_steps.rb +84 -0
  26. data/features/steps_parser.feature +46 -0
  27. data/features/support/env.rb +38 -0
  28. data/gherkin.gemspec +61 -0
  29. data/ikvm/.gitignore +3 -0
  30. data/java/.gitignore +5 -0
  31. data/java/src/main/java/gherkin/lexer/i18n/.gitignore +1 -0
  32. data/java/src/main/resources/gherkin/.gitignore +1 -0
  33. data/js/lib/gherkin/lexer/i18n/ar.js +1094 -0
  34. data/js/lib/gherkin/lexer/i18n/bg.js +1308 -0
  35. data/js/lib/gherkin/lexer/i18n/ca.js +1236 -0
  36. data/js/lib/gherkin/lexer/i18n/cs.js +1090 -0
  37. data/js/lib/gherkin/lexer/i18n/cy_gb.js +958 -0
  38. data/js/lib/gherkin/lexer/i18n/da.js +974 -0
  39. data/js/lib/gherkin/lexer/i18n/de.js +1082 -0
  40. data/js/lib/gherkin/lexer/i18n/en.js +965 -0
  41. data/js/lib/gherkin/lexer/i18n/en_au.js +902 -0
  42. data/js/lib/gherkin/lexer/i18n/en_lol.js +859 -0
  43. data/js/lib/gherkin/lexer/i18n/en_pirate.js +1136 -0
  44. data/js/lib/gherkin/lexer/i18n/en_scouse.js +1289 -0
  45. data/js/lib/gherkin/lexer/i18n/en_tx.js +942 -0
  46. data/js/lib/gherkin/lexer/i18n/eo.js +916 -0
  47. data/js/lib/gherkin/lexer/i18n/es.js +1049 -0
  48. data/js/lib/gherkin/lexer/i18n/et.js +915 -0
  49. data/js/lib/gherkin/lexer/i18n/fi.js +894 -0
  50. data/js/lib/gherkin/lexer/i18n/fr.js +1116 -0
  51. data/js/lib/gherkin/lexer/i18n/he.js +1044 -0
  52. data/js/lib/gherkin/lexer/i18n/hr.js +994 -0
  53. data/js/lib/gherkin/lexer/i18n/hu.js +1043 -0
  54. data/js/lib/gherkin/lexer/i18n/id.js +884 -0
  55. data/js/lib/gherkin/lexer/i18n/it.js +1007 -0
  56. data/js/lib/gherkin/lexer/i18n/ja.js +1344 -0
  57. data/js/lib/gherkin/lexer/i18n/ko.js +1028 -0
  58. data/js/lib/gherkin/lexer/i18n/lt.js +972 -0
  59. data/js/lib/gherkin/lexer/i18n/lu.js +1057 -0
  60. data/js/lib/gherkin/lexer/i18n/lv.js +1092 -0
  61. data/js/lib/gherkin/lexer/i18n/nl.js +1036 -0
  62. data/js/lib/gherkin/lexer/i18n/no.js +986 -0
  63. data/js/lib/gherkin/lexer/i18n/pl.js +1140 -0
  64. data/js/lib/gherkin/lexer/i18n/pt.js +1000 -0
  65. data/js/lib/gherkin/lexer/i18n/ro.js +1089 -0
  66. data/js/lib/gherkin/lexer/i18n/ru.js +1560 -0
  67. data/js/lib/gherkin/lexer/i18n/sk.js +972 -0
  68. data/js/lib/gherkin/lexer/i18n/sr_cyrl.js +1728 -0
  69. data/js/lib/gherkin/lexer/i18n/sr_latn.js +1220 -0
  70. data/js/lib/gherkin/lexer/i18n/sv.js +997 -0
  71. data/js/lib/gherkin/lexer/i18n/tr.js +1014 -0
  72. data/js/lib/gherkin/lexer/i18n/uk.js +1572 -0
  73. data/js/lib/gherkin/lexer/i18n/uz.js +1302 -0
  74. data/js/lib/gherkin/lexer/i18n/vi.js +1124 -0
  75. data/js/lib/gherkin/lexer/i18n/zh_cn.js +902 -0
  76. data/js/lib/gherkin/lexer/i18n/zh_tw.js +940 -0
  77. data/lib/.gitignore +4 -0
  78. data/lib/gherkin.rb +2 -0
  79. data/lib/gherkin/c_lexer.rb +17 -0
  80. data/lib/gherkin/formatter/ansi_escapes.rb +95 -0
  81. data/lib/gherkin/formatter/argument.rb +16 -0
  82. data/lib/gherkin/formatter/escaping.rb +15 -0
  83. data/lib/gherkin/formatter/filter_formatter.rb +136 -0
  84. data/lib/gherkin/formatter/hashable.rb +19 -0
  85. data/lib/gherkin/formatter/json_formatter.rb +102 -0
  86. data/lib/gherkin/formatter/line_filter.rb +26 -0
  87. data/lib/gherkin/formatter/model.rb +236 -0
  88. data/lib/gherkin/formatter/pretty_formatter.rb +243 -0
  89. data/lib/gherkin/formatter/regexp_filter.rb +21 -0
  90. data/lib/gherkin/formatter/step_printer.rb +17 -0
  91. data/lib/gherkin/formatter/tag_count_formatter.rb +47 -0
  92. data/lib/gherkin/formatter/tag_filter.rb +19 -0
  93. data/lib/gherkin/i18n.rb +175 -0
  94. data/lib/gherkin/i18n.yml +588 -0
  95. data/lib/gherkin/json_parser.rb +137 -0
  96. data/lib/gherkin/lexer/i18n_lexer.rb +47 -0
  97. data/lib/gherkin/listener/event.rb +45 -0
  98. data/lib/gherkin/listener/formatter_listener.rb +113 -0
  99. data/lib/gherkin/native.rb +7 -0
  100. data/lib/gherkin/native/ikvm.rb +55 -0
  101. data/lib/gherkin/native/java.rb +55 -0
  102. data/lib/gherkin/native/null.rb +9 -0
  103. data/lib/gherkin/parser/meta.txt +5 -0
  104. data/lib/gherkin/parser/parser.rb +164 -0
  105. data/lib/gherkin/parser/root.txt +11 -0
  106. data/lib/gherkin/parser/steps.txt +4 -0
  107. data/lib/gherkin/rb_lexer.rb +8 -0
  108. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  109. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  110. data/lib/gherkin/rubify.rb +24 -0
  111. data/lib/gherkin/tag_expression.rb +62 -0
  112. data/lib/gherkin/version.rb +3 -0
  113. data/ragel/i18n/.gitignore +1 -0
  114. data/ragel/lexer.c.rl.erb +439 -0
  115. data/ragel/lexer.java.rl.erb +208 -0
  116. data/ragel/lexer.rb.rl.erb +167 -0
  117. data/ragel/lexer_common.rl.erb +50 -0
  118. data/spec/gherkin/c_lexer_spec.rb +21 -0
  119. data/spec/gherkin/fixtures/1.feature +8 -0
  120. data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
  121. data/spec/gherkin/fixtures/complex.feature +45 -0
  122. data/spec/gherkin/fixtures/complex.json +143 -0
  123. data/spec/gherkin/fixtures/complex_for_filtering.feature +60 -0
  124. data/spec/gherkin/fixtures/complex_with_tags.feature +61 -0
  125. data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
  126. data/spec/gherkin/fixtures/hantu_pisang.feature +35 -0
  127. data/spec/gherkin/fixtures/i18n_fr.feature +14 -0
  128. data/spec/gherkin/fixtures/i18n_no.feature +7 -0
  129. data/spec/gherkin/fixtures/i18n_zh-CN.feature +9 -0
  130. data/spec/gherkin/fixtures/scenario_outline_with_tags.feature +13 -0
  131. data/spec/gherkin/fixtures/scenario_without_steps.feature +5 -0
  132. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  133. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  134. data/spec/gherkin/fixtures/with_bom.feature +3 -0
  135. data/spec/gherkin/formatter/ansi_escapes_spec.rb +19 -0
  136. data/spec/gherkin/formatter/filter_formatter_spec.rb +165 -0
  137. data/spec/gherkin/formatter/model_spec.rb +28 -0
  138. data/spec/gherkin/formatter/pretty_formatter_spec.rb +158 -0
  139. data/spec/gherkin/formatter/spaces.feature +9 -0
  140. data/spec/gherkin/formatter/step_printer_spec.rb +55 -0
  141. data/spec/gherkin/formatter/tabs.feature +9 -0
  142. data/spec/gherkin/formatter/tag_count_formatter_spec.rb +30 -0
  143. data/spec/gherkin/i18n_spec.rb +152 -0
  144. data/spec/gherkin/java_lexer_spec.rb +20 -0
  145. data/spec/gherkin/java_libs.rb +20 -0
  146. data/spec/gherkin/json_parser_spec.rb +113 -0
  147. data/spec/gherkin/lexer/i18n_lexer_spec.rb +43 -0
  148. data/spec/gherkin/output_stream_string_io.rb +20 -0
  149. data/spec/gherkin/parser/parser_spec.rb +16 -0
  150. data/spec/gherkin/rb_lexer_spec.rb +19 -0
  151. data/spec/gherkin/sexp_recorder.rb +56 -0
  152. data/spec/gherkin/shared/lexer_group.rb +593 -0
  153. data/spec/gherkin/shared/py_string_group.rb +153 -0
  154. data/spec/gherkin/shared/row_group.rb +125 -0
  155. data/spec/gherkin/shared/tags_group.rb +54 -0
  156. data/spec/gherkin/tag_expression_spec.rb +137 -0
  157. data/spec/spec_helper.rb +69 -0
  158. data/tasks/bench.rake +184 -0
  159. data/tasks/bench/feature_builder.rb +49 -0
  160. data/tasks/bench/generated/.gitignore +1 -0
  161. data/tasks/bench/null_listener.rb +4 -0
  162. data/tasks/compile.rake +102 -0
  163. data/tasks/cucumber.rake +20 -0
  164. data/tasks/gems.rake +35 -0
  165. data/tasks/ikvm.rake +79 -0
  166. data/tasks/ragel_task.rb +70 -0
  167. data/tasks/rdoc.rake +9 -0
  168. data/tasks/release.rake +30 -0
  169. data/tasks/rspec.rake +8 -0
  170. metadata +609 -0
@@ -0,0 +1,56 @@
1
+ require 'gherkin/rubify'
2
+ require 'gherkin/formatter/model'
3
+
4
+ module Gherkin
5
+ class SexpRecorder
6
+ include Rubify
7
+
8
+ def initialize
9
+ @sexps = []
10
+ end
11
+
12
+ def method_missing(event, *args)
13
+ event = :scenario_outline if event == :scenarioOutline # Special Java Lexer handling
14
+ event = :py_string if event == :pyString # Special Java Lexer handling
15
+ event = :syntax_error if event == :syntaxError # Special Java Lexer handling
16
+ args = rubify(args)
17
+ args = sexpify(args)
18
+ @sexps << [event] + args
19
+ end
20
+
21
+ def to_sexp
22
+ @sexps
23
+ end
24
+
25
+ # Useful in IRB
26
+ def reset!
27
+ @sexps = []
28
+ end
29
+
30
+ def errors
31
+ @sexps.select { |sexp| sexp[0] == :syntax_error }
32
+ end
33
+
34
+ def line(number)
35
+ @sexps.find { |sexp| sexp.last == number }
36
+ end
37
+
38
+ def sexpify(o)
39
+ if (defined?(JRUBY_VERSION) && Java.java.util.Collection === o) || Array === o
40
+ o.map{|e| sexpify(e)}
41
+ elsif(Formatter::Model::Row === o)
42
+ {
43
+ "cells" => sexpify(o.cells),
44
+ "comments" => sexpify(o.comments),
45
+ "line" => o.line,
46
+ }
47
+ elsif(Formatter::Model::Comment === o)
48
+ o.value
49
+ elsif(Formatter::Model::Tag === o)
50
+ o.name
51
+ else
52
+ o
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,593 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer" do
7
+ def scan(gherkin)
8
+ @lexer.scan(gherkin)
9
+ end
10
+
11
+ describe "Comments" do
12
+ it "should parse a one line comment" do
13
+ scan("# My comment\n")
14
+ @listener.to_sexp.should == [
15
+ [:comment, "# My comment", 1],
16
+ [:eof]
17
+ ]
18
+ end
19
+
20
+ it "should parse a multiline comment" do
21
+ scan("# Hello\n\n# World\n")
22
+ @listener.to_sexp.should == [
23
+ [:comment, "# Hello", 1],
24
+ [:comment, "# World", 3],
25
+ [:eof]
26
+ ]
27
+ end
28
+
29
+ it "should not consume comments as part of a multiline name" do
30
+ scan("Scenario: test\n#hello\n Scenario: another")
31
+ @listener.to_sexp.should == [
32
+ [:scenario, "Scenario", "test", "", 1],
33
+ [:comment, "#hello", 2],
34
+ [:scenario, "Scenario", "another", "", 3],
35
+ [:eof]
36
+ ]
37
+ end
38
+
39
+ it "should not consume comments as part of a multiline example name" do
40
+ scan("Examples: thing\n# ho hum\n| 1 | 2 |\n| 3 | 4 |\n")
41
+ @listener.to_sexp.should == [
42
+ [:examples, "Examples", "thing", "", 1],
43
+ [:comment, "# ho hum", 2],
44
+ [:row, ["1","2"], 3],
45
+ [:row, ["3","4"], 4],
46
+ [:eof]
47
+ ]
48
+ end
49
+
50
+ it "should allow empty comment lines" do
51
+ scan("#\n # A comment\n #\n")
52
+ @listener.to_sexp.should == [
53
+ [:comment, "#", 1],
54
+ [:comment, "# A comment", 2],
55
+ [:comment, "#", 3],
56
+ [:eof]
57
+ ]
58
+ end
59
+
60
+ it "should not allow comments within the Feature description" do
61
+ lambda {
62
+ scan("Feature: something\nAs a something\n# Comment\nI want something")
63
+ }.should raise_error(/Lexing error on line 4/)
64
+ end
65
+ end
66
+
67
+ describe "Tags" do
68
+ it "should not take the tags as part of a multiline name feature element" do
69
+ scan("Feature: hi\n Scenario: test\n\n@hello\n Scenario: another")
70
+ @listener.to_sexp.should == [
71
+ [:feature, "Feature", "hi", "", 1],
72
+ [:scenario, "Scenario", "test", "", 2],
73
+ [:tag, "@hello", 4],
74
+ [:scenario, "Scenario", "another", "", 5],
75
+ [:eof]
76
+ ]
77
+ end
78
+ end
79
+
80
+ describe "Background" do
81
+ it "should allow an empty background name and description" do
82
+ scan("Background:\nGiven I am a step\n")
83
+ @listener.to_sexp.should == [
84
+ [:background, "Background", "", "", 1],
85
+ [:step, "Given ", "I am a step", 2],
86
+ [:eof]
87
+ ]
88
+ end
89
+
90
+ it "should allow an empty background description" do
91
+ scan("Background: Yeah\nGiven I am a step\n")
92
+ @listener.to_sexp.should == [
93
+ [:background, "Background", "Yeah", "", 1],
94
+ [:step, "Given ", "I am a step", 2],
95
+ [:eof]
96
+ ]
97
+ end
98
+
99
+ it "should allow multiline descriptions ending at eof" do
100
+ scan("Background: I have several\n Lines to look at\n None starting with Given")
101
+ @listener.to_sexp.should == [
102
+ [:background, "Background", "I have several", " Lines to look at\n None starting with Given", 1],
103
+ [:eof]
104
+ ]
105
+ end
106
+
107
+ it "should allow multiline descriptions, including whitespace" do
108
+ scan(%{Feature: Hi
109
+ Background: It is my ambition to say
110
+ in ten sentences
111
+ what others say
112
+ in a whole book.
113
+ Given I am a step})
114
+ @listener.to_sexp.should == [
115
+ [:feature, "Feature", "Hi", "", 1],
116
+ [:background, "Background", "It is my ambition to say", "in ten sentences\n what others say \nin a whole book.",2],
117
+ [:step, "Given ", "I am a step", 6],
118
+ [:eof]
119
+ ]
120
+ end
121
+ end
122
+
123
+ describe "Scenarios" do
124
+ it "should be parsed" do
125
+ scan("Scenario: Hello\n")
126
+ @listener.to_sexp.should == [
127
+ [:scenario, "Scenario", "Hello", "", 1],
128
+ [:eof]
129
+ ]
130
+ end
131
+
132
+ it "should allow whitespace lines after the Scenario line" do
133
+ scan(%{Scenario: bar
134
+
135
+ Given baz
136
+ })
137
+ @listener.to_sexp.should == [
138
+ [:scenario, "Scenario", "bar", "", 1],
139
+ [:step, "Given ", "baz", 3],
140
+ [:eof]
141
+ ]
142
+ end
143
+
144
+ it "should allow multiline descriptions, including whitespace" do
145
+ scan(%{Scenario: It is my ambition to say
146
+ in ten sentences
147
+ what others say
148
+ in a whole book.
149
+ Given I am a step
150
+ })
151
+ @listener.to_sexp.should == [
152
+ [:scenario, "Scenario", "It is my ambition to say", "in ten sentences\nwhat others say \n in a whole book.", 1],
153
+ [:step, "Given ", "I am a step", 5],
154
+ [:eof]
155
+ ]
156
+ end
157
+
158
+ it "should allow multiline names ending at eof" do
159
+ scan("Scenario: I have several\nLines to look at\n None starting with Given")
160
+ @listener.to_sexp.should == [
161
+ [:scenario, "Scenario", "I have several", "Lines to look at\nNone starting with Given", 1],
162
+ [:eof]
163
+ ]
164
+ end
165
+
166
+ it "should ignore gherkin keywords embedded in other words" do
167
+ scan(%{Scenario: I have a Button
168
+ Buttons are great
169
+ Given I have some
170
+ But I might not because I am a Charles Dickens character
171
+ })
172
+ @listener.to_sexp.should == [
173
+ [:scenario, "Scenario", "I have a Button", "Buttons are great", 1],
174
+ [:step, "Given ", "I have some", 3],
175
+ [:step, "But ", "I might not because I am a Charles Dickens character", 4],
176
+ [:eof]
177
+ ]
178
+ end
179
+
180
+ it "should allow step keywords in Scenario names" do
181
+ scan(%{Scenario: When I have when in scenario
182
+ I should be fine
183
+ Given I am a step
184
+ })
185
+ @listener.to_sexp.should == [
186
+ [:scenario, "Scenario", "When I have when in scenario", "I should be fine", 1],
187
+ [:step, "Given ", "I am a step", 3],
188
+ [:eof]
189
+ ]
190
+ end
191
+ end
192
+
193
+ describe "Scenario Outlines" do
194
+ it "should be parsed" do
195
+ scan(<<-HERE)
196
+ Scenario Outline: Hello
197
+ With a description
198
+ Given a <what> cucumber
199
+ Examples: With a name
200
+ and a description
201
+ |what|
202
+ |green|
203
+ HERE
204
+ @listener.to_sexp.should == [
205
+ [:scenario_outline, "Scenario Outline", "Hello", "With a description", 1],
206
+ [:step, "Given ", "a <what> cucumber", 3],
207
+ [:examples, "Examples", "With a name", "and a description", 4],
208
+ [:row, ["what"], 6],
209
+ [:row, ["green"], 7],
210
+ [:eof]
211
+ ]
212
+ end
213
+
214
+
215
+ it "should parse with no steps or examples" do
216
+ scan(%{Scenario Outline: Hello
217
+
218
+ Scenario: My Scenario
219
+ })
220
+ @listener.to_sexp.should == [
221
+ [:scenario_outline, "Scenario Outline", "Hello", "", 1],
222
+ [:scenario, "Scenario", "My Scenario", "", 3],
223
+ [:eof]
224
+ ]
225
+ end
226
+
227
+ it "should allow multiline description" do
228
+ scan(<<-HERE)
229
+ Scenario Outline: It is my ambition to say
230
+ in ten sentences
231
+ what others say
232
+ in a whole book.
233
+ Given I am a step
234
+ HERE
235
+ @listener.to_sexp.should == [
236
+ [:scenario_outline, "Scenario Outline", "It is my ambition to say", "in ten sentences\n what others say \nin a whole book.", 1],
237
+ [:step, "Given ", "I am a step", 5],
238
+ [:eof]
239
+ ]
240
+ end
241
+ end
242
+
243
+ describe "Examples" do
244
+ it "should be parsed" do
245
+ scan(%{Examples:
246
+ |x|y|
247
+ |5|6|
248
+ })
249
+ @listener.to_sexp.should == [
250
+ [:examples, "Examples", "", "", 1],
251
+ [:row, ["x","y"], 2],
252
+ [:row, ["5","6"], 3],
253
+ [:eof]
254
+ ]
255
+ end
256
+
257
+ it "should parse multiline example names" do
258
+ scan(%{Examples: I'm a multiline name
259
+ and I'm ok
260
+ f'real
261
+ |x|
262
+ |5|
263
+ })
264
+ @listener.to_sexp.should == [
265
+ [:examples, "Examples", "I'm a multiline name", "and I'm ok\nf'real", 1],
266
+ [:row, ["x"], 4],
267
+ [:row, ["5"], 5],
268
+ [:eof]
269
+ ]
270
+ end
271
+ end
272
+
273
+ describe "Steps" do
274
+ it "should parse steps with inline table" do
275
+ scan(%{Given I have a table
276
+ |a|b|
277
+ })
278
+ @listener.to_sexp.should == [
279
+ [:step, "Given ", "I have a table", 1],
280
+ [:row, ['a','b'], 2],
281
+ [:eof]
282
+ ]
283
+ end
284
+
285
+ it "should parse steps with inline py_string" do
286
+ scan("Given I have a string\n\"\"\"\nhello\nworld\n\"\"\"")
287
+ @listener.to_sexp.should == [
288
+ [:step, "Given ", "I have a string", 1],
289
+ [:py_string, "hello\nworld", 2],
290
+ [:eof]
291
+ ]
292
+ end
293
+ end
294
+
295
+ describe "A single feature, single scenario, single step" do
296
+ it "should find the feature, scenario, and step" do
297
+ scan("Feature: Feature Text\n Scenario: Reading a Scenario\n Given there is a step\n")
298
+ @listener.to_sexp.should == [
299
+ [:feature, "Feature", "Feature Text", "", 1],
300
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
301
+ [:step, "Given ", "there is a step", 3],
302
+ [:eof]
303
+ ]
304
+ end
305
+ end
306
+
307
+ describe "A feature ending in whitespace" do
308
+ it "should not raise an error when whitespace follows the Feature, Scenario, and Steps" do
309
+ scan("Feature: Feature Text\n Scenario: Reading a Scenario\n Given there is a step\n ")
310
+ @listener.to_sexp.should == [
311
+ [:feature, "Feature", "Feature Text", "", 1],
312
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
313
+ [:step, "Given ", "there is a step", 3],
314
+ [:eof]
315
+ ]
316
+ end
317
+ end
318
+
319
+ describe "A single feature, single scenario, three steps" do
320
+
321
+ it "should find the feature, scenario, and three steps" do
322
+ scan("Feature: Feature Text\n Scenario: Reading a Scenario\n Given there is a step\n And another step\n And a third step\n")
323
+ @listener.to_sexp.should == [
324
+ [:feature, "Feature", "Feature Text", "", 1],
325
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
326
+ [:step, "Given ", "there is a step", 3],
327
+ [:step, "And ", "another step", 4],
328
+ [:step, "And ", "a third step", 5],
329
+ [:eof]
330
+ ]
331
+ end
332
+ end
333
+
334
+ describe "A single feature with no scenario" do
335
+ it "should find the feature" do
336
+ scan("Feature: Feature Text\n")
337
+ @listener.to_sexp.should == [
338
+ [:feature, "Feature", "Feature Text", "", 1],
339
+ [:eof]
340
+ ]
341
+ end
342
+
343
+ it "should parse a one line feature with no newline" do
344
+ scan("Feature: hi")
345
+ @listener.to_sexp.should == [
346
+ [:feature, "Feature", "hi", "", 1],
347
+ [:eof]
348
+ ]
349
+ end
350
+ end
351
+
352
+ describe "A multi-line feature with no scenario" do
353
+ it "should find the feature" do
354
+ scan("Feature: Feature Text\n And some more text")
355
+ @listener.to_sexp.should == [
356
+ [:feature, "Feature", "Feature Text", "And some more text", 1],
357
+ [:eof]
358
+ ]
359
+ end
360
+ end
361
+
362
+ describe "A feature with a scenario but no steps" do
363
+ it "should find the feature and scenario" do
364
+ scan("Feature: Feature Text\nScenario: Reading a Scenario\n")
365
+ @listener.to_sexp.should == [
366
+ [:feature, "Feature", "Feature Text", "", 1],
367
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
368
+ [:eof]
369
+ ]
370
+ end
371
+ end
372
+
373
+ describe "A feature with two scenarios" do
374
+ it "should find the feature and two scenarios" do
375
+ scan("Feature: Feature Text\nScenario: Reading a Scenario\n Given a step\n\nScenario: A second scenario\n Given another step\n")
376
+ @listener.to_sexp.should == [
377
+ [:feature, "Feature", "Feature Text", "", 1],
378
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
379
+ [:step, "Given ", "a step", 3],
380
+ [:scenario, "Scenario", "A second scenario", "", 5],
381
+ [:step, "Given ", "another step", 6],
382
+ [:eof]
383
+ ]
384
+ end
385
+
386
+ it "should find the feature and two scenarios without indentation" do
387
+ scan("Feature: Feature Text\nScenario: Reading a Scenario\nGiven a step\nScenario: A second scenario\nGiven another step\n")
388
+ @listener.to_sexp.should == [
389
+ [:feature, "Feature", "Feature Text", "", 1],
390
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
391
+ [:step, "Given ", "a step", 3],
392
+ [:scenario, "Scenario", "A second scenario", "", 4],
393
+ [:step, "Given ", "another step", 5],
394
+ [:eof]
395
+ ]
396
+ end
397
+ end
398
+
399
+ describe "A simple feature with comments" do
400
+ it "should find the feature, scenarios, steps, and comments in the proper order" do
401
+ scan_file("simple_with_comments.feature")
402
+ @listener.to_sexp.should == [
403
+ [:comment, "# Here is a comment", 1],
404
+ [:feature, "Feature", "Feature Text", "", 2],
405
+ [:comment, "# Here is another # comment", 3],
406
+ [:scenario, "Scenario", "Reading a Scenario", "", 4],
407
+ [:comment, "# Here is a third comment", 5],
408
+ [:step, "Given ", "there is a step", 6],
409
+ [:comment, "# Here is a fourth comment", 7],
410
+ [:eof]
411
+ ]
412
+ end
413
+
414
+ it "should support comments in tables" do
415
+ scan_file("comments_in_table.feature")
416
+ @listener.to_sexp.should == [
417
+ [:feature, "Feature", "x", "", 1],
418
+ [:scenario_outline, "Scenario Outline", "x", "", 3],
419
+ [:step, "Then ", "x is <state>", 4],
420
+ [:examples, "Examples", "", "", 6],
421
+ [:row, ["state"], 7],
422
+ [:comment, "# comment", 8],
423
+ [:row, ["1"], 9],
424
+ [:eof]
425
+ ]
426
+ end
427
+ end
428
+
429
+ describe "A feature with tags everywhere" do
430
+ it "should find the feature, scenario, step, and tags in the proper order" do
431
+ scan_file("simple_with_tags.feature")
432
+ @listener.to_sexp.should == [
433
+ [:comment, "# FC", 1],
434
+ [:tag, "@ft",2],
435
+ [:feature, "Feature", "hi", "", 3],
436
+ [:tag, "@st1", 5],
437
+ [:tag, "@st2", 5],
438
+ [:scenario, "Scenario", "First", "", 6],
439
+ [:step, "Given ", "Pepper", 7],
440
+ [:tag, "@st3", 9],
441
+ [:tag, "@st4", 10],
442
+ [:tag, "@ST5", 10],
443
+ [:tag, "@#^%&ST6**!", 10],
444
+ [:scenario, "Scenario", "Second", "", 11],
445
+ [:eof]
446
+ ]
447
+ end
448
+ end
449
+
450
+ describe "Comment or tag between Feature elements where previous narrative starts with same letter as a keyword" do
451
+ it "should lex this feature properly" do
452
+ scan_file("1.feature")
453
+ @listener.to_sexp.should == [
454
+ [:feature, "Feature", "Logging in", "So that I can be myself", 1],
455
+ [:comment, "# Comment", 3],
456
+ [:scenario, "Scenario", "Anonymous user can get a login form.", "Scenery here", 4],
457
+ [:tag, "@tag", 7],
458
+ [:scenario, "Scenario", "Another one", "", 8],
459
+ [:eof]
460
+ ]
461
+ end
462
+ end
463
+
464
+ describe "A complex feature with tags, comments, multiple scenarios, and multiple steps and tables" do
465
+ it "should find things in the right order" do
466
+ scan_file("complex.feature")
467
+ @listener.to_sexp.should == [
468
+ [:comment, "#Comment on line 1", 1],
469
+ [:comment, "#Comment on line 2", 2],
470
+ [:tag, "@tag1", 3],
471
+ [:tag, "@tag2", 3],
472
+ [:feature, "Feature", "Feature Text", "In order to test multiline forms\nAs a ragel writer\nI need to check for complex combinations", 4],
473
+ [:comment, "#Comment on line 9", 9],
474
+ [:comment, "#Comment on line 11", 11],
475
+ [:background, "Background", "", "", 13],
476
+ [:step, "Given ", "this is a background step", 14],
477
+ [:step, "And ", "this is another one", 15],
478
+ [:tag, "@tag3", 17],
479
+ [:tag, "@tag4", 17],
480
+ [:scenario, "Scenario", "Reading a Scenario", "", 18],
481
+ [:step, "Given ", "there is a step", 19],
482
+ [:step, "But ", "not another step", 20],
483
+ [:tag, "@tag3", 22],
484
+ [:scenario, "Scenario", "Reading a second scenario", "With two lines of text", 23],
485
+ [:comment, "#Comment on line 24", 25],
486
+ [:step, "Given ", "a third step with a table", 26],
487
+ [:row, %w{a b}, 27],
488
+ [:row, %w{c d}, 28],
489
+ [:row, %w{e f}, 29],
490
+ [:step, "And ", "I am still testing things", 30],
491
+ [:row, %w{g h}, 31],
492
+ [:row, %w{e r}, 32],
493
+ [:row, %w{k i}, 33],
494
+ [:row, ['n', ''], 34],
495
+ [:step, "And ", "I am done testing these tables", 35],
496
+ [:comment, "#Comment on line 29", 36],
497
+ [:step, "Then ", "I am happy", 37],
498
+ [:scenario, "Scenario", "Hammerzeit", "", 39],
499
+ [:step, "Given ", "All work and no play", 40],
500
+ [:py_string, "Makes Homer something something\nAnd something else", 41 ],
501
+ [:step, "Then ", "crazy", 45],
502
+ [:eof]
503
+ ]
504
+ end
505
+ end
506
+
507
+ describe "Windows stuff" do
508
+ it "should find things in the right order for CRLF features" do
509
+ scan_file("dos_line_endings.feature")
510
+ @listener.to_sexp.should == [
511
+ [:comment, "#Comment on line 1", 1],
512
+ [:comment, "#Comment on line 2", 2],
513
+ [:tag, "@tag1", 3],
514
+ [:tag, "@tag2", 3],
515
+ [:feature, "Feature", "Feature Text", "In order to test multiline forms\r\nAs a ragel writer\r\nI need to check for complex combinations", 4],
516
+ [:comment, "#Comment on line 9", 9],
517
+ [:comment, "#Comment on line 11", 11],
518
+ [:background, "Background", "", "", 13],
519
+ [:step, "Given ", "this is a background step", 14],
520
+ [:step, "And ", "this is another one", 15],
521
+ [:tag, "@tag3", 17],
522
+ [:tag, "@tag4", 17],
523
+ [:scenario, "Scenario", "Reading a Scenario", "", 18],
524
+ [:step, "Given ", "there is a step", 19],
525
+ [:step, "But ", "not another step", 20],
526
+ [:tag, "@tag3", 22],
527
+ [:scenario, "Scenario", "Reading a second scenario", "With two lines of text", 23],
528
+ [:comment, "#Comment on line 24", 25],
529
+ [:step, "Given ", "a third step with a table", 26],
530
+ [:row, %w{a b}, 27],
531
+ [:row, %w{c d}, 28],
532
+ [:row, %w{e f}, 29],
533
+ [:step, "And ", "I am still testing things", 30],
534
+ [:row, %w{g h}, 31],
535
+ [:row, %w{e r}, 32],
536
+ [:row, %w{k i}, 33],
537
+ [:row, ['n', ''], 34],
538
+ [:step, "And ", "I am done testing these tables", 35],
539
+ [:comment, "#Comment on line 29", 36],
540
+ [:step, "Then ", "I am happy", 37],
541
+ [:scenario, "Scenario", "Hammerzeit", "", 39],
542
+ [:step, "Given ", "All work and no play", 40],
543
+ [:py_string, "Makes Homer something something\r\nAnd something else", 41],
544
+ [:step, "Then ", "crazy", 45],
545
+ [:eof]
546
+ ]
547
+ end
548
+
549
+ it "should cope with the retarded BOM that many Windows editors insert at the beginning of a file" do
550
+ scan_file("with_bom.feature")
551
+ @listener.to_sexp.should == [
552
+ [:feature, "Feature", "Feature Text", "", 1],
553
+ [:scenario, "Scenario", "Reading a Scenario", "", 2],
554
+ [:step, "Given ", "there is a step", 3],
555
+ [:eof]
556
+ ]
557
+ end
558
+ end
559
+
560
+ describe "errors" do
561
+ it "should raise a Lexing error if an unparseable token is found" do
562
+ ["Some text\nFeature: Hi",
563
+ "Feature: Hi\nBackground:\nGiven something\nScenario A scenario",
564
+ "Scenario: My scenario\nGiven foo\nAand bar\nScenario: another one\nGiven blah"].each do |text|
565
+ lambda { scan(text) }.should raise_error(/Lexing error on line/)
566
+ end
567
+ end
568
+
569
+ it "should include the line number and context of the error" do
570
+ lambda {
571
+ scan("Feature: hello\nScenario: My scenario\nGiven foo\nAand blah\nHmmm wrong\nThen something something")
572
+ }.should raise_error(/Lexing error on line 4/)
573
+ end
574
+
575
+ it "Feature keyword should terminate narratives for multiline capable tokens" do
576
+ scan("Feature:\nBackground:\nFeature:\nScenario Outline:\nFeature:\nScenario:\nFeature:\nExamples:\nFeature:\n")
577
+ @listener.to_sexp.should == [
578
+ [:feature, "Feature", "", "", 1],
579
+ [:background, "Background", "", "", 2],
580
+ [:feature, "Feature", "", "", 3],
581
+ [:scenario_outline, "Scenario Outline", "", "", 4],
582
+ [:feature, "Feature", "", "", 5],
583
+ [:scenario, "Scenario", "", "", 6],
584
+ [:feature, "Feature", "", "", 7],
585
+ [:examples, "Examples", "","", 8],
586
+ [:feature, "Feature", "", "", 9],
587
+ [:eof]
588
+ ]
589
+ end
590
+ end
591
+ end
592
+ end
593
+ end