// Unit tests for fn::tween (cpp/functions/core/tween_curves.{h,cpp}). // // Cubre boundaries (t=0, t=1), valor central (t=0.5) y monotonicidad para las // curvas que deberian ser monotonas (linear, *_quad, *_cubic, *_expo, bounce // in/out, elastic in/out NO son monotonas — overshoot intencional). #define CATCH_CONFIG_MAIN #include "catch_amalgamated.hpp" #include "core/tween_curves.h" #include using fn::tween::Ease; namespace { constexpr float kEps = 1e-5f; bool is_monotonic_increasing(Ease e, int samples = 64) { float prev = fn::tween::apply(e, 0.0f); for (int i = 1; i <= samples; ++i) { float t = static_cast(i) / samples; float v = fn::tween::apply(e, t); if (v < prev - kEps) return false; prev = v; } return true; } } // namespace TEST_CASE("tween: linear is identity", "[tween]") { REQUIRE(fn::tween::linear(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::linear(0.5f) == Catch::Approx(0.5f)); REQUIRE(fn::tween::linear(1.0f) == Catch::Approx(1.0f)); } TEST_CASE("tween: quadratic boundary conditions", "[tween]") { REQUIRE(fn::tween::in_quad(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::in_quad(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::out_quad(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::out_quad(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::in_out_quad(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::in_out_quad(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::in_out_quad(0.5f) == Catch::Approx(0.5f)); } TEST_CASE("tween: cubic boundary conditions", "[tween]") { REQUIRE(fn::tween::in_cubic(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::in_cubic(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::out_cubic(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::out_cubic(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::in_out_cubic(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::in_out_cubic(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::in_out_cubic(0.5f) == Catch::Approx(0.5f)); } TEST_CASE("tween: exponential boundary conditions", "[tween]") { REQUIRE(fn::tween::in_expo(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::in_expo(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::out_expo(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::out_expo(1.0f) == Catch::Approx(1.0f)); REQUIRE(fn::tween::in_out_expo(0.0f) == Catch::Approx(0.0f)); REQUIRE(fn::tween::in_out_expo(1.0f) == Catch::Approx(1.0f)); } TEST_CASE("tween: in_quad < out_quad in mid-range", "[tween]") { // Convexity: in_quad starts slow, out_quad starts fast. REQUIRE(fn::tween::in_quad(0.5f) < fn::tween::out_quad(0.5f)); REQUIRE(fn::tween::in_cubic(0.3f) < fn::tween::out_cubic(0.3f)); } TEST_CASE("tween: monotonic curves are monotonic", "[tween][monotonic]") { // Curvas monotonicamente crecientes en [0,1]. REQUIRE(is_monotonic_increasing(Ease::Linear)); REQUIRE(is_monotonic_increasing(Ease::InQuad)); REQUIRE(is_monotonic_increasing(Ease::OutQuad)); REQUIRE(is_monotonic_increasing(Ease::InOutQuad)); REQUIRE(is_monotonic_increasing(Ease::InCubic)); REQUIRE(is_monotonic_increasing(Ease::OutCubic)); REQUIRE(is_monotonic_increasing(Ease::InOutCubic)); REQUIRE(is_monotonic_increasing(Ease::InExpo)); REQUIRE(is_monotonic_increasing(Ease::OutExpo)); REQUIRE(is_monotonic_increasing(Ease::InOutExpo)); } TEST_CASE("tween: elastic and bounce hit endpoints exactly", "[tween]") { // Elastic / bounce overshoot pero deben pasar por (0,0) y (1,1). for (Ease e : {Ease::InElastic, Ease::OutElastic, Ease::InOutElastic, Ease::InBounce, Ease::OutBounce, Ease::InOutBounce}) { INFO("ease: " << fn::tween::name(e)); REQUIRE(fn::tween::apply(e, 0.0f) == Catch::Approx(0.0f).margin(1e-4f)); REQUIRE(fn::tween::apply(e, 1.0f) == Catch::Approx(1.0f).margin(1e-4f)); } } TEST_CASE("tween: apply dispatch matches direct calls", "[tween]") { REQUIRE(fn::tween::apply(Ease::Linear, 0.42f) == Catch::Approx(fn::tween::linear(0.42f))); REQUIRE(fn::tween::apply(Ease::InCubic, 0.3f) == Catch::Approx(fn::tween::in_cubic(0.3f))); REQUIRE(fn::tween::apply(Ease::OutBounce, 0.7f) == Catch::Approx(fn::tween::out_bounce(0.7f))); } TEST_CASE("tween: name() returns non-null for every Ease", "[tween]") { for (int i = 0; i < fn::tween::ease_count; ++i) { const char* n = fn::tween::name(static_cast(i)); REQUIRE(n != nullptr); REQUIRE(std::string(n).size() > 0); } }