// Standalone tests for reasampler::instrument::ui::sample_chrome — no VST3, no REAPER, no // test framework. // // Covers: the chrome band's two rows (toolbar over strip row, tiling the band exactly); the // toolbar's fixed right-anchored run in order (Hold, bake, preview, velocity cell, // Mono|Stereo, Browse) with the title taking the remainder; the velocity and Hold knobs // centred in their cells above their labels; the piano strip owning its whole row at every // width; no rect on the // toolbar overlapping any other; degenerate bands yielding no inverted rects; and the preview // button's play-triangle glyph, which sits inside the button without changing its rect. #include "../src/core/instrument/ui/knob_deck.h" #include "../src/core/instrument/ui/sample_bands.h" #include "../src/core/instrument/ui/sample_chrome.h" #include #include using namespace reasampler; using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // The real constant, not a copy: the shell passes kDeckKnobSize into chromeRects, and a // hand-copied stand-in here had already drifted from it once. static constexpr int kKnob = kDeckKnobSize; static Rect chromeBand(int w = kEditorMinWidth, int h = kEditorMinHeight) { return computeSampleBands(w, h, 120).chrome; } // True when the two rects share at least one pixel. static bool overlaps(const Rect& a, const Rect& b) { if (a.empty() || b.empty()) return false; return a.x < b.right() && b.x < a.right() && a.y < b.bottom() && b.y < a.bottom(); } static void testRowsTileTheBandExactly() { const Rect band = chromeBand(); const ChromeRects r = chromeRects(band, kKnob); CHECK(r.toolbar.y == band.y); CHECK(r.controls.y == r.toolbar.bottom()); CHECK(r.controls.bottom() == band.bottom()); CHECK(r.toolbar.x == band.x && r.toolbar.right() == band.right()); CHECK(r.controls.x == band.x && r.controls.right() == band.right()); // The toolbar has to be tall enough for the velocity knob cell it now carries. CHECK(r.toolbar.height >= kKnob); CHECK(r.controls.height > 0); } static void testToolbarRunIsOrderedRightToLeftWithoutOverlap() { const Rect band = chromeBand(); const ChromeRects r = chromeRects(band, kKnob); // Rightmost first: Browse, stereo, mono, velocity cell, preview, title. CHECK(r.navBrowse.right() == band.right() - kPad); CHECK(r.navBrowse.width == kNavButtonWidth); CHECK(r.chanStereo.right() <= r.navBrowse.x); CHECK(r.chanMono.right() == r.chanStereo.x); CHECK(r.velCell.right() <= r.chanMono.x); CHECK(r.preview.right() <= r.velCell.x); CHECK(r.bake.right() <= r.preview.x); CHECK(r.bake.width == kBakeButtonWidth); CHECK(r.bake.y == r.preview.y); // shares the run's button baseline CHECK(r.bake.height == r.preview.height); CHECK(r.holdCell.right() <= r.bake.x); CHECK(r.title.right() <= r.holdCell.x); // the title yields to the whole run CHECK(r.title.x == band.x + kPad); CHECK(r.title.width > 0); // Every toolbar rect sits inside the toolbar row. const Rect items[] = {r.title, r.holdCell, r.bake, r.preview, r.velCell, r.chanMono, r.chanStereo, r.navBrowse}; for (const Rect& it : items) { CHECK(it.y >= r.toolbar.y && it.bottom() <= r.toolbar.bottom()); } } static void testChromePartsNeverOverlapAtAnyWidth() { for (int w = 560; w <= 2400; w += 37) { const ChromeRects r = chromeRects(chromeBand(w, 620), kKnob); // The strip row and the toolbar row are disjoint by construction; the strip must // stay inside its own row, clear of every control. CHECK(!overlaps(r.toolbar, r.rootStrip)); CHECK(r.rootStrip.y >= r.controls.y && r.rootStrip.bottom() <= r.controls.bottom()); const Rect items[] = {r.holdCell, r.bake, r.preview, r.velCell, r.chanMono, r.chanStereo, r.navBrowse}; for (const Rect& it : items) { CHECK(!overlaps(it, r.rootStrip)); CHECK(!overlaps(it, r.title)); } // The run's own members are pairwise disjoint (the knob/label pairs are inside their // cells, so they are checked against the cell's neighbours, not the cell). constexpr int kRunCount = static_cast(sizeof(items) / sizeof(items[0])); for (int i = 0; i < kRunCount; ++i) { for (int j = i + 1; j < kRunCount; ++j) CHECK(!overlaps(items[i], items[j])); } } } static void testStripOwnsItsWholeRowAndGrowsWithTheWindow() { const ChromeRects narrow = chromeRects(chromeBand(600, 620), kKnob); const ChromeRects wide = chromeRects(chromeBand(1000, 620), kKnob); for (const ChromeRects* r : {&narrow, &wide}) { // Inset only by the shared band pad — the same inset the waveform band beneath uses, // so the two line up. No control shortens it. CHECK(r->rootStrip.x == r->controls.x + kPad); CHECK(r->rootStrip.right() == r->controls.right() - kPad); } CHECK(wide.rootStrip.width == narrow.rootStrip.width + 400); // The fixed run keeps its size; every extra pixel goes to the title, not the run. CHECK(wide.preview.width == narrow.preview.width); CHECK(wide.velCell.width == narrow.velCell.width); CHECK(wide.title.width == narrow.title.width + 400); } static void testVelocityKnobIsCentredInItsCellAboveTheLabel() { const ChromeRects r = chromeRects(chromeBand(), kKnob); CHECK(r.velKnob.width == kKnob && r.velKnob.height == kKnob); CHECK(r.velKnob.y == r.velCell.y); const int leftGap = r.velKnob.x - r.velCell.x; const int rightGap = r.velCell.right() - r.velKnob.right(); CHECK(leftGap == rightGap); // horizontally centred in the cell CHECK(r.velLabel.y == r.velKnob.bottom()); CHECK(r.velLabel.bottom() == r.velCell.bottom()); CHECK(r.velLabel.height > 0); CHECK(r.velLabel.x == r.velCell.x && r.velLabel.right() == r.velCell.right()); } // The Hold cell reserves its width whether or not the control is drawn — a run laid out // conditionally would slide Bake and Preview out from under the pointer whenever a loop is // dialled in or out. Its interior follows the velocity cell's grammar exactly. static void testHoldCellIsReservedAndFollowsTheVelocityCellGrammar() { const ChromeRects r = chromeRects(chromeBand(), kKnob); CHECK(r.holdCell.width == r.velCell.width); CHECK(r.holdCell.y == r.velCell.y && r.holdCell.height == r.velCell.height); CHECK(r.holdKnob.width == kKnob && r.holdKnob.height == kKnob); CHECK(r.holdKnob.y == r.holdCell.y); CHECK(r.holdKnob.x - r.holdCell.x == r.holdCell.right() - r.holdKnob.right()); CHECK(r.holdLabel.y == r.holdKnob.bottom()); CHECK(r.holdLabel.bottom() == r.holdCell.bottom()); CHECK(r.holdLabel.x == r.holdCell.x && r.holdLabel.right() == r.holdCell.right()); // Widening the window leaves the reservation alone; the title takes the extra pixels. const ChromeRects wide = chromeRects(chromeBand(1000, 620), kKnob); CHECK(wide.holdCell.width == r.holdCell.width); } static void testDegenerateBandYieldsNoInvertedRects() { const ChromeRects empty = chromeRects(Rect{}, kKnob); CHECK(empty.toolbar.empty() && empty.controls.empty()); CHECK(empty.rootStrip.empty() && empty.preview.empty()); // A band far too narrow for the fixed run: everything collapses left, nothing inverts. const ChromeRects tiny = chromeRects(Rect::ltrb(0, 0, 40, kTitleHeight + kChromeRowHeight), kKnob); const Rect items[] = {tiny.title, tiny.holdCell, tiny.holdKnob, tiny.holdLabel, tiny.bake, tiny.preview, tiny.velCell, tiny.velKnob, tiny.velLabel, tiny.chanMono, tiny.chanStereo, tiny.navBrowse, tiny.rootStrip}; for (const Rect& it : items) CHECK(it.right() >= it.x && it.bottom() >= it.y); } static void testPreviewGlyphSitsInsideTheButtonAndPointsRight() { const ChromeRects r = chromeRects(chromeBand(), kKnob); const PreviewGlyph g = previewGlyph(r.preview); CHECK(!g.empty()); // Wholly inside the button — the glyph replaces the label, it does not resize the target. CHECK(g.leftX >= r.preview.x && g.apexX <= r.preview.right()); CHECK(g.topY >= r.preview.y && g.bottomY <= r.preview.bottom()); // Right-pointing, and the apex on the button's own centre line so it reads as balanced. CHECK(g.apexX > g.leftX); CHECK(g.apexY == r.preview.y + r.preview.height / 2); CHECK(g.apexY - g.topY == g.bottomY - g.apexY); // isosceles about the centre line // The button's rect is what the hit-test uses, and the glyph must not have moved it: the // preview button still sits at the run's fixed size, exactly where the text button did. CHECK(r.preview.width == 64 && r.preview.height == 24); } static void testPreviewGlyphDegradesRatherThanOverflowing() { // An unusably small button yields an empty glyph (draw nothing) rather than a triangle // spilling past the button edge. CHECK(previewGlyph(Rect{}).empty()); CHECK(previewGlyph(Rect::ltrb(0, 0, 6, 6)).empty()); // A tall, wide button caps the glyph instead of scaling without bound. const PreviewGlyph big = previewGlyph(Rect::ltrb(0, 0, 400, 200)); CHECK(!big.empty()); CHECK(big.bottomY - big.topY <= 14); } static void testToolbarOnlyBandStillPlacesTheNav() { // A band clipped to just the toolbar row: the strip row is empty but Browse still // resolves, so the empty state's call-to-action is never unreachable. const ChromeRects r = chromeRects(Rect::ltrb(0, 0, 400, kTitleHeight), kKnob); CHECK(r.toolbar.height == kTitleHeight); CHECK(r.controls.empty()); CHECK(r.rootStrip.empty()); CHECK(r.navBrowse.width == kNavButtonWidth); } int main() { testRowsTileTheBandExactly(); testToolbarRunIsOrderedRightToLeftWithoutOverlap(); testChromePartsNeverOverlapAtAnyWidth(); testStripOwnsItsWholeRowAndGrowsWithTheWindow(); testVelocityKnobIsCentredInItsCellAboveTheLabel(); testHoldCellIsReservedAndFollowsTheVelocityCellGrammar(); testDegenerateBandYieldsNoInvertedRects(); testPreviewGlyphSitsInsideTheButtonAndPointsRight(); testPreviewGlyphDegradesRatherThanOverflowing(); testToolbarOnlyBandStillPlacesTheNav(); if (g_fail == 0) { std::printf("sample_chrome: all tests passed\n"); return 0; } std::printf("sample_chrome: %d failure(s)\n", g_fail); return 1; }