turnip 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Turnip
2
2
 
3
- Turnip is a [Gherkin](https://github.com/cucumber/cucumber/wiki/Gherkin) extension for RSpec. It allows you to write tests in Gherkin and run them through your RSpec environment. Basically you can write cucumber features in RSpec.
3
+ Turnip is a [Gherkin](https://github.com/cucumber/cucumber/wiki/Gherkin)
4
+ extension for RSpec. It allows you to write tests in Gherkin and run them
5
+ through your RSpec environment. Basically you can write cucumber features in
6
+ RSpec.
4
7
 
5
8
  ## DISCLAIMER, READ THIS!!!
6
9
 
7
- Turnip is a proof of concept, there are currently NO TESTS, and there is a lot of cucumber's syntax it does NOT support. There are currently no tables, multiline string or scenario outlines.
10
+ Turnip is a proof of concept, there are currently VERY FEW TESTS, and there is
11
+ a lot of cucumber's syntax it does NOT support. There are currently no tables,
12
+ multiline string or scenario outlines.
8
13
 
9
14
  ## Installation
10
15
 
@@ -22,12 +27,17 @@ group :test do
22
27
  end
23
28
  ```
24
29
 
25
- Now edit the `.rspec` file in your project directory (create it if doesn't exist), and add the following line:
30
+ Now edit the `.rspec` file in your project directory (create it if doesn't
31
+ exist), and add the following line:
26
32
 
27
33
  ```
28
34
  -r turnip
29
35
  ```
30
36
 
37
+ ## Compatibility
38
+
39
+ Turnip does not work on Ruby 1.8.X.
40
+
31
41
  ## Usage
32
42
 
33
43
  Add a feature file anywhere in your `spec` directory:
@@ -49,13 +59,16 @@ Now you can run it just like you would run any other rspec spec:
49
59
  rspec spec/acceptance/attack_monster.feature
50
60
  ```
51
61
 
52
- It will automatically be run if you run all your specs with `rake spec` or `rspec spec`.
62
+ It will automatically be run if you run all your specs with `rake spec` or
63
+ `rspec spec`.
53
64
 
54
65
  Yes, that's really it.
55
66
 
56
67
  ## Defining steps
57
68
 
58
- You might want to define some steps. You can put them anywhere. Turnip automatically requires your `spec_helper`, so you can add them there or put them in separate files (recommended). Define them like this:
69
+ You might want to define some steps. You can put them anywhere. Turnip
70
+ automatically requires your `spec_helper`, so you can add them there or put
71
+ them in separate files (recommended). Define them like this:
59
72
 
60
73
  ``` ruby
61
74
  step "there is a monster" do
@@ -64,3 +77,94 @@ end
64
77
  ```
65
78
 
66
79
  Note that unlike Cucumber, Turnip does not support regexps in step definitions.
80
+ You can however use placeholders in your step definitions, like this:
81
+
82
+ ``` ruby
83
+ step "there is a monster called :name" do |name|
84
+ @monster = Monster.new(name)
85
+ end
86
+ ```
87
+
88
+ You can now put values in this placeholder, either quoted or not:
89
+
90
+ ``` cucumber
91
+ Given there is a monster called Jonas
92
+ And there is a monster called "Jonas Nicklas"
93
+ ```
94
+
95
+ ## Defining placeholders
96
+
97
+ But what if you want to be more specific in what to match in those
98
+ placeholders, and it is bothersome to have to constantly cast them to the
99
+ correct type. Turnip's placeholder solve both problems, like this:
100
+
101
+ ``` ruby
102
+ step "there are :count monsters" do |count|
103
+ count.times { Monster.new(name) }
104
+ end
105
+
106
+ placeholder :count do
107
+ match /\d+/ do |count|
108
+ count.to_i
109
+ end
110
+
111
+ match /no/ do
112
+ 0
113
+ end
114
+ end
115
+ ```
116
+
117
+ You would now be able to use these steps like this:
118
+
119
+ ``` cucumber
120
+ Given there are 4 monsters
121
+ Given there are no monsters
122
+ ```
123
+
124
+ Placeholders can extract matches from the regular expressions as well. For
125
+ example:
126
+
127
+ ``` ruby
128
+ placeholder :monster do
129
+ match /(blue|green|red) (furry|bald) monster/ do |color, hair|
130
+ Monster.new(color, hair)
131
+ end
132
+ end
133
+ ```
134
+
135
+ These regular expressions must not use anchors, e.g. `^` or `$`. They may not
136
+ contain named capture groups, e.g. `(?<color>blue|green)`.
137
+
138
+ ## Using with Capybara
139
+
140
+ Just require `turnip/capybara`, either in your `spec_helper` or by
141
+ adding `-r turnip/capybara` to your `.rspec` file. You can now use the
142
+ same tags you'd use in Cucumber to switch between drivers e.g.
143
+ `@javascript` or `@selenium`. Your Turnip features will also be run
144
+ with the `:type => :request` metadata, so that Capybara is included and
145
+ also any other extensions you might want to add.
146
+
147
+ ## License
148
+
149
+ (The MIT License)
150
+
151
+ Copyright (c) 2011 Jonas Nicklas
152
+
153
+ Permission is hereby granted, free of charge, to any person obtaining
154
+ a copy of this software and associated documentation files (the
155
+ 'Software'), to deal in the Software without restriction, including
156
+ without limitation the rights to use, copy, modify, merge, publish,
157
+ distribute, sublicense, and/or sell copies of the Software, and to
158
+ permit persons to whom the Software is furnished to do so, subject to
159
+ the following conditions:
160
+
161
+ The above copyright notice and this permission notice shall be
162
+ included in all copies or substantial portions of the Software.
163
+
164
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
165
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
166
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
167
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
168
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
169
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
170
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -5,6 +5,15 @@ require "turnip/version"
5
5
  require "turnip/loader"
6
6
  require "turnip/builder"
7
7
  require "turnip/run"
8
- require "turnip/steps"
8
+ require "turnip/step_definition"
9
+ require "turnip/placeholder"
9
10
  require "turnip/dsl"
10
11
  require "turnip/rspec"
12
+
13
+ module Turnip
14
+ class << self
15
+ attr_accessor :type
16
+ end
17
+ end
18
+
19
+ Turnip.type = :turnip
@@ -1,61 +1,70 @@
1
1
  module Turnip
2
- class Feature
3
- attr_reader :scenarios, :backgrounds
4
- def initialize(raw)
5
- @raw = raw
6
- @scenarios = []
7
- @backgrounds = []
8
- end
2
+ class Builder
3
+ module Tags
4
+ def tags
5
+ @raw.tags.map { |tag| tag.name.sub(/^@/, '') }
6
+ end
9
7
 
10
- def name
11
- @raw.name
12
- end
13
- end
8
+ def tags_hash
9
+ Hash[tags.map { |t| [t.to_sym, true] }]
10
+ end
14
11
 
15
- class Background
16
- attr_reader :steps
17
- def initialize(raw)
18
- @raw = raw
19
- @steps = []
12
+ def metadata_hash
13
+ tags_hash
14
+ end
20
15
  end
21
- end
22
16
 
23
- class Scenario
24
- attr_reader :name, :steps
25
- def initialize(raw)
26
- @raw = raw
27
- @steps = []
17
+ module Name
18
+ def name
19
+ @raw.name
20
+ end
28
21
  end
29
22
 
30
- def name
31
- @raw.name
32
- end
23
+ class Feature
24
+ include Tags
25
+ include Name
33
26
 
34
- def tags
35
- @raw.tags.map { |tag| tag.name.sub(/^@/, '').to_sym }
36
- end
27
+ attr_reader :scenarios, :backgrounds
37
28
 
38
- def tags_hash
39
- Hash[tags.map { |t| [t, true] }]
29
+ def initialize(raw)
30
+ @raw = raw
31
+ @scenarios = []
32
+ @backgrounds = []
33
+ end
34
+
35
+ def metadata_hash
36
+ super.merge(:type => Turnip.type, :turnip => true)
37
+ end
40
38
  end
41
39
 
42
- def metadata_hash
43
- tags_hash
40
+ class Background
41
+ attr_reader :steps
42
+ def initialize(raw)
43
+ @raw = raw
44
+ @steps = []
45
+ end
44
46
  end
45
- end
46
47
 
47
- class Step
48
- attr_reader :name
49
- def initialize(raw)
50
- @raw = raw
48
+ class Scenario
49
+ include Tags
50
+ include Name
51
+
52
+ attr_reader :steps
53
+
54
+ def initialize(raw)
55
+ @raw = raw
56
+ @steps = []
57
+ end
51
58
  end
52
59
 
53
- def name
54
- @raw.name
60
+ class Step
61
+ include Name
62
+
63
+ def initialize(raw)
64
+ @raw = raw
65
+ end
55
66
  end
56
- end
57
67
 
58
- class Builder
59
68
  attr_reader :features
60
69
 
61
70
  def initialize
@@ -63,22 +72,22 @@ module Turnip
63
72
  end
64
73
 
65
74
  def background(background)
66
- @current_step_context = Turnip::Background.new(background)
75
+ @current_step_context = Background.new(background)
67
76
  @current_feature.backgrounds << @current_step_context
68
77
  end
69
78
 
70
79
  def feature(feature)
71
- @current_feature = Turnip::Feature.new(feature)
80
+ @current_feature = Feature.new(feature)
72
81
  @features << @current_feature
73
82
  end
74
83
 
75
84
  def scenario(scenario)
76
- @current_step_context = Turnip::Scenario.new(scenario)
85
+ @current_step_context = Scenario.new(scenario)
77
86
  @current_feature.scenarios << @current_step_context
78
87
  end
79
88
 
80
89
  def step(step)
81
- @current_step = Turnip::Step.new(step)
90
+ @current_step = Step.new(step)
82
91
  @current_step_context.steps << @current_step
83
92
  end
84
93
 
@@ -0,0 +1,16 @@
1
+ require 'capybara/rspec'
2
+
3
+ Turnip.type = :request
4
+
5
+ RSpec.configure do |config|
6
+ config.before do
7
+ if self.class.include?(Capybara::DSL) and example.metadata[:turnip]
8
+ Capybara.current_driver = Capybara.javascript_driver if example.metadata.has_key?(:javascript)
9
+ example.metadata.each do |tag, value|
10
+ if Capybara.drivers.has_key?(tag)
11
+ Capybara.current_driver = tag
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,10 +1,13 @@
1
1
  module Turnip
2
2
  module DSL
3
3
  def step(description, &block)
4
- Turnip::Steps.add_step(description, &block)
4
+ Turnip::StepDefinition.add(description, &block)
5
+ end
6
+
7
+ def placeholder(name, &block)
8
+ Turnip::Placeholder.add(name, &block)
5
9
  end
6
10
  end
7
11
  end
8
12
 
9
13
  self.extend Turnip::DSL
10
-
@@ -0,0 +1,66 @@
1
+ module Turnip
2
+ class Placeholder
3
+ class Match < Struct.new(:regexp, :block);end
4
+
5
+ class << self
6
+ def add(name, &block)
7
+ placeholders[name] = Placeholder.new(name, &block)
8
+ end
9
+
10
+ def resolve(name)
11
+ find(name).regexp
12
+ end
13
+
14
+ def apply(name, value)
15
+ find(name).apply(value)
16
+ end
17
+
18
+ def find(name)
19
+ placeholders[name] or default
20
+ end
21
+
22
+ private
23
+
24
+ def placeholders
25
+ @placeholders ||= {}
26
+ end
27
+
28
+ def default
29
+ @default ||= new(:default) do
30
+ match %r((?:"([^"]+)"|([a-zA-Z0-9_-]+))) do |first, second|
31
+ first or second
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def initialize(name, &block)
38
+ @name = name
39
+ @matches = []
40
+ instance_eval(&block)
41
+ end
42
+
43
+ def apply(value)
44
+ match, params = find_match(value)
45
+ if match and match.block then match.block.call(*params) else value end
46
+ end
47
+
48
+ def match(regexp, &block)
49
+ @matches << Match.new(regexp, block)
50
+ end
51
+
52
+ def regexp
53
+ Regexp.new(@matches.map(&:regexp).join('|'))
54
+ end
55
+
56
+ private
57
+
58
+ def find_match(value)
59
+ @matches.each do |m|
60
+ result = value.scan(m.regexp)
61
+ return m, result.flatten unless result.empty?
62
+ end
63
+ nil
64
+ end
65
+ end
66
+ end
@@ -6,18 +6,18 @@ module Turnip
6
6
  parser.parse(content, nil, 0)
7
7
 
8
8
  builder.features.each do |feature|
9
- describe feature.name do
9
+ describe feature.name, feature.metadata_hash do
10
10
  feature.backgrounds.each do |background|
11
11
  before do
12
12
  background.steps.each do |step|
13
- Turnip::Steps.execute_step(self, step.name)
13
+ Turnip::StepDefinition.execute(self, step.name)
14
14
  end
15
15
  end
16
16
  end
17
17
  feature.scenarios.each do |scenario|
18
18
  it scenario.name, scenario.metadata_hash do
19
19
  scenario.steps.each do |step|
20
- Turnip::Steps.execute_step(self, step.name)
20
+ Turnip::StepDefinition.execute(self, step.name)
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,65 @@
1
+ module Turnip
2
+ class StepDefinition
3
+ class Match < Struct.new(:params, :block); end
4
+ class Pending < StandardError; end
5
+ class Ambiguous < StandardError; end
6
+
7
+ attr_reader :expression, :block
8
+
9
+ class << self
10
+ def execute(context, description)
11
+ match = find(description)
12
+ context.instance_exec(*match.params, &match.block)
13
+ rescue Pending
14
+ context.pending "the step '#{description}' is not implemented"
15
+ end
16
+
17
+ def add(expression, &block)
18
+ steps << StepDefinition.new(expression, &block)
19
+ end
20
+
21
+ def find(description)
22
+ found = steps.map do |step|
23
+ step.match(description)
24
+ end.compact
25
+ raise Pending, description if found.length == 0
26
+ raise Ambiguous, description if found.length > 1
27
+ found[0]
28
+ end
29
+
30
+ def steps
31
+ @steps ||= []
32
+ end
33
+ end
34
+
35
+ def initialize(expression, &block)
36
+ @expression = expression
37
+ @block = block
38
+ end
39
+
40
+ def regexp
41
+ @regexp ||= compile_regexp
42
+ end
43
+
44
+ def match(description)
45
+ result = description.match(regexp)
46
+ if result
47
+ params = result.captures
48
+ result.names.each_with_index do |name, index|
49
+ params[index] = Turnip::Placeholder.apply(name.to_sym, params[index])
50
+ end
51
+ Match.new(params, block)
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def compile_regexp
58
+ regexp = Regexp.escape(expression)
59
+ regexp = expression.gsub(/:([\w]+)/) do |_|
60
+ "(?<#{$1}>#{Placeholder.resolve($1.to_sym)})"
61
+ end
62
+ Regexp.new("^#{regexp}$")
63
+ end
64
+ end
65
+ end
@@ -1,3 +1,3 @@
1
1
  module Turnip
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,12 @@
1
+ Feature: A simple feature
2
+ Scenario: Interpolation with quotes
3
+ Given there is a monster called "John Smith"
4
+ Then it should be called "John Smith"
5
+
6
+ Scenario: Interpolation without quotes
7
+ Given there is a monster called John
8
+ Then it should be called "John"
9
+
10
+ Scenario: Interpolation with customer regexp
11
+ Given there are 3 monkeys with blue hair
12
+ Then there should be 3 monkeys with blue hair
@@ -0,0 +1,67 @@
1
+ require 'turnip/placeholder'
2
+
3
+ describe Turnip::Placeholder do
4
+ def anchor(exp)
5
+ Regexp.new("^#{exp}$")
6
+ end
7
+
8
+ describe ".resolve" do
9
+ it "returns a regexp for the given placeholder" do
10
+ placeholder = Turnip::Placeholder.add(:test) { match(/foo/); match(/\d/) }
11
+ resolved = Turnip::Placeholder.resolve(:test)
12
+ "foo".should =~ anchor(resolved)
13
+ "5".should =~ anchor(resolved)
14
+ "bar".should_not =~ anchor(resolved)
15
+ end
16
+
17
+ it "fall through to using the standard placeholder regexp" do
18
+ resolved = Turnip::Placeholder.resolve(:does_not_exist)
19
+ "foo".should =~ anchor(resolved)
20
+ '"this is a test"'.should =~ anchor(resolved)
21
+ "foo bar".should_not =~ anchor(resolved)
22
+ end
23
+ end
24
+
25
+ describe ".apply" do
26
+ it "returns a regexp for the given placeholder" do
27
+ placeholder = Turnip::Placeholder.add(:test) do
28
+ match(/foo/) { :foo_bar }
29
+ match(/\d/) { |num| num.to_i }
30
+ end
31
+ Turnip::Placeholder.apply(:test, "foo").should eq(:foo_bar)
32
+ Turnip::Placeholder.apply(:test, "5").should eq(5)
33
+ Turnip::Placeholder.apply(:test, "bar").should eq("bar")
34
+ end
35
+
36
+ it "extracts any captured expressions and passes them to the block" do
37
+ placeholder = Turnip::Placeholder.add(:test) do
38
+ match(/mo(nk)(ey)/) { |nk, ey| nk.to_s.reverse + '|' + ey.to_s.upcase }
39
+ end
40
+ Turnip::Placeholder.apply(:test, "monkey").should eq('kn|EY')
41
+ Turnip::Placeholder.apply(:test, "bar").should eq("bar")
42
+ end
43
+ end
44
+
45
+ describe "#regexp" do
46
+ it "should match a given fragment" do
47
+ placeholder = Turnip::Placeholder.new(:test) { match(/foo/) }
48
+ "foo".should =~ placeholder.regexp
49
+ end
50
+
51
+ it "should match multiple fragments" do
52
+ placeholder = Turnip::Placeholder.new(:test) { match(/foo/); match(/\d/) }
53
+ "foo".should =~ placeholder.regexp
54
+ "5".should =~ placeholder.regexp
55
+ end
56
+
57
+ it "should not match an incorrect fragment" do
58
+ placeholder = Turnip::Placeholder.new(:test) { match(/foo/) }
59
+ "bar".should_not =~ placeholder.regexp
60
+ end
61
+
62
+ it "should not multiple incorrect fragments" do
63
+ placeholder = Turnip::Placeholder.new(:test) { match(/foo/); match(/\d/) }
64
+ "bar".should_not =~ placeholder.regexp
65
+ end
66
+ end
67
+ end
@@ -19,3 +19,35 @@ end
19
19
 
20
20
  step "this is ambiguous" do
21
21
  end
22
+
23
+ step "there is a monster called :name" do |name|
24
+ @monster_name = name
25
+ end
26
+
27
+ step 'it should be called "John Smith"' do
28
+ @monster_name.should == "John Smith"
29
+ end
30
+
31
+ step 'it should be called "John"' do
32
+ @monster_name.should == "John"
33
+ end
34
+
35
+ step "there are :count monkeys with :color hair" do |count, color|
36
+ @monkeys = Array.new(count) { color }
37
+ end
38
+
39
+ step "there should be 3 monkeys with blue hair" do
40
+ @monkeys.should == [:blue, :blue, :blue]
41
+ end
42
+
43
+ placeholder :count do
44
+ match /\d+/ do |count|
45
+ count.to_i
46
+ end
47
+ end
48
+
49
+ placeholder :color do
50
+ match /blue|green|red/ do |color|
51
+ color.to_sym
52
+ end
53
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turnip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-21 00:00:00.000000000Z
12
+ date: 2011-10-25 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &2153440840 !ruby/object:Gem::Requirement
16
+ requirement: &2153850520 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '2.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2153440840
24
+ version_requirements: *2153850520
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: gherkin
27
- requirement: &2153440340 !ruby/object:Gem::Requirement
27
+ requirement: &2153850100 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2153440340
35
+ version_requirements: *2153850100
36
36
  description: Provides the ability to define steps and run Gherkin files from with
37
37
  RSpec
38
38
  email:
@@ -46,20 +46,23 @@ files:
46
46
  - Gemfile
47
47
  - README.md
48
48
  - Rakefile
49
- - lib/carrot.rb
50
49
  - lib/turnip.rb
51
50
  - lib/turnip/builder.rb
51
+ - lib/turnip/capybara.rb
52
52
  - lib/turnip/dsl.rb
53
53
  - lib/turnip/loader.rb
54
+ - lib/turnip/placeholder.rb
54
55
  - lib/turnip/rspec.rb
55
56
  - lib/turnip/run.rb
56
- - lib/turnip/steps.rb
57
+ - lib/turnip/step_definition.rb
57
58
  - lib/turnip/version.rb
58
59
  - spec/fixture/ambiguous.feature
59
60
  - spec/fixture/backgrounds.feature
61
+ - spec/fixture/interpolation.feature
60
62
  - spec/fixture/pending.feature
61
63
  - spec/fixture/simple_feature.feature
62
64
  - spec/fixture/tags.feature
65
+ - spec/placeholder_spec.rb
63
66
  - spec/spec_helper.rb
64
67
  - turnip.gemspec
65
68
  homepage: ''
@@ -89,7 +92,9 @@ summary: Gherkin extension for RSpec
89
92
  test_files:
90
93
  - spec/fixture/ambiguous.feature
91
94
  - spec/fixture/backgrounds.feature
95
+ - spec/fixture/interpolation.feature
92
96
  - spec/fixture/pending.feature
93
97
  - spec/fixture/simple_feature.feature
94
98
  - spec/fixture/tags.feature
99
+ - spec/placeholder_spec.rb
95
100
  - spec/spec_helper.rb
@@ -1,10 +0,0 @@
1
- require "gherkin"
2
- require "gherkin/formatter/tag_count_formatter"
3
-
4
- require "carrot/version"
5
- require "carrot/loader"
6
- require "carrot/builder"
7
- require "carrot/run"
8
- require "carrot/steps"
9
- require "carrot/dsl"
10
- require "carrot/rspec"
@@ -1,31 +0,0 @@
1
- module Turnip
2
- module Steps
3
- class Pending < StandardError; end
4
- class Ambiguous < StandardError; end
5
-
6
- extend self
7
-
8
- def execute_step(context, description)
9
- context.instance_eval(&find_step(description))
10
- rescue Pending
11
- context.pending "the step '#{description}' is not implemented"
12
- end
13
-
14
- def add_step(description, &block)
15
- steps << [description, block]
16
- end
17
-
18
- def find_step(description)
19
- found = steps.select do |step|
20
- step.first == description
21
- end
22
- raise Pending, description if found.length == 0
23
- raise Ambiguous, description if found.length > 1
24
- found[0][1]
25
- end
26
-
27
- def steps
28
- @steps ||= []
29
- end
30
- end
31
- end