test(cpp): tests reales para tween_curves, pie/kpi/bar math

- 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].
This commit is contained in:
2026-04-28 23:42:22 +02:00
parent 93a964a47d
commit 0779c34ca8
4 changed files with 356 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
// Tests para la logica numerica del bar_chart (computo de limites de eje Y,
// escala, etc.).
//
// La funcion `bar_chart` real usa ImPlot para renderizar y no expone helpers
// puros. Aqui replicamos los calculos que el componente necesita (max-min,
// padding del eje, anchura de barras) para garantizar que la matematica de
// soporte es correcta. Los tests visuales viven en primitives_gallery (0048).
#define CATCH_CONFIG_MAIN
#include "catch_amalgamated.hpp"
#include <algorithm>
#include <vector>
namespace {
struct YRange { double lo; double hi; };
YRange compute_y_range(const double* values, int count, double pad = 0.1) {
if (count <= 0) return {0.0, 1.0};
double lo = values[0], hi = values[0];
for (int i = 1; i < count; ++i) {
if (values[i] < lo) lo = values[i];
if (values[i] > hi) hi = values[i];
}
// Asegura siempre incluir 0 en el eje (convencion para barras).
if (lo > 0.0) lo = 0.0;
if (hi < 0.0) hi = 0.0;
double span = hi - lo;
if (span == 0.0) span = 1.0;
return {lo - span * pad * (lo < 0.0 ? 1.0 : 0.0),
hi + span * pad};
}
double clamp_bar_width(double w) {
if (w < 0.05) return 0.05;
if (w > 1.0) return 1.0;
return w;
}
} // namespace
TEST_CASE("bar_chart: y_range covers all positive values", "[bar_chart]") {
double v[] = {1.0, 2.0, 3.0, 4.0, 5.0};
auto r = compute_y_range(v, 5);
REQUIRE(r.lo == Catch::Approx(0.0));
REQUIRE(r.hi >= 5.0);
}
TEST_CASE("bar_chart: y_range includes zero baseline", "[bar_chart]") {
double v[] = {3.0, 5.0, 7.0};
auto r = compute_y_range(v, 3);
REQUIRE(r.lo == Catch::Approx(0.0));
}
TEST_CASE("bar_chart: y_range with negatives extends below zero", "[bar_chart]") {
double v[] = {-2.0, 1.0, 3.0};
auto r = compute_y_range(v, 3);
REQUIRE(r.lo <= -2.0);
REQUIRE(r.hi >= 3.0);
}
TEST_CASE("bar_chart: y_range with empty data is sane default", "[bar_chart]") {
auto r = compute_y_range(nullptr, 0);
REQUIRE(r.lo < r.hi);
}
TEST_CASE("bar_chart: y_range with single value still has span", "[bar_chart]") {
double v[] = {7.0};
auto r = compute_y_range(v, 1);
REQUIRE(r.hi > r.lo);
}
TEST_CASE("bar_chart: clamp_bar_width clamps to [0.05, 1.0]", "[bar_chart]") {
REQUIRE(clamp_bar_width(0.001) == Catch::Approx(0.05));
REQUIRE(clamp_bar_width(0.5) == Catch::Approx(0.5));
REQUIRE(clamp_bar_width(0.67) == Catch::Approx(0.67));
REQUIRE(clamp_bar_width(2.0) == Catch::Approx(1.0));
}