From a9e407a39fa741143247355e7d03c9240b4412e4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 8 Oct 2026 00:30:08 +0200 Subject: [PATCH] Test reply draft acknowledgements and quote clicks across content types --- tools/chatgui/tests/test_file_drop.cpp | 41 +++++++++++++++++++++ tools/chatgui/tests/test_invite_links.cpp | 43 +++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/tools/chatgui/tests/test_file_drop.cpp b/tools/chatgui/tests/test_file_drop.cpp index 84c4854e..7e1798bf 100644 --- a/tools/chatgui/tests/test_file_drop.cpp +++ b/tools/chatgui/tests/test_file_drop.cpp @@ -46,6 +46,47 @@ static bool dropUrls(QWidget* target, const QList& urls, bool process = tr class FileDropTest : public QObject { Q_OBJECT private slots: + void replyDraftAndLateResults() { + InputBar bar(nullptr, nullptr); bar.resize(600, 160); bar.show(); bar.setRecordingContext("/tmp", "123"); + auto* editor = bar.findChild(); QVERIFY(editor); + QSignalSpy sends(&bar, &InputBar::sendMessage); + const QByteArray first(32, 'a'), second(32, 'b'); + bar.setReply(first, "Alice", "Original"); editor->setPlainText("answer"); + QTest::keyClick(editor, Qt::Key_Return); QCOMPARE(sends.size(), 1); + QCOMPARE(sends[0][1].toByteArray(), first); const quint64 id = sends[0][2].toULongLong(); QVERIFY(id); + QCOMPARE(editor->toPlainText(), QString("answer")); + QTest::keyClick(editor, Qt::Key_Return); QCOMPARE(sends.size(), 1); // Не отправлять повторно до результата. + bar.submissionResult(id, false); QCOMPARE(editor->toPlainText(), QString("answer")); + QTest::keyClick(editor, Qt::Key_Return); QCOMPARE(sends.size(), 2); + bar.setReply(second, "Bob", "Another original"); editor->setPlainText("new draft"); + bar.submissionResult(sends[1][2].toULongLong(), true); QCOMPARE(editor->toPlainText(), QString("new draft")); + QTest::keyClick(editor, Qt::Key_Return); QCOMPARE(sends.size(), 3); QCOMPARE(sends[2][1].toByteArray(), second); + bar.submissionResult(sends[2][2].toULongLong(), true); QVERIFY(editor->toPlainText().isEmpty()); + editor->setPlainText("ordinary"); QTest::keyClick(editor, Qt::Key_Return); QVERIFY(sends[3][1].toByteArray().isEmpty()); + bar.setReply(first, "Alice", "Original"); QTest::keyClick(editor, Qt::Key_Escape); + editor->setPlainText("cancelled"); QTest::keyClick(editor, Qt::Key_Return); QVERIFY(sends[4][1].toByteArray().isEmpty()); + bar.setReply(first, "Alice", "Original"); bar.setRecordingContext("/tmp", "456"); + editor->setPlainText("other channel"); QTest::keyClick(editor, Qt::Key_Return); QVERIFY(sends[5][1].toByteArray().isEmpty()); + } + + void replyAttachmentAndTextHaveIndependentResults() { + QTemporaryDir dir; QVERIFY(dir.isValid()); const QString file = makeFile(dir, "answer.txt"); + InputBar bar(nullptr, nullptr); bar.resize(600, 180); bar.show(); bar.setRecordingContext(dir.path(), "123"); + auto* editor = bar.findChild(); QVERIFY(editor); + const QByteArray original(32, 'a'); bar.setReply(original, "Alice", "original"); + QVERIFY(dropUrls(editor->viewport(), {QUrl::fromLocalFile(file)})); editor->setPlainText("answer"); + QSignalSpy files(&bar, &InputBar::sendFileAttachment), texts(&bar, &InputBar::sendMessage); + QTest::keyClick(editor, Qt::Key_Return); QCOMPARE(files.size(), 1); QCOMPARE(texts.size(), 1); + QCOMPARE(files[0][4].toByteArray(), original); QCOMPARE(texts[0][1].toByteArray(), original); + QVERIFY(files[0][5].toULongLong() != texts[0][2].toULongLong()); + bar.submissionResult(texts[0][2].toULongLong(), true); QVERIFY(editor->toPlainText().isEmpty()); + bar.submissionResult(files[0][5].toULongLong(), false); + QTest::keyClick(editor, Qt::Key_Return); QCOMPARE(files.size(), 2); QCOMPARE(texts.size(), 1); + QCOMPARE(files[1][4].toByteArray(), original); + bar.submissionResult(files[1][5].toULongLong(), true); + editor->setPlainText("next"); QTest::keyClick(editor, Qt::Key_Return); QVERIFY(texts[1][1].toByteArray().isEmpty()); + } + void initTestCase() { debug_config_init(); debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_DEBUG); QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); diff --git a/tools/chatgui/tests/test_invite_links.cpp b/tools/chatgui/tests/test_invite_links.cpp index ced15dbe..2d2ee7bb 100644 --- a/tools/chatgui/tests/test_invite_links.cpp +++ b/tools/chatgui/tests/test_invite_links.cpp @@ -18,6 +18,49 @@ extern "C" { class InviteLinksTest : public QObject { Q_OBJECT private slots: + void replyClick_data() { + QTest::addColumn("type"); QTest::addColumn("outgoing"); QTest::addColumn("width"); + for (const auto* type : {"text/plain", "audio/opus", "image/png", "video/mp4", "application/octet-stream", "poll", "emoji"}) + for (bool outgoing : {false, true}) for (int width : {280, 600}) + QTest::newRow(qPrintable(QString("%1-%2-%3").arg(type).arg(outgoing).arg(width))) << QString(type) << outgoing << width; + } + + void replyClick() { + QFETCH(QString, type); QFETCH(bool, outgoing); QFETCH(int, width); + QStandardItemModel model; auto* message = new QStandardItem; + message->setData(type == "emoji" ? QString::fromUtf8("😀") : QString("Answer"), MsgTextRole); + message->setData(type == "emoji" ? "text/plain" : type, MsgContentTypeRole); + message->setData(outgoing, MsgIsOutgoingRole); message->setData("1001", MsgChannelIdRole); + message->setData(QString(64, 'a'), MsgMessageKeyRole); message->setData("Alice", MsgAuthorRole); + message->setData("12:34", MsgTimeRole); message->setData("example.txt", MsgFileDisplayNameRole); + message->setData("0.5s", MsgVoiceDurationRole); message->setData(QString(64, 'b'), MsgReplyToRole); + message->setData("Bob", MsgQuoteAuthorRole); message->setData(QString(2000, 'x'), MsgQuoteTextRole); + model.appendRow(message); + ChatView view; MessageDelegate delegate; view.setModel(&model); view.setItemDelegate(&delegate); + view.setSelectionMode(QAbstractItemView::NoSelection); view.resize(width, 500); view.show(); QTest::qWait(10); + const auto index = model.index(0, 0); QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view; + const int before = delegate.sizeHint(option, index).height(); + message->setData(true, MsgHasQuoteRole); view.doItemsLayout(); + option.rect = view.visualRect(index); + QVERIFY(delegate.sizeHint(option, index).height() > before); + QVERIFY(delegate.sizeHint(option, index).height() < before + 120); + QSignalSpy replies(&delegate, &MessageDelegate::replyRequested), polls(&delegate, &MessageDelegate::pollRequested); + int downloads = 0; delegate.setDownloadCallback([&](const QModelIndex&) { downloads++; return true; }); + // Верхняя цитата у медиа/emoji; у текста она ниже имени автора. + const bool text = type == "text/plain" || type == "poll"; + const int y = text ? 8 + (outgoing ? 0 : QFontMetrics(view.font()).height() + 2) + 6 : 6; + const QPoint quote(view.viewport()->width() / 2, y); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, quote); + QCOMPARE(replies.size(), 1); QCOMPARE(replies[0][0].toString(), QString(64, 'b')); + QCOMPARE(downloads, 0); QCOMPARE(polls.size(), 0); + QTest::mouseClick(view.viewport(), Qt::RightButton, Qt::NoModifier, quote); QCOMPARE(replies.size(), 1); + QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, quote); + QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, quote + QPoint(30, 0)); + QCOMPARE(replies.size(), 1); + QImage image(option.rect.size(), QImage::Format_ARGB32); image.fill(Qt::transparent); + QPainter painter(&image); delegate.paint(&painter, option, index); painter.end(); + } + void initTestCase() { debug_config_init(); } void pollLeftClick_data() { QTest::addColumn("outgoing"); QTest::addColumn("width");