aslakhellesoy-gherkin 0.0.1
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.
- data/.document +5 -0
- data/.gitignore +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +37 -0
- data/Rakefile +25 -0
- data/VERSION.yml +4 -0
- data/ext/gherkin/extconf.rb +6 -0
- data/gherkin.gemspec +82 -0
- data/lib/gherkin.rb +1 -0
- data/lib/gherkin/i18n.yml +561 -0
- data/lib/gherkin/parser.rb +11 -0
- data/lib/gherkin/parser/.preserve +0 -0
- data/ragel/feature.rb.rl.erb +117 -0
- data/ragel/feature_common.rl.erb +37 -0
- data/ragel/misc.c.rl +4 -0
- data/ragel/misc.rb.rl +52 -0
- data/ragel/table.rb.rl +54 -0
- data/ragel/table_common.rl +12 -0
- data/spec/gherkin/feature_spec.rb +439 -0
- data/spec/gherkin/gherkin_parser/complex.feature +23 -0
- data/spec/gherkin/gherkin_parser/i18n_no.feature +6 -0
- data/spec/gherkin/gherkin_parser/simple.feature +3 -0
- data/spec/gherkin/gherkin_parser/simple_with_comments.feature +7 -0
- data/spec/gherkin/gherkin_parser/simple_with_tags.feature +5 -0
- data/spec/gherkin/i18n_spec.rb +28 -0
- data/spec/gherkin/multiline_step_args_spec.rb +98 -0
- data/spec/gherkin/sexp_recorder.rb +15 -0
- data/spec/gherkin/table_spec.rb +83 -0
- data/spec/gherkin/tags_spec.rb +57 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/ext.rake +35 -0
- data/tasks/ragel.rake +81 -0
- data/tasks/rdoc.rake +14 -0
- data/tasks/rspec.rake +16 -0
- metadata +102 -0
File without changes
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class Feature
|
4
|
+
%%{
|
5
|
+
machine feature;
|
6
|
+
|
7
|
+
action begin_content {
|
8
|
+
@content_start = p
|
9
|
+
}
|
10
|
+
|
11
|
+
action store_feature_content {
|
12
|
+
if !@backup or (p==eof)
|
13
|
+
con = data[@content_start...p].pack("c*")
|
14
|
+
else
|
15
|
+
con = data[@content_start...@backup].pack("c*")
|
16
|
+
end
|
17
|
+
con.strip!
|
18
|
+
@listener.feature(@keyword, con, @current_line)
|
19
|
+
if @backup
|
20
|
+
p = @backup-1
|
21
|
+
end
|
22
|
+
@backup = nil
|
23
|
+
}
|
24
|
+
|
25
|
+
action store_background_content {
|
26
|
+
if !@backup or (p==eof)
|
27
|
+
con = data[@content_start...p].pack("c*")
|
28
|
+
else
|
29
|
+
con = data[@content_start...@backup].pack("c*")
|
30
|
+
end
|
31
|
+
@listener.background(@keyword, multiline_strip(con), @current_line)
|
32
|
+
if @backup
|
33
|
+
p = @backup-1
|
34
|
+
end
|
35
|
+
@backup = nil
|
36
|
+
}
|
37
|
+
|
38
|
+
action store_scenario_content {
|
39
|
+
if !@backup or (p==eof)
|
40
|
+
con = data[@content_start...p].pack("c*")
|
41
|
+
else
|
42
|
+
con = data[@content_start...@backup].pack("c*")
|
43
|
+
end
|
44
|
+
@listener.scenario(@keyword, multiline_strip(con), @current_line)
|
45
|
+
if @backup
|
46
|
+
p = @backup-1
|
47
|
+
end
|
48
|
+
@backup = nil
|
49
|
+
}
|
50
|
+
|
51
|
+
action store_step_content {
|
52
|
+
con = data[@content_start...p].pack("c*")
|
53
|
+
con.strip!
|
54
|
+
@listener.step(@keyword, con, @current_line)
|
55
|
+
}
|
56
|
+
|
57
|
+
action store_comment_content {
|
58
|
+
con = data[@content_start...p].pack("c*")
|
59
|
+
con.strip!
|
60
|
+
@listener.comment(con, @line_number)
|
61
|
+
}
|
62
|
+
|
63
|
+
action store_tag_content {
|
64
|
+
con = data[@content_start...p].pack("c*")
|
65
|
+
con.strip!
|
66
|
+
@listener.tag(con, @current_line)
|
67
|
+
}
|
68
|
+
|
69
|
+
action inc_line_number {
|
70
|
+
@line_number += 1
|
71
|
+
}
|
72
|
+
|
73
|
+
action current_line {
|
74
|
+
@current_line = @line_number
|
75
|
+
}
|
76
|
+
|
77
|
+
action start_keyword {
|
78
|
+
@keyword_start ||= p
|
79
|
+
}
|
80
|
+
|
81
|
+
action end_keyword {
|
82
|
+
@keyword = data[@keyword_start...p].pack("c*").sub(/:$/,'').strip
|
83
|
+
@keyword_start = nil
|
84
|
+
}
|
85
|
+
|
86
|
+
action backup {
|
87
|
+
@backup = p
|
88
|
+
}
|
89
|
+
|
90
|
+
action end_table {
|
91
|
+
table_to_parse = '|' + data[@content_start...p].pack("c*").strip
|
92
|
+
Gherkin::Parser::Table.new(@listener, @current_line).scan(table_to_parse)
|
93
|
+
p = p-1
|
94
|
+
}
|
95
|
+
|
96
|
+
include feature_common "feature_common.<%= lang %>.rl";
|
97
|
+
}%%
|
98
|
+
|
99
|
+
def initialize(listener)
|
100
|
+
@listener = listener
|
101
|
+
%% write data;
|
102
|
+
end
|
103
|
+
|
104
|
+
def scan(data)
|
105
|
+
data = data.unpack("c*") if data.is_a?(String)
|
106
|
+
@line_number = 1
|
107
|
+
eof = data.size
|
108
|
+
%% write init;
|
109
|
+
%% write exec;
|
110
|
+
end
|
111
|
+
|
112
|
+
def multiline_strip(text)
|
113
|
+
text.split("\n").map{|s| s.strip!}.join("\n")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
%%{
|
2
|
+
machine feature_common;
|
3
|
+
|
4
|
+
# Language specific
|
5
|
+
FEATURE = '<%= i18n['feature'] %>:' >start_keyword %end_keyword;
|
6
|
+
BACKGROUND = '<%= i18n['background'] %>:' >start_keyword %end_keyword;
|
7
|
+
SCENARIO = '<%= i18n['scenario'] %>:' >start_keyword %end_keyword;
|
8
|
+
STEP = ('<%= i18n['given'] %> ' | '<%= i18n['when'] %> ' | '<%= i18n['and'] %> ' | '<%= i18n['then'] %> ' | '<%= i18n['but'] %> ') >start_keyword %end_keyword;
|
9
|
+
|
10
|
+
EOL = ('\r'? '\n') @inc_line_number;
|
11
|
+
BAR = '|' >start_keyword %end_keyword;
|
12
|
+
|
13
|
+
# Terminators
|
14
|
+
EndFeatureHeading = EOL+ space* (BACKGROUND | SCENARIO | '@' | '#');
|
15
|
+
EndScenarioHeading = EOL+ space* ( SCENARIO | STEP | '@' | '#' );
|
16
|
+
EndBackgroundHeading = EOL+ space* ( SCENARIO | STEP | '@' | '#' );
|
17
|
+
StartTable = space* '|';
|
18
|
+
EndTable = EOL space* ^('|' | space);
|
19
|
+
|
20
|
+
FeatureHeading = space* FEATURE %begin_content %current_line ^EndFeatureHeading* %/store_feature_content :>> EndFeatureHeading >backup @store_feature_content;
|
21
|
+
BackgroundHeading = space* BACKGROUND %begin_content %current_line ^EndBackgroundHeading* %/store_background_content :>> EndBackgroundHeading >backup @store_background_content;
|
22
|
+
ScenarioHeading = space* SCENARIO %begin_content %current_line ^EndScenarioHeading* %/store_scenario_content :>> EndScenarioHeading >backup @store_scenario_content;
|
23
|
+
|
24
|
+
Step = space* STEP %begin_content %current_line ^EOL+ %store_step_content %/store_step_content EOL+;
|
25
|
+
Comment = space* '#' >begin_content ^EOL+ %store_comment_content %/store_comment_content EOL+;
|
26
|
+
Tag = ( '@' [^@\r\n\t ]+ >begin_content ) %store_tag_content;
|
27
|
+
Tags = space* (Tag @current_line space*)+ EOL+;
|
28
|
+
Table = StartTable %begin_content %current_line any+ %/end_table :>> EndTable >backup @end_table;
|
29
|
+
|
30
|
+
MultilineStep = Step Table?;
|
31
|
+
Scenario = ScenarioHeading (Comment | MultilineStep)*;
|
32
|
+
Background = BackgroundHeading (Comment | MultilineStep)*;
|
33
|
+
|
34
|
+
Feature = (Tags | Comment)* FeatureHeading (Tags | Comment)* Background? ((Tags | Comment)* Scenario)*;
|
35
|
+
|
36
|
+
main := Feature;
|
37
|
+
}%%
|
data/ragel/misc.c.rl
ADDED
data/ragel/misc.rb.rl
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class Misc
|
4
|
+
%%{
|
5
|
+
machine misc;
|
6
|
+
|
7
|
+
action start {
|
8
|
+
start_col = p - @last_newline
|
9
|
+
start = p + 4
|
10
|
+
}
|
11
|
+
|
12
|
+
action start_line {
|
13
|
+
line_col = p - @last_newline
|
14
|
+
line_start = p
|
15
|
+
}
|
16
|
+
|
17
|
+
action end_line {
|
18
|
+
line = data[line_start...p].pack("U*")
|
19
|
+
offset = line_col - start_col
|
20
|
+
@lines << (offset >= 0 ? line.gsub(/^/, ' ' * offset) : line)
|
21
|
+
}
|
22
|
+
|
23
|
+
newline = ('\r'? '\n') @{ @last_newline = p + 1} ;
|
24
|
+
|
25
|
+
PyStringStart = '"""' >start space* newline ;
|
26
|
+
PyStringEnd = '"""' ;
|
27
|
+
PyStringLine = space* ^newline* >start_line %end_line newline;
|
28
|
+
|
29
|
+
PyString = PyStringStart PyStringLine* PyStringEnd ;
|
30
|
+
|
31
|
+
main := space* PyString ;
|
32
|
+
}%%
|
33
|
+
|
34
|
+
def initialize(listener)
|
35
|
+
@listener = listener
|
36
|
+
@last_newline = 0
|
37
|
+
@lines = []
|
38
|
+
%% write data;
|
39
|
+
end
|
40
|
+
|
41
|
+
def scan(data)
|
42
|
+
data = data.unpack("U*") if data.is_a?(String)
|
43
|
+
eof = data.length
|
44
|
+
|
45
|
+
%% write init;
|
46
|
+
%% write exec;
|
47
|
+
|
48
|
+
@listener.pystring(@lines.join("\n"))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/ragel/table.rb.rl
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class Table
|
4
|
+
%%{
|
5
|
+
machine table;
|
6
|
+
|
7
|
+
action initialize {
|
8
|
+
current_row = []
|
9
|
+
}
|
10
|
+
|
11
|
+
action begin_content {
|
12
|
+
@content_start = p
|
13
|
+
}
|
14
|
+
|
15
|
+
action store_row {
|
16
|
+
@rows << current_row
|
17
|
+
}
|
18
|
+
|
19
|
+
action store_cell_content {
|
20
|
+
con = data[@content_start...p].pack("U*")
|
21
|
+
con.strip!
|
22
|
+
current_row << (con.empty? ? nil : con)
|
23
|
+
}
|
24
|
+
|
25
|
+
action no_content {
|
26
|
+
current_row << nil
|
27
|
+
}
|
28
|
+
|
29
|
+
include table_common "table_common.rl";
|
30
|
+
}%%
|
31
|
+
|
32
|
+
def initialize(listener,line=nil)
|
33
|
+
@line = line
|
34
|
+
@listener = listener
|
35
|
+
%% write data;
|
36
|
+
end
|
37
|
+
|
38
|
+
def scan(data)
|
39
|
+
@rows = []
|
40
|
+
data = data.unpack("U*") if data.is_a?(String)
|
41
|
+
eof = data.size
|
42
|
+
|
43
|
+
%% write init;
|
44
|
+
%% write exec;
|
45
|
+
|
46
|
+
if @line
|
47
|
+
@listener.table(@rows, @line)
|
48
|
+
else
|
49
|
+
@listener.table(@rows)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
%%{
|
2
|
+
machine table_common;
|
3
|
+
|
4
|
+
EOL = '\r'? '\n';
|
5
|
+
BAR = '|';
|
6
|
+
cell_content = ^('|' | EOL);
|
7
|
+
|
8
|
+
cell = cell_content+ >begin_content BAR >store_cell_content | BAR >no_content;
|
9
|
+
table_row = space* BAR >initialize cell+ space* %/store_row space* :>> EOL;
|
10
|
+
table = table_row+ @store_row;
|
11
|
+
main := table;
|
12
|
+
}%%
|
@@ -0,0 +1,439 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
module Parser
|
6
|
+
describe "parsing" do
|
7
|
+
before do
|
8
|
+
@listener = Gherkin::SexpRecorder.new
|
9
|
+
@feature = Parser['en'].new(@listener)
|
10
|
+
end
|
11
|
+
|
12
|
+
def scan_file(file)
|
13
|
+
Feature.new(@listener).scan(File.new(File.dirname(__FILE__) + "/gherkin_parser/" + file).read)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "Comments" do
|
17
|
+
it "should parse a file with only a one line comment" do
|
18
|
+
@feature.scan("# My comment\nFeature: hi")
|
19
|
+
@listener.to_sexp.should == [
|
20
|
+
[:comment, "# My comment", 1],
|
21
|
+
[:feature, "Feature", "hi", 2],
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should parse a one line comment" do
|
26
|
+
@feature.scan("# My comment")
|
27
|
+
@listener.to_sexp.should == [[:comment, "# My comment", 1]]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should parse a file with only a multiline comment" do
|
31
|
+
@feature.scan("#Hello\n#World\nFeature: hi")
|
32
|
+
@listener.to_sexp.should == [
|
33
|
+
[:comment, "#Hello", 1],
|
34
|
+
[:comment, "#World", 2],
|
35
|
+
[:feature, "Feature", "hi", 3]
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should parse a file with only a multiline comment" do
|
40
|
+
pending("TODO: Do multiline comments need to be compressed into a single message?")
|
41
|
+
@feature.scan("# Hello\n# World\nFeature: hi")
|
42
|
+
@listener.to_sexp.should == [
|
43
|
+
[:comment, "# Hello\n# World", 1],
|
44
|
+
[:feature, "hi", 3]
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should parse a file with no comments" do
|
49
|
+
@feature.scan("Feature: hi\n")
|
50
|
+
@listener.to_sexp.should == [[:feature, "Feature", "hi", 1]]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should parse a file with only a multiline comment with newlines" do
|
54
|
+
pending("TODO: Do multiline comments need to be compressed into a single message?")
|
55
|
+
@feature.scan("# Hello\n\n# World\n")
|
56
|
+
@listener.to_sexp.should == [[:comment, "# Hello\n\n# World\n"]]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not consume comments as part of a multiline name" do
|
60
|
+
@feature.scan("Feature: hi\n Scenario: test\n\n#hello\n Scenario: another")
|
61
|
+
@listener.to_sexp.should == [
|
62
|
+
[:feature, "Feature", "hi", 1],
|
63
|
+
[:scenario, "Scenario", "test", 2],
|
64
|
+
[:comment, "#hello", 4],
|
65
|
+
[:scenario, "Scenario", "another", 5]
|
66
|
+
]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "Tags" do
|
71
|
+
it "should parse a file with tags on a feature" do
|
72
|
+
@feature.scan("# My comment\n@hello @world\nFeature: hi\n")
|
73
|
+
@listener.to_sexp.should == [
|
74
|
+
[:comment, "# My comment", 1],
|
75
|
+
[:tag, "hello", 2],
|
76
|
+
[:tag, "world", 2],
|
77
|
+
[:feature, "Feature", "hi", 3]
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not take the tags as part of a multiline name feature element" do
|
82
|
+
@feature.scan("Feature: hi\n Scenario: test\n\n@hello\n Scenario: another")
|
83
|
+
@listener.to_sexp.should == [
|
84
|
+
[:feature, "Feature", "hi", 1],
|
85
|
+
[:scenario, "Scenario", "test", 2],
|
86
|
+
[:tag, "hello", 4],
|
87
|
+
[:scenario, "Scenario", "another", 5]
|
88
|
+
]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should parse a file with tags on a scenario" do
|
92
|
+
@feature.scan(%{# FC
|
93
|
+
@ft
|
94
|
+
Feature: hi
|
95
|
+
|
96
|
+
@st1 @st2
|
97
|
+
Scenario: First
|
98
|
+
Given Pepper
|
99
|
+
|
100
|
+
@st3
|
101
|
+
@st4 @ST5 @#^%&ST6**!
|
102
|
+
Scenario: Second})
|
103
|
+
@listener.to_sexp.should == [
|
104
|
+
[:comment, "# FC", 1],
|
105
|
+
[:tag, "ft",2],
|
106
|
+
[:feature, "Feature", "hi", 3],
|
107
|
+
[:tag, "st1", 5],
|
108
|
+
[:tag, "st2", 5],
|
109
|
+
[:scenario, "Scenario", "First", 6],
|
110
|
+
[:step, "Given", "Pepper", 7],
|
111
|
+
[:tag, "st3", 9],
|
112
|
+
[:tag, "st4", 10],
|
113
|
+
[:tag, "ST5", 10],
|
114
|
+
[:tag, "#^%&ST6**!", 10],
|
115
|
+
[:scenario, "Scenario", "Second", 11]
|
116
|
+
]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "Background" do
|
121
|
+
it "should allow an empty background description" do
|
122
|
+
@feature.scan("Feature: Hi\nBackground:\nGiven I am a step\n")
|
123
|
+
@listener.to_sexp.should == [
|
124
|
+
[:feature, "Feature", "Hi", 1],
|
125
|
+
[:background, "Background", "", 2],
|
126
|
+
[:step, "Given", "I am a step", 3]
|
127
|
+
]
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should allow multiline names ending at eof" do
|
131
|
+
@feature.scan("Feature: Feature Text\n Background: I have several\n Lines to look at\n None starting with Given")
|
132
|
+
@listener.to_sexp.should == [
|
133
|
+
[:feature, "Feature", "Feature Text", 1],
|
134
|
+
[:background, "Background", "I have several\nLines to look at\nNone starting with Given", 2]
|
135
|
+
]
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should have steps" do
|
139
|
+
@feature.scan("Feature: Hi\nBackground: Run this first\nGiven I am a step\n")
|
140
|
+
@listener.to_sexp.should == [
|
141
|
+
[:feature, "Feature", "Hi", 1],
|
142
|
+
[:background, "Background", "Run this first", 2],
|
143
|
+
[:step, "Given", "I am a step", 3]
|
144
|
+
]
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should find scenarios after background" do
|
148
|
+
@feature.scan("Feature: Hi\n#This needs to run first\nBackground: Run this first\nGiven I am a step\n\n Scenario: A Scenario\nGiven I am a step")
|
149
|
+
@listener.to_sexp.should == [
|
150
|
+
[:feature, "Feature", "Hi", 1],
|
151
|
+
[:comment, "#This needs to run first", 2],
|
152
|
+
[:background, "Background", "Run this first", 3],
|
153
|
+
[:step, "Given", "I am a step", 4],
|
154
|
+
[:scenario, "Scenario", "A Scenario", 6],
|
155
|
+
[:step, "Given", "I am a step", 7]
|
156
|
+
]
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should allow multiline names" do
|
160
|
+
@feature.scan(%{Feature: Hi
|
161
|
+
Background: It is my ambition to say
|
162
|
+
in ten sentences
|
163
|
+
what others say
|
164
|
+
in a whole book.
|
165
|
+
Given I am a step})
|
166
|
+
@listener.to_sexp.should == [
|
167
|
+
[:feature, "Feature", "Hi", 1],
|
168
|
+
[:background, "Background", "It is my ambition to say\nin ten sentences\nwhat others say\nin a whole book.",2],
|
169
|
+
[:step, "Given", "I am a step", 6]
|
170
|
+
]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "Scenarios" do
|
175
|
+
it "can be empty" do
|
176
|
+
@feature.scan("Feature: Hi\n\nScenario: Hello\n")
|
177
|
+
@listener.to_sexp.should == [
|
178
|
+
[:feature, "Feature", "Hi", 1],
|
179
|
+
[:scenario, "Scenario", "Hello", 3]
|
180
|
+
]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should allow whitespace lines after the Scenario line" do
|
184
|
+
@feature.scan(%{Feature: Foo
|
185
|
+
|
186
|
+
Scenario: bar
|
187
|
+
|
188
|
+
Given baz})
|
189
|
+
@listener.to_sexp.should == [
|
190
|
+
[:feature, "Feature", "Foo", 1],
|
191
|
+
[:scenario, "Scenario", "bar", 3],
|
192
|
+
[:step, "Given", "baz", 5]
|
193
|
+
]
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should have steps" do
|
197
|
+
@feature.scan("Feature: Hi\nScenario: Hello\nGiven I am a step\n")
|
198
|
+
@listener.to_sexp.should == [
|
199
|
+
[:feature, "Feature", "Hi", 1],
|
200
|
+
[:scenario, "Scenario", "Hello", 2],
|
201
|
+
[:step, "Given", "I am a step", 3]
|
202
|
+
]
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should have steps with inline table" do
|
206
|
+
@feature.scan(%{Feature: Hi
|
207
|
+
Scenario: Hello
|
208
|
+
Given I have a table
|
209
|
+
|a|b|
|
210
|
+
})
|
211
|
+
@listener.to_sexp.should == [
|
212
|
+
[:feature, "Feature", "Hi", 1],
|
213
|
+
[:scenario, "Scenario", "Hello", 2],
|
214
|
+
[:step, "Given", "I have a table", 3],
|
215
|
+
[:table, [['a','b']], 4]
|
216
|
+
]
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should allow multiple steps each with tables" do
|
220
|
+
@feature.scan(%{Feature: Hi
|
221
|
+
Scenario: Hello
|
222
|
+
Given I have a table
|
223
|
+
|a|b|
|
224
|
+
|c|d|
|
225
|
+
|e|f|
|
226
|
+
And I am still testing things
|
227
|
+
|g|h|
|
228
|
+
|e|r|
|
229
|
+
|k|i|
|
230
|
+
|n||
|
231
|
+
And I am done testing these tables
|
232
|
+
})
|
233
|
+
@listener.to_sexp.should == [
|
234
|
+
[:feature, "Feature", "Hi", 1],
|
235
|
+
[:scenario, "Scenario", "Hello", 2],
|
236
|
+
[:step, "Given", "I have a table", 3],
|
237
|
+
[:table, [['a','b'],['c','d'],['e','f']], 4],
|
238
|
+
[:step, "And", "I am still testing things", 7],
|
239
|
+
[:table, [['g','h'],['e','r'],['k','i'],['n',nil]], 8],
|
240
|
+
[:step, "And", "I am done testing these tables", 12],
|
241
|
+
]
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should have steps with inline py_string" do
|
245
|
+
pending
|
246
|
+
@feature.scan(%{Feature: Hi
|
247
|
+
Scenario: Hello
|
248
|
+
Given I have a string
|
249
|
+
|
250
|
+
|
251
|
+
"""
|
252
|
+
hello
|
253
|
+
world
|
254
|
+
"""
|
255
|
+
|
256
|
+
})
|
257
|
+
@listener.to_sexp.should == [
|
258
|
+
[:feature, "Feature", "Hi", 1],
|
259
|
+
[:scenario, "Scenario", "Hello", 2],
|
260
|
+
[:step, "Given", "I have a string", 3],
|
261
|
+
[:py_string, "hello\nworld"]
|
262
|
+
]
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should allow multiline names" do
|
266
|
+
@feature.scan(%{Feature: Hi
|
267
|
+
Scenario: It is my ambition to say
|
268
|
+
in ten sentences
|
269
|
+
what others say
|
270
|
+
in a whole book.
|
271
|
+
Given I am a step
|
272
|
+
|
273
|
+
})
|
274
|
+
@listener.to_sexp.should == [
|
275
|
+
[:feature, "Feature", "Hi", 1],
|
276
|
+
[:scenario, "Scenario", "It is my ambition to say\nin ten sentences\nwhat others say\nin a whole book.", 2],
|
277
|
+
[:step, "Given", "I am a step", 6]
|
278
|
+
]
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should allow multiline names ending at eof" do
|
282
|
+
@feature.scan("Feature: Feature Text\n And some more text\n\n Scenario: I have several\n Lines to look at\n None starting with Given")
|
283
|
+
@listener.to_sexp.should == [
|
284
|
+
[:feature, "Feature", "Feature Text\n And some more text", 1],
|
285
|
+
[:scenario, "Scenario", "I have several\nLines to look at\nNone starting with Given", 4]
|
286
|
+
]
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should ignore gherkin keywords which are parts of other words in the name" do
|
290
|
+
@feature.scan(%{Feature: Parser bug
|
291
|
+
Scenario: I have a Button
|
292
|
+
Buttons are great
|
293
|
+
Given I have it
|
294
|
+
})
|
295
|
+
@listener.to_sexp.should == [
|
296
|
+
[:feature, "Feature", "Parser bug", 1],
|
297
|
+
[:scenario, "Scenario", "I have a Button\nButtons are great", 2],
|
298
|
+
[:step, "Given", "I have it", 4]
|
299
|
+
]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
describe "A single feature, single scenario, single step" do
|
304
|
+
|
305
|
+
it "should find the feature, scenario, and step" do
|
306
|
+
@feature.scan("Feature: Feature Text\n Scenario: Reading a Scenario\n Given there is a step\n")
|
307
|
+
@listener.to_sexp.should == [
|
308
|
+
[:feature, "Feature", "Feature Text", 1],
|
309
|
+
[:scenario, "Scenario", "Reading a Scenario", 2],
|
310
|
+
[:step, "Given", "there is a step", 3]
|
311
|
+
]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
describe "A single feature, single scenario, three steps" do
|
316
|
+
|
317
|
+
it "should find the feature, scenario, and three steps" do
|
318
|
+
@feature.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")
|
319
|
+
@listener.to_sexp.should == [
|
320
|
+
[:feature, "Feature", "Feature Text", 1],
|
321
|
+
[:scenario, "Scenario", "Reading a Scenario", 2],
|
322
|
+
[:step, "Given", "there is a step", 3],
|
323
|
+
[:step, "And", "another step", 4],
|
324
|
+
[:step, "And", "a third step", 5]
|
325
|
+
]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
describe "A single feature with no scenario" do
|
330
|
+
it "should find the feature" do
|
331
|
+
@feature.scan("Feature: Feature Text\n")
|
332
|
+
@listener.to_sexp.should == [[:feature, "Feature", "Feature Text", 1]]
|
333
|
+
end
|
334
|
+
|
335
|
+
it "should parse a one line feature with no newline" do
|
336
|
+
@feature.scan("Feature: hi")
|
337
|
+
@listener.to_sexp.should == [[:feature, "Feature", "hi", 1]]
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
describe "A multi-line feature with no scenario" do
|
342
|
+
it "should find the feature" do
|
343
|
+
@feature.scan("Feature: Feature Text\n And some more text")
|
344
|
+
@listener.to_sexp.should == [[:feature, "Feature", "Feature Text\n And some more text", 1]]
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe "A feature with a scenario but no steps" do
|
349
|
+
it "should find the feature and scenario" do
|
350
|
+
@feature.scan("Feature: Feature Text\nScenario: Reading a Scenario\n")
|
351
|
+
@listener.to_sexp.should == [
|
352
|
+
[:feature, "Feature", "Feature Text", 1],
|
353
|
+
[:scenario, "Scenario", "Reading a Scenario", 2]
|
354
|
+
]
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
describe "A feature with two scenarios" do
|
359
|
+
it "should find the feature and two scenarios" do
|
360
|
+
@feature.scan("Feature: Feature Text\nScenario: Reading a Scenario\n Given a step\n\nScenario: A second scenario\n Given 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", 5],
|
366
|
+
[:step, "Given", "another step", 6]
|
367
|
+
]
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should find the feature and two scenarios without indentation" do
|
371
|
+
@feature.scan("Feature: Feature Text\nScenario: Reading a Scenario\nGiven a step\nScenario: A second scenario\nGiven another step\n")
|
372
|
+
@listener.to_sexp.should == [
|
373
|
+
[:feature, "Feature", "Feature Text", 1],
|
374
|
+
[:scenario, "Scenario", "Reading a Scenario", 2],
|
375
|
+
[:step, "Given", "a step", 3],
|
376
|
+
[:scenario, "Scenario", "A second scenario", 4],
|
377
|
+
[:step, "Given", "another step", 5]
|
378
|
+
]
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe "A simple feature with comments" do
|
383
|
+
it "should find the feature, scenarios, steps, and comments in the proper order" do
|
384
|
+
scan_file("simple_with_comments.feature")
|
385
|
+
@listener.to_sexp.should == [
|
386
|
+
[:comment, "# Here is a comment", 1],
|
387
|
+
[:feature, "Feature", "Feature Text", 2],
|
388
|
+
[:comment, "# Here is another # comment", 3],
|
389
|
+
[:scenario, "Scenario", "Reading a Scenario", 4],
|
390
|
+
[:comment, "# Here is a third comment", 5],
|
391
|
+
[:step, "Given", "there is a step", 6],
|
392
|
+
[:comment, "# Here is a fourth comment", 7]
|
393
|
+
]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe "A simple feature with tags" do
|
398
|
+
it "should find the feature, scenario, step, and tags in the proper order" do
|
399
|
+
scan_file("simple_with_tags.feature")
|
400
|
+
@listener.to_sexp.should == [
|
401
|
+
[:tag, "tag1", 1],
|
402
|
+
[:tag, "tag2", 1],
|
403
|
+
[:feature, "Feature", "Feature Text", 2],
|
404
|
+
[:tag, "tag3", 3],
|
405
|
+
[:tag, "tag4", 3],
|
406
|
+
[:scenario, "Scenario", "Reading a Scenario", 4],
|
407
|
+
[:step, "Given", "there is a step", 5]
|
408
|
+
]
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
describe "A complex feature with tags, comments, multiple scenarios, and multiple steps" do
|
413
|
+
it "should find things in the right order" do
|
414
|
+
scan_file("complex.feature")
|
415
|
+
@listener.to_sexp.should == [
|
416
|
+
[:comment, "#Comment on line 1", 1],
|
417
|
+
[:tag, "tag1", 2],
|
418
|
+
[:tag, "tag2", 2],
|
419
|
+
[:comment, "#Comment on line 3", 3],
|
420
|
+
[:feature, "Feature", "Feature Text\n In order to test multiline forms\n As a ragel writer\n I need to check for complex combinations", 4],
|
421
|
+
[:comment, "#Comment on line 9", 9],
|
422
|
+
[:comment, "#Comment on line 11", 11],
|
423
|
+
[:tag, "tag3", 13],
|
424
|
+
[:tag, "tag4", 13],
|
425
|
+
[:scenario, "Scenario", "Reading a Scenario", 14],
|
426
|
+
[:step, "Given", "there is a step", 15],
|
427
|
+
[:step, "But", "not another step", 16],
|
428
|
+
[:tag, "tag3", 18],
|
429
|
+
[:scenario, "Scenario", "Reading a second scenario", 19],
|
430
|
+
[:comment, "#Comment on line 20", 20],
|
431
|
+
[:step, "Given", "a third step", 21],
|
432
|
+
[:comment, "#Comment on line 22", 22],
|
433
|
+
[:step, "Then", "I am happy", 23]
|
434
|
+
]
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|