Fix drag-out losing audio and FX-container drops losing the capture; re-home ingest under shell/actions
This commit is contained in:
@@ -221,6 +221,52 @@ static void testMixedTallies() {
|
||||
CHECK(l.skippedDuplicate == 1);
|
||||
}
|
||||
|
||||
// --- OS hand-off ordering -----------------------------------------------------
|
||||
//
|
||||
// The drag-out-lands-without-audio regression: the shell used to wind its internal drag down
|
||||
// (release capture, clear drag state) BEFORE checking whether the payload had resolved to any
|
||||
// on-disk file. A payload that resolved to nothing therefore consumed the gesture — the drag
|
||||
// died half-torn-down and the user saw a drop with no audio and dragged again. The two side
|
||||
// effects are one decision; these pin that.
|
||||
|
||||
// Nothing draggable -> hand off nothing AND keep the internal drag alive.
|
||||
static void testEmptyPathsHandsOffNothingAndKeepsInternalDrag() {
|
||||
const OsHandoff h = decideOsHandoff({});
|
||||
CHECK(!h.startOsDrag);
|
||||
CHECK(!h.releaseInternalDrag);
|
||||
}
|
||||
|
||||
// A resolvable payload -> release the internal drag, then start the OS drag.
|
||||
static void testResolvablePayloadHandsOff() {
|
||||
const OsHandoff one = decideOsHandoff({"C:/proj/bank/a.wav"});
|
||||
CHECK(one.startOsDrag);
|
||||
CHECK(one.releaseInternalDrag);
|
||||
const OsHandoff many = decideOsHandoff({"a.wav", "b.wav", "c.wav"});
|
||||
CHECK(many.startOsDrag);
|
||||
CHECK(many.releaseInternalDrag);
|
||||
}
|
||||
|
||||
// The coupling itself: releasing without starting is the defect, so it must be unreachable
|
||||
// for every input the assembler can produce.
|
||||
static void testNeverReleasesWithoutStarting() {
|
||||
const std::vector<std::vector<std::string>> inputs = {
|
||||
{}, {"a.wav"}, {"a.wav", "b.wav"}, {""},
|
||||
};
|
||||
for (const std::vector<std::string>& in : inputs) {
|
||||
const OsHandoff h = decideOsHandoff(in);
|
||||
CHECK(!(h.releaseInternalDrag && !h.startOsDrag));
|
||||
}
|
||||
}
|
||||
|
||||
// End to end through the assembler: a selection whose every entry is stale/unresolvable
|
||||
// yields the keep-the-drag verdict, which is exactly the case the shell used to mishandle.
|
||||
static void testAllSkippedSelectionKeepsInternalDrag() {
|
||||
const PathList l = assemblePathList({missing("g1.wav"), unresolved()});
|
||||
const OsHandoff h = decideOsHandoff(l.paths);
|
||||
CHECK(!h.startOsDrag);
|
||||
CHECK(!h.releaseInternalDrag);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testInsidePanelStaysInternal();
|
||||
testLeavingClientAreaIsOsDrag();
|
||||
@@ -244,6 +290,11 @@ int main() {
|
||||
testAllSkippedYieldsEmpty();
|
||||
testMixedTallies();
|
||||
|
||||
testEmptyPathsHandsOffNothingAndKeepsInternalDrag();
|
||||
testResolvablePayloadHandsOff();
|
||||
testNeverReleasesWithoutStarting();
|
||||
testAllSkippedSelectionKeepsInternalDrag();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -234,6 +234,72 @@ static void testEmbedStripIsNotHotspot() {
|
||||
CHECK(!infoNamesFxHotspot("mcp.fxembed extra"));
|
||||
}
|
||||
|
||||
// --- Drop surface parity: container vs FX button ------------------------------
|
||||
//
|
||||
// The container-drop regression (instance loads, capture does not): the two surfaces must be
|
||||
// one code path carrying one payload. The payload is a function of the capture alone, so the
|
||||
// surface cannot influence it — and both surfaces must classify as hotspots so both reach it.
|
||||
|
||||
// Every surface a drop can land on admits the SAME capture and yields a payload that decodes
|
||||
// back to it. The payload takes the capture and nothing else, so the surface cannot influence
|
||||
// it — walking the surfaces here is what pins that the container family is not second-class.
|
||||
static void testEveryDropSurfaceCarriesTheCapture() {
|
||||
const char* surfaces[] = {
|
||||
"fx_chain", // FX chain window / container list — the reported failing surface
|
||||
"fx_0", // floating FX window
|
||||
"tcp.fx", // TCP FX button — the reference path
|
||||
"mcp.fx", // MCP FX button
|
||||
};
|
||||
const std::string sampleId = "cap-7f3a";
|
||||
for (const char* info : surfaces) {
|
||||
CHECK(infoNamesFxHotspot(info));
|
||||
const ParsedPreset p = parsePreset(buildInstrumentDropPreset(sampleId));
|
||||
CHECK(p.ok);
|
||||
CHECK(p.classId == vstClassIdHex());
|
||||
const ComponentState cs = deserializeComponentState(p.compChunk, kRate);
|
||||
CHECK(cs.selectionId == sampleId);
|
||||
}
|
||||
}
|
||||
|
||||
// --- All-or-nothing rollback --------------------------------------------------
|
||||
|
||||
// The add failed: nothing was created, so there is nothing to delete and nothing loaded.
|
||||
static void testAddFailureLeavesNothingToRollBack() {
|
||||
const DropOutcome out = decideDropOutcome(DropAttempt{-1, false});
|
||||
CHECK(!out.loaded);
|
||||
CHECK(out.rollbackFxIndex < 0);
|
||||
}
|
||||
|
||||
// The instance was created but the preset did not apply: the caller MUST delete that exact
|
||||
// index — an instance without its capture is the orphan the contract forbids.
|
||||
static void testPresetFailureRollsBackTheCreatedIndex() {
|
||||
const DropOutcome zero = decideDropOutcome(DropAttempt{0, false});
|
||||
CHECK(!zero.loaded);
|
||||
CHECK(zero.rollbackFxIndex == 0);
|
||||
const DropOutcome later = decideDropOutcome(DropAttempt{4, false});
|
||||
CHECK(!later.loaded);
|
||||
CHECK(later.rollbackFxIndex == 4);
|
||||
// Container-addressed indices (0x2000000-flagged) roll back by the same rule — the
|
||||
// obligation follows the index REAPER handed back, whatever space it names.
|
||||
const DropOutcome inContainer = decideDropOutcome(DropAttempt{0x2000000 + 5, false});
|
||||
CHECK(!inContainer.loaded);
|
||||
CHECK(inContainer.rollbackFxIndex == 0x2000000 + 5);
|
||||
}
|
||||
|
||||
// Both halves succeeded: loaded, and nothing to undo.
|
||||
static void testSuccessKeepsTheInstance() {
|
||||
const DropOutcome out = decideDropOutcome(DropAttempt{2, true});
|
||||
CHECK(out.loaded);
|
||||
CHECK(out.rollbackFxIndex < 0);
|
||||
}
|
||||
|
||||
// A "preset applied" report with no instance behind it can never read as loaded.
|
||||
static void testNoInstanceIsNeverLoaded() {
|
||||
const DropOutcome out = decideDropOutcome(DropAttempt{-1, true});
|
||||
CHECK(!out.loaded);
|
||||
CHECK(out.rollbackFxIndex < 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testClassIdHexPinnedPerChannel();
|
||||
testPresetRoundTripsThroughInstrumentReader();
|
||||
@@ -248,6 +314,12 @@ int main() {
|
||||
testNonFxSurfacesAreNotHotspot();
|
||||
testEmbedStripIsNotHotspot();
|
||||
|
||||
testEveryDropSurfaceCarriesTheCapture();
|
||||
testAddFailureLeavesNothingToRollBack();
|
||||
testPresetFailureRollsBackTheCreatedIndex();
|
||||
testSuccessKeepsTheInstance();
|
||||
testNoInstanceIsNeverLoaded();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user