#include "../src/animtimer.h" #include "../src/chatview.h" #include "../src/emoji.h" #include "../src/messagedelegate.h" #include "../src/emojipanel.h" #include "../src/lottieicon.h" #include "../../../lib/debug_config.h" #include #include #include #include #include #include #include static QStringList playbackLog; static void captureLog(int, const char *, const char *message) { playbackLog.append(QString::fromUtf8(message)); } // Два коротких цикла разной длины выявляют остановку посреди кадра и ожидание общего кратного. static QByteArray animationData(int frames) { QByteArray json = QString(R"({"v":"5.7.4","fr":30,"ip":0,"op":%1,"w":32,"h":32,"layers":[]})").arg(frames).toUtf8(); QByteArray gzip(1024, '\0'); z_stream stream = {}; if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 16 + MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) return {}; stream.next_in = reinterpret_cast(json.data()); stream.avail_in = json.size(); stream.next_out = reinterpret_cast(gzip.data()); stream.avail_out = gzip.size(); int result = deflate(&stream, Z_FINISH); gzip.resize(stream.total_out); deflateEnd(&stream); return result == Z_STREAM_END ? gzip : QByteArray(); } class EmojiPlaybackTest : public QObject { Q_OBJECT std::unique_ptr shortIcon, longIcon; private slots: void init() { debug_config_init(); debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_DEBUG); debug_set_log_hook(captureLog); playbackLog.clear(); shortIcon = std::make_unique(animationData(7), "short"); longIcon = std::make_unique(animationData(11), "long"); QVERIFY(shortIcon->isValid()); QVERIFY(longIcon->isValid()); AnimTimer::instance()->registerIcon("short", shortIcon.get()); AnimTimer::instance()->registerIcon("long", longIcon.get()); } void cleanup() { AnimTimer::shutdown(); debug_set_log_hook(nullptr); shortIcon.reset(); longIcon.reset(); } void messagesFinishEachCycle() { auto *timer = AnimTimer::instance(); QSignalSpy ticks(timer, &AnimTimer::ticked); timer->play(); QTest::qWait(1850); QVERIFY(playbackLog.filter("AnimTimer: playback finished").isEmpty()); int count = ticks.count(); QTest::qWait(100); QVERIFY(ticks.count() > count); QTRY_COMPARE_WITH_TIMEOUT(playbackLog.filter("AnimTimer: playback finished").size(), 1, 1000); QCOMPARE(shortIcon->currentFrame(), 0); QCOMPARE(longIcon->currentFrame(), 0); count = ticks.count(); QTest::qWait(150); QCOMPARE(ticks.count(), count); } void anotherHoverRestartsDeadline() { auto *timer = AnimTimer::instance(); QSignalSpy ticks(timer, &AnimTimer::ticked); timer->play(); QTest::qWait(1400); timer->play(); QTest::qWait(1000); QVERIFY(playbackLog.filter("AnimTimer: playback finished").isEmpty()); int count = ticks.count(); QTest::qWait(100); QVERIFY(ticks.count() > count); QTRY_COMPARE_WITH_TIMEOUT(playbackLog.filter("AnimTimer: playback finished").size(), 1, 1500); QCOMPARE(shortIcon->currentFrame(), 0); QCOMPARE(longIcon->currentFrame(), 0); } void pickerFinishesWithOrWithoutHover() { AnimTimer::instance()->registerIcon(":/animations/robot.tgs", shortIcon.get()); EmojiPanel panel; panel.resize(320, 240); panel.findChild()->setCurrentIndex(3); panel.show(); QPushButton *button = nullptr; for (auto *candidate : panel.findChildren()) { if (candidate->toolTip() == ":robot:" && candidate->hasMouseTracking()) button = candidate; } QVERIFY(button); QEvent leave(QEvent::Leave); QApplication::sendEvent(button, &leave); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QEnterEvent enter(QPointF(1, 1), QPointF(1, 1), QPointF(1, 1)); #else QEvent enter(QEvent::Enter); #endif QApplication::sendEvent(button, &enter); QTest::qWait(1850); QVERIFY(playbackLog.filter("EmojiButton: playback finished").isEmpty()); QTRY_COMPARE_WITH_TIMEOUT(playbackLog.filter("EmojiButton: playback finished").size(), 1, 1000); QTest::qWait(150); QCOMPARE(playbackLog.filter("EmojiButton: hover trigger").size(), 1); QApplication::sendEvent(button, &leave); QApplication::sendEvent(button, &enter); QTest::qWait(100); QApplication::sendEvent(button, &leave); QTest::qWait(1750); QCOMPARE(playbackLog.filter("EmojiButton: playback finished").size(), 1); QTRY_COMPARE_WITH_TIMEOUT(playbackLog.filter("EmojiButton: playback finished").size(), 2, 1000); } void visibleMessagesTriggerOnce() { builtinEmojiSet(); QStandardItemModel model; for (int row = 0; row < 6; ++row) { auto *item = new QStandardItem("message"); item->setSizeHint(QSize(200, 60)); item->setData(row + 1, MsgIdRole); item->setData("test-channel", MsgChannelIdRole); item->setData(row == 0 || row == 5 ? QString::fromUtf8("🤖") : QString("text"), MsgTextRole); model.appendRow(item); } ChatView view; view.setModel(&model); view.resize(240, 140); QSignalSpy triggers(&view, &ChatView::animationTriggered); connect(&view, &ChatView::animationTriggered, AnimTimer::instance(), &AnimTimer::play); view.show(); QTRY_COMPARE(triggers.count(), 1); view.viewport()->update(); QTest::qWait(100); QCOMPARE(triggers.count(), 1); view.scrollToBottom(); QTRY_COMPARE(triggers.count(), 2); view.scrollToTop(); QTRY_COMPARE(triggers.count(), 3); view.scrollToBottom(); QTRY_COMPARE(triggers.count(), 4); auto *message = new QStandardItem("new message"); message->setSizeHint(QSize(200, 60)); message->setData(7, MsgIdRole); message->setData("test-channel", MsgChannelIdRole); message->setData(QString::fromUtf8("🤖"), MsgTextRole); model.appendRow(message); view.scrollToBottom(); QTRY_COMPARE(triggers.count(), 5); view.viewport()->update(); QTest::qWait(100); QCOMPARE(triggers.count(), 5); } void hoverTriggersOnEntry() { builtinEmojiSet(); QStandardItemModel model; auto *message = new QStandardItem("message"); message->setSizeHint(QSize(200, 60)); message->setData(1, MsgIdRole); message->setData(QString::fromUtf8("🤖"), MsgTextRole); model.appendRow(message); ChatView view; view.setModel(&model); view.resize(240, 140); view.show(); QTest::qWait(50); QSignalSpy triggers(&view, &ChatView::animationTriggered); const QPointF point = view.visualRect(model.index(0, 0)).center(); QMouseEvent move(QEvent::MouseMove, point, point, Qt::NoButton, Qt::NoButton, Qt::NoModifier); QApplication::sendEvent(view.viewport(), &move); QCOMPARE(triggers.count(), 1); QApplication::sendEvent(view.viewport(), &move); QCOMPARE(triggers.count(), 1); QEvent leave(QEvent::Leave); QApplication::sendEvent(view.viewport(), &leave); QCOMPARE(triggers.count(), 1); QApplication::sendEvent(view.viewport(), &move); QCOMPARE(triggers.count(), 2); } }; QTEST_MAIN(EmojiPlaybackTest) #include "test_emoji_playback.moc"