From f132a5ec92e9579e2d43b2ccf6e6ff810386d6d8 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Mon, 28 Sep 2026 22:42:56 +0200 Subject: [PATCH] test(plot): pin the hot-handle re-index on region removal, refresh coverage `remove_region()` now clears or decrements the published `hot_.region` with the cache it re-indexes, so a frame after a removal highlights the region the handle actually names. The new case covers both branches (a handle above the hole follows it down, the removed region's handle is cleared with its zone), and the coverage document is regenerated by the command that measured it: 95.2% of src/, every file at or above 80%. --- docs/coverage.md | 12 ++++++------ tests/test_plot.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/coverage.md b/docs/coverage.md index 5d1884d..8f1773e 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -43,7 +43,7 @@ when one is missing. ``` -coverage_floor: ok -- 23 files under src/, aggregate 95.3% lines (3284/3447), every file at or above 80%, aggregate target 90% +coverage_floor: ok -- 23 files under src/, aggregate 95.2% lines (3302/3469), every file at or above 80%, aggregate target 90% ``` | File | Lines | Line rate | Functions | @@ -54,24 +54,24 @@ coverage_floor: ok -- 23 files under src/, aggregate 95.3% lines (3284/3447), ev | `src/data/stream_series.cpp` | 39/39 | 100.0% | 100.0% | | `src/gl/handle.cpp` | 256/256 | 100.0% | 100.0% | | `src/gl/loader.cpp` | 81/81 | 100.0% | 100.0% | -| `src/input/controller.cpp` | 327/341 | 95.9% | 100.0% | +| `src/input/controller.cpp` | 329/343 | 95.9% | 100.0% | | `src/math/transform.cpp` | 28/28 | 100.0% | 100.0% | | `src/plot/axis.cpp` | 12/12 | 100.0% | 100.0% | -| `src/plot/figure.cpp` | 213/218 | 97.7% | 95.5% | +| `src/plot/figure.cpp` | 218/223 | 97.8% | 95.5% | | `src/plot/grid.cpp` | 15/16 | 93.8% | 100.0% | | `src/plot/measure.cpp` | 21/21 | 100.0% | 100.0% | -| `src/plot/plot.cpp` | 733/753 | 97.3% | 98.9% | +| `src/plot/plot.cpp` | 743/763 | 97.4% | 98.9% | | `src/plot/ticks.cpp` | 93/99 | 93.9% | 100.0% | | `src/render/buffer.cpp` | 88/98 | 89.8% | 100.0% | | `src/render/embedded_mono_font.cpp` | 45/45 | 100.0% | 100.0% | | `src/render/primitives.cpp` | 71/75 | 94.7% | 100.0% | | `src/render/program.cpp` | 26/27 | 96.3% | 100.0% | -| `src/render/renderer.cpp` | 529/604 | 87.6% | 93.7% | +| `src/render/renderer.cpp` | 530/609 | 87.0% | 93.7% | | `src/render/text.cpp` | 27/30 | 90.0% | 100.0% | | `src/render/text_atlas.cpp` | 40/41 | 97.6% | 100.0% | | `src/render/vertex_array.cpp` | 59/59 | 100.0% | 100.0% | | `src/text/true_type_font.cpp` | 270/291 | 92.8% | 96.8% | -| **src/ aggregate** | **3284/3447** | **95.3%** | **98.3%** | +| **src/ aggregate** | **3302/3469** | **95.2%** | **98.3%** | diff --git a/tests/test_plot.cpp b/tests/test_plot.cpp index 9dd80d6..58841b1 100644 --- a/tests/test_plot.cpp +++ b/tests/test_plot.cpp @@ -1787,6 +1787,41 @@ PLOT_TEST(removing_a_region_shifts_the_ids_and_keeps_the_statistics) { PLOT_CHECK(!p.region_stats(9u, tr).valid); } +/// The published hot handles name a region by the same index the vector does, so a +/// removal has to re-index them with the rest: a frame drawn before the next pointer event +/// reads `hot_handles()`, and one left naming the old index would highlight the wrong +/// region's bound -- and an open drag would edit a different region. The handle on the +/// removed region is cleared with its zone; one naming a region above the hole follows it +/// down. +PLOT_TEST(removing_a_region_reindexes_the_published_hot_handles) { + plot::Plot p; + p.set_viewport({0.0f, 0.0f, 800.0f, 400.0f}); + p.add_x_axis({"t", {0.0, 99.0}, false, plot::AxisPlace::Bottom, false}); + p.add_y_axis({"v", {0.0, 99.0}, false, plot::AxisPlace::Left, false}); + plot::Region bounds; + bounds.snap = plot::RegionSnap::Free; + PLOT_CHECK(p.add_region(bounds) == 0u); + PLOT_CHECK(p.add_region(bounds) == 1u); + PLOT_CHECK(p.add_region(bounds) == 2u); + + // A handle on the last region, with the hole below it: it follows the region down. + plot::HotHandles hot; + hot.region = 2u; + hot.zone = plot::RegionGrab::Right; + p.set_hot_handles(hot); + p.remove_region(0u); + PLOT_CHECK(p.hot_handles().region == 1u); + PLOT_CHECK(p.hot_handles().zone == plot::RegionGrab::Right); + + // A handle on the region that is removed: it is cleared, zone and all. + hot.region = 0u; + hot.zone = plot::RegionGrab::Left; + p.set_hot_handles(hot); + p.remove_region(0u); + PLOT_CHECK(p.hot_handles().region == plot::kInvalid); + PLOT_CHECK(p.hot_handles().zone == plot::RegionGrab::None); +} + /// A bound that is not a number is not a bound. `std::min`/`std::max` propagate a NaN /// through *both* ends of the pair, so accepting one would not merely invert the interval: /// it would replace the finite partner too, and a `Free` region stored that way could never