test(app): add end-to-end scenarios against the real main window

Bundled registry/sample loading, edge selection populating the grouped
property panel, undoable title edit, solver + animation clock, and a
headless render smoke check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ilya Ashikhmin 2026-07-02 23:37:18 +02:00
parent 18289b33e1
commit 4f6fe37b4d
2 changed files with 144 additions and 0 deletions

View File

@ -13,3 +13,9 @@ diag_add_test(tst_solver diagcore)
diag_add_test(tst_json diagcore) diag_add_test(tst_json diagcore)
diag_add_test(tst_undo diagscene) diag_add_test(tst_undo diagscene)
diag_add_test(tst_scene diagscene) diag_add_test(tst_scene diagscene)
qt_add_resources(TEST_RESOURCES ${CMAKE_SOURCE_DIR}/resources/resources.qrc)
add_executable(tst_app tst_app.cpp ${TEST_RESOURCES})
target_link_libraries(tst_app PRIVATE diagapp Qt6::Test)
add_test(NAME tst_app COMMAND tst_app)
set_tests_properties(tst_app PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")

138
tests/tst_app.cpp Normal file
View File

@ -0,0 +1,138 @@
// End-to-end scenarios against the real MainWindow with bundled resources
// (registry config, SVGs, sample document), offscreen.
#include "app/MainWindow.h"
#include "app/PropertyPanel.h"
#include "core/FlowSolver.h"
#include "core/NetworkModel.h"
#include "scene/DiagramScene.h"
#include "scene/EdgeItem.h"
#include "scene/NodeItem.h"
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QGroupBox>
#include <QLineEdit>
#include <QTest>
#include <QUndoStack>
using namespace diag;
class TestApp : public QObject {
Q_OBJECT
private slots:
void initTestCase()
{
Q_INIT_RESOURCE(resources);
}
void registryLoads_fromBundledConfig()
{
MainWindow window;
const TypeRegistry* reg = window.model()->registry();
QVERIFY(reg->nodeTypes().size() >= 9);
QVERIFY(reg->relation(QStringLiteral("water")));
QVERIFY(reg->relation(QStringLiteral("power")));
QVERIFY(reg->catalogue(QStringLiteral("pipe_series")));
QCOMPARE(reg->nodeGroups(),
(QStringList{QStringLiteral("Water supply"), QStringLiteral("Power")}));
}
void sampleLoads_andRoutes()
{
MainWindow window;
window.show();
window.loadSample();
QVERIFY(window.model()->nodeIds().size() >= 8);
QVERIFY(window.model()->edgeIds().size() >= 7);
// Every edge item has an orthogonal polyline attached to its ports.
for (QUuid id : window.model()->edgeIds()) {
EdgeItem* item = window.scene()->edgeItem(id);
QVERIFY(item);
QVERIFY(item->polyline().size() >= 2);
}
}
void selectingEdge_populatesGroupedPropertyPanel()
{
MainWindow window;
window.show();
window.loadSample();
const QUuid edgeId = window.model()->edgeIds().first();
window.scene()->clearSelection();
window.scene()->edgeItem(edgeId)->setSelected(true);
auto* panel = window.findChild<PropertyPanel*>();
QVERIFY(panel);
const auto groups = panel->findChildren<QGroupBox*>();
QStringList titles;
for (auto* g : groups)
titles << g->title();
QVERIFY(titles.contains(QStringLiteral("General")));
QVERIFY(titles.contains(QStringLiteral("Presentation")));
QVERIFY(titles.contains(QStringLiteral("Physics")));
// Style enums and physics editors exist.
QVERIFY(panel->findChildren<QComboBox*>().size() >= 3);
QVERIFY(panel->findChildren<QDoubleSpinBox*>().size() >= 2);
}
void editingTitle_isUndoable()
{
MainWindow window;
window.show();
window.loadSample();
const QUuid nodeId = window.model()->nodeIds().first();
window.scene()->clearSelection();
window.scene()->nodeItem(nodeId)->setSelected(true);
auto* panel = window.findChild<PropertyPanel*>();
auto* titleEdit = panel->findChild<QLineEdit*>();
QVERIFY(titleEdit);
titleEdit->setText(QStringLiteral("Renamed"));
emit titleEdit->editingFinished();
QCOMPARE(window.model()->nodeProperty(nodeId, "title").toString(),
QStringLiteral("Renamed"));
window.scene()->undoStack()->undo();
QCOMPARE(window.model()->nodeProperty(nodeId, "title").toString(),
QStringLiteral("Intake"));
}
void simulation_assignsFlowsAndAnimates()
{
MainWindow window;
window.show();
window.loadSample();
window.scene()->setFlowAnimationEnabled(true);
// Solve both networks like the Simulation menu action does.
for (const Relation& rel : window.model()->registry()->relations())
FlowSolver::apply(*window.model(),
FlowSolver::solve(*window.model(), rel.id));
// Water main from the intake carries the full balanced demand.
double maxWater = 0;
for (QUuid id : window.model()->edgeIds())
if (window.model()->edge(id)->relation == QLatin1String("water"))
maxWater = qMax(maxWater, qAbs(window.model()->edge(id)->flowRate));
QVERIFY(maxWater > 1.0);
QVERIFY(window.scene()->maxAbsFlow() >= maxWater);
const double phase0 = window.scene()->animationPhase();
QTRY_VERIFY_WITH_TIMEOUT(window.scene()->animationPhase() > phase0, 1000);
window.scene()->setFlowAnimationEnabled(false);
}
void screenshot_rendersWithoutCrash()
{
MainWindow window;
window.resize(1000, 700);
window.show();
window.loadSample();
const QPixmap shot = window.grab();
QVERIFY(!shot.isNull());
QVERIFY(shot.width() > 0);
}
};
QTEST_MAIN(TestApp)
#include "tst_app.moc"