Browse Source

Verify transparent reaction footer clicks across text and media layouts

master
evgeny 5 days ago
parent
commit
4f0870b961
  1. 4
      tools/chatgui/src/messagedelegate.cpp
  2. 70
      tools/chatgui/tests/test_invite_links.cpp

4
tools/chatgui/src/messagedelegate.cpp

@ -410,7 +410,8 @@ MessageDelegate::Layout MessageDelegate::calcLayout(
|| index.data(MsgMessageKeyRole).toString().size() != 64 || layout.bubbleRect.isEmpty()) return layout;
QFont font = option.font; font.setPointSize(qMax(1, font.pointSize() - 1));
const int height = qMax(28, QFontMetrics(font).height() + 8), left = layout.bubbleRect.left();
int y = layout.bubbleRect.bottom() + 4, x = left + height + 4;
int y = qMax(layout.bubbleRect.bottom(), qMax(layout.statusRect.bottom(), layout.deliveryRect.bottom())) + 4;
int x = left + height + 4;
layout.reactionHeart = QRect(left, y, height, height);
for (const auto& value : index.data(MsgReactionsRole).toList()) {
const auto reaction = value.toMap();
@ -435,6 +436,7 @@ MessageDelegate::Layout MessageDelegate::calcBodyLayout(
if (v) L.viewWidth = v->viewport()->width();
}
if (L.viewWidth <= 0) L.viewWidth = 300;
L.narrow = L.viewWidth - 2 * kMarginH - kAvatar - kGap < 400;
bool hasQuote = index.data(MsgHasQuoteRole).toBool();
QString text = index.data(MsgTextRole).toString();

70
tools/chatgui/tests/test_invite_links.cpp

@ -6,6 +6,7 @@
#include <QtTest>
#include <QStandardItemModel>
#include <QClipboard>
#include <QPainter>
extern "C" {
#include "chat/invite_link.h"
}
@ -13,6 +14,75 @@ extern "C" {
class InviteLinksTest : public QObject {
Q_OBJECT
private slots:
void reactionFooter_data() {
QTest::addColumn<QString>("type"); QTest::addColumn<bool>("outgoing"); QTest::addColumn<int>("width");
for (const auto& type : {"text/plain", "audio/opus", "image/png", "video/mp4", "application/octet-stream", "poll"})
for (bool outgoing : {false, true}) for (int width : {280, 600}) {
const QByteArray name = QString("%1-%2-%3").arg(type).arg(outgoing).arg(width).toUtf8();
QTest::newRow(name.constData()) << QString(type) << outgoing << width;
}
}
void reactionFooter() {
QFETCH(QString, type); QFETCH(bool, outgoing); QFETCH(int, width);
QStandardItemModel model; auto* message = new QStandardItem;
message->setData(type == "text/plain" || type == "poll" ? QString("Example message") : QString::fromUtf8("😀"), MsgTextRole);
message->setData(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("00:05", MsgVoiceDurationRole); message->setData("example.dat", MsgFileDisplayNameRole);
message->setData(1, MsgIdRole); message->setData(1, MsgFileDownloadStateRole);
message->setData(QVariantList{QVariantMap{{"id", 7}, {"emoji", QString::fromUtf8("👍")}, {"count", 2}, {"mine", true}}}, MsgReactionsRole);
model.appendRow(message);
ChatView view; MessageDelegate delegate; view.setModel(&model); view.setItemDelegate(&delegate);
view.setSelectionMode(QAbstractItemView::NoSelection); view.resize(width, 480); view.show(); QTest::qWait(10);
int downloads = 0; delegate.setDownloadCallback([&](const QModelIndex&) { downloads++; return true; });
const auto index = model.index(0, 0); QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view;
option.rect = view.visualRect(index); option.rect.setHeight(delegate.sizeHint(option, index).height());
QSignalSpy requested(&delegate, &MessageDelegate::reactionRequested);
QMap<int, QPoint> points; QRect heartHits;
/* Ищем области по фактическим событиям, не повторяя вычисления layout. */
for (int y = 1; y < option.rect.height(); y += 4) for (int x = 1; x < view.viewport()->width(); x += 4) {
const QPoint point(x, y);
QMouseEvent press(QEvent::MouseButtonPress, point, view.viewport()->mapToGlobal(point), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QMouseEvent release(QEvent::MouseButtonRelease, point, view.viewport()->mapToGlobal(point), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
requested.clear(); delegate.editorEvent(&press, &model, option, index); delegate.editorEvent(&release, &model, option, index);
if (!requested.isEmpty()) {
QCOMPARE(requested.count(), 1); const int emoji = requested.first().at(1).toInt();
if (!points.contains(emoji)) points[emoji] = point;
if (emoji == -1) heartHits = heartHits.isValid() ? heartHits.united(QRect(point, QSize(1, 1))) : QRect(point, QSize(1, 1));
}
}
QVERIFY(points.contains(-1) && points.contains(7)); downloads = 0; requested.clear();
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, points[-1]);
QCOMPARE(requested.count(), 1); QCOMPARE(requested.last().at(1).toInt(), -1);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, points[7]);
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, points[7]);
QCOMPARE(requested.count(), 3); QCOMPARE(requested.last().at(1).toInt(), 7); QCOMPARE(downloads, 0);
QTest::mouseClick(view.viewport(), Qt::RightButton, Qt::NoModifier, points[-1]); QCOMPARE(requested.count(), 3);
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, points[-1]);
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, points[-1] + QPoint(20, 0));
QCOMPARE(requested.count(), 3); QCOMPARE(downloads, 0);
const QColor background(70, 80, 90); QImage image(option.rect.size(), QImage::Format_ARGB32); image.fill(background);
QPainter painter(&image); delegate.paint(&painter, option, index); painter.end();
QCOMPARE(image.pixelColor(heartHits.center()), background); /* внутри сердечко прозрачно */
bool whiteContour = false;
for (int y = heartHits.top(); y <= heartHits.bottom(); y++) for (int x = heartHits.left(); x <= heartHits.right(); x++)
if (image.pixelColor(x, y) == QColor(Qt::white)) whiteContour = true;
QVERIFY(whiteContour);
if (type == "text/plain" && !outgoing && width == 600) QVERIFY(image.save("/tmp/utun2-reaction-heart-preview.png"));
}
void largeEmojiHasNoReactionFooter() {
QStandardItemModel model; auto* message = new QStandardItem;
message->setData(QString::fromUtf8("😀"), MsgTextRole); message->setData("1001", MsgChannelIdRole); model.appendRow(message);
ChatView view; MessageDelegate delegate; view.setModel(&model); view.setItemDelegate(&delegate); view.resize(600, 240);
QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view;
const auto index = model.index(0, 0); const auto size = delegate.sizeHint(option, index);
message->setData(QString(64, 'a'), MsgMessageKeyRole); QCOMPARE(delegate.sizeHint(option, index), size);
}
void messageLinkBoundaries() {
QFile fixture(QFINDTESTDATA("message_links_cases.tsv"));
QVERIFY(fixture.open(QIODevice::ReadOnly));

Loading…
Cancel
Save