diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index 068666e1a..3b3bebe72 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -1061,7 +1061,6 @@ void MessageList::refresh(bool outgoing) { m_localNicks = m_db->getLocalNicknames(m_currentChannelId); - QScrollBar* sb = m_view->verticalScrollBar(); const bool wasAtBottom = nearHistoryBottom(); bool added = false; diff --git a/tools/chatgui/tests/test_group_edits.cpp b/tools/chatgui/tests/test_group_edits.cpp index 2ecff6674..65121afb0 100644 --- a/tools/chatgui/tests/test_group_edits.cpp +++ b/tools/chatgui/tests/test_group_edits.cpp @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -128,6 +131,79 @@ static int inCore(std::function operation) { class GroupEditTest : public QObject { Q_OBJECT private slots: + void newMessageScroll_data() { + QTest::addColumn("dm"); QTest::addColumn("scenario"); + for (bool dm : {false, true}) for (const QString& scenario : {"bottom", "near", "boundary", "far", "own", "unchanged"}) + QTest::newRow(qPrintable(QString("%1-%2").arg(dm ? "pm" : "group", scenario))) << dm << scenario; + } + void newMessageScroll() { + QFETCH(bool, dm); QFETCH(QString, scenario); + SendCore core; QVERIFY(core.start()); + auto* history = core.history.data(); history->resize(500, 420); history->show(); + auto* view = history->findChild(); QVERIFY(view); + auto* model = qobject_cast(view->model()); QVERIFY(model); + QJsonArray messages; QString conversation; quint64 generation = 0; + if (dm) { + SC_MYKEYS peer{}; uint8_t ed[32]; QCOMPARE(sc_generate_keypair(&peer), 0); + QCOMPARE(sc_derive_ed25519_pubkey(peer.private_key, ed), 0); + QCOMPARE(dm_start(core.instance, sc_derive_node_id_from_pubkey(peer.public_key), peer.public_key, ed, "Scroll peer", nullptr), 0); + const auto conversations = core.db.getDmConversations(); QCOMPARE(conversations.size(), 1); + conversation = conversations.first().convId; generation = conversations.first().generation; + history->loadChannel("dm:" + conversation); + } + const auto append = [&](int seq, const QString& text, bool outgoing = false) { + if (dm) { + messages.append(QJsonObject{{"seq", seq}, {"ts", seq * 1000}, {"dir", outgoing ? 1 : 0}, + {"author", QString::number(core.instance->node_id)}, {"data", text}, {"content_type", "text/plain"}}); + return true; + } + const auto bytes = text.toUtf8(); chat_msg_submit request{}; + snprintf(request.channel_id, sizeof(request.channel_id), "%s", qPrintable(core.channel)); + strcpy(request.content_type, "text/plain"); request.data = (uint8_t*)bytes.constData(); request.data_len = bytes.size(); + request.timestamp = 1000000 + seq * 1000; + return chat_core_submit_message(core.instance, &request) == 0; + }; + const auto snapshot = [&] { + QByteArray packet; packet.append(char(conversation.toUtf8().size())); packet.append(conversation.toUtf8()); + packet.append(reinterpret_cast(&generation), sizeof(generation)); + packet.append(QJsonDocument(messages).toJson(QJsonDocument::Compact)); history->onDmMessages((const uint8_t*)packet.constData(), packet.size()); + }; + for (int i = 1; i <= 60; ++i) QVERIFY(append(i, QString("message %1\nsecond line").arg(i))); + if (dm) snapshot(); else history->loadChannel(core.channel); + QTest::qWait(30); view->doItemsLayout(); view->scrollToBottom(); + auto* scrollbar = view->verticalScrollBar(); const int line = view->fontMetrics().lineSpacing(); + QVERIFY(scrollbar->maximum() > 30 * line); + if (!dm && scenario == "own") { + auto* delegate = qobject_cast(view->itemDelegate()); QVERIFY(delegate); + delegate->replyRequested(model->index(0, 0).data(MsgMessageKeyRole).toString()); + QCoreApplication::processEvents(); + } + const int distance = scenario == "bottom" ? 0 : scenario == "boundary" ? 10 * line : + scenario == "far" || scenario == "own" ? 30 * line : 5 * line; + scrollbar->setValue(scrollbar->maximum() - distance); const int previous = scrollbar->value(); + const QString text = "```cpp\n" + QString("int value = 1;\n").repeated(30) + "```"; + if (scenario != "unchanged") { + if (!dm && scenario == "own") { + core.editor()->setPlainText(text); QVERIFY(core.send()); core.pump(); + QCOMPARE(scrollbar->value(), scrollbar->maximum()); + } else QVERIFY(append(61, text, scenario == "own")); + } + if (dm) snapshot(); else history->refresh(); + QCoreApplication::processEvents(); view->doItemsLayout(); + const bool follow = scenario != "far" && scenario != "unchanged"; + if (follow) QCOMPARE(scrollbar->value(), scrollbar->maximum()); + else QCOMPARE(scrollbar->value(), previous); + GUI_DEBUG("scroll verified: dm=%d scenario=%s previous=%d current=%d maximum=%d", dm, qPrintable(scenario), previous, + scrollbar->value(), scrollbar->maximum()); + // Отложенная прокрутка предыдущего канала не должна сдвинуть новую историю. + if (scenario == "near") { + if (dm) { QVERIFY(append(62, "next")); snapshot(); } + else { QVERIFY(append(62, "next")); history->refresh(); } + history->loadChannel(QString()); + QCoreApplication::processEvents(); QCOMPARE(model->rowCount(), 0); + } + } + void clipboardImageCommitsThroughCore_data() { QTest::addColumn("dm"); QTest::addColumn("jpeg"); QTest::addColumn("manual"); QTest::newRow("png-group-reply") << false << false << false;