cd64be9129
Deletes src/action_buttons.{h,cpp} and tests/test_action_buttons.cpp;
removes the static library, test target, and reaper_reasampler link
entry from CMakeLists.txt; scrubs all dangling comment references.
249 lines
11 KiB
C++
249 lines
11 KiB
C++
// Standalone tests for reasampler::drag_out — no REAPER, no test framework. Same fast loop
|
|
// as the sibling pure tests (mode_switch et al.): assert the gesture-
|
|
// boundary decision and the path-list assembly directly.
|
|
//
|
|
// Covers (M11 drag-out brief §test cases):
|
|
// * Gesture boundary: inside-panel drag stays Internal; leaving the client area with
|
|
// armed samples -> OsDrag; no armed samples (or not dragging) -> None; half-open edge
|
|
// behavior; re-entry back inside returns to Internal (position-only decision).
|
|
// * Path-list assembly: single, multi, dedupe (cross-bank copy case), skip-missing,
|
|
// skip-unresolved, empty selection, order preservation, mixed tallies.
|
|
|
|
#include "../src/drag_out.h"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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)
|
|
|
|
// --- Gesture boundary ---------------------------------------------------------
|
|
|
|
static const PanelClientRect kPanel{0, 0, 400, 300};
|
|
|
|
// A drag with samples, pointer well inside the client rect -> the existing internal drag
|
|
// (invariant #4: inside-panel drag stays internal, unchanged).
|
|
static void testInsidePanelStaysInternal() {
|
|
DragState s{/*dragging=*/true, /*hasArmedSamples=*/true};
|
|
CHECK(decideGesture(200, 150, kPanel, s) == DragGesture::Internal);
|
|
CHECK(decideGesture(0, 0, kPanel, s) == DragGesture::Internal); // top-left corner
|
|
CHECK(decideGesture(399, 299, kPanel, s) == DragGesture::Internal); // last inside px
|
|
}
|
|
|
|
// A drag with samples whose pointer has left the client rect (any edge) -> OS drag.
|
|
static void testLeavingClientAreaIsOsDrag() {
|
|
DragState s{/*dragging=*/true, /*hasArmedSamples=*/true};
|
|
CHECK(decideGesture(-1, 150, kPanel, s) == DragGesture::OsDrag); // left of panel
|
|
CHECK(decideGesture(400, 150, kPanel, s) == DragGesture::OsDrag); // right edge (x+w)
|
|
CHECK(decideGesture(200, -5, kPanel, s) == DragGesture::OsDrag); // above
|
|
CHECK(decideGesture(200, 300, kPanel, s) == DragGesture::OsDrag); // below (y+h)
|
|
CHECK(decideGesture(1000, 1000, kPanel, s) == DragGesture::OsDrag);// far outside
|
|
}
|
|
|
|
// The half-open boundary: x+width and y+height are OUTSIDE (OsDrag), the pixel just inside
|
|
// is Internal — matches the panel's other hit-tests so the edge is claimed consistently.
|
|
static void testBoundaryHalfOpen() {
|
|
DragState s{true, true};
|
|
CHECK(decideGesture(399, 150, kPanel, s) == DragGesture::Internal);
|
|
CHECK(decideGesture(400, 150, kPanel, s) == DragGesture::OsDrag);
|
|
CHECK(decideGesture(200, 299, kPanel, s) == DragGesture::Internal);
|
|
CHECK(decideGesture(200, 300, kPanel, s) == DragGesture::OsDrag);
|
|
}
|
|
|
|
// No armed samples -> None regardless of position (an empty-payload drag never goes to the
|
|
// OS). Not dragging -> None even with samples (the shell asks only mid-drag, but the guard
|
|
// is explicit).
|
|
static void testNoDragOrNoSamplesIsNone() {
|
|
CHECK(decideGesture(1000, 1000, kPanel, DragState{true, false}) == DragGesture::None);
|
|
CHECK(decideGesture(200, 150, kPanel, DragState{true, false}) == DragGesture::None);
|
|
CHECK(decideGesture(1000, 1000, kPanel, DragState{false, true}) == DragGesture::None);
|
|
CHECK(decideGesture(200, 150, kPanel, DragState{false, false}) == DragGesture::None);
|
|
}
|
|
|
|
// Re-entry: the decision is position-only, so a pointer that left (OsDrag) and came back
|
|
// inside reads Internal again. (The shell, having handed off to the modal OS loop, simply
|
|
// stops asking — but the pure function must not be stateful.)
|
|
static void testReentryReturnsInternal() {
|
|
DragState s{true, true};
|
|
CHECK(decideGesture(500, 150, kPanel, s) == DragGesture::OsDrag); // left
|
|
CHECK(decideGesture(200, 150, kPanel, s) == DragGesture::Internal); // re-entered
|
|
}
|
|
|
|
// A non-zero panel origin (the client rect need not sit at 0,0) — the boundary tracks the
|
|
// rect, not the absolute axes.
|
|
static void testOffsetPanelRect() {
|
|
PanelClientRect p{50, 20, 100, 80}; // spans x[50,150) y[20,100)
|
|
DragState s{true, true};
|
|
CHECK(decideGesture(100, 60, p, s) == DragGesture::Internal);
|
|
CHECK(decideGesture(49, 60, p, s) == DragGesture::OsDrag); // just left of origin
|
|
CHECK(decideGesture(150, 60, p, s) == DragGesture::OsDrag); // x+width
|
|
CHECK(decideGesture(100, 19, p, s) == DragGesture::OsDrag); // just above origin
|
|
}
|
|
|
|
// --- S17 InstrumentDrop gesture (single-capture over REAPER UI) ---------------
|
|
//
|
|
// M11 REGRESSION GUARD (load-bearing): every M11 case above uses DragState{true, true},
|
|
// which leaves singleCapture=overReaperUi=false — so an M11-era payload outside the client
|
|
// rect still decides OsDrag exactly as before. The tests above ARE the M11 non-regression
|
|
// proof; these add the new middle case.
|
|
|
|
// A SINGLE-capture drag that has left the panel but is still over REAPER's own UI is an
|
|
// instrument drop (heading for a track's FX button), NOT an OS drag.
|
|
static void testSingleCaptureOverReaperUiIsInstrumentDrop() {
|
|
DragState s{/*dragging=*/true, /*hasArmedSamples=*/true,
|
|
/*singleCapture=*/true, /*overReaperUi=*/true};
|
|
CHECK(decideGesture(500, 150, kPanel, s) == DragGesture::InstrumentDrop); // right of panel
|
|
CHECK(decideGesture(-5, 150, kPanel, s) == DragGesture::InstrumentDrop); // left of panel
|
|
CHECK(decideGesture(200, 400, kPanel, s) == DragGesture::InstrumentDrop); // below
|
|
}
|
|
|
|
// InstrumentDrop is an OUTSIDE-only refinement: the same single-capture state INSIDE the
|
|
// client rect is still the unchanged Internal bank-to-bank drag (invariant #4).
|
|
static void testSingleCaptureInsidePanelStaysInternal() {
|
|
DragState s{true, true, /*singleCapture=*/true, /*overReaperUi=*/true};
|
|
CHECK(decideGesture(200, 150, kPanel, s) == DragGesture::Internal);
|
|
}
|
|
|
|
// A single-capture drag that has left REAPER ENTIRELY (overReaperUi=false) falls through to
|
|
// OsDrag — the M11 OS drag-out to Explorer/another DAW, unchanged. This is the boundary
|
|
// refinement's other half: leaving the client rect no longer immediately means OS-bound.
|
|
static void testSingleCaptureOffReaperIsOsDrag() {
|
|
DragState s{true, true, /*singleCapture=*/true, /*overReaperUi=*/false};
|
|
CHECK(decideGesture(500, 150, kPanel, s) == DragGesture::OsDrag);
|
|
}
|
|
|
|
// A MULTI-capture drag over REAPER's UI is REJECTED for InstrumentDrop (the S17 open-question
|
|
// lean): it is NOT a single instrument placement, so it falls through to OsDrag even while
|
|
// over REAPER's UI — the multi-file drag-out is the natural gesture for a multi payload.
|
|
static void testMultiCaptureOverReaperUiIsOsDrag() {
|
|
DragState s{true, true, /*singleCapture=*/false, /*overReaperUi=*/true};
|
|
CHECK(decideGesture(500, 150, kPanel, s) == DragGesture::OsDrag);
|
|
}
|
|
|
|
// Not-dragging / no-armed-samples still short-circuits to None regardless of the S17 fields.
|
|
static void testS17FieldsIgnoredWhenNotDragging() {
|
|
CHECK(decideGesture(500, 150, kPanel,
|
|
DragState{false, true, true, true}) == DragGesture::None);
|
|
CHECK(decideGesture(500, 150, kPanel,
|
|
DragState{true, false, true, true}) == DragGesture::None);
|
|
}
|
|
|
|
// --- Path-list assembly -------------------------------------------------------
|
|
|
|
static ResolvedSample ok(const std::string& p) { return ResolvedSample{p, true}; }
|
|
static ResolvedSample missing(const std::string& p) { return ResolvedSample{p, false}; }
|
|
static ResolvedSample unresolved() { return ResolvedSample{"", false}; }
|
|
|
|
static void testSinglePath() {
|
|
PathList l = assemblePathList({ok("C:/proj/bank/a.wav")});
|
|
CHECK(l.paths.size() == 1);
|
|
CHECK(l.paths[0] == "C:/proj/bank/a.wav");
|
|
CHECK(l.skippedMissing == 0);
|
|
CHECK(l.skippedUnresolved == 0);
|
|
CHECK(l.skippedDuplicate == 0);
|
|
}
|
|
|
|
// Multiple distinct paths pass through in selection order (order preserved).
|
|
static void testMultiPreservesOrder() {
|
|
PathList l = assemblePathList({ok("b.wav"), ok("a.wav"), ok("c.wav")});
|
|
CHECK(l.paths.size() == 3);
|
|
CHECK(l.paths[0] == "b.wav");
|
|
CHECK(l.paths[1] == "a.wav");
|
|
CHECK(l.paths[2] == "c.wav");
|
|
}
|
|
|
|
// Two index entries resolving to the SAME file (the cross-bank copy case — one file, two
|
|
// entries) yield ONE CF_HDROP path; the extra is counted, first occurrence wins.
|
|
static void testDedupeSamePath() {
|
|
PathList l = assemblePathList({ok("x.wav"), ok("y.wav"), ok("x.wav")});
|
|
CHECK(l.paths.size() == 2);
|
|
CHECK(l.paths[0] == "x.wav");
|
|
CHECK(l.paths[1] == "y.wav");
|
|
CHECK(l.skippedDuplicate == 1);
|
|
}
|
|
|
|
// A stale index entry (file gone from disk) is skipped — never a dangling path on the OS
|
|
// clipboard.
|
|
static void testSkipMissing() {
|
|
PathList l = assemblePathList({ok("a.wav"), missing("gone.wav"), ok("b.wav")});
|
|
CHECK(l.paths.size() == 2);
|
|
CHECK(l.paths[0] == "a.wav");
|
|
CHECK(l.paths[1] == "b.wav");
|
|
CHECK(l.skippedMissing == 1);
|
|
}
|
|
|
|
// An unresolvable entry (empty path — no project dir / empty relative) is skipped; note an
|
|
// empty path is skippedUnresolved, NOT skippedMissing, even though fileExists is false.
|
|
static void testSkipUnresolved() {
|
|
PathList l = assemblePathList({ok("a.wav"), unresolved(), ok("b.wav")});
|
|
CHECK(l.paths.size() == 2);
|
|
CHECK(l.skippedUnresolved == 1);
|
|
CHECK(l.skippedMissing == 0);
|
|
}
|
|
|
|
// Empty selection -> empty list, all tallies zero (the shell reads paths.empty() and does
|
|
// not start a drag).
|
|
static void testEmptySelection() {
|
|
PathList l = assemblePathList({});
|
|
CHECK(l.paths.empty());
|
|
CHECK(l.skippedMissing == 0);
|
|
CHECK(l.skippedUnresolved == 0);
|
|
CHECK(l.skippedDuplicate == 0);
|
|
}
|
|
|
|
// All-skipped selection -> empty list with the right tallies (nothing draggable).
|
|
static void testAllSkippedYieldsEmpty() {
|
|
PathList l = assemblePathList({missing("g1.wav"), unresolved(), missing("g2.wav")});
|
|
CHECK(l.paths.empty());
|
|
CHECK(l.skippedMissing == 2);
|
|
CHECK(l.skippedUnresolved == 1);
|
|
}
|
|
|
|
// Mixed: every skip class at once, plus a survivor, with independent tallies.
|
|
static void testMixedTallies() {
|
|
PathList l = assemblePathList({
|
|
ok("keep.wav"), // survives
|
|
missing("gone.wav"), // skippedMissing
|
|
unresolved(), // skippedUnresolved
|
|
ok("keep.wav"), // skippedDuplicate
|
|
ok("also.wav"), // survives
|
|
});
|
|
CHECK(l.paths.size() == 2);
|
|
CHECK(l.paths[0] == "keep.wav");
|
|
CHECK(l.paths[1] == "also.wav");
|
|
CHECK(l.skippedMissing == 1);
|
|
CHECK(l.skippedUnresolved == 1);
|
|
CHECK(l.skippedDuplicate == 1);
|
|
}
|
|
|
|
int main() {
|
|
testInsidePanelStaysInternal();
|
|
testLeavingClientAreaIsOsDrag();
|
|
testBoundaryHalfOpen();
|
|
testNoDragOrNoSamplesIsNone();
|
|
testReentryReturnsInternal();
|
|
testOffsetPanelRect();
|
|
|
|
testSingleCaptureOverReaperUiIsInstrumentDrop();
|
|
testSingleCaptureInsidePanelStaysInternal();
|
|
testSingleCaptureOffReaperIsOsDrag();
|
|
testMultiCaptureOverReaperUiIsOsDrag();
|
|
testS17FieldsIgnoredWhenNotDragging();
|
|
|
|
testSinglePath();
|
|
testMultiPreservesOrder();
|
|
testDedupeSamePath();
|
|
testSkipMissing();
|
|
testSkipUnresolved();
|
|
testEmptySelection();
|
|
testAllSkippedYieldsEmpty();
|
|
testMixedTallies();
|
|
|
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|