You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.7 KiB
201 lines
7.7 KiB
#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"
|
|
|