Files
oak-editor/tests/gtest/common_html_test.cpp
T
Mike-Solar bb40b4923e style: unify identifier naming per updated conventions
Automated with clang-tidy readability-identifier-naming (config added to
.clang-tidy) plus scripted passes, per the updated rules now documented
in CONTRIBUTING.md:

- types (class/struct/enum/alias/template params): PascalCase
- functions, variables, members: snake_case (incl. rational -> Rational)
- private/protected members: trailing underscore; static member
  variables likewise (instance_, available_themes_)
- constants and enum values: snake_case (kLinear -> k_linear,
  F32P -> f32p); ALL_CAPS reserved for macros
- macros: OAK_ prefix (OLIVE_ADD_TEST/OLIVE_ASSERT/OLIVE_CONFIG ->
  OAK_ADD_TEST/OAK_ASSERT/OAK_CONFIG, GL_PREAMBLE -> OAK_GL_PREAMBLE,
  include guards -> OAK_*)
- file names: all lowercase (Current/Plugin/OliveHost/OliveClip/
  OlivePluginInstance -> current/plugin/olivehost/oliveclip/
  oliveplugininstance)
- getters share the member name sans underscore, setters set_foo()
- Qt and third-party (OpenFX) virtual overrides and framework callbacks
  keep their original names (exempt in .clang-tidy)

Manual follow-ups required where automation could not reach:
- string-based QMetaObject/SIGNAL/SLOT references updated to renamed
  methods (AddTask, CreatedFile, DeleteSpecificFile, moveSelectionUp, ...)
- macro bodies referencing renamed methods (OLIVE_CONFIG,
  NODE_DEFAULT_DESTRUCTOR, MANAGEDDISPLAYWIDGET_*)
- self-shadowing locals renamed where signals/methods became same-named
  (size_changed, worker_count, selected_items, import param, filters)
- third_party OFX member/namespace usages restored (OFX::Host::*,
  _created, _clipPrefsDirty, createInstance, clearPersistentMessage)
- STL protocol aliases restored (const_iterator) with .clang-tidy
  ignore rules; qHash overloads restored

Full build and test suite pass: ctest 4/4, ~1960 gtest cases green.
2026-07-19 16:10:54 +08:00

252 lines
6.5 KiB
C++

#include <gtest/gtest.h>
#include <QTextCursor>
#include <QTextDocument>
#include "common/html.h"
namespace
{
QTextDocument *make_doc(const QString &plain_text)
{
auto *doc = new QTextDocument();
QTextCursor c(doc);
c.insertText(plain_text);
return doc;
}
// Extract the single fragment of a single-block document for format checks.
QTextFragment only_fragment(QTextDocument *doc)
{
QTextBlock block = doc->begin();
auto it = block.begin();
EXPECT_NE(it, block.end());
return it.fragment();
}
} // namespace
TEST(CommonHtml, DocToHtmlWrapsTextInParagraph)
{
std::unique_ptr<QTextDocument> doc(make_doc(QStringLiteral("hello")));
const QString html = olive::Html::doc_to_html(doc.get());
EXPECT_TRUE(html.contains(QStringLiteral("<p")));
EXPECT_TRUE(html.contains(QStringLiteral("hello")));
EXPECT_TRUE(html.contains(QStringLiteral("</p>")));
}
TEST(CommonHtml, DocToHtmlEscapesSpecialCharacters)
{
std::unique_ptr<QTextDocument> doc(make_doc(QStringLiteral("a<b>&\"c\"")));
const QString html = olive::Html::doc_to_html(doc.get());
EXPECT_FALSE(html.contains(QStringLiteral("a<b>")));
EXPECT_TRUE(html.contains(QStringLiteral("&lt;")));
EXPECT_TRUE(html.contains(QStringLiteral("&amp;")));
}
TEST(CommonHtml, AlignmentIsWrittenAsAttribute)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignRight);
c.setBlockFormat(fmt);
c.insertText(QStringLiteral("right"));
const QString html = olive::Html::doc_to_html(&doc);
EXPECT_TRUE(html.contains(QStringLiteral("align=\"right\"")));
}
TEST(CommonHtml, CenterAlignmentIsWrittenAsAttribute)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignHCenter);
c.setBlockFormat(fmt);
c.insertText(QStringLiteral("center"));
const QString html = olive::Html::doc_to_html(&doc);
EXPECT_TRUE(html.contains(QStringLiteral("align=\"center\"")));
}
TEST(CommonHtml, LeftAlignmentWritesNoAlignAttribute)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignLeft);
c.setBlockFormat(fmt);
c.insertText(QStringLiteral("left"));
const QString html = olive::Html::doc_to_html(&doc);
EXPECT_FALSE(html.contains(QStringLiteral("align=")));
}
TEST(CommonHtml, CharFormatRoundTrip)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextCharFormat fmt;
fmt.setFontWeight(QFont::Bold);
fmt.setFontItalic(true);
fmt.setFontUnderline(true);
fmt.setFontStrikeOut(true);
fmt.setFontOverline(true);
fmt.setFontPointSize(24.0);
fmt.setForeground(QColor(255, 0, 0));
c.insertText(QStringLiteral("styled"), fmt);
const QString html = olive::Html::doc_to_html(&doc);
QTextDocument parsed;
olive::Html::html_to_doc(&parsed, html);
ASSERT_EQ(parsed.begin().text(), QStringLiteral("styled"));
const QTextFragment frag = only_fragment(&parsed);
const QTextCharFormat &out = frag.charFormat();
EXPECT_EQ(out.fontWeight(), QFont::Bold);
EXPECT_TRUE(out.fontItalic());
EXPECT_TRUE(out.fontUnderline());
EXPECT_TRUE(out.fontStrikeOut());
EXPECT_TRUE(out.fontOverline());
EXPECT_DOUBLE_EQ(out.fontPointSize(), 24.0);
EXPECT_EQ(out.foreground().color(), QColor(255, 0, 0));
}
TEST(CommonHtml, ColorWithAlphaRoundTripsAsRgba)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextCharFormat fmt;
QColor semi(10, 20, 30, 128);
fmt.setForeground(semi);
c.insertText(QStringLiteral("x"), fmt);
const QString html = olive::Html::doc_to_html(&doc);
EXPECT_TRUE(html.contains(QStringLiteral("rgba(")));
QTextDocument parsed;
olive::Html::html_to_doc(&parsed, html);
const QTextFragment frag = only_fragment(&parsed);
const QColor out = frag.charFormat().foreground().color();
EXPECT_EQ(out.red(), semi.red());
EXPECT_EQ(out.green(), semi.green());
EXPECT_EQ(out.blue(), semi.blue());
EXPECT_NEAR(out.alphaF(), semi.alphaF(), 0.01);
}
TEST(CommonHtml, BlockAlignmentRoundTrips)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextBlockFormat bfmt;
bfmt.setAlignment(Qt::AlignHCenter);
c.setBlockFormat(bfmt);
c.insertText(QStringLiteral("centered"));
QTextDocument parsed;
olive::Html::html_to_doc(&parsed, olive::Html::doc_to_html(&doc));
EXPECT_TRUE(parsed.begin().blockFormat().alignment() & Qt::AlignHCenter);
}
TEST(CommonHtml, MultipleBlocksSurviveRoundTrip)
{
QTextDocument doc;
QTextCursor c(&doc);
c.insertText(QStringLiteral("first"));
c.insertBlock();
c.insertText(QStringLiteral("second"));
QTextDocument parsed;
olive::Html::html_to_doc(&parsed, olive::Html::doc_to_html(&doc));
EXPECT_EQ(parsed.blockCount(), 2);
EXPECT_EQ(parsed.begin().text(), QStringLiteral("first"));
EXPECT_EQ(parsed.begin().next().text(), QStringLiteral("second"));
}
TEST(CommonHtml, LineSeparatorBecomesBr)
{
QTextDocument doc;
QTextCursor c(&doc);
c.insertText(QStringLiteral("line1"));
c.insertText(QString(QChar(QChar::LineSeparator)) +
QStringLiteral("line2"));
const QString html = olive::Html::doc_to_html(&doc);
EXPECT_TRUE(html.contains(QStringLiteral("<br/>")));
}
TEST(CommonHtml, HtmlToDocReplacesExistingContent)
{
QTextDocument doc;
QTextCursor c(&doc);
c.insertText(QStringLiteral("old content that should disappear"));
olive::Html::html_to_doc(&doc, QStringLiteral("<p>new</p>"));
EXPECT_EQ(doc.toPlainText().trimmed(), QStringLiteral("new"));
}
TEST(CommonHtml, HtmlToDocHandlesInvalidMarkupWithoutCrash)
{
QTextDocument doc;
// Malformed markup must not crash; error is only logged.
olive::Html::html_to_doc(&doc, QStringLiteral("<p>unclosed"));
SUCCEED();
}
TEST(CommonHtml, EmptyHtmlProducesEmptyDocument)
{
QTextDocument doc;
olive::Html::html_to_doc(&doc, QString());
EXPECT_LE(doc.blockCount(), 1);
EXPECT_TRUE(doc.toPlainText().trimmed().isEmpty());
}
TEST(CommonHtml, LetterSpacingAndStretchRoundTrip)
{
QTextDocument doc;
QTextCursor c(&doc);
QTextCharFormat fmt;
fmt.setFontLetterSpacing(150.0);
fmt.setFontStretch(125);
c.insertText(QStringLiteral("spaced"), fmt);
QTextDocument parsed;
olive::Html::html_to_doc(&parsed, olive::Html::doc_to_html(&doc));
const QTextFragment frag = only_fragment(&parsed);
EXPECT_DOUBLE_EQ(frag.charFormat().fontLetterSpacing(), 150.0);
EXPECT_EQ(frag.charFormat().fontStretch(), 125);
}
TEST(CommonHtml, NestedInlineTagsMergeFormats)
{
QTextDocument doc;
olive::Html::html_to_doc(
&doc, QStringLiteral(
"<p><span style=\"font-weight: 600;\"><span "
"style=\"font-style: italic;\">both</span></span></p>"));
const QTextFragment frag = only_fragment(&doc);
EXPECT_TRUE(frag.charFormat().fontItalic());
// CSS font-weight 600 maps to 75 on the legacy 0-99 Qt weight scale
EXPECT_EQ(frag.charFormat().fontWeight(), 75);
}