// Standalone tests for reasampler::mode_switch — no REAPER, no test framework. // Same fast loop as the sibling pure tests (bank_grid et al.): assert the segmented // mode-switch layout math and hit-testing directly. // // Covers (D5 brief §unit-test): N=2 and N=3 segment layout; exact tiling (adjacent // segments abut with no gap/overlap, half-open boundaries — no pixel claimed twice, // last segment reaches the header's right edge even when width doesn't divide // evenly); hit-test hits per segment and misses (outside the band above/below/ // left/right, boundary pixels); degenerate widths and counts. #include "../src/core/view/mode_switch.h" #include #include #include using namespace reasampler; using namespace reasampler::view; 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: N=2 ------------------------------------------------------------- // A header 100 wide at origin (10, 4), height 24, split in two: segments // [10,60) and [60,110), each 50 wide, full header y/height. static void testTwoSegmentsEvenSplit() { HeaderRect h{10, 4, 100, 24}; auto rects = computeSegmentRects(h, 2); CHECK(rects.size() == 2); CHECK((rects[0] == SegmentRect{10, 4, 50, 24})); CHECK((rects[1] == SegmentRect{60, 4, 50, 24})); } // --- Layout: N=3, uneven width absorbed at boundaries ------------------------ // 100 wide / 3 doesn't divide evenly: boundaries at floor(i*100/3) = // 0, 33, 66, 100 -> widths 33, 33, 34. The rounding lands in the LAST segment; // segments still abut exactly and the last reaches header.x + width (0+100). static void testThreeSegmentsUnevenTilesExactly() { HeaderRect h{0, 0, 100, 20}; auto rects = computeSegmentRects(h, 3); CHECK(rects.size() == 3); CHECK((rects[0] == SegmentRect{0, 0, 33, 20})); CHECK((rects[1] == SegmentRect{33, 0, 33, 20})); CHECK((rects[2] == SegmentRect{66, 0, 34, 20})); // Adjacent segments abut (no gap, no overlap): each left edge == prior right. CHECK(rects[1].x == rects[0].x + rects[0].width); CHECK(rects[2].x == rects[1].x + rects[1].width); // Last segment's right edge reaches the header's right edge exactly. CHECK(rects[2].x + rects[2].width == h.x + h.width); } // --- Layout: full coverage invariant for a range of widths ------------------- // For any width and count, the segments must tile [x, x+width) with no gap/overlap: // first left == x, last right == x+width, every mid boundary shared. static void testTilingCoversHeaderForVariousWidths() { for (int width = 1; width <= 200; ++width) { for (int count = 1; count <= 5; ++count) { HeaderRect h{7, 3, width, 16}; auto rects = computeSegmentRects(h, count); CHECK(static_cast(rects.size()) == count); CHECK(rects.front().x == h.x); CHECK(rects.back().x + rects.back().width == h.x + h.width); for (std::size_t i = 1; i < rects.size(); ++i) CHECK(rects[i].x == rects[i - 1].x + rects[i - 1].width); } } } // --- Layout: degenerate -------------------------------------------------------- // segmentCount <= 0 or non-positive width yields no rects (the panel draws nothing // / falls back to full-client grid). static void testLayoutDegenerate() { CHECK(computeSegmentRects(HeaderRect{0, 0, 100, 20}, 0).empty()); CHECK(computeSegmentRects(HeaderRect{0, 0, 100, 20}, -3).empty()); CHECK(computeSegmentRects(HeaderRect{0, 0, 0, 20}, 2).empty()); CHECK(computeSegmentRects(HeaderRect{0, 0, -5, 20}, 2).empty()); } // A single segment fills the whole header. static void testSingleSegmentFillsHeader() { HeaderRect h{5, 5, 80, 24}; auto rects = computeSegmentRects(h, 1); CHECK(rects.size() == 1); CHECK((rects[0] == SegmentRect{5, 5, 80, 24})); } // --- Hit-test: hits ----------------------------------------------------------- // A point inside each segment maps to that segment's index. Header 100 wide at // x=10, two segments [10,60) and [60,110). static void testHitTestHitsEachSegment() { HeaderRect h{10, 4, 100, 24}; CHECK(hitTestSegment(10, 4, h, 2) == 0); // top-left corner of segment 0 CHECK(hitTestSegment(35, 15, h, 2) == 0); // middle of segment 0 CHECK(hitTestSegment(59, 27, h, 2) == 0); // last pixel of segment 0 (band) CHECK(hitTestSegment(60, 15, h, 2) == 1); // first pixel of segment 1 CHECK(hitTestSegment(109, 4, h, 2) == 1); // last pixel column of segment 1 } // The boundary pixel belongs to exactly ONE segment (half-open): px==60 is the // start of segment 1, never the end of segment 0 — so no pixel is double-claimed. static void testHitTestBoundaryHalfOpen() { HeaderRect h{10, 4, 100, 24}; CHECK(hitTestSegment(59, 10, h, 2) == 0); CHECK(hitTestSegment(60, 10, h, 2) == 1); // Three-way boundaries at floor(i*100/2)+x are consistent for N=3 too. HeaderRect h3{0, 0, 99, 20}; // 99/3 = 33 exactly -> edges 0,33,66,99 CHECK(hitTestSegment(32, 5, h3, 3) == 0); CHECK(hitTestSegment(33, 5, h3, 3) == 1); CHECK(hitTestSegment(65, 5, h3, 3) == 1); CHECK(hitTestSegment(66, 5, h3, 3) == 2); CHECK(hitTestSegment(98, 5, h3, 3) == 2); } // --- Hit-test: misses --------------------------------------------------------- // Points outside the header band on any side miss (-1). Below the band is where // the grid lives; the panel treats -1 as "fall through to grid handling". static void testHitTestMissesOutsideBand() { HeaderRect h{10, 4, 100, 24}; CHECK(hitTestSegment(9, 10, h, 2) == -1); // left of header CHECK(hitTestSegment(110, 10, h, 2) == -1); // right edge (px == x+width, excluded) CHECK(hitTestSegment(50, 3, h, 2) == -1); // above the band CHECK(hitTestSegment(50, 28, h, 2) == -1); // below the band (into the grid) CHECK(hitTestSegment(200, 200, h, 2) == -1); // far away } // Degenerate hit-test inputs miss safely. static void testHitTestDegenerate() { HeaderRect h{10, 4, 100, 24}; CHECK(hitTestSegment(50, 15, h, 0) == -1); // no segments CHECK(hitTestSegment(50, 15, h, -2) == -1); CHECK(hitTestSegment(50, 15, HeaderRect{10, 4, 0, 24}, 2) == -1); // zero width CHECK(hitTestSegment(50, 15, HeaderRect{10, 4, 100, 0}, 2) == -1); // zero height } // --- Cross-check: every point in the header hit-tests to the segment that DREW // it (hit-test and layout agree, the load-bearing consistency invariant). ------ static void testHitTestMatchesLayout() { HeaderRect h{4, 2, 87, 18}; // deliberately awkward width/origin const int count = 3; auto rects = computeSegmentRects(h, count); for (int px = h.x; px < h.x + h.width; ++px) { const int seg = hitTestSegment(px, h.y + 1, h, count); CHECK(seg >= 0); // The returned segment's rect must contain px (half-open). const SegmentRect& r = rects[static_cast(seg)]; CHECK(px >= r.x && px < r.x + r.width); } } int main() { testTwoSegmentsEvenSplit(); testThreeSegmentsUnevenTilesExactly(); testTilingCoversHeaderForVariousWidths(); testLayoutDegenerate(); testSingleSegmentFillsHeader(); testHitTestHitsEachSegment(); testHitTestBoundaryHalfOpen(); testHitTestMissesOutsideBand(); testHitTestDegenerate(); testHitTestMatchesLayout(); if (g_fail == 0) std::printf("All tests passed.\n"); return g_fail ? 1 : 0; }