gherkin 1.0.30-universal-dotnet

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 (104) hide show
  1. data/.gitattributes +2 -0
  2. data/.gitignore +9 -0
  3. data/.mailmap +2 -0
  4. data/History.txt +187 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +59 -0
  7. data/Rakefile +58 -0
  8. data/VERSION.yml +5 -0
  9. data/bin/gherkin +5 -0
  10. data/cucumber.yml +3 -0
  11. data/features/escaped_pipes.feature +8 -0
  12. data/features/feature_parser.feature +226 -0
  13. data/features/native_lexer.feature +19 -0
  14. data/features/parser_with_native_lexer.feature +205 -0
  15. data/features/pretty_printer.feature +14 -0
  16. data/features/step_definitions/eyeball_steps.rb +3 -0
  17. data/features/step_definitions/gherkin_steps.rb +30 -0
  18. data/features/step_definitions/pretty_formatter_steps.rb +55 -0
  19. data/features/steps_parser.feature +46 -0
  20. data/features/support/env.rb +33 -0
  21. data/ikvm/.gitignore +3 -0
  22. data/java/.gitignore +2 -0
  23. data/java/src/main/java/gherkin/lexer/.gitignore +1 -0
  24. data/java/src/main/resources/gherkin/.gitignore +1 -0
  25. data/lib/.gitignore +4 -0
  26. data/lib/gherkin.rb +2 -0
  27. data/lib/gherkin/c_lexer.rb +17 -0
  28. data/lib/gherkin/cli/main.rb +33 -0
  29. data/lib/gherkin/formatter/argument.rb +27 -0
  30. data/lib/gherkin/formatter/colors.rb +119 -0
  31. data/lib/gherkin/formatter/escaping.rb +15 -0
  32. data/lib/gherkin/formatter/monochrome_format.rb +9 -0
  33. data/lib/gherkin/formatter/pretty_formatter.rb +168 -0
  34. data/lib/gherkin/i18n.rb +176 -0
  35. data/lib/gherkin/i18n.yml +588 -0
  36. data/lib/gherkin/i18n_lexer.rb +38 -0
  37. data/lib/gherkin/native.rb +7 -0
  38. data/lib/gherkin/native/ikvm.rb +55 -0
  39. data/lib/gherkin/native/java.rb +47 -0
  40. data/lib/gherkin/native/null.rb +9 -0
  41. data/lib/gherkin/parser/event.rb +45 -0
  42. data/lib/gherkin/parser/filter_listener.rb +199 -0
  43. data/lib/gherkin/parser/meta.txt +5 -0
  44. data/lib/gherkin/parser/parser.rb +142 -0
  45. data/lib/gherkin/parser/root.txt +11 -0
  46. data/lib/gherkin/parser/steps.txt +4 -0
  47. data/lib/gherkin/parser/tag_expression.rb +50 -0
  48. data/lib/gherkin/rb_lexer.rb +8 -0
  49. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  50. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  51. data/lib/gherkin/rubify.rb +18 -0
  52. data/lib/gherkin/tools.rb +8 -0
  53. data/lib/gherkin/tools/files.rb +35 -0
  54. data/lib/gherkin/tools/reformat.rb +19 -0
  55. data/lib/gherkin/tools/stats.rb +21 -0
  56. data/lib/gherkin/tools/stats_listener.rb +57 -0
  57. data/ragel/i18n/.gitignore +1 -0
  58. data/ragel/lexer.c.rl.erb +425 -0
  59. data/ragel/lexer.java.rl.erb +216 -0
  60. data/ragel/lexer.rb.rl.erb +173 -0
  61. data/ragel/lexer_common.rl.erb +50 -0
  62. data/spec/gherkin/c_lexer_spec.rb +21 -0
  63. data/spec/gherkin/csharp_lexer_spec.rb +20 -0
  64. data/spec/gherkin/fixtures/1.feature +8 -0
  65. data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
  66. data/spec/gherkin/fixtures/complex.feature +45 -0
  67. data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
  68. data/spec/gherkin/fixtures/i18n_fr.feature +14 -0
  69. data/spec/gherkin/fixtures/i18n_no.feature +7 -0
  70. data/spec/gherkin/fixtures/i18n_zh-CN.feature +9 -0
  71. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  72. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  73. data/spec/gherkin/fixtures/with_bom.feature +3 -0
  74. data/spec/gherkin/formatter/argument_spec.rb +28 -0
  75. data/spec/gherkin/formatter/colors_spec.rb +19 -0
  76. data/spec/gherkin/formatter/pretty_formatter_spec.rb +162 -0
  77. data/spec/gherkin/formatter/spaces.feature +9 -0
  78. data/spec/gherkin/formatter/tabs.feature +9 -0
  79. data/spec/gherkin/i18n_lexer_spec.rb +26 -0
  80. data/spec/gherkin/i18n_spec.rb +144 -0
  81. data/spec/gherkin/java_lexer_spec.rb +21 -0
  82. data/spec/gherkin/parser/filter_listener_spec.rb +390 -0
  83. data/spec/gherkin/parser/parser_spec.rb +50 -0
  84. data/spec/gherkin/parser/tag_expression_spec.rb +116 -0
  85. data/spec/gherkin/rb_lexer_spec.rb +19 -0
  86. data/spec/gherkin/sexp_recorder.rb +32 -0
  87. data/spec/gherkin/shared/lexer_spec.rb +550 -0
  88. data/spec/gherkin/shared/py_string_spec.rb +150 -0
  89. data/spec/gherkin/shared/row_spec.rb +104 -0
  90. data/spec/gherkin/shared/tags_spec.rb +50 -0
  91. data/spec/spec_helper.rb +87 -0
  92. data/tasks/bench.rake +188 -0
  93. data/tasks/bench/feature_builder.rb +49 -0
  94. data/tasks/bench/generated/.gitignore +1 -0
  95. data/tasks/bench/null_listener.rb +4 -0
  96. data/tasks/compile.rake +89 -0
  97. data/tasks/cucumber.rake +26 -0
  98. data/tasks/gems.rake +45 -0
  99. data/tasks/ikvm.rake +47 -0
  100. data/tasks/ragel_task.rb +70 -0
  101. data/tasks/rdoc.rake +12 -0
  102. data/tasks/release.rake +26 -0
  103. data/tasks/rspec.rake +15 -0
  104. metadata +257 -0
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+ require 'gherkin/parser/parser'
3
+
4
+ module Gherkin
5
+ module Parser
6
+ describe Parser do
7
+ before do
8
+ @listener = mock('listener')
9
+ @parser = Parser.new(@listener, true)
10
+ end
11
+
12
+ it "should delegate events to the listener" do
13
+ @listener.should_receive(:comment).with("# Content", 1)
14
+ @parser.comment("# Content", 1)
15
+ end
16
+
17
+ it "should raise helpful error messages by default" do
18
+ lambda {
19
+ @parser.scenario("Scenario", "My pet scenario", 12)
20
+ }.should raise_error(/Parse error on line 12\. Found scenario when expecting one of: comment, feature, tag\. \(Current state: root\)\.$/)
21
+ end
22
+
23
+ it "should allow empty files" do
24
+ @listener.should_receive(:eof)
25
+ @parser.eof
26
+ end
27
+
28
+ it "should delegate an error message when raise on error is false" do
29
+ @listener.should_receive(:syntax_error).with(sym(:root), sym(:background), a([:comment, :feature, :tag]), 1)
30
+ @parser = Parser.new(@listener, false)
31
+ @parser.background("Background", "Content", 1)
32
+ end
33
+
34
+ [true, false].each do |native|
35
+ it "should be reusable for several feature files (native lexer: #{native})" do
36
+ listener = mock('listener', :null_object => true)
37
+ parser = Parser.new(listener, true)
38
+ lexer = Gherkin::I18nLexer.new(parser, native)
39
+ feature = <<-EOF
40
+ Feature: foo
41
+ Scenario: bar
42
+ Given zap
43
+ EOF
44
+ lexer.scan(feature)
45
+ lexer.scan(feature)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,116 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+ require 'gherkin/parser/tag_expression'
3
+
4
+ module Gherkin
5
+ module Parser
6
+ describe TagExpression do
7
+ context "no tags" do
8
+ before(:each) do
9
+ @e = TagExpression.new([])
10
+ end
11
+
12
+ it "should match @foo" do
13
+ @e.eval(['@foo']).should == true
14
+ end
15
+
16
+ it "should match empty tags" do
17
+ @e.eval([]).should == true
18
+ end
19
+ end
20
+
21
+ context "@foo" do
22
+ before(:each) do
23
+ @e = TagExpression.new(['@foo'])
24
+ end
25
+
26
+ it "should match @foo" do
27
+ @e.eval(['@foo']).should == true
28
+ end
29
+
30
+ it "should not match @bar" do
31
+ @e.eval(['@bar']).should == false
32
+ end
33
+
34
+ it "should not match no tags" do
35
+ @e.eval([]).should == false
36
+ end
37
+ end
38
+
39
+ context "!@foo" do
40
+ before(:each) do
41
+ @e = TagExpression.new(['~@foo'])
42
+ end
43
+
44
+ it "should match @bar" do
45
+ @e.eval(['@bar']).should == true
46
+ end
47
+
48
+ it "should not match @foo" do
49
+ @e.eval(['@foo']).should == false
50
+ end
51
+ end
52
+
53
+ context "@foo || @bar" do
54
+ before(:each) do
55
+ @e = TagExpression.new(['@foo,@bar'])
56
+ end
57
+
58
+ it "should match @foo" do
59
+ @e.eval(['@foo']).should == true
60
+ end
61
+
62
+ it "should match @bar" do
63
+ @e.eval(['@bar']).should == true
64
+ end
65
+
66
+ it "should not match @zap" do
67
+ @e.eval(['@zap']).should == false
68
+ end
69
+ end
70
+
71
+ context "(@foo || @bar) && !@zap" do
72
+ before(:each) do
73
+ @e = TagExpression.new(['@foo,@bar', '~@zap'])
74
+ end
75
+
76
+ it "should match @foo" do
77
+ @e.eval(['@foo']).should == true
78
+ end
79
+
80
+ it "should not match @foo @zap" do
81
+ @e.eval(['@foo', '@zap']).should == false
82
+ end
83
+ end
84
+
85
+ context "(@foo:3 || !@bar:4) && @zap:5" do
86
+ before(:each) do
87
+ @e = TagExpression.new(['@foo:3,~@bar','@zap:5'])
88
+ end
89
+
90
+ it "should count tags for positive tags" do
91
+ rubify_hash(@e.limits).should == {'@foo' => 3, '@zap' => 5}
92
+ end
93
+
94
+ it "should match @foo @zap" do
95
+ @e.eval(['@foo', '@zap']).should == true
96
+ end
97
+ end
98
+
99
+ context "Parsing '@foo:3,~@bar', '@zap:5'" do
100
+ before(:each) do
101
+ @e = TagExpression.new([' @foo:3 , ~@bar ', ' @zap:5 '])
102
+ end
103
+
104
+ unless defined?(JRUBY_VERSION)
105
+ it "should split and trim (ruby implementation detail)" do
106
+ @e.__send__(:ruby_expression).should == "(!vars['@bar']||vars['@foo'])&&(vars['@zap'])"
107
+ end
108
+ end
109
+
110
+ it "should have limits" do
111
+ rubify_hash(@e.limits).should == {"@zap"=>5, "@foo"=>3}
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,19 @@
1
+ #encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
+ require 'gherkin/rb_lexer/en'
4
+
5
+ module Gherkin
6
+ module Lexer
7
+ describe "Ruby Lexer" do
8
+ before do
9
+ @listener = Gherkin::SexpRecorder.new
10
+ @lexer = Gherkin::RbLexer::En.new(@listener)
11
+ end
12
+
13
+ it_should_behave_like "a Gherkin lexer"
14
+ it_should_behave_like "a Gherkin lexer lexing tags"
15
+ it_should_behave_like "a Gherkin lexer lexing py_strings"
16
+ it_should_behave_like "a Gherkin lexer lexing rows"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ module Gherkin
2
+ class SexpRecorder
3
+ def initialize
4
+ @sexps = []
5
+ end
6
+
7
+ def method_missing(event, *args)
8
+ event = :scenario_outline if event == :scenarioOutline # Special Java Lexer handling
9
+ event = :py_string if event == :pyString # Special Java Lexer handling
10
+ event = :syntax_error if event == :syntaxError # Special Java Lexer handling
11
+ args[0] = args[0].to_a if event == :row # Special JRuby handling
12
+ @sexps << [event] + args
13
+ end
14
+
15
+ def to_sexp
16
+ @sexps
17
+ end
18
+
19
+ # Useful in IRB
20
+ def reset!
21
+ @sexps = []
22
+ end
23
+
24
+ def errors
25
+ @sexps.select { |sexp| sexp[0] == :syntax_error }
26
+ end
27
+
28
+ def line(number)
29
+ @sexps.find { |sexp| sexp.last == number }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,550 @@
1
+ #encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer" do
7
+
8
+ describe "Comments" do
9
+ it "should parse a one line comment" do
10
+ @lexer.scan("# My comment\n")
11
+ @listener.to_sexp.should == [
12
+ [:comment, "# My comment", 1],
13
+ [:eof]
14
+ ]
15
+ end
16
+
17
+ it "should parse a multiline comment" do
18
+ @lexer.scan("# Hello\n\n# World\n")
19
+ @listener.to_sexp.should == [
20
+ [:comment, "# Hello", 1],
21
+ [:comment, "# World", 3],
22
+ [:eof]
23
+ ]
24
+ end
25
+
26
+ it "should not consume comments as part of a multiline name" do
27
+ @lexer.scan("Scenario: test\n#hello\n Scenario: another")
28
+ @listener.to_sexp.should == [
29
+ [:scenario, "Scenario", "test", 1],
30
+ [:comment, "#hello", 2],
31
+ [:scenario, "Scenario", "another", 3],
32
+ [:eof]
33
+ ]
34
+ end
35
+
36
+ it "should allow empty comment lines" do
37
+ @lexer.scan("#\n # A comment\n #\n")
38
+ @listener.to_sexp.should == [
39
+ [:comment, "#", 1],
40
+ [:comment, "# A comment", 2],
41
+ [:comment, "#", 3],
42
+ [:eof]
43
+ ]
44
+ end
45
+
46
+ it "should not allow comments within the Feature description" do
47
+ lambda {
48
+ @lexer.scan("Feature: something\nAs a something\n# Comment\nI want something")
49
+ }.should raise_error(/Lexing error on line 4/)
50
+ end
51
+ end
52
+
53
+ describe "Tags" do
54
+ it "should not take the tags as part of a multiline name feature element" do
55
+ @lexer.scan("Feature: hi\n Scenario: test\n\n@hello\n Scenario: another")
56
+ @listener.to_sexp.should == [
57
+ [:feature, "Feature", "hi", 1],
58
+ [:scenario, "Scenario", "test", 2],
59
+ [:tag, "@hello", 4],
60
+ [:scenario, "Scenario", "another", 5],
61
+ [:eof]
62
+ ]
63
+ end
64
+ end
65
+
66
+ describe "Background" do
67
+ it "should allow an empty background description" do
68
+ @lexer.scan("Background:\nGiven I am a step\n")
69
+ @listener.to_sexp.should == [
70
+ [:background, "Background", "", 1],
71
+ [:step, "Given ", "I am a step", 2],
72
+ [:eof]
73
+ ]
74
+ end
75
+
76
+ it "should allow multiline names ending at eof" do
77
+ @lexer.scan("Background: I have several\n Lines to look at\n None starting with Given")
78
+ @listener.to_sexp.should == [
79
+ [:background, "Background", "I have several\nLines to look at\nNone starting with Given", 1],
80
+ [:eof]
81
+ ]
82
+ end
83
+
84
+ it "should allow multiline names" do
85
+ @lexer.scan(%{Feature: Hi
86
+ Background: It is my ambition to say
87
+ in ten sentences
88
+ what others say
89
+ in a whole book.
90
+ Given I am a step})
91
+ @listener.to_sexp.should == [
92
+ [:feature, "Feature", "Hi", 1],
93
+ [:background, "Background", "It is my ambition to say\nin ten sentences\nwhat others say\nin a whole book.",2],
94
+ [:step, "Given ", "I am a step", 6],
95
+ [:eof]
96
+ ]
97
+ end
98
+ end
99
+
100
+ describe "Scenarios" do
101
+ it "should be parsed" do
102
+ @lexer.scan("Scenario: Hello\n")
103
+ @listener.to_sexp.should == [
104
+ [:scenario, "Scenario", "Hello", 1],
105
+ [:eof]
106
+ ]
107
+ end
108
+
109
+ it "should allow whitespace lines after the Scenario line" do
110
+ @lexer.scan(%{Scenario: bar
111
+
112
+ Given baz
113
+ })
114
+ @listener.to_sexp.should == [
115
+ [:scenario, "Scenario", "bar", 1],
116
+ [:step, "Given ", "baz", 3],
117
+ [:eof]
118
+ ]
119
+ end
120
+
121
+ it "should allow multiline names" do
122
+ @lexer.scan(%{Scenario: It is my ambition to say
123
+ in ten sentences
124
+ what others say
125
+ in a whole book.
126
+ Given I am a step
127
+ })
128
+ @listener.to_sexp.should == [
129
+ [:scenario, "Scenario", "It is my ambition to say\nin ten sentences\nwhat others say\nin a whole book.", 1],
130
+ [:step, "Given ", "I am a step", 5],
131
+ [:eof]
132
+ ]
133
+ end
134
+
135
+ it "should allow multiline names ending at eof" do
136
+ @lexer.scan("Scenario: I have several\n Lines to look at\n None starting with Given")
137
+ @listener.to_sexp.should == [
138
+ [:scenario, "Scenario", "I have several\nLines to look at\nNone starting with Given", 1],
139
+ [:eof]
140
+ ]
141
+ end
142
+
143
+ it "should ignore gherkin keywords embedded in other words" do
144
+ @lexer.scan(%{Scenario: I have a Button
145
+ Buttons are great
146
+ Given I have some
147
+ But I might not because I am a Charles Dickens character
148
+ })
149
+ @listener.to_sexp.should == [
150
+ [:scenario, "Scenario", "I have a Button\nButtons are great", 1],
151
+ [:step, "Given ", "I have some", 3],
152
+ [:step, "But ", "I might not because I am a Charles Dickens character", 4],
153
+ [:eof]
154
+ ]
155
+ end
156
+
157
+ it "should allow step names in Scenario descriptions" do
158
+ @lexer.scan(%{Scenario: When I have when in scenario
159
+ I should be fine
160
+ Given I am a step
161
+ })
162
+ @listener.to_sexp.should == [
163
+ [:scenario, "Scenario", "When I have when in scenario\nI should be fine", 1],
164
+ [:step, "Given ", "I am a step", 3],
165
+ [:eof]
166
+ ]
167
+ end
168
+ end
169
+
170
+ describe "Scenario Outlines" do
171
+ it "should be parsed" do
172
+ @lexer.scan(%{Scenario Outline: Hello
173
+ Given a <what> cucumber
174
+ Examples:
175
+ |what|
176
+ |green|
177
+ })
178
+ @listener.to_sexp.should == [
179
+ [:scenario_outline, "Scenario Outline", "Hello", 1],
180
+ [:step, "Given ", "a <what> cucumber", 2],
181
+ [:examples, "Examples", "", 3],
182
+ [:row, ["what"], 4],
183
+ [:row, ["green"], 5],
184
+ [:eof]
185
+ ]
186
+ end
187
+
188
+ it "should parse with no steps or examples" do
189
+ @lexer.scan(%{Scenario Outline: Hello
190
+
191
+ Scenario: My Scenario
192
+ })
193
+ @listener.to_sexp.should == [
194
+ [:scenario_outline, "Scenario Outline", "Hello", 1],
195
+ [:scenario, "Scenario", "My Scenario", 3],
196
+ [:eof]
197
+ ]
198
+ end
199
+
200
+ it "should allow multiline names" do
201
+ @lexer.scan(%{Scenario Outline: It is my ambition to say
202
+ in ten sentences
203
+ what others say
204
+ in a whole book.
205
+ Given I am a step
206
+
207
+ })
208
+ @listener.to_sexp.should == [
209
+ [:scenario_outline, "Scenario Outline", "It is my ambition to say\nin ten sentences\nwhat others say\nin a whole book.", 1],
210
+ [:step, "Given ", "I am a step", 5],
211
+ [:eof]
212
+ ]
213
+ end
214
+ end
215
+
216
+ describe "Examples" do
217
+ it "should be parsed" do
218
+ @lexer.scan(%{Examples:
219
+ |x|y|
220
+ |5|6|
221
+ })
222
+ @listener.to_sexp.should == [
223
+ [:examples, "Examples", "", 1],
224
+ [:row, ["x","y"], 2],
225
+ [:row, ["5","6"], 3],
226
+ [:eof]
227
+ ]
228
+ end
229
+
230
+ it "should parse multiline example names" do
231
+ @lexer.scan(%{Examples: I'm a multiline name
232
+ and I'm ok
233
+ f'real
234
+ |x|
235
+ |5|
236
+ })
237
+ @listener.to_sexp.should == [
238
+ [:examples, "Examples", "I'm a multiline name\nand I'm ok\nf'real", 1],
239
+ [:row, ["x"], 4],
240
+ [:row, ["5"], 5],
241
+ [:eof]
242
+ ]
243
+ end
244
+ end
245
+
246
+ describe "Steps" do
247
+ it "should parse steps with inline table" do
248
+ @lexer.scan(%{Given I have a table
249
+ |a|b|
250
+ })
251
+ @listener.to_sexp.should == [
252
+ [:step, "Given ", "I have a table", 1],
253
+ [:row, ['a','b'], 2],
254
+ [:eof]
255
+ ]
256
+ end
257
+
258
+ it "should parse steps with inline py_string" do
259
+ @lexer.scan("Given I have a string\n\"\"\"\nhello\nworld\n\"\"\"")
260
+ @listener.to_sexp.should == [
261
+ [:step, "Given ", "I have a string", 1],
262
+ [:py_string, "hello\nworld", 2],
263
+ [:eof]
264
+ ]
265
+ end
266
+ end
267
+
268
+ describe "A single feature, single scenario, single step" do
269
+ it "should find the feature, scenario, and step" do
270
+ @lexer.scan("Feature: Feature Text\n Scenario: Reading a Scenario\n Given there is a step\n")
271
+ @listener.to_sexp.should == [
272
+ [:feature, "Feature", "Feature Text", 1],
273
+ [:scenario, "Scenario", "Reading a Scenario", 2],
274
+ [:step, "Given ", "there is a step", 3],
275
+ [:eof]
276
+ ]
277
+ end
278
+ end
279
+
280
+ describe "A feature ending in whitespace" do
281
+ it "should not raise an error when whitespace follows the Feature, Scenario, and Steps" do
282
+ @lexer.scan("Feature: Feature Text\n Scenario: Reading a Scenario\n Given there is a step\n ")
283
+ @listener.to_sexp.should == [
284
+ [:feature, "Feature", "Feature Text", 1],
285
+ [:scenario, "Scenario", "Reading a Scenario", 2],
286
+ [:step, "Given ", "there is a step", 3],
287
+ [:eof]
288
+ ]
289
+ end
290
+ end
291
+
292
+ describe "A single feature, single scenario, three steps" do
293
+
294
+ it "should find the feature, scenario, and three steps" do
295
+ @lexer.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")
296
+ @listener.to_sexp.should == [
297
+ [:feature, "Feature", "Feature Text", 1],
298
+ [:scenario, "Scenario", "Reading a Scenario", 2],
299
+ [:step, "Given ", "there is a step", 3],
300
+ [:step, "And ", "another step", 4],
301
+ [:step, "And ", "a third step", 5],
302
+ [:eof]
303
+ ]
304
+ end
305
+ end
306
+
307
+ describe "A single feature with no scenario" do
308
+ it "should find the feature" do
309
+ @lexer.scan("Feature: Feature Text\n")
310
+ @listener.to_sexp.should == [
311
+ [:feature, "Feature", "Feature Text", 1],
312
+ [:eof]
313
+ ]
314
+ end
315
+
316
+ it "should parse a one line feature with no newline" do
317
+ @lexer.scan("Feature: hi")
318
+ @listener.to_sexp.should == [
319
+ [:feature, "Feature", "hi", 1],
320
+ [:eof]
321
+ ]
322
+ end
323
+ end
324
+
325
+ describe "A multi-line feature with no scenario" do
326
+ it "should find the feature" do
327
+ @lexer.scan("Feature: Feature Text\n And some more text")
328
+ @listener.to_sexp.should == [
329
+ [:feature, "Feature", "Feature Text\nAnd some more text", 1],
330
+ [:eof]
331
+ ]
332
+ end
333
+ end
334
+
335
+ describe "A feature with a scenario but no steps" do
336
+ it "should find the feature and scenario" do
337
+ @lexer.scan("Feature: Feature Text\nScenario: Reading a Scenario\n")
338
+ @listener.to_sexp.should == [
339
+ [:feature, "Feature", "Feature Text", 1],
340
+ [:scenario, "Scenario", "Reading a Scenario", 2],
341
+ [:eof]
342
+ ]
343
+ end
344
+ end
345
+
346
+ describe "A feature with two scenarios" do
347
+ it "should find the feature and two scenarios" do
348
+ @lexer.scan("Feature: Feature Text\nScenario: Reading a Scenario\n Given a step\n\nScenario: A second scenario\n Given another step\n")
349
+ @listener.to_sexp.should == [
350
+ [:feature, "Feature", "Feature Text", 1],
351
+ [:scenario, "Scenario", "Reading a Scenario", 2],
352
+ [:step, "Given ", "a step", 3],
353
+ [:scenario, "Scenario", "A second scenario", 5],
354
+ [:step, "Given ", "another step", 6],
355
+ [:eof]
356
+ ]
357
+ end
358
+
359
+ it "should find the feature and two scenarios without indentation" do
360
+ @lexer.scan("Feature: Feature Text\nScenario: Reading a Scenario\nGiven a step\nScenario: A second scenario\nGiven another step\n")
361
+ @listener.to_sexp.should == [
362
+ [:feature, "Feature", "Feature Text", 1],
363
+ [:scenario, "Scenario", "Reading a Scenario", 2],
364
+ [:step, "Given ", "a step", 3],
365
+ [:scenario, "Scenario", "A second scenario", 4],
366
+ [:step, "Given ", "another step", 5],
367
+ [:eof]
368
+ ]
369
+ end
370
+ end
371
+
372
+ describe "A simple feature with comments" do
373
+ it "should find the feature, scenarios, steps, and comments in the proper order" do
374
+ scan_file("simple_with_comments.feature")
375
+ @listener.to_sexp.should == [
376
+ [:comment, "# Here is a comment", 1],
377
+ [:feature, "Feature", "Feature Text", 2],
378
+ [:comment, "# Here is another # comment", 3],
379
+ [:scenario, "Scenario", "Reading a Scenario", 4],
380
+ [:comment, "# Here is a third comment", 5],
381
+ [:step, "Given ", "there is a step", 6],
382
+ [:comment, "# Here is a fourth comment", 7],
383
+ [:eof]
384
+ ]
385
+ end
386
+
387
+ it "should support comments in tables" do
388
+ scan_file("comments_in_table.feature")
389
+ @listener.to_sexp.should == [
390
+ [:feature, "Feature", "x", 1],
391
+ [:scenario_outline, "Scenario Outline", "x", 3],
392
+ [:step, "Then ", "x is <state>", 4],
393
+ [:examples, "Examples", "", 6],
394
+ [:row, ["state"], 7],
395
+ [:comment, "# comment", 8],
396
+ [:row, ["1"], 9],
397
+ [:eof]
398
+ ]
399
+ end
400
+ end
401
+
402
+ describe "A feature with tags everywhere" do
403
+ it "should find the feature, scenario, step, and tags in the proper order" do
404
+ scan_file("simple_with_tags.feature")
405
+ @listener.to_sexp.should == [
406
+ [:comment, "# FC", 1],
407
+ [:tag, "@ft",2],
408
+ [:feature, "Feature", "hi", 3],
409
+ [:tag, "@st1", 5],
410
+ [:tag, "@st2", 5],
411
+ [:scenario, "Scenario", "First", 6],
412
+ [:step, "Given ", "Pepper", 7],
413
+ [:tag, "@st3", 9],
414
+ [:tag, "@st4", 10],
415
+ [:tag, "@ST5", 10],
416
+ [:tag, "@#^%&ST6**!", 10],
417
+ [:scenario, "Scenario", "Second", 11],
418
+ [:eof]
419
+ ]
420
+ end
421
+ end
422
+
423
+ describe "Comment or tag between Feature elements where previous narrative starts with same letter as a keyword" do
424
+ it "should lex this feature properly" do
425
+ scan_file("1.feature")
426
+ @listener.to_sexp.should == [
427
+ [:feature, "Feature", "Logging in\nSo that I can be myself", 1],
428
+ [:comment, "# Comment", 3],
429
+ [:scenario, "Scenario", "Anonymous user can get a login form.\nScenery here", 4],
430
+ [:tag, "@tag", 7],
431
+ [:scenario, "Scenario", "Another one", 8],
432
+ [:eof]
433
+ ]
434
+ end
435
+ end
436
+
437
+ describe "A complex feature with tags, comments, multiple scenarios, and multiple steps and tables" do
438
+ it "should find things in the right order" do
439
+ scan_file("complex.feature")
440
+ @listener.to_sexp.should == [
441
+ [:comment, "#Comment on line 1", 1],
442
+ [:comment, "#Comment on line 2", 2],
443
+ [:tag, "@tag1", 3],
444
+ [:tag, "@tag2", 3],
445
+ [:feature, "Feature", "Feature Text\nIn order to test multiline forms\nAs a ragel writer\nI need to check for complex combinations", 4],
446
+ [:comment, "#Comment on line 9", 9],
447
+ [:comment, "#Comment on line 11", 11],
448
+ [:background, "Background", "", 13],
449
+ [:step, "Given ", "this is a background step", 14],
450
+ [:step, "And ", "this is another one", 15],
451
+ [:tag, "@tag3", 17],
452
+ [:tag, "@tag4", 17],
453
+ [:scenario, "Scenario", "Reading a Scenario", 18],
454
+ [:step, "Given ", "there is a step", 19],
455
+ [:step, "But ", "not another step", 20],
456
+ [:tag, "@tag3", 22],
457
+ [:scenario, "Scenario", "Reading a second scenario\nWith two lines of text", 23],
458
+ [:comment, "#Comment on line 24", 25],
459
+ [:step, "Given ", "a third step with a table", 26],
460
+ [:row, %w{a b}, 27],
461
+ [:row, %w{c d}, 28],
462
+ [:row, %w{e f}, 29],
463
+ [:step, "And ", "I am still testing things", 30],
464
+ [:row, %w{g h}, 31],
465
+ [:row, %w{e r}, 32],
466
+ [:row, %w{k i}, 33],
467
+ [:row, ['n', ''], 34],
468
+ [:step, "And ", "I am done testing these tables", 35],
469
+ [:comment, "#Comment on line 29", 36],
470
+ [:step, "Then ", "I am happy", 37],
471
+ [:scenario, "Scenario", "Hammerzeit", 39],
472
+ [:step, "Given ", "All work and no play", 40],
473
+ [:py_string, "Makes Homer something something\nAnd something else", 41 ],
474
+ [:step, "Then ", "crazy", 45],
475
+ [:eof]
476
+ ]
477
+ end
478
+ end
479
+
480
+ describe "Windows stuff" do
481
+ it "should find things in the right order for CRLF features" do
482
+ scan_file("dos_line_endings.feature")
483
+ @listener.to_sexp.should == [
484
+ [:comment, "#Comment on line 1", 1],
485
+ [:comment, "#Comment on line 2", 2],
486
+ [:tag, "@tag1", 3],
487
+ [:tag, "@tag2", 3],
488
+ [:feature, "Feature", "Feature Text\r\nIn order to test multiline forms\r\nAs a ragel writer\r\nI need to check for complex combinations", 4],
489
+ [:comment, "#Comment on line 9", 9],
490
+ [:comment, "#Comment on line 11", 11],
491
+ [:background, "Background", "", 13],
492
+ [:step, "Given ", "this is a background step", 14],
493
+ [:step, "And ", "this is another one", 15],
494
+ [:tag, "@tag3", 17],
495
+ [:tag, "@tag4", 17],
496
+ [:scenario, "Scenario", "Reading a Scenario", 18],
497
+ [:step, "Given ", "there is a step", 19],
498
+ [:step, "But ", "not another step", 20],
499
+ [:tag, "@tag3", 22],
500
+ [:scenario, "Scenario", "Reading a second scenario\r\nWith two lines of text", 23],
501
+ [:comment, "#Comment on line 24", 25],
502
+ [:step, "Given ", "a third step with a table", 26],
503
+ [:row, %w{a b}, 27],
504
+ [:row, %w{c d}, 28],
505
+ [:row, %w{e f}, 29],
506
+ [:step, "And ", "I am still testing things", 30],
507
+ [:row, %w{g h}, 31],
508
+ [:row, %w{e r}, 32],
509
+ [:row, %w{k i}, 33],
510
+ [:row, ['n', ''], 34],
511
+ [:step, "And ", "I am done testing these tables", 35],
512
+ [:comment, "#Comment on line 29", 36],
513
+ [:step, "Then ", "I am happy", 37],
514
+ [:scenario, "Scenario", "Hammerzeit", 39],
515
+ [:step, "Given ", "All work and no play", 40],
516
+ [:py_string, "Makes Homer something something\r\nAnd something else", 41],
517
+ [:step, "Then ", "crazy", 45],
518
+ [:eof]
519
+ ]
520
+ end
521
+
522
+ it "should cope with the retarded BOM that many Windows editors insert at the beginning of a file" do
523
+ scan_file("with_bom.feature")
524
+ @listener.to_sexp.should == [
525
+ [:feature, "Feature", "Feature Text", 1],
526
+ [:scenario, "Scenario", "Reading a Scenario", 2],
527
+ [:step, "Given ", "there is a step", 3],
528
+ [:eof]
529
+ ]
530
+ end
531
+ end
532
+
533
+ describe "errors" do
534
+ it "should raise a Lexing error if an unparseable token is found" do
535
+ ["Some text\nFeature: Hi",
536
+ "Feature: Hi\nBackground:\nGiven something\nScenario A scenario",
537
+ "Scenario: My scenario\nGiven foo\nAand bar\nScenario: another one\nGiven blah"].each do |text|
538
+ lambda { @lexer.scan(text) }.should raise_error(/Lexing error on line/)
539
+ end
540
+ end
541
+
542
+ it "should include the line number and context of the error" do
543
+ lambda {
544
+ @lexer.scan("Feature: hello\nScenario: My scenario\nGiven foo\nAand blah\nHmmm wrong\nThen something something")
545
+ }.should raise_error(/Lexing error on line 4/)
546
+ end
547
+ end
548
+ end
549
+ end
550
+ end