# frozen_string_literal: true module ConvenientService module RSpec module Matchers module Custom ## # expect { method.call } # .to call_chain_next.on(method) # .with_arguments(*args, **kwargs, &block) # .and_return_its_value # class CallChainNext include ::RSpec::Expectations include ::RSpec::Matchers include ::RSpec::Mocks::ExampleMethods def matches?(block_expectation) @block_expectation = block_expectation value = nil expect { value = block_expectation.call }.to change(method, :chain_called?).from(false).to(true) if used_with_arguments? expect(method.chain_args).to eq(expected_args) expect(method.chain_kwargs).to eq(expected_kwargs) expect(method.chain_block).to eq(expected_block) end if used_and_return_its_value? expect(value).to eq(method.chain_value) end true end ## # https://github.com/rspec/rspec-expectations/blob/main/lib/rspec/matchers/built_in/change.rb#L50 # def does_not_match?(block_expectation) @block_expectation = block_expectation value = nil ## # NOTE: `expect { value = block_expectation.call }.not_to change(method, :chain_called?).from(false).to(true)` raises the following error: # # NotImplementedError: # `expect { }.not_to change { }.to()` is not supported # # That is why `expect(method.chain_called?).to eq(false)` is introduced. # expect(method.chain_called?).to eq(false) expect { value = block_expectation.call }.not_to change(method, :chain_called?) if used_with_arguments? expect(method.chain_args).not_to eq(expected_args) expect(method.chain_kwargs).not_to eq(expected_kwargs) expect(method.chain_block).not_to eq(expected_block) end if used_and_return_its_value? expect(value).not_to eq(method.chain_value) end true end def supports_block_expectations? true end def description "call `chain.next`" end def failure_message "expected to call `chain.next`" end def failure_message_when_negated "expected NOT to call `chain.next`" end def on(method) chain[:method] = method self end def with_arguments(*args, **kwargs, &block) chain[:with_arguments] = {args: args, kwargs: kwargs, block: block} self end def and_return_its_value chain[:and_return_its_value] = true self end private attr_reader :block_expectation def used_with_arguments? chain.key?(:with_arguments) end def used_and_return_its_value? chain.key?(:and_return_its_value) end def chain @chain ||= {} end def method @method ||= chain[:method] end def args @args ||= chain.dig(:with_arguments, :args) || [] end alias_method :expected_args, :args def kwargs @kwargs ||= chain.dig(:with_arguments, :kwargs) || {} end alias_method :expected_kwargs, :kwargs ## # NOTE: `if defined?` is used in order to cache `nil` if needed. # def block return @block if defined? @block @block = chain.dig(:with_arguments, :block) end alias_method :expected_block, :block end end end end end