defmodule ExSpec do
  @moduledoc """
  ExSpec is a simple wrapper around ExUnit that adds Rspec-style macros. Specifically, it adds `context` and `it`.

  While it takes inspiration from Rspec, ExSpec is significantly simplier. The `context` macro has only two functions:

  1. Aid test organization
  2. Prepend to the message of any `it` defined within its do blocks

  The `it` macro is identical to `ExUnit.Case.test` except that it is aware of the messages of its surrounding `context` blocks. It also works seemlessly with `ExUnit`'s `describe` function.

  Other than the functionality described above, ExSpec is just ExUnit. When `use`ing `ExSpec`, any options provided will be passed to `ExUnit.Case` (e.g. `async: true`).

  A simple example is shown below. For more examples, see the tests.

  ### Example

      defmodule PersonTest do
        use ExSpec, async: true

        describe "name" do
          context "with first and last name" do
            it "joins the names with a space" do
              drew = %Person{first_name: "Drew", last_name: "Olson"}

              assert Person.name(drew) == "Drew Olson"
            end
          end

          context "with only a first name" do
            it "returns the first name" do
              drew = %Person{first_name: "Drew", last_name: nil}

              assert Person.name(drew) == "Drew"
            end
          end
        end
      end
  """

  defmacro __using__(options) do
    quote do
      use ExUnit.Case, unquote(options)

      import ExSpec

      Module.put_attribute(__MODULE__, :ex_spec_contexts, [])
    end
  end

  defmacro context(message, body) do
    quote do
      previous_contexts = Module.get_attribute(__MODULE__, :ex_spec_contexts)
      context = %ExSpec.Context{name: unquote(message)}
      Module.put_attribute(__MODULE__, :ex_spec_contexts, [context | previous_contexts])

      unquote(body)

      Module.put_attribute(__MODULE__, :ex_spec_contexts, previous_contexts)
    end
  end

  defmacro it(message) do
    quote do
      full_message = ExSpec.Helpers.full_message(__MODULE__, unquote(message))

      ExUnit.Case.test(full_message)
    end
  end

  defmacro it(message, var \\ quote(do: _), body) do
    quote do
      full_message = ExSpec.Helpers.full_message(__MODULE__, unquote(message))

      ExUnit.Case.test(full_message, unquote(var), unquote(body))
    end
  end
end
