cucumber-cucumber-expressions 17.1.0 → 18.0.0

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.
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cucumber/cucumber_expressions/cucumber_expression'
4
- require 'cucumber/cucumber_expressions/regular_expression'
5
- require 'cucumber/cucumber_expressions/parameter_type_registry'
6
-
7
- module Cucumber
8
- module CucumberExpressions
9
- class Color
10
- attr_reader :name
11
-
12
- def initialize(name)
13
- @name = name
14
- end
15
-
16
- def ==(other)
17
- other.is_a?(Color) && other.name == name
18
- end
19
- end
20
-
21
- class CssColor
22
- attr_reader :name
23
-
24
- def initialize(name)
25
- @name = name
26
- end
27
-
28
- def ==(other)
29
- other.is_a?(CssColor) && other.name == name
30
- end
31
- end
32
-
33
- class Coordinate
34
- attr_reader :x, :y, :z
35
-
36
- def initialize(x, y, z)
37
- @x, @y, @z = x, y, z
38
- end
39
-
40
- def ==(other)
41
- other.is_a?(Coordinate) && other.x == x && other.y == y && other.z == z
42
- end
43
- end
44
-
45
- describe 'Custom parameter type' do
46
- before do
47
- parameter_type_registry = ParameterTypeRegistry.new
48
- parameter_type_registry.define_parameter_type(
49
- ParameterType.new(
50
- 'color', # name
51
- /red|blue|yellow/, # regexp
52
- Color, # type
53
- ->(s) { Color.new(s) }, # transform
54
- true, # use_for_snippets
55
- false # prefer_for_regexp_match
56
- )
57
- )
58
- @parameter_type_registry = parameter_type_registry
59
- end
60
-
61
- it 'throws exception for illegal character in parameter name' do
62
- expect do
63
- ParameterType.new(
64
- '[string]',
65
- /.*/,
66
- String,
67
- ->(s) { s },
68
- true,
69
- false
70
- )
71
- end.to raise_error("Illegal character in parameter name {[string]}. Parameter names may not contain '[]()$.|?*+'")
72
- end
73
-
74
- describe CucumberExpression do
75
- it 'matches parameters with custom parameter type' do
76
- expression = described_class.new('I have a {color} ball', @parameter_type_registry)
77
- transformed_argument_value = expression.match('I have a red ball')[0].value(nil)
78
-
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(
84
- ParameterType.new(
85
- 'coordinate',
86
- /(\d+),\s*(\d+),\s*(\d+)/,
87
- Coordinate,
88
- ->(x, y, z) { Coordinate.new(x.to_i, y.to_i, z.to_i) },
89
- true,
90
- false
91
- )
92
- )
93
-
94
- expression = described_class.new('A {int} thick line from {coordinate} to {coordinate}', @parameter_type_registry)
95
- args = expression.match('A 5 thick line from 10,20,30 to 40,50,60')
96
-
97
- thick = args[0].value(nil)
98
- expect(thick).to eq(5)
99
-
100
- from = args[1].value(nil)
101
- expect(from).to eq(Coordinate.new(10, 20, 30))
102
-
103
- to = args[2].value(nil)
104
- expect(to).to eq(Coordinate.new(40, 50, 60))
105
- end
106
-
107
- it 'matches parameters with custom parameter type using optional capture group' do
108
- parameter_type_registry = ParameterTypeRegistry.new
109
- parameter_type_registry.define_parameter_type(
110
- ParameterType.new(
111
- 'color',
112
- [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
113
- Color,
114
- ->(s) { Color.new(s) },
115
- true,
116
- false
117
- )
118
- )
119
- expression = described_class.new('I have a {color} ball', parameter_type_registry)
120
- transformed_argument_value = expression.match('I have a dark red ball')[0].value(nil)
121
-
122
- expect(transformed_argument_value).to eq(Color.new('dark red'))
123
- end
124
-
125
- it 'defers transformation until queried from argument' do
126
- @parameter_type_registry.define_parameter_type(
127
- ParameterType.new(
128
- 'throwing',
129
- /bad/,
130
- CssColor,
131
- ->(s) { raise "Can't transform [#{s}]" },
132
- true,
133
- false
134
- )
135
- )
136
- expression = described_class.new('I have a {throwing} parameter', @parameter_type_registry)
137
- args = expression.match('I have a bad parameter')
138
-
139
- expect { args[0].value(nil) }.to raise_error("Can't transform [bad]")
140
- end
141
-
142
- describe 'conflicting parameter type' do
143
- it 'is detected for type name' do
144
- expect {
145
- @parameter_type_registry.define_parameter_type(
146
- ParameterType.new(
147
- 'color',
148
- /.*/,
149
- CssColor,
150
- ->(s) { CssColor.new(s) },
151
- true,
152
- false
153
- )
154
- )
155
- }.to raise_error('There is already a parameter with name color')
156
- end
157
-
158
- it 'is not detected for type' do
159
- @parameter_type_registry.define_parameter_type(
160
- ParameterType.new(
161
- 'whatever',
162
- /.*/,
163
- Color,
164
- ->(s) { Color.new(s) },
165
- false,
166
- false
167
- )
168
- )
169
- end
170
-
171
- it 'is not detected for regexp' do
172
- @parameter_type_registry.define_parameter_type(
173
- ParameterType.new(
174
- 'css-color',
175
- /red|blue|yellow/,
176
- CssColor,
177
- ->(s) { CssColor.new(s) },
178
- true,
179
- false
180
- )
181
- )
182
- css_color = described_class.new('I have a {css-color} ball', @parameter_type_registry)
183
- css_color_value = css_color.match('I have a blue ball')[0].value(nil)
184
-
185
- expect(css_color_value).to eq(CssColor.new('blue'))
186
-
187
- color = described_class.new('I have a {color} ball', @parameter_type_registry)
188
- color_value = color.match('I have a blue ball')[0].value(nil)
189
-
190
- expect(color_value).to eq(Color.new('blue'))
191
- end
192
- end
193
- end
194
-
195
- describe RegularExpression do
196
- it 'matches arguments with custom parameter types without a name' do
197
- parameter_type_registry = ParameterTypeRegistry.new
198
- parameter_type_registry.define_parameter_type(
199
- ParameterType.new(
200
- nil,
201
- /red|blue|yellow/,
202
- Color,
203
- ->(s) { Color.new(s) },
204
- true,
205
- false
206
- )
207
- )
208
- expression = described_class.new(/I have a (red|blue|yellow) ball/, parameter_type_registry)
209
- value = expression.match('I have a red ball')[0].value(nil)
210
-
211
- expect(value).to eq(Color.new('red'))
212
- end
213
- end
214
- end
215
- end
216
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cucumber/cucumber_expressions/expression_factory'
4
-
5
- module Cucumber
6
- module CucumberExpressions
7
- describe ExpressionFactory do
8
- before do
9
- @expression_factory = described_class.new(ParameterTypeRegistry.new)
10
- end
11
-
12
- it 'creates a RegularExpression' do
13
- expect(@expression_factory.create_expression(/x/).class).to eq(RegularExpression)
14
- end
15
-
16
- it 'creates a CucumberExpression' do
17
- expect(@expression_factory.create_expression('{int}').class).to eq(CucumberExpression)
18
- end
19
- end
20
- end
21
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cucumber/cucumber_expressions/parameter_type_registry'
4
- require 'cucumber/cucumber_expressions/parameter_type'
5
- require 'cucumber/cucumber_expressions/errors'
6
-
7
- module Cucumber
8
- module CucumberExpressions
9
- CAPITALISED_WORD = /[A-Z]+\w+/.freeze
10
-
11
- class Name; end
12
- class Person; end
13
- class Place; end
14
-
15
- describe ParameterTypeRegistry do
16
- before do
17
- @registry = described_class.new
18
- end
19
-
20
- it 'does not allow more than one prefer_for_regexp_match parameter type for each regexp' do
21
- @registry.define_parameter_type(ParameterType.new('name', CAPITALISED_WORD, Name, ->(_) { Name.new }, true, true))
22
- @registry.define_parameter_type(ParameterType.new('person', CAPITALISED_WORD, Person, ->(_) { Person.new }, true, false))
23
- expect do
24
- @registry.define_parameter_type(ParameterType.new('place', CAPITALISED_WORD, Place, ->(_) { Place.new }, true, true))
25
- end.to raise_error(
26
- CucumberExpressionError,
27
- 'There can only be one preferential parameter type per regexp. The regexp /[A-Z]+\\w+/ is used for two: {name} and {place}'
28
- )
29
- end
30
-
31
- it 'looks up prefer_for_regexp_match parameter type by regexp' do
32
- name = ParameterType.new('name', CAPITALISED_WORD, Name, ->(_) { Name.new }, true, false)
33
- person = ParameterType.new('person', CAPITALISED_WORD, Person, ->(_) { Person.new }, true, true)
34
- place = ParameterType.new('place', CAPITALISED_WORD, Place, ->(_) { Place.new }, true, false)
35
-
36
- @registry.define_parameter_type(name)
37
- @registry.define_parameter_type(person)
38
- @registry.define_parameter_type(place)
39
-
40
- expect(@registry.lookup_by_regexp(CAPITALISED_WORD.source, /([A-Z]+\w+) and ([A-Z]+\w+)/, 'Lisa and Bob')).to eq(person)
41
- end
42
-
43
- it 'throws ambiguous exception when no parameter types are prefer_for_regexp_match' do
44
- name = ParameterType.new('name', CAPITALISED_WORD, Name, ->(_) { Name.new }, true, false)
45
- person = ParameterType.new('person', CAPITALISED_WORD, Person, ->(_) { Person.new }, true, false)
46
- place = ParameterType.new('place', CAPITALISED_WORD, Place, ->(_) { Place.new }, true, false)
47
-
48
- @registry.define_parameter_type(name)
49
- @registry.define_parameter_type(person)
50
- @registry.define_parameter_type(place)
51
-
52
- expect do
53
- expect(@registry.lookup_by_regexp(CAPITALISED_WORD.source, /([A-Z]+\w+) and ([A-Z]+\w+)/, 'Lisa and Bob')).to eq(person)
54
- end.to raise_error(
55
- CucumberExpressionError,
56
- "Your Regular Expression /([A-Z]+\\w+) and ([A-Z]+\\w+)/\n" \
57
- "matches multiple parameter types with regexp /[A-Z]+\\w+/:\n " \
58
- "{name}\n " \
59
- "{person}\n " \
60
- "{place}\n" \
61
- "\n" \
62
- "I couldn't decide which one to use. You have two options:\n" \
63
- "\n" \
64
- "1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:\n " \
65
- "{name} and {name}\n " \
66
- "{name} and {person}\n " \
67
- "{name} and {place}\n " \
68
- "{person} and {name}\n " \
69
- "{person} and {person}\n " \
70
- "{person} and {place}\n " \
71
- "{place} and {name}\n " \
72
- "{place} and {person}\n " \
73
- "{place} and {place}\n" \
74
- "\n" \
75
- "2) Make one of the parameter types preferential and continue to use a Regular Expression.\n" \
76
- "\n"
77
- )
78
- end
79
- end
80
- end
81
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cucumber/cucumber_expressions/parameter_type'
4
-
5
- module Cucumber
6
- module CucumberExpressions
7
- describe ParameterType do
8
- it 'does not allow ignore flag on regexp' do
9
- expect do
10
- described_class.new('case-insensitive', /[a-z]+/i, String, ->(s) { s }, true, true)
11
- end.to raise_error(
12
- CucumberExpressionError,
13
- "ParameterType Regexps can't use option Regexp::IGNORECASE"
14
- )
15
- end
16
- end
17
- end
18
- end
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
- require 'cucumber/cucumber_expressions/regular_expression'
5
- require 'cucumber/cucumber_expressions/parameter_type_registry'
6
-
7
- module Cucumber
8
- module CucumberExpressions
9
- describe RegularExpression do
10
- Dir['../testdata/regular-expression/matching/*.yaml'].each do |path|
11
- expectation = YAML.load_file(path)
12
-
13
- it "matches #{path}" do
14
- parameter_registry = ParameterTypeRegistry.new
15
- expression = described_class.new(Regexp.new(expectation['expression']), parameter_registry)
16
- matches = expression.match(expectation['text'])
17
- values = matches.map { |arg| arg.value(nil) }
18
-
19
- expect(values).to eq(expectation['expected_args'])
20
- end
21
- end
22
-
23
- it 'does not transform by default' do
24
- expect(match(/(\d\d)/, '22')).to eq(['22'])
25
- end
26
-
27
- it 'does not transform anonymous' do
28
- expect(match(/(.*)/, '22')).to eq(['22'])
29
- end
30
-
31
- it 'transforms negative int' do
32
- expect(match(/(-?\d+)/, '-22')).to eq([-22])
33
- end
34
-
35
- it 'transforms positive int' do
36
- expect(match(/(\d+)/, '22')).to eq([22])
37
- end
38
-
39
- it 'returns nil when there is no match' do
40
- expect(match(/hello/, 'world')).to be_nil
41
- end
42
-
43
- it 'matches empty string when there is an empty string match' do
44
- expect(match(/^The value equals "([^"]*)"$/, 'The value equals ""')).to eq([''])
45
- end
46
-
47
- it 'matches nested capture group without match' do
48
- expect(match(/^a user( named "([^"]*)")?$/, 'a user')).to eq([nil])
49
- end
50
-
51
- it 'matches nested capture group with match' do
52
- expect(match(/^a user( named "([^"]*)")?$/, 'a user named "Charlie"')).to eq(['Charlie'])
53
- end
54
-
55
- it 'ignores non capturing groups' do
56
- expect(
57
- match(
58
- /(\S+) ?(can|cannot) (?:delete|cancel) the (\d+)(?:st|nd|rd|th) (attachment|slide) ?(?:upload)?/,
59
- 'I can cancel the 1st slide upload'
60
- )
61
- ).to eq(['I', 'can', 1, 'slide'])
62
- end
63
-
64
- it 'matches capture group nested in optional one' do
65
- regexp = /^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/
66
-
67
- expect(match(regexp, 'a purchase')).to eq([nil, nil])
68
- expect(match(regexp, 'a purchase for $33')).to eq([nil, 33])
69
- expect(match(regexp, 'a pre buyer fee model purchase')).to eq(['pre buyer fee model ', nil])
70
- end
71
-
72
- it 'works with escaped parentheses' do
73
- expect(match(/Across the line\(s\)/, 'Across the line(s)')).to eq([])
74
- end
75
-
76
- it 'exposes source and regexp' do
77
- regexp = /I have (\d+) cukes? in my (\+) now/
78
- expression = described_class.new(regexp, ParameterTypeRegistry.new)
79
-
80
- expect(expression.regexp).to eq(regexp)
81
- expect(expression.source).to eq(regexp.source)
82
- end
83
-
84
- def match(expression, text)
85
- regular_expression = RegularExpression.new(expression, ParameterTypeRegistry.new)
86
- arguments = regular_expression.match(text)
87
- return nil if arguments.nil?
88
-
89
- arguments.map { |arg| arg.value(nil) }
90
- end
91
- end
92
- end
93
- end
@@ -1,186 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cucumber/cucumber_expressions/tree_regexp'
4
-
5
- module Cucumber
6
- module CucumberExpressions
7
- describe TreeRegexp do
8
- it 'exposes the group source' do
9
- tr = described_class.new(/(a(?:b)?)(c)/)
10
- expect(tr.group_builder.children.map { |gb| gb.source }).to eq(['a(?:b)?', 'c'])
11
- end
12
-
13
- it 'builds a tree' do
14
- tr = described_class.new(/(a(?:b)?)(c)/)
15
- group = tr.match('ac')
16
- expect(group.value).to eq('ac')
17
- expect(group.children[0].value).to eq('a')
18
- expect(group.children[0].children).to eq([])
19
- expect(group.children[1].value).to eq('c')
20
- end
21
-
22
- it 'ignores `?:` as a non-capturing group' do
23
- tr = described_class.new(/a(?:b)(c)/)
24
- group = tr.match('abc')
25
- expect(group.value).to eq('abc')
26
- expect(group.children.length).to eq 1
27
- expect(group.children[0].value).to eq('c')
28
- end
29
-
30
- it 'ignores `?!` as a non-capturing group' do
31
- tr = described_class.new(/a(?!b)(.+)/)
32
- group = tr.match('aBc')
33
- expect(group.value).to eq('aBc')
34
- expect(group.children.length).to eq 1
35
- end
36
-
37
- it 'ignores `?=` as a non-capturing group' do
38
- tr = described_class.new(/a(?=b)(.+)$/)
39
- group = tr.match('abc')
40
- expect(group.value).to eq('abc')
41
- expect(group.children[0].value).to eq('bc')
42
- expect(group.children.length).to eq 1
43
- end
44
-
45
- it 'ignores `?<=` as a non-capturing group' do
46
- tr = described_class.new(/a(.+)(?<=c)$/)
47
- group = tr.match('abc')
48
- expect(group.value).to eq('abc')
49
- expect(group.children[0].value).to eq('bc')
50
- expect(group.children.length).to eq 1
51
- end
52
-
53
- it 'ignores `?<!` as a non-capturing group' do
54
- tr = described_class.new(/a(.+)(?<!b)$/)
55
- group = tr.match('abc')
56
- expect(group.value).to eq('abc')
57
- expect(group.children[0].value).to eq('bc')
58
- expect(group.children.length).to eq 1
59
- end
60
-
61
- it 'ignores `?<!` as a non-capturing group' do
62
- tr = described_class.new(/a(.+)(?<!b)$/)
63
- group = tr.match('abc')
64
- expect(group.value).to eq('abc')
65
- expect(group.children[0].value).to eq('bc')
66
- expect(group.children.length).to eq 1
67
- end
68
-
69
- it 'ignores `?>` as a non-capturing group' do
70
- tr = described_class.new(/a(?>b)c/)
71
- group = tr.match('abc')
72
- expect(group.value).to eq('abc')
73
- expect(group.children.length).to eq 0
74
- end
75
-
76
- it 'throws an error when there are named capture groups because they are buggy in Ruby' do
77
- # https://github.com/cucumber/cucumber/issues/329
78
- expect { described_class.new(/^I am a person( named "(?<first_name>.+) (?<last_name>.+)")?$/) }
79
- .to raise_error(/Named capture groups are not supported/)
80
- end
81
-
82
- it 'matches an optional group' do
83
- tr = described_class.new(/^Something( with an optional argument)?/)
84
- group = tr.match('Something')
85
- expect(group.children[0].value).to be_nil
86
- end
87
-
88
- it 'matches nested groups' do
89
- tr = described_class.new(/^A (\d+) thick line from ((\d+),\s*(\d+),\s*(\d+)) to ((\d+),\s*(\d+),\s*(\d+))/)
90
- group = tr.match('A 5 thick line from 10,20,30 to 40,50,60')
91
-
92
- expect(group.children[0].value).to eq('5')
93
- expect(group.children[1].value).to eq('10,20,30')
94
- expect(group.children[1].children[0].value).to eq('10')
95
- expect(group.children[1].children[1].value).to eq('20')
96
- expect(group.children[1].children[2].value).to eq('30')
97
- expect(group.children[2].value).to eq('40,50,60')
98
- expect(group.children[2].children[0].value).to eq('40')
99
- expect(group.children[2].children[1].value).to eq('50')
100
- expect(group.children[2].children[2].value).to eq('60')
101
- end
102
-
103
- it 'detects multiple non-capturing groups' do
104
- tr = described_class.new(/(?:a)(:b)(\?c)(d)/)
105
- group = tr.match('a:b?cd')
106
- expect(group.children.length).to eq(3)
107
- end
108
-
109
- it 'works with escaped backslashes' do
110
- tr = described_class.new(/foo\\(bar|baz)/)
111
- group = tr.match('foo\\bar')
112
- expect(group.children.length).to eq(1)
113
- end
114
-
115
- it 'works with escaped slashes' do
116
- tr = described_class.new(/^I go to '\/(.+)'$/)
117
- group = tr.match("I go to '/hello'")
118
- expect(group.children.length).to eq(1)
119
- end
120
-
121
- it 'works with digit and word regexp metacharacters' do
122
- tr = described_class.new(/^(\d) (\w+)$/)
123
- group = tr.match('2 you')
124
- expect(group.children.length).to eq(2)
125
- end
126
-
127
- it 'captures non-capturing groups with capturing groups inside' do
128
- tr = described_class.new(/the stdout(?: from "(.*?)")?/)
129
- group = tr.match('the stdout')
130
- expect(group.value).to eq('the stdout')
131
- expect(group.children[0].value).to eq(nil)
132
- expect(group.children.length).to eq(1)
133
- end
134
-
135
- it 'works with flags' do
136
- tr = described_class.new(/HELLO/i)
137
- group = tr.match('hello')
138
- expect(group.value).to eq('hello')
139
- end
140
-
141
- it('does not consider parentheses in regexp character classes as a group') do
142
- tr = described_class.new(/^drawings: ([A-Z_, ()]+)$/)
143
- group = tr.match('drawings: ONE, TWO(ABC)')
144
- expect(group.value).to eq('drawings: ONE, TWO(ABC)')
145
- expect(group.children[0].value).to eq('ONE, TWO(ABC)')
146
- expect(group.children.length).to eq(1)
147
- end
148
-
149
- it 'works with inline flags' do
150
- tr = described_class.new(/(?i)HELLO/)
151
- group = tr.match('hello')
152
- expect(group.value).to eq('hello')
153
- expect(group.children.length).to eq 0
154
- end
155
-
156
- it 'works with non-capturing inline flags' do
157
- tr = described_class.new(/(?i:HELLO)/)
158
- group = tr.match('hello')
159
- expect(group.value).to eq('hello')
160
- expect(group.children.length).to eq 0
161
- end
162
-
163
- it 'works with empty capturing groups' do
164
- tr = described_class.new(/()/)
165
- group = tr.match('')
166
- expect(group.value).to eq('')
167
- expect(group.children[0].value).to eq('')
168
- expect(group.children.length).to eq 1
169
- end
170
-
171
- it 'works with empty non-capturing groups' do
172
- tr = described_class.new(/(?:)/)
173
- group = tr.match('')
174
- expect(group.value).to eq('')
175
- expect(group.children.length).to eq 0
176
- end
177
-
178
- it 'works with empty non-look ahead groups' do
179
- tr = described_class.new(/(?<=)/)
180
- group = tr.match('')
181
- expect(group.value).to eq('')
182
- expect(group.children.length).to eq 0
183
- end
184
- end
185
- end
186
- end