Files
utun2/tools/chatgui/tests/test_message_text.cpp
T

113 lines
7.4 KiB
C++

/* Разметка, реальные языковые определения, кеш и метрики текста без участия сетевого ядра. */
#include "../src/message_text.h"
#include "../src/messagedelegate.h"
#include "../../../lib/debug_config.h"
#include <QtTest>
#include <QStandardItemModel>
class MessageTextTest : public QObject {
Q_OBJECT
private slots:
void initTestCase() { debug_enable_console(true); debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_DEBUG); }
void parse_data() {
QTest::addColumn<QString>("source"); QTest::addColumn<QString>("visible");
QTest::addColumn<int>("styles"); QTest::addColumn<int>("blocks");
QTest::newRow("plain") << "hello\nworld" << "hello\nworld" << 0 << 0;
QTest::newRow("styles") << "**жирный** *наклонный* ~~удалённый~~" << "жирный наклонный удалённый" << 3 << 0;
QTest::newRow("unclosed") << "**hello *world ~~bye" << "**hello *world ~~bye" << 0 << 0;
QTest::newRow("no-multiline-style") << "**hello\nworld**" << "**hello\nworld**" << 0 << 0;
QTest::newRow("empty-style") << "**** ** ** ~~~~" << "**** ** ** ~~~~" << 0 << 0;
QTest::newRow("no-nesting") << "**hello *world* **" << "**hello world **" << 1 << 0;
QTest::newRow("nested-literal") << "**hello *world***" << "**hello *world***" << 0 << 0;
QTest::newRow("escapes") << R"(\*hello\* \\ ~~bye~~)" << "*hello* \\ bye" << 1 << 0;
QTest::newRow("code") << "before\n```cpp\n int x;\n\t/* **literal** */\n\n```\nafter"
<< "before\n int x;\n\t/* **literal** */\n\nafter" << 0 << 1;
QTest::newRow("empty-code") << "```\n```" << "" << 0 << 1;
QTest::newRow("windows-newlines") << "```cpp\r\nint x;\r\n```" << "int x;" << 0 << 1;
QTest::newRow("blank-code") << "```\n\n\n```" << "\n" << 0 << 1;
QTest::newRow("two-blocks") << "```c\nint x;\n```\n**bold**\n```python\npass\n```"
<< "int x;\nbold\npass" << 1 << 2;
QTest::newRow("unclosed-code") << "```cpp\nint x;" << "```cpp\nint x;" << 0 << 0;
QTest::newRow("inline-fence") << "text ``` code ```" << "text ``` code ```" << 0 << 0;
QTest::newRow("indented-fence") << " ```\nx\n ```" << " ```\nx\n ```" << 0 << 0;
QTest::newRow("trailing-fence-text") << "```cpp extra\nx\n```" << "```cpp extra\nx\n```" << 0 << 0;
QTest::newRow("no-word-markup") << "some*identifier* a**b**c" << "some*identifier* a**b**c" << 0 << 0;
QTest::newRow("url-markers") << "https://example.com/*path*/ **https://example.org**"
<< "https://example.com/*path*/ https://example.org" << 1 << 0;
}
void parse() {
QFETCH(QString, source); QFETCH(QString, visible); QFETCH(int, styles); QFETCH(int, blocks);
const auto parsed = parseMessageText(source);
QCOMPARE(parsed.text, visible); QCOMPARE(parsed.styles.size(), styles); QCOMPARE(parsed.code.size(), blocks);
for (const auto& style : parsed.styles) {
QVERIFY(style.start >= 0); QVERIFY(style.length > 0); QVERIFY(style.start + style.length <= parsed.text.size());
}
}
void stylePropertiesAndCodeLinks() {
const auto parsed = parseMessageText("**bold** *italic* ~~strike~~\n```cpp\nhttps://example.com\n```\nhttps://example.org");
QCOMPARE(parsed.styles[0].format.fontWeight(), QFont::Bold);
QVERIFY(parsed.styles[1].format.fontItalic()); QVERIFY(parsed.styles[2].format.fontStrikeOut());
QCOMPARE(parsed.links.size(), 1); QCOMPARE(parsed.links[0].url, QString("https://example.org"));
QVERIFY(parsed.isCode(parsed.text.indexOf("https://example.com")));
QVERIFY(!parsed.isCode(parsed.links[0].start));
}
void syntaxAndCache() {
QStandardItemModel model(1, 1); const auto index = model.index(0, 0);
const QString source = "```cpp\n/* comment\n continued */\nint x = 42;\n```";
model.setData(index, source, MsgTextRole);
const auto parsed = formattedMessageText(index); QVERIFY(parsed);
QCOMPARE(parsed->text, QString("/* comment\n continued */\nint x = 42;"));
QVERIFY(!parsed->lightSyntax.isEmpty()); QVERIFY(!parsed->darkSyntax.isEmpty());
auto colorAt = [&](int position) {
QColor color;
for (const auto& token : parsed->lightSyntax)
if (position >= token.start && position < token.start + token.length) color = token.format.foreground().color();
return color;
};
QVERIFY(colorAt(0).isValid()); QCOMPARE(colorAt(0), colorAt(parsed->text.indexOf("continued")));
QVERIFY(colorAt(0) != colorAt(parsed->text.indexOf("int")));
QCOMPARE(formattedMessageText(index).data(), parsed.data());
QCOMPARE(index.data(MsgTextRole).toString(), source);
model.setData(index, "```unknown_language\nint x;\n```", MsgTextRole);
const auto unknown = formattedMessageText(index); QVERIFY(unknown); QVERIFY(unknown->lightSyntax.isEmpty());
model.setData(index, "```\nint x;\n```", MsgTextRole);
const auto plainCode = formattedMessageText(index); QVERIFY(plainCode); QVERIFY(plainCode->lightSyntax.isEmpty());
model.setData(index, "```\n```", MsgTextRole);
const auto emptyCode = formattedMessageText(index); QVERIFY(emptyCode);
QTextLayout emptyLayout; emptyLayout.setFont(QApplication::font());
QVERIFY(layoutFormattedMessage(emptyLayout, *emptyCode, 200, false) > 0);
QCOMPARE(emptyLayout.lineCount(), 1); QVERIFY(emptyCode->isCode(emptyLayout.lineAt(0).textStart()));
model.setData(index, "**invitation**", MsgTextRole); model.setData(index, "utun://abc", MsgInviteLinkRole);
QVERIFY(!formattedMessageText(index));
model.setData(index, QString(), MsgInviteLinkRole); model.setData(index, "poll", MsgContentTypeRole);
QVERIFY(!formattedMessageText(index));
}
void layoutPreservesTextAndThemeGeometry() {
QStandardItemModel model(1, 1); const auto index = model.index(0, 0);
model.setData(index, "**Текст 😀**\n```cpp\n\tint value = 123456789; // длинная строка\n\n```\n*конец*", MsgTextRole);
const auto parsed = formattedMessageText(index); QVERIFY(parsed);
const auto original = parsed->text;
for (int width : {80, 200, 600}) {
QTextLayout light, dark; light.setFont(QApplication::font()); dark.setFont(light.font());
const qreal height = layoutFormattedMessage(light, *parsed, width, false);
QCOMPARE(layoutFormattedMessage(dark, *parsed, width, true), height);
QCOMPARE(light.lineCount(), dark.lineCount());
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "formatted layout: width=%d lines=%d height=%.1f", width, light.lineCount(), height);
QVERIFY(light.lineCount() >= 4);
for (int i = 0; i < light.lineCount(); ++i) {
QCOMPARE(light.lineAt(i).textStart(), dark.lineAt(i).textStart());
QCOMPARE(light.lineAt(i).naturalTextWidth(), dark.lineAt(i).naturalTextWidth());
QVERIFY(light.lineAt(i).y() + light.lineAt(i).height() <= height);
}
QCOMPARE(parsed->text, original);
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "message formatting: real C++ tokens, multiline state, theme geometry and cache verified");
}
};
QTEST_MAIN(MessageTextTest)
#include "test_message_text.moc"