Skip to content

Commit 522e702

Browse files
Add Windows toast cleanup to test utils
Implement Windows-specific notification cleanup for tests: add WinRT/UIAutomation helpers (clearToastNotificationHistory, closeVisibleToastNotifications, releaseCom, isCloseButtonInToastArea, invokeButton) and update dismissNativeNotifications to use them. Add required Windows headers/includes and link additional Windows libs (ole32, oleaut32, runtimeobject, uiautomationcore) in tests/CMakeLists.txt. Also simplify the README icon support line and replace an explicit sleep in waitForNativeNotificationTimeout with a call to dismissNativeNotifications.
1 parent 6fdb6b6 commit 522e702

7 files changed

Lines changed: 244 additions & 11 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
134134
endif()
135135

136136
if(BUILD_TESTS)
137+
target_compile_definitions(${PROJECT_NAME} PRIVATE TRAY_ENABLE_TEST_HOOKS=1)
138+
137139
#
138140
# Additional setup for coverage
139141
# https://gcovr.com/en/stable/guide/compiling.html#compiler-options

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,7 @@ The `icon` and `notification_icon` fields can be a path to an image file or an i
133133
are resolved from the process working directory, so applications should copy or install icon files where the running
134134
process can find them.
135135

136-
| Component | Backend | Supported inputs | Notes |
137-
|-----------------------------------------|--------------------------------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
138-
| Tray icon (`icon`) | Qt `QSystemTrayIcon` / `QIcon` on all platforms | SVG, ICO, PNG, Qt theme icon names | Loaded through Qt's `QIcon` path; SVG, ICO, and PNG are tested. Theme icon names are resolved by Qt when the platform/theme supports them. |
139-
| Notification icon (`notification_icon`) | Qt `QSystemTrayIcon::showMessage` / `QIcon` on all platforms | SVG, ICO, PNG, Qt theme icon names | Loaded through Qt's `QIcon` path; SVG, ICO, and PNG are tested. Theme icon names are resolved by Qt when the platform/theme supports them. |
136+
SVG, ICO, PNG, and Qt theme icon names are supported.
140137

141138
For the most predictable cross-platform behavior, use SVG or PNG files for both tray and notification icons. ICO is
142139
supported by the Qt-backed paths tested by this project.

src/QtTrayMenu.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
// qt includes
99
#include <QApplication>
10+
#include <QByteArray>
1011
#include <QCursor>
1112
#include <QDebug>
1213
#include <QMouseEvent>
@@ -19,6 +20,12 @@ namespace {
1920
int defaultArgc = 1; // NOSONAR(cpp:S5421): This is required for QApplication's argc/argv constructor
2021
char defaultArgv0[] = "TrayMenuApp"; // NOSONAR(cpp:S5421): This is required for QApplication's argc/argv constructor
2122
char *defaultArgv[] = {defaultArgv0, nullptr}; // NOSONAR(cpp:S5421,cpp:S5954): This is required for QApplication's argc/argv constructor
23+
24+
#ifdef TRAY_ENABLE_TEST_HOOKS
25+
bool nativeNotificationsSuppressed() {
26+
return qgetenv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS") == QByteArrayLiteral("1");
27+
}
28+
#endif
2229
} // namespace
2330

2431
QtTrayMenu::QtTrayMenu(QObject *parent, const bool debug):
@@ -209,11 +216,20 @@ void QtTrayMenu::createNotification() {
209216

210217
QIcon QtTrayMenu::lookupIcon(QString icon) const {
211218
// Find icon for tray
212-
if (std::filesystem::exists(icon.toStdString())) {
219+
const auto icon_path = std::filesystem::path(icon.toStdString());
220+
if (std::filesystem::exists(icon_path)) {
213221
if (auto result = QIcon(icon); !result.isNull()) {
214222
return result;
215223
}
216224
}
225+
if (icon_path.is_relative()) {
226+
const auto app_icon_path = std::filesystem::path(QApplication::applicationDirPath().toStdString()) / icon_path;
227+
if (std::filesystem::exists(app_icon_path)) {
228+
if (auto result = QIcon(QString::fromStdString(app_icon_path.string())); !result.isNull()) {
229+
return result;
230+
}
231+
}
232+
}
217233
if (auto result = QIcon::fromTheme(icon); !result.isNull()) {
218234
return result;
219235
}
@@ -310,6 +326,11 @@ void QtTrayMenu::showMessage(const QString &title, const QString &msg, std::func
310326
}
311327
if (QSystemTrayIcon::supportsMessages()) {
312328
notificationCallback = std::move(callback);
329+
#ifdef TRAY_ENABLE_TEST_HOOKS
330+
if (nativeNotificationsSuppressed()) {
331+
return;
332+
}
333+
#endif
313334
emit trayIcon->showMessage(title, msg, icon, msecs);
314335
}
315336
}
@@ -320,6 +341,11 @@ void QtTrayMenu::showMessage(const QString &title, const QString &msg, const QSt
320341
}
321342
if (QSystemTrayIcon::supportsMessages()) {
322343
notificationCallback = std::move(callback);
344+
#ifdef TRAY_ENABLE_TEST_HOOKS
345+
if (nativeNotificationsSuppressed()) {
346+
return;
347+
}
348+
#endif
323349
emit trayIcon->showMessage(title, msg, lookupIcon(iconPath), msecs);
324350
}
325351
}

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ include_directories("${GTEST_SOURCE_DIR}/googletest/include" "${GTEST_SOURCE_DIR
2020
if (APPLE)
2121
set(TEST_LIBS "-framework Cocoa")
2222
elseif (WIN32)
23-
set(TEST_LIBS gdi32 gdiplus)
23+
set(TEST_LIBS gdi32 gdiplus ole32 oleaut32 runtimeobject uiautomationcore)
2424
endif()
2525

2626
file(GLOB_RECURSE TEST_SOURCES

tests/unit/test_tray.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ namespace {
4646
return info.param.name;
4747
}
4848

49+
class NativeNotificationSuppression {
50+
public:
51+
NativeNotificationSuppression() {
52+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "1");
53+
}
54+
55+
~NativeNotificationSuppression() {
56+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "1");
57+
}
58+
};
59+
60+
class NativeNotificationDisplay {
61+
public:
62+
NativeNotificationDisplay() {
63+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "0");
64+
}
65+
66+
~NativeNotificationDisplay() {
67+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "1");
68+
}
69+
};
70+
4971
void PrintTo(const TrayIconParam &param, std::ostream *os) {
5072
*os << param.name;
5173
}
@@ -169,6 +191,8 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must
169191
void SetUp() override {
170192
BaseTest::SetUp();
171193

194+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "1");
195+
172196
// Wire up callbacks (file-scope arrays can't use addresses of class statics at init time)
173197
g_submenu[0].cb = hello_cb;
174198
g_submenu[1].cb = toggle_cb;
@@ -235,6 +259,7 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must
235259

236260
void TearDown() override {
237261
ShutdownTray();
262+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "1");
238263
BaseTest::TearDown();
239264
}
240265

@@ -272,6 +297,7 @@ TEST_P(TrayIconTest, TestTrayIconDisplay) {
272297
trayRunning = (result == 0);
273298
EXPECT_EQ(result, 0);
274299
WaitForTrayReady();
300+
dismissNativeNotifications();
275301
EXPECT_TRUE(captureScreenshot(std::string("tray_icon_") + iconParam.name));
276302
}
277303

@@ -372,6 +398,7 @@ TEST_P(TrayNotificationIconTest, TestNotificationDisplay) {
372398
if (const std::string skipReason = nativeNotificationSkipReason(); !skipReason.empty()) {
373399
GTEST_SKIP() << skipReason;
374400
}
401+
NativeNotificationDisplay displayNativeNotifications;
375402

376403
const auto &iconParam = GetParam();
377404
testTray.icon = iconParam.icon;
@@ -382,7 +409,8 @@ TEST_P(TrayNotificationIconTest, TestNotificationDisplay) {
382409
dismissNativeNotifications();
383410

384411
// Set notification properties
385-
testTray.notification_title = "Test Notification";
412+
const std::string notificationTitle = std::string("Test Notification ") + iconParam.name;
413+
testTray.notification_title = notificationTitle.c_str();
386414
testTray.notification_text = "This is a test notification message";
387415
testTray.notification_icon = iconParam.icon;
388416

@@ -404,6 +432,8 @@ TEST_P(TrayNotificationIconTest, TestNotificationCallback) {
404432
GTEST_SKIP() << skipReason;
405433
}
406434

435+
NativeNotificationSuppression suppressNativeNotifications;
436+
407437
const auto &iconParam = GetParam();
408438
testTray.icon = iconParam.icon;
409439

@@ -605,6 +635,8 @@ TEST_F(TrayTest, TestMenuAppearsOnLeftClick) {
605635
TEST_P(TrayNotificationIconTest, TestNotificationCallbackFiredOnClick) {
606636
// Regression test for: clicking a notification did not invoke the callback.
607637
// The test hook exercises the same stored callback used by Qt's messageClicked signal.
638+
NativeNotificationSuppression suppressNativeNotifications;
639+
608640
const auto &iconParam = GetParam();
609641
testTray.icon = iconParam.icon;
610642
static bool callbackInvoked = false;
@@ -639,6 +671,8 @@ TEST_P(TrayNotificationIconTest, TestNotificationCallbackFiredOnClick) {
639671
}
640672

641673
TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
674+
NativeNotificationSuppression suppressNativeNotifications;
675+
642676
static int callbackCount = 0;
643677
callbackCount = 0;
644678

tests/unit/test_tray_qt.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class TrayQtCoverageTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture mem
4444
void SetUp() override {
4545
BaseTest::SetUp();
4646

47+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "1");
48+
4749
tray_set_log_callback(nullptr);
4850
tray_set_app_info(nullptr, nullptr, nullptr);
4951

@@ -76,6 +78,8 @@ class TrayQtCoverageTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture mem
7678
trayRunning = false;
7779
}
7880

81+
setEnv("TRAY_TEST_SUPPRESS_NATIVE_NOTIFICATIONS", "0");
82+
7983
tray_set_log_callback(nullptr);
8084
BaseTest::TearDown();
8185
}

0 commit comments

Comments
 (0)