cucumber-cucumber-expressions 8.3.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.
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