// Standalone tests for reasampler::view_model — no REAPER, no test framework. // Mirror of test_bank_model: iterate the hard logic outside the DAW. // // Covers (PLAN.md D1 test cases): // 1. N-mode proven — >=3 modes, membership + derivation still correct. // 2. Parent derivation — a folder with descendant leaves in different modes is // visible in each of those modes. // 3. Restore round-trip — snapshot -> park -> restore returns every driven flag to // its captured value; includes the "flag already at 0 stays 0" (no default). // 4. show-both leaf never appears in a park op-list and is visible in all modes. // 5. Unknown/stale GUID tolerated (ignore-and-prune, no crash). // 6. JSON round-trip lossless: modes + membership + show-both + snapshots + active. #include "../src/view_model.h" #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) // -- helpers ----------------------------------------------------------------- static bool visibleHas(const std::set& v, const std::string& g) { return v.count(g) > 0; } // Does any park TrackPlan in the plan target `guid`? static bool parkTargets(const TogglePlan& plan, const std::string& guid) { for (const auto& p : plan.park) for (const auto& f : p.flags) if (f.guid == guid) return true; return false; } // Find the single restore plan for `guid`, or nullptr. static const TrackPlan* restoreFor(const TogglePlan& plan, const std::string& guid) { for (const auto& p : plan.restore) if (!p.flags.empty() && p.flags.front().guid == guid) return &p; return nullptr; } static int flagValue(const TrackPlan& p, Flag f) { for (const auto& op : p.flags) if (op.flag == f) return op.value; return -999; // sentinel: flag absent } // -- 1. N-mode proven -------------------------------------------------------- static void testNModeRegistryAndMembership() { ViewModel vm; // Seeded: Arrange + Design. CHECK(vm.modes().size() == 2); CHECK(vm.modes().contains(kArrangeModeId)); CHECK(vm.modes().contains(kDesignModeId)); CHECK(vm.activeModeId() == kArrangeModeId); // Add a third mode — proves N-mode, not boolean. CHECK(vm.modes().add(Mode{"mixdown", "Mixdown", 2})); CHECK(vm.modes().size() == 3); CHECK(vm.modes().contains("mixdown")); // Duplicate id and empty id are rejected without mutation. CHECK(!vm.modes().add(Mode{"mixdown", "Dup", 5})); CHECK(!vm.modes().add(Mode{"", "Empty", 6})); CHECK(vm.modes().size() == 3); // Membership across three modes. CHECK(vm.membership().tag("{A}", kArrangeModeId)); CHECK(vm.membership().tag("{D}", kDesignModeId)); CHECK(vm.membership().tag("{M}", "mixdown")); // Leaf-belongs rule holds in each mode. CHECK(vm.leafBelongsToMode("{D}", kDesignModeId)); CHECK(!vm.leafBelongsToMode("{D}", kArrangeModeId)); CHECK(vm.leafBelongsToMode("{M}", "mixdown")); CHECK(!vm.leafBelongsToMode("{M}", kDesignModeId)); // Untagged leaf defaults to Arrange, and only Arrange. CHECK(vm.leafBelongsToMode("{UNTAGGED}", kArrangeModeId)); CHECK(!vm.leafBelongsToMode("{UNTAGGED}", kDesignModeId)); // Retag moves the leaf (single-mode semantics). CHECK(vm.membership().tag("{D}", "mixdown")); CHECK(vm.leafBelongsToMode("{D}", "mixdown")); CHECK(!vm.leafBelongsToMode("{D}", kDesignModeId)); // Untag returns to the Arrange default. CHECK(vm.membership().untag("{D}")); CHECK(vm.leafBelongsToMode("{D}", kArrangeModeId)); CHECK(!vm.membership().untag("{D}")); // second untag is a no-op // setActiveMode rejects an unregistered id, accepts a registered one. CHECK(!vm.setActiveMode("nope")); CHECK(vm.activeModeId() == kArrangeModeId); CHECK(vm.setActiveMode("mixdown")); CHECK(vm.activeModeId() == "mixdown"); } // -- 2. Parent derivation ---------------------------------------------------- static void testParentDerivationMultiMode() { ViewModel vm; // Folder {F} holds two leaves: {L1} in Arrange (untagged default), {L2} in Design. FolderTree tree; tree.nodes.push_back(FolderNode{"{F}", "", /*isParent=*/true}); tree.nodes.push_back(FolderNode{"{L1}", "{F}", false}); tree.nodes.push_back(FolderNode{"{L2}", "{F}", false}); vm.membership().tag("{L2}", kDesignModeId); // {L1} stays untagged ⇒ Arrange. auto arrange = vm.visibleTracks(tree, kArrangeModeId); auto design = vm.visibleTracks(tree, kDesignModeId); // The parent is visible in BOTH modes because it has a descendant in each. CHECK(visibleHas(arrange, "{F}")); CHECK(visibleHas(design, "{F}")); // Leaves appear only in their own mode. CHECK(visibleHas(arrange, "{L1}") && !visibleHas(arrange, "{L2}")); CHECK(visibleHas(design, "{L2}") && !visibleHas(design, "{L1}")); // Nested folder chain: grandparent {G} > parent {F2} > leaf {L3} (Design). // The whole chain up to the root must be visible in Design. FolderTree nested; nested.nodes.push_back(FolderNode{"{G}", "", true}); nested.nodes.push_back(FolderNode{"{F2}", "{G}", true}); nested.nodes.push_back(FolderNode{"{L3}", "{F2}", false}); ViewModel vm2; vm2.membership().tag("{L3}", kDesignModeId); auto d2 = vm2.visibleTracks(nested, kDesignModeId); CHECK(visibleHas(d2, "{L3}")); CHECK(visibleHas(d2, "{F2}")); CHECK(visibleHas(d2, "{G}")); // In Arrange, none of the chain is visible (no Arrange leaf under it). auto a2 = vm2.visibleTracks(nested, kArrangeModeId); CHECK(!visibleHas(a2, "{L3}") && !visibleHas(a2, "{F2}") && !visibleHas(a2, "{G}")); // A parent is NEVER parked, in either mode. auto planD = vm.planToggle(tree, kDesignModeId); auto planA = vm.planToggle(tree, kArrangeModeId); CHECK(!parkTargets(planD, "{F}")); CHECK(!parkTargets(planA, "{F}")); } // -- 3. Restore round-trip (the trust anchor) -------------------------------- static void testRestoreRoundTripSnapshotValues() { // Direct planner check: park is fixed zeros; restore is snapshot verbatim. TrackSnapshot snap; snap.showInTcp = 1; snap.showInMixer = 1; snap.mainSend = 0; // user had it OUT of the mix for their own reason snap.fxEnable = 1; snap.fxOffline = {0, 1, 0}; // slot 1 was already offline before parking TrackPlan park = makeParkPlan("{T}", /*fxCount=*/3); CHECK(flagValue(park, Flag::ShowInTcp) == 0); CHECK(flagValue(park, Flag::ShowInMixer) == 0); CHECK(flagValue(park, Flag::MainSend) == 0); CHECK(flagValue(park, Flag::FxEnable) == 0); CHECK(park.fxOffline.size() == 3); for (const auto& op : park.fxOffline) CHECK(op.offline == true); TrackPlan restore = makeRestorePlan("{T}", snap); // Every flag returns to its CAPTURED value — not a hardcoded "on". CHECK(flagValue(restore, Flag::ShowInTcp) == 1); CHECK(flagValue(restore, Flag::ShowInMixer) == 1); CHECK(flagValue(restore, Flag::MainSend) == 0); // the "already at 0 stays 0" case CHECK(flagValue(restore, Flag::FxEnable) == 1); CHECK(restore.fxOffline.size() == 3); CHECK(restore.fxOffline[0].offline == false); CHECK(restore.fxOffline[1].offline == true); // was offline pre-park ⇒ stays offline CHECK(restore.fxOffline[2].offline == false); // A snapshot entirely at 0 must restore entirely to 0 (no default leaks in). TrackSnapshot zero; // all zeros, empty fxOffline TrackPlan rz = makeRestorePlan("{Z}", zero); CHECK(flagValue(rz, Flag::ShowInTcp) == 0); CHECK(flagValue(rz, Flag::ShowInMixer) == 0); CHECK(flagValue(rz, Flag::MainSend) == 0); CHECK(flagValue(rz, Flag::FxEnable) == 0); CHECK(rz.fxOffline.empty()); // End-to-end via planToggle: a leaf tagged Design, snapshotted, parked while in // Arrange, then restored when we toggle back to Design. ViewModel vm; FolderTree tree; tree.nodes.push_back(FolderNode{"{DES}", "", false}); vm.membership().tag("{DES}", kDesignModeId); vm.storeSnapshot("{DES}", snap); // Toggle to Arrange: {DES} is inactive ⇒ parked. auto toArrange = vm.planToggle(tree, kArrangeModeId); CHECK(parkTargets(toArrange, "{DES}")); CHECK(restoreFor(toArrange, "{DES}") == nullptr); // not restored while inactive // Toggle to Design: {DES} is active AND has a snapshot ⇒ restored from it. auto toDesign = vm.planToggle(tree, kDesignModeId); CHECK(!parkTargets(toDesign, "{DES}")); const TrackPlan* r = restoreFor(toDesign, "{DES}"); CHECK(r != nullptr); if (r) { CHECK(flagValue(*r, Flag::MainSend) == 0); // captured 0 comes back 0 CHECK(flagValue(*r, Flag::ShowInTcp) == 1); } } // -- 4. show-both leaf -------------------------------------------------------- static void testShowBothNeverParkedVisibleEverywhere() { ViewModel vm; CHECK(vm.modes().add(Mode{"mixdown", "Mixdown", 2})); FolderTree tree; tree.nodes.push_back(FolderNode{"{SB}", "", false}); // Tag into Design, then pin show-both. vm.membership().tag("{SB}", kDesignModeId); CHECK(vm.membership().setShowBoth("{SB}", true)); CHECK(vm.membership().isShowBoth("{SB}")); // Visible in EVERY mode. CHECK(visibleHas(vm.visibleTracks(tree, kArrangeModeId), "{SB}")); CHECK(visibleHas(vm.visibleTracks(tree, kDesignModeId), "{SB}")); CHECK(visibleHas(vm.visibleTracks(tree, "mixdown"), "{SB}")); // Never parked, in any mode — even a mode it isn't tagged into. CHECK(!parkTargets(vm.planToggle(tree, kArrangeModeId), "{SB}")); CHECK(!parkTargets(vm.planToggle(tree, kDesignModeId), "{SB}")); CHECK(!parkTargets(vm.planToggle(tree, "mixdown"), "{SB}")); // Clearing show-both restores normal one-mode parking: now in Arrange it parks. CHECK(vm.membership().setShowBoth("{SB}", false)); CHECK(parkTargets(vm.planToggle(tree, kArrangeModeId), "{SB}")); CHECK(!parkTargets(vm.planToggle(tree, kDesignModeId), "{SB}")); } // -- 5. Unknown/stale GUID tolerated ----------------------------------------- static void testStaleGuidTolerated() { ViewModel vm; // Tag two leaves, but the tree only knows one — the other GUID is stale (its // track was deleted / restructured while parked). vm.membership().tag("{LIVE}", kDesignModeId); vm.membership().tag("{GHOST}", kDesignModeId); vm.storeSnapshot("{GHOST}", TrackSnapshot{}); // stale snapshot too FolderTree tree; tree.nodes.push_back(FolderNode{"{LIVE}", "", false}); // {GHOST} absent from the tree. // No crash; the stale GUID is simply ignored (prune-safe). auto plan = vm.planToggle(tree, kArrangeModeId); CHECK(parkTargets(plan, "{LIVE}")); // live leaf still planned CHECK(!parkTargets(plan, "{GHOST}")); // stale leaf never emitted // Visibility derivation also ignores the stale GUID without incident. auto vis = vm.visibleTracks(tree, kDesignModeId); CHECK(visibleHas(vis, "{LIVE}")); CHECK(!visibleHas(vis, "{GHOST}")); // An empty tree with tagged members: nothing planned, no crash. FolderTree empty; auto emptyPlan = vm.planToggle(empty, kDesignModeId); CHECK(emptyPlan.park.empty() && emptyPlan.restore.empty()); } // -- 6. JSON round-trip lossless --------------------------------------------- static void testJsonRoundTrip() { ViewModel vm; // Modes: seeded pair + a third; also an out-of-order ordinal to prove sorting // survives round-trip. CHECK(vm.modes().add(Mode{"print", "Print \"stem\"\n", 5})); CHECK(vm.modes().add(Mode{"mixdown", "Mixdown", 2})); // Membership: a plain Design leaf, a show-both leaf, an Arrange leaf, and a // leaf carrying multiple modes (representable via restore; exercises the set). vm.membership().tag("{A}", kArrangeModeId); vm.membership().tag("{D}", kDesignModeId); vm.membership().tag("{SB}", "mixdown"); vm.membership().setShowBoth("{SB}", true); Membership multi; multi.modeIds = {kDesignModeId, "mixdown"}; multi.showBoth = false; CHECK(vm.membership().restore("{MULTI}", multi)); // Snapshots: one full, one with a per-FX vector, including the tricky 0-values. TrackSnapshot s1; s1.showInTcp = 1; s1.showInMixer = 0; s1.mainSend = 1; s1.fxEnable = 0; s1.fxOffline = {1, 0, 1, 1}; vm.storeSnapshot("{D}", s1); TrackSnapshot s2; // all zeros, empty fx vector vm.storeSnapshot("{A}", s2); // Active mode set to a non-default. CHECK(vm.setActiveMode("mixdown")); std::string json = vm.serialize(); auto back = ViewModel::deserialize(json); CHECK(back.has_value()); CHECK(back && *back == vm); // String form is stable across a second round-trip. if (back) CHECK(back->serialize() == json); // Spot-check the load-bearing bits survived. if (back) { CHECK(back->activeModeId() == "mixdown"); CHECK(back->modes().size() == 4); const Mode* print = back->modes().query("print"); CHECK(print && print->displayName == "Print \"stem\"\n" && print->ordinal == 5); CHECK(back->membership().isShowBoth("{SB}")); const Membership* mm = back->membership().query("{MULTI}"); CHECK(mm && mm->modeIds.size() == 2 && mm->modeIds.count("mixdown")); const TrackSnapshot* snap = back->snapshot("{D}"); CHECK(snap && snap->mainSend == 1 && snap->fxEnable == 0); CHECK(snap && snap->fxOffline.size() == 4 && snap->fxOffline[1] == 0); } } static void testEmptyModelRoundTrip() { ViewModel vm; // default: Arrange + Design seeded, active = Arrange, no members std::string json = vm.serialize(); auto back = ViewModel::deserialize(json); CHECK(back.has_value()); CHECK(back && *back == vm); // Lenient empty root ⇒ a default-seeded model. auto empty = ViewModel::deserialize("{}"); CHECK(empty.has_value()); CHECK(empty && empty->modes().size() == 2); CHECK(empty && empty->activeModeId() == kArrangeModeId); CHECK(empty && empty->membership().empty()); } static void testMalformedJson() { const char* bad[] = { "", "{", "not json", "{\"modes\":[", "{\"modes\":[{\"id\":\"x\"", // truncated mode "{\"activeMode\":\"ghost\"}", // active mode not registered "{\"modes\":[{\"id\":\"a\",\"ordinal\":0},{\"id\":\"a\",\"ordinal\":1}]}", // dup id "{\"membership\":[{\"guid\":\"\"}]}", // empty guid "{\"snapshots\":[{\"showInTcp\":1}]}", // snapshot without guid "{\"snapshots\":[{\"guid\":\"x\",\"fxOffline\":[1,notanumber]}]}", "{\"modes\":[]}trailing", // trailing garbage }; for (const char* j : bad) { auto r = ViewModel::deserialize(j); CHECK(!r.has_value()); } } int main() { testNModeRegistryAndMembership(); testParentDerivationMultiMode(); testRestoreRoundTripSnapshotValues(); testShowBothNeverParkedVisibleEverywhere(); testStaleGuidTolerated(); testJsonRoundTrip(); testEmptyModelRoundTrip(); testMalformedJson(); if (g_fail == 0) std::printf("All tests passed.\n"); return g_fail ? 1 : 0; }