cucumber-expressions 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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