# frozen_string_literal: true

# rubocop:disable Style/ClassVars
require 'spec_helper'
require 'cucumber/glue/registry_and_more'

module Cucumber
  module Glue
    describe StepDefinition do
      let(:id)             { double }
      let(:user_interface) { double('user interface') }
      let(:support_code)   { Cucumber::Runtime::SupportCode.new(user_interface) }
      let(:registry)       { support_code.registry }
      let(:test_case)      { double('scenario', language: 'en').as_null_object }
      let(:dsl) do
        registry
        Object.new.extend(Cucumber::Glue::Dsl)
      end

      before do
        registry.begin_scenario(test_case)
        @@inside = nil
      end

      def run_step(text)
        step_match(text).invoke(MultilineArgument::None.new)
      end

      def step_match(text)
        StepMatchSearch.new(registry.method(:step_matches), Configuration.default).call(text).first
      end

      it 'allows calling of other steps' do
        dsl.Given(/Outside/) do
          step 'Inside'
        end
        dsl.Given(/Inside/) do
          @@inside = true
        end

        run_step 'Outside'

        expect(@@inside).to be true
      end

      it 'allows calling of other steps with inline arg' do
        dsl.Given(/Outside/) do
          step 'Inside', table([['inside']])
        end
        dsl.Given(/Inside/) do |t|
          @@inside = t.raw[0][0]
        end

        run_step 'Outside'

        expect(@@inside).to eq 'inside'
      end

      context 'mapping to world methods' do
        it 'calls a method on the world when specified with a symbol' do
          expect(registry.current_world).to receive(:with_symbol)

          dsl.Given(/With symbol/, :with_symbol)

          run_step 'With symbol'
        end

        it 'calls a method on a specified object' do
          target = double('target')

          allow(registry.current_world).to receive(:target) { target }

          dsl.Given(/With symbol on block/, :with_symbol, on: -> { target })

          expect(target).to receive(:with_symbol)

          run_step 'With symbol on block'
        end

        it 'calls a method on a specified world attribute' do
          target = double('target')

          allow(registry.current_world).to receive(:target) { target }

          dsl.Given(/With symbol on symbol/, :with_symbol, on: :target)

          expect(target).to receive(:with_symbol)

          run_step 'With symbol on symbol'
        end

        it 'has the correct location' do
          dsl.Given(/With symbol/, :with_symbol)
          expect(step_match('With symbol').file_colon_line).to eq "spec/cucumber/glue/step_definition_spec.rb:#{__LINE__ - 1}"
        end
      end

      it 'raises UndefinedDynamicStep when inside step is not defined' do
        dsl.Given(/Outside/) do
          step 'Inside'
        end

        expect(-> { run_step 'Outside' }).to raise_error(Cucumber::UndefinedDynamicStep)
      end

      it 'raises UndefinedDynamicStep when an undefined step is parsed dynamically' do
        dsl.Given(/Outside/) do
          steps %(
            Given Inside
          )
        end

        expect(-> { run_step 'Outside' }).to raise_error(Cucumber::UndefinedDynamicStep)
      end

      it 'raises UndefinedDynamicStep when an undefined step with doc string is parsed dynamically' do
        dsl.Given(/Outside/) do
          steps %(
            Given Inside
            """
            abc
            """
          )
        end

        expect(-> { run_step 'Outside' }).to raise_error(Cucumber::UndefinedDynamicStep)
      end

      it 'raises UndefinedDynamicStep when an undefined step with data table is parsed dynamically' do
        dsl.Given(/Outside/) do
          steps %(
            Given Inside
             | a |
             | 1 |
          )
        end

        expect(-> { run_step 'Outside' }).to raise_error(Cucumber::UndefinedDynamicStep)
      end

      it 'allows forced pending' do
        dsl.Given(/Outside/) do
          pending('Do me!')
        end

        expect(-> { run_step 'Outside' }).to raise_error(Cucumber::Pending, 'Do me!')
      end

      it 'raises ArityMismatchError when the number of capture groups differs from the number of step arguments' do
        dsl.Given(/No group: \w+/) do |arg|
        end

        expect(-> { run_step 'No group: arg' }).to raise_error(Cucumber::Glue::ArityMismatchError)
      end

      it 'does not modify the step_match arg when arg is modified in a step' do
        dsl.Given(/My car is (.*)/) do |colour|
          colour << 'xxx'
        end

        step_name = 'My car is white'
        step_args = step_match(step_name).args

        expect(-> { run_step step_name }).not_to change { step_args.first } # rubocop:disable Lint/AmbiguousBlockAssociation
      end

      context 'with ParameterType' do
        class Actor
          attr_reader :name
          attr_writer :name
          def initialize(name)
            @name = name
          end
        end

        before(:each) do
          @actor = nil

          dsl.ParameterType(
            name: 'actor',
            regexp: /[A-Z]{1}[a-z]+/,
            transformer: ->(name) { @actor = Actor.new(name) }
          )
        end

        it 'uses the instance created by the ParameterType transformer proc' do
          dsl.Given 'capture this: {actor}' do |arg|
            expect(arg.name).to eq 'Anjie'
            expect(arg).to be @actor
          end

          run_step 'capture this: Anjie'
        end

        it 'does not modify the step_match arg when arg is modified in a step' do
          dsl.Given 'capture this: {actor}' do |actor|
            actor.name = 'Dave'
          end

          run_step 'capture this: Anjie'
          step_args = step_match('capture this: Anjie').args
          expect(step_args[0].name).to_not eq 'Dave'
          expect(step_args[0].name).to eq 'Anjie'
        end
      end

      context 'allows log' do
        it 'calls "attach" with the correct media type' do
          expect(user_interface).to receive(:attach).with('wasup', 'text/x.cucumber.log+plain')
          dsl.Given(/Loud/) do
            log 'wasup'
          end
          run_step 'Loud'
        end

        it 'calls `to_s` if the message is not a String' do
          expect(user_interface).to receive(:attach).with('["Not", 1, "string"]', 'text/x.cucumber.log+plain')
          dsl.Given(/Loud/) do
            log ['Not', 1, 'string']
          end
          run_step 'Loud'
        end
      end

      it 'recognizes $arg style captures' do
        arg_value = 'up'
        dsl.Given 'capture this: {word}' do |arg|
          expect(arg).to eq arg_value
        end
        run_step 'capture this: up'
      end

      it 'has a JSON representation of the signature' do
        expect(StepDefinition.new(
          id,
          registry,
          /I CAN HAZ (\d+) CUKES/i,
          -> {},
          {}
        ).to_hash).to eq(
          source: {
            type: 'regular expression',
            expression: 'I CAN HAZ (\\d+) CUKES'
          },
          regexp: {
            source: 'I CAN HAZ (\\d+) CUKES', flags: 'i'
          }
        )
      end
    end
  end
end
# rubocop:enable Style/ClassVars
