diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 649593e9..f421dd70 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -308,7 +308,14 @@ enable_testing() add_executable(test_message_notification tests/test_message_notification.cpp) target_link_libraries(test_message_notification PRIVATE chatgui_notifications ${QT_TEST_LIB} pthread ${CMAKE_DL_LIBS}) -add_test(NAME test_message_notification COMMAND test_message_notification) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_program(NOTIFICATION_DBUS_RUN_SESSION dbus-run-session REQUIRED) + target_compile_definitions(test_message_notification PRIVATE UTUN_TEST_ACTIVITY_DBUS) + target_link_libraries(test_message_notification PRIVATE ${QT_DBUS}) + add_test(NAME test_message_notification COMMAND ${NOTIFICATION_DBUS_RUN_SESSION} -- $) +else() + add_test(NAME test_message_notification COMMAND test_message_notification) +endif() set_tests_properties(test_message_notification PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 60) add_executable(test_file_drop tests/test_file_drop.cpp src/inputbar.cpp src/audiorecorder.cpp src/audio_device.cpp src/sound_manager.cpp diff --git a/tools/chatgui/tests/test_message_notification.cpp b/tools/chatgui/tests/test_message_notification.cpp new file mode 100644 index 00000000..3ee637c9 --- /dev/null +++ b/tools/chatgui/tests/test_message_notification.cpp @@ -0,0 +1,178 @@ +/* Проверяем время жизни уведомления и первую активность без ввода в настоящую desktop-сессию. */ +#include "../src/message_notification.h" +#include "../src/user_activity_monitor.h" +#include "../../../lib/debug_config.h" + +#include +#include +#include +#include +#include + +#ifdef UTUN_TEST_ACTIVITY_DBUS +#include +#include +#include +#endif + +class FakeSystemActivity : public UserActivityMonitor { +public: + FakeSystemActivity() { clock.start(); } + QElapsedTimer clock; + qint64 lastInput = -5000; + bool available = true; +protected: + qint64 systemIdleTime() override { return available ? clock.elapsed() - lastInput : -1; } +}; + +#ifdef UTUN_TEST_ACTIVITY_DBUS +class FakeGnomeIdle : public QObject, protected QDBusContext { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.gnome.Mutter.IdleMonitor") +public: + FakeGnomeIdle() { clock.start(); } + QElapsedTimer clock; + qint64 lastInput = -5000; + int delayMs = 0; + int requests = 0; +public slots: + qulonglong GetIdletime() { + ++requests; + const qulonglong idle = clock.elapsed() - lastInput; + if (delayMs) { + const QDBusMessage reply = message().createReply(QVariant::fromValue(idle)); + setDelayedReply(true); + QTimer::singleShot(delayMs, this, [reply] { QDBusConnection::sessionBus().send(reply); }); + } + return idle; + } +}; +#endif + +class MessageNotificationTest : public QObject { + Q_OBJECT +private slots: + void initTestCase() { + debug_config_init(); + debug_set_level(DEBUG_LEVEL_DEBUG); + qunsetenv("XDG_SESSION_TYPE"); + } + + void firstSystemActivityAndCancellation() { + FakeSystemActivity activity; + QSignalSpy spy(&activity, &UserActivityMonitor::activity); + activity.start(); + QTest::qWait(600); + QCOMPARE(spy.size(), 0); // Старый ввод до начала наблюдения не считается активностью. + activity.lastInput = activity.clock.elapsed(); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 700); + QTest::qWait(300); + QCOMPARE(spy.size(), 1); + activity.start(); + activity.stop(); + activity.lastInput = activity.clock.elapsed(); + QTest::qWait(400); + QCOMPARE(spy.size(), 1); + activity.start(); + QTest::qWait(20); + activity.lastInput = activity.clock.elapsed(); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 2, 700); + } + + void localFallbackDoesNotConsumeInput() { + FakeSystemActivity activity; + activity.available = false; + QSignalSpy spy(&activity, &UserActivityMonitor::activity); + QLineEdit input; + input.show(); + activity.start(); + QTest::keyClick(&input, Qt::Key_A); + QTest::keyClick(&input, Qt::Key_B); + QCOMPARE(input.text(), QString("ab")); + QCOMPARE(spy.size(), 1); + } + + void waitsForActivityThenExpiresWithoutExtension() { + QWidget hiddenParent; + QLineEdit otherWindow; + otherWindow.show(); + QApplication::setActiveWindow(&otherWindow); + MessageNotification notification(&hiddenParent); + notification.showMessage("42", "Alice", "Test channel", {}); + QVERIFY(notification.isVisible()); + QCOMPARE(QApplication::activeWindow(), &otherWindow); + QTest::qWait(10200); + QVERIFY(notification.isVisible()); // Без активности автоматического таймера нет. + QElapsedTimer elapsed; + elapsed.start(); + QTest::keyClick(&otherWindow, Qt::Key_A); + QTest::qWait(5500); + QVERIFY(notification.isVisible()); + QTest::keyClick(&otherWindow, Qt::Key_B); + QTRY_VERIFY_WITH_TIMEOUT(!notification.isVisible(), 6000); + QVERIFY(elapsed.elapsed() >= 10000); + QVERIFY(elapsed.elapsed() < 12000); + QCOMPARE(otherWindow.text(), QString("ab")); + } + + void replacementCancelsOldCountdownAndOpensLatestChat() { + QLineEdit otherWindow; + otherWindow.show(); + MessageNotification notification; + QSignalSpy activated(¬ification, &MessageNotification::activated); + notification.showMessage("42", "Alice", "First channel", {}); + QTest::keyClick(&otherWindow, Qt::Key_A); + QTest::qWait(100); + notification.showMessage("dm:7", "Bob", "Direct messages", QRect(-10, -10, 20, 20)); + QTest::qWait(10200); + QVERIFY(notification.isVisible()); // Прежний таймер не закрывает новое уведомление. + const QRect area = notification.screen()->availableGeometry(); + QVERIFY(area.contains(notification.geometry())); + QTest::mouseClick(¬ification, Qt::LeftButton, Qt::NoModifier, QPoint(30, notification.height() - 15)); + QVERIFY(!notification.isVisible()); + QCOMPARE(activated.size(), 1); + QCOMPARE(activated[0][0].toString(), QString("dm:7")); + notification.showMessage("43", "Charlie", "Third channel", {}); + auto* closeButton = notification.findChild("notificationCloseButton"); + QVERIFY(closeButton); + QTest::mouseClick(closeButton, Qt::LeftButton); + QVERIFY(!notification.isVisible()); + QCOMPARE(activated.size(), 1); + } + +#ifdef UTUN_TEST_ACTIVITY_DBUS + void gnomeIdleAndStaleReplies() { + auto bus = QDBusConnection::sessionBus(); + QVERIFY(bus.registerService("org.gnome.Mutter.IdleMonitor")); + FakeGnomeIdle idle; + QVERIFY(bus.registerObject("/org/gnome/Mutter/IdleMonitor/Core", &idle, QDBusConnection::ExportAllSlots)); + qputenv("XDG_SESSION_TYPE", "wayland"); + UserActivityMonitor activity; + QSignalSpy spy(&activity, &UserActivityMonitor::activity); + activity.start(); + QTRY_VERIFY_WITH_TIMEOUT(idle.requests >= 2, 1000); + QCOMPARE(spy.size(), 0); + idle.lastInput = idle.clock.elapsed(); + QTRY_COMPARE_WITH_TIMEOUT(spy.size(), 1, 1000); + + idle.delayMs = 300; + activity.start(); + QTest::qWait(20); + idle.lastInput = idle.clock.elapsed(); + const int before = idle.requests; + QTRY_VERIFY_WITH_TIMEOUT(idle.requests > before, 1000); + activity.stop(); + idle.lastInput = idle.clock.elapsed() - 5000; + activity.start(); + QTest::qWait(800); + QCOMPARE(spy.size(), 1); // Ответ об активности прежнего наблюдения должен быть отброшен. + activity.stop(); + qunsetenv("XDG_SESSION_TYPE"); + bus.unregisterObject("/org/gnome/Mutter/IdleMonitor/Core"); + bus.unregisterService("org.gnome.Mutter.IdleMonitor"); + } +#endif +}; + +QTEST_MAIN(MessageNotificationTest) +#include "test_message_notification.moc"