summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp')
-rw-r--r--tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp505
1 files changed, 498 insertions, 7 deletions
diff --git a/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp b/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
index b258869dfd..0d261bc27e 100644
--- a/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
+++ b/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QTextDocument>
@@ -34,11 +34,21 @@ private slots:
void testWriteNestedBulletLists_data();
void testWriteNestedBulletLists();
void testWriteNestedNumericLists();
+ void testWriteNumericListWithStart();
void testWriteTable();
+ void frontMatter();
+ void charFormatWrapping_data();
+ void charFormatWrapping();
+ void charFormat_data();
+ void charFormat();
void rewriteDocument_data();
void rewriteDocument();
void fromHtml_data();
void fromHtml();
+ void fromPlainTextAndBack_data();
+ void fromPlainTextAndBack();
+ void escapeSpecialCharacters_data();
+ void escapeSpecialCharacters();
private:
bool isMainFontFixed();
@@ -47,6 +57,8 @@ private:
private:
QTextDocument *document;
+ QFont m_monoFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
+ QFont m_defaultFont;
};
void tst_QTextMarkdownWriter::init()
@@ -284,6 +296,7 @@ void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
list1->add(cursor.block());
QTextListFormat fmt2;
+ // Alpha "numbering" is not supported in markdown, so we'll actually get decimal.
fmt2.setStyle(QTextListFormat::ListLowerAlpha);
fmt2.setNumberSuffix(QLatin1String(")"));
fmt2.setIndent(2);
@@ -305,7 +318,24 @@ void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
list2->add(cursor.block());
const QString output = documentToUnixMarkdown();
- // There's no QTextList API to set the starting number so we hard-coded all lists to start at 1 (QTBUG-65384)
+
+ #ifdef DEBUG_WRITE_OUTPUT
+ {
+ QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".md"));
+ out.open(QFile::WriteOnly);
+ out.write(output.toUtf8());
+ out.close();
+ }
+ {
+ QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".html"));
+ out.open(QFile::WriteOnly);
+ out.write(document->toHtml().toUtf8());
+ out.close();
+ }
+#endif
+
+ // While we can set the start index for a block, if list items intersect each other, they will
+ // still use the list numbering.
const QString expected = QString::fromLatin1(
"1. ListItem 1\n 1) ListItem 2\n 1. ListItem 3\n2. ListItem 4\n 2) ListItem 5\n");
if (output != expected && isMainFontFixed())
@@ -313,6 +343,92 @@ void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
QCOMPARE(output, expected);
}
+void tst_QTextMarkdownWriter::testWriteNumericListWithStart()
+{
+ QTextCursor cursor(document);
+
+ // The first list will start at 2.
+ QTextListFormat fmt1;
+ fmt1.setStyle(QTextListFormat::ListDecimal);
+ fmt1.setStart(2);
+ QTextList *list1 = cursor.createList(fmt1);
+ cursor.insertText("ListItem 1");
+ list1->add(cursor.block());
+
+ // This list uses the default start (1) again.
+ QTextListFormat fmt2;
+ // Alpha "numbering" is not supported in markdown, so we'll actually get decimal.
+ fmt2.setStyle(QTextListFormat::ListLowerAlpha);
+ fmt2.setNumberSuffix(QLatin1String(")"));
+ fmt2.setIndent(2);
+ QTextList *list2 = cursor.insertList(fmt2);
+ cursor.insertText("ListItem 2");
+
+ // Negative list numbers are disallowed by most Markdown implementations. This list will start
+ // at 1 for that reason.
+ QTextListFormat fmt3;
+ fmt3.setStyle(QTextListFormat::ListDecimal);
+ fmt3.setIndent(3);
+ fmt3.setStart(-1);
+ cursor.insertList(fmt3);
+ cursor.insertText("ListItem 3");
+
+ // Continuing list1, so the second item will have the number 3.
+ cursor.insertBlock();
+ cursor.insertText("ListItem 4");
+ list1->add(cursor.block());
+
+ // This will look out of place: it's in a different position than its list would suggest.
+ // Generates invalid markdown numbering (OK for humans, but md4c will parse it differently than we "meant").
+ // TODO QTBUG-111707: the writer needs to add newlines, otherwise ListItem 5 becomes part of the text for ListItem 4.
+ cursor.insertBlock();
+ cursor.insertText("ListItem 5");
+ list2->add(cursor.block());
+
+ // 0 indexed lists are fine.
+ QTextListFormat fmt4;
+ fmt4.setStyle(QTextListFormat::ListDecimal);
+ fmt4.setStart(0);
+ QTextList *list4 = cursor.insertList(fmt4);
+ cursor.insertText("SecondList Item 0");
+ list4->add(cursor.block());
+
+ // Ensure list numbers are incremented properly.
+ cursor.insertBlock();
+ cursor.insertText("SecondList Item 1");
+ list4->add(cursor.block());
+
+ const QString output = documentToUnixMarkdown();
+ const QString expected = QString::fromLatin1(
+ R"(2. ListItem 1
+ 1) ListItem 2
+ 1. ListItem 3
+3. ListItem 4
+ 2) ListItem 5
+0. SecondList Item 0
+1. SecondList Item 1
+)");
+
+#ifdef DEBUG_WRITE_OUTPUT
+ {
+ QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".md"));
+ out.open(QFile::WriteOnly);
+ out.write(output.toUtf8());
+ out.close();
+ }
+ {
+ QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".html"));
+ out.open(QFile::WriteOnly);
+ out.write(document->toHtml().toUtf8());
+ out.close();
+ }
+#endif
+
+ if (output != expected && isMainFontFixed())
+ QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
+ QCOMPARE(output, expected);
+}
+
void tst_QTextMarkdownWriter::testWriteTable()
{
QTextCursor cursor(document);
@@ -420,16 +536,231 @@ void tst_QTextMarkdownWriter::testWriteTable()
QCOMPARE(md, expected);
}
+void tst_QTextMarkdownWriter::frontMatter()
+{
+ QTextCursor cursor(document);
+ cursor.insertText("bar");
+ document->setMetaInformation(QTextDocument::FrontMatter, "foo");
+
+ const QString output = documentToUnixMarkdown();
+ const QString expectedOutput("---\nfoo\n---\nbar\n\n");
+ if (output != expectedOutput && isMainFontFixed())
+ QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
+ QCOMPARE(output, expectedOutput);
+}
+
+void tst_QTextMarkdownWriter::charFormatWrapping_data()
+{
+ QTest::addColumn<QTextFormat::Property>("property");
+ QTest::addColumn<QVariant>("propertyValue");
+ QTest::addColumn<QString>("followingText");
+ QTest::addColumn<QString>("expectedIndicator");
+
+ const QString spaced = " after";
+ const QString unspaced = ", and some more after";
+
+ QTest::newRow("FontFixedPitch-spaced")
+ << QTextFormat::FontFixedPitch << QVariant(true) << spaced << "`";
+ QTest::newRow("FontFixedPitch-unspaced")
+ << QTextFormat::FontFixedPitch << QVariant(true) << unspaced << "`";
+ QTest::newRow("FontItalic")
+ << QTextFormat::FontItalic << QVariant(true) << spaced << "*";
+ QTest::newRow("FontUnderline")
+ << QTextFormat::FontUnderline << QVariant(true) << spaced << "_";
+ QTest::newRow("FontStrikeOut")
+ << QTextFormat::FontStrikeOut << QVariant(true) << spaced << "~~";
+ QTest::newRow("FontWeight-spaced")
+ << QTextFormat::FontWeight << QVariant(700) << spaced << "**";
+ QTest::newRow("FontWeight-unspaced")
+ << QTextFormat::FontWeight << QVariant(700) << unspaced << "**";
+}
+
+void tst_QTextMarkdownWriter::charFormatWrapping() // QTBUG-116927
+{
+ QFETCH(QTextFormat::Property, property);
+ QFETCH(QVariant, propertyValue);
+ QFETCH(QString, expectedIndicator);
+ QFETCH(QString, followingText);
+
+ const QString newLine("\n");
+ QTextCursor cursor(document);
+ cursor.insertText("around sixty-four characters to go before some formatted words ");
+ QTextCharFormat fmt;
+ fmt.setProperty(property, propertyValue);
+ cursor.setCharFormat(fmt);
+ cursor.insertText("formatted text");
+
+ cursor.setCharFormat({});
+ cursor.insertText(followingText);
+ qsizetype lastNewLineIndex = 100;
+
+ for (int push = 0; push < 10; ++push) {
+ if (push > 0) {
+ cursor.movePosition(QTextCursor::StartOfBlock);
+ cursor.insertText("a");
+ }
+
+ const QString output = documentToUnixMarkdown().trimmed(); // get rid of trailing newlines
+ const auto nlIdx = output.indexOf(newLine);
+ qCDebug(lcTests) << "push" << push << ":" << output << "newline @" << nlIdx;
+ // we're always wrapping in this test: expect to find a newline
+ QCOMPARE_GT(nlIdx, 70);
+ // don't expect the newline to be more than one character to the right of where we found it last time
+ // i.e. if we already started breaking in the middle: "`formatted\ntext`",
+ // then we would not expect that prepending one more character would make it go
+ // back to breaking afterwards: "`formatted text`\n" (because then the line becomes longer than necessary)
+ QCOMPARE_LE(nlIdx, lastNewLineIndex + 1);
+ lastNewLineIndex = nlIdx;
+ const QString nextChars = output.sliced(nlIdx + newLine.size(), expectedIndicator.size());
+ const auto startingIndicatorIdx = output.indexOf(expectedIndicator);
+ // the starting indicator always exists, except in case of font problems on some CI platforms
+ if (startingIndicatorIdx <= 0)
+ QSKIP("starting indicator not found, probably due to platform font problems (QTBUG-103484 etc.)");
+ const auto endingIndicatorIdx = output.indexOf(expectedIndicator, startingIndicatorIdx + 5);
+ qCDebug(lcTests) << "next chars past newline" << nextChars
+ << "indicators @" << startingIndicatorIdx << endingIndicatorIdx;
+ // the closing indicator must exist
+ QCOMPARE_GT(endingIndicatorIdx, startingIndicatorIdx);
+ // don't start a new line with an ending indicator:
+ // we can have "**formatted\ntext**" or "**formatted text**\n" or "\n**formatted text**"
+ // but not "**formatted text\n**"
+ if (startingIndicatorIdx < nlIdx)
+ QCOMPARE_NE(nextChars, expectedIndicator);
+ }
+}
+
+void tst_QTextMarkdownWriter::charFormat_data()
+{
+ QTest::addColumn<QTextFormat::Property>("property");
+ QTest::addColumn<QVariant>("propertyValue");
+ QTest::addColumn<QFont>("explicitFont");
+ QTest::addColumn<QString>("expectedOutput");
+
+ const QTextFormat::Property NoProperty = QTextFormat::ObjectIndex;
+
+ QTest::newRow("FontFixedPitch")
+ << QTextFormat::FontFixedPitch << QVariant(true) << m_defaultFont
+ << "before `formatted` after";
+ if (!isFixedFontProportional()) {
+ // QTBUG-54623 QTBUG-75649 QTBUG-79900 QTBUG-103484 etc.
+ QTest::newRow("mono font") << NoProperty << QVariant() << m_monoFont
+ << "before `formatted` after";
+ }
+
+ {
+ QFont font;
+ font.setItalic(true);
+ QTest::newRow("italic font")
+ << NoProperty << QVariant() << font
+ << "before *formatted* after";
+ }
+ QTest::newRow("FontItalic")
+ << QTextFormat::FontItalic << QVariant(true) << m_defaultFont
+ << "before *formatted* after";
+
+ {
+ QFont font;
+ font.setUnderline(true);
+ QTest::newRow("underline font")
+ << NoProperty << QVariant() << font
+ << "before _formatted_ after";
+ }
+ QTest::newRow("FontUnderline")
+ << QTextFormat::FontUnderline << QVariant(true) << m_defaultFont
+ << "before _formatted_ after";
+
+ {
+ QFont font;
+ font.setStrikeOut(true);
+ QTest::newRow("strikeout font")
+ << NoProperty << QVariant() << font
+ << "before ~~formatted~~ after";
+ }
+ QTest::newRow("FontStrikeOut")
+ << QTextFormat::FontStrikeOut << QVariant(true) << m_defaultFont
+ << "before ~~formatted~~ after";
+
+ {
+ QFont font;
+ font.setBold(true);
+ QTest::newRow("bold font")
+ << NoProperty << QVariant() << font
+ << "before **formatted** after";
+ }
+ {
+ QFont font;
+ font.setWeight(QFont::Black);
+ QTest::newRow("black font")
+ << NoProperty << QVariant() << font
+ << "before **formatted** after";
+ }
+ QTest::newRow("FontWeight")
+ << QTextFormat::FontWeight << QVariant(700) << m_defaultFont
+ << "before **formatted** after";
+
+ QTest::newRow("AnchorHref")
+ << QTextFormat::AnchorHref << QVariant("linky linky") << m_defaultFont
+ << "before [formatted](linky linky) after";
+
+ QTest::newRow("TextToolTip") // no effect without AnchorHref
+ << QTextFormat::TextToolTip << QVariant("such a tool") << m_defaultFont
+ << "before formatted after";
+}
+
+void tst_QTextMarkdownWriter::charFormat()
+{
+ if (isMainFontFixed())
+ QSKIP("QTextMarkdownWriter would generate bogus backticks");
+
+ QFETCH(QTextFormat::Property, property);
+ QFETCH(QVariant, propertyValue);
+ QFETCH(QFont, explicitFont);
+ QFETCH(QString, expectedOutput);
+
+ QTextCursor cursor(document);
+ cursor.insertText("before ");
+
+ QTextCharFormat fmt;
+ if (explicitFont != m_defaultFont)
+ fmt.setFont(explicitFont);
+ if (property != QTextFormat::ObjectIndex) // != 0
+ fmt.setProperty(property, propertyValue);
+ if (explicitFont == m_monoFont) {
+ QFontInfo fontInfo(fmt.font());
+ qCDebug(lcTests) << "mono font" << explicitFont << "fontInfo fixedPitch" << fontInfo.fixedPitch() << "fmt fixedPitch" << fmt.fontFixedPitch();
+ }
+ cursor.setCharFormat(fmt);
+ cursor.insertText("formatted");
+
+ cursor.setCharFormat({});
+ cursor.insertText(" after");
+
+ const QString output = documentToUnixMarkdown();
+#ifdef DEBUG_WRITE_OUTPUT
+ {
+ QFile out(QDir::temp().filePath(QLatin1String(QTest::currentDataTag()) + ".md"));
+ out.open(QFile::WriteOnly);
+ out.write(output.toUtf8());
+ out.close();
+ }
+#endif
+ QCOMPARE(output.trimmed(), expectedOutput);
+}
+
void tst_QTextMarkdownWriter::rewriteDocument_data()
{
QTest::addColumn<QString>("inputFile");
QTest::newRow("block quotes") << "blockquotes.md";
+ QTest::newRow("block quotes with lists") << "blockquotesWithLists.md";
+ // QTest::newRow("list item with block quote") << "listItemWithBlockquote.md"; // not supported for now
QTest::newRow("example") << "example.md";
QTest::newRow("list items after headings") << "headingsAndLists.md";
QTest::newRow("word wrap") << "wordWrap.md";
QTest::newRow("links") << "links.md";
QTest::newRow("lists and code blocks") << "listsAndCodeBlocks.md";
+ QTest::newRow("front matter") << "yaml.md";
+ QTest::newRow("long headings") << "longHeadings.md";
}
void tst_QTextMarkdownWriter::rewriteDocument()
@@ -462,7 +793,7 @@ void tst_QTextMarkdownWriter::fromHtml_data()
QTest::newRow("long URL") <<
"<span style=\"font-style:italic;\">https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/</span>" <<
- "*https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/*\n\n";
+ "\n*https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/*\n\n";
QTest::newRow("non-emphasis inline asterisk") << "3 * 4" << "3 * 4\n\n";
QTest::newRow("arithmetic") << "(2 * a * x + b)^2 = b^2 - 4 * a * c" << "(2 * a * x + b)^2 = b^2 - 4 * a * c\n\n";
QTest::newRow("escaped asterisk after newline") <<
@@ -495,13 +826,42 @@ void tst_QTextMarkdownWriter::fromHtml_data()
QTest::newRow("code") <<
"<pre class=\"language-pseudocode\">\n#include \"foo.h\"\n\nblock {\n statement();\n}\n\n</pre>" <<
"```pseudocode\n#include \"foo.h\"\n\nblock {\n statement();\n}\n\n```\n\n";
- // TODO
-// QTest::newRow("escaped number and paren after double newline") <<
-// "<p>(The first sentence of this paragraph is a line, the next paragraph has a number</p>13) but that's not part of an ordered list" <<
-// "(The first sentence of this paragraph is a line, the next paragraph has a number\n\n13\\) but that's not part of an ordered list\n\n";
+ QTest::newRow("escaped number and paren after single newline") <<
+ "<p>(The first sentence of this paragraph is a line, next paragraph has a number 13) but that's not part of an ordered list</p>" <<
+ "(The first sentence of this paragraph is a line, next paragraph has a number\n13\\) but that's not part of an ordered list\n\n";
+ QTest::newRow("escaped number and paren after double newline") <<
+ "<p>(The first sentence of this paragraph is a line, the next paragraph has a number</p>13) but that's not part of an ordered list" <<
+ "(The first sentence of this paragraph is a line, the next paragraph has a number\n\n13\\) but that's not part of an ordered list\n\n";
QTest::newRow("preformats with embedded backticks") <<
"<pre>none `one` ``two``</pre>plain<pre>```three``` ````four````</pre>plain" <<
"```\nnone `one` ``two``\n\n```\nplain\n\n```\n```three``` ````four````\n\n```\nplain\n\n";
+ QTest::newRow("list items with and without checkboxes") <<
+ "<ul><li>bullet</li><li class=\"unchecked\">unchecked item</li><li class=\"checked\">checked item</li></ul>" <<
+ "- bullet\n- [ ] unchecked item\n- [x] checked item\n";
+ QTest::newRow("table with backslash in cell") << // QTBUG-96051
+ "<table><tr><td>1011011 [</td><td>1011100 backslash \\</td></tr></table>" <<
+ "|1011011 [|1011100 backslash \\\\|";
+ // https://spec.commonmark.org/0.31.2/#example-12
+ // escaping punctuation is ok, but QTextMarkdownWriter currently doesn't do that (which is also ok)
+ QTest::newRow("punctuation") <<
+ R"(<p>!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</p>)" <<
+ R"(!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~)";
+ // https://spec.commonmark.org/0.31.2/#example-14
+ QTest::newRow("backslash asterisk no emphasis") << // QTBUG-122083
+ R"(\*no emphasis*)" <<
+ R"(\\\*no emphasis*)";
+ // https://spec.commonmark.org/0.31.2/#example-15
+ QTest::newRow("backslash before emphasis") <<
+ R"(\<em>emphasis</em>)" <<
+ R"(\\*emphasis*)";
+ // https://spec.commonmark.org/0.31.2/#example-20
+ QTest::newRow("backslash-asterisk in autolink") <<
+ R"(<p><a href="https://example.com?find=\\*">https://example.com?find=\*</a></p>)" <<
+ R"(<https://example.com?find=\\*>)";
+ // https://spec.commonmark.org/0.31.2/#example-24
+ QTest::newRow("plus in fenced code lang") <<
+ "<pre class=\"language-foo+bar\">foo</pre>" <<
+ "```foo+bar\nfoo\n```";
}
void tst_QTextMarkdownWriter::fromHtml()
@@ -521,6 +881,137 @@ void tst_QTextMarkdownWriter::fromHtml()
}
#endif
+ output = output.trimmed();
+ expectedOutput = expectedOutput.trimmed();
+ if (output != expectedOutput && (isMainFontFixed() || isFixedFontProportional()))
+ QEXPECT_FAIL("", "fixed main font or proportional fixed font (QTBUG-103484)", Continue);
+ QCOMPARE(output, expectedOutput);
+}
+
+void tst_QTextMarkdownWriter::fromPlainTextAndBack_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("expectedMarkdown");
+
+ // tests to verify that fixing QTBUG-122083 is safe
+ QTest::newRow("single backslashes") <<
+ R"(\ again: \ not esc: \* \-\-\ \*abc*)" <<
+ R"(\\ again: \\ not esc: \\* \\-\\-\\ \\\*abc*)";
+ // https://spec.commonmark.org/0.31.2/#example-12
+ QTest::newRow("punctuation") <<
+ R"(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)" <<
+ R"(!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~)";
+ // https://spec.commonmark.org/0.31.2/#example-13
+ QTest::newRow("literal backslashes") <<
+ QString(uR"(\→\A\a\ \3\φ\«)") <<
+ "\\\\\u2192\\\\A\\\\a\\\\ \\\\3\\\\\u03C6\\\\\u00AB";
+ // 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)";
+ // end of tests to verify that fixing QTBUG-122083 is safe
+ // (it's ok to add unrelated plain-to-markdown-to-plaintext cases later)
+}
+
+void tst_QTextMarkdownWriter::fromPlainTextAndBack()
+{
+ QFETCH(QString, input);
+ QFETCH(QString, expectedMarkdown);
+
+ document->setPlainText(input);
+ QString output = documentToUnixMarkdown();
+
+#ifdef DEBUG_WRITE_OUTPUT
+ {
+ QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".md");
+ out.open(QFile::WriteOnly);
+ out.write(output.toUtf8());
+ out.close();
+ }
+#endif
+
+ output = output.trimmed();
+ expectedMarkdown = expectedMarkdown.trimmed();
+ if (output != expectedMarkdown && (isMainFontFixed() || isFixedFontProportional()))
+ QSKIP("", "fixed main font or proportional fixed font (QTBUG-103484)");
+ QCOMPARE(output, expectedMarkdown);
+ QCOMPARE(document->toPlainText(), input);
+ document->setMarkdown(output);
+ QCOMPARE(document->toPlainText(), input);
+ if (document->blockCount() == 1)
+ QCOMPARE(document->firstBlock().text(), input);
+}
+
+void tst_QTextMarkdownWriter::escapeSpecialCharacters_data()
+{
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("expectedOutput");
+
+ QTest::newRow("backslash") << "foo \\ bar \\\\ baz \\" << "foo \\\\ bar \\\\\\\\ baz \\\\";
+ QTest::newRow("not emphasized") << "*normal* **normal too**" << "\\*normal* \\**normal too**";
+ QTest::newRow("not code") << "`normal` `normal too`" << "\\`normal` \\`normal too`";
+ QTest::newRow("code fence") << "```not a fence; ``` no risk here; ```not a fence" // TODO slightly inconsistent
+ << "\\```not a fence; ``` no risk here; \\```not a fence";
+ QTest::newRow("not html") << "<p>not a tag: <br/> nope</p>" << "\\<p>not a tag: \\<br/> nope\\</p>";
+ QTest::newRow("not a link") << "text [not a link](/foo)" << "text \\[not a link](/foo)";
+ QTest::newRow("not a circle") << "* polaris" << "\\* polaris";
+ QTest::newRow("not a square") << "+ groovy" << "\\+ groovy";
+ QTest::newRow("not a bullet") << "- stayin alive" << "\\- stayin alive";
+ QTest::newRow("arithmetic") << "1 + 2 - 3 * 4" << "1 + 2 - 3 * 4";
+ QTest::newRow("not a list") << "1. not a list" << "1\\. not a list";
+ QTest::newRow("not a list either") << "Jupiter and 10." << "Jupiter and 10.";
+ QTest::newRow("not a heading") << "# not a heading" << "\\# not a heading";
+ QTest::newRow("a non-entity") << "&ouml; not a character entity" << "\\&ouml; not a character entity";
+}
+
+/*! \internal
+ If the user types into a Qt-based editor plain text that the
+ markdown parser would misinterpret, escape it when we save to markdown
+ to clarify that it's plain text.
+ https://spec.commonmark.org/0.31.2/#backslash-escapes
+*/
+void tst_QTextMarkdownWriter::escapeSpecialCharacters() // QTBUG-96051, QTBUG-122083
+{
+ QFETCH(QString, input);
+ QFETCH(QString, expectedOutput);
+
+ document->setPlainText(input);
+ QString output = documentToUnixMarkdown();
+
+#ifdef DEBUG_WRITE_OUTPUT
+ {
+ QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".md");
+ out.open(QFile::WriteOnly);
+ out.write(output.toUtf8());
+ out.close();
+ }
+#endif
+
+ output = output.trimmed();
if (output != expectedOutput && (isMainFontFixed() || isFixedFontProportional()))
QEXPECT_FAIL("", "fixed main font or proportional fixed font (QTBUG-103484)", Continue);
QCOMPARE(output, expectedOutput);