#include "ApplicationSetup.hpp"
#include "TestGraphModel.hpp"
#include "UITestHelper.hpp"
#include <QtNodes/internal/BasicGraphicsScene.hpp>
#include <QtNodes/internal/GraphicsView.hpp>
#include <QtNodes/internal/NodeGraphicsObject.hpp>
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
#include <QtNodes/Definitions>
#include <catch2/catch.hpp>
#include <QTest>
#include <QSignalSpy>
#include <QGraphicsSceneMouseEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QKeyEvent>
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QTimer>
using QtNodes::BasicGraphicsScene;
using QtNodes::ConnectionGraphicsObject;
using QtNodes::ConnectionId;
using QtNodes::GraphicsView;
using QtNodes::InvalidNodeId;
using QtNodes::NodeGraphicsObject;
using QtNodes::NodeId;
using QtNodes::NodeRole;
using QtNodes::PortIndex;
using QtNodes::PortType;
TEST_CASE("UI Interaction - Node Movement", "[ui][visual]")
{
auto app = applicationSetup();
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
REQUIRE(QTest::qWaitForWindowExposed(&view));
UITestHelper::waitForUI();
SECTION("Create and move a node visually")
{
NodeId nodeId = model->addNode("TestNode");
REQUIRE(nodeId != InvalidNodeId);
QPointF initialPos(100, 100);
model->setNodeData(nodeId, NodeRole::Position, initialPos);
UITestHelper::waitForUI();
scene.update();
view.update();
UITestHelper::waitForUI();
NodeGraphicsObject* nodeGraphics = nullptr;
for (auto item : scene.items()) {
if (auto node = qgraphicsitem_cast<NodeGraphicsObject*>(item)) {
nodeGraphics = node;
break;
}
}
REQUIRE(nodeGraphics != nullptr);
nodeGraphics->setPos(initialPos);
UITestHelper::waitForUI();
QPointF actualInitialPos = model->nodeData(nodeId, NodeRole::Position).value<QPointF>();
CHECK(actualInitialPos.x() == Approx(initialPos.x()).margin(1.0));
CHECK(actualInitialPos.y() == Approx(initialPos.y()).margin(1.0));
QSignalSpy positionSpy(model.get(), &TestGraphModel::nodePositionUpdated);
QPointF newPos(200, 150);
model->setNodeData(nodeId, NodeRole::Position, newPos);
nodeGraphics->setPos(newPos);
UITestHelper::waitForUI();
QPointF finalPos = model->nodeData(nodeId, NodeRole::Position).value<QPointF>();
CHECK(finalPos.x() == Approx(newPos.x()).epsilon(0.1));
CHECK(finalPos.y() == Approx(newPos.y()).epsilon(0.1));
CHECK(positionSpy.count() >= 1);
QPointF nodeCenter = nodeGraphics->boundingRect().center();
QPointF scenePos = nodeGraphics->mapToScene(nodeCenter);
QPoint viewPos = view.mapFromScene(scenePos);
if (view.windowHandle()) {
QTest::mousePress(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, viewPos);
UITestHelper::waitForUI();
QTest::mouseMove(view.windowHandle(), viewPos + QPoint(30, 20));
UITestHelper::waitForUI();
QTest::mouseRelease(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, viewPos + QPoint(30, 20));
UITestHelper::waitForUI();
}
CHECK(model->allNodeIds().size() == 1);
CHECK(nodeGraphics->isVisible());
}
SECTION("Multiple node selection and movement")
{
NodeId node1 = model->addNode("TestNode");
NodeId node2 = model->addNode("TestNode");
model->setNodeData(node1, NodeRole::Position, QPointF(50, 50));
model->setNodeData(node2, NodeRole::Position, QPointF(150, 50));
UITestHelper::waitForUI();
auto items = scene.items();
for (auto item : items) {
if (auto nodeItem = qgraphicsitem_cast<NodeGraphicsObject*>(item)) {
nodeItem->setSelected(true);
break;
}
}
UITestHelper::waitForUI();
auto selectedItems = scene.selectedItems();
CHECK(selectedItems.size() >= 1);
QPointF selectionStart(25, 25);
QPointF selectionEnd(175, 75);
QPoint startPoint = view.mapFromScene(selectionStart);
QPoint endPoint = view.mapFromScene(selectionEnd);
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, startPoint);
UITestHelper::waitForUI();
QTest::mouseMove(view.viewport(), endPoint);
UITestHelper::waitForUI();
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, endPoint);
UITestHelper::waitForUI();
CHECK(model->allNodeIds().size() == 2);
}
}
TEST_CASE("UI Interaction - Connection Creation", "[ui][visual]")
{
auto app = applicationSetup();
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
UITestHelper::waitForUI();
SECTION("Create connection by dragging between ports")
{
NodeId node1 = model->addNode("TestNode");
NodeId node2 = model->addNode("TestNode");
model->setNodeData(node1, NodeRole::Position, QPointF(100, 100));
model->setNodeData(node2, NodeRole::Position, QPointF(300, 100));
UITestHelper::waitForUI();
QSignalSpy connectionSpy(model.get(), &TestGraphModel::connectionCreated);
QPointF outputPortPos(180, 120);
QPointF inputPortPos(300, 120);
UITestHelper::simulateMouseDrag(&view, outputPortPos, inputPortPos);
UITestHelper::waitForUI();
auto connections = model->allConnectionIds(node1);
CHECK(connections.size() >= 0);
INFO("Connection creation signals emitted: " << connectionSpy.count());
CHECK(connectionSpy.count() >= 0);
CHECK(true);
}
SECTION("Disconnect connection by dragging from port")
{
NodeId node1 = model->addNode("TestNode");
NodeId node2 = model->addNode("TestNode");
model->setNodeData(node1, NodeRole::Position, QPointF(100, 100));
model->setNodeData(node2, NodeRole::Position, QPointF(300, 100));
UITestHelper::waitForUI();
PortIndex outputPort = 0;
PortIndex inputPort = 0;
ConnectionId connectionId{node1, outputPort, node2, inputPort};
model->addConnection(connectionId);
UITestHelper::waitForUI();
auto connectionsBefore = model->allConnectionIds(node1);
INFO("Connections before disconnect: " << connectionsBefore.size());
QSignalSpy disconnectionSpy(model.get(), &TestGraphModel::connectionDeleted);
QPointF outputPortPos(180, 120);
QPointF dragAwayPos(200, 200);
UITestHelper::simulateMouseDrag(&view, outputPortPos, dragAwayPos);
UITestHelper::waitForUI();
auto connectionsAfter = model->allConnectionIds(node1);
INFO("Connections after disconnect attempt: " << connectionsAfter.size());
INFO("Disconnection signals emitted: " << disconnectionSpy.count());
CHECK(disconnectionSpy.count() >= 0);
CHECK(true);
}
SECTION("Disconnect by selecting and deleting connection")
{
NodeId node1 = model->addNode("TestNode");
NodeId node2 = model->addNode("TestNode");
model->setNodeData(node1, NodeRole::Position, QPointF(100, 100));
model->setNodeData(node2, NodeRole::Position, QPointF(300, 100));
UITestHelper::waitForUI();
PortIndex outputPort = 0;
PortIndex inputPort = 0;
ConnectionId connectionId{node1, outputPort, node2, inputPort};
model->addConnection(connectionId);
UITestHelper::waitForUI();
scene.update();
view.update();
UITestHelper::waitForUI();
ConnectionGraphicsObject* connectionGraphics = nullptr;
for (auto item : scene.items()) {
if (auto conn = qgraphicsitem_cast<ConnectionGraphicsObject*>(item)) {
connectionGraphics = conn;
break;
}
}
if (connectionGraphics) {
connectionGraphics->setSelected(true);
UITestHelper::waitForUI();
QSignalSpy deletionSpy(model.get(), &TestGraphModel::connectionDeleted);
QKeyEvent deleteEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier);
QApplication::sendEvent(&view, &deleteEvent);
UITestHelper::waitForUI();
INFO("Connection deletion signals emitted: " << deletionSpy.count());
CHECK(deletionSpy.count() >= 0);
CHECK(true);
} else {
CHECK(true);
}
}
}
TEST_CASE("UI Interaction - Zoom and Pan", "[ui][visual]")
{
auto app = applicationSetup();
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
UITestHelper::waitForUI();
SECTION("Zoom using mouse wheel")
{
NodeId nodeId = model->addNode("TestNode");
model->setNodeData(nodeId, NodeRole::Position, QPointF(400, 300));
UITestHelper::waitForUI();
QTransform initialTransform = view.transform();
QPoint viewCenter = view.rect().center();
QWheelEvent wheelEvent(viewCenter, view.mapToGlobal(viewCenter),
QPoint(0, 0), QPoint(0, 120),
Qt::NoButton, Qt::NoModifier, Qt::ScrollPhase::NoScrollPhase, false);
QApplication::sendEvent(view.viewport(), &wheelEvent);
UITestHelper::waitForUI();
QTransform newTransform = view.transform();
CHECK(newTransform.m11() != initialTransform.m11());
}
SECTION("Pan using middle mouse button drag")
{
NodeId nodeId = model->addNode("TestNode");
model->setNodeData(nodeId, NodeRole::Position, QPointF(400, 300));
UITestHelper::waitForUI();
QPointF initialCenter = view.mapToScene(view.rect().center());
QPointF panStart(400, 300);
QPointF panEnd(450, 350);
UITestHelper::simulateMouseDrag(&view, panStart, panEnd);
UITestHelper::waitForUI();
QPointF newCenter = view.mapToScene(view.rect().center());
CHECK(true);
}
}
TEST_CASE("UI Interaction - Keyboard Shortcuts", "[ui][visual]")
{
auto app = applicationSetup();
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
view.setFocus();
UITestHelper::waitForUI();
SECTION("Delete key removes selected nodes")
{
NodeId nodeId = model->addNode("TestNode");
model->setNodeData(nodeId, NodeRole::Position, QPointF(100, 100));
UITestHelper::waitForUI();
CHECK(model->nodeExists(nodeId));
NodeGraphicsObject* nodeGraphics = nullptr;
for (auto item : scene.items()) {
if (auto node = qgraphicsitem_cast<NodeGraphicsObject*>(item)) {
nodeGraphics = node;
break;
}
}
if (nodeGraphics) {
nodeGraphics->setSelected(true);
UITestHelper::waitForUI();
QSignalSpy deletionSpy(model.get(), &TestGraphModel::nodeDeleted);
#ifdef Q_OS_MACOS
QTest::keyClick(&view, Qt::Key_Backspace);
#else
QTest::keyClick(&view, Qt::Key_Delete);
#endif
UITestHelper::waitForUI();
CHECK(deletionSpy.count() >= 0);
CHECK_FALSE(model->nodeExists(nodeId));
}
}
SECTION("Ctrl+Z for undo operations")
{
NodeId nodeId = model->addNode("TestNode");
UITestHelper::waitForUI();
QKeyEvent undoEvent(QEvent::KeyPress, Qt::Key_Z, Qt::ControlModifier);
QApplication::sendEvent(&view, &undoEvent);
UITestHelper::waitForUI();
CHECK(true);
}
}
TEST_CASE("UI Interaction - Context Menu", "[ui][visual]")
{
auto app = applicationSetup();
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
UITestHelper::waitForUI();
SECTION("Right-click context menu")
{
QPointF clickPos(400, 300);
UITestHelper::simulateMousePress(&view, clickPos, Qt::RightButton);
UITestHelper::waitForUI();
UITestHelper::simulateMouseRelease(&view, clickPos, Qt::RightButton);
UITestHelper::waitForUI();
CHECK(true);
}
}
TEST_CASE("UI Interaction - Stress Test", "[ui][visual][stress]")
{
auto app = applicationSetup();
auto model = std::make_shared<TestGraphModel>();
BasicGraphicsScene scene(*model);
GraphicsView view(&scene);
view.resize(800, 600);
view.show();
UITestHelper::waitForUI();
SECTION("Rapid mouse movements and clicks")
{
std::vector<NodeId> nodes;
for (int i = 0; i < 5; ++i) {
NodeId nodeId = model->addNode("TestNode");
model->setNodeData(nodeId, NodeRole::Position, QPointF(100 + i * 100, 100 + i * 50));
nodes.push_back(nodeId);
}
UITestHelper::waitForUI();
for (int i = 0; i < 10; ++i) {
QPointF randomPos(100 + (i * 50) % 600, 100 + (i * 30) % 400);
UITestHelper::simulateMousePress(&view, randomPos);
UITestHelper::waitForUI();
QPointF movePos(randomPos.x() + 20, randomPos.y() + 20);
UITestHelper::simulateMouseMove(&view, movePos);
UITestHelper::waitForUI();
UITestHelper::simulateMouseRelease(&view, movePos);
UITestHelper::waitForUI();
}
CHECK(true);
}
SECTION("Memory and performance under UI load")
{
for (int i = 0; i < 20; ++i) {
NodeId nodeId = model->addNode("TestNode");
model->setNodeData(nodeId, NodeRole::Position, QPointF(i * 30, i * 20));
UITestHelper::waitForUI();
if (i % 3 == 0) {
model->deleteNode(nodeId);
UITestHelper::waitForUI();
}
}
CHECK(true);
}
}