diagrams-fable-qt/tests/tst_model.cpp
Ilya Ashikhmin 26905a4ba0 feat(core): add network model with typed ports, relations and grouped properties
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>
2026-07-02 23:14:11 +02:00

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"