lib/minitest_to_rspec/input/model/call.rb
# frozen_string_literal: true
require 'minitest_to_rspec/input/model/base'
module MinitestToRspec
module Input
module Model
# Data object. Represents a `:call` s-expression.
class Call < Base
attr_reader :original
def initialize(exp)
assert_sexp_type(:call, exp)
@exp = exp.dup
@original = exp.dup
end
class << self
def assert_difference?(exp)
exp.sexp_type == :call && new(exp).assert_difference?
end
def assert_no_difference?(exp)
exp.sexp_type == :call && new(exp).assert_no_difference?
end
def assert_nothing_raised?(exp)
exp.sexp_type == :call && new(exp).assert_nothing_raised?
end
def assert_raise?(exp)
exp.sexp_type == :call && new(exp).assert_raise?
end
def assert_raises?(exp)
exp.sexp_type == :call && new(exp).assert_raises?
end
def refute_raise?(exp)
exp.sexp_type == :call && new(exp).refute_raise?
end
def refute_raises?(exp)
exp.sexp_type == :call && new(exp).refute_raises?
end
def method_name?(exp, name)
exp.sexp_type == :call && new(exp).method_name.to_s == name.to_s
end
end
def arguments
@exp[3..-1] || []
end
def argument_types
arguments.map(&:sexp_type)
end
def assert_difference?
return false unless method_name == :assert_difference
[[:str], %i[str lit]].include?(argument_types)
end
def assert_no_difference?
method_name == :assert_no_difference &&
arguments.length == 1 &&
arguments[0].sexp_type == :str
end
def assert_nothing_raised?
method_name == :assert_nothing_raised && arguments.empty?
end
def assert_raise?
method_name == :assert_raise && raise_error_args?
end
def assert_raises?
method_name == :assert_raises && raise_error_args?
end
def refute_raise?
method_name == :refute_raise && raise_error_args?
end
def refute_raises?
method_name == :refute_raises && raise_error_args?
end
def calls_in_receiver_chain
receiver_chain.each_with_object([]) do |e, a|
next unless sexp_type?(:call, e)
a << self.class.new(e)
end
end
def find_call_in_receiver_chain(method_names)
name_array = [method_names].flatten
calls_in_receiver_chain.find { |i|
name_array.include?(i.method_name)
}
end
def method_name
@exp[2]
end
def num_arguments
arguments.length
end
def one_string_argument?
arguments.length == 1 && string?(arguments[0])
end
# Returns true if arguments can be processed into RSpec's `raise_error`
# matcher. When the last argument is a string, it represents the
# assertion failure message, which will be discarded later.
def raise_error_args?
arg_types = arguments.map(&:sexp_type)
[[], [:str], [:const], %i[const str], [:colon2]].include?(arg_types)
end
def receiver
@exp[1]
end
# Consider the following chain of method calls:
#
# @a.b.c
#
# whose S-expression is
#
# s(:call, s(:call, s(:call, nil, :a), :b), :c)
#
# the "receiver chain" is
#
# [
# s(:call, s(:call, nil, :a), :b),
# s(:call, nil, :a),
# nil
# ]
#
# The order of the returned array matches the order in which
# messages are received, i.e. the order of execution.
#
# Note that the final receiver `nil` is included. This `nil`
# represents the implicit receiver, e.g. `self` or `main`.
#
def receiver_chain
receivers = []
ptr = @exp
while sexp_type?(:call, ptr)
receivers << ptr[1]
ptr = ptr[1]
end
receivers
end
def receiver_chain_include?(method_name)
receiver_chain.compact.any? { |r|
self.class.method_name?(r, method_name)
}
end
def require_test_helper?
method_name == :require &&
one_string_argument? &&
arguments[0][1] == 'test_helper'
end
def question_mark_method?
method_name.to_s.end_with?('?')
end
private
def string?(exp)
exp.sexp_type == :str
end
end
end
end
end