defmodule AssertionsTest do
  # We can't run these async because we're capturing IO in most of them.
  use Assertions.Case
  doctest Assertions

  import ExUnit.CaptureIO, only: [capture_io: 1]

  describe "assert_lists_equal/2" do
    test "works when composed with other assertions" do
      list1 = [DateTime.utc_now(), DateTime.utc_now()]
      list2 = [DateTime.utc_now(), DateTime.utc_now()]
      assert_lists_equal(list1, list2, &assert_structs_equal(&1, &2, [:year, :month]))
    end
  end

  describe "assert_lists_equal/3" do
    test "works with comparisons that raise an error instead of returning false" do
      list1 = [%{foo: 1}, %{foo: 2}, %{foo: 3}]
      list2 = [%{foo: 2}, %{foo: 1}, %{foo: 3}]
      comparison = &assert_maps_equal(&1, &2, [:foo])
      assert_lists_equal(list1, list2, comparison)
    end

    test "keeps the right ordering in the comparison function" do
      list1 = [:a, :c, :b, :d]
      list2 = ["a", "b", "c", "d"]
      assert_lists_equal(list1, list2, &(Atom.to_string(&1) == &2))
    end

    test "fails correctly with non-equal sized lists" do
      list1 = [:a, :c, :b, :d, :e]
      list2 = ["a", "b", "c", "d"]

      try do
        assert_lists_equal(list1, list2, &(Atom.to_string(&1) == &2))
        flunk("Should have failed")
      rescue
        e in [ExUnit.AssertionError] ->
          assert e.left == [:e]
          assert e.right == []
      end

      list1 = [:a, :c, :b, :d]
      list2 = ["a", "b", "c", "d", "d"]

      try do
        assert_lists_equal(list1, list2, &(Atom.to_string(&1) == &2))
        flunk("Should have failed")
      rescue
        e in [ExUnit.AssertionError] ->
          assert e.left == []
          assert e.right == ["d"]
      end

      list1 = [:a, :d]
      list2 = ["a", "a", "b", "c", "c"]

      try do
        assert_lists_equal(list1, list2, &(Atom.to_string(&1) == &2))
        flunk("Should have failed")
      rescue
        e in [ExUnit.AssertionError] ->
          assert e.left == [:d]
          assert e.right == ["a", "b", "c", "c"]
      end
    end
  end

  describe "assert_maps_equal/3" do
    test "works with custom functions" do
      assert_maps_equal(
        %{a: :b},
        %{b: "b"},
        &(Map.get(&1, :a) == String.to_atom(Map.get(&2, :b)))
      )
    end
  end

  describe "assert_map_in_list/3" do
    test "fails with a list of keys to compare by" do
      defmodule AssertMapInList do
        use Assertions.Case, async: true

        test "fails" do
          assert_map_in_list(%{a: :b, c: :d}, [%{a: :b, c: :e}], [:a, :c])
        end
      end

      output = run_tests()
      assert output =~ "Map matching the values for keys `:a, :c` not found"
    end
  end

  describe "assert_structs_equal/3" do
    defmodule Nested do
      defstruct [:key, :list, :map]
    end

    test "works with nested assertions" do
      first = %Nested{key: :value, list: [1, 2, 3], map: %{a: :a}}
      second = %Nested{key: :value, list: [1, 3, 2], map: %{"a" => :a}}

      assert_structs_equal(first, second, fn left, right ->
        assert left.key == right.key
        assert_lists_equal(left.list, right.list)
        assert_maps_equal(left.map, right.map, &(&1.a == &2["a"]))
      end)
    end
  end

  describe "assert_raise/1" do
    test "doesn't override ExUnit's assert_raise/2 or assert_raise/3" do
      assert_raise(ExUnit.AssertionError, fn ->
        assert_raise(fn ->
          first = 1
          second = 2
          first / second
        end)
      end)

      regex = ~r/Expected exception but nothing was raised/

      assert_raise ExUnit.AssertionError, regex, fn ->
        assert_raise(fn ->
          first = 1
          second = 2
          first / second
        end)
      end
    end
  end

  defp run_tests do
    ExUnit.Server.modules_loaded(false)
    capture_io(fn -> ExUnit.run() end)
  end
end
