Data-driven node types (SVG + port config) loaded from JSON, relation-based connection validation, and a property system supporting string, int, float, enum, catalogue item(s), color and value-list types grouped by category. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
146 lines
5.4 KiB
C++
146 lines
5.4 KiB
C++
#include "TestFixtures.h"
|
|
#include "core/NetworkModel.h"
|
|
|
|
#include <QSignalSpy>
|
|
#include <QTest>
|
|
|
|
using namespace diag;
|
|
|
|
class TestModel : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void init()
|
|
{
|
|
fixtures::fillRegistry(m_reg);
|
|
delete m_model;
|
|
m_model = new NetworkModel(&m_reg, this);
|
|
}
|
|
|
|
void addNode_unknownTypeFails()
|
|
{
|
|
QVERIFY(m_model->addNode(QStringLiteral("nope"), {0, 0}).isNull());
|
|
QCOMPARE(m_model->nodeIds().size(), 0);
|
|
}
|
|
|
|
void addNode_emitsAndStores()
|
|
{
|
|
QSignalSpy spy(m_model, &NetworkModel::nodeAdded);
|
|
const QUuid id = m_model->addNode(QStringLiteral("pump"), {100, 40});
|
|
QVERIFY(!id.isNull());
|
|
QCOMPARE(spy.count(), 1);
|
|
QCOMPARE(m_model->node(id)->pos, QPointF(100, 40));
|
|
QCOMPARE(m_model->nodeTypeOf(id)->id, QStringLiteral("pump"));
|
|
}
|
|
|
|
void connect_validOutToIn()
|
|
{
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid pump = m_model->addNode(QStringLiteral("pump"), {200, 0});
|
|
QVERIFY(m_model->canConnect(src, "out", pump, "in"));
|
|
const QUuid e = m_model->addEdge(src, "out", pump, "in");
|
|
QVERIFY(!e.isNull());
|
|
QCOMPARE(m_model->edge(e)->relation, QStringLiteral("water"));
|
|
}
|
|
|
|
void connect_directionNormalized()
|
|
{
|
|
// Dragging from the In port must still produce an Out->In edge.
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid pump = m_model->addNode(QStringLiteral("pump"), {200, 0});
|
|
const QUuid e = m_model->addEdge(pump, "in", src, "out");
|
|
QVERIFY(!e.isNull());
|
|
QCOMPARE(m_model->edge(e)->fromNode, src);
|
|
QCOMPARE(m_model->edge(e)->toNode, pump);
|
|
}
|
|
|
|
void connect_relationMismatchFails()
|
|
{
|
|
const QUuid gen = m_model->addNode(QStringLiteral("generator"), {0, 0});
|
|
const QUuid pump = m_model->addNode(QStringLiteral("pump"), {200, 0});
|
|
QString reason;
|
|
QVERIFY(!m_model->canConnect(gen, "out", pump, "in", &reason));
|
|
QVERIFY(reason.contains(QStringLiteral("relation")));
|
|
QVERIFY(m_model->addEdge(gen, "out", pump, "in").isNull());
|
|
}
|
|
|
|
void connect_twoInputsFails()
|
|
{
|
|
const QUuid a = m_model->addNode(QStringLiteral("pump"), {0, 0});
|
|
const QUuid b = m_model->addNode(QStringLiteral("consumer"), {200, 0});
|
|
QVERIFY(!m_model->canConnect(a, "in", b, "in"));
|
|
}
|
|
|
|
void connect_selfPortFails()
|
|
{
|
|
const QUuid j = m_model->addNode(QStringLiteral("junction"), {0, 0});
|
|
QVERIFY(!m_model->canConnect(j, "w", j, "w"));
|
|
// Different ports on the same node are allowed.
|
|
QVERIFY(m_model->canConnect(j, "w", j, "e"));
|
|
}
|
|
|
|
void connect_occupiedPortFails()
|
|
{
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid p1 = m_model->addNode(QStringLiteral("pump"), {200, 0});
|
|
const QUuid p2 = m_model->addNode(QStringLiteral("pump"), {200, 100});
|
|
QVERIFY(!m_model->addEdge(src, "out", p1, "in").isNull());
|
|
QString reason;
|
|
QVERIFY(!m_model->canConnect(p2, "out", p1, "in", &reason));
|
|
QVERIFY(reason.contains(QStringLiteral("occupied")));
|
|
// Source "out" is unlimited (maxConnections = 0).
|
|
QVERIFY(m_model->canConnect(src, "out", p2, "in"));
|
|
}
|
|
|
|
void connect_duplicateEdgeFails()
|
|
{
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid j = m_model->addNode(QStringLiteral("junction"), {200, 0});
|
|
QVERIFY(!m_model->addEdge(src, "out", j, "w").isNull());
|
|
QVERIFY(!m_model->canConnect(src, "out", j, "w"));
|
|
QVERIFY(!m_model->canConnect(j, "w", src, "out"));
|
|
}
|
|
|
|
void junction_allowsManyConnections()
|
|
{
|
|
const QUuid j = m_model->addNode(QStringLiteral("junction"), {0, 0});
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {-200, 0});
|
|
const QUuid c1 = m_model->addNode(QStringLiteral("consumer"), {200, -100});
|
|
const QUuid c2 = m_model->addNode(QStringLiteral("consumer"), {200, 100});
|
|
QVERIFY(!m_model->addEdge(src, "out", j, "w").isNull());
|
|
QVERIFY(!m_model->addEdge(j, "e", c1, "in").isNull());
|
|
QVERIFY(!m_model->addEdge(j, "e", c2, "in").isNull());
|
|
QCOMPARE(m_model->connectionCount(j, "e"), 2);
|
|
}
|
|
|
|
void removeNode_cascadesEdges()
|
|
{
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid pump = m_model->addNode(QStringLiteral("pump"), {200, 0});
|
|
const QUuid c = m_model->addNode(QStringLiteral("consumer"), {400, 0});
|
|
m_model->addEdge(src, "out", pump, "in");
|
|
m_model->addEdge(pump, "out", c, "in");
|
|
QSignalSpy edgeSpy(m_model, &NetworkModel::edgeRemoved);
|
|
m_model->removeNode(pump);
|
|
QCOMPARE(edgeSpy.count(), 2);
|
|
QCOMPARE(m_model->edgeIds().size(), 0);
|
|
QCOMPARE(m_model->nodeIds().size(), 2);
|
|
}
|
|
|
|
void moveNode_emitsAndUpdatesPortPos()
|
|
{
|
|
const QUuid pump = m_model->addNode(QStringLiteral("pump"), {0, 0});
|
|
QSignalSpy spy(m_model, &NetworkModel::nodeMoved);
|
|
m_model->moveNode(pump, {100, 100});
|
|
QCOMPARE(spy.count(), 1);
|
|
// Port "out" is at relative (1.0, 0.5) of a 60x60 node.
|
|
QCOMPARE(m_model->portScenePos(pump, "out"), QPointF(160, 130));
|
|
}
|
|
|
|
private:
|
|
TypeRegistry m_reg;
|
|
NetworkModel* m_model = nullptr;
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestModel)
|
|
#include "tst_model.moc"
|