cucumber-expressions 3.0.0 → 4.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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/.rsync +2 -0
  3. data/Makefile +5 -9
  4. data/cucumber-expressions.gemspec +1 -1
  5. data/examples.txt +7 -11
  6. data/lib/cucumber/cucumber_expressions/argument.rb +25 -5
  7. data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +40 -0
  8. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +8 -26
  9. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +36 -19
  10. data/lib/cucumber/cucumber_expressions/errors.rb +40 -0
  11. data/lib/cucumber/cucumber_expressions/generated_expression.rb +23 -3
  12. data/lib/cucumber/cucumber_expressions/group.rb +66 -0
  13. data/lib/cucumber/cucumber_expressions/parameter_type.rb +38 -5
  14. data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +4 -4
  15. data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +34 -54
  16. data/lib/cucumber/cucumber_expressions/regular_expression.rb +27 -27
  17. data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +43 -0
  18. data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +10 -2
  19. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +4 -11
  20. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +42 -43
  21. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +109 -94
  22. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +3 -3
  23. data/spec/cucumber/cucumber_expressions/group_spec.rb +34 -0
  24. data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +86 -0
  25. data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +17 -25
  26. metadata +14 -5
  27. data/lib/cucumber/cucumber_expressions/argument_builder.rb +0 -17
@@ -11,6 +11,7 @@ module Cucumber
11
11
  def initialize(name)
12
12
  @name = name
13
13
  end
14
+
14
15
  ### [color-constructor]
15
16
 
16
17
  def ==(other)
@@ -18,75 +19,106 @@ module Cucumber
18
19
  end
19
20
  end
20
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
+
21
46
  describe "Custom parameter" do
22
47
  before do
23
48
  parameter_registry = ParameterTypeRegistry.new
24
- ### [add-color-parameter]
49
+ ### [add-color-parameter-type]
25
50
  parameter_registry.define_parameter_type(ParameterType.new(
26
- 'color',
27
- Color,
28
- /red|blue|yellow/,
29
- lambda { |s| Color.new(s) }
51
+ 'color',
52
+ /red|blue|yellow/,
53
+ Color,
54
+ lambda {|s| Color.new(s)},
55
+ true,
56
+ false
30
57
  ))
31
- ### [add-color-parameter]
58
+ ### [add-color-parameter-type]
32
59
  @parameter_type_registry = parameter_registry
33
60
  end
34
61
 
35
62
  describe CucumberExpression do
36
63
  it "matches parameters with custom parameter type" do
37
- expression = CucumberExpression.new("I have a {color} ball", [], @parameter_type_registry)
38
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
39
- expect( transformed_argument_value ).to eq(Color.new('red'))
64
+ expression = CucumberExpression.new("I have a {color} ball", @parameter_type_registry)
65
+ transformed_argument_value = expression.match("I have a red ball")[0].value
66
+ expect(transformed_argument_value).to eq(Color.new('red'))
40
67
  end
41
68
 
42
- it "matches parameters with custom parameter type using optional capture group" do
43
- parameter_registry = ParameterTypeRegistry.new
44
- parameter_registry.define_parameter_type(ParameterType.new(
45
- 'color',
46
- Color,
47
- [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
48
- lambda { |s| Color.new(s) }
69
+ it "matches parameters with multiple capture groups" do
70
+ @parameter_type_registry.define_parameter_type(ParameterType.new(
71
+ 'coordinate',
72
+ /(\d+),\s*(\d+),\s*(\d+)/,
73
+ Coordinate,
74
+ lambda {|x, y, z| Coordinate.new(x.to_i, y.to_i, z.to_i)},
75
+ true,
76
+ false
49
77
  ))
50
- expression = CucumberExpression.new("I have a {color} ball", [], parameter_registry)
51
- transformed_argument_value = expression.match("I have a dark red ball")[0].transformed_value
52
- expect( transformed_argument_value ).to eq(Color.new('dark red'))
53
- end
54
78
 
55
- it "matches parameters with custom parameter type without constructor function and transform" do
56
- parameter_registry = ParameterTypeRegistry.new
57
- parameter_registry.define_parameter_type(ParameterType.new(
58
- 'color',
59
- nil,
60
- /red|blue|yellow/,
61
- nil
62
- ))
63
- expression = CucumberExpression.new("I have a {color} ball", [], parameter_registry)
64
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
65
- expect( transformed_argument_value ).to eq('red')
66
- end
79
+ expression = CucumberExpression.new(
80
+ 'A {int} thick line from {coordinate} to {coordinate}',
81
+ @parameter_type_registry
82
+ )
83
+ args = expression.match('A 5 thick line from 10,20,30 to 40,50,60')
67
84
 
68
- it "matches parameters with explicit type" do
69
- expression = CucumberExpression.new("I have a {color} ball", [Color], @parameter_type_registry)
70
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
71
- expect( transformed_argument_value ).to eq(Color.new('red'))
85
+ thick = args[0].value
86
+ expect(thick).to eq(5)
87
+
88
+ from = args[1].value
89
+ expect(from).to eq(Coordinate.new(10, 20, 30))
90
+
91
+ to = args[2].value
92
+ expect(to).to eq(Coordinate.new(40, 50, 60))
72
93
  end
73
94
 
74
- it "matches parameters with explicit type that isn't registered" do
75
- expression = CucumberExpression.new("I have a {color} ball", [Color], ParameterTypeRegistry.new)
76
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
77
- expect( transformed_argument_value ).to eq(Color.new('red'))
95
+ it "matches parameters with custom parameter type using optional capture group" do
96
+ parameter_type_registry = ParameterTypeRegistry.new
97
+ parameter_type_registry.define_parameter_type(ParameterType.new(
98
+ 'color',
99
+ [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/],
100
+ Color,
101
+ lambda {|s| Color.new(s)},
102
+ true,
103
+ false
104
+ ))
105
+ expression = CucumberExpression.new("I have a {color} ball", parameter_type_registry)
106
+ transformed_argument_value = expression.match("I have a dark red ball")[0].value
107
+ expect(transformed_argument_value).to eq(Color.new('dark red'))
78
108
  end
79
109
 
80
110
  it "defers transformation until queried from argument" do
81
111
  @parameter_type_registry.define_parameter_type(ParameterType.new(
82
112
  'throwing',
83
- String,
84
113
  /bad/,
85
- lambda { |s| raise "Can't transform [#{s}]" }
114
+ CssColor,
115
+ lambda {|s| raise "Can't transform [#{s}]"},
116
+ true,
117
+ false
86
118
  ))
87
- expression = CucumberExpression.new("I have a {throwing} parameter", [], @parameter_type_registry)
119
+ expression = CucumberExpression.new("I have a {throwing} parameter", @parameter_type_registry)
88
120
  args = expression.match("I have a bad parameter")
89
- expect { args[0].transformed_value }.to raise_error("Can't transform [bad]")
121
+ expect {args[0].value}.to raise_error("Can't transform [bad]")
90
122
  end
91
123
 
92
124
  describe "conflicting parameter type" do
@@ -94,69 +126,52 @@ module Cucumber
94
126
  expect {
95
127
  @parameter_type_registry.define_parameter_type(ParameterType.new(
96
128
  'color',
97
- String,
98
- /.*/,
99
- lambda { |s| s }
100
- ))
101
- }.to raise_error("There is already a parameter with type name color")
102
- end
103
-
104
- it "is detected for type" do
105
- expect {
106
- @parameter_type_registry.define_parameter_type(ParameterType.new(
107
- 'color2',
108
- Color,
109
129
  /.*/,
110
- lambda { |s| Color.new(s) }
130
+ CssColor,
131
+ lambda {|s| CssColor.new(s)},
132
+ true,
133
+ false
111
134
  ))
112
- }.to raise_error("There is already a parameter with type Cucumber::CucumberExpressions::Color")
135
+ }.to raise_error("There is already a parameter with name color")
113
136
  end
114
137
 
115
- it "is detected for regexp" do
116
- expect {
117
- @parameter_type_registry.define_parameter_type(ParameterType.new(
118
- 'color2',
119
- String,
120
- /red|blue|yellow/,
121
- lambda { |s| s }
122
- ))
123
- }.to raise_error("There is already a parameter with regexp red|blue|yellow")
124
- end
125
-
126
- it "is not detected when type is nil" do
138
+ it "is not detected for type" do
127
139
  @parameter_type_registry.define_parameter_type(ParameterType.new(
128
- 'foo',
129
- nil,
130
- /foo/,
131
- lambda { |s| s }
140
+ 'whatever',
141
+ /.*/,
142
+ Color,
143
+ lambda {|s| Color.new(s)},
144
+ true,
145
+ false
132
146
  ))
147
+ end
148
+
149
+ it "is not detected for regexp" do
133
150
  @parameter_type_registry.define_parameter_type(ParameterType.new(
134
- 'bar',
135
- nil,
136
- /bar/,
137
- lambda { |s| s }
151
+ 'css-color',
152
+ /red|blue|yellow/,
153
+ CssColor,
154
+ lambda {|s| CssColor.new(s)},
155
+ true,
156
+ false
138
157
  ))
158
+
159
+ css_color = CucumberExpression.new("I have a {css-color} ball", @parameter_type_registry)
160
+ css_color_value = css_color.match("I have a blue ball")[0].value
161
+ expect(css_color_value).to eq(CssColor.new("blue"))
162
+
163
+ color = CucumberExpression.new("I have a {color} ball", @parameter_type_registry)
164
+ color_value = color.match("I have a blue ball")[0].value
165
+ expect(color_value).to eq(Color.new("blue"))
139
166
  end
140
167
  end
141
168
  end
142
169
 
143
170
  describe RegularExpression do
144
- it "matches parameters with explicit constructor" do
145
- expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, [Color], @parameter_type_registry)
146
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
147
- expect( transformed_argument_value ).to eq(Color.new('red'))
148
- end
149
-
150
- it "matches parameters without explicit constructor" do
151
- expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, [], @parameter_type_registry)
152
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
153
- expect( transformed_argument_value ).to eq(Color.new('red'))
154
- end
155
-
156
- it "matches parameters with explicit type that isn't registered" do
157
- expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, [Color], ParameterTypeRegistry.new)
158
- transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
159
- expect( transformed_argument_value ).to eq(Color.new('red'))
171
+ it "matches arguments with custom parameter type" do
172
+ expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, @parameter_type_registry)
173
+ value = expression.match("I have a red ball")[0].value
174
+ expect(value).to eq(Color.new('red'))
160
175
  end
161
176
  end
162
177
  end
@@ -8,12 +8,12 @@ module Cucumber
8
8
  describe 'examples.txt' do
9
9
  def match(expression_text, text)
10
10
  expression = expression_text =~ /\/(.*)\// ?
11
- RegularExpression.new(Regexp.new($1), [], ParameterTypeRegistry.new) :
12
- CucumberExpression.new(expression_text, [], ParameterTypeRegistry.new)
11
+ RegularExpression.new(Regexp.new($1), ParameterTypeRegistry.new) :
12
+ CucumberExpression.new(expression_text, ParameterTypeRegistry.new)
13
13
 
14
14
  arguments = expression.match(text)
15
15
  return nil if arguments.nil?
16
- arguments.map { |arg| arg.transformed_value }
16
+ arguments.map { |arg| arg.value }
17
17
  end
18
18
 
19
19
  File.open(File.expand_path("../../../../examples.txt", __FILE__), "r:utf-8") do |io|
@@ -0,0 +1,34 @@
1
+ require 'cucumber/cucumber_expressions/group'
2
+
3
+ module Cucumber
4
+ module CucumberExpressions
5
+ describe Group do
6
+ it 'matches optional group' do
7
+ regexp = /^Something( with an optional argument)?/
8
+ string = 'Something'
9
+ matches = regexp.match(string)
10
+ group = Group.new(matches, string)
11
+
12
+ expect(group.children[0].value).to eq(nil)
13
+ end
14
+
15
+ it 'matches nested groups' do
16
+ regexp = /^A (\d+) thick line from ((\d+),\s*(\d+),\s*(\d+)) to ((\d+),\s*(\d+),\s*(\d+))?/
17
+ string = 'A 5 thick line from 10,20,30 to 40,50,60'
18
+
19
+ matches = regexp.match(string)
20
+ group = Group.new(matches, string)
21
+
22
+ expect(group.children[0].value).to eq('5')
23
+ expect(group.children[1].value).to eq('10,20,30')
24
+ expect(group.children[1].children[0].value).to eq('10')
25
+ expect(group.children[1].children[1].value).to eq('20')
26
+ expect(group.children[1].children[2].value).to eq('30')
27
+ expect(group.children[2].value).to eq('40,50,60')
28
+ expect(group.children[2].children[0].value).to eq('40')
29
+ expect(group.children[2].children[1].value).to eq('50')
30
+ expect(group.children[2].children[2].value).to eq('60')
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,86 @@
1
+ require 'cucumber/cucumber_expressions/parameter_type_registry'
2
+ require 'cucumber/cucumber_expressions/parameter_type'
3
+ require 'cucumber/cucumber_expressions/errors'
4
+
5
+ module Cucumber
6
+ module CucumberExpressions
7
+
8
+ CAPITALISED_WORD = /[A-Z]+\w+/
9
+
10
+ class Name
11
+ end
12
+
13
+ class Person
14
+ end
15
+
16
+ class Place
17
+ end
18
+
19
+ describe ParameterTypeRegistry do
20
+ before do
21
+ @registry = ParameterTypeRegistry.new
22
+ end
23
+
24
+ it 'does not allow more than one prefer_for_regexp_match parameter type for each regexp' do
25
+ @registry.define_parameter_type(ParameterType.new("name", CAPITALISED_WORD, Name, lambda {|s| Name.new}, true, true))
26
+ @registry.define_parameter_type(ParameterType.new("person", CAPITALISED_WORD, Person, lambda {|s| Person.new}, true, false))
27
+ expect do
28
+ @registry.define_parameter_type(ParameterType.new("place", CAPITALISED_WORD, Place, lambda {|s| Place.new}, true, true))
29
+ end.to raise_error(
30
+ CucumberExpressionError,
31
+ "There can only be one prefer_for_regexp_match parameter type per regexp. The regexp /[A-Z]+\\w+/ is used for two prefer_for_regexp_match parameter types, {name} and {place}"
32
+ )
33
+ end
34
+
35
+ it 'looks up prefer_for_regexp_match parameter type by regexp' do
36
+ name = ParameterType.new("name", CAPITALISED_WORD, Name, lambda {|s| Name.new}, true, false)
37
+ person = ParameterType.new("person", CAPITALISED_WORD, Person, lambda {|s| Person.new}, true, true)
38
+ place = ParameterType.new("place", CAPITALISED_WORD, Place, lambda {|s| Place.new}, true, false)
39
+
40
+ @registry.define_parameter_type(name)
41
+ @registry.define_parameter_type(person)
42
+ @registry.define_parameter_type(place)
43
+
44
+ expect(@registry.lookup_by_regexp(CAPITALISED_WORD.source, /([A-Z]+\w+) and ([A-Z]+\w+)/, "Lisa and Bob")).to eq(person)
45
+ end
46
+
47
+ it 'throws ambiguous exception when no parameter types are prefer_for_regexp_match' do
48
+ name = ParameterType.new("name", CAPITALISED_WORD, Name, lambda {|s| Name.new}, true, false)
49
+ person = ParameterType.new("person", CAPITALISED_WORD, Person, lambda {|s| Person.new}, true, false)
50
+ place = ParameterType.new("place", CAPITALISED_WORD, Place, lambda {|s| Place.new}, true, false)
51
+
52
+ @registry.define_parameter_type(name)
53
+ @registry.define_parameter_type(person)
54
+ @registry.define_parameter_type(place)
55
+
56
+ expect do
57
+ expect(@registry.lookup_by_regexp(CAPITALISED_WORD.source, /([A-Z]+\w+) and ([A-Z]+\w+)/, "Lisa and Bob")).to eq(person)
58
+ end.to raise_error(
59
+ CucumberExpressionError,
60
+ "Your Regular Expression /([A-Z]+\\w+) and ([A-Z]+\\w+)/\n" +
61
+ "matches multiple parameter types with regexp /[A-Z]+\\w+/:\n" +
62
+ " {name}\n" +
63
+ " {person}\n" +
64
+ " {place}\n" +
65
+ "\n" +
66
+ "I couldn't decide which one to use. You have two options:\n" +
67
+ "\n" +
68
+ "1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:\n" +
69
+ " {name} and {name}\n" +
70
+ " {name} and {person}\n" +
71
+ " {name} and {place}\n" +
72
+ " {person} and {name}\n" +
73
+ " {person} and {person}\n" +
74
+ " {person} and {place}\n" +
75
+ " {place} and {name}\n" +
76
+ " {place} and {person}\n" +
77
+ " {place} and {place}\n" +
78
+ "\n" +
79
+ "2) Make one of the parameter types prefer_for_regexp_match and continue to use a Regular Expression.\n" +
80
+ "\n"
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
86
+
@@ -9,11 +9,10 @@ module Cucumber
9
9
 
10
10
  ### [capture-match-arguments]
11
11
  expr = /I have (\d+) cukes? in my (\w*) now/
12
- types = ['int', nil]
13
- expression = RegularExpression.new(expr, types, parameter_type_registry)
12
+ expression = RegularExpression.new(expr, parameter_type_registry)
14
13
  args = expression.match("I have 7 cukes in my belly now")
15
- expect( args[0].transformed_value ).to eq(7)
16
- expect( args[1].transformed_value ).to eq("belly")
14
+ expect( args[0].value ).to eq(7)
15
+ expect( args[1].value ).to eq("belly")
17
16
  ### [capture-match-arguments]
18
17
  end
19
18
 
@@ -21,50 +20,43 @@ module Cucumber
21
20
  expect( match(/(\d\d)/, "22") ).to eq(["22"])
22
21
  end
23
22
 
24
- it "transforms int to float by explicit type name" do
25
- expect( match(/(.*)/, "22", ['float']) ).to eq([22.0])
23
+ it "transforms negative int" do
24
+ expect( match(/(-?\d+)/, "-22") ).to eq([-22])
26
25
  end
27
26
 
28
- it "transforms int to float by explicit function" do
29
- expect( match(/(.*)/, "22", [Float]) ).to eq([22.0])
30
- end
31
-
32
- it "transforms int by parameter pattern" do
33
- expect( match(/(-?\d+)/, "22") ).to eq([22])
34
- end
35
-
36
- it "transforms int by alternate parameter pattern" do
27
+ it "transforms positive int" do
37
28
  expect( match(/(\d+)/, "22") ).to eq([22])
38
29
  end
39
30
 
40
31
  it "transforms float without integer part" do
41
- expect( match(/(.*)/, ".22", ['float']) ).to eq([0.22])
32
+ expect( match(/(-?\d*\.?\d+)/, ".22") ).to eq([0.22])
42
33
  end
43
34
 
44
35
  it "transforms float with sign" do
45
- expect( match(/(.*)/, "-1.22", ['float']) ).to eq([-1.22])
36
+ expect( match(/(-?\d*\.?\d+)/, "-1.22") ).to eq([-1.22])
46
37
  end
47
38
 
48
39
  it "returns nil when there is no match" do
49
40
  expect( match(/hello/, "world") ).to be_nil
50
41
  end
51
42
 
52
- it "fails when type is not type name or class" do
53
- expect( lambda { match(/(.*)/, "-1.22", [99]) } ).to raise_error(
54
- # Ruby 2.3 and older report Fixnum, 2.4 and newer report Integer
55
- /Type must be string or class, but was 99 of type (?:Fixnum)|(?:Integer)/)
43
+ it "ignores non capturing groups" do
44
+ expect( match(
45
+ /(\S+) ?(can|cannot)? (?:delete|cancel) the (\d+)(?:st|nd|rd|th) (attachment|slide) ?(?:upload)?/,
46
+ "I can cancel the 1st slide upload")
47
+ ).to eq(["I", "can", 1, "slide"])
56
48
  end
57
49
 
58
50
  it "exposes source" do
59
51
  expr = /I have (\d+) cukes? in my (\+) now/
60
- expect(RegularExpression.new(expr, [], ParameterTypeRegistry.new).source).to eq(expr)
52
+ expect(RegularExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr)
61
53
  end
62
54
 
63
- def match(expression, text, types = [])
64
- regular_expression = RegularExpression.new(expression, types, ParameterTypeRegistry.new)
55
+ def match(expression, text)
56
+ regular_expression = RegularExpression.new(expression, ParameterTypeRegistry.new)
65
57
  arguments = regular_expression.match(text)
66
58
  return nil if arguments.nil?
67
- arguments.map { |arg| arg.transformed_value }
59
+ arguments.map { |arg| arg.value }
68
60
  end
69
61
  end
70
62
  end