require 'cucumber/step_definition'
require 'cucumber/core_ext/instance_exec'
module Cucumber
# This is the main interface for registering step definitions, which is done
# from *_steps.rb files. This module is included right at the top-level
# so #register_step_definition (and more interestingly - its aliases) are
# available from the top-level.
module StepMother
attr_writer :snippet_generator
class Undefined < StandardError
attr_reader :step_name
def initialize(step_name)
super %{Undefined step: "#{step_name}"}
@step_name = step_name
end
Cucumber::EXCEPTION_STATUS[self] = :undefined
end
class Pending < StandardError
Cucumber::EXCEPTION_STATUS[self] = :pending
end
# Raised when a step matches 2 or more StepDefinition
class Ambiguous < StandardError
def initialize(step_name, step_definitions)
message = "Ambiguous match of \"#{step_name}\":\n\n"
message << step_definitions.map{|sd| sd.to_backtrace_line}.join("\n")
message << "\n\n"
super(message)
end
end
# Raised when 2 or more StepDefinition have the same Regexp
class Redundant < StandardError
def initialize(step_def_1, step_def_2)
message = "Multiple step definitions have the same Regexp:\n\n"
message << step_def_1.to_backtrace_line << "\n"
message << step_def_2.to_backtrace_line << "\n\n"
super(message)
end
end
# Registers a new StepDefinition. This method is aliased
# to Given, When and Then.
#
# See Cucumber#alias_steps for details on how to
# create your own aliases.
#
# The +&proc+ gets executed in the context of a world
# object, which is defined by #World. A new world
# object is created for each scenario and is shared across
# step definitions within that scenario.
def register_step_definition(regexp, &proc)
step_definition = StepDefinition.new(regexp, &proc)
step_definitions.each do |already|
raise Redundant.new(already, step_definition) if already.match(regexp)
end
step_definitions << step_definition
step_definition
end
def world(scenario, &proc)
world = new_world
begin
(@before_procs ||= []).each do |proc|
world.cucumber_instance_exec(false, 'Before', scenario, &proc)
end
yield world
ensure
(@after_procs ||= []).each do |proc|
world.cucumber_instance_exec(false, 'After', scenario, &proc)
end
end
end
# Registers a Before proc. You can call this method as many times as you
# want (typically from ruby scripts under support).
def Before(&proc)
(@before_procs ||= []) << proc
end
def After(&proc)
(@after_procs ||= []) << proc
end
# Registers a World proc. You can call this method as many times as you
# want (typically from ruby scripts under support).
def World(&proc)
(@world_procs ||= []) << proc
end
# Creates a new world instance
def new_world #:nodoc:
world = Object.new
(@world_procs ||= []).each do |proc|
world = proc.call(world)
end
world.extend(WorldMethods)
world.__cucumber_step_mother = self
world.extend(::Spec::Matchers) if defined?(::Spec::Matchers)
world
end
# Looks up the StepDefinition that matches +step_name+
def step_definition(step_name) #:nodoc:
found = step_definitions.select do |step_definition|
step_definition.match(step_name)
end
raise Undefined.new(step_name) if found.empty?
raise Ambiguous.new(step_name, found) if found.size > 1
found[0]
end
def step_definitions
@step_definitions ||= []
end
def snippet_text(step_keyword, step_name)
@snippet_generator.snippet_text(step_keyword, step_name)
end
module WorldMethods #:nodoc:
attr_writer :__cucumber_step_mother, :__cucumber_current_step
# Call a step from within a step definition
def __cucumber_invoke(name, *multiline_arguments)
begin
# TODO: Very similar to code in Step. Refactor. Get back StepInvocation?
# Make more similar to JBehave?
step_definition = @__cucumber_step_mother.step_definition(name)
matched_args = step_definition.matched_args(name)
args = (matched_args + multiline_arguments)
step_definition.execute(name, self, *args)
rescue Exception => e
@__cucumber_current_step.exception = e
raise e
end
end
def table(text, file=nil, line=0)
@table_parser ||= Parser::TableParser.new
@table_parser.parse_or_fail(text.strip, file, line)
end
def pending(message = "TODO")
if block_given?
begin
yield
rescue Exception => e
raise Pending.new(message)
end
raise Pending.new("Expected pending '#{message}' to fail. No Error was raised. No longer pending?")
else
raise Pending.new(message)
end
end
end
end
end