178 lines
6.8 KiB
C++
178 lines
6.8 KiB
C++
#include <gtest/gtest.h>
|
||
|
||
#include <QJsonArray>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QTextBlock>
|
||
#include <QTextDocument>
|
||
#include <QTextFragment>
|
||
|
||
#include "panels/QuillDelta.hpp"
|
||
|
||
using namespace geopro::app;
|
||
|
||
namespace {
|
||
|
||
QJsonArray opsFromJson(const char* json) {
|
||
return QJsonDocument::fromJson(json).array();
|
||
}
|
||
|
||
// 从 Delta ops 取第 idx 个文本 op 的 attributes(便于断言)。
|
||
QJsonObject attrsOf(const QJsonArray& ops, int idx) {
|
||
return ops.at(idx).toObject().value(QStringLiteral("attributes")).toObject();
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// ── 反序列化(Delta → 文档):基本行内样式落到字符格式 ──────────────────────
|
||
TEST(QuillDelta, DeltaToDocumentAppliesInlineFormats) {
|
||
const auto ops = opsFromJson(R"([
|
||
{"insert":"Hello","attributes":{"bold":true,"italic":true,"underline":true,
|
||
"color":"#ff0000","size":"24px"}},
|
||
{"insert":"\n"}
|
||
])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
|
||
EXPECT_EQ(doc.toPlainText(), QStringLiteral("Hello"));
|
||
const QTextFragment frag = doc.begin().begin().fragment();
|
||
ASSERT_TRUE(frag.isValid());
|
||
const QTextCharFormat f = frag.charFormat();
|
||
EXPECT_GE(f.fontWeight(), QFont::Bold);
|
||
EXPECT_TRUE(f.fontItalic());
|
||
EXPECT_TRUE(f.fontUnderline());
|
||
EXPECT_EQ(f.foreground().color().name(QColor::HexRgb), QStringLiteral("#ff0000"));
|
||
EXPECT_NEAR(f.fontPointSize(), 24.0 * 3.0 / 4.0, 0.01); // 24px → 18pt
|
||
}
|
||
|
||
// ── 序列化(文档 → Delta):行内样式回写 attributes ─────────────────────────
|
||
TEST(QuillDelta, DocumentToDeltaEmitsInlineAttrs) {
|
||
QTextDocument doc;
|
||
QTextCursor cur(&doc);
|
||
QTextCharFormat f;
|
||
f.setFontWeight(QFont::Bold);
|
||
f.setForeground(QColor(QStringLiteral("#00ff00")));
|
||
cur.insertText(QStringLiteral("World"), f);
|
||
|
||
const QJsonArray ops = documentToDelta(doc);
|
||
ASSERT_GE(ops.size(), 1);
|
||
EXPECT_EQ(ops.at(0).toObject().value(QStringLiteral("insert")).toString(),
|
||
QStringLiteral("World"));
|
||
const QJsonObject a = attrsOf(ops, 0);
|
||
EXPECT_TRUE(a.value(QStringLiteral("bold")).toBool());
|
||
EXPECT_EQ(a.value(QStringLiteral("color")).toString(), QStringLiteral("#00ff00"));
|
||
}
|
||
|
||
// ── 往返:纯文本不丢、不多生成空块 ─────────────────────────────────────────
|
||
TEST(QuillDelta, RoundTripPlainTextSingleLine) {
|
||
const auto ops = opsFromJson(R"([{"insert":"just text"},{"insert":"\n"}])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
EXPECT_EQ(doc.toPlainText(), QStringLiteral("just text"));
|
||
EXPECT_EQ(doc.blockCount(), 1); // 不应多出尾部空块
|
||
|
||
const QJsonArray back = documentToDelta(doc);
|
||
QString text;
|
||
for (const QJsonValue& v : back)
|
||
text += v.toObject().value(QStringLiteral("insert")).toString();
|
||
EXPECT_EQ(text, QStringLiteral("just text\n"));
|
||
}
|
||
|
||
// ── 往返:多行 + 行内样式保持 ──────────────────────────────────────────────
|
||
TEST(QuillDelta, RoundTripMultilineWithBoldPreserved) {
|
||
const auto ops = opsFromJson(R"([
|
||
{"insert":"line1 "},
|
||
{"insert":"bold","attributes":{"bold":true}},
|
||
{"insert":"\nline2\n"}
|
||
])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
EXPECT_EQ(doc.toPlainText(), QStringLiteral("line1 bold\nline2"));
|
||
EXPECT_EQ(doc.blockCount(), 2);
|
||
|
||
// 第二趟往返稳定:重新序列化后再反序列化文本一致。
|
||
const QJsonArray back = documentToDelta(doc);
|
||
QTextDocument doc2;
|
||
deltaToDocument(back, doc2);
|
||
EXPECT_EQ(doc2.toPlainText(), QStringLiteral("line1 bold\nline2"));
|
||
EXPECT_EQ(doc2.blockCount(), 2);
|
||
}
|
||
|
||
// ── 块级:标题落到 headingLevel 并回写 header ──────────────────────────────
|
||
TEST(QuillDelta, HeaderBlockRoundTrip) {
|
||
const auto ops = opsFromJson(R"([
|
||
{"insert":"Title"},
|
||
{"insert":"\n","attributes":{"header":2}},
|
||
{"insert":"body"},
|
||
{"insert":"\n"}
|
||
])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
EXPECT_EQ(doc.begin().blockFormat().headingLevel(), 2);
|
||
|
||
const QJsonArray back = documentToDelta(doc);
|
||
// 找到带 header 的换行 op。
|
||
bool found = false;
|
||
for (const QJsonValue& v : back) {
|
||
const QJsonObject op = v.toObject();
|
||
if (op.value(QStringLiteral("insert")).toString() == QStringLiteral("\n") &&
|
||
op.value(QStringLiteral("attributes")).toObject().value(QStringLiteral("header")).toInt() == 2) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
EXPECT_TRUE(found);
|
||
}
|
||
|
||
// ── 块级:有序列表 → ListDecimal,回写 list:ordered ───────────────────────
|
||
TEST(QuillDelta, OrderedListBlockRoundTrip) {
|
||
const auto ops = opsFromJson(R"([
|
||
{"insert":"item"},
|
||
{"insert":"\n","attributes":{"list":"ordered"}}
|
||
])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
ASSERT_NE(doc.begin().textList(), nullptr);
|
||
|
||
const QJsonArray back = documentToDelta(doc);
|
||
bool found = false;
|
||
for (const QJsonValue& v : back) {
|
||
const QJsonObject a = v.toObject().value(QStringLiteral("attributes")).toObject();
|
||
if (a.value(QStringLiteral("list")).toString() == QStringLiteral("ordered")) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
EXPECT_TRUE(found);
|
||
}
|
||
|
||
// ── 容错:无法识别的 attributes 降级(保留文本,不崩) ──────────────────────
|
||
TEST(QuillDelta, UnknownAttributesDegradeGracefully) {
|
||
const auto ops = opsFromJson(R"([
|
||
{"insert":"keepme","attributes":{"script":"super","strike":true,"link":"http://x"}},
|
||
{"insert":"\n"}
|
||
])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
EXPECT_EQ(doc.toPlainText(), QStringLiteral("keepme"));
|
||
}
|
||
|
||
// ── 容错:非文本 insert(图片/嵌入对象)被丢弃,不崩 ───────────────────────
|
||
TEST(QuillDelta, NonStringInsertDropped) {
|
||
const auto ops = opsFromJson(R"([
|
||
{"insert":"text"},
|
||
{"insert":{"image":"data:..."}},
|
||
{"insert":"\n"}
|
||
])");
|
||
QTextDocument doc;
|
||
deltaToDocument(ops, doc);
|
||
EXPECT_EQ(doc.toPlainText(), QStringLiteral("text"));
|
||
}
|
||
|
||
// ── 空 ops:空文档 ─────────────────────────────────────────────────────────
|
||
TEST(QuillDelta, EmptyOpsYieldEmptyDocument) {
|
||
QTextDocument doc;
|
||
deltaToDocument(QJsonArray{}, doc);
|
||
EXPECT_TRUE(doc.toPlainText().isEmpty());
|
||
}
|