defmodule Doumi.Test.Comparison do
@doc """
Compare two values for equality.
## Examples
iex> Doumi.Test.Comparison.same_values?(1, 1)
true
iex> Doumi.Test.Comparison.same_values?("foo", "bar")
false
iex> Doumi.Test.Comparison.same_values?(~U[2023-01-01 00:00:00Z], ~U[2023-01-01 00:00:00.000Z])
true
iex> Doumi.Test.Comparison.same_values?(Decimal.new("1.1"), 1.1)
true
"""
@spec same_values?(any(), any()) :: boolean()
def same_values?(%DateTime{} = a, %DateTime{} = b), do: DateTime.compare(a, b) == :eq
if Code.ensure_loaded?(Decimal) do
def same_values?(%Decimal{} = a, %Decimal{} = b), do: Decimal.equal?(a, b)
def same_values?(%Decimal{} = a, b) when is_integer(b), do: Decimal.equal?(a, b)
def same_values?(%Decimal{} = a, b) when is_float(b),
do: Decimal.equal?(a, Decimal.from_float(b))
def same_values?(%Decimal{} = a, b) when is_binary(b), do: Decimal.equal?(a, Decimal.new(b))
def same_values?(a, %Decimal{} = b), do: same_values?(b, a)
end
def same_values?(a, b), do: a == b
@doc """
Compare two maps have the same value for a given fields.
The two maps must always contain the given field.
## Examples
iex> Doumi.Test.Comparison.same_fields?(%{a: 1, b: 2}, %{a: 1, b: 2}, [:a, :b])
true
iex> Doumi.Test.Comparison.same_fields?(%{a: 1, b: 2}, %{a: 1, b: 3}, [:a, :b])
false
iex> Doumi.Test.Comparison.same_fields?(%{a: 1}, %{a: 1, b: 2}, [:a, :b])
** (KeyError) key :b not found in: %{a: 1}
"""
@spec same_fields?(map(), map(), list()) :: boolean()
def same_fields?(a, b, keys) when is_map(a) and is_map(b) and is_list(keys) do
Enum.all?(keys, &same_values?(Map.fetch!(a, &1), Map.fetch!(b, &1)))
end
if Code.ensure_loaded?(Ecto) do
@doc """
Compare two records have the same primary keys.
"""
@spec same_records?(Ecto.Schema.t(), Ecto.Schema.t()) :: boolean()
def same_records?(a, b) do
with true <- a.__struct__ == b.__struct__,
primary_keys <- a.__struct__.__schema__(:primary_key),
true <- same_fields?(a, b, primary_keys) do
true
else
_ -> false
end
end
end
end