13 changed files with 338 additions and 51 deletions
@ -0,0 +1,201 @@
|
||||
#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 <QtTest> |
||||
#include <QEnterEvent> |
||||
#include <QPushButton> |
||||
#include <QTabWidget> |
||||
#include <QStandardItemModel> |
||||
#include <zlib.h> |
||||
#include <memory> |
||||
|
||||
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<Bytef *>(json.data()); |
||||
stream.avail_in = json.size(); |
||||
stream.next_out = reinterpret_cast<Bytef *>(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<LottieIcon> 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<LottieIcon>(animationData(7), "short"); |
||||
longIcon = std::make_unique<LottieIcon>(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<QTabWidget *>()->setCurrentIndex(3); |
||||
panel.show(); |
||||
QPushButton *button = nullptr; |
||||
for (auto *candidate : panel.findChildren<QPushButton *>()) { |
||||
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" |
||||
Loading…
Reference in new issue