From 97eeb3abde067953378bbd2b0f3b33a3e8962fbe Mon Sep 17 00:00:00 2001 From: wjyrich Date: Tue, 28 Jul 2026 14:22:17 +0800 Subject: [PATCH] feat: add applet enable/disable API with DConfig support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the ability to enable or disable DApplet instances via DConfig, controlling whether their root objects appear in the parent containment model. The new `enable` property is exposed as a Q_PROPERTY on DApplet, backed by a DConfig key `enable` under `org.deepin.dde.shell`. When loading, DApplet reads this config and watches for changes. Disabled applets remain loaded but their root objects are removed from the containment's `appletItems` model; re-enabling re-inserts them. This enables runtime toggling of applet visibility without unloading/ reloading plugins. Unit tests are added for the DAppletItemModel (append/remove) and the enable property change notification logic. Log: Added DApplet enable/disable feature via DConfig Influence: 1. Test creating an applet and verifying its enable property is true by default 2. Set enable to false via dde-dconfig and verify the applet root object is removed from the containment model 3. Set enable to true and verify the root object reappears 4. Verify that setEnable with the same value does not emit enableChanged signal 5. Test loading a root plugin (like dock) - its enable should not be affected (skipped) 6. Verify DConfig changes are watched and applied dynamically at runtime 7. Run unit tests: EnablePropertyNotifiesOnlyOnChange, AppendsRootObjects, RemovesRootObject feat: 添加支持通过 DConfig 启用/禁用 Applet 的接口 新增通过 DConfig 控制 DApplet 实例启用的能力,决定其根对象是否显示在父容 器模型中。 新的 `enable` 属性作为 DApplet 的 Q_PROPERTY 暴露,由 `org.deepin.dde.shell` 下的 DConfig 键 `enable` 驱动。加载时 DApplet 读 取该配置并监听变化。 被禁用的 Applet 实例仍保持加载,但根对象从容器的 `appletItems` 模型中 移除;重新启用后会重新追加。这实现了无需卸载/重新加载插件即可运行时切换 Applet 可见性。 新增了 DAppletItemModel(追加/移除)以及 enable 属性变更通知逻辑的单元 测试。 Log: 新增 DApplet 启用/禁用功能 Influence: 1. 测试创建 Applet 并验证其 enable 属性默认为 true 2. 通过 dde-dconfig 将 enable 设为 false,验证根对象已从容器模型中移除 3. 设为 true 后验证根对象重新出现 4. 验证 setEnable 相同值时不会触发 enableChanged 信号 5. 测试加载根插件(如 dock),其 enable 不应受影响(跳过) 6. 验证 DConfig 变化能被监听并在运行时动态生效 7. 运行单元测试:EnablePropertyNotifiesOnlyOnChange、 AppendsRootObjects、RemovesRootObject PMS: TASK-393299 --- docs/plugin/API_en.md | 13 ++++ docs/plugin/API_zh_CN.md | 13 ++++ shell/CMakeLists.txt | 2 + shell/appletconfigmanager.cpp | 89 +++++++++++++++++++++++ shell/appletconfigmanager.h | 39 ++++++++++ shell/appletloader.cpp | 105 +++++++++++++++++++++++++-- shell/appletloader.h | 5 +- shell/main.cpp | 13 +++- shell/org.deepin.dde.shell.json | 11 +++ tests/CMakeLists.txt | 3 +- tests/frame/CMakeLists.txt | 63 ++++++++++++++++ tests/frame/appletitemmodeltests.cpp | 49 +++++++++++++ tests/frame/applettests.cpp | 17 +++++ tests/frame/pluginloadertests.cpp | 51 +++++++++++++ 14 files changed, 458 insertions(+), 15 deletions(-) create mode 100644 shell/appletconfigmanager.cpp create mode 100644 shell/appletconfigmanager.h create mode 100644 tests/frame/CMakeLists.txt create mode 100644 tests/frame/appletitemmodeltests.cpp create mode 100644 tests/frame/applettests.cpp create mode 100644 tests/frame/pluginloadertests.cpp diff --git a/docs/plugin/API_en.md b/docs/plugin/API_en.md index 90552fcee..9eb6fcb2e 100644 --- a/docs/plugin/API_en.md +++ b/docs/plugin/API_en.md @@ -257,6 +257,19 @@ class DS_SHARE DPluginMetaData : public QObject }; ``` +### Applet Enable Configuration + +`AppletConfigManager` in the `dde-shell` process reads `org.deepin.dde.shell / org.deepin.dde.shell / enable`, uses the applet's `Plugin.Id` as the DConfig subpath, and passes the enabled state to `DAppletLoader`. This configuration applies only to child applets that declare `Plugin.Parent`; top-level panels such as the Dock are always created by the startup flow and are not controlled by DConfig. When the value is `false`, the corresponding child applet instance is not created. Runtime configuration changes unload or reload that child applet immediately. `dde-shell-frame` does not read or store this configuration state. + +The `-d` command-line option is handled by `DPluginLoader`, which filters the corresponding applet, including a top-level panel, while scanning metadata. It has higher priority than DConfig; an applet disabled with `-d` cannot be re-enabled through DConfig during the lifetime of the process. + +For example, to disable the launchpad applet: + +```bash +dde-dconfig set -a org.deepin.dde.shell -r org.deepin.dde.shell \ + -s /org.deepin.ds.dock.launcherapplet -k enable -v false +``` + ### DAppletBridge API Bridge for communicating with applet from C++: diff --git a/docs/plugin/API_zh_CN.md b/docs/plugin/API_zh_CN.md index f4316d2a3..1b2d1a971 100644 --- a/docs/plugin/API_zh_CN.md +++ b/docs/plugin/API_zh_CN.md @@ -257,6 +257,19 @@ class DS_SHARE DPluginMetaData : public QObject }; ``` +### Applet 启用配置 + +`dde-shell` 进程中的 `AppletConfigManager` 读取 `org.deepin.dde.shell / org.deepin.dde.shell / enable`,并使用 Applet 的 `Plugin.Id` 作为 DConfig subpath,再将启用状态交给 `DAppletLoader`。该配置只作用于包含 `Plugin.Parent` 的子 Applet;Dock 等顶层 Panel 始终由启动流程创建,不受 DConfig 控制。值为 `false` 时不会创建对应的子 Applet 实例;运行时修改配置会实时卸载或重新加载对应子 Applet。`dde-shell-frame` 本身不读取或保存此配置状态。 + +命令行参数 `-d` 由 `DPluginLoader` 处理,会在扫描 metadata 时直接过滤对应 Applet,包括顶层 Panel。它的优先级高于 DConfig;在当前进程生命周期内,被 `-d` 禁用的 Applet 无法通过 DConfig 重新启用。 + +例如禁用启动器 Applet: + +```bash +dde-dconfig set -a org.deepin.dde.shell -r org.deepin.dde.shell \ + -s /org.deepin.ds.dock.launcherapplet -k enable -v false +``` + ### DAppletBridge API 从 C++ 与小部件通信的桥接: diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index c201baa5b..8f6adb1c9 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -15,6 +15,8 @@ endif() add_executable(dde-shell main.cpp + appletconfigmanager.h + appletconfigmanager.cpp appletloader.h appletloader.cpp shell.h diff --git a/shell/appletconfigmanager.cpp b/shell/appletconfigmanager.cpp new file mode 100644 index 000000000..3f65f9b45 --- /dev/null +++ b/shell/appletconfigmanager.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "appletconfigmanager.h" + +#include "pluginloader.h" + +#include +#include +#include + +DS_BEGIN_NAMESPACE + +DCORE_USE_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(dsLoaderLog) + +static constexpr auto AppId{"org.deepin.dde.shell"}; +static constexpr auto ConfigName{"org.deepin.dde.shell"}; +static constexpr auto EnableKey{"enable"}; + +AppletConfigManager::AppletConfigManager(QObject *parent) + : QObject(parent) +{ +} + +QStringList AppletConfigManager::disabledPlugins() const +{ + return m_disabledPlugins; +} + +bool AppletConfigManager::isAppletEnabled(const QString &pluginId) const +{ + return !m_disabledPlugins.contains(pluginId); +} + +void AppletConfigManager::setAppletEnabled(const QString &pluginId, bool enabled) +{ + const bool currentlyEnabled = isAppletEnabled(pluginId); + if (currentlyEnabled == enabled) + return; + + if (enabled) + m_disabledPlugins.removeAll(pluginId); + else + m_disabledPlugins.append(pluginId); + Q_EMIT appletEnabledChanged(pluginId, enabled); +} + +void AppletConfigManager::ensureAppletConfigs() +{ + auto loader = DPluginLoader::instance(); + const auto plugins = loader->plugins(); + QSet pluginIds; + for (const auto &plugin : plugins) { + // Top-level panels/applets are created by AppletManager and are not + // controlled by DConfig. Only child applets can be toggled at runtime. + if (plugin.value(QLatin1String("Parent")).toString().isEmpty()) + continue; + pluginIds.insert(plugin.pluginId()); + } + + for (const auto &pluginId : pluginIds) { + if (m_appletConfigs.contains(pluginId)) + continue; + if (!isAppletEnabled(pluginId)) + continue; + + auto config = DConfig::create(QLatin1String(AppId), + QLatin1String(ConfigName), + QLatin1Char('/') + pluginId, + this); + m_appletConfigs.insert(pluginId, config); + if (!config || !config->isValid()) { + qCWarning(dsLoaderLog) << "Unable to create applet DConfig; applet remains enabled:" << pluginId; + setAppletEnabled(pluginId, true); + continue; + } + + setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool()); + QObject::connect(config, &DConfig::valueChanged, this, [this, pluginId, config](const QString &key) { + if (key == QLatin1String(EnableKey)) + setAppletEnabled(pluginId, config->value(QLatin1String(EnableKey), true).toBool()); + }); + } +} + +DS_END_NAMESPACE diff --git a/shell/appletconfigmanager.h b/shell/appletconfigmanager.h new file mode 100644 index 000000000..4dbb0fd7d --- /dev/null +++ b/shell/appletconfigmanager.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dsglobal.h" + +#include +#include +#include + +namespace Dtk::Core { +class DConfig; +} + +DS_BEGIN_NAMESPACE + +class AppletConfigManager : public QObject +{ + Q_OBJECT +public: + explicit AppletConfigManager(QObject *parent = nullptr); + + QStringList disabledPlugins() const; + bool isAppletEnabled(const QString &pluginId) const; + void ensureAppletConfigs(); + +Q_SIGNALS: + void appletEnabledChanged(const QString &pluginId, bool enabled); + +private: + void setAppletEnabled(const QString &pluginId, bool enabled); + + QMap m_appletConfigs; + QStringList m_disabledPlugins; +}; + +DS_END_NAMESPACE diff --git a/shell/appletloader.cpp b/shell/appletloader.cpp index 3564f91a0..ef12e2566 100644 --- a/shell/appletloader.cpp +++ b/shell/appletloader.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 -2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "appletloader.h" +#include "appletconfigmanager.h" #include "pluginloader.h" #include "applet.h" #include "containment.h" @@ -84,22 +85,35 @@ class DAppletLoaderPrivate : public DObjectPrivate bool init(DApplet *applet); void createChildren(DApplet *applet); + void handleAppletEnabledChanged(const QString &pluginId, bool enabled); + void createEnabledApplets(DApplet *applet, const QString &pluginId); + void removeDisabledApplets(DApplet *applet, const QString &pluginId); void loadTranslation(const DPluginMetaData &pluginData); void removeTranslation(const QString &pluginId); + void removeTranslations(const QString &pluginId); QPointer m_applet = nullptr; + QPointer m_configManager = nullptr; QMap m_pluginTranslators; D_DECLARE_PUBLIC(DAppletLoader); }; -DAppletLoader::DAppletLoader(class DApplet *applet, QObject *parent) +DAppletLoader::DAppletLoader(DApplet *applet, AppletConfigManager *configManager, QObject *parent) : QObject(parent) , DObject(*new DAppletLoaderPrivate(this)) { D_D(DAppletLoader); d->m_applet = applet; + d->m_configManager = configManager; + Q_ASSERT(configManager); + QObject::connect(configManager, + &AppletConfigManager::appletEnabledChanged, + this, + [d](const QString &pluginId, bool enabled) { + d->handleAppletEnabledChanged(pluginId, enabled); + }); } DAppletLoader::~DAppletLoader() @@ -115,10 +129,10 @@ void DAppletLoader::exec() if (!d->load(d->m_applet)) return; - d->createRootObject(d->m_applet); - if (!d->init(d->m_applet)) return; + + d->createRootObject(d->m_applet); } DApplet *DAppletLoader::applet() const @@ -146,10 +160,11 @@ void DAppletLoaderPrivate::doCreateRootObject(DApplet *applet) } }); - qCDebug(dsLoaderLog) << "Begin to create rootObject the applet:" << applet->pluginId(); - if (!engine->create()) { - engine->deleteLater(); - } + QMetaObject::invokeMethod(engine, [engine]() { + if (!engine->create()) { + engine->deleteLater(); + } + }, Qt::QueuedConnection); } bool DAppletLoaderPrivate::doLoad(DApplet *applet) @@ -190,6 +205,8 @@ void DAppletLoaderPrivate::createChildren(DApplet *applet) const auto data = applet->appletData(); auto groups = groupList(applet, data); for (const auto &item : std::as_const(groups)) { + if (!m_configManager->isAppletEnabled(item.pluginId())) + continue; auto child = containment->createApplet(item); if (!child) { @@ -199,6 +216,67 @@ void DAppletLoaderPrivate::createChildren(DApplet *applet) } } +void DAppletLoaderPrivate::handleAppletEnabledChanged(const QString &pluginId, bool enabled) +{ + if (!m_applet) + return; + + if (enabled) { + createEnabledApplets(m_applet, pluginId); + return; + } + + removeDisabledApplets(m_applet, pluginId); + removeTranslations(pluginId); +} + +void DAppletLoaderPrivate::createEnabledApplets(DApplet *applet, const QString &pluginId) +{ + auto containment = qobject_cast(applet); + if (!containment) + return; + + const auto groups = groupList(applet, applet->appletData()); + for (const auto &data : groups) { + if (data.pluginId() != pluginId) + continue; + + auto child = containment->createApplet(data); + if (!child) + continue; + + loadTranslation(child->pluginMetaData()); + if (!load(child) || !init(child)) { + removeTranslations(pluginId); + continue; + } + createRootObject(child); + } + + const auto children = containment->applets(); + for (auto child : children) { + if (child->pluginId() == pluginId) + continue; + createEnabledApplets(child, pluginId); + } +} + +void DAppletLoaderPrivate::removeDisabledApplets(DApplet *applet, const QString &pluginId) +{ + auto containment = qobject_cast(applet); + if (!containment) + return; + + const auto children = containment->applets(); + for (auto child : children) { + if (child->pluginId() == pluginId) { + containment->removeApplet(child); + continue; + } + removeDisabledApplets(child, pluginId); + } +} + bool DAppletLoaderPrivate::load(DApplet *applet) { if (!doLoad(applet)) { @@ -248,6 +326,9 @@ void DAppletLoaderPrivate::loadTranslation(const DPluginMetaData &pluginData) const QString baseDir = pluginData.pluginDir(); const QString pluginId = pluginData.pluginId(); + if (!m_configManager->isAppletEnabled(pluginId)) + return; + auto translator = new QTranslator(qApp); const QString pluginTranslationDir(baseDir + "/translations/"); if (translator->load(QLocale::system(), pluginId, QLatin1String("_"), pluginTranslationDir)) { @@ -275,4 +356,12 @@ void DAppletLoaderPrivate::removeTranslation(const QString &pluginId) } } +void DAppletLoaderPrivate::removeTranslations(const QString &pluginId) +{ + const auto children = DPluginLoader::instance()->childrenPlugin(pluginId); + for (const auto &child : children) + removeTranslations(child.pluginId()); + removeTranslation(pluginId); +} + DS_END_NAMESPACE diff --git a/shell/appletloader.h b/shell/appletloader.h index 5f088ded4..01e185b9f 100644 --- a/shell/appletloader.h +++ b/shell/appletloader.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -12,13 +12,14 @@ DS_BEGIN_NAMESPACE class DApplet; +class AppletConfigManager; class DAppletLoaderPrivate; class DAppletLoader : public QObject, public DTK_CORE_NAMESPACE::DObject { Q_OBJECT D_DECLARE_PRIVATE(DAppletLoader) public: - explicit DAppletLoader(DApplet *applet, QObject *parent = nullptr); + explicit DAppletLoader(DApplet *applet, AppletConfigManager *configManager, QObject *parent = nullptr); virtual ~DAppletLoader() override; void exec(); diff --git a/shell/main.cpp b/shell/main.cpp index eebd0069c..9fdbc0a94 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -19,6 +19,7 @@ #include "applet.h" #include "containment.h" #include "pluginloader.h" +#include "appletconfigmanager.h" #include "appletloader.h" #include "qmlengine.h" #include "shell.h" @@ -49,9 +50,11 @@ static void disableLogOutput() class AppletManager { public: - explicit AppletManager(const QStringList &pluginIds) + explicit AppletManager(const QStringList &pluginIds, AppletConfigManager *configManager) { qCDebug(dsLog) << "Preloading plugins:" << pluginIds; + Q_ASSERT(configManager); + auto rootApplet = qobject_cast(DPluginLoader::instance()->rootApplet()); Q_ASSERT(rootApplet); @@ -62,9 +65,8 @@ class AppletManager continue; } - auto loader = new DAppletLoader(applet); + auto loader = new DAppletLoader(applet, configManager); m_loaders << loader; - QObject::connect(loader, &DAppletLoader::failed, qApp, [this, loader, pluginIds](const QString &pluginId) { if (pluginIds.contains(pluginId)) { m_loaders.removeOne(loader); @@ -90,6 +92,7 @@ class AppletManager item->deleteLater(); } } + QList m_loaders; }; @@ -200,11 +203,13 @@ int main(int argc, char *argv[]) } shell.dconfigsMigrate(); + AppletConfigManager appletConfigManager; + appletConfigManager.ensureAppletConfigs(); // TODO disable qml's cache avoid to parsing error for ExecutionEngine. shell.disableQmlCache(); shell.setFlickableWheelDeceleration(6000); - AppletManager manager(pluginIds); + AppletManager manager(pluginIds, &appletConfigManager); if (parser.isSet(sceneviewOption)) manager.enableSceneview(); diff --git a/shell/org.deepin.dde.shell.json b/shell/org.deepin.dde.shell.json index 55af2a67a..9c2b926ae 100644 --- a/shell/org.deepin.dde.shell.json +++ b/shell/org.deepin.dde.shell.json @@ -11,6 +11,17 @@ "description": "DConfig that has completed migration", "permissions": "readwrite", "visibility": "private" + }, + "enable": { + "value": true, + "serial": 0, + "flags": [], + "name": "Enable Applet", + "name[zh_CN]": "启用 Applet", + "description": "Enable or disable the dde-shell applet selected by the DConfig subpath.", + "description[zh_CN]": "启用或禁用由 DConfig subpath 指定的 dde-shell Applet。", + "permissions": "readwrite", + "visibility": "private" } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77e6bee23..17d7acad5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: CC0-1.0 add_subdirectory(panels) +add_subdirectory(frame) diff --git a/tests/frame/CMakeLists.txt b/tests/frame/CMakeLists.txt new file mode 100644 index 000000000..21a5c36c0 --- /dev/null +++ b/tests/frame/CMakeLists.txt @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: CC0-1.0 + +find_package(GTest REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core) + +include(GoogleTest) + +add_executable(appletitemmodel_tests + ${CMAKE_SOURCE_DIR}/frame/appletitemmodel.cpp + ${CMAKE_SOURCE_DIR}/frame/appletitemmodel.h + appletitemmodeltests.cpp +) + +target_link_libraries(appletitemmodel_tests + GTest::GTest + GTest::Main + Qt${QT_VERSION_MAJOR}::Core +) + +target_include_directories(appletitemmodel_tests PRIVATE + ${CMAKE_SOURCE_DIR}/frame +) + +gtest_discover_tests(appletitemmodel_tests) + +add_executable(applet_tests + applettests.cpp +) + +target_link_libraries(applet_tests + dde-shell-frame + GTest::GTest + GTest::Main + Qt${QT_VERSION_MAJOR}::Core +) + +add_test( + NAME DApplet.DoesNotExposeEnableProperty + COMMAND ${CMAKE_COMMAND} -E env + LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/frame + $ + --gtest_filter=DApplet.DoesNotExposeEnableProperty +) + +add_executable(pluginloader_tests + pluginloadertests.cpp +) + +target_link_libraries(pluginloader_tests + dde-shell-frame + GTest::GTest + Qt${QT_VERSION_MAJOR}::Core +) + +add_test( + NAME DPluginLoader.DisabledAppletIsExcludedFromMetadata + COMMAND ${CMAKE_COMMAND} -E env + LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/frame + $ + --gtest_filter=DPluginLoader.DisabledAppletIsExcludedFromMetadata +) diff --git a/tests/frame/appletitemmodeltests.cpp b/tests/frame/appletitemmodeltests.cpp new file mode 100644 index 000000000..dce36d16a --- /dev/null +++ b/tests/frame/appletitemmodeltests.cpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "appletitemmodel.h" + +#include + +DS_USE_NAMESPACE + +namespace { +QObject *rootObjectAt(const DAppletItemModel &model, int row) +{ + return model.data(model.index(row, 0), DAppletItemModel::Data).value(); +} +} + +TEST(DAppletItemModel, AppendsRootObjects) +{ + DAppletItemModel model; + QObject first; + QObject second; + QObject third; + + model.append(&first); + model.append(&second); + model.append(&third); + + ASSERT_EQ(model.rowCount(QModelIndex()), 3); + EXPECT_EQ(rootObjectAt(model, 0), &first); + EXPECT_EQ(rootObjectAt(model, 1), &second); + EXPECT_EQ(rootObjectAt(model, 2), &third); +} + +TEST(DAppletItemModel, RemovesRootObject) +{ + DAppletItemModel model; + QObject first; + QObject middle; + QObject last; + + model.append(&first); + model.append(&middle); + model.append(&last); + model.remove(&middle); + + ASSERT_EQ(model.rowCount(QModelIndex()), 2); + EXPECT_EQ(model.rootObjects(), QList({&first, &last})); +} diff --git a/tests/frame/applettests.cpp b/tests/frame/applettests.cpp new file mode 100644 index 000000000..01846aa0f --- /dev/null +++ b/tests/frame/applettests.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "applet.h" + +#include + +DS_USE_NAMESPACE + +TEST(DApplet, DoesNotExposeEnableProperty) +{ + DApplet applet; + + EXPECT_EQ(applet.metaObject()->indexOfProperty("enable"), -1); + EXPECT_EQ(applet.metaObject()->indexOfSignal("enableChanged(bool)"), -1); +} diff --git a/tests/frame/pluginloadertests.cpp b/tests/frame/pluginloadertests.cpp new file mode 100644 index 000000000..e91f0bde7 --- /dev/null +++ b/tests/frame/pluginloadertests.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "pluginloader.h" + +#include +#include +#include +#include + +#include + +DS_USE_NAMESPACE + +TEST(DPluginLoader, DisabledAppletIsExcludedFromMetadata) +{ + QTemporaryDir packageRoot; + ASSERT_TRUE(packageRoot.isValid()); + + const QString pluginId = QStringLiteral("org.deepin.ds.test.disabled-applet"); + const QString packageDir = packageRoot.filePath(pluginId); + ASSERT_TRUE(QDir().mkpath(packageDir)); + + QFile metadataFile(packageDir + QStringLiteral("/metadata.json")); + ASSERT_TRUE(metadataFile.open(QIODevice::WriteOnly)); + metadataFile.write(R"({ + "Plugin": { + "Version": "1.0", + "Id": "org.deepin.ds.test.disabled-applet", + "Url": "main.qml" + } + })"); + metadataFile.close(); + + auto loader = DPluginLoader::instance(); + loader->addPackageDir(packageRoot.path()); + ASSERT_TRUE(loader->plugin(pluginId).isValid()); + + loader->setDisabledApplets({pluginId}); + + EXPECT_TRUE(loader->disabledApplets().contains(pluginId)); + EXPECT_FALSE(loader->plugin(pluginId).isValid()); +} + +int main(int argc, char **argv) +{ + QCoreApplication application(argc, argv); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}