// 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), the re-park union, the // park-shaped chain read, and the EXPOSE report's wording. #include "../src/shell/view/view_park.h" #include "core/view/view_mode_model.h" // makeParkPlan — the park flags' only producer #include #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) // -- 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 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 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 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 prior{ FxOfflineState{"{FX-A}", 1}, FxOfflineState{"{FX-B}", 0}}; const std::vector live{ FxOfflineState{"{FX-A}", 0}, FxOfflineState{"{FX-B}", 0}, FxOfflineState{"{FX-P}", 0}}; const std::vector 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 prior{ FxOfflineState{"{FX-A}", 1}, FxOfflineState{"{FX-REMOVED}", 1}}; const std::vector live{FxOfflineState{"{FX-A}", 0}}; const std::vector 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); } // -- chainReadsParked: the EXPOSE trigger, flags only ------------------------- // // The live values the fold reads, keyed by Flag -- so an assertion names the flag // it varies rather than a position in makeParkPlan's op order. struct FlagValues { int showInTcp = 0, showInMixer = 0, mainSend = 0, fxEnable = 0; int operator()(Flag f) const { switch (f) { case Flag::ShowInTcp: return showInTcp; case Flag::ShowInMixer: return showInMixer; case Flag::MainSend: return mainSend; case Flag::FxEnable: return fxEnable; } return -1; } }; static void testAChainSittingAtEveryParkedValueReadsAsParkShaped() { const TrackPlan park = makeParkPlan("{A}"); CHECK(chainReadsParked(park.flags, FlagValues{0, 0, 0, 0})); } static void testOneFlagStillAtTheUsersValueMeansNotParkShaped() { const TrackPlan park = makeParkPlan("{A}"); // Each case varies exactly one flag, so together they also prove all four are // in the plan: a missing op makes its case read parked and fail here. CHECK(!chainReadsParked(park.flags, FlagValues{1, 0, 0, 0})); CHECK(!chainReadsParked(park.flags, FlagValues{0, 1, 0, 0})); CHECK(!chainReadsParked(park.flags, FlagValues{0, 0, 1, 0})); CHECK(!chainReadsParked(park.flags, FlagValues{0, 0, 0, 1})); } static void testAnEmptyPlanProvesNothing() { CHECK(!chainReadsParked(std::vector{}, FlagValues{0, 0, 0, 0})); } static void testFxOfflineAloneIsNotFoldedIntoTheParkedRead() { // chainReadsParked takes no FX argument at all: an FX-only-offline chain // cannot make it read parked, by construction -- there is nothing here it // could read. That is the resolved design question (see view_park.h): // flags-fine/FX-offline stays on the ordinary capture path, never EXPOSE. const TrackPlan park = makeParkPlan("{A}"); CHECK(!chainReadsParked(park.flags, FlagValues{1, 1, 1, 1})); // flags all fine } // -- the EXPOSE console report ------------------------------------------------- static void testExposedTracksAreNamedAndEmptyListSaysNothing() { CHECK(describeExposedTracks({}).empty()); const std::string one = describeExposedTracks({"Bass"}); CHECK(one.find("1 track ") != std::string::npos); CHECK(one.find("Bass") != std::string::npos); const std::string two = describeExposedTracks({"Bass", "Drum bus"}); CHECK(two.find("2 tracks ") != std::string::npos); CHECK(two.find("Bass") != std::string::npos); CHECK(two.find("Drum bus") != std::string::npos); } static void testExposedReportNeverUsesRefusalWording() { // Daniel's ruling was EXPOSE, not refuse -- the wording must never suggest the // tool declined to act. const std::string msg = describeExposedTracks({"Bass"}); CHECK(msg.find("refus") == std::string::npos); } static void testShouldReportSkipsAnExactRepeatOnly() { const std::vector a{"Bass"}; const std::vector b{"Bass", "Drum bus"}; CHECK(!shouldReport(/*sameOwnerAsLast=*/true, a, a)); // exact repeat: silent CHECK(shouldReport(/*sameOwnerAsLast=*/true, a, b)); // set changed: reprint CHECK(shouldReport(/*sameOwnerAsLast=*/false, a, a)); // owner (project) changed: reprint CHECK(shouldReport(/*sameOwnerAsLast=*/true, a, {})); // healed to none: reprint (resets memo) } int main() { testEachDrivenFlagAddressesItsOwnReaperParameter(); testNoDrivenFlagCanEverAddressMuteOrSolo(); testAParkPlanDrivesEveryDrivenFlagToZero(); testAPluginAddedWhileRestoredJoinsTheUnionRatherThanBeingDropped(); testAPluginRemovedWhileRestoredStaysInTheUnionForResolveFxRestoreToDrop(); testAChainSittingAtEveryParkedValueReadsAsParkShaped(); testOneFlagStillAtTheUsersValueMeansNotParkShaped(); testAnEmptyPlanProvesNothing(); testFxOfflineAloneIsNotFoldedIntoTheParkedRead(); testExposedTracksAreNamedAndEmptyListSaysNothing(); testExposedReportNeverUsesRefusalWording(); testShouldReportSkipsAnExactRepeatOnly(); if (g_fail == 0) std::printf("All tests passed.\n"); return g_fail ? 1 : 0; }