// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "formeditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace CppEditor; using namespace CPlusPlus; using namespace Designer; using namespace Designer::Internal; using namespace Utils; namespace Designer::Internal { class DocumentContainsFunctionDefinition: protected SymbolVisitor { public: bool operator()(Scope *scope, const QString function) { if (!scope) return false; m_referenceFunction = function; m_result = false; accept(scope); return m_result; } protected: bool preVisit(Symbol *) { return !m_result; } bool visit(Function *symbol) { const QString function = m_overview.prettyName(symbol->name()); if (function == m_referenceFunction) m_result = true; return false; } private: bool m_result; QString m_referenceFunction; Overview m_overview; }; class DocumentContainsDeclaration: protected SymbolVisitor { public: bool operator()(Scope *scope, const QString &function) { if (!scope) return false; m_referenceFunction = function; m_result = false; accept(scope); return m_result; } protected: bool preVisit(Symbol *) { return !m_result; } void postVisit(Symbol *symbol) { if (symbol->asClass()) m_currentClass.clear(); } bool visit(Class *symbol) { m_currentClass = m_overview.prettyName(symbol->name()); return true; } bool visit(Declaration *symbol) { QString declaration = m_overview.prettyName(symbol->name()); if (!m_currentClass.isEmpty()) declaration = m_currentClass + "::" + declaration; if (m_referenceFunction == declaration) m_result = true; return false; } private: bool m_result; QString m_referenceFunction; QString m_currentClass; Overview m_overview; }; bool documentContainsFunctionDefinition(const Document::Ptr &document, const QString &function) { return DocumentContainsFunctionDefinition()(document->globalNamespace(), function); } bool documentContainsMemberFunctionDeclaration(const Document::Ptr &document, const QString &declaration) { return DocumentContainsDeclaration()(document->globalNamespace(), declaration); } class GoToSlotTestCase : public CppEditor::Tests::TestCase { public: GoToSlotTestCase(const FilePaths &files) { QVERIFY(succeededSoFar()); QCOMPARE(files.size(), 3); QList editors; for (const FilePath &file : files) { IEditor *editor = EditorManager::openEditor(file); TextEditor::BaseTextEditor *e = qobject_cast(editor); QVERIFY(e); closeEditorAtEndOfTestCase(editor); editors << e; } const FilePath cppFile = files.at(0); const FilePath hFile = files.at(1); QCOMPARE(DocumentModel::openedDocuments().size(), files.size()); waitForFilesInGlobalSnapshot({cppFile, hFile}); // Execute "Go To Slot" QDesignerIntegrationInterface *integration = designerEditor()->integration(); QVERIFY(integration); integration->emitNavigateToSlot("pushButton", "clicked()", QStringList()); QCOMPARE(EditorManager::currentDocument()->filePath(), cppFile); QVERIFY(EditorManager::currentDocument()->isModified()); // Wait for updated documents for (TextEditor::BaseTextEditor *editor : std::as_const(editors)) { QElapsedTimer t; t.start(); const FilePath filePath = editor->document()->filePath(); if (auto parser = BuiltinEditorDocumentParser::get(filePath)) { while (t.elapsed() < 2000) { if (Document::Ptr document = parser->document()) { if (document->editorRevision() == 2) break; } QApplication::processEvents(); } } } // Compare const auto cppDocumentParser = BuiltinEditorDocumentParser::get(cppFile); QVERIFY(cppDocumentParser); const Document::Ptr cppDocument = cppDocumentParser->document(); QVERIFY(cppDocument->editorRevision() >= 2); QVERIFY(checkDiagsnosticMessages(cppDocument)); const auto hDocumentParser = BuiltinEditorDocumentParser::get(hFile); QVERIFY(hDocumentParser); const Document::Ptr hDocument = hDocumentParser->document(); QVERIFY(checkDiagsnosticMessages(hDocument)); QVERIFY(documentContainsFunctionDefinition(cppDocument, "Form::on_pushButton_clicked")); QVERIFY(documentContainsMemberFunctionDeclaration(hDocument, "Form::on_pushButton_clicked")); } static bool checkDiagsnosticMessages(const Document::Ptr &document) { if (!document) return false; // Since no project is opened and the ui_*.h is not generated, // the following diagnostic messages will be ignored. const QStringList ignoreList = QStringList({"ui_form.h: No such file or directory", "QWidget: No such file or directory"}); QList cleanedDiagnosticMessages; const auto diagnosticMessages = document->diagnosticMessages(); for (const Document::DiagnosticMessage &message : diagnosticMessages) { if (!ignoreList.contains(message.text())) cleanedDiagnosticMessages << message; } return cleanedDiagnosticMessages.isEmpty(); } }; class GoToSlotTest final : public QObject { Q_OBJECT private slots: void test_gotoslot(); void test_gotoslot_data(); }; /// Check: Executes "Go To Slot..." on a QPushButton in a *.ui file and checks if the respective /// header and source files are correctly updated. void GoToSlotTest::test_gotoslot() { class SystemSettingsMgr { public: SystemSettingsMgr() : m_saveAfterRefactor(Core::Internal::systemSettings().autoSaveAfterRefactoring.value()) { Core::Internal::systemSettings().autoSaveAfterRefactoring.setValue(false); } ~SystemSettingsMgr() { Core::Internal::systemSettings().autoSaveAfterRefactoring.setValue(m_saveAfterRefactor); } private: const bool m_saveAfterRefactor; } systemSettingsMgr; QFETCH(FilePaths, files); (GoToSlotTestCase(files)); } void GoToSlotTest::test_gotoslot_data() { QTest::addColumn("files"); const auto dataDir = [](const QString &subdir) { return FilePath::fromUserInput(SRCDIR "/../../../tests/designer/" + subdir); }; FilePath testDataDirWithoutProject = dataDir("gotoslot_withoutProject"); QVERIFY(testDataDirWithoutProject.exists()); QTest::newRow("withoutProject") << FilePaths({testDataDirWithoutProject / "form.cpp", testDataDirWithoutProject / "form.h", testDataDirWithoutProject / "form.ui"}); // Finding the right class for inserting definitions/declarations is based on // finding a class with a member whose type is the class from the "ui_xxx.h" header. // In the following test data the header files contain an extra class referencing // the same class name. FilePath testDataDir; testDataDir = dataDir("gotoslot_insertIntoCorrectClass_pointer"); QVERIFY(testDataDir.exists()); QTest::newRow("insertIntoCorrectClass_pointer") << FilePaths({testDataDir / "form.cpp", testDataDir / "form.h", testDataDirWithoutProject / "form.ui"}); // reuse testDataDir = dataDir("gotoslot_insertIntoCorrectClass_non-pointer"); QVERIFY(testDataDir.exists()); QTest::newRow("insertIntoCorrectClass_non-pointer") << FilePaths({testDataDir / "form.cpp", testDataDir / "form.h", testDataDirWithoutProject / "form.ui"}); // reuse testDataDir = dataDir("gotoslot_insertIntoCorrectClass_pointer_ns_using"); QVERIFY(testDataDir.exists()); QTest::newRow("insertIntoCorrectClass_pointer_ns_using") << FilePaths({testDataDir / "form.cpp", testDataDir / "form.h", testDataDir / "form.ui"}); } QObject *createGoToSlotTest() { return new GoToSlotTest; } } // Designer::Internal #include "gotoslot_test.moc"