lib/ex_matchers/size.ex

defmodule ExMatchers.Size do
  @moduledoc false

  import ExUnit.Assertions
  import ExMatchers.Custom

  defprotocol SizeMatcher do
    @fallback_to_any true
    def to_match(value)
    def to_match(value, size)
    def to_not_match(value)
    def to_not_match(value, size)
  end

  defimpl SizeMatcher, for: Map do
    def to_match(value) do
      to_match(value, 0)
    end
    def to_match(value, size) do
      assert map_size(value) == size
    end
    def to_not_match(value) do
      to_not_match(value, 0)
    end
    def to_not_match(value, size) do
      refute map_size(value) == size
    end
  end

  defimpl SizeMatcher, for: Tuple do
    def to_match(value) do
      to_match(value, 0)
    end
    def to_match(value, size) do
      assert tuple_size(value) == size
    end
    def to_not_match(value) do
      to_not_match(value, 0)
    end
    def to_not_match(value, size) do
      refute tuple_size(value) == size
    end
  end

  defimpl SizeMatcher, for: BitString do
    def to_match(value) do
      to_match(value, 0)
    end
    def to_match(value, size) do
      assert byte_size(String.trim(value)) == size
    end
    def to_not_match(value) do
      to_not_match(value, 0)
    end
    def to_not_match(value, size) do
      refute byte_size(String.trim(value)) == size
    end
  end

  defimpl SizeMatcher, for: List do
    def to_match(value) do
      to_match(value, 0)
    end
    def to_match(value, size) do
      assert length(value) == size
    end
    def to_not_match(value) do
      to_not_match(value, 0)
    end
    def to_not_match(value, size) do
      refute length(value) == size
    end
  end

  defimpl SizeMatcher, for: MapSet do
    def to_match(value) do
      to_match(value, 0)
    end
    def to_match(value, size) do
      assert MapSet.size(value) == size
    end
    def to_not_match(value) do
      to_not_match(value, 0)
    end
    def to_not_match(value, size) do
      refute MapSet.size(value) == size
    end
  end

  defimpl SizeMatcher, for: Range do
    def to_match(value) do
      to_match(value, 0)
    end
    def to_match(value, size) do
      assert Enum.count(value) == size
    end
    def to_not_match(value) do
      to_not_match(value, 0)
    end
    def to_not_match(value, size) do
      refute Enum.count(value) == size
    end
  end

  defimpl SizeMatcher, for: Atom do
    def to_match(nil), do: true
    def to_match(nil, _size), do: true
    def to_not_match(nil), do: flunk "Nil is empty"
    def to_not_match(nil, _size), do: flunk "Nil is empty"
  end

  defimpl SizeMatcher, for: Any do
    def to_match(value) do
      flunk "Size not supported for #{inspect(value)}"
    end
    def to_match(value, size) do
      flunk "Size not supported for #{inspect(value)} with size #{size}"
    end
    def to_not_match(value) do
      flunk "Size not supported for #{inspect(value)}"
    end
    def to_not_match(value, size) do
      flunk "Size not supported for #{inspect(value)} with size #{size}"
    end
  end

  defmatcher be_empty,           matcher: SizeMatcher
  defmatcher have_items(number), matcher: SizeMatcher

end