From be7421e6e2fbca28916be5f52fc89582a5767e76 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 00:25:23 +0100 Subject: [PATCH] Verify visible copy confirmation, expiry and repeated clicks --- doc/message_links.md | 2 ++ tools/chatgui/tests/test_invite_links.cpp | 29 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/doc/message_links.md b/doc/message_links.md index 1f69ebf0e..beb5243d8 100644 --- a/doc/message_links.md +++ b/doc/message_links.md @@ -36,6 +36,8 @@ int value = 42; сохраняются. Длинные строки визуально переносятся без изменения копируемого текста. Над каждым блоком находится кнопка Copy: копирует весь код без ограждений и метки языка, включая пробелы, табуляции и пустые строки. Перетаскивание не активирует кнопку. +После копирования кнопка на 1,5 секунды подсвечивается и показывает «Скопировано»; +повторное нажатие продлевает подтверждение. Размер кнопки при этом не меняется. В шапке слева показано имя указанного/распознанного языка (например, C++ или JavaScript), справа — Copy. Если язык определить не удалось, показано `text`; неизвестная явная метка показывается как написана в ограждении. Внутри кода нет форматирования маркерами, кликабельных ссылок и анимированных emoji. diff --git a/tools/chatgui/tests/test_invite_links.cpp b/tools/chatgui/tests/test_invite_links.cpp index d8fd87ccb..df67b41c0 100644 --- a/tools/chatgui/tests/test_invite_links.cpp +++ b/tools/chatgui/tests/test_invite_links.cpp @@ -440,6 +440,35 @@ private slots: QVERIFY2(elapsed < 5000, "Message link parsing exceeded the UI regression guard"); } + void codeCopyConfirmation() { + QStandardItemModel model(1, 1); const auto index = model.index(0, 0); + model.setData(index, "```cpp\nint value = 42;\n```", MsgTextRole); + ChatView view; MessageDelegate delegate; + view.setModel(&model); view.setItemDelegate(&delegate); view.resize(400, 220); view.show(); QTest::qWait(30); + QStyleOptionViewItem option; option.initFrom(&view); option.widget = &view; option.rect = view.visualRect(index); + QPoint button; bool found = false; + for (int y = 0; y < option.rect.height() && !found; y += 4) for (int x = 0; x < view.viewport()->width(); x += 8) { + if (delegate.codeCopyAt(option, index, QPoint(x, y)) != 0) continue; + button = QPoint(x, y); found = true; break; + } + QVERIFY(found); + const auto original = view.viewport()->grab().toImage(); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, button); + QCOMPARE(QApplication::clipboard()->text(), QString("int value = 42;")); + const auto copied = view.viewport()->grab().toImage(); + QVERIFY(copied != original); + QVERIFY(view.grab().save(QDir::tempPath() + "/code-copy-confirmation.png")); + QTest::qWait(800); + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, button); + QTest::qWait(800); // Первый timeout уже прошёл, повторное нажатие должно продлить надпись. + QCOMPARE(view.viewport()->grab().toImage(), copied); + QTRY_COMPARE_WITH_TIMEOUT(view.viewport()->grab().toImage(), original, 2000); + // Уничтожение строки и вида во время подтверждения безопасно. + QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, button); + model.clear(); QCoreApplication::processEvents(); + QCOMPARE(model.rowCount(), 0); + } + void codeBlockCopy_data() { QTest::addColumn("outgoing"); QTest::addColumn("dark"); QTest::addColumn("width"); for (bool outgoing : {false, true}) for (bool dark : {false, true}) for (int width : {280, 700})