feat(view): D2 Wave 2 — apply managed-lane ops + timer-diff auto-tag new content
Drives fixed-lane C_LANEPLAYS for managed lanes only (name-keyed, ordinal-renumber safe) in the view shell; diffs live track/item GUIDs on the panel timer to auto-tag new content to the active mode, first-poll-guarded. Pure guid_diff + lane_keys modules unit-tested; folds in Wave-1 polish.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// Standalone tests for reasampler::newGuids + GuidBaseline — no REAPER, no test
|
||||
// framework. Mirror of test_view_mode_model: iterate the hard logic outside the DAW.
|
||||
//
|
||||
// Covers (D2 Wave-2 new-content detection):
|
||||
// 1. newGuids: current \ previous, empty-GUID filtering, determinism.
|
||||
// 2. GuidBaseline first-poll guard: the first observe() after open reports NOTHING
|
||||
// new (pre-existing content stays Arrange) and establishes the baseline.
|
||||
// 3. Incremental detection: only GUIDs added since the prior observe() are returned.
|
||||
// 4. Deletion drops from the baseline so a reused GUID is re-detected.
|
||||
// 5. reset() (project switch) re-arms the first-poll guard: the next observe()
|
||||
// re-baselines and reports nothing new — never diffs across projects.
|
||||
|
||||
#include "../src/guid_diff.h"
|
||||
|
||||
#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)
|
||||
|
||||
static bool has(const std::vector<std::string>& v, const std::string& g) {
|
||||
for (const auto& e : v) if (e == g) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- 1. newGuids set difference ----------------------------------------------
|
||||
|
||||
static void testNewGuidsDifference() {
|
||||
std::set<std::string> prev{"{A}", "{B}"};
|
||||
std::set<std::string> cur{"{A}", "{B}", "{C}", "{D}"};
|
||||
|
||||
auto added = newGuids(prev, cur);
|
||||
CHECK(added.size() == 2);
|
||||
CHECK(has(added, "{C}"));
|
||||
CHECK(has(added, "{D}"));
|
||||
CHECK(!has(added, "{A}")); // pre-existing, not new
|
||||
CHECK(!has(added, "{B}"));
|
||||
|
||||
// No change ⇒ nothing new.
|
||||
CHECK(newGuids(cur, cur).empty());
|
||||
|
||||
// A removed GUID is not "new" (it is absent from current).
|
||||
std::set<std::string> shrunk{"{A}"};
|
||||
CHECK(newGuids(prev, shrunk).empty());
|
||||
|
||||
// Determinism: ascending set order.
|
||||
std::set<std::string> p2;
|
||||
std::set<std::string> c2{"{Z}", "{A}", "{M}"};
|
||||
auto ordered = newGuids(p2, c2);
|
||||
CHECK(ordered.size() == 3);
|
||||
CHECK(ordered[0] == "{A}" && ordered[1] == "{M}" && ordered[2] == "{Z}");
|
||||
}
|
||||
|
||||
static void testNewGuidsIgnoresEmpty() {
|
||||
std::set<std::string> prev{"{A}"};
|
||||
std::set<std::string> cur{"", "{A}", "{B}"}; // empty ⇒ a GUID-read failure
|
||||
auto added = newGuids(prev, cur);
|
||||
CHECK(added.size() == 1);
|
||||
CHECK(has(added, "{B}"));
|
||||
CHECK(!has(added, "")); // never tag an empty GUID
|
||||
}
|
||||
|
||||
// -- 2. First-poll guard -----------------------------------------------------
|
||||
|
||||
static void testBaselineFirstPollReportsNothing() {
|
||||
GuidBaseline b;
|
||||
CHECK(!b.primed());
|
||||
// First observe after open: pre-existing content must NOT be tagged.
|
||||
auto first = b.observe({"{A}", "{B}", "{C}"});
|
||||
CHECK(first.empty()); // nothing new at open
|
||||
CHECK(b.primed());
|
||||
}
|
||||
|
||||
// -- 3. Incremental detection ------------------------------------------------
|
||||
|
||||
static void testBaselineIncremental() {
|
||||
GuidBaseline b;
|
||||
b.observe({"{A}", "{B}"}); // baseline
|
||||
auto t1 = b.observe({"{A}", "{B}", "{C}"});
|
||||
CHECK(t1.size() == 1 && has(t1, "{C}")); // only the newly-added GUID
|
||||
|
||||
// Next tick with a further addition — earlier-added {C} is now baseline.
|
||||
auto t2 = b.observe({"{A}", "{B}", "{C}", "{D}"});
|
||||
CHECK(t2.size() == 1 && has(t2, "{D}"));
|
||||
CHECK(!has(t2, "{C}"));
|
||||
|
||||
// A steady state reports nothing new.
|
||||
CHECK(b.observe({"{A}", "{B}", "{C}", "{D}"}).empty());
|
||||
}
|
||||
|
||||
// -- 4. Deletion drops from baseline; reused GUID re-detected ----------------
|
||||
|
||||
static void testBaselineDeletionReDetect() {
|
||||
GuidBaseline b;
|
||||
b.observe({"{A}", "{B}"});
|
||||
// Delete {B}: not "new", and drops out of the baseline.
|
||||
CHECK(b.observe({"{A}"}).empty());
|
||||
// {B} reappears (REAPER reused the GUID or the user re-added) ⇒ detected again.
|
||||
auto again = b.observe({"{A}", "{B}"});
|
||||
CHECK(again.size() == 1 && has(again, "{B}"));
|
||||
}
|
||||
|
||||
// -- 5. reset() re-arms the first-poll guard (project switch) -----------------
|
||||
|
||||
static void testResetReBaselines() {
|
||||
GuidBaseline b;
|
||||
b.observe({"{A}"}); // project 1 baseline
|
||||
b.observe({"{A}", "{B}"}); // {B} detected in project 1
|
||||
|
||||
b.reset();
|
||||
CHECK(!b.primed());
|
||||
// Switching to project 2: its pre-existing content must NOT be mass-tagged even
|
||||
// though those GUIDs were never seen before reset.
|
||||
auto afterSwitch = b.observe({"{X}", "{Y}", "{Z}"});
|
||||
CHECK(afterSwitch.empty()); // re-baselined, nothing new
|
||||
CHECK(b.primed());
|
||||
// Content created in project 2 after the switch IS detected.
|
||||
auto p2new = b.observe({"{X}", "{Y}", "{Z}", "{W}"});
|
||||
CHECK(p2new.size() == 1 && has(p2new, "{W}"));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testNewGuidsDifference();
|
||||
testNewGuidsIgnoresEmpty();
|
||||
testBaselineFirstPollReportsNothing();
|
||||
testBaselineIncremental();
|
||||
testBaselineDeletionReDetect();
|
||||
testResetReBaselines();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user