Current section

Files

Jump to
power_assert lib power_assert renderer.ex
Raw

lib/power_assert/renderer.ex

defmodule PowerAssert.Renderer do
@moduledoc false
alias PowerAssert.PositionAndValue
@doc """
renders test result
"""
def render(code_ast, position_and_values, lhs_result \\ nil, rhs_result \\ nil)
def render(code_ast, [], lhs_result, rhs_result) do
Macro.to_string(code_ast) <> extra_information(lhs_result, rhs_result)
end
def render(code_ast, position_and_values, lhs_result, rhs_result) do
code_str = Macro.to_string(code_ast)
position_and_values =
Enum.sort(position_and_values, fn %PositionAndValue{position: x_pos},
%PositionAndValue{position: y_pos} ->
x_pos > y_pos
end)
%PositionAndValue{position: max_pos} =
Enum.max_by(position_and_values, fn %PositionAndValue{position: pos} -> pos end)
first_line = String.duplicate(" ", max_pos + 1) |> replace_with_bar(position_and_values)
lines = make_lines([], Enum.count(position_and_values), position_and_values, -1)
Enum.join([code_str, first_line] ++ lines, "\n") <> extra_information(lhs_result, rhs_result)
end
defp make_lines(lines, 0, _, _latest_pos) do
lines
end
defp make_lines(lines, times, position_and_values, latest_pos) do
[%PositionAndValue{position: pos, value: value} | t] = position_and_values
value = inspect(value)
value_len = String.length(value)
lines =
if latest_pos != -1 && latest_pos - (pos + value_len) > 0 do
[last_line | tail_lines] = Enum.reverse(lines)
{before_str, after_str} = String.split_at(last_line, pos)
{_removed_str, after_str} = String.split_at(after_str, value_len)
line = before_str <> value <> after_str
Enum.reverse([line | tail_lines])
else
line = String.duplicate(" ", pos + 1)
line = replace_with_bar(line, position_and_values)
line = String.replace(line, ~r/\|$/, value)
lines ++ [line]
end
make_lines(lines, times - 1, t, pos)
end
defp replace_with_bar(line, position_and_values) do
Enum.reduce(position_and_values, line, fn %PositionAndValue{position: pos}, line ->
{front, back} = String.split_at(line, pos + 1)
String.replace(front, ~r/ $/, "|") <> back
end)
end
defp extra_information(lhs_result, rhs_result)
when is_list(lhs_result) and is_list(rhs_result) do
[
"\n\nonly in lhs: " <> ((lhs_result -- rhs_result) |> inspect),
"only in rhs: " <> ((rhs_result -- lhs_result) |> inspect)
]
|> Enum.join("\n")
end
defp extra_information(lhs_result, rhs_result) when is_map(lhs_result) and is_map(rhs_result) do
lhs_result = Map.delete(lhs_result, :__struct__)
rhs_result = Map.delete(rhs_result, :__struct__)
in_left = Map.split(lhs_result, Map.keys(rhs_result)) |> elem(1)
in_right = Map.split(rhs_result, Map.keys(lhs_result)) |> elem(1)
str = "\n"
str =
if map_size(in_left) != 0 do
str <> "\nonly in lhs: " <> inspect(in_left)
else
str
end
str =
if map_size(in_right) != 0 do
str <> "\nonly in rhs: " <> inspect(in_right)
else
str
end
diff = collect_map_diff(lhs_result, rhs_result)
str =
case Enum.empty?(diff) do
true -> str
false -> str <> "\ndifference:\n" <> Enum.join(diff, "\n")
end
str
end
defp extra_information(lhs_result, rhs_result) do
if String.valid?(lhs_result) && String.valid?(rhs_result) do
extra_information_for_string(lhs_result, rhs_result)
else
""
end
end
defp extra_information_for_string(lhs_result, rhs_result) do
"\n\ndifference:" <> "\n" <> lhs_result <> "\n" <> rhs_result
end
defp collect_map_diff(map1, map2) do
Enum.reduce(map2, [], fn {k, v}, acc ->
case Map.fetch(map1, k) do
{:ok, ^v} ->
acc
{:ok, map1_value} ->
acc ++ ["key #{inspect(k)} => {#{inspect(map1_value)}, #{inspect(v)}}"]
_ ->
acc
end
end)
end
end