This commit is contained in:
Joey Yakimowich-Payne 2026-01-30 09:24:46 -07:00
commit a07f94c93d
10 changed files with 718 additions and 25 deletions

View file

@ -2,6 +2,7 @@
#include "../../gui/GraphEditorWidget.h"
#include "../../gui/PresetManager.h"
#include "../../gui/VolumeWidgets.h"
#include "../../gui/WarpGraphModel.h"
#include <QAction>
@ -963,3 +964,214 @@ TEST_CASE("splitter sizes persist in layout JSON") {
QFile::remove(path);
}
TEST_CASE("model volume state defaults to 1.0 and unmuted") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100600, "vol-default", "Audio/Sink")).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100600);
REQUIRE(qtId != 0);
auto state = model.nodeVolumeState(qtId);
REQUIRE(state.volume == Catch::Approx(1.0f));
REQUIRE_FALSE(state.mute);
}
TEST_CASE("setNodeVolumeState updates model and calls test helper") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100610, "vol-set", "Audio/Sink")).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100610);
REQUIRE(qtId != 0);
WarpGraphModel::NodeVolumeState ns;
ns.volume = 0.5f;
ns.mute = true;
model.setNodeVolumeState(qtId, ns);
auto state = model.nodeVolumeState(qtId);
REQUIRE(state.volume == Catch::Approx(0.5f));
REQUIRE(state.mute);
auto apiState = tc.client->Test_GetNodeVolume(warppipe::NodeId{100610});
REQUIRE(apiState.ok());
REQUIRE(apiState.value.volume == Catch::Approx(0.5f));
REQUIRE(apiState.value.mute);
}
TEST_CASE("nodeVolumeChanged signal emitted on state change") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100620, "vol-signal", "Audio/Sink")).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100620);
REQUIRE(qtId != 0);
bool signalFired = false;
QObject::connect(&model, &WarpGraphModel::nodeVolumeChanged,
[&](QtNodes::NodeId id, WarpGraphModel::NodeVolumeState prev,
WarpGraphModel::NodeVolumeState cur) {
if (id == qtId) {
signalFired = true;
REQUIRE(prev.volume == Catch::Approx(1.0f));
REQUIRE(cur.volume == Catch::Approx(0.3f));
REQUIRE(cur.mute);
}
});
WarpGraphModel::NodeVolumeState ns;
ns.volume = 0.3f;
ns.mute = true;
model.setNodeVolumeState(qtId, ns);
REQUIRE(signalFired);
}
TEST_CASE("volume widget created for new nodes") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100630, "vol-widget", "Audio/Sink")).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100630);
REQUIRE(qtId != 0);
auto widget = model.nodeData(qtId, QtNodes::NodeRole::Widget);
REQUIRE(widget.isValid());
auto *w = widget.value<QWidget *>();
REQUIRE(w != nullptr);
auto *vol = qobject_cast<NodeVolumeWidget *>(w);
REQUIRE(vol != nullptr);
REQUIRE(vol->volume() == 100);
REQUIRE_FALSE(vol->isMuted());
}
TEST_CASE("setNodeVolumeState syncs inline widget") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100640, "vol-sync", "Audio/Sink")).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100640);
auto *w = model.nodeData(qtId, QtNodes::NodeRole::Widget).value<QWidget *>();
auto *vol = qobject_cast<NodeVolumeWidget *>(w);
REQUIRE(vol != nullptr);
WarpGraphModel::NodeVolumeState ns;
ns.volume = 0.7f;
ns.mute = true;
model.setNodeVolumeState(qtId, ns);
REQUIRE(vol->volume() == 70);
REQUIRE(vol->isMuted());
}
TEST_CASE("preset saves and loads volume state") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100650, "vol-preset", "Audio/Sink", {}, {}, true)).ok());
REQUIRE(tc.client->Test_InsertPort(
MakePort(100651, 100650, "FL", true)).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100650);
WarpGraphModel::NodeVolumeState ns;
ns.volume = 0.6f;
ns.mute = true;
model.setNodeVolumeState(qtId, ns);
QString path = QStandardPaths::writableLocation(
QStandardPaths::TempLocation) +
"/warppipe_test_vol_preset.json";
REQUIRE(PresetManager::savePreset(path, tc.client.get(), &model));
QFile file(path);
REQUIRE(file.open(QIODevice::ReadOnly));
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
file.close();
QJsonObject root = doc.object();
REQUIRE(root.contains("volumes"));
QJsonArray volArr = root["volumes"].toArray();
bool found = false;
for (const auto &val : volArr) {
QJsonObject obj = val.toObject();
if (obj["name"].toString() == "vol-preset") {
found = true;
REQUIRE(obj["volume"].toDouble() == Catch::Approx(0.6));
REQUIRE(obj["mute"].toBool());
}
}
REQUIRE(found);
WarpGraphModel model2(tc.client.get());
model2.refreshFromClient();
auto qtId2 = model2.qtNodeIdForPw(100650);
auto stateBefore = model2.nodeVolumeState(qtId2);
REQUIRE(stateBefore.volume == Catch::Approx(1.0f));
REQUIRE(PresetManager::loadPreset(path, tc.client.get(), &model2));
auto stateAfter = model2.nodeVolumeState(qtId2);
REQUIRE(stateAfter.volume == Catch::Approx(0.6f));
REQUIRE(stateAfter.mute);
QFile::remove(path);
}
TEST_CASE("volume state cleaned up on node deletion") {
auto tc = TestClient::Create();
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
ensureApp();
REQUIRE(tc.client->Test_InsertNode(
MakeNode(100660, "vol-del", "Audio/Sink")).ok());
WarpGraphModel model(tc.client.get());
model.refreshFromClient();
auto qtId = model.qtNodeIdForPw(100660);
WarpGraphModel::NodeVolumeState ns;
ns.volume = 0.4f;
model.setNodeVolumeState(qtId, ns);
REQUIRE(tc.client->Test_RemoveGlobal(100660).ok());
model.refreshFromClient();
REQUIRE_FALSE(model.nodeExists(qtId));
auto state = model.nodeVolumeState(qtId);
REQUIRE(state.volume == Catch::Approx(1.0f));
REQUIRE_FALSE(state.mute);
}