GUI Milestone 8b
This commit is contained in:
parent
3a8450cb70
commit
4a248e5622
6 changed files with 682 additions and 37 deletions
|
|
@ -5,8 +5,11 @@
|
|||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -553,3 +556,296 @@ TEST_CASE("findPwNodeIdByName returns 0 for ghost nodes without pw mapping") {
|
|||
|
||||
REQUIRE(model.findPwNodeIdByName("ghost-lookup") == 100220);
|
||||
}
|
||||
|
||||
TEST_CASE("saveLayout stores and loadLayout restores view state") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100300, "view-state-node", "Audio/Sink")).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
|
||||
WarpGraphModel::ViewState vs;
|
||||
vs.scale = 1.5;
|
||||
vs.centerX = 123.4;
|
||||
vs.centerY = 567.8;
|
||||
vs.valid = true;
|
||||
|
||||
QString path = QStandardPaths::writableLocation(
|
||||
QStandardPaths::TempLocation) +
|
||||
"/warppipe_test_viewstate.json";
|
||||
model.saveLayout(path, vs);
|
||||
|
||||
WarpGraphModel model2(tc.client.get());
|
||||
bool loaded = model2.loadLayout(path);
|
||||
REQUIRE(loaded);
|
||||
|
||||
auto restored = model2.savedViewState();
|
||||
REQUIRE(restored.valid);
|
||||
REQUIRE(restored.scale == Catch::Approx(1.5));
|
||||
REQUIRE(restored.centerX == Catch::Approx(123.4));
|
||||
REQUIRE(restored.centerY == Catch::Approx(567.8));
|
||||
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST_CASE("saveLayout without view state omits view key") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100310, "no-view-node", "Audio/Sink")).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
|
||||
QString path = QStandardPaths::writableLocation(
|
||||
QStandardPaths::TempLocation) +
|
||||
"/warppipe_test_noview.json";
|
||||
model.saveLayout(path);
|
||||
|
||||
WarpGraphModel model2(tc.client.get());
|
||||
model2.loadLayout(path);
|
||||
|
||||
auto restored = model2.savedViewState();
|
||||
REQUIRE_FALSE(restored.valid);
|
||||
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST_CASE("ghost nodes persist in layout JSON") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100320, "ghost-persist-app", "Stream/Output/Audio", "TestApp")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100321, 100320, "output_FL", false)).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100322, 100320, "input_FL", true)).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
REQUIRE_FALSE(model.isGhost(model.qtNodeIdForPw(100320)));
|
||||
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100320).ok());
|
||||
model.refreshFromClient();
|
||||
|
||||
auto ghostQt = model.findPwNodeIdByName("ghost-persist-app");
|
||||
REQUIRE(ghostQt == 100320);
|
||||
|
||||
QString path = QStandardPaths::writableLocation(
|
||||
QStandardPaths::TempLocation) +
|
||||
"/warppipe_test_ghosts.json";
|
||||
model.saveLayout(path);
|
||||
|
||||
WarpGraphModel model2(tc.client.get());
|
||||
model2.loadLayout(path);
|
||||
|
||||
auto ids = model2.allNodeIds();
|
||||
bool foundGhost = false;
|
||||
for (auto id : ids) {
|
||||
const WarpNodeData *d = model2.warpNodeData(id);
|
||||
if (d && d->info.name == "ghost-persist-app") {
|
||||
foundGhost = true;
|
||||
REQUIRE(model2.isGhost(id));
|
||||
REQUIRE(d->info.application_name == "TestApp");
|
||||
REQUIRE(d->inputPorts.size() == 1);
|
||||
REQUIRE(d->outputPorts.size() == 1);
|
||||
REQUIRE(d->inputPorts[0].name == "input_FL");
|
||||
REQUIRE(d->outputPorts[0].name == "output_FL");
|
||||
break;
|
||||
}
|
||||
}
|
||||
REQUIRE(foundGhost);
|
||||
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST_CASE("layout version 1 files still load") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
QString path = QStandardPaths::writableLocation(
|
||||
QStandardPaths::TempLocation) +
|
||||
"/warppipe_test_v1.json";
|
||||
QFile file(path);
|
||||
REQUIRE(file.open(QIODevice::WriteOnly));
|
||||
file.write(R"({"version":1,"nodes":[{"name":"legacy-node","x":10,"y":20}]})");
|
||||
file.close();
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
REQUIRE(model.loadLayout(path));
|
||||
|
||||
auto vs = model.savedViewState();
|
||||
REQUIRE_FALSE(vs.valid);
|
||||
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST_CASE("ghost connections preserved when node becomes ghost") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100400, "gc-sink", "Audio/Sink")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100401, 100400, "in_FL", true)).ok());
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100402, "gc-app", "Stream/Output/Audio", "GCApp")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100403, 100402, "out_FL", false)).ok());
|
||||
|
||||
REQUIRE(tc.client->Test_InsertLink(
|
||||
MakeLink(100404, 100403, 100401)).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
|
||||
auto sinkQt = model.qtNodeIdForPw(100400);
|
||||
auto appQt = model.qtNodeIdForPw(100402);
|
||||
REQUIRE(model.allConnectionIds(appQt).size() == 1);
|
||||
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100402).ok());
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100404).ok());
|
||||
model.refreshFromClient();
|
||||
|
||||
REQUIRE(model.isGhost(appQt));
|
||||
REQUIRE(model.connectionExists(
|
||||
QtNodes::ConnectionId{appQt, 0, sinkQt, 0}));
|
||||
}
|
||||
|
||||
TEST_CASE("ghost connections survive save/load round-trip") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100410, "gcrt-sink", "Audio/Sink")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100411, 100410, "in_FL", true)).ok());
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100412, "gcrt-app", "Stream/Output/Audio", "GCRTApp")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100413, 100412, "out_FL", false)).ok());
|
||||
|
||||
REQUIRE(tc.client->Test_InsertLink(
|
||||
MakeLink(100414, 100413, 100411)).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100412).ok());
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100414).ok());
|
||||
model.refreshFromClient();
|
||||
|
||||
auto appQt = model.qtNodeIdForPw(100412);
|
||||
REQUIRE(appQt == 0);
|
||||
|
||||
QString path = QStandardPaths::writableLocation(
|
||||
QStandardPaths::TempLocation) +
|
||||
"/warppipe_test_ghostconns.json";
|
||||
model.saveLayout(path);
|
||||
|
||||
WarpGraphModel model2(tc.client.get());
|
||||
model2.loadLayout(path);
|
||||
model2.refreshFromClient();
|
||||
|
||||
QtNodes::NodeId sinkQt2 = 0;
|
||||
QtNodes::NodeId appQt2 = 0;
|
||||
for (auto id : model2.allNodeIds()) {
|
||||
const WarpNodeData *d = model2.warpNodeData(id);
|
||||
if (d && d->info.name == "gcrt-sink")
|
||||
sinkQt2 = id;
|
||||
if (d && d->info.name == "gcrt-app")
|
||||
appQt2 = id;
|
||||
}
|
||||
REQUIRE(sinkQt2 != 0);
|
||||
REQUIRE(appQt2 != 0);
|
||||
REQUIRE(model2.isGhost(appQt2));
|
||||
|
||||
auto conns = model2.allConnectionIds(appQt2);
|
||||
REQUIRE(conns.size() == 1);
|
||||
auto conn = *conns.begin();
|
||||
REQUIRE(conn.outNodeId == appQt2);
|
||||
REQUIRE(conn.inNodeId == sinkQt2);
|
||||
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST_CASE("ghost connections cleaned when ghost un-ghosts") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100420, "gcug-sink", "Audio/Sink")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100421, 100420, "in_FL", true)).ok());
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100422, "gcug-app", "Stream/Output/Audio", "GCUGApp")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100423, 100422, "out_FL", false)).ok());
|
||||
|
||||
REQUIRE(tc.client->Test_InsertLink(
|
||||
MakeLink(100424, 100423, 100421)).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
|
||||
auto appQt = model.qtNodeIdForPw(100422);
|
||||
auto sinkQt = model.qtNodeIdForPw(100420);
|
||||
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100422).ok());
|
||||
REQUIRE(tc.client->Test_RemoveGlobal(100424).ok());
|
||||
model.refreshFromClient();
|
||||
REQUIRE(model.isGhost(appQt));
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100425, "gcug-app", "Stream/Output/Audio", "GCUGApp")).ok());
|
||||
REQUIRE(tc.client->Test_InsertPort(
|
||||
MakePort(100426, 100425, "out_FL", false)).ok());
|
||||
model.refreshFromClient();
|
||||
|
||||
REQUIRE_FALSE(model.isGhost(appQt));
|
||||
|
||||
auto conns = model.allConnectionIds(appQt);
|
||||
bool hasOldGhostConn = false;
|
||||
for (const auto &c : conns) {
|
||||
if (c.outNodeId == appQt && c.inNodeId == sinkQt)
|
||||
hasOldGhostConn = true;
|
||||
}
|
||||
REQUIRE_FALSE(hasOldGhostConn);
|
||||
}
|
||||
|
||||
TEST_CASE("clearSavedPositions resets model positions") {
|
||||
auto tc = TestClient::Create();
|
||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||
ensureApp();
|
||||
|
||||
REQUIRE(tc.client->Test_InsertNode(
|
||||
MakeNode(100340, "clear-pos-node", "Audio/Sink")).ok());
|
||||
|
||||
WarpGraphModel model(tc.client.get());
|
||||
model.refreshFromClient();
|
||||
|
||||
auto id = model.qtNodeIdForPw(100340);
|
||||
REQUIRE(id != 0);
|
||||
auto posBefore = model.nodeData(id, QtNodes::NodeRole::Position).toPointF();
|
||||
|
||||
model.clearSavedPositions();
|
||||
model.autoArrange();
|
||||
auto posAfter = model.nodeData(id, QtNodes::NodeRole::Position).toPointF();
|
||||
|
||||
REQUIRE(posAfter != QPointF(0, 0));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue