|
|
|
|
@ -2,6 +2,7 @@
|
|
|
|
|
#include "../src/messagedelegate.h" |
|
|
|
|
#include "../src/chatview.h" |
|
|
|
|
#include "../src/accountdelegate.h" |
|
|
|
|
#include "../src/message_links.h" |
|
|
|
|
#include <QtTest> |
|
|
|
|
#include <QStandardItemModel> |
|
|
|
|
#include <QClipboard> |
|
|
|
|
@ -12,6 +13,80 @@ extern "C" {
|
|
|
|
|
class InviteLinksTest : public QObject { |
|
|
|
|
Q_OBJECT |
|
|
|
|
private slots: |
|
|
|
|
void messageLinkBoundaries() { |
|
|
|
|
QFile fixture(QFINDTESTDATA("message_links_cases.tsv")); |
|
|
|
|
QVERIFY(fixture.open(QIODevice::ReadOnly)); |
|
|
|
|
int count = 0; |
|
|
|
|
for (const auto& line : QString::fromUtf8(fixture.readAll()).split('\n')) { |
|
|
|
|
if (line.isEmpty() || line.startsWith('#')) continue; |
|
|
|
|
auto columns = line.split('\t'); |
|
|
|
|
QString text = columns.takeFirst(); text.replace(QStringLiteral("\\n"), QStringLiteral("\n")); |
|
|
|
|
const auto links = messageLinks(text); |
|
|
|
|
QStringList urls; |
|
|
|
|
int end = 0; |
|
|
|
|
for (const auto& link : links) { |
|
|
|
|
urls.append(link.url); |
|
|
|
|
QVERIFY(link.start >= end); |
|
|
|
|
QCOMPARE(text.mid(link.start, link.length), link.url); |
|
|
|
|
end = link.start + link.length; |
|
|
|
|
} |
|
|
|
|
QCOMPARE(urls, columns); |
|
|
|
|
count++; |
|
|
|
|
} |
|
|
|
|
QVERIFY(count >= 15); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void webLinksUsePaintedCoordinatesAndDragDoesNotOpen() { |
|
|
|
|
const QString first = QStringLiteral("https://пример.рф/путь?") + QString(160, 'a') + QStringLiteral("=1#fragment"); |
|
|
|
|
const QString second = QStringLiteral("http://example.org/a(b)?x=2"); |
|
|
|
|
const QString invite = QStringLiteral("utun://abc+/=="); |
|
|
|
|
const QString text = QStringLiteral("✨ (%1),\nВторая: %2.\nПриглашение: %3").arg(first, second, invite); |
|
|
|
|
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); |
|
|
|
|
QMap<QString, QPoint> points; |
|
|
|
|
QSet<int> rows; |
|
|
|
|
for (int y = 8; y < option.rect.height(); y += 8) { |
|
|
|
|
for (int x = 8; x < view.viewport()->width(); x += 12) { |
|
|
|
|
const QString hit = delegate.linkAt(option, index, QPoint(x, y)); |
|
|
|
|
if (hit.isEmpty()) continue; |
|
|
|
|
QVERIFY(hit == first || hit == second || hit == invite); |
|
|
|
|
points[hit] = QPoint(x, y); |
|
|
|
|
if (hit == first) rows.insert(y); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
QCOMPARE(points.size(), 3); QVERIFY(rows.size() > 4); |
|
|
|
|
QVERIFY(delegate.linkAt(option, index, QPoint(0, 0)).isEmpty()); |
|
|
|
|
QSignalSpy clicked(&view, &ChatView::linkActivated); |
|
|
|
|
for (const auto& link : {first, second, invite}) { |
|
|
|
|
QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, points.value(link)); |
|
|
|
|
QCOMPARE(clicked.last().first().toString(), link); |
|
|
|
|
} |
|
|
|
|
QCOMPARE(clicked.count(), 3); |
|
|
|
|
const QPoint start = points.value(first), end = start + QPoint(20, 0); |
|
|
|
|
QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::NoModifier, start); |
|
|
|
|
QMouseEvent drag(QEvent::MouseMove, end, view.viewport()->mapToGlobal(end), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); |
|
|
|
|
QApplication::sendEvent(view.viewport(), &drag); |
|
|
|
|
QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::NoModifier, end); |
|
|
|
|
QTest::qWait(20); |
|
|
|
|
QCOMPARE(clicked.count(), 3); |
|
|
|
|
const auto selection = view.textSelection(); |
|
|
|
|
QVERIFY(selection.startChar >= text.indexOf(first) && selection.endChar <= text.indexOf(first) + first.length()); |
|
|
|
|
QVERIFY(selection.endChar > selection.startChar); |
|
|
|
|
QTest::keyClick(&view, Qt::Key_C, Qt::ControlModifier); |
|
|
|
|
QCOMPARE(QApplication::clipboard()->text(), text.mid(selection.startChar, selection.endChar - selection.startChar)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void compactInvitationUsesFullLinkForClickAndCopy() { |
|
|
|
|
struct InviteData invite = {}; |
|
|
|
|
invite.channelId = 42; invite.join_key = 123; invite.pubkey[0] = 1; invite.addrCount = 8; |
|
|
|
|
|