// Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_IMPORT_PLUGIN(QuickControlsSanityPlugin) using namespace QQuickVisualTestUtils; using namespace QQuickControlsTestUtils; using namespace Qt::StringLiterals; class tst_Sanity : public QQmlDataTest { Q_OBJECT public: tst_Sanity(); private slots: void initTestCase() override; void jsFiles(); void qmllint(); void qmllint_data(); void quickControlsSanityPlugin(); void quickControlsSanityPlugin_data(); private: QMap sourceQmlFiles; QMap installedQmlFiles; QStringList m_importPaths; QQmlJSLinter m_linter; QList m_categories; }; tst_Sanity::tst_Sanity() : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings), m_importPaths({ QLibraryInfo::path(QLibraryInfo::QmlImportsPath) }), m_linter(m_importPaths, m_importPaths), m_categories(QQmlJSLogger::defaultCategories()) { // We do not care about any warnings that aren't explicitly created by controls-sanity. // Mainly because a lot of false positives are generated because we are linting files from // different modules directly without their generated qmldirs. m_linter.setPluginsEnabled(true); for (auto &category : m_categories) { category.setLevel(QtCriticalMsg); category.setIgnored(true); } for (auto &plugin: m_linter.plugins()) { if (plugin.name() != u"QuickControlsSanity") { plugin.setEnabled(false); continue; } for (const auto &category: plugin.categories()) { m_categories.append(category); m_categories.back().setLevel(QtWarningMsg); m_categories.back().setIgnored(false); } } } void tst_Sanity::initTestCase() { QQmlDataTest::initTestCase(); QQmlEngine engine; QQmlComponent component(&engine); component.setData(QString("import QtQuick.Templates 2.%1; Control { }").arg(15).toUtf8(), QUrl()); const QStringList qmlTypeNames = QQmlMetaType::qmlTypeNames(); // Collect the files from each style in the source tree. QDirIterator it(QQC2_IMPORT_PATH, QStringList() << "*.qml" << "*.js", QDir::Files, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); QFileInfo info = it.fileInfo(); if (qmlTypeNames.contains(QStringLiteral("QtQuick.Templates/") + info.baseName())) sourceQmlFiles.insert(info.dir().dirName() + "/" + info.fileName(), info.filePath()); } // Then, collect the files from each installed style directory. const QVector> styleRelativePaths = { { "basic", "QtQuick/Controls/Basic" }, { "fusion", "QtQuick/Controls/Fusion" }, { "material", "QtQuick/Controls/Material" }, { "universal", "QtQuick/Controls/Universal" }, // TODO: add native styles: QTBUG-87108 { "ios", "QtQuick/Controls/iOS" } }; for (const auto &stylePathPair : styleRelativePaths) { forEachControl(&engine, QQC2_IMPORT_PATH, stylePathPair.first, stylePathPair.second, QStringList(), [&](const QString &relativePath, const QUrl &absoluteUrl) { installedQmlFiles.insert(relativePath, absoluteUrl.toLocalFile()); }); } } void tst_Sanity::jsFiles() { QMap::const_iterator it; for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) { if (QFileInfo(it.value()).suffix() == QStringLiteral("js")) QFAIL(qPrintable(it.value() + ": JS files are not allowed")); } } void tst_Sanity::qmllint() { QFETCH(QString, control); QFETCH(QString, filePath); QJsonArray output; bool success = m_linter.lintFile(filePath, nullptr, true, &output, m_importPaths, {}, {}, m_categories) == QQmlJSLinter::LintSuccess; QVERIFY2(success, qPrintable(QJsonDocument(output).toJson(QJsonDocument::Compact))); } void tst_Sanity::qmllint_data() { QTest::addColumn("control"); QTest::addColumn("filePath"); QMap::const_iterator it; for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) QTest::newRow(qPrintable(it.key())) << it.key() << it.value(); } void tst_Sanity::quickControlsSanityPlugin() { QFETCH(QString, filePath); QFETCH(QString, result); QJsonArray output; bool hasWarnings = m_linter.lintFile(testFile(filePath), nullptr, true, &output, m_importPaths, {}, {}, m_categories) == QQmlJSLinter::HasWarnings; QVERIFY(hasWarnings); const auto &warningsOutput = output.first().toObject().value("warnings").toArray(); QCOMPARE(warningsOutput.first().toObject().value("message"), result); } void tst_Sanity::quickControlsSanityPlugin_data() { QTest::addColumn("filePath"); QTest::addColumn("result"); QTest::newRow("functionDeclarations") << QStringLiteral("functionDeclarations.qml") << QStringLiteral("Declared function \"add\""); QTest::newRow("signalHandlers") << QStringLiteral("signalHandlers.qml") << QStringLiteral("Declared signal handler \"onCompleted\""); QTest::newRow("anchors") << QStringLiteral("anchors.qml") << QStringLiteral("Using anchors here"); } QTEST_MAIN(tst_Sanity) #include "tst_sanity.moc"