222 lines
8.4 KiB
Elixir
222 lines
8.4 KiB
Elixir
|
defmodule DancingDots.AnimationTest do
|
||
|
use ExUnit.Case
|
||
|
import ExUnit.CaptureIO
|
||
|
|
||
|
describe "Animation module" do
|
||
|
@tag task_id: 1
|
||
|
test "defines two required callbacks, init/1 and handle_frame/3" do
|
||
|
assert DancingDots.Animation.behaviour_info(:callbacks) == [{:init, 1}, {:handle_frame, 3}]
|
||
|
end
|
||
|
|
||
|
@tag task_id: 1
|
||
|
test "defines no optional callbacks" do
|
||
|
assert DancingDots.Animation.behaviour_info(:optional_callbacks) == []
|
||
|
end
|
||
|
|
||
|
@tag task_id: 2
|
||
|
test "defines a __using__ macro" do
|
||
|
Code.ensure_loaded(DancingDots.Animation)
|
||
|
assert macro_exported?(DancingDots.Animation, :__using__, 1)
|
||
|
end
|
||
|
|
||
|
@tag task_id: 2
|
||
|
test "__using__ provides a default implementation of init/1" do
|
||
|
defmodule TestAnimation1 do
|
||
|
use DancingDots.Animation
|
||
|
def handle_frame(dot, _frame_number, _opts), do: dot
|
||
|
end
|
||
|
|
||
|
assert function_exported?(TestAnimation1, :init, 1)
|
||
|
end
|
||
|
|
||
|
@tag task_id: 2
|
||
|
test "the default implementation of init/1 returns opts unchanged" do
|
||
|
defmodule TestAnimation2 do
|
||
|
use DancingDots.Animation
|
||
|
def handle_frame(dot, _frame_number, _opts), do: dot
|
||
|
end
|
||
|
|
||
|
assert TestAnimation2.init([]) == {:ok, []}
|
||
|
assert TestAnimation2.init(:anything) == {:ok, :anything}
|
||
|
assert TestAnimation2.init({1, 2, 3}) == {:ok, {1, 2, 3}}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 2
|
||
|
test "the default implementation of init/1 can be overridden" do
|
||
|
defmodule TestAnimation3 do
|
||
|
use DancingDots.Animation
|
||
|
def init(_), do: {:ok, []}
|
||
|
def handle_frame(dot, _frame_number, _opts), do: dot
|
||
|
end
|
||
|
|
||
|
assert TestAnimation3.init(3) == {:ok, []}
|
||
|
assert TestAnimation3.init(:anything) == {:ok, []}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 2
|
||
|
test "__using__ does not provide a default implementation of handle_frame/3" do
|
||
|
capture_io(:stderr, fn ->
|
||
|
Code.compile_quoted do
|
||
|
quote do
|
||
|
defmodule TestAnimation4 do
|
||
|
use DancingDots.Animation
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
refute function_exported?(TestAnimation4, :handle_frame, 3)
|
||
|
end
|
||
|
|
||
|
@tag task_id: 2
|
||
|
test "__using__ sets Animation as the behaviour, emitting compilation warnings about missing callbacks" do
|
||
|
compilation_warnings =
|
||
|
capture_io(:stderr, fn ->
|
||
|
Code.compile_quoted do
|
||
|
quote do
|
||
|
defmodule TestAnimation5 do
|
||
|
use DancingDots.Animation
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
assert compilation_warnings =~
|
||
|
"function handle_frame/3 required by behaviour DancingDots.Animation is not implemented"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "Flicker module" do
|
||
|
@tag task_id: 3
|
||
|
test "implements Animation behaviour" do
|
||
|
Code.ensure_loaded(DancingDots.Flicker)
|
||
|
assert function_exported?(DancingDots.Flicker, :init, 1)
|
||
|
assert function_exported?(DancingDots.Flicker, :handle_frame, 3)
|
||
|
end
|
||
|
|
||
|
@tag task_id: 3
|
||
|
test "uses the default init/1 implementation" do
|
||
|
assert DancingDots.Flicker.init([]) == {:ok, []}
|
||
|
assert DancingDots.Flicker.init(:anything) == {:ok, :anything}
|
||
|
assert DancingDots.Flicker.init({1, 2, 3}) == {:ok, {1, 2, 3}}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 3
|
||
|
test "every 4th frame, handle_frame/3 sets the dot's opacity to half the original value" do
|
||
|
dot1 = %DancingDots.Dot{x: 10, y: 30, radius: 4, opacity: 1}
|
||
|
dot2 = %DancingDots.Dot{x: 0, y: 0, radius: 10, opacity: 0.6}
|
||
|
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 1, []) == %{dot1 | opacity: 1}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 2, []) == %{dot1 | opacity: 1}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 3, []) == %{dot1 | opacity: 1}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 4, []) == %{dot1 | opacity: 0.5}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 5, []) == %{dot1 | opacity: 1}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 6, []) == %{dot1 | opacity: 1}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 7, []) == %{dot1 | opacity: 1}
|
||
|
assert DancingDots.Flicker.handle_frame(dot1, 8, []) == %{dot1 | opacity: 0.5}
|
||
|
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 13, []) == %{dot2 | opacity: 0.6}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 14, []) == %{dot2 | opacity: 0.6}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 15, []) == %{dot2 | opacity: 0.6}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 16, []) == %{dot2 | opacity: 0.3}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 17, []) == %{dot2 | opacity: 0.6}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 18, []) == %{dot2 | opacity: 0.6}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 19, []) == %{dot2 | opacity: 0.6}
|
||
|
assert DancingDots.Flicker.handle_frame(dot2, 20, []) == %{dot2 | opacity: 0.3}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 3
|
||
|
test "can be used in a dot group" do
|
||
|
dot1 = %DancingDots.Dot{x: 44, y: 44, radius: 2, opacity: 0.8}
|
||
|
dot2 = %DancingDots.Dot{x: 0, y: 0, radius: 3, opacity: 0.5}
|
||
|
|
||
|
{:ok, dot_group} =
|
||
|
DancingDots.DotGroup.new([dot1, dot2])
|
||
|
|> DancingDots.DotGroup.add_animation(DancingDots.Flicker, [])
|
||
|
|
||
|
assert dot_group ==
|
||
|
%DancingDots.DotGroup{
|
||
|
animations_with_opts: [{DancingDots.Flicker, []}],
|
||
|
dots: [dot1, dot2]
|
||
|
}
|
||
|
|
||
|
assert DancingDots.DotGroup.render_dots(dot_group, 4) == [
|
||
|
%{dot1 | opacity: 0.4},
|
||
|
%{dot2 | opacity: 0.25}
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "Zoom module" do
|
||
|
@tag task_id: 4
|
||
|
test "implements Animation behaviour" do
|
||
|
Code.ensure_loaded(DancingDots.Zoom)
|
||
|
assert function_exported?(DancingDots.Zoom, :init, 1)
|
||
|
assert function_exported?(DancingDots.Zoom, :handle_frame, 3)
|
||
|
end
|
||
|
|
||
|
@tag task_id: 4
|
||
|
test "has a custom init/1 implementation that checks that a valid velocity was passed as an option" do
|
||
|
assert DancingDots.Zoom.init(velocity: 10) == {:ok, [velocity: 10]}
|
||
|
|
||
|
assert DancingDots.Zoom.init([]) ==
|
||
|
{:error,
|
||
|
"The :velocity option is required, and its value must be a number. Got: nil"}
|
||
|
|
||
|
assert DancingDots.Zoom.init(velocity: "7") ==
|
||
|
{:error,
|
||
|
"The :velocity option is required, and its value must be a number. Got: \"7\""}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 4
|
||
|
test "the first frame, handle_frame/3 returns the dot unchanged" do
|
||
|
dot = %DancingDots.Dot{x: 90, y: 90, radius: 100, opacity: 0.5}
|
||
|
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 1, velocity: 3) == dot
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 1, velocity: 30) == dot
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 1, velocity: -7) == dot
|
||
|
end
|
||
|
|
||
|
@tag task_id: 4
|
||
|
test "every frame after the first, handle_frame/3 grows the dot's radius by the given positive velocity" do
|
||
|
dot = %DancingDots.Dot{x: 90, y: 90, radius: 100, opacity: 0.5}
|
||
|
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 1, velocity: 3) == %{dot | radius: 100}
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 2, velocity: 3) == %{dot | radius: 103}
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 3, velocity: 3) == %{dot | radius: 106}
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 101, velocity: 3) == %{dot | radius: 400}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 4
|
||
|
test "every frame after the first, handle_frame/3 shrinks the dot's radius by the given negative velocity" do
|
||
|
dot = %DancingDots.Dot{x: 100, y: 0, radius: 400, opacity: 0.9}
|
||
|
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 1, velocity: -1) == %{dot | radius: 400}
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 2, velocity: -1) == %{dot | radius: 399}
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 3, velocity: -1) == %{dot | radius: 398}
|
||
|
assert DancingDots.Zoom.handle_frame(dot, 101, velocity: -1) == %{dot | radius: 300}
|
||
|
end
|
||
|
|
||
|
@tag task_id: 4
|
||
|
test "can be used in a dot group" do
|
||
|
dot1 = %DancingDots.Dot{x: 0, y: 0, radius: 100, opacity: 0.3}
|
||
|
dot2 = %DancingDots.Dot{x: 0, y: 0, radius: 150, opacity: 0.3}
|
||
|
|
||
|
{:ok, dot_group} =
|
||
|
DancingDots.DotGroup.new([dot1, dot2])
|
||
|
|> DancingDots.DotGroup.add_animation(DancingDots.Zoom, velocity: 10)
|
||
|
|
||
|
assert dot_group ==
|
||
|
%DancingDots.DotGroup{
|
||
|
animations_with_opts: [{DancingDots.Zoom, [velocity: 10]}],
|
||
|
dots: [dot1, dot2]
|
||
|
}
|
||
|
|
||
|
assert DancingDots.DotGroup.render_dots(dot_group, 50) == [
|
||
|
%{dot1 | radius: 590},
|
||
|
%{dot2 | radius: 640}
|
||
|
]
|
||
|
end
|
||
|
end
|
||
|
end
|