Files
BiLauncher/tests/qml/tst_qml_smoke.cpp
T

109 lines
4.8 KiB
C++
Raw Normal View History

2026-10-12 13:14:08 +03:00
// Дымовой тест главного окна: Main.qml поднимается так же, как в main.cpp,
// без единого предупреждения QML, и ни одного запроса в настоящую сеть.
// Ловит то, что не видно компилятору: опечатки в именах свойств и ключах Loc.t,
// сломанные биндинги, несуществующие картинки в ресурсах.
#include "testenv.h"
#include "fakeinternet.h"
#include "fixtures.h"
#include "launcherpaths.h"
#include "localization.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QQuickWindow>
#include <QtQml/qqmlextensionplugin.h>
Q_IMPORT_QML_PLUGIN(BiLauncherPlugin)
namespace {
QStringList g_problems;
QtMessageHandler g_previous = nullptr;
void collect(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
// Предупреждения QML приходят с файлом qrc:/… или с категорией qt.qml.*.
const QString file = QString::fromUtf8(context.file ? context.file : "");
const QString category = QString::fromUtf8(context.category ? context.category : "");
const bool fromQml = file.startsWith(QLatin1String("qrc:")) || file.endsWith(QLatin1String(".qml"))
|| category.startsWith(QLatin1String("qt.qml"))
|| message.contains(QLatin1String(".qml:"));
if (type != QtDebugMsg && type != QtInfoMsg && fromQml)
g_problems << message;
if (g_previous)
g_previous(type, context, message);
}
} // namespace
class TestQmlSmoke : public QObject
{
Q_OBJECT
FakeInternet m_net;
private slots:
void initTestCase()
{
QString error;
2026-11-01 21:07:03 +03:00
QVERIFY2(m_net.start(&error), qPrintable(error));
m_net.setFallback(FakeResponse::error(503));
2026-10-12 13:14:08 +03:00
const QString game = TestEnv::freshDir(QStringLiteral("game"));
Fixtures::writeJson(LauncherPaths::settingsFile(), QJsonObject{{"gameDir", game}, {"language", "en"}});
QQuickStyle::setStyle(QStringLiteral("Basic"));
g_previous = qInstallMessageHandler(collect);
}
void cleanupTestCase() { qInstallMessageHandler(g_previous); }
void init() { g_problems.clear(); }
void mainWindowLoadsCleanly()
{
// Один движок на весь тест, как в приложении: Localization держит указатель
// на JS-движок, создавший синглтон, и второй QQmlApplicationEngine после
// уничтожения первого оставил бы его висячим.
QQmlApplicationEngine engine;
QStringList warnings;
connect(&engine, &QQmlEngine::warnings, this, [&](const QList<QQmlError> &list) {
for (const QQmlError &e : list)
warnings << e.toString();
});
engine.loadFromModule("BiLauncher", "Main");
QCOMPARE(engine.rootObjects().size(), 1);
auto *window = qobject_cast<QQuickWindow *>(engine.rootObjects().constFirst());
QVERIFY(window);
QVERIFY(QTest::qWaitForWindowExposed(window));
// Даём отработать отложенным таймерам, загрузке каталогов и анимациям.
QTest::qWait(2500);
QVERIFY2(warnings.isEmpty(), qPrintable(warnings.join(u'\n')));
QVERIFY2(g_problems.isEmpty(), qPrintable(g_problems.join(u'\n')));
// Смена языка на лету перестраивает Loc.t — биндинги не должны ломаться.
const QString titleEn = window->title();
Localization::instance().setLanguage(QStringLiteral("ru"));
QTest::qWait(500);
Localization::instance().setLanguage(QStringLiteral("en"));
QTest::qWait(300);
QCOMPARE(window->title(), titleEn);
QVERIFY2(warnings.isEmpty(), qPrintable(warnings.join(u'\n')));
QVERIFY2(g_problems.isEmpty(), qPrintable(g_problems.join(u'\n')));
// Наружу — только через FakeInternet, и только на известные адреса лаунчера.
static const QStringList known{"mojang.com", "galeonworx.com", "adoptium.net", "fabricmc.net",
"quiltmc.org", "minecraftforge.net", "neoforged.net"};
for (const QString &url : m_net.requestedUrls()) {
const QString host = QUrl(url).host();
const bool ok = std::any_of(known.cbegin(), known.cend(),
[&](const QString &domain) { return host.endsWith(domain); });
QVERIFY2(ok, qPrintable("неожиданный запрос: " + url));
}
}
};
BILAUNCHER_GUI_TEST_MAIN(TestQmlSmoke)
#include "tst_qml_smoke.moc"