lib/machete/assertions.ex

defmodule Machete.Assertions do
  @moduledoc """
  Macros for use within the context of ExUnit tests, to provide `~>` awareness to `assert/1` and
  `refute/1` macros. Because macros cannot be defined in multiple modules, proper use of this
  module requires the user to take `ExUnit.Assertions`' version of `assert/1` and `refute/1` out
  of scope, and to allow this module's versions of those macros to call through to
  `ExUnit.Assertions`' version for conditions other than `~>`. See `Machete.__using__/1` for
  details.
  """

  @doc """
  A custom implementation of the `assert/1` macro to provide support for useful errors on `~>`
  mismatches; all other patterns are passed through to `ExUnit.Assertions.assert/1`
  """
  defmacro assert({:~>, meta, [left, right]} = assertion) do
    code = Macro.escape({:assert, meta, [assertion]}, prune_metadata: true)

    quote bind_quoted: [left: left, right: right, code: code] do
      case(left ~>> right) do
        [] ->
          true

        mismatches ->
          formatted_mismatches = Machete.Mismatch.format_mismatches(mismatches, "  ")

          raise ExUnit.AssertionError,
            context: :~>,
            expr: code,
            message: "Assertion with ~> failed\n\nMismatches:\n\n#{formatted_mismatches}"
      end
    end
  end

  defmacro assert(assert) do
    quote do
      ExUnit.Assertions.assert(unquote(assert))
    end
  end

  @doc """
  A custom implementation of the `refute/1` macro to provide support for useful errors on `~>`
  mismatches; all other patterns are passed through to `ExUnit.Assertions.refute/1`
  """
  defmacro refute({:~>, meta, [_left, _right]} = assertion) do
    code = Macro.escape({:refute, meta, [assertion]}, prune_metadata: true)

    quote do
      if unquote(assertion) do
        raise ExUnit.AssertionError,
          context: :~>,
          expr: unquote(code),
          message: "Refute with ~> failed, both sides match"
      else
        false
      end
    end
  end

  defmacro refute(assert) do
    quote do
      ExUnit.Assertions.refute(unquote(assert))
    end
  end
end