lib/test_prof/cops/rspec/aggregate_examples/its.rb
# frozen_string_literal: true module RuboCop module Cop module RSpec class AggregateExamples < ::RuboCop::Cop::Cop # @example `its` # # # Supports regular `its` call with an attribute/method name, # # or a chain of methods expressed as a string with dots. # # its(:one) { is_expected.to be(true) } # its('two') { is_expected.to be(false) } # its('phone_numbers.size') { is_expected.to be 2 } # # @example `its` with single-element array argument # # # Also supports single-element array argument. # # its(['headers']) { is_expected.to include(encoding: 'text') } # # @example `its` with multi-element array argument is ambiguous # # # Does not support `its` with multi-element array argument due to # # an ambiguity. Transformation depends on the type of the subject: # # - a Hash: `hash[element1][element2]...` # # - and arbitrary type: `hash[element1, element2, ...]` # # It is impossible to infer the type to propose a proper correction. # # its(['ambiguous', 'elements']) { ... } # # @example `its` with metadata # # its('header', html: true) { is_expected.to include(text: 'hello') } # module Its extend RuboCop::NodePattern::Macros private # It's impossible to aggregate `its` body as is, it needs to be # converted to `expect(subject.something).to ...` def new_body(node) return super unless its?(node) transform_its(node.body, node.send_node.arguments) end def transform_its(body, arguments) argument = arguments.first replacement = case argument.type when :array key = argument.values.first "expect(subject[#{key.source}])" else property = argument.value "expect(subject.#{property})" end body.source.gsub(/is_expected|are_expected/, replacement) end def example_metadata(example) return super unless its?(example.send_node) # First parameter to `its` is not metadata. example.send_node.arguments[1..-1] end def its?(node) node.method_name == :its end # In addition to base definition, matches examples with: # - no `its` with an multiple-element array argument due to # an ambiguity, when SUT can be a hash, and result will be defined # by calling `[]` on SUT subsequently, e.g. `subject[one][two]`, # or any other type of object implementing `[]`, and then all the # array arguments are passed to `[]`, e.g. `subject[one, two]`. def_node_matcher :example_for_autocorrect?, <<-PATTERN [ #super !#its_with_multi_element_array_argument? !#its_with_send_or_var_argument? ] PATTERN def_node_matcher :its_with_multi_element_array_argument?, <<-PATTERN (block (send nil? :its (array _ _ ...)) ...) PATTERN def_node_matcher :its_with_send_or_var_argument?, <<-PATTERN (block (send nil? :its { send lvar ivar cvar gvar const }) ...) PATTERN end end end end end