55#include < array>
66#include < atomic>
77#include < chrono>
8+ #include < ostream>
9+ #include < string>
810#include < thread>
911
1012#if defined(_WIN32) || defined(_WIN64)
1517#include " src/tray.h"
1618#include " tests/screenshot_utils.h"
1719
18- constexpr const char *TRAY_ICON1 = " icon.png " ;
19- constexpr const char *TRAY_ICON2 = " icon2 .png" ;
20+ constexpr const char *TRAY_ICON_ICO = " icon.ico " ;
21+ constexpr const char *TRAY_ICON_PNG = " icon .png" ;
2022constexpr const char *TRAY_ICON_SVG = " icon.svg" ;
23+ constexpr const char *TRAY_ICON2_ICO = " icon2.ico" ;
24+ constexpr const char *TRAY_ICON2_PNG = " icon2.png" ;
25+ constexpr const char *TRAY_ICON2_SVG = " icon2.svg" ;
2126constexpr const char *TRAY_ICON_THEMED = " mail-message-new" ;
27+ constexpr const char *TRAY_ICON1 = TRAY_ICON_PNG ;
28+ constexpr const char *TRAY_ICON2 = TRAY_ICON2_PNG ;
2229
2330// File-scope tray data shared across all TrayTest instances
2431namespace {
32+ struct TrayIconParam {
33+ const char *name;
34+ const char *icon;
35+ const char *alternateIcon;
36+ };
37+
38+ constexpr std::array<TrayIconParam, 4 > TRAY_ICON_PARAMS {
39+ {{" svg" , TRAY_ICON_SVG , TRAY_ICON2_SVG },
40+ {" ico" , TRAY_ICON_ICO , TRAY_ICON2_ICO },
41+ {" png" , TRAY_ICON_PNG , TRAY_ICON2_PNG },
42+ {" themed" , TRAY_ICON_THEMED , TRAY_ICON_THEMED }}
43+ };
44+
45+ std::string trayIconParamName (const ::testing::TestParamInfo<TrayIconParam> &info) {
46+ return info.param .name ;
47+ }
48+
49+ void PrintTo (const TrayIconParam ¶m, std::ostream *os) {
50+ *os << param.name ;
51+ }
52+
53+ std::string nativeNotificationSkipReason () {
54+ #if defined(_WIN32)
55+ QUERY_USER_NOTIFICATION_STATE notification_state;
56+ if (const HRESULT ns = SHQueryUserNotificationState (¬ification_state); ns != S_OK || notification_state != QUNS_ACCEPTS_NOTIFICATIONS ) {
57+ return " Notifications not accepted in this environment. SHQueryUserNotificationState result: " + std::to_string (ns) + " , state: " + std::to_string (notification_state);
58+ }
59+ #endif
60+
61+ return {};
62+ }
63+
2564 struct tray_menu g_submenu7_8[] = { // NOSONAR(cpp:S5945, cpp:S5421) - C-style array with null sentinel required by tray C API; mutable for runtime callback assignment
2665 {.text = " 7" , .cb = nullptr },
2766 {.text = " -" },
@@ -172,8 +211,16 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must
172211 }
173212 };
174213
175- ensureIconInTestDir (TRAY_ICON1 );
176- ensureIconInTestDir (TRAY_ICON_SVG );
214+ auto ensureFileIconInTestDir = [&ensureIconInTestDir](const char *iconName) {
215+ if (std::filesystem::path (iconName).has_extension ()) {
216+ ensureIconInTestDir (iconName);
217+ }
218+ };
219+
220+ for (const auto &iconParam : TRAY_ICON_PARAMS ) {
221+ ensureFileIconInTestDir (iconParam.icon );
222+ ensureFileIconInTestDir (iconParam.alternateIcon );
223+ }
177224
178225 trayRunning = false ;
179226 testTray.icon = TRAY_ICON1 ;
@@ -201,6 +248,14 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must
201248 }
202249};
203250
251+ class TrayIconTest :
252+ public TrayTest,
253+ public ::testing::WithParamInterface<TrayIconParam> {};
254+
255+ class TrayNotificationIconTest :
256+ public TrayTest,
257+ public ::testing::WithParamInterface<TrayIconParam> {};
258+
204259TEST_F (TrayTest, TestTrayInit) {
205260 int result = tray_init (&testTray);
206261 trayRunning = (result == 0 );
@@ -209,6 +264,17 @@ TEST_F(TrayTest, TestTrayInit) {
209264 EXPECT_TRUE (captureScreenshot (" tray_icon_initial" ));
210265}
211266
267+ TEST_P (TrayIconTest, TestTrayIconDisplay) {
268+ const auto &iconParam = GetParam ();
269+ testTray.icon = iconParam.icon ;
270+
271+ int result = tray_init (&testTray);
272+ trayRunning = (result == 0 );
273+ EXPECT_EQ (result, 0 );
274+ WaitForTrayReady ();
275+ EXPECT_TRUE (captureScreenshot (std::string (" tray_icon_" ) + iconParam.name ));
276+ }
277+
212278TEST_F (TrayTest, TestTrayLoop) {
213279 int initResult = tray_init (&testTray);
214280 trayRunning = (initResult == 0 );
@@ -302,14 +368,12 @@ TEST_F(TrayTest, TestSubmenuCallback) {
302368 testTray.menu [4 ].submenu [0 ].submenu [0 ].cb (&testTray.menu [4 ].submenu [0 ].submenu [0 ]);
303369}
304370
305- TEST_F (TrayTest, TestNotificationDisplay) {
306- #if defined(_WIN32)
307- QUERY_USER_NOTIFICATION_STATE notification_state;
308- if (HRESULT ns = SHQueryUserNotificationState (¬ification_state);
309- ns != S_OK || notification_state != QUNS_ACCEPTS_NOTIFICATIONS ) {
310- GTEST_SKIP () << " Notifications not accepted in this environment. SHQueryUserNotificationState result: " << ns << " , state: " << notification_state;
371+ TEST_P (TrayNotificationIconTest, TestNotificationDisplay) {
372+ if (const std::string skipReason = nativeNotificationSkipReason (); !skipReason.empty ()) {
373+ GTEST_SKIP () << skipReason;
311374 }
312- #endif
375+
376+ const auto &iconParam = GetParam ();
313377
314378 int initResult = tray_init (&testTray);
315379 trayRunning = (initResult == 0 );
@@ -318,12 +382,12 @@ TEST_F(TrayTest, TestNotificationDisplay) {
318382 // Set notification properties
319383 testTray.notification_title = " Test Notification" ;
320384 testTray.notification_text = " This is a test notification message" ;
321- testTray.notification_icon = TRAY_ICON1 ;
385+ testTray.notification_icon = iconParam. icon ;
322386
323387 tray_update (&testTray);
324388
325389 WaitForTrayReady ();
326- EXPECT_TRUE (captureScreenshot (" tray_notification_displayed " ));
390+ EXPECT_TRUE (captureScreenshot (std::string ( " tray_notification_ " ) + iconParam. name + " _icon " ));
327391
328392 // Clear notification
329393 testTray.notification_title = nullptr ;
@@ -333,14 +397,12 @@ TEST_F(TrayTest, TestNotificationDisplay) {
333397 waitForNativeNotificationTimeout ();
334398}
335399
336- TEST_F (TrayTest, TestNotificationCallback) {
337- #if defined(_WIN32)
338- QUERY_USER_NOTIFICATION_STATE notification_state;
339- if (HRESULT ns = SHQueryUserNotificationState (¬ification_state);
340- ns != S_OK || notification_state != QUNS_ACCEPTS_NOTIFICATIONS ) {
341- GTEST_SKIP () << " Notifications not accepted in this environment. SHQueryUserNotificationState result: " << ns << " , state: " << notification_state;
400+ TEST_P (TrayNotificationIconTest, TestNotificationCallback) {
401+ if (const std::string skipReason = nativeNotificationSkipReason (); !skipReason.empty ()) {
402+ GTEST_SKIP () << skipReason;
342403 }
343- #endif
404+
405+ const auto &iconParam = GetParam ();
344406
345407 static bool callbackInvoked = false ;
346408 auto notification_callback = []() {
@@ -354,7 +416,7 @@ TEST_F(TrayTest, TestNotificationCallback) {
354416 // Set notification with callback
355417 testTray.notification_title = " Clickable Notification" ;
356418 testTray.notification_text = " Click this notification to test callback" ;
357- testTray.notification_icon = TRAY_ICON1 ;
419+ testTray.notification_icon = iconParam. icon ;
358420 testTray.notification_cb = notification_callback;
359421
360422 tray_update (&testTray);
@@ -423,6 +485,8 @@ TEST_F(TrayTest, TestMenuItemContext) {
423485}
424486
425487TEST_F (TrayTest, TestCheckboxStates) {
488+ testTray.icon = TRAY_ICON_SVG ;
489+
426490 int initResult = tray_init (&testTray);
427491 trayRunning = (initResult == 0 );
428492 ASSERT_EQ (initResult, 0 );
@@ -503,6 +567,8 @@ TEST_F(TrayTest, TestQuitCallback) {
503567}
504568
505569TEST_F (TrayTest, TestTrayShowMenu) {
570+ testTray.icon = TRAY_ICON_SVG ;
571+
506572 int initResult = tray_init (&testTray);
507573 trayRunning = (initResult == 0 );
508574 ASSERT_EQ (initResult, 0 );
@@ -515,61 +581,24 @@ TEST_F(TrayTest, TestTrayExit) {
515581 tray_exit ();
516582}
517583
518- TEST_F (TrayTest, TestTrayIconThemed) {
519- testTray.icon = TRAY_ICON_THEMED ;
520- int result = tray_init (&testTray);
521- trayRunning = (result == 0 );
522- ASSERT_EQ (result, 0 );
523- WaitForTrayReady ();
524- EXPECT_TRUE (captureScreenshot (" tray_icon_themed" ));
525- testTray.icon = TRAY_ICON1 ;
526- }
527-
528- TEST_F (TrayTest, TestTrayIconSvgFile) {
529- testTray.icon = TRAY_ICON_SVG ;
530- int result = tray_init (&testTray);
531- trayRunning = (result == 0 );
532- ASSERT_EQ (result, 0 );
533- WaitForTrayReady ();
534- EXPECT_TRUE (captureScreenshot (" tray_icon_svg" ));
535- testTray.icon = TRAY_ICON1 ;
536- }
537-
538- TEST_F (TrayTest, TestNotificationWithThemedIcon) {
539- int initResult = tray_init (&testTray);
540- trayRunning = (initResult == 0 );
541- ASSERT_EQ (initResult, 0 );
542-
543- testTray.notification_title = " Test Notification" ;
544- testTray.notification_text = " This is a test notification message" ;
545- testTray.notification_icon = TRAY_ICON_THEMED ;
546- tray_update (&testTray);
547-
548- WaitForTrayReady ();
549- EXPECT_TRUE (captureScreenshot (" tray_notification_themed_icon" ));
550-
551- testTray.notification_title = nullptr ;
552- testTray.notification_text = nullptr ;
553- testTray.notification_icon = nullptr ;
554- tray_update (&testTray);
555- waitForNativeNotificationTimeout ();
556- }
557-
558584TEST_F (TrayTest, TestMenuAppearsOnLeftClick) {
559585 // Regression test for: clicking the tray icon did not bring up the menu.
560586 // The activated(Trigger) signal was not connected to the menu popup logic.
561587 // tray_show_menu() exercises the same code path that the activated handler calls.
588+ testTray.icon = TRAY_ICON_SVG ;
589+
562590 int initResult = tray_init (&testTray);
563591 trayRunning = (initResult == 0 );
564592 ASSERT_EQ (initResult, 0 );
565593
566594 captureMenuStateAndExit (" tray_menu_left_click" ); // NOSONAR(cpp:S6168) - helper uses std::thread for AppleClang 17 compatibility
567595}
568596
569- TEST_F (TrayTest , TestNotificationCallbackFiredOnClick) {
597+ TEST_P (TrayNotificationIconTest , TestNotificationCallbackFiredOnClick) {
570598 // Regression test for: clicking a notification did not invoke the callback.
571599 // On the D-Bus path, QSystemTrayIcon::messageClicked is never emitted; the
572600 // callback must be routed through TrayNotificationHandler::onActionInvoked.
601+ const auto &iconParam = GetParam ();
573602 static bool callbackInvoked = false ;
574603 callbackInvoked = false ;
575604
@@ -579,7 +608,7 @@ TEST_F(TrayTest, TestNotificationCallbackFiredOnClick) {
579608
580609 testTray.notification_title = " Clickable Notification" ;
581610 testTray.notification_text = " Click to test callback" ;
582- testTray.notification_icon = TRAY_ICON1 ;
611+ testTray.notification_icon = iconParam. icon ;
583612 testTray.notification_cb = []() {
584613 callbackInvoked = true ;
585614 };
@@ -623,7 +652,7 @@ TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
623652
624653 testTray.notification_title = " Menu Callback Regression" ;
625654 testTray.notification_text = " Notification update should not break menu callbacks" ;
626- testTray.notification_icon = TRAY_ICON1 ;
655+ testTray.notification_icon = TRAY_ICON_SVG ;
627656 tray_update (&testTray);
628657 WaitForTrayReady ();
629658
@@ -639,3 +668,17 @@ TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
639668
640669 testTray.menu [0 ].cb = original_cb;
641670}
671+
672+ INSTANTIATE_TEST_SUITE_P (
673+ TrayIcons,
674+ TrayIconTest,
675+ ::testing::ValuesIn (TRAY_ICON_PARAMS ),
676+ trayIconParamName
677+ );
678+
679+ INSTANTIATE_TEST_SUITE_P (
680+ TrayNotificationIcons,
681+ TrayNotificationIconTest,
682+ ::testing::ValuesIn (TRAY_ICON_PARAMS ),
683+ trayIconParamName
684+ );
0 commit comments