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,12 +248,23 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must
201248 }
202249};
203250
204- TEST_F (TrayTest, TestTrayInit) {
251+ class TrayIconTest :
252+ public TrayTest,
253+ public ::testing::WithParamInterface<TrayIconParam> {};
254+
255+ class TrayNotificationIconTest :
256+ public TrayTest,
257+ public ::testing::WithParamInterface<TrayIconParam> {};
258+
259+ TEST_P (TrayIconTest, TestTrayInit) {
260+ const auto &iconParam = GetParam ();
261+ testTray.icon = iconParam.icon ;
262+
205263 int result = tray_init (&testTray);
206264 trayRunning = (result == 0 );
207265 EXPECT_EQ (result, 0 );
208266 WaitForTrayReady ();
209- EXPECT_TRUE (captureScreenshot (" tray_icon_initial " ));
267+ EXPECT_TRUE (captureScreenshot (std::string ( " tray_icon_ " ) + iconParam. name ));
210268}
211269
212270TEST_F (TrayTest, TestTrayLoop) {
@@ -302,14 +360,12 @@ TEST_F(TrayTest, TestSubmenuCallback) {
302360 testTray.menu [4 ].submenu [0 ].submenu [0 ].cb (&testTray.menu [4 ].submenu [0 ].submenu [0 ]);
303361}
304362
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;
363+ TEST_P (TrayNotificationIconTest, TestNotificationDisplay) {
364+ if (const std::string skipReason = nativeNotificationSkipReason (); !skipReason.empty ()) {
365+ GTEST_SKIP () << skipReason;
311366 }
312- #endif
367+
368+ const auto &iconParam = GetParam ();
313369
314370 int initResult = tray_init (&testTray);
315371 trayRunning = (initResult == 0 );
@@ -318,12 +374,12 @@ TEST_F(TrayTest, TestNotificationDisplay) {
318374 // Set notification properties
319375 testTray.notification_title = " Test Notification" ;
320376 testTray.notification_text = " This is a test notification message" ;
321- testTray.notification_icon = TRAY_ICON1 ;
377+ testTray.notification_icon = iconParam. icon ;
322378
323379 tray_update (&testTray);
324380
325381 WaitForTrayReady ();
326- EXPECT_TRUE (captureScreenshot (" tray_notification_displayed " ));
382+ EXPECT_TRUE (captureScreenshot (std::string ( " tray_notification_ " ) + iconParam. name + " _icon " ));
327383
328384 // Clear notification
329385 testTray.notification_title = nullptr ;
@@ -333,14 +389,12 @@ TEST_F(TrayTest, TestNotificationDisplay) {
333389 waitForNativeNotificationTimeout ();
334390}
335391
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;
392+ TEST_P (TrayNotificationIconTest, TestNotificationCallback) {
393+ if (const std::string skipReason = nativeNotificationSkipReason (); !skipReason.empty ()) {
394+ GTEST_SKIP () << skipReason;
342395 }
343- #endif
396+
397+ const auto &iconParam = GetParam ();
344398
345399 static bool callbackInvoked = false ;
346400 auto notification_callback = []() {
@@ -354,7 +408,7 @@ TEST_F(TrayTest, TestNotificationCallback) {
354408 // Set notification with callback
355409 testTray.notification_title = " Clickable Notification" ;
356410 testTray.notification_text = " Click this notification to test callback" ;
357- testTray.notification_icon = TRAY_ICON1 ;
411+ testTray.notification_icon = iconParam. icon ;
358412 testTray.notification_cb = notification_callback;
359413
360414 tray_update (&testTray);
@@ -423,6 +477,8 @@ TEST_F(TrayTest, TestMenuItemContext) {
423477}
424478
425479TEST_F (TrayTest, TestCheckboxStates) {
480+ testTray.icon = TRAY_ICON_SVG ;
481+
426482 int initResult = tray_init (&testTray);
427483 trayRunning = (initResult == 0 );
428484 ASSERT_EQ (initResult, 0 );
@@ -503,6 +559,8 @@ TEST_F(TrayTest, TestQuitCallback) {
503559}
504560
505561TEST_F (TrayTest, TestTrayShowMenu) {
562+ testTray.icon = TRAY_ICON_SVG ;
563+
506564 int initResult = tray_init (&testTray);
507565 trayRunning = (initResult == 0 );
508566 ASSERT_EQ (initResult, 0 );
@@ -515,61 +573,24 @@ TEST_F(TrayTest, TestTrayExit) {
515573 tray_exit ();
516574}
517575
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-
558576TEST_F (TrayTest, TestMenuAppearsOnLeftClick) {
559577 // Regression test for: clicking the tray icon did not bring up the menu.
560578 // The activated(Trigger) signal was not connected to the menu popup logic.
561579 // tray_show_menu() exercises the same code path that the activated handler calls.
580+ testTray.icon = TRAY_ICON_SVG ;
581+
562582 int initResult = tray_init (&testTray);
563583 trayRunning = (initResult == 0 );
564584 ASSERT_EQ (initResult, 0 );
565585
566586 captureMenuStateAndExit (" tray_menu_left_click" ); // NOSONAR(cpp:S6168) - helper uses std::thread for AppleClang 17 compatibility
567587}
568588
569- TEST_F (TrayTest , TestNotificationCallbackFiredOnClick) {
589+ TEST_P (TrayNotificationIconTest , TestNotificationCallbackFiredOnClick) {
570590 // Regression test for: clicking a notification did not invoke the callback.
571591 // On the D-Bus path, QSystemTrayIcon::messageClicked is never emitted; the
572592 // callback must be routed through TrayNotificationHandler::onActionInvoked.
593+ const auto &iconParam = GetParam ();
573594 static bool callbackInvoked = false ;
574595 callbackInvoked = false ;
575596
@@ -579,7 +600,7 @@ TEST_F(TrayTest, TestNotificationCallbackFiredOnClick) {
579600
580601 testTray.notification_title = " Clickable Notification" ;
581602 testTray.notification_text = " Click to test callback" ;
582- testTray.notification_icon = TRAY_ICON1 ;
603+ testTray.notification_icon = iconParam. icon ;
583604 testTray.notification_cb = []() {
584605 callbackInvoked = true ;
585606 };
@@ -623,7 +644,7 @@ TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
623644
624645 testTray.notification_title = " Menu Callback Regression" ;
625646 testTray.notification_text = " Notification update should not break menu callbacks" ;
626- testTray.notification_icon = TRAY_ICON1 ;
647+ testTray.notification_icon = TRAY_ICON_SVG ;
627648 tray_update (&testTray);
628649 WaitForTrayReady ();
629650
@@ -639,3 +660,17 @@ TEST_F(TrayTest, TestMenuCallbackAfterNotificationUpdate) {
639660
640661 testTray.menu [0 ].cb = original_cb;
641662}
663+
664+ INSTANTIATE_TEST_SUITE_P (
665+ TrayIcons,
666+ TrayIconTest,
667+ ::testing::ValuesIn (TRAY_ICON_PARAMS ),
668+ trayIconParamName
669+ );
670+
671+ INSTANTIATE_TEST_SUITE_P (
672+ TrayNotificationIcons,
673+ TrayNotificationIconTest,
674+ ::testing::ValuesIn (TRAY_ICON_PARAMS ),
675+ trayIconParamName
676+ );
0 commit comments