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,13 @@ 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 ();
377+ testTray.icon = iconParam.icon ;
313378
314379 int initResult = tray_init (&testTray);
315380 trayRunning = (initResult == 0 );
@@ -318,12 +383,12 @@ TEST_F(TrayTest, TestNotificationDisplay) {
318383 // Set notification properties
319384 testTray.notification_title = " Test Notification" ;
320385 testTray.notification_text = " This is a test notification message" ;
321- testTray.notification_icon = TRAY_ICON1 ;
386+ testTray.notification_icon = iconParam. icon ;
322387
323388 tray_update (&testTray);
324389
325390 WaitForTrayReady ();
326- EXPECT_TRUE (captureScreenshot (" tray_notification_displayed " ));
391+ EXPECT_TRUE (captureScreenshot (std::string ( " tray_notification_ " ) + iconParam. name + " _icon " ));
327392
328393 // Clear notification
329394 testTray.notification_title = nullptr ;
@@ -333,14 +398,13 @@ TEST_F(TrayTest, TestNotificationDisplay) {
333398 waitForNativeNotificationTimeout ();
334399}
335400
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;
401+ TEST_P (TrayNotificationIconTest, TestNotificationCallback) {
402+ if (const std::string skipReason = nativeNotificationSkipReason (); !skipReason.empty ()) {
403+ GTEST_SKIP () << skipReason;
342404 }
343- #endif
405+
406+ const auto &iconParam = GetParam ();
407+ testTray.icon = iconParam.icon ;
344408
345409 static bool callbackInvoked = false ;
346410 auto notification_callback = []() {
@@ -354,7 +418,7 @@ TEST_F(TrayTest, TestNotificationCallback) {
354418 // Set notification with callback
355419 testTray.notification_title = " Clickable Notification" ;
356420 testTray.notification_text = " Click this notification to test callback" ;
357- testTray.notification_icon = TRAY_ICON1 ;
421+ testTray.notification_icon = iconParam. icon ;
358422 testTray.notification_cb = notification_callback;
359423
360424 tray_update (&testTray);
@@ -423,6 +487,8 @@ TEST_F(TrayTest, TestMenuItemContext) {
423487}
424488
425489TEST_F (TrayTest, TestCheckboxStates) {
490+ testTray.icon = TRAY_ICON_SVG ;
491+
426492 int initResult = tray_init (&testTray);
427493 trayRunning = (initResult == 0 );
428494 ASSERT_EQ (initResult, 0 );
@@ -503,6 +569,8 @@ TEST_F(TrayTest, TestQuitCallback) {
503569}
504570
505571TEST_F (TrayTest, TestTrayShowMenu) {
572+ testTray.icon = TRAY_ICON_SVG ;
573+
506574 int initResult = tray_init (&testTray);
507575 trayRunning = (initResult == 0 );
508576 ASSERT_EQ (initResult, 0 );
@@ -515,61 +583,25 @@ TEST_F(TrayTest, TestTrayExit) {
515583 tray_exit ();
516584}
517585
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-
558586TEST_F (TrayTest, TestMenuAppearsOnLeftClick) {
559587 // Regression test for: clicking the tray icon did not bring up the menu.
560588 // The activated(Trigger) signal was not connected to the menu popup logic.
561589 // tray_show_menu() exercises the same code path that the activated handler calls.
590+ testTray.icon = TRAY_ICON_SVG ;
591+
562592 int initResult = tray_init (&testTray);
563593 trayRunning = (initResult == 0 );
564594 ASSERT_EQ (initResult, 0 );
565595
566596 captureMenuStateAndExit (" tray_menu_left_click" ); // NOSONAR(cpp:S6168) - helper uses std::thread for AppleClang 17 compatibility
567597}
568598
569- TEST_F (TrayTest , TestNotificationCallbackFiredOnClick) {
599+ TEST_P (TrayNotificationIconTest , TestNotificationCallbackFiredOnClick) {
570600 // Regression test for: clicking a notification did not invoke the callback.
571601 // On the D-Bus path, QSystemTrayIcon::messageClicked is never emitted; the
572602 // callback must be routed through TrayNotificationHandler::onActionInvoked.
603+ const auto &iconParam = GetParam ();
604+ testTray.icon = iconParam.icon ;
573605 static bool callbackInvoked = false ;
574606 callbackInvoked = false ;
575607
@@ -579,7 +611,7 @@ TEST_F(TrayTest, TestNotificationCallbackFiredOnClick) {
579611
580612 testTray.notification_title = " Clickable Notification" ;
581613 testTray.notification_text = " Click to test callback" ;
582- testTray.notification_icon = TRAY_ICON1 ;
614+ testTray.notification_icon = iconParam. icon ;
583615 testTray.notification_cb = []() {
584616 callbackInvoked = true ;
585617 };
@@ -623,7 +655,7 @@ TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
623655
624656 testTray.notification_title = " Menu Callback Regression" ;
625657 testTray.notification_text = " Notification update should not break menu callbacks" ;
626- testTray.notification_icon = TRAY_ICON1 ;
658+ testTray.notification_icon = TRAY_ICON_SVG ;
627659 tray_update (&testTray);
628660 WaitForTrayReady ();
629661
@@ -639,3 +671,17 @@ TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
639671
640672 testTray.menu [0 ].cb = original_cb;
641673}
674+
675+ INSTANTIATE_TEST_SUITE_P (
676+ TrayIcons,
677+ TrayIconTest,
678+ ::testing::ValuesIn (TRAY_ICON_PARAMS ),
679+ trayIconParamName
680+ );
681+
682+ INSTANTIATE_TEST_SUITE_P (
683+ TrayNotificationIcons,
684+ TrayNotificationIconTest,
685+ ::testing::ValuesIn (TRAY_ICON_PARAMS ),
686+ trayIconParamName
687+ );
0 commit comments