Revision control
++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ #include <QtTest/QtTest>
++
++ #include <QtCore/QScopeGuard>
++ #include <QtCore/QTemporaryDir>
++ #include <QtSql/QSqlDatabase>
++ #include <QtSql/QSqlQuery>
++
++ #include <QtHelp/QHelpEngineCore>
++ #include <QtHelp/QHelpSearchEngineCore>
++ #include <QtHelp/QHelpSearchResult>
++
++ class tst_QHelpSearchEngineCore : public QObject
++ {
++ Q_OBJECT
++
++ private slots:
++ void initTestCase();
++
++ void search_data();
++ void search();
++
++ private:
++ QTemporaryDir m_dir;
++ std::unique_ptr<QHelpEngineCore> m_helpEngine;
++ std::unique_ptr<QHelpSearchEngineCore> m_searchEngine;
++ };
++
++ static bool addFile(QSqlQuery &query, int fileId, const QString &name, const QByteArray &data)
++ {
++ query.prepare("INSERT INTO FileDataTable (Id, Data) VALUES (?, ?)");
++ query.addBindValue(fileId);
++ query.addBindValue(qCompress(data));
++ if (!query.exec())
++ return false;
++
++ query.prepare("INSERT INTO FileNameTable (FolderId, Name, FileId, Title) VALUES (1, ?, ?, ?)");
++ query.addBindValue(name);
++ query.addBindValue(fileId);
++ query.addBindValue(name);
++ if (!query.exec())
++ return false;
++
++ // Make the file part of every filter attribute set of test.qch.
++ query.prepare("INSERT INTO FileFilterTable (FilterAttributeId, FileId) "
++ "SELECT Id, ? FROM FilterAttributeTable");
++ query.addBindValue(fileId);
++ return query.exec();
++ }
++
++ static QByteArray html(const QByteArray &word)
++ {
++ return "<html><head><title>" + word + "</title></head><body><p>" + word
++ + "</p></body></html>";
++ }
++
++ void tst_QHelpSearchEngineCore::initTestCase()
++ {
++ QVERIFY(m_dir.isValid());
++
++ const QString qchFile = m_dir.filePath("test.qch");
++ QVERIFY(QFile::copy(QLatin1String(SRCDIR) + "/data/test.qch", qchFile));
++ QVERIFY(QFile::setPermissions(qchFile, QFile::WriteUser | QFile::ReadUser));
++
++ {
++ QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "searchdb");
++ const auto cleanup = qScopeGuard([&db] {
++ db.close();
++ db = {};
++ QSqlDatabase::removeDatabase("searchdb");
++ });
++ db.setDatabaseName(qchFile);
++ QVERIFY(db.open());
++ QSqlQuery query(db);
++ QVERIFY(addFile(query, 100, "extra.html", html("quokkahtml")));
++ QVERIFY(addFile(query, 101, "page.htm", html("quokkahtm")));
++ QVERIFY(addFile(query, 102, "notes.txt", "quokkatxt"));
++ QVERIFY(addFile(query, 103, "style.css", "quokkacss { color: red; }"));
++ }
++
++ m_helpEngine.reset(new QHelpEngineCore(m_dir.filePath("collection.qhc")));
++ m_helpEngine->setReadOnly(false);
++ QVERIFY(m_helpEngine->setupData());
++ QVERIFY(m_helpEngine->registerDocumentation(qchFile));
++
++ m_searchEngine.reset(new QHelpSearchEngineCore(m_helpEngine.get()));
++ QSignalSpy indexingSpy(m_searchEngine.get(), &QHelpSearchEngineCore::indexingFinished);
++ m_searchEngine->reindexDocumentation();
++ QVERIFY(indexingSpy.wait(10000));
++ }
++
++ void tst_QHelpSearchEngineCore::search_data()
++ {
++ QTest::addColumn<QString>("word");
++ QTest::addColumn<QString>("expectedFile");
++ QTest::addColumn<bool>("needsFullQtHelp");
++
++ QTest::newRow("html") << "quokkahtml" << "extra.html" << true;
++ QTest::newRow("htm") << "quokkahtm" << "page.htm" << true;
++ QTest::newRow("txt") << "quokkatxt" << "notes.txt" << false;
++ QTest::newRow("css") << "quokkacss" << QString() << false;
++ }
++
++ void tst_QHelpSearchEngineCore::search()
++ {
++ QFETCH(QString, word);
++ QFETCH(QString, expectedFile);
++ QFETCH(bool, needsFullQtHelp);
++
++ #ifndef TEST_FULLQTHELP
++ if (needsFullQtHelp)
++ QSKIP("The contents of HTML files are indexed only with fullqthelp.");
++ #else
++ Q_UNUSED(needsFullQtHelp);
++ #endif
++
++ QSignalSpy searchingSpy(m_searchEngine.get(), &QHelpSearchEngineCore::searchingFinished);
++ m_searchEngine->search(word);
++ QVERIFY(searchingSpy.wait(10000));
++
++ const QList<QHelpSearchResult> results
++ = m_searchEngine->searchResults(0, m_searchEngine->searchResultCount());
++ QSet<QString> files;
++ for (const QHelpSearchResult &result : results)
++ files.insert(result.url().path().section(u'/', -1));
++
++ if (expectedFile.isEmpty())
++ QVERIFY2(files.isEmpty(), qPrintable(QStringList(files.cbegin(), files.cend()).join(u',')));
++ else
++ QCOMPARE(files, QSet<QString>{expectedFile});
++ }
++
++ QTEST_MAIN(tst_QHelpSearchEngineCore)
++ #include "tst_qhelpsearchenginecore.moc"