f858f3a9fc
- test_tween_curves: boundary conditions (t=0, t=0.5, t=1), monotonicidad para curvas no oscilantes, dispatch via apply(), names() no nulos. - test_pie_chart_math: replica slice_at (anonymous namespace en pie_chart.cpp) y testea hit-test angular, edge del radio, distribucion proporcional. - test_kpi_card_math: classify_delta (Up/Down/Flat) y pct_change con zero/negativos. La logica visual la cubre primitives_gallery (issue 0048). - test_bar_chart_math: compute_y_range (incluye 0, negativos, vacio, single value) y clamp_bar_width [0.05, 1.0].
111 lines
4.5 KiB
C++
111 lines
4.5 KiB
C++
// 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 <cmath>
|
|
|
|
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<float>(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<Ease>(i));
|
|
REQUIRE(n != nullptr);
|
|
REQUIRE(std::string(n).size() > 0);
|
|
}
|
|
}
|