/* Проверка реальных координат перенесённых ссылок и кнопки приглашения в строке участника. */ #include "../src/messagedelegate.h" #include "../src/chatview.h" #include "../src/accountdelegate.h" #include #include #include extern "C" { #include "chat/invite_link.h" } class InviteLinksTest : public QObject { Q_OBJECT private slots: void compactInvitationUsesFullLinkForClickAndCopy() { struct InviteData invite = {}; invite.channelId = 42; invite.join_key = 123; invite.pubkey[0] = 1; invite.addrCount = 8; for (auto& address : invite.addrs) { address.family = 6; address.proto = INVITE_PROTO_TCP; address.port = 443; } char buffer[1024]; QVERIFY(invite_link_encode(&invite, nullptr, buffer, sizeof(buffer)) > 0); const QString link = QString::fromUtf8(buffer); const QString text = QStringLiteral("Приглашение в группу «Группа»\n") + link; for (int width : {240, 480}) { QStandardItemModel model; auto* message = new QStandardItem; message->setData(text, MsgTextRole); message->setData(link, MsgInviteLinkRole); message->setData(QStringLiteral("text/plain"), MsgContentTypeRole); message->setData(true, MsgIsOutgoingRole); model.appendRow(message); ChatView view; MessageDelegate delegate; view.setModel(&model); view.setItemDelegate(&delegate); view.resize(width, 300); view.setLinkHitTest([&](const QModelIndex& index, const QPoint& point) { QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view; option.rect = view.visualRect(index); return delegate.linkAt(option, index, point); }); view.show(); QTest::qWait(30); const QModelIndex index = model.index(0, 0); QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view; option.rect = view.visualRect(index); QPoint firstPoint, lastPoint; QSet rows; for (int y = 0; y < option.rect.height(); ++y) { for (int x = 0; x < view.viewport()->width(); x += 2) { const QString hit = delegate.linkAt(option, index, QPoint(x, y)); if (hit.isEmpty()) continue; QCOMPARE(hit, link); rows.insert(y); if (firstPoint.isNull()) firstPoint = QPoint(x, y); lastPoint = QPoint(x, y); } } QVERIFY(!firstPoint.isNull()); QVERIFY(rows.size() <= QFontMetrics(option.font).height() + 1); QVERIFY(option.rect.height() < 160); QSignalSpy clicked(&view, &ChatView::linkActivated); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, lastPoint); QCOMPARE(clicked.count(), 1); QCOMPARE(clicked.last().first().toString(), link); QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, firstPoint); QMouseEvent drag(QEvent::MouseMove, lastPoint, view.viewport()->mapToGlobal(lastPoint), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(view.viewport(), &drag); QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, lastPoint); QTest::qWait(20); QCOMPARE(clicked.count(), 1); QTest::keyClick(&view, Qt::Key_C, Qt::ControlModifier); QCOMPARE(QApplication::clipboard()->text(), link); QCOMPARE(index.data(MsgTextRole).toString(), text); } } void wrappedLinksPreserveTheirCompleteText() { struct InviteData invite = {}; invite.channelId = 42; invite.join_key = 123; invite.pubkey[0] = 1; invite.addrCount = 8; for (auto& address : invite.addrs) { address.family = 6; address.proto = INVITE_PROTO_TCP; address.port = 443; } char buffer[1024]; QVERIFY(invite_link_encode(&invite, nullptr, buffer, sizeof(buffer)) > 0); const QString first = QString::fromUtf8(buffer); invite.join_key++; QVERIFY(invite_link_encode(&invite, nullptr, buffer, sizeof(buffer)) > 0); const QString second = QString::fromUtf8(buffer); const QString text = QStringLiteral("Приглашение\n(%1),\nВторая группа: %2.").arg(first, second); QStandardItemModel model; auto* message = new QStandardItem; message->setData(text, MsgTextRole); message->setData(QStringLiteral("text/plain"), MsgContentTypeRole); message->setData(true, MsgIsOutgoingRole); model.appendRow(message); ChatView view; MessageDelegate delegate; view.setModel(&model); view.setItemDelegate(&delegate); view.resize(280, 900); view.setLinkHitTest([&](const QModelIndex& index, const QPoint& point) { QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view; option.rect = view.visualRect(index); return delegate.linkAt(option, index, point); }); view.show(); QTest::qWait(30); const QModelIndex index = model.index(0, 0); QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view; option.rect = view.visualRect(index); QPoint firstPoint, secondPoint; QSet firstRows; for (int y = 10; y < option.rect.height(); y += 8) { for (int x = 10; x < view.viewport()->width(); x += 16) { const QString link = delegate.linkAt(option, index, QPoint(x, y)); if (link == first) { if (firstPoint.isNull()) firstPoint = QPoint(x, y); firstRows.insert(y); } else if (link == second) secondPoint = QPoint(x, y); else QVERIFY(link.isEmpty()); } } QVERIFY(firstRows.size() > 4); QVERIFY(!firstPoint.isNull()); QVERIFY(!secondPoint.isNull()); QVERIFY(delegate.linkAt(option, index, QPoint(0, 0)).isEmpty()); QSignalSpy clicked(&view, &ChatView::linkActivated); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, firstPoint); QCOMPARE(clicked.count(), 1); QCOMPARE(clicked.last().first().toString(), first); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, secondPoint); QCOMPARE(clicked.count(), 2); QCOMPARE(clicked.last().first().toString(), second); /* Выделение использует те же перенесённые строки, что и отрисовка ссылки. */ QPoint dragEnd = firstPoint + QPoint(20, 0); QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, firstPoint); QMouseEvent drag(QEvent::MouseMove, dragEnd, view.viewport()->mapToGlobal(dragEnd), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(view.viewport(), &drag); QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, dragEnd); QTest::qWait(20); QCOMPARE(clicked.count(), 2); const auto selection = view.textSelection(); QVERIFY(selection.startChar >= text.indexOf(first)); QVERIFY(selection.endChar <= text.indexOf(first) + first.length()); QVERIFY(selection.endChar > selection.startChar); QVERIFY(selection.endChar - selection.startChar < 20); } void memberPlusTargetsOtherMembersOnly() { QStandardItemModel model; auto* other = new QStandardItem("other"); other->setData(false, Qt::UserRole + 2); model.appendRow(other); auto* self = new QStandardItem("self"); self->setData(true, Qt::UserRole + 2); model.appendRow(self); QListView view; AccountDelegate delegate; view.setModel(&model); view.setItemDelegate(&delegate); view.resize(240, 140); view.show(); QTest::qWait(30); QSignalSpy invites(&delegate, &AccountDelegate::inviteRequested); const QRect row = view.visualRect(model.index(0, 0)); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(row.right() - 17, row.center().y())); QCOMPARE(invites.count(), 1); QCOMPARE(qvariant_cast(invites.first().first()).row(), 0); const QRect own = view.visualRect(model.index(1, 0)); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(own.right() - 17, own.center().y())); QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(60, row.center().y())); QCOMPARE(invites.count(), 1); } }; QTEST_MAIN(InviteLinksTest) #include "test_invite_links.moc"