diff --git a/autotests/dfm-search-tests/CMakeLists.txt b/autotests/dfm-search-tests/CMakeLists.txt index 4c60bee..0378324 100644 --- a/autotests/dfm-search-tests/CMakeLists.txt +++ b/autotests/dfm-search-tests/CMakeLists.txt @@ -27,6 +27,8 @@ target_link_libraries(dfm-search-test # Add size_parser source for testing (it's part of dfm-searcher client but we test it here) target_sources(dfm-search-test PRIVATE ${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/size_parser.cpp + ${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/time_parser.cpp + ${CMAKE_SOURCE_DIR}/src/dfm-search/dfm-search-client/cli_options.cpp ${CMAKE_SOURCE_DIR}/3rdparty/testutils/stub-ext/stub-shadow.cpp ) diff --git a/autotests/dfm-search-tests/main.cpp b/autotests/dfm-search-tests/main.cpp index 278fdea..7894bd2 100644 --- a/autotests/dfm-search-tests/main.cpp +++ b/autotests/dfm-search-tests/main.cpp @@ -24,6 +24,7 @@ extern QObject *create_tst_ContentRetriever(); extern QObject *create_tst_ContentSearchEngine(); extern QObject *create_tst_FileNameSearchEngine(); extern QObject *create_tst_RecentSearchEngine(); +extern QObject *create_tst_CliOptions(); int main(int argc, char *argv[]) { @@ -106,5 +107,9 @@ int main(int argc, char *argv[]) result |= QTest::qExec(testObj18, argc, argv); delete testObj18; + QObject *testObj19 = create_tst_CliOptions(); + result |= QTest::qExec(testObj19, argc, argv); + delete testObj19; + return result; } diff --git a/autotests/dfm-search-tests/tst_cli_options.cpp b/autotests/dfm-search-tests/tst_cli_options.cpp new file mode 100644 index 0000000..43e15f7 --- /dev/null +++ b/autotests/dfm-search-tests/tst_cli_options.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "cli_options.h" + +using namespace dfmsearch; + +class tst_CliOptions : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testFeaturesCount(); + void testFeaturesContent(); + void testFeaturesOrder(); + void testFeaturesJsonOutput(); + void testFeaturesTextOutput(); +}; + +// --- supportedFeatures() integrity tests --- + +void tst_CliOptions::testFeaturesCount() +{ + QStringList features = CliOptions::supportedFeatures(); + QCOMPARE(features.size(), 26); +} + +void tst_CliOptions::testFeaturesContent() +{ + QStringList features = CliOptions::supportedFeatures(); + QStringList expected = { + "filename", "content", "ocr", "recent", "semantic", + "indexed", "realtime", + "simple", "boolean", "wildcard", + "preview", "pinyin", "pinyin-acronym", "case-sensitive", + "include-hidden", "file-types", "file-extensions", "exclude", + "time-filter", "size-filter", "max-results", "max-preview", + "offset", "filename-in-content", "json-output", "verbose" + }; + QCOMPARE(features, expected); +} + +void tst_CliOptions::testFeaturesOrder() +{ + QStringList features = CliOptions::supportedFeatures(); + // Verify first and last items to ensure order is preserved + QCOMPARE(features.first(), QStringLiteral("filename")); + QCOMPARE(features.last(), QStringLiteral("verbose")); + + // Verify category boundaries + QCOMPARE(features.at(4), QStringLiteral("semantic")); // last search type + QCOMPARE(features.at(6), QStringLiteral("realtime")); // last search method + QCOMPARE(features.at(9), QStringLiteral("wildcard")); // last query type + QCOMPARE(features.at(10), QStringLiteral("preview")); // first functional feature +} + +void tst_CliOptions::testFeaturesJsonOutput() +{ + // Replicate the JSON formatting logic from parse() to verify structure + QStringList features = CliOptions::supportedFeatures(); + + QJsonObject root; + root["type"] = QStringLiteral("features"); + QJsonArray featuresArray; + for (const QString &feature : features) { + featuresArray.append(feature); + } + root["features"] = featuresArray; + root["count"] = features.size(); + QJsonDocument doc(root); + + QVERIFY(doc.isObject()); + QJsonObject obj = doc.object(); + QCOMPARE(obj.value("type").toString(), QStringLiteral("features")); + QCOMPARE(obj.value("count").toInt(), 26); + + QJsonArray arr = obj.value("features").toArray(); + QCOMPARE(arr.size(), 26); + QCOMPARE(arr.at(0).toString(), QStringLiteral("filename")); + QCOMPARE(arr.at(25).toString(), QStringLiteral("verbose")); +} + +void tst_CliOptions::testFeaturesTextOutput() +{ + // Replicate the text output formatting (space-separated) from parse() + QStringList features = CliOptions::supportedFeatures(); + QString textOutput = features.join(' '); + + // Should be a single line with 25 spaces separating 26 items + QCOMPARE(textOutput.count(' '), 25); + QVERIFY(!textOutput.contains('\n')); + QVERIFY(textOutput.startsWith(QStringLiteral("filename"))); + QVERIFY(textOutput.endsWith(QStringLiteral("verbose"))); +} + +QObject *create_tst_CliOptions() +{ + return new tst_CliOptions(); +} + +#include "tst_cli_options.moc" diff --git a/src/dfm-search/dfm-search-client/cli_options.cpp b/src/dfm-search/dfm-search-client/cli_options.cpp index 0a41566..e31fe29 100644 --- a/src/dfm-search/dfm-search-client/cli_options.cpp +++ b/src/dfm-search/dfm-search-client/cli_options.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include +#include #include #include @@ -53,16 +56,36 @@ CliOptions::CliOptions() m_timeRangeOption(QStringList() << "time-range", "Custom time range (start,end)", "range"), m_sizeMinOption(QStringList() << "size-min", "Minimum file size (e.g., 1K, 10M, 1G, 512)", "size"), m_sizeMaxOption(QStringList() << "size-max", "Maximum file size (e.g., 1K, 10M, 1G, 512)", "size"), - m_versionOption(QStringList() << "version", "Show version information and exit") + m_versionOption(QStringList() << "version", "Show version information and exit"), + m_featuresOption(QStringList() << "features", "List supported features and exit") { setupOptions(); } +QStringList CliOptions::supportedFeatures() +{ + // 静态特性列表(编译时确定,26 项) + // Search types + return { + "filename", "content", "ocr", "recent", "semantic", + // Search methods + "indexed", "realtime", + // Query types + "simple", "boolean", "wildcard", + // Functional features + "preview", "pinyin", "pinyin-acronym", "case-sensitive", + "include-hidden", "file-types", "file-extensions", "exclude", + "time-filter", "size-filter", "max-results", "max-preview", + "offset", "filename-in-content", "json-output", "verbose" + }; +} + void CliOptions::setupOptions() { m_parser.setApplicationDescription("DFM Search Client"); m_parser.addHelpOption(); m_parser.addOption(m_versionOption); + m_parser.addOption(m_featuresOption); // Basic options m_parser.addOption(m_typeOption); @@ -163,6 +186,7 @@ void CliOptions::printHelp() const std::cout << "Output Options:" << std::endl; std::cout << " --json, -j Output results in JSON format" << std::endl; std::cout << " --verbose, -v Enable verbose output with detailed result information" << std::endl; + std::cout << " --features List supported features and exit" << std::endl; std::cout << " --help Display this help" << std::endl; std::cout << " --version Show version information and exit" << std::endl; std::cout << std::endl; @@ -238,6 +262,29 @@ bool CliOptions::parse(QCoreApplication &app, SearchCliConfig &config) std::exit(0); } + if (m_parser.isSet(m_featuresOption)) { + // 静态特性列表(编译时确定) + static const QStringList kFeatures = supportedFeatures(); + + if (m_parser.isSet(m_jsonOption)) { + // JSON output + QJsonObject root; + root["type"] = QStringLiteral("features"); + QJsonArray featuresArray; + for (const QString &feature : kFeatures) { + featuresArray.append(feature); + } + root["features"] = featuresArray; + root["count"] = kFeatures.size(); + QJsonDocument doc(root); + std::cout << doc.toJson(QJsonDocument::Indented).toStdString() << std::endl; + } else { + // Space-separated text output + std::cout << kFeatures.join(' ').toStdString() << std::endl; + } + std::exit(0); + } + QStringList positionalArgs = m_parser.positionalArguments(); // For preview subcommand, the positional args are: [] [path2 ...] diff --git a/src/dfm-search/dfm-search-client/cli_options.h b/src/dfm-search/dfm-search-client/cli_options.h index f783b45..379171c 100644 --- a/src/dfm-search/dfm-search-client/cli_options.h +++ b/src/dfm-search/dfm-search-client/cli_options.h @@ -90,6 +90,13 @@ class CliOptions */ void printHelp() const; + /** + * @brief 返回 --features 输出的静态特性列表(编译时确定,26 项) + * + * 提取为静态方法以便单元测试验证列表完整性,无需启动 CLI 进程。 + */ + static QStringList supportedFeatures(); + private: void setupOptions(); bool validateConfig(SearchCliConfig &config); @@ -135,6 +142,7 @@ class CliOptions QCommandLineOption m_sizeMinOption; QCommandLineOption m_sizeMaxOption; QCommandLineOption m_versionOption; + QCommandLineOption m_featuresOption; }; } // namespace dfmsearch