// Standalone tests for the Design View repair pass's pure decision — no REAPER, no // test framework. // // The two properties the pass exists for: it drives a flag ONLY where a park record // names the track (so a track the tool never parked can never have its main send // re-enabled under it), and it fires on applied-output drift rather than on "an undo // happened". Both AppliedStates are produced by the real fingerprint // (view::appliedState) over real models, so a change to what the fingerprint carries // shows up here rather than being papered over by hand-built fixtures. #include "../src/shell/view/view_reconcile.h" #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) namespace { TrackSnapshot captured(int showInTcp, int showInMixer, int mainSend, int fxEnable) { TrackSnapshot snap; snap.showInTcp = showInTcp; snap.showInMixer = showInMixer; snap.mainSend = mainSend; snap.fxEnable = fxEnable; return snap; } bool hasOp(const std::vector& ops, const std::string& guid, Flag flag, int value) { for (const TrackFlagOp& op : ops) if (op.guid == guid && op.flag == flag && op.value == value) return true; return false; } bool namesTrack(const std::vector& ops, const std::string& guid) { for (const TrackFlagOp& op : ops) if (op.guid == guid) return true; return false; } } // namespace // -- the trigger: applied-output drift, not "an undo happened" ----------------- static void testAnUndoThatLeftOurAppliedOutputAloneRepairsNothing() { // The rejected trigger is "did an undo occur" — an undo that touched nothing we // drive still answers yes, and repairing on it rewrites state the user owns. ViewModeModel model; model.setActiveMode(kDesignModeId); model.storeSnapshot("{A}", captured(1, 1, 1, 1)); const view::AppliedState state = view::appliedState(model); CHECK(planReconcile(state, state).empty()); } static void testADriftedActiveModeAloneIsNotAReasonToDriveATrack() { // Drift is the gate, not the instruction: the record still has an owner, so the // reapply that follows this pass is what drives it. ViewModeModel applied; applied.setActiveMode(kDesignModeId); applied.storeSnapshot("{A}", captured(1, 1, 1, 1)); ViewModeModel reloaded; // active mode Arrange — the fingerprint differs reloaded.storeSnapshot("{A}", captured(1, 1, 1, 1)); CHECK(view::appliedState(applied) != view::appliedState(reloaded)); CHECK(planReconcile(view::appliedState(applied), view::appliedState(reloaded)).empty()); } // -- the scope rule: a record names the track, or nothing is written ----------- static void testARecordTheReloadedModelLostIsHandedBackItsCapturedValues() { // The undo case: REAPER rolled view_state back to before the switch, so the model // no longer records the park — but the flags are still driven on the track, and // this memo is the only surviving account of what they were. ViewModeModel applied; applied.setActiveMode(kDesignModeId); applied.storeSnapshot("{A}", captured(1, 1, 1, 1)); const ViewModeModel reloaded; // Arrange, no records at all const std::vector ops = planReconcile(view::appliedState(applied), view::appliedState(reloaded)); CHECK(ops.size() == 4); CHECK(hasOp(ops, "{A}", Flag::ShowInTcp, 1)); CHECK(hasOp(ops, "{A}", Flag::ShowInMixer, 1)); CHECK(hasOp(ops, "{A}", Flag::MainSend, 1)); CHECK(hasOp(ops, "{A}", Flag::FxEnable, 1)); } static void testARepairedFlagReturnsToItsCapturedValueNeverToOn() { // A track the user had OUT of the master mix and FX-bypassed before we parked it: // the repair must hand back exactly that, not a hardcoded "on". ViewModeModel applied; applied.setActiveMode(kDesignModeId); applied.storeSnapshot("{A}", captured(1, 0, 0, 0)); const ViewModeModel reloaded; const std::vector ops = planReconcile(view::appliedState(applied), view::appliedState(reloaded)); CHECK(hasOp(ops, "{A}", Flag::MainSend, 0)); CHECK(hasOp(ops, "{A}", Flag::ShowInMixer, 0)); CHECK(hasOp(ops, "{A}", Flag::FxEnable, 0)); CHECK(!hasOp(ops, "{A}", Flag::MainSend, 1)); } static void testATrackNeitherStateRecordsIsNeverDrivenAtAll() { // The regression this rule is built against: a track the model merely calls // active is NOT a track the tool parked. It holds no captured value, so asserting // B_MAINSEND=1 on it would silently re-enable a send the user disabled by hand. ViewModeModel applied; applied.setActiveMode(kDesignModeId); applied.storeSnapshot("{A}", captured(1, 1, 1, 1)); ViewModeModel reloaded; reloaded.membership().tag("{B}", kArrangeModeId); // active, never parked, no record const std::vector ops = planReconcile(view::appliedState(applied), view::appliedState(reloaded)); CHECK(namesTrack(ops, "{A}")); CHECK(!namesTrack(ops, "{B}")); } static void testASpentFlagHalfIsNotARecordToRepairFrom() { // A restore already handed the flags back and zeroed them on the record; the FX // half outlives that. Repairing from the spent half would drive those zeros — // i.e. re-park the track — so the fingerprint must not carry it. ViewModeModel applied; applied.setActiveMode(kDesignModeId); applied.storeSnapshot("{A}", captured(1, 1, 1, 1)); applied.releaseSnapshotFlags("{A}"); const ViewModeModel reloaded; CHECK(planReconcile(view::appliedState(applied), view::appliedState(reloaded)).empty()); } // -- the per-project memo: a tab switch must not erase another project's account -- static void testASecondProjectNotedInBetweenDoesNotEraseTheFirstProjectsMemo() { // The bug this replaced: a single global slot meant noting project B's applied // state overwrote project A's entirely, so a repair for A run afterward found no // memo to compare against and silently skipped -- exactly what a tab-switch then // an undo on the ORIGINAL project produces within one timer period. ReaProject* const projA = reinterpret_cast(1); ReaProject* const projB = reinterpret_cast(2); ViewModeModel appliedA; appliedA.setActiveMode(kDesignModeId); appliedA.storeSnapshot("{A}", captured(1, 1, 1, 1)); const ViewModeModel appliedB; // untouched — B has nothing driven std::map memo; memo[projA] = view::appliedState(appliedA); memo[projB] = view::appliedState(appliedB); // noted in between; must not clobber A const ViewModeModel reloadedA; // A's park record dropped, as an undo would leave it const ProjectReconcileResult result = planReconcilePerProject(memo, projA, view::appliedState(reloadedA)); CHECK(result.ops.size() == 4); CHECK(hasOp(result.ops, "{A}", Flag::MainSend, 1)); } static void testAProjectWithNoPriorMemoGetsNoRepair() { // "A project we hold no memo for gets no repair" -- the tool has no account of // driving anything there, so it has no opinion (see the header's SCOPE note). ReaProject* const projC = reinterpret_cast(3); const std::map memo; // never noted const ViewModeModel reloaded; const ProjectReconcileResult result = planReconcilePerProject(memo, projC, view::appliedState(reloaded)); CHECK(result.ops.empty()); } int main() { testAnUndoThatLeftOurAppliedOutputAloneRepairsNothing(); testADriftedActiveModeAloneIsNotAReasonToDriveATrack(); testARecordTheReloadedModelLostIsHandedBackItsCapturedValues(); testARepairedFlagReturnsToItsCapturedValueNeverToOn(); testATrackNeitherStateRecordsIsNeverDrivenAtAll(); testASpentFlagHalfIsNotARecordToRepairFrom(); testASecondProjectNotedInBetweenDoesNotEraseTheFirstProjectsMemo(); testAProjectWithNoPriorMemoGetsNoRepair(); if (g_fail == 0) std::printf("All tests passed.\n"); return g_fail ? 1 : 0; }