|
- # frozen_string_literal: true
-
- require 'cucumber/step_match'
- require 'cucumber/glue/invoke_in_world'
-
- module Cucumber
- module Glue
- # A Step Definition holds a Regexp pattern and a Proc, and is
- # typically created by calling {Dsl#register_rb_step_definition Given, When or Then}
- # in the step_definitions Ruby files.
- #
- # Example:
- #
- # Given /I have (\d+) cucumbers in my belly/ do
- # # some code here
- # end
- #
- class StepDefinition
- class MissingProc < StandardError
- def message
- 'Step definitions must always have a proc or symbol'
- end
- end
-
- class << self
- def new(id, registry, string_or_regexp, proc_or_sym, options)
- raise MissingProc if proc_or_sym.nil?
-
- super id, registry, registry.create_expression(string_or_regexp), create_proc(proc_or_sym, options)
- end
-
- private
-
- def create_proc(proc_or_sym, options)
- return proc_or_sym if proc_or_sym.is_a?(Proc)
- raise ArgumentError unless proc_or_sym.is_a?(Symbol)
-
- message = proc_or_sym
- target_proc = parse_target_proc_from(options)
- patch_location_onto lambda { |*args|
- target = instance_exec(&target_proc)
- target.send(message, *args)
- }
- end
-
- def patch_location_onto(block)
- location = Core::Test::Location.of_caller(5)
- block.define_singleton_method(:source_location) { [location.file, location.line] }
- block
- end
-
- def parse_target_proc_from(options)
- return -> { self } unless options.key?(:on)
-
- target = options[:on]
- case target
- when Proc
- target
- when Symbol
- -> { send(target) }
- else
- -> { raise ArgumentError, 'Target must be a symbol or a proc' }
- end
- end
- end
-
- attr_reader :id, :expression, :registry
-
- def initialize(id, registry, expression, proc)
- raise 'No regexp' if expression.is_a?(Regexp)
-
- @id = id
- @registry = registry
- @expression = expression
- @proc = proc
- # @registry.available_step_definition(regexp_source, location)
- end
-
- def to_envelope
- Cucumber::Messages::Envelope.new(
- step_definition: Cucumber::Messages::StepDefinition.new(
- id: id,
- pattern: Cucumber::Messages::StepDefinitionPattern.new(
- source: expression.source.to_s,
- type: expression_type
- ),
- source_reference: Cucumber::Messages::SourceReference.new(
- uri: location.file,
- location: Cucumber::Messages::Location.new(
- line: location.lines.first
- )
- )
- )
- )
- end
-
- def expression_type
- return Cucumber::Messages::StepDefinitionPatternType::CUCUMBER_EXPRESSION if expression.is_a?(CucumberExpressions::CucumberExpression)
-
- Cucumber::Messages::StepDefinitionPatternType::REGULAR_EXPRESSION
- end
-
- # @api private
- def to_hash
- type = expression.is_a?(CucumberExpressions::RegularExpression) ? 'regular expression' : 'cucumber expression'
- regexp = expression.regexp
- flags = ''
- flags += 'm' if (regexp.options & Regexp::MULTILINE) != 0
- flags += 'i' if (regexp.options & Regexp::IGNORECASE) != 0
- flags += 'x' if (regexp.options & Regexp::EXTENDED) != 0
- {
- source: {
- type: type,
- expression: expression.source
- },
- regexp: {
- source: regexp.source,
- flags: flags
- }
- }
- end
-
- # @api private
- def ==(other)
- expression.source == other.expression.source
- end
-
- # @api private
- def arguments_from(step_name)
- @expression.match(step_name)
- end
-
- # @api private
- # TODO: inline this and step definition just be a value object
- def invoke(args)
- InvokeInWorld.cucumber_instance_exec_in(@registry.current_world, true, @expression.to_s, *args, &@proc)
- rescue ArityMismatchError => e
- e.backtrace.unshift(backtrace_line)
- raise e
- end
-
- # @api private
- def backtrace_line
- "#{location}:in `#{@expression}'"
- end
-
- # @api private
- def file_colon_line
- case @proc
- when Proc
- location.to_s
- when Symbol
- ":#{@proc}"
- end
- end
-
- # The source location where the step definition can be found
- def location
- @location ||= Cucumber::Core::Test::Location.from_source_location(*@proc.source_location)
- end
-
- # @api private
- def file
- @file ||= location.file
- end
- end
- end
- end
|