cucumber-cucumber-expressions 8.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +5 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  4. data/.rspec +1 -0
  5. data/.rsync +4 -0
  6. data/.subrepo +1 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +21 -0
  9. data/Makefile +1 -0
  10. data/README.md +5 -0
  11. data/Rakefile +27 -0
  12. data/cucumber-cucumber-expressions.gemspec +33 -0
  13. data/default.mk +70 -0
  14. data/examples.txt +31 -0
  15. data/lib/cucumber/cucumber_expressions/argument.rb +37 -0
  16. data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +49 -0
  17. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +119 -0
  18. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +105 -0
  19. data/lib/cucumber/cucumber_expressions/errors.rb +40 -0
  20. data/lib/cucumber/cucumber_expressions/generated_expression.rb +31 -0
  21. data/lib/cucumber/cucumber_expressions/group.rb +18 -0
  22. data/lib/cucumber/cucumber_expressions/group_builder.rb +42 -0
  23. data/lib/cucumber/cucumber_expressions/parameter_type.rb +81 -0
  24. data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +59 -0
  25. data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +70 -0
  26. data/lib/cucumber/cucumber_expressions/regular_expression.rb +48 -0
  27. data/lib/cucumber/cucumber_expressions/tree_regexp.rb +76 -0
  28. data/scripts/update-gemspec +32 -0
  29. data/spec/capture_warnings.rb +74 -0
  30. data/spec/coverage.rb +7 -0
  31. data/spec/cucumber/cucumber_expressions/argument_spec.rb +17 -0
  32. data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +43 -0
  33. data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +231 -0
  34. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +57 -0
  35. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +212 -0
  36. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +202 -0
  37. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +30 -0
  38. data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +86 -0
  39. data/spec/cucumber/cucumber_expressions/parameter_type_spec.rb +15 -0
  40. data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +80 -0
  41. data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +133 -0
  42. metadata +162 -0
@@ -0,0 +1,57 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/parameter_type_registry'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ describe CucumberExpression do
7
+ context "Regexp translation" do
8
+ def assert_regexp(expression, regexp)
9
+ cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new)
10
+ expect(regexp).to eq(cucumber_expression.regexp)
11
+ end
12
+
13
+ it "translates no arguments" do
14
+ assert_regexp(
15
+ "I have 10 cukes in my belly now",
16
+ /^I have 10 cukes in my belly now$/
17
+ )
18
+ end
19
+
20
+ it "translates alternation" do
21
+ assert_regexp(
22
+ "I had/have a great/nice/charming friend",
23
+ /^I (?:had|have) a (?:great|nice|charming) friend$/
24
+ )
25
+ end
26
+
27
+ it "translates alternation with non-alpha" do
28
+ assert_regexp(
29
+ "I said Alpha1/Beta1",
30
+ /^I said (?:Alpha1|Beta1)$/
31
+ )
32
+ end
33
+
34
+ it "translates parameters" do
35
+ assert_regexp(
36
+ "I have {float} cukes at {int} o'clock",
37
+ /^I have ((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][-+]?\d+)?) cukes at ((?:-?\d+)|(?:\d+)) o'clock$/
38
+ )
39
+ end
40
+
41
+ it "translates parenthesis to non-capturing optional capture group" do
42
+ assert_regexp(
43
+ "I have many big(ish) cukes",
44
+ /^I have many big(?:ish)? cukes$/
45
+ )
46
+ end
47
+
48
+ it "translates parenthesis with alpha unicode" do
49
+ assert_regexp(
50
+ "Привет, Мир(ы)!",
51
+ /^Привет, Мир(?:ы)?!$/
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,212 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/parameter_type_registry'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ describe CucumberExpression do
7
+ it "documents match arguments" do
8
+ parameter_registry = ParameterTypeRegistry.new
9
+
10
+ ### [capture-match-arguments]
11
+ expr = "I have {int} cuke(s)"
12
+ expression = CucumberExpression.new(expr, parameter_registry)
13
+ args = expression.match("I have 7 cukes")
14
+ expect(args[0].value(nil)).to eq(7)
15
+ ### [capture-match-arguments]
16
+ end
17
+
18
+ it "matches word" do
19
+ expect(match("three {word} mice", "three blind mice")).to eq(['blind'])
20
+ end
21
+
22
+ it('matches double quoted string') do
23
+ expect(match('three {string} mice', 'three "blind" mice')).to eq(['blind'])
24
+ end
25
+
26
+ it('matches multiple double quoted strings') do
27
+ expect(match('three {string} and {string} mice', 'three "blind" and "crippled" mice')).to eq(['blind', 'crippled'])
28
+ end
29
+
30
+ it('matches single quoted string') do
31
+ expect(match('three {string} mice', "three 'blind' mice")).to eq(['blind'])
32
+ end
33
+
34
+ it('matches multiple single quoted strings') do
35
+ expect(match('three {string} and {string} mice', "three 'blind' and 'crippled' mice")).to eq(['blind', 'crippled'])
36
+ end
37
+
38
+ it('does not match misquoted string') do
39
+ expect(match('three {string} mice', 'three "blind\' mice')).to eq(nil)
40
+ end
41
+
42
+ it('matches single quoted string with double quotes') do
43
+ expect(match('three {string} mice', 'three \'"blind"\' mice')).to eq(['"blind"'])
44
+ end
45
+
46
+ it('matches double quoted string with single quotes') do
47
+ expect(match('three {string} mice', 'three "\'blind\'" mice')).to eq(["'blind'"])
48
+ end
49
+
50
+ it('matches double quoted string with escaped double quote') do
51
+ expect(match('three {string} mice', 'three "bl\\"nd" mice')).to eq(['bl"nd'])
52
+ end
53
+
54
+ it('matches single quoted string with escaped single quote') do
55
+ expect(match('three {string} mice', "three 'bl\\'nd' mice")).to eq(["bl'nd"])
56
+ end
57
+
58
+ it('matches single quoted empty string as empty string') do
59
+ expect(match('three {string} mice', "three '' mice")).to eq([''])
60
+ end
61
+
62
+ it('matches double quoted empty string as empty string') do
63
+ expect(match('three {string} mice', 'three "" mice')).to eq([''])
64
+ end
65
+
66
+ it('matches single quoted empty string as empty string, along with other strings') do
67
+ expect(match('three {string} and {string} mice', "three '' and 'handsome' mice")).to eq(['', 'handsome'])
68
+ end
69
+
70
+ it('matches double quoted empty string as empty string, along with other strings') do
71
+ expect(match('three {string} and {string} mice', 'three "" and "handsome" mice')).to eq(['', 'handsome'])
72
+ end
73
+
74
+ it 'matches escaped parentheses' do
75
+ expect(match('three \\(exceptionally) {string} mice', 'three (exceptionally) "blind" mice')).to eq(['blind'])
76
+ end
77
+
78
+ it "matches escaped slash" do
79
+ expect(match("12\\/2020", "12/2020")).to eq([])
80
+ end
81
+
82
+ it "matches int" do
83
+ expect(match("{int}", "22")).to eq([22])
84
+ end
85
+
86
+ it "doesn't match float as int" do
87
+ expect(match("{int}", "1.22")).to be_nil
88
+ end
89
+
90
+ it "matches int as float" do
91
+ expect(match("{float}", "0")).to eq([0.0])
92
+ end
93
+
94
+ it "matches float" do
95
+ expect(match("{float}", "")).to eq(nil)
96
+ expect(match("{float}", ".")).to eq(nil)
97
+ expect(match("{float}", ",")).to eq(nil)
98
+ expect(match("{float}", "-")).to eq(nil)
99
+ expect(match("{float}", "E")).to eq(nil)
100
+ expect(match("{float}", "1,")).to eq(nil)
101
+ expect(match("{float}", ",1")).to eq(nil)
102
+ expect(match("{float}", "1.")).to eq(nil)
103
+
104
+ expect(match("{float}", "1")).to eq([1])
105
+ expect(match("{float}", "-1")).to eq([-1])
106
+ expect(match("{float}", "1.1")).to eq([1.1])
107
+ expect(match("{float}", "1,000")).to eq(nil)
108
+ expect(match("{float}", "1,000,0")).to eq(nil)
109
+ expect(match("{float}", "1,000.1")).to eq(nil)
110
+ expect(match("{float}", "1,000,10")).to eq(nil)
111
+ expect(match("{float}", "1,0.1")).to eq(nil)
112
+ expect(match("{float}", "1,000,000.1")).to eq(nil)
113
+ expect(match("{float}", "-1.1")).to eq([-1.1])
114
+
115
+ expect(match("{float}", ".1")).to eq([0.1])
116
+ expect(match("{float}", "-.1")).to eq([-0.1])
117
+ expect(match("{float}", "-.1000001")).to eq([-0.1000001])
118
+ expect(match("{float}", "1E1")).to eq([10.0])
119
+ expect(match("{float}", ".1E1")).to eq([1])
120
+ expect(match("{float}", "E1")).to eq(nil)
121
+ expect(match("{float}", "-.1E-1")).to eq([-0.01])
122
+ expect(match("{float}", "-.1E-2")).to eq([-0.001])
123
+ expect(match("{float}", "-.1E+1")).to eq([-1])
124
+ expect(match("{float}", "-.1E+2")).to eq([-10])
125
+ expect(match("{float}", "-.1E1")).to eq([-1])
126
+ expect(match("{float}", "-.1E2")).to eq([-10])
127
+ end
128
+
129
+ it "matches anonymous" do
130
+ expect(match("{}", "0.22")).to eq(["0.22"])
131
+ end
132
+
133
+ '[]()$.|?*+'.split('').each do |char|
134
+ it "does not allow parameter type with #{char}" do
135
+ expect {match("{#{char}string}", "something")}.to raise_error("Illegal character '#{char}' in parameter name {#{char}string}")
136
+ end
137
+ end
138
+
139
+ it "throws unknown parameter type" do
140
+ expect {match("{unknown}", "something")}.to raise_error('Undefined parameter type {unknown}')
141
+ end
142
+
143
+ it "does not allow optional parameter types" do
144
+ expect {match("({int})", "3")}.to raise_error('Parameter types cannot be optional: ({int})')
145
+ end
146
+
147
+ it "does allow escaped optional parameter types" do
148
+ expect(match("\\({int})", "(3)")).to eq([3])
149
+ end
150
+
151
+ it "does not allow text/parameter type alternation" do
152
+ expect {match("x/{int}", "3")}.to raise_error('Parameter types cannot be alternative: x/{int}')
153
+ end
154
+
155
+ it "does not allow parameter type/text alternation" do
156
+ expect {match("{int}/x", "3")}.to raise_error('Parameter types cannot be alternative: {int}/x')
157
+ end
158
+
159
+ it "exposes source" do
160
+ expr = "I have {int} cuke(s)"
161
+ expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr)
162
+ end
163
+
164
+ it "delegates transform to custom object" do
165
+ parameter_type_registry = ParameterTypeRegistry.new
166
+ parameter_type_registry.define_parameter_type(
167
+ ParameterType.new(
168
+ 'widget',
169
+ /\w+/,
170
+ Object,
171
+ -> (s) {
172
+ self.create_widget(s)
173
+ },
174
+ false,
175
+ true
176
+ )
177
+ )
178
+ expression = CucumberExpression.new(
179
+ 'I have a {widget}',
180
+ parameter_type_registry
181
+ )
182
+
183
+ class World
184
+ def create_widget(s)
185
+ "widget:#{s}"
186
+ end
187
+ end
188
+
189
+ args = expression.match("I have a bolt")
190
+ expect(args[0].value(World.new)).to eq('widget:bolt')
191
+ end
192
+
193
+ describe "escapes special characters" do
194
+ %w(\\ [ ] ^ $ . | ? * +).each do |character|
195
+ it "escapes #{character}" do
196
+ expr = "I have {int} cuke(s) and #{character}"
197
+ expression = CucumberExpression.new(expr, ParameterTypeRegistry.new)
198
+ arg1 = expression.match("I have 800 cukes and #{character}")[0]
199
+ expect(arg1.value(nil)).to eq(800)
200
+ end
201
+ end
202
+ end
203
+
204
+ def match(expression, text)
205
+ cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new)
206
+ args = cucumber_expression.match(text)
207
+ return nil if args.nil?
208
+ args.map {|arg| arg.value(nil)}
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,202 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/regular_expression'
3
+ require 'cucumber/cucumber_expressions/parameter_type_registry'
4
+
5
+ module Cucumber
6
+ module CucumberExpressions
7
+ class Color
8
+ attr_reader :name
9
+
10
+ ### [color-constructor]
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+
15
+ ### [color-constructor]
16
+
17
+ def ==(other)
18
+ other.is_a?(Color) && other.name == name
19
+ end
20
+ end
21
+
22
+ class CssColor
23
+ attr_reader :name
24
+
25
+ def initialize(name)
26
+ @name = name
27
+ end
28
+
29
+ def ==(other)
30
+ other.is_a?(CssColor) && other.name == name
31
+ end
32
+ end
33
+
34
+ class Coordinate
35
+ attr_reader :x, :y, :z
36
+
37
+ def initialize(x, y, z)
38
+ @x, @y, @z = x, y, z
39
+ end
40
+
41
+ def ==(other)
42
+ other.is_a?(Coordinate) && other.x == x && other.y == y && other.z == z
43
+ end
44
+ end
45
+
46
+ describe "Custom parameter type" do
47
+ before do
48
+ parameter_type_registry = ParameterTypeRegistry.new
49
+ ### [add-color-parameter-type]
50
+ parameter_type_registry.define_parameter_type(ParameterType.new(
51
+ 'color', # name
52
+ /red|blue|yellow/, # regexp
53
+ Color, # type
54
+ lambda {|s| Color.new(s)}, # transform
55
+ true, # use_for_snippets
56
+ false # prefer_for_regexp_match
57
+ ))
58
+ ### [add-color-parameter-type]
59
+ @parameter_type_registry = parameter_type_registry
60
+ end
61
+
62
+ it "throws exception for illegal character in parameter name" do
63
+ expect do
64
+ ParameterType.new(
65
+ '[string]',
66
+ /.*/,
67
+ String,
68
+ lambda {|s| s},
69
+ true,
70
+ false
71
+ )
72
+ end.to raise_error("Illegal character '[' in parameter name {[string]}")
73
+ end
74
+
75
+ describe CucumberExpression do
76
+ it "matches parameters with custom parameter type" do
77
+ expression = CucumberExpression.new("I have a {color} ball", @parameter_type_registry)
78
+ transformed_argument_value = expression.match("I have a red ball")[0].value(nil)
79
+ expect(transformed_argument_value).to eq(Color.new('red'))
80
+ end
81
+
82
+ it "matches parameters with multiple capture groups" do
83
+ @parameter_type_registry.define_parameter_type(ParameterType.new(
84
+ 'coordinate',
85
+ /(\d+),\s*(\d+),\s*(\d+)/,
86
+ Coordinate,
87
+ lambda {|x, y, z| Coordinate.new(x.to_i, y.to_i, z.to_i)},
88
+ true,
89
+ false
90
+ ))
91
+
92
+ expression = CucumberExpression.new(
93
+ 'A {int} thick line from {coordinate} to {coordinate}',
94
+ @parameter_type_registry
95
+ )
96
+ args = expression.match('A 5 thick line from 10,20,30 to 40,50,60')
97
+
98
+ thick = args[0].value(nil)
99
+ expect(thick).to eq(5)
100
+
101
+ from = args[1].value(nil)
102
+ expect(from).to eq(Coordinate.new(10, 20, 30))
103
+
104
+ to = args[2].value(nil)
105
+ expect(to).to eq(Coordinate.new(40, 50, 60))
106
+ end
107
+
108
+ it "matches parameters with custom parameter type using optional capture group" do
109
+ parameter_type_registry = ParameterTypeRegistry.new
110
+ parameter_type_registry.define_parameter_type(ParameterType.new(
111
+ 'color',
112
+ [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
113
+ Color,
114
+ lambda {|s| Color.new(s)},
115
+ true,
116
+ false
117
+ ))
118
+ expression = CucumberExpression.new("I have a {color} ball", parameter_type_registry)
119
+ transformed_argument_value = expression.match("I have a dark red ball")[0].value(nil)
120
+ expect(transformed_argument_value).to eq(Color.new('dark red'))
121
+ end
122
+
123
+ it "defers transformation until queried from argument" do
124
+ @parameter_type_registry.define_parameter_type(ParameterType.new(
125
+ 'throwing',
126
+ /bad/,
127
+ CssColor,
128
+ lambda {|s| raise "Can't transform [#{s}]"},
129
+ true,
130
+ false
131
+ ))
132
+ expression = CucumberExpression.new("I have a {throwing} parameter", @parameter_type_registry)
133
+ args = expression.match("I have a bad parameter")
134
+ expect {args[0].value(nil)}.to raise_error("Can't transform [bad]")
135
+ end
136
+
137
+ describe "conflicting parameter type" do
138
+ it "is detected for type name" do
139
+ expect {
140
+ @parameter_type_registry.define_parameter_type(ParameterType.new(
141
+ 'color',
142
+ /.*/,
143
+ CssColor,
144
+ lambda {|s| CssColor.new(s)},
145
+ true,
146
+ false
147
+ ))
148
+ }.to raise_error("There is already a parameter with name color")
149
+ end
150
+
151
+ it "is not detected for type" do
152
+ @parameter_type_registry.define_parameter_type(ParameterType.new(
153
+ 'whatever',
154
+ /.*/,
155
+ Color,
156
+ lambda {|s| Color.new(s)},
157
+ false,
158
+ false
159
+ ))
160
+ end
161
+
162
+ it "is not detected for regexp" do
163
+ @parameter_type_registry.define_parameter_type(ParameterType.new(
164
+ 'css-color',
165
+ /red|blue|yellow/,
166
+ CssColor,
167
+ lambda {|s| CssColor.new(s)},
168
+ true,
169
+ false
170
+ ))
171
+
172
+ css_color = CucumberExpression.new("I have a {css-color} ball", @parameter_type_registry)
173
+ css_color_value = css_color.match("I have a blue ball")[0].value(nil)
174
+ expect(css_color_value).to eq(CssColor.new("blue"))
175
+
176
+ color = CucumberExpression.new("I have a {color} ball", @parameter_type_registry)
177
+ color_value = color.match("I have a blue ball")[0].value(nil)
178
+ expect(color_value).to eq(Color.new("blue"))
179
+ end
180
+ end
181
+ end
182
+
183
+ describe RegularExpression do
184
+ it "matches arguments with custom parameter type without name" do
185
+ parameter_type_registry = ParameterTypeRegistry.new
186
+ parameter_type_registry.define_parameter_type(ParameterType.new(
187
+ nil,
188
+ /red|blue|yellow/,
189
+ Color,
190
+ lambda {|s| Color.new(s)},
191
+ true,
192
+ false
193
+ ))
194
+
195
+ expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, parameter_type_registry)
196
+ value = expression.match("I have a red ball")[0].value(nil)
197
+ expect(value).to eq(Color.new('red'))
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end