summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp')
-rw-r--r--tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp459
1 files changed, 415 insertions, 44 deletions
diff --git a/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp b/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
index 5a8df45d99..d9fe000253 100644
--- a/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
+++ b/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
@@ -1,30 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QBuffer>
@@ -54,6 +29,7 @@ class tst_QTextMarkdownImporter : public QObject
Q_OBJECT
private slots:
+ void paragraphs();
void headingBulletsContinuations();
void thematicBreaks();
void lists_data();
@@ -62,8 +38,19 @@ private slots:
void nestedSpans();
void avoidBlankLineAtBeginning_data();
void avoidBlankLineAtBeginning();
+ void fragmentsAndProperties_data();
+ void fragmentsAndProperties();
void pathological_data();
void pathological();
+ void fencedCodeBlocks_data();
+ void fencedCodeBlocks();
+ void frontMatter_data();
+ void frontMatter();
+ void toRawText_data();
+ void toRawText();
+
+private:
+ bool isMainFontFixed();
public:
enum CharFormat {
@@ -75,12 +62,62 @@ public:
Mono = 0x10,
Link = 0x20
};
+ Q_ENUM(CharFormat)
Q_DECLARE_FLAGS(CharFormats, CharFormat)
};
Q_DECLARE_METATYPE(tst_QTextMarkdownImporter::CharFormats)
Q_DECLARE_OPERATORS_FOR_FLAGS(tst_QTextMarkdownImporter::CharFormats)
+bool tst_QTextMarkdownImporter::isMainFontFixed()
+{
+ bool ret = QFontInfo(QGuiApplication::font()).fixedPitch();
+ if (ret) {
+ qCWarning(lcTests) << "QFontDatabase::GeneralFont is monospaced: markdown writing is likely to use too many backticks";
+ qCWarning(lcTests) << "system fonts: fixed" << QFontDatabase::systemFont(QFontDatabase::FixedFont)
+ << "fixed?" << QFontInfo(QFontDatabase::systemFont(QFontDatabase::FixedFont)).fixedPitch()
+ << "general" << QFontDatabase::systemFont(QFontDatabase::GeneralFont);
+ }
+ return ret;
+}
+
+void tst_QTextMarkdownImporter::paragraphs()
+{
+ QFile f(QFINDTESTDATA("data/paragraphs.md"));
+ QVERIFY(f.open(QFile::ReadOnly | QIODevice::Text));
+ QString md = QString::fromUtf8(f.readAll());
+ f.close();
+
+ int lineSeparatorCount = 0;
+ QTextDocument doc;
+ QTextMarkdownImporter(&doc, QTextMarkdownImporter::DialectGitHub).import(md);
+ QTextFrame::iterator iterator = doc.rootFrame()->begin();
+ int i = 0;
+ while (!iterator.atEnd()) {
+ QTextBlock block = iterator.currentBlock();
+ int lineSeparatorPos = block.text().indexOf(QChar::LineSeparator);
+ qCDebug(lcTests) << i << block.text();
+ while (lineSeparatorPos > 0) {
+ ++lineSeparatorCount;
+ qCDebug(lcTests) << " LineSeparator @" << lineSeparatorPos;
+ lineSeparatorPos = block.text().indexOf(QChar::LineSeparator, lineSeparatorPos + 1);
+ }
+ ++iterator;
+ ++i;
+ }
+ QCOMPARE(doc.blockCount(), 3);
+ QCOMPARE(lineSeparatorCount, 2);
+
+#ifdef DEBUG_WRITE_HTML
+ {
+ QFile out("/tmp/paragraphs.html");
+ out.open(QFile::WriteOnly);
+ out.write(doc.toHtml().toLatin1());
+ out.close();
+ }
+#endif
+}
+
void tst_QTextMarkdownImporter::headingBulletsContinuations()
{
const QStringList expectedBlocks = QStringList() <<
@@ -106,7 +143,7 @@ void tst_QTextMarkdownImporter::headingBulletsContinuations()
f.close();
QTextDocument doc;
- QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, md);
+ QTextMarkdownImporter(&doc, QTextMarkdownImporter::DialectGitHub).import(md);
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
QStringList::const_iterator expectedIt = expectedBlocks.constBegin();
@@ -154,7 +191,7 @@ void tst_QTextMarkdownImporter::thematicBreaks()
f.close();
QTextDocument doc;
- QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, md);
+ QTextMarkdownImporter(&doc, QTextMarkdownImporter::DialectGitHub).import(md);
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
int i = 0;
@@ -187,61 +224,123 @@ void tst_QTextMarkdownImporter::thematicBreaks()
void tst_QTextMarkdownImporter::lists_data()
{
QTest::addColumn<QString>("input");
+ QTest::addColumn<int>("skipToCheckStart");
+ QTest::addColumn<int>("expectedListStart");
QTest::addColumn<int>("expectedItemCount");
QTest::addColumn<bool>("expectedEmptyItems");
QTest::addColumn<QString>("rewrite");
// Some of these cases show odd behavior, which is subject to change
// as the importer and the writer are tweaked to fix bugs over time.
- QTest::newRow("dot newline") << ".\n" << 0 << true << ".\n\n";
- QTest::newRow("number dot newline") << "1.\n" << 1 << true << "1. \n";
- QTest::newRow("star newline") << "*\n" << 1 << true << "* \n";
- QTest::newRow("hyphen newline") << "-\n" << 1 << true << "- \n";
- QTest::newRow("hyphen space newline") << "- \n" << 1 << true << "- \n";
- QTest::newRow("hyphen space letter newline") << "- a\n" << 1 << false << "- a\n";
+ QTest::newRow("dot newline") << ".\n" << 0 << 1 << 0 << true << ".\n\n";
+ QTest::newRow("number dot newline") << "1.\n" << 0 << 1 << 1 << true << "1. \n";
+ QTest::newRow("number offset start") << "2. text\n" << 0 << 2 << 1 << false << "2. text\n";
+ QTest::newRow("second list offset start")
+ << "1. text\n\nintervening paragraph\n\n4. second list item"
+ << 2 << 4 << 2 << false
+ << "1. text\n\nintervening paragraph\n\n4. second list item\n";
+ QTest::newRow("list continuation offset start")
+ << "3. text\n\n next paragraph in item 1\n10. second list item"
+ << 2 << 3 << 2 << false
+ << "3. text\n\n next paragraph in item 1\n\n4. second list item\n";
+ QTest::newRow("nested list offset start")
+ << "1. text\n\n 0. indented list item\n\n4. second item in first list"
+ << 1 << 0 << 3 << false
+ << "1. text\n 0. indented list item\n2. second item in first list\n";
+ QTest::newRow("offset start after nested list")
+ << "1. text\n\n 0. indented list item\n\n4. second item in first list"
+ << 2 << 1 << 3 << false
+ << "1. text\n 0. indented list item\n2. second item in first list\n";
+ QTest::newRow("star newline") << "*\n" << 0 << 1 << 1 << true << "* \n";
+ QTest::newRow("hyphen newline") << "-\n" << 0 << 1 << 1 << true << "- \n";
+ QTest::newRow("hyphen space newline") << "- \n" << 0 << 1 << 1 << true << "- \n";
+ QTest::newRow("hyphen space letter newline") << "- a\n" << 0 << 1 << 1 << false << "- a\n";
QTest::newRow("hyphen nbsp newline") <<
- QString::fromUtf8("-\u00A0\n") << 0 << true << "-\u00A0\n\n";
- QTest::newRow("nested empty lists") << "*\n *\n *\n" << 1 << true << " * \n";
- QTest::newRow("list nested in empty list") << "-\n * a\n" << 2 << false << "- \n * a\n";
+ QString::fromUtf8("-\u00A0\n") << 0 << 1 << 0 << true << "\\-\u00A0\n\n";
+ QTest::newRow("nested empty lists") << "*\n *\n *\n" << 0 << 1 << 1 << true << " * \n";
+ QTest::newRow("list nested in empty list") << "-\n * a\n" << 0 << 1 << 2 << false << "- \n * a\n";
QTest::newRow("lists nested in empty lists")
- << "-\n * a\n * b\n- c\n *\n + d\n" << 5 << false
+ << "-\n * a\n * b\n- c\n *\n + d\n" << 0 << 1 << 5 << false
<< "- \n * a\n * b\n- c *\n + d\n";
QTest::newRow("numeric lists nested in empty lists")
- << "- \n 1. a\n 2. b\n- c\n 1.\n + d\n" << 4 << false
+ << "- \n 1. a\n 2. b\n- c\n 1.\n + d\n" << 0 << 1 << 4 << false
<< "- \n 1. a\n 2. b\n- c 1. + d\n";
+ QTest::newRow("styled spans in list items")
+ << "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n"
+ << 0 << 1 << 5 << false
+ << "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n";
}
void tst_QTextMarkdownImporter::lists()
{
QFETCH(QString, input);
+ QFETCH(int, skipToCheckStart);
+ QFETCH(int, expectedListStart);
QFETCH(int, expectedItemCount);
QFETCH(bool, expectedEmptyItems);
QFETCH(QString, rewrite);
QTextDocument doc;
doc.setMarkdown(input); // QTBUG-78870 : don't crash
+
+#ifdef DEBUG_WRITE_HTML
+ {
+ QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
+ out.open(QFile::WriteOnly);
+ out.write(doc.toHtml().toLatin1());
+ out.close();
+ }
+#endif
+ qCDebug(lcTests) << " original:" << input;
+ qCDebug(lcTests) << "rewritten:" << doc.toMarkdown();
+
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
int i = 0;
int itemCount = 0;
bool emptyItems = true;
+ QString firstItemFontFamily;
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check whether the block is text or a horizontal rule
QTextBlock block = iterator.currentBlock();
+ QTextListFormat listFmt;
if (block.textList()) {
++itemCount;
if (!block.text().isEmpty())
emptyItems = false;
+ listFmt = block.textList()->format();
}
qCDebug(lcTests, "%d %s%s", i,
(block.textList() ? "<li>" : "<p>"), qPrintable(block.text()));
+ QTextCharFormat listItemFmt = block.charFormat();
+ QFont listItemFont = listItemFmt.font();
+ // QTextDocumentLayoutPrivate::drawListItem() uses listItemFont to render numbers in an ordered list.
+ // We want that to be consistent, regardless whether the list item's text begins with a styled span.
+ if (firstItemFontFamily.isEmpty())
+ firstItemFontFamily = listItemFont.family();
+ else
+ QCOMPARE(listItemFont.family(), firstItemFontFamily);
+ QCOMPARE(listItemFont.bold(), false);
+ QCOMPARE(listItemFont.italic(), false);
+ QCOMPARE(listItemFont.underline(), false);
+ QCOMPARE(listItemFont.fixedPitch(), false);
+ QCOMPARE(listItemFmt.fontItalic(), false);
+ QCOMPARE(listItemFmt.fontUnderline(), false);
+ QCOMPARE(listItemFmt.fontFixedPitch(), false);
+ if (i == skipToCheckStart) {
+ qCDebug(lcTests) << "skipped to list item" << i << block.text()
+ << "start" << listFmt.start() << "expected" << expectedListStart;
+ QCOMPARE(listFmt.start(), expectedListStart);
+ }
++iterator;
++i;
}
QCOMPARE(itemCount, expectedItemCount);
QCOMPARE(emptyItems, expectedEmptyItems);
+ if (doc.toMarkdown() != rewrite && isMainFontFixed())
+ QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
QCOMPARE(doc.toMarkdown(), rewrite);
}
@@ -333,13 +432,15 @@ void tst_QTextMarkdownImporter::nestedSpans()
<< "underlined" << fmt.fontUnderline()
<< "strikeout" << fmt.fontStrikeOut() << "anchor" << fmt.isAnchor()
<< "monospace" << QFontInfo(fmt.font()).fixedPitch() // depends on installed fonts (QTBUG-75649)
- << fmt.fontFixedPitch() // returns false even when font family is "monospace"
- << fmt.hasProperty(QTextFormat::FontFixedPitch); // works
+ << fmt.fontFixedPitch()
+ << fmt.hasProperty(QTextFormat::FontFixedPitch)
+ << "expected" << expectedFormat;
QCOMPARE(fmt.fontWeight() > QFont::Normal, expectedFormat.testFlag(Bold));
QCOMPARE(fmt.fontItalic(), expectedFormat.testFlag(Italic));
QCOMPARE(fmt.fontUnderline(), expectedFormat.testFlag(Underlined));
QCOMPARE(fmt.fontStrikeOut(), expectedFormat.testFlag(Strikeout));
QCOMPARE(fmt.isAnchor(), expectedFormat.testFlag(Link));
+ QCOMPARE(fmt.fontFixedPitch(), expectedFormat.testFlag(Mono));
QCOMPARE(fmt.hasProperty(QTextFormat::FontFixedPitch), expectedFormat.testFlag(Mono));
++iterator;
}
@@ -352,7 +453,7 @@ void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning_data()
QTest::newRow("Text block") << QString("Markdown text") << 1;
QTest::newRow("Headline") << QString("Markdown text\n============") << 1;
- QTest::newRow("Code block") << QString(" Markdown text") << 2;
+ QTest::newRow("Code block") << QString(" Markdown text") << 1;
QTest::newRow("Unordered list") << QString("* Markdown text") << 1;
QTest::newRow("Ordered list") << QString("1. Markdown text") << 1;
QTest::newRow("Blockquote") << QString("> Markdown text") << 1;
@@ -364,7 +465,7 @@ void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning() // QTBUG-81060
QFETCH(int, expectedNumberOfParagraphs);
QTextDocument doc;
- QTextMarkdownImporter(QTextMarkdownImporter::DialectGitHub).import(&doc, input);
+ QTextMarkdownImporter(&doc, QTextMarkdownImporter::DialectGitHub).import(input);
QTextFrame::iterator iterator = doc.rootFrame()->begin();
int i = 0;
while (!iterator.atEnd()) {
@@ -378,6 +479,70 @@ void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning() // QTBUG-81060
QCOMPARE(i, expectedNumberOfParagraphs);
}
+void tst_QTextMarkdownImporter::fragmentsAndProperties_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<int>("fragmentToCheck");
+ QTest::addColumn<QString>("expectedText");
+ QTest::addColumn<QTextFormat::Property>("propertyToCheck");
+ QTest::addColumn<QVariant>("expectedPropertyValue");
+ QTest::addColumn<int>("expectedNumberOfBlocks");
+ QTest::addColumn<int>("expectedNumberOfFragments");
+
+ QTest::newRow("entitiesInHtmlFontBlock") // QTBUG-94245
+ << QString("<font color='red'>&lt;123 test&gt;</font>&nbsp;test")
+ << 0 << "<123 test>" << QTextFormat::ForegroundBrush << QVariant(QBrush(QColor("red")))
+ << 1 << 2;
+ QTest::newRow("entitiesInHtmlBoldBlock") // QTBUG-91222
+ << QString("<b>x&amp;lt;</b>")
+ << 0 << "x&lt;" << QTextFormat::FontWeight << QVariant(700)
+ << 1 << 1;
+}
+
+void tst_QTextMarkdownImporter::fragmentsAndProperties()
+{
+ QFETCH(QString, input);
+ QFETCH(int, fragmentToCheck);
+ QFETCH(QString, expectedText);
+ QFETCH(QTextFormat::Property, propertyToCheck);
+ QFETCH(QVariant, expectedPropertyValue);
+ QFETCH(int, expectedNumberOfBlocks);
+ QFETCH(int, expectedNumberOfFragments);
+
+ QTextDocument doc;
+ QTextMarkdownImporter(&doc, QTextMarkdownImporter::DialectGitHub).import(input);
+#ifdef DEBUG_WRITE_HTML
+ {
+ QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
+ out.open(QFile::WriteOnly);
+ out.write(doc.toHtml().toLatin1());
+ out.close();
+ }
+#endif
+ QTextFrame::iterator blockIter = doc.rootFrame()->begin();
+ int blockCount = 0;
+ int fragCount = 0;
+ while (!blockIter.atEnd()) {
+ QTextBlock block = blockIter.currentBlock();
+ auto fragIter = block.begin();
+ while (!fragIter.atEnd()) {
+ auto frag = fragIter.fragment();
+ qCDebug(lcTests) << "fragment" << fragCount << ':' << frag.text() << Qt::hex << frag.charFormat().properties();
+ if (fragCount == fragmentToCheck) {
+ QVariant prop = frag.charFormat().property(propertyToCheck);
+ QCOMPARE(prop, expectedPropertyValue);
+ QCOMPARE(frag.text(), expectedText);
+ }
+ ++fragIter;
+ ++fragCount;
+ }
+ ++blockIter;
+ ++blockCount;
+ }
+ QCOMPARE(blockCount, expectedNumberOfBlocks);
+ QCOMPARE(fragCount, expectedNumberOfFragments);
+}
+
void tst_QTextMarkdownImporter::pathological_data()
{
QTest::addColumn<QString>("warning");
@@ -400,5 +565,211 @@ void tst_QTextMarkdownImporter::pathological() // avoid crashing on crazy input
QTextDocument().setMarkdown(f.readAll());
}
+void tst_QTextMarkdownImporter::fencedCodeBlocks_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<int>("expectedCodeBlockCount");
+ QTest::addColumn<int>("expectedPlainBlockCount");
+ QTest::addColumn<QString>("expectedLanguage");
+ QTest::addColumn<QString>("expectedFenceChar");
+ QTest::addColumn<QString>("rewrite");
+
+ QTest::newRow("backtick fence with language")
+ << "```pseudocode\nprint('hello world\\n')\n```\n"
+ << 1 << 0 << "pseudocode" << "`"
+ << "```pseudocode\nprint('hello world\\n')\n```\n\n";
+ QTest::newRow("backtick fence with punctuated language")
+ << "```html+js\n<html><head><script>function hi() { console.log('\\\"hello world') }</script></head>blah</html>\n```\n"
+ << 1 << 0 << "html+js" << "`"
+ << "```html+js\n<html><head><script>function hi() { console.log('\\\"hello world') }</script></head>blah</html>\n```\n\n";
+ QTest::newRow("tilde fence with language")
+ << "~~~pseudocode\nprint('hello world\\n')\n~~~\n"
+ << 1 << 0 << "pseudocode" << "~"
+ << "~~~pseudocode\nprint('hello world\\n')\n~~~\n\n";
+ QTest::newRow("embedded backticks")
+ << "```\nnone `one` ``two``\n```\nplain\n```\n```three``` ````four````\n```\nplain\n"
+ << 2 << 2 << QString() << "`"
+ << "```\nnone `one` ``two``\n```\nplain\n\n```\n```three``` ````four````\n```\nplain\n\n";
+}
+
+void tst_QTextMarkdownImporter::fencedCodeBlocks()
+{
+ QFETCH(QString, input);
+ QFETCH(int, expectedCodeBlockCount);
+ QFETCH(int, expectedPlainBlockCount);
+ QFETCH(QString, expectedLanguage);
+ QFETCH(QString, expectedFenceChar);
+ QFETCH(QString, rewrite);
+
+ QTextDocument doc;
+ doc.setMarkdown(input);
+
+#ifdef DEBUG_WRITE_HTML
+ {
+ QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
+ out.open(QFile::WriteOnly);
+ out.write(doc.toHtml().toLatin1());
+ out.close();
+ }
+#endif
+
+ QTextFrame::iterator iterator = doc.rootFrame()->begin();
+ QTextFrame *currentFrame = iterator.currentFrame();
+ int codeBlockCount = 0;
+ int plainBlockCount = 0;
+ while (!iterator.atEnd()) {
+ // There are no child frames
+ QCOMPARE(iterator.currentFrame(), currentFrame);
+ // Check whether the block is code or plain
+ QTextBlock block = iterator.currentBlock();
+ const bool codeBlock = block.blockFormat().hasProperty(QTextFormat::BlockCodeFence);
+ QCOMPARE(block.blockFormat().nonBreakableLines(), codeBlock);
+ QCOMPARE(block.blockFormat().stringProperty(QTextFormat::BlockCodeLanguage), codeBlock ? expectedLanguage : QString());
+ if (codeBlock) {
+ QCOMPARE(block.blockFormat().stringProperty(QTextFormat::BlockCodeFence), expectedFenceChar);
+ ++codeBlockCount;
+ } else {
+ ++plainBlockCount;
+ }
+ qCDebug(lcTests) << (codeBlock ? "code" : "text") << block.text() << block.charFormat().fontFamilies();
+ ++iterator;
+ }
+ QCOMPARE(codeBlockCount, expectedCodeBlockCount);
+ QCOMPARE(plainBlockCount, expectedPlainBlockCount);
+ if (doc.toMarkdown() != rewrite && isMainFontFixed())
+ QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
+ QCOMPARE(doc.toMarkdown(), rewrite);
+}
+
+void tst_QTextMarkdownImporter::frontMatter_data()
+{
+ QTest::addColumn<QString>("inputFile");
+ QTest::addColumn<int>("expectedBlockCount");
+
+ QTest::newRow("yaml + markdown") << QFINDTESTDATA("data/yaml.md") << 1;
+ QTest::newRow("yaml only") << QFINDTESTDATA("data/yaml-only.md") << 0;
+}
+
+void tst_QTextMarkdownImporter::frontMatter()
+{
+ QFETCH(QString, inputFile);
+ QFETCH(int, expectedBlockCount);
+
+ QFile f(inputFile);
+ QVERIFY(f.open(QFile::ReadOnly | QIODevice::Text));
+ QString md = QString::fromUtf8(f.readAll());
+ f.close();
+ const int yamlBegin = md.indexOf("name:");
+ const int yamlEnd = md.indexOf("---", yamlBegin);
+ const QString yaml = md.sliced(yamlBegin, yamlEnd - yamlBegin);
+
+ QTextDocument doc;
+ QTextMarkdownImporter(&doc, QTextMarkdownImporter::DialectGitHub).import(md);
+ int blockCount = 0;
+ for (QTextFrame::iterator iterator = doc.rootFrame()->begin(); !iterator.atEnd(); ++iterator) {
+ // Check whether the block is text or a horizontal rule
+ if (!iterator.currentBlock().text().isEmpty())
+ ++blockCount;
+ }
+ QCOMPARE(blockCount, expectedBlockCount); // yaml is not part of the markdown text
+ QCOMPARE(doc.metaInformation(QTextDocument::FrontMatter), yaml); // without fences
+}
+
+void tst_QTextMarkdownImporter::toRawText_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("expectedRawText");
+
+ // tests to verify that fixing QTBUG-122083 is safe
+ // https://spec.commonmark.org/0.31.2/#example-12
+ QTest::newRow("punctuation backslash escapes") <<
+ R"(\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~)" <<
+ R"(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)";
+ // https://spec.commonmark.org/0.31.2/#example-13
+ QTest::newRow("literal backslashes") <<
+ QString(uR"(\→\A\a\ \3\φ\«)") <<
+ QString(uR"(\→\A\a\ \3\φ\«)");
+ // https://spec.commonmark.org/0.31.2/#example-14
+ QTest::newRow("escape to avoid em") <<
+ R"(\*not emphasized*)" <<
+ R"(*not emphasized*)";
+ QTest::newRow("escape to avoid html") <<
+ R"(\<br/> not a tag)" <<
+ R"(<br/> not a tag)";
+ QTest::newRow("escape to avoid link") <<
+ R"(\[not a link](/foo))" <<
+ R"([not a link](/foo))";
+ QTest::newRow("escape to avoid mono") <<
+ R"(\`not code`)" <<
+ R"(`not code`)";
+ QTest::newRow("escape to avoid num list") <<
+ R"(1\. not a list)" <<
+ R"(1. not a list)";
+ QTest::newRow("escape to avoid list") <<
+ R"(\* not a list)" <<
+ R"(* not a list)";
+ QTest::newRow("escape to avoid heading") <<
+ R"(\# not a heading)" <<
+ R"(# not a heading)";
+ QTest::newRow("escape to avoid reflink") <<
+ R"(\[foo]: /url "not a reference")" <<
+ R"([foo]: /url "not a reference")";
+ QTest::newRow("escape to avoid entity") <<
+ R"(\&ouml; not a character entity)" <<
+ R"(&ouml; not a character entity)";
+ // https://spec.commonmark.org/0.31.2/#example-15
+ QTest::newRow("escape backslash only") <<
+ R"(\\*emphasis*)" <<
+ R"(\emphasis)";
+ // https://spec.commonmark.org/0.31.2/#example-16
+ QTest::newRow("backslash line break") <<
+ "foo\\\nbar" <<
+ "foo\u2029bar";
+ // https://spec.commonmark.org/0.31.2/#example-17
+ QTest::newRow("backslash in mono span") <<
+ R"(`` \[\` ``)" <<
+ R"(\[\`)";
+ // https://spec.commonmark.org/0.31.2/#example-18
+ QTest::newRow("backslash in indented code") <<
+ R"( \[\])" <<
+ R"(\[\])";
+ // https://spec.commonmark.org/0.31.2/#example-19
+ QTest::newRow("backslash in fenced code") <<
+ "~~~\n\\[\\]\n~~~" <<
+ R"(\[\])";
+ // https://spec.commonmark.org/0.31.2/#example-20
+ QTest::newRow("backslash in autolink") <<
+ R"(<https://example.com?find=\*>)" <<
+ R"(https://example.com?find=\*)";
+ // https://spec.commonmark.org/0.31.2/#example-21
+ QTest::newRow("backslash in autolink") <<
+ "<a href=\"/bar\\/)\"" <<
+ "<a href=\"/bar/)\"";
+ // https://spec.commonmark.org/0.31.2/#example-22
+ QTest::newRow("escapes in link") <<
+ R"([foo](/bar\* "ti\*tle"))" <<
+ R"(foo)";
+ // https://spec.commonmark.org/0.31.2/#example-24
+ QTest::newRow("backslash in code lang") <<
+ "```\nfoo\\+bar\nfoo\n```" <<
+ "foo\\+bar\u2029foo";
+ // end of tests to verify that fixing QTBUG-122083 is safe
+ // (it's ok to add unrelated markdown-to-rawtext cases later)
+}
+
+void tst_QTextMarkdownImporter::toRawText()
+{
+ QFETCH(QString, input);
+ QFETCH(QString, expectedRawText);
+
+ QTextDocument doc;
+ doc.setMarkdown(input);
+
+ // These are testing md4c more than Qt, so any change may be an md4c bug, or a fix
+ QCOMPARE(doc.toRawText(), expectedRawText);
+ if (doc.blockCount() == 1)
+ QCOMPARE(doc.firstBlock().text(), expectedRawText);
+}
+
QTEST_MAIN(tst_QTextMarkdownImporter)
#include "tst_qtextmarkdownimporter.moc"