class RSpec::Matchers::BuiltIn::ContainExactly

Not intended to be instantiated directly.
Provides the implementation for ‘contain_exactly` and `match_array`.
@api private
rubocop:disable Metrics/ClassLength

def actual_collection_line

def actual_collection_line
  message_line('actual collection contained', actual)
end

def best_solution

def best_solution
  @best_solution ||= pairings_maximizer.find_best_solution
end

def convert_actual_to_an_array

def convert_actual_to_an_array
  if actual.respond_to?(:to_ary)
    @actual = actual.to_ary
  elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
    @actual = actual.to_a
  else
    false
  end
end

def describe_collection(collection, surface_descriptions=false)

def describe_collection(collection, surface_descriptions=false)
  if surface_descriptions
    "#{description_of(safe_sort(surface_descriptions_in collection))}\n"
  else
    "#{description_of(safe_sort(collection))}\n"
  end
end

def description

Returns:
  • (String) -

Other tags:
    Api: - private
def description
  list = EnglishPhrasing.list(surface_descriptions_in(expected))
  "contain exactly#{list}"
end

def expected_collection_line

def expected_collection_line
  message_line('expected collection contained', expected, true)
end

def extra_elements_line

def extra_elements_line
  message_line('the extra elements were', extra_items)
end

def extra_items

def extra_items
  @extra_items ||= best_solution.unmatched_actual_indexes.map do |index|
    actual[index]
  end
end

def failure_message

Returns:
  • (String) -

Other tags:
    Api: - private
def failure_message
  if Array === actual
    generate_failure_message
  else
    "expected a collection that can be converted to an array with " \
    "`#to_ary` or `#to_a`, but got #{actual_formatted}"
  end
end

def failure_message_when_negated

Returns:
  • (String) -

Other tags:
    Api: - private
def failure_message_when_negated
  list = EnglishPhrasing.list(surface_descriptions_in(expected))
  "expected #{actual_formatted} not to contain exactly#{list}"
end

def generate_failure_message

def generate_failure_message
  message = expected_collection_line
  message += actual_collection_line
  message += missing_elements_line unless missing_items.empty?
  message += extra_elements_line unless extra_items.empty?
  message
end

def match(_expected, _actual)

def match(_expected, _actual)
  return false unless convert_actual_to_an_array
  match_when_sorted? || (extra_items.empty? && missing_items.empty?)
end

def match_when_sorted?

works, so it's worth a try.
the slowness of the full matching algorithm, and in common cases this
or matchers as expected items), but it's practically free compared to
This cannot always work (e.g. when dealing with unsortable items,
def match_when_sorted?
  values_match?(safe_sort(expected), safe_sort(actual))
end

def matches?(actual)

def matches?(actual)
  @pairings_maximizer = nil
  @best_solution = nil
  @extra_items = nil
  @missing_items = nil
  super(actual)
end

def message_line(prefix, collection, surface_descriptions=false)

def message_line(prefix, collection, surface_descriptions=false)
  "%-32s%s" % [prefix + ':',
               describe_collection(collection, surface_descriptions)]
end

def missing_elements_line

def missing_elements_line
  message_line('the missing elements were', missing_items, true)
end

def missing_items

def missing_items
  @missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
    expected[index]
  end
end

def pairings_maximizer

def pairings_maximizer
  @pairings_maximizer ||= begin
    expected_matches = Hash[Array.new(expected.size) { |i| [i, []] }]
    actual_matches   = Hash[Array.new(actual.size)   { |i| [i, []] }]
    expected.each_with_index do |e, ei|
      actual.each_with_index do |a, ai|
        next unless values_match?(e, a)
        expected_matches[ei] << ai
        actual_matches[ai] << ei
      end
    end
    PairingsMaximizer.new(expected_matches, actual_matches)
  end
end

def safe_sort(array)

def safe_sort(array)
  array.sort
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
  array
end

def to_a_disallowed?(object)

:nocov:
def to_a_disallowed?(object)
  case object
  when NilClass, String then true
  else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
  end
end

def to_a_disallowed?(object)

def to_a_disallowed?(object)
  NilClass === object
end