summaryrefslogtreecommitdiff
path: root/spec/support/matchers/be_sorted.rb
blob: b0ab93efbb217717a4df50604a30c25fdd4d783a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# frozen_string_literal: true

# Assert that this collection is sorted by argument and order
#
# By default, this checks that the collection is sorted ascending
# but you can check order by specific field and order by passing
# them, either as arguments, or using the fluent interface, eg:
#
# ```
# # Usage examples:
# expect(collection).to be_sorted
# expect(collection).to be_sorted(:field)
# expect(collection).to be_sorted(:field, :desc)
# expect(collection).to be_sorted.asc
# expect(collection).to be_sorted.desc.by(&:field)
# expect(collection).to be_sorted.by(&:field).desc
# expect(collection).to be_sorted.by { |x| [x.foo, x.bar] }
# ```
RSpec::Matchers.define :be_sorted do |on = :itself, order = :asc|
  def by(&block)
    @comparator = block
    self
  end

  def asc
    @direction = :asc
    self
  end

  def desc
    @direction = :desc
    self
  end

  def format_with(proc)
    @format_with = proc
    self
  end

  define_method :comparator do
    @comparator || on
  end

  define_method :descending? do
    (@direction || order.to_sym) == :desc
  end

  def order(items)
    descending? ? items.reverse : items
  end

  def sort(items)
    items.sort_by(&comparator)
  end

  match do |actual|
    next true unless actual.present? # empty collection is sorted

    actual = actual.to_a if actual.respond_to?(:to_a) && !actual.respond_to?(:sort_by)

    @got = actual
    @expected = order(sort(actual))

    @expected == actual
  end

  def failure_message
    "Expected #{show(@expected)}, got #{show(@got)}"
  end

  def show(things)
    if @format_with
      things.map(&@format_with)
    else
      things
    end
  end
end