Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base: 91a42f9dfe
...
compare: 293ccee256
  • 12 commits
  • 23 files changed
  • 0 commit comments
  • 1 contributor
267 README.md
View
@@ -27,7 +27,7 @@ Now edit the `.rspec` file in your project directory (create it if doesn't
exist), and add the following line:
```
--r turnip
+-r turnip/rspec
```
## Development
@@ -73,33 +73,31 @@ Yes, that's really it.
## Defining steps
-You might want to define some steps. Just as in cucumber, your step files
-should be named `[something]_steps.rb`. All files ending in `*steps.rb`
-will be automatically required if they are under the Turnip step directory.
-
-The default step directory for Turnip is `spec/`. You can override this
-in your `spec_helper` by setting `Turnip::Config.step_dirs`. For example:
+You can define steps on any module:
``` ruby
-# spec/spec_helper.rb
-RSpec.configure do |config|
- Turnip::Config.step_dirs = 'examples'
- Turnip::StepLoader.load_steps
+module MonsterSteps
+ step "there is a monster" do
+ @monster = Monster.new
+ end
end
```
-This would set the Turnip step dirs to `examples/` and automatically load
-all `*steps.rb` files anywhere under that directory.
+You can now include this module in RSpec:
-The steps you define in your step files can be global or they can be scoped
-to certain features (or scenarios)...
+``` ruby
+RSpec.configure { |c| c.include MonsterSteps }
+```
+
+Steps are implemented as regular Ruby methods under the hood, so you can
+use Ruby's normal inheritance chain to mix and match steps.
### Global steps
-Global steps can be used by any feature/scenario you write since they are
-unscoped. The names must be unique across all step files in the global
-namespace.
-Define them in your step file like this:
+Turnip has a special module called `Turnip::Steps`, which is automatically
+included in RSpec. If you add steps to this module, they are available in all
+your features. As a convenience, there is a shortcut to doing this, just call
+`step` in the global namespace like this:
``` ruby
step "there is a monster" do
@@ -107,6 +105,8 @@ step "there is a monster" do
end
```
+### Placeholders
+
Note that unlike Cucumber, Turnip does not support regexps in step definitions.
You can however use placeholders in your step definitions, like this:
@@ -133,31 +133,40 @@ end
That will match both "there is X monster" or "there are X monsters".
-You can also define custom step placeholders. More on that later.
+You can also define custom step placeholders. More on that later.
### Scoped steps
-Scoped steps help you to organize steps that are specific to
-certain features or scenarios. They only need to be unique within
-the scopes being used by the running scenario.
-To define scoped steps use `steps_for`:
+Since steps are defined on modules, you can pick and choose which of them are
+available in which feature. This can be extremely useful if you have a large
+number of steps, and do not want them to potentially conflict.
+
+If you had some scenarios which talk to the database directly, and some which
+go through a user interface, you could implement it as follows:
``` ruby
-steps_for :interface do
+module InterfaceSteps
step "I do it" do
...
end
end
-steps_for :database do
+module DatabaseSteps
step "I do it" do
...
end
end
+
+RSpec.configure do |config|
+ config.include InterfaceSteps, :interface => true
+ config.include DatabaseSteps, :database => true
+end
```
-Even though the step is named the same, you can now use it in
-your feature files like so:
+Turnip turns tags into RSpec metadata, so you can use RSpec's conditional
+include feature to include these steps only for those scenarios tagged the
+appropriate way. So even though the step is named the same, you can now use it
+in your feature files like so:
``` cucumber
@interface
@@ -167,86 +176,42 @@ Scenario: do it through the interface
Scenario: do it through the database
```
-Note that this would still cause an error if you tagged a Scenario
-with both `@interface` and `@database` at the same time.
+Be careful though not to tag a feature with both `@interface` and `@database`
+in this example. Since steps use the Ruby inheritance chain, the step which is
+included last will "win", just like any other Ruby method. This might not be
+what you expect.
-Scoped steps are really just Ruby modules under the covers so you
-can do anything you'd normally want to do including defining
-helper/utility methods and variables. Check out
-[features/alignment_steps.rb](https://github.com/jnicklas/turnip/blob/master/examples/alignment_steps.rb)
-and
-[features/evil_steps.rb](https://github.com/jnicklas/turnip/blob/master/examples/evil_steps.rb) for basic examples.
-
-### Reusing steps
-When using scoped steps in Turnip, you can tell it to also include steps
-defined in another `steps_for` block. The syntax for that is `use_steps`:
+Since this pattern of creating a module and including it for a specific tag
+is very common, we have created a handy shortcut for it:
``` ruby
-# dragon_steps.rb
-steps_for :dragon do
- use_steps :knight
-
- attr_accessor :dragon
-
- def dragon_attack
- dragon * 10
- end
-
- step "there is a dragon" do
- self.dragon = 1
- end
-
- step "the dragon attacks the knight" do
- knight.attacked_for(dragon_attack)
+steps_for :interface do
+ step "I do it" do
+ ...
end
end
+```
-# red_dragon_steps.rb
-steps_for :red_dragon do
- use_steps :dragon
+Check out [features/alignment_steps.rb](https://github.com/jnicklas/turnip/blob/master/examples/alignment_steps.rb)
+for an example.
- attr_accessor :red_dragon
+### Where to place steps
- def dragon_attack
- attack = super
- if red_dragon
- attack + 15
- else
- attack
- end
- end
+All files ending in `*steps.rb` will be automatically required if they are
+under the Turnip step directory. The default step directory for Turnip is
+`spec/`. You can override this in your `spec_helper` by setting
+`Turnip.step_dirs`. For example:
- step "the dragon breathes fire" do
- self.red_dragon = 1
- end
-end
+``` ruby
+# spec/spec_helper.rb
+Turnip.step_dirs = ['examples']
```
-
-In this example we are making full use of Ruby's modules including using super
-to call the included module's version of `dragon_attack`, for example with the
-following feature file:
-
-``` cucumber
-Feature: Red Dragons are deadly
-
- @dragon
- Scenario:
- Given there is a dragon
- And there is a knight
- When the dragon attacks the knight
- Then the knight is alive
-
- @red_dragon
- Scenario:
- Given there is a dragon
- And the dragon breathes fire
- And there is a knight
- When the dragon attacks the knight
- Then the knight is dead
-```
+This would set the Turnip step dirs to `examples/` and automatically load
+all `*steps.rb` files anywhere under that directory.
### Calling steps from other steps
+
You can also call steps from other steps. This is done by just calling `step
"name_of_the_step"`, so for instance if you have:
@@ -265,51 +230,36 @@ Now if you use the step `calling a step` in any Scenario, then the value of
`@value` will be 2 afterwards as it first executes the code defined for the step
`a random step`. You can think of it as a simple method call.
-### Auto-included steps
-By default, Turnip will automatically make available any steps defined in
-a `steps_for` block with the same name as the feature file being run. For
-example, given this step file:
+### Calling steps manually
-``` ruby
-# user_signup_steps.rb
-steps_for :user_signup do
- step "I am on the homepage" do
- ...
- end
+This is a more esoteric feature of Turnip, of use mostly to people who want to
+do crazy stuff. The `Turnip::Execute` module has a method called `step`, this
+method executes a step, given a string as it might appear in a feature file.
- step "I signup with valid info" do
- ...
- end
+For example:
- step "I should see a welcome message" do
- ...
- end
-end
-```
-
-Then the following feature file would run just fine even though we
-did not explicitly tag it with `@user_signup`.
+``` ruby
+class Monster
+ include Turnip::Execute
-``` cucumber
-# user_signup.feature
-Feature: A user can signup
- Scenario: with email address
- Given I am on the homepage
- When I signup with valid info
- Then I should see a welcome message
-```
+ step("sing a song") { "Arrrghghggh" }
+ step("eat :count villager(s)") { Villager.eat(count) }
+end
-Note that the `steps_for :user_signup` did not technically have to
-appear in the user_signup_steps.rb file; it could have been located
-in any `steps.rb` file that was autoloaded by Turnip.
+monster = Monster.new
+monster.step("sing a song")
+monster.step("eat 1 villager")
+monster.step("eat 5 villagers")
-This feature can be turned off using the `Turnip::Config.autotag_features`
-option if desired.
+Note that in this case `step` from `Turnip::Execute` is an *instance* method,
+whereas `step` used to define the step is a *class* method, they are *not* the
+same method.
## Custom step placeholders
-Do you want to be more specific in what to match in your step
-placeholders? Do you find it bothersome to have to constantly cast them to the
-correct type? Turnip supports custom placeholders to solve both problems, like this:
+
+Do you want to be more specific in what to match in your step placeholders? Do
+you find it bothersome to have to constantly cast them to the correct type?
+Turnip supports custom placeholders to solve both problems, like this:
``` ruby
step "there are :count monsters" do |count|
@@ -349,6 +299,7 @@ These regular expressions must not use anchors, e.g. `^` or `$`. They may not
contain named capture groups, e.g. `(?<color>blue|green)`.
## Table Steps
+
Turnip also supports steps that take a table as a parameter similar to Cucumber:
``` cucumber
@@ -361,7 +312,7 @@ Scenario: This is a feature with a table
And "Moorg" should have 12 hitpoints
```
The table is a `Turnip::Table` object which works in much the same way as Cucumber's
-`Cucumber::Ast::Table` obects.
+`Cucumber::Ast::Table` objects.
E.g. converting the `Turnip::Table` to an array of hashes:
@@ -376,34 +327,32 @@ end
## Using with Capybara
-Just require `turnip/capybara` in your `spec_helper`. You can now use the
-same tags you'd use in Cucumber to switch between drivers e.g.
-`@javascript` or `@selenium`. Your Turnip features will also be run
-with the `:type => :request` metadata, so that Capybara is included and
-also any other extensions you might want to add.
+Just require `turnip/capybara` in your `spec_helper`. You can now use the same
+tags you'd use in Cucumber to switch between drivers e.g. `@javascript` or
+`@selenium`. Your Turnip features will also be run with the `:type => :request`
+metadata, so that Capybara is included and also any other extensions you might
+want to add.
## License
(The MIT License)
-Copyright (c) 2011 Jonas Nicklas
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-'Software'), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
+Copyright (c) 2011-2012 Jonas Nicklas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the 'Software'), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
18 examples/steps/alignment_steps.rb
View
@@ -1,7 +1,23 @@
-steps_for :alignment do
+module Alignment
attr_accessor :alignment
step "that alignment should be :alignment" do |expected_alignment|
alignment.should eq(expected_alignment)
end
end
+
+steps_for :evil do
+ include Alignment
+
+ step "the monster has an alignment" do
+ self.alignment = 'Evil'
+ end
+end
+
+steps_for :neutral do
+ include Alignment
+
+ step "the monster has an alignment" do
+ self.alignment = 'Neutral'
+ end
+end
26 examples/steps/dragon_steps.rb
View
@@ -1,7 +1,7 @@
require_relative "knight_steps"
-steps_for :dragon do
- use_steps :knight
+module DragonSteps
+ include KnightSteps
attr_accessor :dragon
@@ -17,3 +17,25 @@ def dragon_attack
knight.attacked_for(dragon_attack)
end
end
+
+module RedDragonSteps
+ include DragonSteps
+
+ attr_accessor :red_dragon
+
+ def dragon_attack
+ attack = super
+ if red_dragon
+ attack + 15
+ else
+ attack
+ end
+ end
+
+ step "the dragon breathes fire" do
+ self.red_dragon = 1
+ end
+end
+
+RSpec.configure { |c| c.include DragonSteps, :dragon => true }
+RSpec.configure { |c| c.include RedDragonSteps, :red_dragon => true }
7 examples/steps/evil_steps.rb
View
@@ -1,7 +0,0 @@
-steps_for :evil do
- use_steps :alignment
-
- step "the monster has an alignment" do
- self.alignment = 'Evil'
- end
-end
4 examples/steps/knight_steps.rb
View
@@ -1,4 +1,4 @@
-steps_for :knight do
+module KnightSteps
attr_accessor :knight
class Knight
@@ -27,3 +27,5 @@ def attacked_for(amount)
knight.should_not be_alive
end
end
+
+RSpec.configure { |c| c.include KnightSteps, :knight => true }
7 examples/steps/neutral_steps.rb
View
@@ -1,7 +0,0 @@
-steps_for :neutral do
- use_steps :alignment
-
- step "the monster has an alignment" do
- self.alignment = 'Neutral'
- end
-end
20 examples/steps/red_dragon_steps.rb
View
@@ -1,20 +0,0 @@
-require_relative "knight_steps"
-
-steps_for :red_dragon do
- use_steps :dragon
-
- attr_accessor :red_dragon
-
- def dragon_attack
- attack = super
- if red_dragon
- attack + 15
- else
- attack
- end
- end
-
- step "the dragon breathes fire" do
- self.red_dragon = 1
- end
-end
97 lib/turnip.rb
View
@@ -1,97 +1,42 @@
-require "gherkin"
-require "gherkin/formatter/tag_count_formatter"
-
require "turnip/version"
require "turnip/dsl"
-
-require 'rspec'
+require "turnip/execute"
+require "turnip/define"
+require "turnip/builder"
+require "turnip/step_definition"
+require "turnip/placeholder"
+require "turnip/table"
module Turnip
class Pending < StandardError; end
class Ambiguous < StandardError; end
- autoload :Config, 'turnip/config'
- autoload :Loader, 'turnip/loader'
- autoload :Builder, 'turnip/builder'
- autoload :StepDefinition, 'turnip/step_definition'
- autoload :Placeholder, 'turnip/placeholder'
- autoload :Table, 'turnip/table'
- autoload :StepLoader, 'turnip/step_loader'
- autoload :StepModule, 'turnip/step_module'
-
- module Execute
- def step(description, extra_arg=nil)
- matches = methods.map do |method|
- next unless method.to_s.start_with?("match: ")
- send(method.to_s, description)
- end.compact
- raise Turnip::Pending, description if matches.length == 0
- raise Turnip::Ambiguous, description if matches.length > 1
- send("execute: #{matches.first.expression}", *(matches.first.params + [extra_arg].compact))
- end
- end
-
- module Define
- def step(expression, &block)
- step = Turnip::StepDefinition.new(expression, &block)
- send(:define_method, "match: #{expression}") { |description| step.match(description) }
- send(:define_method, "execute: #{expression}", &block)
- end
- end
-
- # The global step module
+ ##
+ #
+ # The global step module, adding steps here will make them available in all
+ # your tests.
+ #
module Steps
- extend Define
- include Execute
-
- def run_step(feature_file, step)
- begin
- step(step.description, step.extra_arg)
- rescue Turnip::Pending
- pending("No such step: '#{step.description}'")
- rescue StandardError => e
- e.backtrace.unshift "#{feature_file}:#{step.line}:in `#{step.description}'"
- raise e
- end
- end
end
class << self
- attr_accessor :type
+ attr_accessor :type, :step_dirs
- def run(feature_file)
- Turnip::Builder.build(feature_file).features.each do |feature|
- describe feature.name, feature.metadata_hash do
- before do
- # This is kind of a hack, but it will make RSpec throw way nicer exceptions
- example.metadata[:file_path] = feature_file
-
- feature.backgrounds.map(&:steps).flatten.each do |step|
- run_step(feature_file, step)
- end
- end
- feature.scenarios.each do |scenario|
- describe scenario.name, scenario.metadata_hash do
- it scenario.steps.map(&:description).join(' -> ') do
- scenario.steps.each do |step|
- run_step(feature_file, step)
- end
- end
- end
- end
- end
+ def load_steps
+ return if @steps_loaded
+ Turnip.step_dirs.each do |dir|
+ Dir.glob(File.join(dir, '**', "*steps.rb")).each { |file| load file, true }
end
+ @steps_loaded = true
end
end
end
Turnip.type = :turnip
+Turnip.step_dirs = ['spec']
-RSpec::Core::Configuration.send(:include, Turnip::Loader)
-
-RSpec.configure do |config|
- config.include Turnip::Steps
- config.pattern << ",**/*.feature"
-end
+Module.send(:include, Turnip::Define)
self.extend Turnip::DSL
+
+require "turnip/rspec"
19 lib/turnip/builder.rb
View
@@ -1,3 +1,5 @@
+require "gherkin"
+
module Turnip
class Builder
module Tags
@@ -80,14 +82,14 @@ def to_scenarios(examples)
Scenario.new(@raw).tap do |scenario|
scenario.steps = steps.map do |step|
new_description = step.description.gsub(/<([^>]*)>/) { |_| Hash[headers.zip(row)][$1] }
- Step.new(new_description, step.extra_arg, step.line)
+ Step.new(new_description, step.extra_args, step.line)
end
end
end
end
end
- class Step < Struct.new(:description, :extra_arg, :line)
+ class Step < Struct.new(:description, :extra_args, :line)
def to_s
description
end
@@ -98,8 +100,7 @@ def to_s
class << self
def build(feature_file)
Turnip::Builder.new.tap do |builder|
- formatter = Gherkin::Formatter::TagCountFormatter.new(builder, {})
- parser = Gherkin::Parser::Parser.new(formatter, true, "root", false)
+ parser = Gherkin::Parser::Parser.new(builder, true)
parser.parse(File.read(feature_file), nil, 0)
end
end
@@ -133,12 +134,16 @@ def examples(examples)
end
def step(step)
+ extra_args = []
if step.doc_string
- extra_arg = step.doc_string.value
+ extra_args.push step.doc_string.value
elsif step.rows
- extra_arg = Turnip::Table.new(step.rows.map { |row| row.cells(&:value) })
+ extra_args.push Turnip::Table.new(step.rows.map { |row| row.cells(&:value) })
end
- @current_step_context.steps << Step.new(step.name, extra_arg, step.line)
+ @current_step_context.steps << Step.new(step.name, extra_args, step.line)
+ end
+
+ def uri(*)
end
def eof
14 lib/turnip/config.rb
View
@@ -1,14 +0,0 @@
-module Turnip
- module Config
- extend self
-
- def step_dirs
- @step_dirs ||= ['spec']
- end
-
- def step_dirs=(dirs)
- @step_dirs = [] unless @step_dirs
- @step_dirs.concat(Array(dirs))
- end
- end
-end
9 lib/turnip/define.rb
View
@@ -0,0 +1,9 @@
+module Turnip
+ module Define
+ def step(expression, &block)
+ step = Turnip::StepDefinition.new(expression, &block)
+ send(:define_method, "match: #{expression}") { |description| step.match(description) }
+ send(:define_method, "execute: #{expression}", &block)
+ end
+ end
+end
11 lib/turnip/dsl.rb
View
@@ -9,7 +9,16 @@ def step(description, &block)
end
def steps_for(tag, &block)
- Turnip::StepModule.steps_for(tag, &block)
+ if tag.to_s == "global"
+ warn "[Turnip] using steps_for(:global) is deprecated, add steps to Turnip::Steps instead"
+ Turnip::Steps.module_eval(&block)
+ else
+ Module.new do
+ singleton_class.send(:define_method, :tag) { tag }
+ module_eval(&block)
+ ::RSpec.configure { |c| c.include self, tag => true }
+ end
+ end
end
end
end
15 lib/turnip/execute.rb
View
@@ -0,0 +1,15 @@
+module Turnip
+ module Execute
+ def step(description, *extra_args)
+ extra_args.concat(description.extra_args) if description.respond_to?(:extra_args)
+
+ matches = methods.map do |method|
+ next unless method.to_s.start_with?("match: ")
+ send(method.to_s, description.to_s)
+ end.compact
+ raise Turnip::Pending, description if matches.length == 0
+ raise Turnip::Ambiguous, description if matches.length > 1
+ send("execute: #{matches.first.expression}", *(matches.first.params + extra_args))
+ end
+ end
+end
16 lib/turnip/loader.rb
View
@@ -1,16 +0,0 @@
-module Turnip
- module Loader
- def load(*a, &b)
- if a.first.end_with?('.feature')
- begin
- require 'spec_helper'
- rescue LoadError
- end
- Turnip::StepLoader.load_steps
- Turnip.run(a.first)
- else
- super
- end
- end
- end
-end
81 lib/turnip/rspec.rb
View
@@ -0,0 +1,81 @@
+require "turnip"
+require "rspec"
+
+module Turnip
+ module RSpec
+
+ ##
+ #
+ # This module hooks Turnip into RSpec by duck punching the load Kernel
+ # method. If the file is a feature file, we run Turnip instead!
+ #
+ module Loader
+ def load(*a, &b)
+ if a.first.end_with?('.feature')
+ begin
+ require 'spec_helper'
+ rescue LoadError
+ end
+ Turnip.load_steps
+ Turnip::RSpec.run(a.first)
+ else
+ super
+ end
+ end
+ end
+
+ ##
+ #
+ # This module provides an improved method to run steps inside RSpec, adding
+ # proper support for pending steps, as well as nicer backtraces.
+ #
+ module Execute
+ include Turnip::Execute
+
+ def run_step(feature_file, step)
+ begin
+ step(step)
+ rescue Turnip::Pending
+ pending("No such step: '#{step}'")
+ rescue StandardError => e
+ e.backtrace.unshift "#{feature_file}:#{step.line}:in `#{step.description}'"
+ raise e
+ end
+ end
+ end
+
+ class << self
+ def run(feature_file)
+ Turnip::Builder.build(feature_file).features.each do |feature|
+ describe feature.name, feature.metadata_hash do
+ before do
+ # This is kind of a hack, but it will make RSpec throw way nicer exceptions
+ example.metadata[:file_path] = feature_file
+
+ feature.backgrounds.map(&:steps).flatten.each do |step|
+ run_step(feature_file, step)
+ end
+ end
+ feature.scenarios.each do |scenario|
+ describe scenario.name, scenario.metadata_hash do
+ it scenario.steps.map(&:description).join(' -> ') do
+ scenario.steps.each do |step|
+ run_step(feature_file, step)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+::RSpec::Core::Configuration.send(:include, Turnip::RSpec::Loader)
+
+::RSpec.configure do |config|
+ config.include Turnip::RSpec::Execute
+ config.include Turnip::Steps
+ config.pattern << ",**/*.feature"
+end
27 lib/turnip/step_loader.rb
View
@@ -1,27 +0,0 @@
-module Turnip
- module StepLoader
- extend self
-
- attr_accessor :steps_loaded
-
- def load_steps
- return if steps_loaded?
- load_step_files
- self.steps_loaded = true
- end
-
- def steps_loaded?
- @steps_loaded
- end
-
- private
-
- def load_step_files
- Turnip::Config.step_dirs.each do |dir|
- Pathname.glob(Pathname.new(dir) + '**' + "*steps.rb").each do |step_file|
- load step_file, true
- end
- end
- end
- end
-end
47 lib/turnip/step_module.rb
View
@@ -1,47 +0,0 @@
-require 'pathname'
-
-module Turnip
- module StepModule
- module DSL
- def use_steps(*tags)
- Turnip::StepModule.modules_for(*tags).each do |mod|
- include mod
- end
- end
- end
-
- extend self
-
- def clear_module_registry
- module_registry.clear
- end
-
- def modules_for(*taggings)
- taggings.map do |tag|
- module_registry[tag]
- end.flatten.uniq
- end
-
- def module_registry
- @module_registry ||= Hash.new { |hash, key| hash[key] = [] }
- end
-
- def steps_for(tag, &block)
- anon = step_module(&block)
-
- module_registry[tag] << anon
-
- RSpec.configure do |config|
- config.include anon, tag => true
- end
- end
-
- def step_module(&block)
- anon = Module.new
- anon.extend(Turnip::Define)
- anon.extend(Turnip::StepModule::DSL)
- anon.module_eval(&block)
- anon
- end
- end
-end
33 spec/define_and_execute.rb
View
@@ -0,0 +1,33 @@
+require "spec_helper"
+
+describe Turnip::Execute do
+ let(:mod) { Module.new }
+ let(:obj) { Object.new.tap { |o| o.extend Turnip::Execute; o.extend mod } }
+
+ it "defines a step method and makes it callable" do
+ mod.step("a test step") { "monkey" }
+ obj.step("a test step").should == "monkey"
+ end
+
+ it "allows placeholders to be filled and passed as arguments" do
+ mod.step("a :test step") { |test| test.upcase }
+ obj.step("a cool step").should == "COOL"
+ end
+
+ it "sends in extra arg from a builder step" do
+ mod.step("a :test step") { |test, foo| test.upcase + foo }
+ obj.step("a cool step", "foo").should == "COOLfoo"
+ end
+
+ it "can be executed with a builder step" do
+ builder_step = stub(:to_s => "a cool step", :extra_args => [])
+ mod.step("a :test step") { |test| test.upcase }
+ obj.step(builder_step).should == "COOL"
+ end
+
+ it "sends in extra arg from a builder step" do
+ builder_step = stub(:to_s => "a cool step", :extra_args => ["foo"])
+ mod.step("a :test step") { |test, foo| test.upcase + foo }
+ obj.step(builder_step).should == "COOLfoo"
+ end
+end
55 spec/dsl_spec.rb
View
@@ -1,33 +1,50 @@
require 'spec_helper'
describe Turnip::DSL do
- before do
- Turnip::StepModule.clear_module_registry
- end
-
let(:context) { stub.tap { |s| s.extend(Turnip::DSL) }}
-
+ let(:an_object) { Object.new.tap { |o| o.extend(Turnip::Execute) }}
describe '.steps_for' do
- it 'delegates to StepModule' do
- Turnip::StepModule.should_receive(:steps_for).with(:example)
- context.steps_for(:example) {}
+ before do
+ ::RSpec.stub(:configure)
end
- end
- describe '.step' do
- context 'first step defined globally' do
- it 'creates a new global entry' do
- context.step('this is a test') {}
- Turnip::StepModule.should be_registered(:global)
+ it 'creates a new module and adds steps to it' do
+ mod = context.steps_for(:foo) do
+ step("foo") { "foo" }
end
+ an_object.extend mod
+ an_object.step("foo").should == "foo"
+ end
+
+ it 'remembers the name of the module' do
+ mod = context.steps_for(:foo) {}
+ mod.tag.should == :foo
end
- context 'all other steps defined globally' do
- it 'adds more steps to the :global step module' do
- context.step('this is a test') {}
- context.step('this is another test') {}
- Turnip::StepModule.module_registry[:global].first.step_module.steps.size.should eq(2)
+ it 'tells RSpec to include the module' do
+ config = stub
+ RSpec.should_receive(:configure).and_yield(config)
+ config.should_receive(:include)
+
+ context.steps_for(:foo) {}
+ end
+
+ it 'warns of deprecation when called with :global' do
+ context.should_receive(:warn)
+ mod = context.steps_for(:global) do
+ step("foo") { "foo" }
end
+ an_object.extend Turnip::Steps
+ an_object.step("foo").should == "foo"
+ end
+ end
+
+ describe '.step' do
+ it 'adds steps to Turnip::Steps' do
+ context.step('this is a test') { "foo" }
+ context.step('this is another test') { "bar" }
+ an_object.extend Turnip::Steps
+ an_object.step("this is a test").should == "foo"
end
end
2  spec/spec_helper.rb
View
@@ -1,3 +1,3 @@
RSpec.configure do |config|
- Turnip::Config.step_dirs = 'examples'
+ Turnip.step_dirs = ['examples']
end
46 spec/step_definition_spec.rb
View
@@ -1,50 +1,6 @@
describe Turnip::StepDefinition do
let(:all_steps) { [] }
- describe ".find" do
- it "returns a step definition that matches the description" do
- all_steps << Turnip::StepDefinition.new("there are :count monsters")
- Turnip::StepDefinition.find(all_steps, "there are 23 monsters").expression.should eq("there are :count monsters")
- end
-
- it "raises an error if the match is ambiguous" do
- all_steps << Turnip::StepDefinition.new("there are :count monsters")
- all_steps << Turnip::StepDefinition.new("there are 23 monsters")
- expect { Turnip::StepDefinition.find(all_steps, "there are 23 monsters") }.to raise_error(Turnip::StepDefinition::Ambiguous)
- end
-
- it "raises an error if there is no match" do
- expect { Turnip::StepDefinition.find(all_steps, "there are 23 monsters") }.to raise_error(Turnip::StepDefinition::Pending)
- end
- end
-
- describe ".execute" do
- let(:context) { stub }
-
- it "executes a step in the given context" do
- all_steps << Turnip::StepDefinition.new("there are :count monsters") { @testing = 123 }
- Turnip::StepDefinition.execute(context, all_steps, stub(:description => "there are 23 monsters", :extra_arg => nil))
- context.instance_variable_get(:@testing).should == 123
- end
-
- it "tells the context that the step is pending" do
- context.should_receive(:pending).with("the step 'there are 23 monsters' is not implemented")
- Turnip::StepDefinition.execute(context, all_steps, stub(:description => "there are 23 monsters", :extra_arg => nil))
- end
-
- it "sends along arguments" do
- all_steps << Turnip::StepDefinition.new("there are :count monsters") { |count| @testing = count.to_i }
- Turnip::StepDefinition.execute(context, all_steps, stub(:description => "there are 23 monsters", :extra_arg => nil))
- context.instance_variable_get(:@testing).should == 23
- end
-
- it "sends along extra arguments" do
- all_steps << Turnip::StepDefinition.new("there are :count monsters") { |count, extra| @testing = extra }
- Turnip::StepDefinition.execute(context, all_steps, stub(:description => "there are 23 monsters", :extra_arg => 'foo'))
- context.instance_variable_get(:@testing).should == 'foo'
- end
- end
-
describe "#match" do
it "matches a simple step" do
step = Turnip::StepDefinition.new("there are monsters") {}
@@ -60,7 +16,7 @@
step.should match("there are 324 monsters")
step.should_not match("there are no monsters")
end
-
+
it "matches quoted placeholders" do
step = Turnip::StepDefinition.new("there is a monster named :name") {}
step.should match("there is a monster named 'Scary'")
29 spec/step_loader_spec.rb
View
@@ -1,29 +0,0 @@
-require 'turnip/step_loader'
-
-describe Turnip::StepLoader do
- describe '.load_steps' do
- context 'when the steps have not been loaded' do
- before { Turnip::StepLoader.steps_loaded = false }
-
- it 'loads all the steps' do
- Turnip::StepLoader.should_receive :load_step_files
- Turnip::StepLoader.load_steps
- end
-
- it 'marks the steps as loaded' do
- Turnip::StepLoader.stub :load_step_files
- Turnip::StepLoader.load_steps
- Turnip::StepLoader.should be_steps_loaded
- end
- end
-
- context 'when the steps have been loaded' do
- before { Turnip::StepLoader.steps_loaded = true }
-
- it 'does not reload all the steps' do
- Turnip::StepLoader.should_not_receive :load_step_files
- Turnip::StepLoader.load_steps
- end
- end
- end
-end
106 spec/step_module_spec.rb
View
@@ -1,106 +0,0 @@
-describe Turnip::StepModule do
- before(:each) do
- Turnip::StepModule.clear_module_registry
- end
-
- describe '.modules_for' do
- it 'returns the unique registered modules' do
- Turnip::StepModule.steps_for(:first) {}
- Turnip::StepModule.steps_for(:second) {}
- Turnip::StepModule.modules_for(:first, :second).size.should eq(2)
- end
-
- it 'returns the unique registered modules with use_steps' do
- Turnip::StepModule.steps_for(:first) {}
- Turnip::StepModule.steps_for(:second) { use_steps :first }
- Turnip::StepModule.steps_for(:third) { use_steps :first, :second }
- Turnip::StepModule.modules_for(:third).size.should eq(3)
- end
-
- it 'ignores a circular step dependency' do
- Turnip::StepModule.steps_for(:first) { use_steps :second }
- Turnip::StepModule.steps_for(:second) { use_steps :first }
- expect do
- Turnip::StepModule.modules_for(:second)
- end.should_not raise_error
- end
-
- it 'orders the step modules from use_steps before the using step module' do
- Turnip::StepModule.steps_for(:first) {}
- Turnip::StepModule.steps_for(:second) { use_steps :first }
- Turnip::StepModule.modules_for(:second).first.should == Turnip::StepModule.module_registry[:first].first.step_module
- Turnip::StepModule.modules_for(:second).last.should == Turnip::StepModule.module_registry[:second].first.step_module
- end
- end
-
- describe '.steps_for' do
- it 'registers the given tag' do
- Turnip::StepModule.steps_for(:first) {}
- Turnip::StepModule.should be_registered(:first)
- end
-
- it 'registers an anonymous modle for the given tags' do
- Turnip::StepModule.steps_for(:first) {}
- Turnip::StepModule.module_registry[:first].first.step_module.should be_instance_of(Module)
- end
-
- end
-
- describe '.step_module' do
- subject do
- Turnip::StepModule.step_module do
- def marker; end
- end
- end
-
- it 'extends the steps DSL' do
- subject.should be_kind_of(Turnip::StepModule::DSL)
- end
-
- it 'creates an anonymous module' do
- # Check for empty string to allow for rbx
- subject.name.should satisfy {|name| name.nil? || name.empty? }
- end
-
- it 'executes the block in the module' do
- # Map to sym to allow for rbx
- subject.instance_methods.map(&:to_sym).should include(:marker)
- end
- end
-
- describe Turnip::StepModule::DSL do
- describe '.step' do
- it 'registers the step for the module' do
- mod = Module.new do
- extend Turnip::StepModule::DSL
- step('example') { true }
- end
- mod.steps.first.expression.should eq('example')
- end
- end
-
- describe '.placeholder' do
- before { Turnip::Placeholder.send(:placeholders).clear }
-
- it 'registers the placeholder globally' do
- mod = Module.new do
- extend Turnip::StepModule::DSL
- placeholder('example') { true }
- end
- Turnip::Placeholder.send(:placeholders).should have_key('example')
- end
- end
-
- describe '.use_steps' do
- it "updates the list of used steps" do
- mod = Module.new do
- extend Turnip::StepModule::DSL
- step('example') { true }
-
- use_steps :other_steps
- end
- mod.uses_steps.should include(:other_steps)
- end
- end
- end
-end

No commit comments for this range

Something went wrong with that request. Please try again.