#include "ApplicationSetup.hpp"
#include "TestGraphModel.hpp"
#include "TestDataFlowNodes.hpp"
#include <catch2/catch.hpp>
#include <QtNodes/DataFlowGraphModel>
#include <QtNodes/NodeDelegateModelRegistry>
using QtNodes::ConnectionId;
using QtNodes::DataFlowGraphModel;
using QtNodes::NodeDelegateModelRegistry;
using QtNodes::NodeId;
class LoopEnabledModel : public TestGraphModel
{
public:
bool loopsEnabled() const override { return true; }
};
class LoopDisabledModel : public TestGraphModel
{
public:
bool loopsEnabled() const override { return false; }
};
TEST_CASE("Loop detection configuration", "[loops]")
{
SECTION("Default AbstractGraphModel allows loops")
{
TestGraphModel model;
CHECK(model.loopsEnabled() == true);
}
SECTION("DataFlowGraphModel disables loops by default")
{
auto app = applicationSetup();
auto registry = std::make_shared<NodeDelegateModelRegistry>();
registry->registerModel<TestSourceNode>("Sources");
DataFlowGraphModel model(registry);
CHECK(model.loopsEnabled() == false);
}
SECTION("Custom model can enable loops")
{
LoopEnabledModel model;
CHECK(model.loopsEnabled() == true);
}
SECTION("Custom model can disable loops")
{
LoopDisabledModel model;
CHECK(model.loopsEnabled() == false);
}
}
TEST_CASE("Loop detection in DataFlowGraphModel", "[loops]")
{
auto app = applicationSetup();
auto registry = std::make_shared<NodeDelegateModelRegistry>();
registry->registerModel<TestSourceNode>("Sources");
registry->registerModel<TestDisplayNode>("Sinks");
DataFlowGraphModel model(registry);
SECTION("Direct self-loop is not possible")
{
NodeId node1 = model.addNode("TestSourceNode");
ConnectionId selfLoop{node1, 0, node1, 0};
CHECK_FALSE(model.connectionPossible(selfLoop));
}
SECTION("Simple A->B connection is allowed")
{
NodeId node1 = model.addNode("TestSourceNode");
NodeId node2 = model.addNode("TestDisplayNode");
ConnectionId conn{node1, 0, node2, 0};
CHECK(model.connectionPossible(conn));
model.addConnection(conn);
CHECK(model.connectionExists(conn));
}
SECTION("Indirect loop A->B->A is prevented")
{
NodeId node1 = model.addNode("TestDisplayNode");
NodeId node2 = model.addNode("TestDisplayNode");
ConnectionId conn1{node1, 0, node2, 0};
CHECK(model.connectionPossible(conn1));
model.addConnection(conn1);
ConnectionId conn2{node2, 0, node1, 0};
CHECK_FALSE(model.connectionPossible(conn2));
}
SECTION("Three node loop A->B->C->A is prevented")
{
NodeId node1 = model.addNode("TestDisplayNode");
NodeId node2 = model.addNode("TestDisplayNode");
NodeId node3 = model.addNode("TestDisplayNode");
ConnectionId conn1{node1, 0, node2, 0};
model.addConnection(conn1);
CHECK(model.connectionExists(conn1));
ConnectionId conn2{node2, 0, node3, 0};
model.addConnection(conn2);
CHECK(model.connectionExists(conn2));
ConnectionId conn3{node3, 0, node1, 0};
CHECK_FALSE(model.connectionPossible(conn3));
}
}
TEST_CASE("Loop-enabled model allows cycles", "[loops]")
{
LoopEnabledModel model;
NodeId node1 = model.addNode("Node1");
NodeId node2 = model.addNode("Node2");
ConnectionId conn1{node1, 0, node2, 0};
model.addConnection(conn1);
CHECK(model.connectionExists(conn1));
SECTION("B->A connection is allowed when loops enabled")
{
ConnectionId conn2{node2, 0, node1, 0};
CHECK(model.connectionPossible(conn2));
}
}
TEST_CASE("Loop-disabled model prevents cycles", "[loops]")
{
LoopDisabledModel model;
NodeId node1 = model.addNode("Node1");
NodeId node2 = model.addNode("Node2");
ConnectionId conn1{node1, 0, node2, 0};
model.addConnection(conn1);
SECTION("Loop-disabled model configuration")
{
CHECK(model.loopsEnabled() == false);
}
}