// Standalone tests for reasampler::prune_button — no REAPER, no test framework. // Same fast loop as the sibling pure tests (mode_switch / tab_strip): assert the // footer prune-button placement math and hit-testing directly. // // Covers (R3 brief §button pure module): right-anchored layout in a wide footer; // vertical inset; SUPPRESSION (empty rect) when the footer is too narrow to clear the // tail-label inset or is degenerate; hit-test in/out/edge (half-open bounds); a // suppressed/empty button claims no point; draw and hit-test agree over the whole rect. #include "../src/prune_button.h" #include using namespace reasampler; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // --- Layout: wide footer, right-anchored ------------------------------------- // Footer 400 wide at origin (0, 100), height 26. Default spec: buttonWidth 72, // rightInset 84, verticalInset 4, minLeftInset 120. Right edge = 0+400-84 = 316, // left = 316-72 = 244 (>= 0+120, so placed). Top = 100+4 = 104, height = 26-8 = 18. static void testWideFooterRightAnchored() { FooterRect f{0, 100, 400, 26}; const ButtonRect b = computePruneButton(f, PruneButtonSpec{}); CHECK(!b.empty()); CHECK((b == ButtonRect{244, 104, 72, 18})); // Right edge sits at the rightInset from the footer's right. CHECK(b.x + b.width == f.x + f.width - 84); // Left edge clears the reserved tail-label inset. CHECK(b.x >= f.x + 120); } // Origin offset is honoured (button anchors to THIS footer's right, not 0). static void testOffsetFooterAnchors() { FooterRect f{10, 200, 400, 26}; const ButtonRect b = computePruneButton(f, PruneButtonSpec{}); CHECK(!b.empty()); CHECK(b.x + b.width == f.x + f.width - 84); // = 10+400-84 = 326 CHECK(b.x == 254); } // --- Suppression: too narrow / degenerate ------------------------------------ // A footer just wide enough that the button's left edge would fall past the // minLeftInset is suppressed (empty). left = x + width - rightInset - buttonWidth. // Need left < x + minLeftInset -> width < rightInset + buttonWidth + minLeftInset // = 84 + 72 + 120 = 276. Width 275 suppresses; 276 places (boundary). static void testNarrowFooterSuppressed() { CHECK(computePruneButton(FooterRect{0, 0, 275, 26}, PruneButtonSpec{}).empty()); CHECK(!computePruneButton(FooterRect{0, 0, 276, 26}, PruneButtonSpec{}).empty()); } static void testDegenerateFooterSuppressed() { CHECK(computePruneButton(FooterRect{0, 0, 0, 26}, PruneButtonSpec{}).empty()); // no width CHECK(computePruneButton(FooterRect{0, 0, 400, 0}, PruneButtonSpec{}).empty()); // no height PruneButtonSpec zero{}; zero.buttonWidth = 0; CHECK(computePruneButton(FooterRect{0, 0, 400, 26}, zero).empty()); // zero button } // A very thin footer (height <= 2*verticalInset) still places a button but clamps its // height to the footer's own, rather than yielding a negative height. static void testThinFooterClampsHeight() { FooterRect f{0, 0, 400, 6}; // 6 <= 2*4, so height would be negative -> clamp const ButtonRect b = computePruneButton(f, PruneButtonSpec{}); CHECK(!b.empty()); CHECK(b.y == f.y); CHECK(b.height == f.height); } // --- Hit-test ---------------------------------------------------------------- static void testHitTestInside() { FooterRect f{0, 100, 400, 26}; const ButtonRect b = computePruneButton(f, PruneButtonSpec{}); // {244,104,72,18} CHECK(hitTestPruneButton(b.x, b.y, b)); // top-left corner (inclusive) CHECK(hitTestPruneButton(b.x + b.width - 1, b.y + b.height - 1, b)); // bottom-right inclusive CHECK(hitTestPruneButton(b.x + b.width / 2, b.y + b.height / 2, b)); // centre } // Half-open bounds: the far edges (x+width, y+height) are EXCLUDED, matching the draw. static void testHitTestEdgesExcluded() { FooterRect f{0, 100, 400, 26}; const ButtonRect b = computePruneButton(f, PruneButtonSpec{}); CHECK(!hitTestPruneButton(b.x - 1, b.y, b)); // just left CHECK(!hitTestPruneButton(b.x + b.width, b.y, b)); // right edge excluded CHECK(!hitTestPruneButton(b.x, b.y - 1, b)); // just above CHECK(!hitTestPruneButton(b.x, b.y + b.height, b)); // bottom edge excluded } // A suppressed (empty) button never claims a point — a click in the footer where the // button would have been falls through to the tail cycle, never a phantom prune. static void testEmptyButtonClaimsNothing() { ButtonRect empty{}; CHECK(!hitTestPruneButton(0, 0, empty)); CHECK(!hitTestPruneButton(5, 5, empty)); // A "no button" from a narrow footer also claims nothing at any point. const ButtonRect suppressed = computePruneButton(FooterRect{0, 0, 200, 26}, PruneButtonSpec{}); CHECK(suppressed.empty()); CHECK(!hitTestPruneButton(150, 13, suppressed)); } // Draw/hit-test agreement: every point inside the computed rect hit-tests true, and the // four immediate outside neighbours hit-test false (the load-bearing consistency). static void testHitTestMatchesLayout() { FooterRect f{3, 7, 377, 22}; // awkward origin/size const ButtonRect b = computePruneButton(f, PruneButtonSpec{}); CHECK(!b.empty()); for (int py = b.y; py < b.y + b.height; ++py) for (int px = b.x; px < b.x + b.width; ++px) CHECK(hitTestPruneButton(px, py, b)); CHECK(!hitTestPruneButton(b.x - 1, b.y, b)); CHECK(!hitTestPruneButton(b.x + b.width, b.y, b)); } int main() { testWideFooterRightAnchored(); testOffsetFooterAnchors(); testNarrowFooterSuppressed(); testDegenerateFooterSuppressed(); testThinFooterClampsHeight(); testHitTestInside(); testHitTestEdgesExcluded(); testEmptyButtonClaimsNothing(); testHitTestMatchesLayout(); if (g_fail == 0) std::printf("prune_button: all tests passed\n"); else std::printf("prune_button: %d CHECK(s) FAILED\n", g_fail); return g_fail == 0 ? 0 : 1; }