Files
reasampler/tests/test_view_park.cpp
T

150 lines
6.4 KiB
C++

// Standalone tests for the Design View park surface's pure decisions — no REAPER, no
// test framework.
//
// The properties under test: the four flags a park drives address exactly the four
// REAPER parameters the tool owns (never mute, never solo), and one mode apply leaves
// at most ONE undo point — none at all when a reapply found everything already where
// it wanted it.
#include "../src/shell/view/view_park.h"
#include "../src/shell/view/view.h" // applyMintsUndoPoint — the apply's undo-point fold
#include "core/view/view_mode_model.h" // makeParkPlan — the park flags' only producer
#include <cstdio>
#include <set>
#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)
// -- what a park is allowed to address ----------------------------------------
//
// trackFlagParm is the ONE mapping the park's pre-park read, the restore's write and
// the reconcile's repair all share; a mistyped parameter there is silent at compile
// time and drives the wrong track property forever.
static void testEachDrivenFlagAddressesItsOwnReaperParameter() {
const std::set<std::string> parms{trackFlagParm(Flag::ShowInTcp),
trackFlagParm(Flag::ShowInMixer),
trackFlagParm(Flag::MainSend),
trackFlagParm(Flag::FxEnable)};
CHECK(parms.size() == 4); // no two flags collapse onto one parameter
CHECK(parms.count("B_SHOWINTCP") == 1);
CHECK(parms.count("B_SHOWINMIXER") == 1);
CHECK(parms.count("B_MAINSEND") == 1);
CHECK(parms.count("I_FXEN") == 1);
}
static void testNoDrivenFlagCanEverAddressMuteOrSolo() {
// The never-touch-mute invariant, held at the one place a Flag becomes a REAPER
// parameter name.
for (const Flag f : {Flag::ShowInTcp, Flag::ShowInMixer, Flag::MainSend, Flag::FxEnable}) {
const std::string parm = trackFlagParm(f);
CHECK(parm != "B_MUTE");
CHECK(parm != "I_SOLO");
}
}
static void testAParkPlanDrivesEveryDrivenFlagToZero() {
// The four zeros are what a parked track's driven flags actually read; if
// makeParkPlan ever writes something else, this is the test that says so.
const TrackPlan park = makeParkPlan("{A}");
CHECK(park.flags.size() == 4);
std::set<std::string> parms;
for (const TrackFlagOp& op : park.flags) {
CHECK(op.guid == "{A}");
CHECK(op.value == 0);
parms.insert(trackFlagParm(op.flag));
}
CHECK(parms.size() == 4);
}
// -- the apply's one undo point ----------------------------------------------
//
// These pin applyMintsUndoPoint's fold only. The `wroteAnything` verdict it consumes
// — writeIfChanged / parkTrack / restoreTrack, all running against live REAPER calls
// — cannot be pinned here; docs/VERIFICATION.md §"Mode switching" is the sole cover
// for that plumbing being wired correctly.
static void testARealSwitchAlwaysLeavesAPointEvenWithNothingToWrite() {
// An explicitly fired action stays undoable even when the plan found nothing to
// write — the discard form is for reapplies only.
CHECK(applyMintsUndoPoint(/*realSwitch=*/true, /*wroteAnything=*/false));
CHECK(applyMintsUndoPoint(/*realSwitch=*/true, /*wroteAnything=*/true));
}
static void testAReapplyThatWroteSomethingLeavesAPoint() {
// A tag/untag reapply that actually parked a track: real project state moved.
CHECK(applyMintsUndoPoint(/*realSwitch=*/false, /*wroteAnything=*/true));
}
static void testAReapplyThatWroteNothingLeavesNoPointAtAll() {
// The project-load reapply over a project saved fully parked: every flag already
// sits where the plan wants it. Opening a project must not cost the user an undo
// step.
CHECK(!applyMintsUndoPoint(/*realSwitch=*/false, /*wroteAnything=*/false));
}
// -- the re-park union: a plugin added between a restore and the next park --------
//
// Sequence under test: park -> restore -> user adds plugin P -> re-park. Without the
// union, the record's prior verbatim-keep would drop P silently -- the reclaimer
// offlines it with everything else, and no snapshot ever names it to bring it back.
static void testAPluginAddedWhileRestoredJoinsTheUnionRatherThanBeingDropped() {
const std::vector<FxOfflineState> prior{
FxOfflineState{"{FX-A}", 1}, FxOfflineState{"{FX-B}", 0}};
const std::vector<FxOfflineState> live{
FxOfflineState{"{FX-A}", 0}, FxOfflineState{"{FX-B}", 0}, FxOfflineState{"{FX-P}", 0}};
const std::vector<FxOfflineState> merged = unionFxOfflineOnRepark(prior, live);
CHECK(merged.size() == 3);
bool foundA = false, foundB = false, foundP = false;
for (const FxOfflineState& fx : merged) {
if (fx.fxGuid == "{FX-A}") { foundA = true; CHECK(fx.offline == 1); } // record wins
if (fx.fxGuid == "{FX-B}") { foundB = true; CHECK(fx.offline == 0); } // record wins
if (fx.fxGuid == "{FX-P}") { foundP = true; CHECK(fx.offline == 0); } // added from live
}
CHECK(foundA && foundB && foundP);
}
static void testAPluginRemovedWhileRestoredStaysInTheUnionForResolveFxRestoreToDrop() {
// The union is not where a removed plugin's entry disappears -- resolveFxRestore
// owns that at restore time (missingIdentity, reported), so the merge must keep
// naming it rather than quietly forgetting it here.
const std::vector<FxOfflineState> prior{
FxOfflineState{"{FX-A}", 1}, FxOfflineState{"{FX-REMOVED}", 1}};
const std::vector<FxOfflineState> live{FxOfflineState{"{FX-A}", 0}};
const std::vector<FxOfflineState> merged = unionFxOfflineOnRepark(prior, live);
CHECK(merged.size() == 2);
bool foundRemoved = false;
for (const FxOfflineState& fx : merged)
if (fx.fxGuid == "{FX-REMOVED}") foundRemoved = true;
CHECK(foundRemoved);
}
int main() {
testEachDrivenFlagAddressesItsOwnReaperParameter();
testNoDrivenFlagCanEverAddressMuteOrSolo();
testAParkPlanDrivesEveryDrivenFlagToZero();
testARealSwitchAlwaysLeavesAPointEvenWithNothingToWrite();
testAReapplyThatWroteSomethingLeavesAPoint();
testAReapplyThatWroteNothingLeavesNoPointAtAll();
testAPluginAddedWhileRestoredJoinsTheUnionRatherThanBeingDropped();
testAPluginRemovedWhileRestoredStaysInTheUnionForResolveFxRestoreToDrop();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
}