// Standalone tests for reasampler::bank_grid — no REAPER, no test framework. // Same fast loop as the sibling pure tests: assert the grid-layout math and the // thumbnail cache-key stringification directly. // // Covers (M5 Wave A brief §Test cases): column count for a given panel width; // cell rects for a full grid (row/column wrapping); the partial-last-row case; // itemCount == 0; a single item; a panel too narrow for even one cell (clamp to // one column); content-height for exact and partial rows; cache-key stability, // width/generation/id sensitivity, and length-prefix collision resistance. #include "../src/bank_grid.h" #include #include #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) // A spec with round numbers so expected rects are trivial to hand-compute: // cell 100x50, gap 10. Column pitch = 110, row pitch = 60, margin = 10. static GridSpec spec() { GridSpec s; s.cellWidth = 100; s.cellHeight = 50; s.gap = 10; return s; } // gap(10) + n*(100+10): 1 col needs 120, 2 need 230, 3 need 340. static void testColumnsForWidth() { const GridSpec s = spec(); CHECK(columnsForWidth(120, s) == 1); // exactly one cell + margins CHECK(columnsForWidth(229, s) == 1); // one pixel short of two columns CHECK(columnsForWidth(230, s) == 2); // exactly two CHECK(columnsForWidth(345, s) == 3); // three plus slack CHECK(columnsForWidth(1000, s) == 9); // many } // A panel narrower than a single cell still yields one column (clipped by the // window, never zero — that would drop every sample). static void testTooNarrowClampsToOneColumn() { const GridSpec s = spec(); CHECK(columnsForWidth(50, s) == 1); CHECK(columnsForWidth(0, s) == 1); CHECK(columnsForWidth(-20, s) == 1); } // Zero items -> no rects (empty state is the panel's concern, not the layout's). static void testZeroItems() { const GridSpec s = spec(); auto rects = computeCellRects(0, 500, s); CHECK(rects.empty()); CHECK(contentHeight(0, 500, s) == 0); } // One item sits at the top-left margin. static void testSingleItem() { const GridSpec s = spec(); auto rects = computeCellRects(1, 500, s); CHECK(rects.size() == 1); CHECK(rects[0] == (CellRect{10, 10, 100, 50})); } // A full 2x2: width forces exactly two columns, four items fill two rows. static void testFullGridWrapping() { const GridSpec s = spec(); // Width 230 -> exactly 2 columns. auto rects = computeCellRects(4, 230, s); CHECK(rects.size() == 4); // Row 0: x = 10, 120 ; y = 10. CHECK(rects[0] == (CellRect{10, 10, 100, 50})); CHECK(rects[1] == (CellRect{120, 10, 100, 50})); // Row 1: y = 70 (10 + 60). CHECK(rects[2] == (CellRect{10, 70, 100, 50})); CHECK(rects[3] == (CellRect{120, 70, 100, 50})); } // Partial last row: 5 items in a 2-column grid -> rows of 2,2,1. The lone last // cell is left-aligned in its row (no centering), same x as column 0. static void testPartialLastRow() { const GridSpec s = spec(); auto rects = computeCellRects(5, 230, s); // 2 columns CHECK(rects.size() == 5); CHECK(rects[4] == (CellRect{10, 130, 100, 50})); // row 2, col 0: y = 10 + 2*60 // content height spans 3 rows: 10 + 3*(50+10) = 190. CHECK(contentHeight(5, 230, s) == 190); } // Content height for an exact-fill grid: 4 items / 2 cols = 2 rows. static void testContentHeightExactRows() { const GridSpec s = spec(); CHECK(contentHeight(4, 230, s) == 130); // 10 + 2*60 CHECK(contentHeight(2, 230, s) == 70); // 10 + 1*60 } // The cache key is stable and sensitive to every field. static void testCacheKeyStabilityAndSensitivity() { ThumbnailKey a{"sample-1", 120, 7}; ThumbnailKey aSame{"sample-1", 120, 7}; CHECK(thumbnailKeyString(a) == thumbnailKeyString(aSame)); // deterministic ThumbnailKey diffWidth{"sample-1", 121, 7}; ThumbnailKey diffGen{"sample-1", 120, 8}; ThumbnailKey diffId{"sample-2", 120, 7}; CHECK(thumbnailKeyString(a) != thumbnailKeyString(diffWidth)); CHECK(thumbnailKeyString(a) != thumbnailKeyString(diffGen)); CHECK(thumbnailKeyString(a) != thumbnailKeyString(diffId)); } // A sampleId containing the delimiter byte must not forge a collision with a // different key. Without the length prefix, id "x|9" with width 0 and id "x" with // width 9 would both tail-concatenate through the '|' delimiter and could match; // the length prefix on the id disambiguates them. static void testCacheKeyDelimiterCollisionResistance() { // The canonical would-be collision: moving a "|9" fragment from the id into // the width field. Length-prefixing the id makes the two forms distinct. ThumbnailKey a{"x|9", 0, 0}; ThumbnailKey b{"x", 9, 0}; CHECK(thumbnailKeyString(a) != thumbnailKeyString(b)); // A second pair in the same spirit, delimiters in both id and adjacent fields. ThumbnailKey k1{"1|2", 3, 0}; ThumbnailKey k2{"1", 23, 0}; CHECK(thumbnailKeyString(k1) != thumbnailKeyString(k2)); } int main() { testColumnsForWidth(); testTooNarrowClampsToOneColumn(); testZeroItems(); testSingleItem(); testFullGridWrapping(); testPartialLastRow(); testContentHeightExactRows(); testCacheKeyStabilityAndSensitivity(); testCacheKeyDelimiterCollisionResistance(); if (g_fail == 0) std::printf("All tests passed.\n"); return g_fail ? 1 : 0; }