// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" // #define DEBUG_WRITE_OUTPUT QT_FORWARD_DECLARE_CLASS(QTextDocument) class tst_QTextDocument : public QObject { Q_OBJECT public: tst_QTextDocument(); private slots: void init(); void cleanup(); void cleanupTestCase(); void getSetCheck(); void isEmpty(); void find_data(); void find(); void find2(); void findWithRegularExpression_data(); void findWithRegularExpression(); void findMultiple(); void basicIsModifiedChecks(); void moreIsModified(); void isModified2(); void isModified3(); void isModified4(); void noundo_basicIsModifiedChecks(); void noundo_moreIsModified(); void noundo_isModified2(); void noundo_isModified3(); void mightBeRichText(); void mightBeRichText_data(); void task240325(); void preFont(); void stylesheetFont_data(); void stylesheetFont(); void toHtml_data(); void toHtml(); void toHtml2(); void setFragmentMarkersInHtmlExport(); void setMediaRule(); void toHtmlBodyBgColor(); void toHtmlBodyBgColorRgba(); void toHtmlBodyBgColorTransparent(); void toHtmlRootFrameProperties(); void toHtmlLineHeightProperties(); void toHtmlDefaultFontSpacingProperties(); void toHtmlTextDecorationUnderline(); void capitalizationHtmlInExport(); void wordspacingHtmlExport(); void cursorPositionChanged(); void cursorPositionChangedOnSetText(); void textFrameIterator(); void markContentsDirty(); void clonePreservesMetaInformation(); void clonePreservesPageSize(); void clonePreservesPageBreakPolicies(); void clonePreservesDefaultFont(); void clonePreservesRootFrameFormat(); void clonePreservesResources(); void clonePreservesUserStates(); void clonePreservesIndentWidth(); void clonePreservesFormatsWhenEmpty(); void blockCount(); void defaultStyleSheet(); void defaultTableStyle_data(); void defaultTableStyle(); void resolvedFontInEmptyFormat(); void defaultRootFrameMargin(); void clearResources(); void setPlainText(); void toPlainText_data(); void toPlainText(); void toRawText(); void deleteTextObjectsOnClear(); void maximumBlockCount(); void adjustSize(); void initialUserData(); void html_defaultFont(); void blockCountChanged(); void nonZeroDocumentLengthOnClear(); void setTextPreservesUndoRedoEnabled(); void firstLast(); void backgroundImage_toHtml(); void backgroundImage_toHtml2(); void backgroundImage_clone(); void backgroundImage_copy(); void documentCleanup(); void characterAt(); void revisions(); void revisionWithUndoCompressionAndUndo(); void testUndoCommandAdded(); void testUndoBlocks(); void receiveCursorPositionChangedAfterContentsChange(); void copiedFontSize(); void QTBUG25778_pixelSizeFromHtml(); void htmlExportImportBlockCount(); void QTBUG27354_spaceAndSoftSpace(); void baseUrl_data(); void baseUrl(); void QTBUG28998_linkColor(); void textCursorUsageWithinContentsChange(); void cssInheritance(); void lineHeightType(); void cssLineHeightMultiplier(); void fontTagFace(); void clearUndoRedoStacks(); void mergeFontFamilies(); void resourceProvider(); void contentsChangeIndices_data(); void contentsChangeIndices(); void insertHtmlWithComments_data(); void insertHtmlWithComments(); void delayedLayout(); private: void backgroundImage_checkExpectedHtml(const QTextDocument &doc); void buildRegExpData(); static QString cssFontSizeString(const QFont &font); void writeActualAndExpected(const char* testTag, const QString &actual, const QString &expected); QTextDocument *doc; QTextCursor cursor; QFont defaultFont; QString htmlHead; QString htmlTail; }; class MyAbstractTextDocumentLayout : public QAbstractTextDocumentLayout { public: MyAbstractTextDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc) {} void draw(QPainter *, const PaintContext &) override {} int hitTest(const QPointF &, Qt::HitTestAccuracy) const override { return 0; } int pageCount() const override { return 0; } QSizeF documentSize() const override { return QSizeF(); } QRectF frameBoundingRect(QTextFrame *) const override { return QRectF(); } QRectF blockBoundingRect(const QTextBlock &) const override { return QRectF(); } void documentChanged(int, int, int) override {} }; QString tst_QTextDocument::cssFontSizeString(const QFont &font) { return font.pointSize() >= 0 ? QString::number(font.pointSizeF()) + QStringLiteral("pt") : QString::number(font.pixelSize()) + QStringLiteral("px"); } void tst_QTextDocument::writeActualAndExpected(const char *testTag, const QString &actual, const QString &expected) { #ifdef DEBUG_WRITE_OUTPUT { QFile out(QDir::temp().absoluteFilePath(QLatin1String(testTag) + QLatin1String("-actual.html"))); out.open(QFile::WriteOnly); out.write(actual.toUtf8()); out.close(); } { QFile out(QDir::temp().absoluteFilePath(QLatin1String(testTag) + QLatin1String("-expected.html"))); out.open(QFile::WriteOnly); out.write(expected.toUtf8()); out.close(); } #else Q_UNUSED(testTag); Q_UNUSED(actual); Q_UNUSED(expected); #endif } // Testing get/set functions void tst_QTextDocument::getSetCheck() { QTextDocument obj1; // QAbstractTextDocumentLayout * QTextDocument::documentLayout() // void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *) QPointer var1 = new MyAbstractTextDocumentLayout(0); obj1.setDocumentLayout(var1); QCOMPARE(static_cast(var1), obj1.documentLayout()); obj1.setDocumentLayout((QAbstractTextDocumentLayout *)0); QVERIFY(var1.isNull()); QVERIFY(obj1.documentLayout()); // bool QTextDocument::useDesignMetrics() // void QTextDocument::setUseDesignMetrics(bool) obj1.setUseDesignMetrics(false); QCOMPARE(false, obj1.useDesignMetrics()); obj1.setUseDesignMetrics(true); QCOMPARE(true, obj1.useDesignMetrics()); } tst_QTextDocument::tst_QTextDocument() { QImage img(16, 16, QImage::Format_ARGB32_Premultiplied); img.save("foo.png"); } void tst_QTextDocument::init() { doc = new QTextDocument; cursor = QTextCursor(doc); defaultFont = QFont(); htmlHead = QString("\n" "" "\n"); htmlHead = htmlHead.arg(defaultFont.family()) .arg(cssFontSizeString(defaultFont)) .arg(defaultFont.weight()) .arg((defaultFont.italic() ? "italic" : "normal")); htmlTail = QString(""); } void tst_QTextDocument::cleanup() { cursor = QTextCursor(); delete doc; doc = 0; } void tst_QTextDocument::cleanupTestCase() { QFile::remove(QLatin1String("foo.png")); } void tst_QTextDocument::isEmpty() { QVERIFY(doc->isEmpty()); } void tst_QTextDocument::find_data() { QTest::addColumn("haystack"); QTest::addColumn("needle"); QTest::addColumn("flags"); QTest::addColumn("from"); QTest::addColumn("anchor"); QTest::addColumn("position"); QTest::newRow("1") << "Hello World" << "World" << int(QTextDocument::FindCaseSensitively) << 0 << 6 << 11; QTest::newRow("2") << QString::fromLatin1("Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1("World") << "World" << int(QTextDocument::FindCaseSensitively) << 1 << 6 << 11; QTest::newRow("3") << QString::fromLatin1("Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1("World") << "Hello" << int(QTextDocument::FindCaseSensitively | QTextDocument::FindBackward) << 10 << 0 << 5; QTest::newRow("4wholewords") << QString::fromLatin1("Hello Blah World") << "Blah" << int(QTextDocument::FindWholeWords) << 0 << 6 << 10; QTest::newRow("5wholewords") << QString::fromLatin1("HelloBlahWorld") << "Blah" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1; QTest::newRow("6wholewords") << QString::fromLatin1("HelloBlahWorld Blah Hah") << "Blah" << int(QTextDocument::FindWholeWords) << 0 << 15 << 19; QTest::newRow("7wholewords") << QString::fromLatin1("HelloBlahWorld Blah Hah") << "Blah" << int(QTextDocument::FindWholeWords | QTextDocument::FindBackward) << 23 << 15 << 19; QTest::newRow("8wholewords") << QString::fromLatin1("Hello: World\n") << "orld" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1; QTest::newRow("across-paragraphs") << QString::fromLatin1("First Parag\nSecond Parag with a lot more text") << "Parag" << int(QTextDocument::FindBackward) << 15 << 6 << 11; QTest::newRow("nbsp") << "Hello" + QString(QChar(QChar::Nbsp)) +"World" << " " << int(QTextDocument::FindCaseSensitively) << 0 << 5 << 6; QTest::newRow("from-the-end") << "Hello World" << "Hello World" << int(QTextDocument::FindCaseSensitively| QTextDocument::FindBackward) << 11 << 0 << 11; QTest::newRow("bw-cross-paras-1") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 7 << 3 << 4; QTest::newRow("bw-cross-paras-2") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 6 << 3 << 4; QTest::newRow("bw-cross-paras-3") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 5 << 3 << 4; QTest::newRow("bw-cross-paras-4") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 3 << 0 << 1; QTest::newRow("bw-cross-paras-5") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 5 << 1 << 2; QTest::newRow("bw-cross-paras-6") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 4 << 1 << 2; QTest::newRow("bw-cross-paras-7") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 3 << 1 << 2; QTest::newRow("bw-cross-paras-8") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 2 << 1 << 2; } void tst_QTextDocument::find() { QFETCH(QString, haystack); QFETCH(QString, needle); QFETCH(int, flags); QFETCH(int, from); QFETCH(int, anchor); QFETCH(int, position); cursor.insertText(haystack); cursor = doc->find(needle, from, QTextDocument::FindFlags(flags)); if (anchor != -1) { QCOMPARE(cursor.anchor(), anchor); QCOMPARE(cursor.position(), position); } else { QVERIFY(cursor.isNull()); } //search using a regular expression QRegularExpression expr(QRegularExpression::escape(needle)); QTextDocument::FindFlags flg(flags); cursor = doc->find(expr, from, flg); if (anchor != -1) { QCOMPARE(cursor.anchor(), anchor); QCOMPARE(cursor.position(), position); } else { QVERIFY(cursor.isNull()); } } void tst_QTextDocument::findWithRegularExpression_data() { buildRegExpData(); } void tst_QTextDocument::findWithRegularExpression() { QFETCH(QString, haystack); QFETCH(QString, needle); QFETCH(int, flags); QFETCH(int, from); QFETCH(int, anchor); QFETCH(int, position); cursor.insertText(haystack); //search using a regular expression QRegularExpression expr(needle); QTextDocument::FindFlags flg(flags); cursor = doc->find(expr, from, flg); if (anchor != -1) { QCOMPARE(cursor.anchor(), anchor); QCOMPARE(cursor.position(), position); } else { QVERIFY(cursor.isNull()); } } void tst_QTextDocument::find2() { doc->setPlainText("aaa"); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); QTextCursor hit = doc->find("a", cursor); QCOMPARE(hit.position(), 2); QCOMPARE(hit.anchor(), 1); } void tst_QTextDocument::findMultiple() { const QString text("foo bar baz foo bar baz"); doc->setPlainText(text); cursor.movePosition(QTextCursor::Start); cursor = doc->find("bar", cursor); QCOMPARE(cursor.selectionStart(), text.indexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); cursor = doc->find("bar", cursor); QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); cursor.movePosition(QTextCursor::End); cursor = doc->find("bar", cursor, QTextDocument::FindBackward); QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); cursor = doc->find("bar", cursor, QTextDocument::FindBackward); QCOMPARE(cursor.selectionStart(), text.indexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); QRegularExpression regularExpression("bar"); cursor.movePosition(QTextCursor::End); cursor = doc->find(regularExpression, cursor, QTextDocument::FindBackward); QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); cursor = doc->find(regularExpression, cursor, QTextDocument::FindBackward); QCOMPARE(cursor.selectionStart(), text.indexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); cursor.movePosition(QTextCursor::Start); cursor = doc->find(regularExpression, cursor); QCOMPARE(cursor.selectionStart(), text.indexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); cursor = doc->find(regularExpression, cursor); QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar")); QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3); } void tst_QTextDocument::basicIsModifiedChecks() { QSignalSpy spy(doc, SIGNAL(modificationChanged(bool))); QVERIFY(!doc->isModified()); cursor.insertText("Hello World"); QVERIFY(doc->isModified()); QCOMPARE(spy.size(), 1); QVERIFY(spy.takeFirst().at(0).toBool()); doc->undo(); QVERIFY(!doc->isModified()); QCOMPARE(spy.size(), 1); QVERIFY(!spy.takeFirst().at(0).toBool()); doc->redo(); QVERIFY(doc->isModified()); QCOMPARE(spy.size(), 1); QVERIFY(spy.takeFirst().at(0).toBool()); } void tst_QTextDocument::moreIsModified() { QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); doc->undo(); QVERIFY(!doc->isModified()); cursor.insertText("Hello"); doc->undo(); QVERIFY(!doc->isModified()); } void tst_QTextDocument::isModified2() { // reported on qt4-preview-feedback QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); doc->setModified(false); QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); } void tst_QTextDocument::isModified3() { QVERIFY(!doc->isModified()); doc->setUndoRedoEnabled(false); doc->setUndoRedoEnabled(true); cursor.insertText("Hello"); QVERIFY(doc->isModified()); doc->undo(); QVERIFY(!doc->isModified()); } void tst_QTextDocument::isModified4() { QVERIFY(!doc->isModified()); cursor.insertText("Hello"); cursor.insertText("World"); doc->setModified(false); QVERIFY(!doc->isModified()); cursor.insertText("Again"); QVERIFY(doc->isModified()); doc->undo(); QVERIFY(!doc->isModified()); doc->undo(); QVERIFY(doc->isModified()); doc->redo(); QVERIFY(!doc->isModified()); doc->redo(); QVERIFY(doc->isModified()); doc->undo(); QVERIFY(!doc->isModified()); doc->undo(); QVERIFY(doc->isModified()); //task 197769 cursor.insertText("Hello"); QVERIFY(doc->isModified()); } void tst_QTextDocument::noundo_basicIsModifiedChecks() { doc->setUndoRedoEnabled(false); QSignalSpy spy(doc, SIGNAL(modificationChanged(bool))); QVERIFY(!doc->isModified()); cursor.insertText("Hello World"); QVERIFY(doc->isModified()); QCOMPARE(spy.size(), 1); QVERIFY(spy.takeFirst().at(0).toBool()); doc->undo(); QVERIFY(doc->isModified()); QCOMPARE(spy.size(), 0); doc->redo(); QVERIFY(doc->isModified()); QCOMPARE(spy.size(), 0); } void tst_QTextDocument::task240325() { doc->setHtml("Foobar Foobar Foobar Foobar"); QImage img(1000, 7000, QImage::Format_ARGB32_Premultiplied); QPainter p(&img); QFontMetrics fm(p.font()); // Set page size to contain image and one "Foobar" doc->setPageSize(QSize(100 + fm.horizontalAdvance("Foobar")*2, 1000)); // Force layout doc->drawContents(&p); QCOMPARE(doc->blockCount(), 1); for (QTextBlock block = doc->begin() ; block!=doc->end() ; block = block.next()) { QTextLayout *layout = block.layout(); QCOMPARE(layout->lineCount(), 4); for (int lineIdx=0;lineIdxlineCount();++lineIdx) { QTextLine line = layout->lineAt(lineIdx); QString text = block.text().mid(line.textStart(), line.textLength()).trimmed(); // Remove start token if (lineIdx == 0) text = text.mid(1); QCOMPARE(text, QString::fromLatin1("Foobar")); } } } void tst_QTextDocument::stylesheetFont_data() { QTest::addColumn("stylesheet"); QTest::addColumn("font"); { QFont font; font.setBold(true); font.setPixelSize(64); QTest::newRow("Regular font specification") << "font-size: 64px; font-weight: bold;" << font; } { QFont font; font.setBold(true); font.setPixelSize(64); QTest::newRow("Shorthand font specification") << "font: normal bold 64px Arial;" << font; } } void tst_QTextDocument::stylesheetFont() { QFETCH(QString, stylesheet); QFETCH(QFont, font); QString html = QString::fromLatin1("" "" "
" "Foobar" "
" "" "").arg(stylesheet); qDebug() << html; doc->setHtml(html); QCOMPARE(doc->blockCount(), 1); // First and only block QTextBlock block = doc->firstBlock(); QString text = block.text(); QCOMPARE(text, QString::fromLatin1("Foobar")); QFont actualFont = block.charFormat().font(); QCOMPARE(actualFont.bold(), font.bold()); QCOMPARE(actualFont.pixelSize(), font.pixelSize()); } void tst_QTextDocument::preFont() { const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); const QString html = QString::fromLatin1( "" "" "
"
                                                "Foobar"
                                                "
" "" ""); doc->setHtml(html); QCOMPARE(doc->blockCount(), 1); // First and only block QTextBlock block = doc->firstBlock(); QString text = block.text(); QCOMPARE(text, QString::fromLatin1("Foobar")); QFont actualFont = block.charFormat().font(); QCOMPARE(actualFont.family(), font.family()); } void tst_QTextDocument::noundo_moreIsModified() { doc->setUndoRedoEnabled(false); QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); doc->undo(); QVERIFY(doc->isModified()); cursor.insertText("Hello"); doc->undo(); QVERIFY(doc->isModified()); } void tst_QTextDocument::noundo_isModified2() { // reported on qt4-preview-feedback QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); doc->setModified(false); QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); } void tst_QTextDocument::noundo_isModified3() { doc->setUndoRedoEnabled(false); QVERIFY(!doc->isModified()); cursor.insertText("Hello"); QVERIFY(doc->isModified()); doc->undo(); QVERIFY(doc->isModified()); } void tst_QTextDocument::mightBeRichText_data() { const char qtDocuHeader[] = "\n" "\n" ""; QVERIFY(Qt::mightBeRichText(QString::fromLatin1(qtDocuHeader))); QTest::addColumn("input"); QTest::addColumn("result"); QTest::newRow("documentation-header") << QString("\n" "\n" "") << true; QTest::newRow("br-nospace") << QString("Test
new line") << true; QTest::newRow("br-space") << QString("Test
new line") << true; QTest::newRow("br-invalidspace") << QString("Test
new line") << false; QTest::newRow("invalid closing tag") << QString("Test
("input"); QTest::addColumn("expectedOutput"); { CREATE_DOC_AND_CURSOR(); cursor.insertText("Blah"); QTest::newRow("simple") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); cursor.insertText("&<>"); QTest::newRow("entities") << QTextDocumentFragment(&doc) << QString("

&<>

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontFamilies({QLatin1String("Times")}); cursor.insertText("Blah", fmt); QTest::newRow("font-family") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontFamilies({QLatin1String("Foo's Family")}); cursor.insertText("Blah", fmt); QTest::newRow("font-family-with-quotes1") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontFamilies({QLatin1String("Foo\"s Family")}); cursor.insertText("Blah", fmt); QTest::newRow("font-family-with-quotes2") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontFamilies(QStringList{ "Times", "serif" }); cursor.insertText("Blah", fmt); QTest::newRow("font-family-with-fallback") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setNonBreakableLines(true); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("pre") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("
Blah
"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontPointSize(40); cursor.insertText("Blah", fmt); QTest::newRow("font-size") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setProperty(QTextFormat::FontSizeIncrement, 2); cursor.insertText("Blah", fmt); QTest::newRow("logical-font-size") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); cursor.insertText("Foo"); QTextCharFormat fmt; fmt.setFontPointSize(40); cursor.insertBlock(QTextBlockFormat(), fmt); fmt.clearProperty(QTextFormat::FontPointSize); cursor.insertText("Blub", fmt); QTest::newRow("no-font-size") << QTextDocumentFragment(&doc) << QString("

Foo

\n

Blub

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setLayoutDirection(Qt::RightToLeft); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("rtl") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignJustify); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("blockalign") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignCenter); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("blockalign2") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("blockalign3") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setBackground(QColor("#0000ff")); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("bgcolor") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setBackground(QColor(255, 0, 0, 51)); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("bgcolor-rgba") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setBackground(QColor(255, 0, 0, 0)); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("bgcolor-transparent") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontWeight(320); cursor.insertText("Blah", fmt); QTest::newRow("font-weight") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontItalic(true); cursor.insertText("Blah", fmt); QTest::newRow("font-italic") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setFontUnderline(true); fmt.setFontOverline(false); cursor.insertText("Blah", fmt); QTest::newRow("text-decoration-1") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setForeground(QColor("#00ff00")); cursor.insertText("Blah", fmt); QTest::newRow("color") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setForeground(QColor(0, 255, 0, 51)); cursor.insertText("Blah", fmt); QTest::newRow("color-rgba") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setForeground(QColor(0, 255, 0, 0)); cursor.insertText("Blah", fmt); QTest::newRow("color-transparent") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setBackground(QColor("#00ff00")); cursor.insertText("Blah", fmt); QTest::newRow("span-bgcolor") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setBackground(QColor(0, 255, 0, 51)); cursor.insertText("Blah", fmt); QTest::newRow("span-bgcolor-rgba") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setBackground(QColor(0, 255, 0, 0)); cursor.insertText("Blah", fmt); QTest::newRow("span-bgcolor-transparent") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setVerticalAlignment(QTextCharFormat::AlignSubScript); cursor.insertText("Blah", fmt); QTest::newRow("valign-sub") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setVerticalAlignment(QTextCharFormat::AlignSuperScript); cursor.insertText("Blah", fmt); QTest::newRow("valign-super") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setAnchor(true); fmt.setAnchorNames({"blub"}); cursor.insertText("Blah", fmt); QTest::newRow("named anchor") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setAnchor(true); fmt.setAnchorHref("http://www.kde.org/"); cursor.insertText("Blah", fmt); QTest::newRow("href anchor") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setAnchor(true); fmt.setAnchorHref("http://www.kde.org/?a=1&b=2"); cursor.insertText("Blah", fmt); QTest::newRow("href anchor with &") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setAnchor(true); fmt.setAnchorHref("http://www.kde.org/?a='&b=\""); cursor.insertText("Blah", fmt); QTest::newRow("href anchor with ' and \"") << QTextDocumentFragment(&doc) << QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); cursor.insertTable(2, 2); QTest::newRow("simpletable") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); QTextTable *table = cursor.insertTable(1, 4); table->mergeCells(0, 0, 1, 2); table->mergeCells(0, 2, 1, 2); QTest::newRow("tablespans") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); QTextTableFormat fmt; fmt.setBorder(1); fmt.setCellSpacing(3); fmt.setCellPadding(3); fmt.setBackground(QColor("#ff00ff")); fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50)); fmt.setAlignment(Qt::AlignHCenter); fmt.setPosition(QTextFrameFormat::FloatRight); cursor.insertTable(2, 2, fmt); QTest::newRow("tableattrs") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); QTextTableFormat fmt; fmt.setBorder(1); fmt.setCellSpacing(3); fmt.setCellPadding(3); fmt.setBackground(QColor("#ff00ff")); fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50)); fmt.setAlignment(Qt::AlignHCenter); fmt.setPosition(QTextFrameFormat::FloatRight); fmt.setLeftMargin(25); fmt.setBottomMargin(35); cursor.insertTable(2, 2, fmt); QTest::newRow("tableattrs2") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); QTextTableFormat fmt; fmt.setHeaderRowCount(2); cursor.insertTable(4, 2, fmt); QTest::newRow("tableheader") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "\n\n\n" "\n\n\n" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); QTextTable *table = cursor.insertTable(2, 2); QTextTable *subTable = table->cellAt(0, 1).firstCursorPosition().insertTable(1, 1); subTable->cellAt(0, 0).firstCursorPosition().insertText("Hey"); QTest::newRow("nestedtable") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "\n\n\n" "
\n\n\n
\n

Hey

"); } { CREATE_DOC_AND_CURSOR(); QTextTableFormat fmt; QList widths; widths.append(QTextLength()); widths.append(QTextLength(QTextLength::PercentageLength, 30)); widths.append(QTextLength(QTextLength::FixedLength, 40)); fmt.setColumnWidthConstraints(widths); cursor.insertTable(1, 3, fmt); QTest::newRow("colwidths") << QTextDocumentFragment(&doc) << QString("" "\n\n\n\n" "
"); } // ### rowspan/colspan tests, once texttable api for that is back again // { CREATE_DOC_AND_CURSOR(); QTextTable *table = cursor.insertTable(1, 1); QTextCursor cellCurs = table->cellAt(0, 0).firstCursorPosition(); QTextCharFormat fmt; fmt.setBackground(QColor("#ffffff")); cellCurs.mergeBlockCharFormat(fmt); QTest::newRow("cellproperties") << QTextDocumentFragment(&doc) << QString("" "\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); // ### fixme: use programmatic api as soon as we can create floats through it const char html[] = "BlahBlubb"; QTest::newRow("image") << QTextDocumentFragment::fromHtml(QString::fromLatin1(html)) << QString("

BlahBlubb

"); } { CREATE_DOC_AND_CURSOR(); QTextImageFormat fmt; fmt.setName("foo"); fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle); cursor.insertImage(fmt); QTest::newRow("image-align-middle") << QTextDocumentFragment(&doc) << QString("

"); } { CREATE_DOC_AND_CURSOR(); QTextImageFormat fmt; fmt.setName("foo"); fmt.setVerticalAlignment(QTextCharFormat::AlignTop); cursor.insertImage(fmt); QTest::newRow("image-align-top") << QTextDocumentFragment(&doc) << QString("

"); } { CREATE_DOC_AND_CURSOR(); QTextImageFormat fmt; fmt.setName("foo"); cursor.insertImage(fmt); cursor.insertImage(fmt); QTest::newRow("2images") << QTextDocumentFragment(&doc) << QString("

"); } { CREATE_DOC_AND_CURSOR(); QString txt = QLatin1String("Blah"); txt += QChar::LineSeparator; txt += QLatin1String("Bar"); cursor.insertText(txt); QTest::newRow("linebreaks") << QTextDocumentFragment(&doc) << QString("

Blah
Bar

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setTopMargin(10); fmt.setBottomMargin(20); fmt.setLeftMargin(30); fmt.setRightMargin(40); cursor.insertBlock(fmt); cursor.insertText("Blah"); QTest::newRow("blockmargins") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextList *list = cursor.insertList(QTextListFormat::ListDisc); cursor.insertText("Blubb"); cursor.insertBlock(); cursor.insertText("Blah"); QCOMPARE(list->count(), 2); QTest::newRow("lists") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("
    \n
  • Blubb
  • \n
  • Blah
"); } { CREATE_DOC_AND_CURSOR(); QTextList *list = cursor.insertList(QTextListFormat::ListDisc); cursor.insertText("Blubb"); cursor.insertBlock(); QTextCharFormat blockCharFmt; blockCharFmt.setForeground(QColor("#0000ff")); cursor.mergeBlockCharFormat(blockCharFmt); QTextCharFormat fmt; fmt.setForeground(QColor("#ff0000")); cursor.insertText("Blah", fmt); QCOMPARE(list->count(), 2); QTest::newRow("charfmt-for-list-item") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("
    \n
  • Blubb
  • \n
  • Blah
"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setIndent(3); fmt.setTextIndent(30); cursor.insertBlock(fmt); cursor.insertText("Test"); QTest::newRow("block-indent") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Test

"); } { CREATE_DOC_AND_CURSOR(); QTextListFormat fmt; fmt.setStyle(QTextListFormat::ListDisc); fmt.setIndent(4); cursor.insertList(fmt); cursor.insertText("Blah"); QTest::newRow("list-indent") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("
    \n
  • Blah
"); } { CREATE_DOC_AND_CURSOR(); cursor.insertBlock(); QTest::newRow("emptyblock") << QTextDocumentFragment(&doc) // after insertBlock() we /do/ have two blocks in the document, so also expect // these in the html output << QString("EMPTYBLOCK") + QString("EMPTYBLOCK"); } { CREATE_DOC_AND_CURSOR(); // if you press enter twice in an empty textedit and then insert 'Test' // you actually get three visible paragraphs, two empty leading ones and // a third with the actual text. the corresponding html representation // therefore should also contain three paragraphs. cursor.insertBlock(); QTextCharFormat fmt; fmt.setForeground(QColor("#00ff00")); fmt.setProperty(QTextFormat::FontSizeIncrement, 1); cursor.mergeBlockCharFormat(fmt); fmt.setProperty(QTextFormat::FontSizeIncrement, 2); cursor.insertText("Test", fmt); QTest::newRow("blockcharfmt") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK

Test

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setForeground(QColor("#00ff00")); cursor.setBlockCharFormat(fmt); fmt.setForeground(QColor("#0000ff")); cursor.insertText("Test", fmt); QTest::newRow("blockcharfmt2") << QTextDocumentFragment(&doc) << QString("

Test

"); } { QTest::newRow("horizontal-ruler") << QTextDocumentFragment::fromHtml("
") << QString("EMPTYBLOCK") + QString("
"); } { QTest::newRow("horizontal-ruler-with-width") << QTextDocumentFragment::fromHtml("
") << QString("EMPTYBLOCK") + QString("
"); } { QTest::newRow("horizontal-ruler-with-color") << QTextDocumentFragment::fromHtml("
") << QString("EMPTYBLOCK") + QString("
"); } { QTest::newRow("horizontal-ruler-with-width-and-color") << QTextDocumentFragment::fromHtml("
") << QString("EMPTYBLOCK") + QString("
"); } { CREATE_DOC_AND_CURSOR(); QTextFrame *mainFrame = cursor.currentFrame(); QTextFrameFormat ffmt; ffmt.setBorder(1); ffmt.setPosition(QTextFrameFormat::FloatRight); ffmt.setMargin(2); ffmt.setWidth(100); ffmt.setHeight(50); ffmt.setBackground(QColor("#00ff00")); cursor.insertFrame(ffmt); cursor.insertText("Hello World"); cursor = mainFrame->lastCursorPosition(); QTest::newRow("frame") << QTextDocumentFragment(&doc) << QString("\n\n
\n

Hello World

"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; fmt.setForeground(QColor("#00ff00")); // fmt.setBackground(QColor("#0000ff")); cursor.setBlockCharFormat(fmt); fmt.setForeground(QBrush()); // fmt.setBackground(QBrush()); cursor.insertText("Test", fmt); // QTest::newRow("nostylebrush") << QTextDocumentFragment(&doc) << QString("

Test

"); QTest::newRow("nostylebrush") << QTextDocumentFragment(&doc) << QString("

Test

"); } { CREATE_DOC_AND_CURSOR(); QTextTable *table = cursor.insertTable(2, 2); table->mergeCells(0, 0, 1, 2); QTextTableFormat fmt = table->format(); QList widths; widths.append(QTextLength(QTextLength::FixedLength, 20)); widths.append(QTextLength(QTextLength::FixedLength, 40)); fmt.setColumnWidthConstraints(widths); table->setFormat(fmt); QTest::newRow("mergedtablecolwidths") << QTextDocumentFragment(&doc) << QString("" "\n\n" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); QTextCharFormat fmt; cursor.insertText("Blah\nGreen yellow green"); cursor.setPosition(0); cursor.setPosition(23, QTextCursor::KeepAnchor); fmt.setBackground(Qt::green); cursor.mergeCharFormat(fmt); cursor.clearSelection(); cursor.setPosition(11); cursor.setPosition(17, QTextCursor::KeepAnchor); fmt.setBackground(Qt::yellow); cursor.mergeCharFormat(fmt); cursor.clearSelection(); QTest::newRow("multiparagraph-bgcolor") << QTextDocumentFragment(&doc) << QString("

Blah

\n" "

Green " "yellow" " green

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat fmt; fmt.setBackground(QColor("#0000ff")); cursor.insertBlock(fmt); QTextCharFormat charfmt; charfmt.setBackground(QColor("#0000ff")); cursor.insertText("Blah", charfmt); QTest::newRow("nospan-bgcolor") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextTable *table = cursor.insertTable(2, 2); QTextCharFormat fmt = table->cellAt(0, 0).format(); fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle); table->cellAt(0, 0).setFormat(fmt); fmt = table->cellAt(0, 1).format(); fmt.setVerticalAlignment(QTextCharFormat::AlignTop); table->cellAt(0, 1).setFormat(fmt); fmt = table->cellAt(1, 0).format(); fmt.setVerticalAlignment(QTextCharFormat::AlignBottom); table->cellAt(1, 0).setFormat(fmt); table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); QTest::newRow("table-vertical-alignment") << QTextDocumentFragment(&doc) << QString("" "\n\n" "\n" "\n\n" "\n" "
\n" "

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextTable *table = cursor.insertTable(2, 2); QTextTableCellFormat fmt = table->cellAt(0, 0).format().toTableCellFormat(); fmt.setLeftPadding(1); table->cellAt(0, 0).setFormat(fmt); fmt = table->cellAt(0, 1).format().toTableCellFormat(); fmt.setRightPadding(1); table->cellAt(0, 1).setFormat(fmt); fmt = table->cellAt(1, 0).format().toTableCellFormat(); fmt.setTopPadding(1); table->cellAt(1, 0).setFormat(fmt); fmt = table->cellAt(1, 1).format().toTableCellFormat(); fmt.setBottomPadding(1); table->cellAt(1, 1).setFormat(fmt); table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); QTest::newRow("table-cell-paddings") << QTextDocumentFragment(&doc) << QString("" "\n\n" "\n" "\n\n" "\n" "
\n" "

Blah

"); } { CREATE_DOC_AND_CURSOR(); QTextTableFormat fmt; fmt.setBorderBrush(QColor("#0000ff")); fmt.setBorderStyle(QTextFrameFormat::BorderStyle_Solid); cursor.insertTable(2, 2, fmt); QTest::newRow("tableborder") << QTextDocumentFragment(&doc) << QString("" "\n\n\n" "\n\n\n" "
"); } { CREATE_DOC_AND_CURSOR(); cursor.insertBlock(); cursor.insertText("Foo"); cursor.block().setUserState(42); QTest::newRow("userstate") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Foo

"); } { CREATE_DOC_AND_CURSOR(); QTextBlockFormat blockFmt; blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore); cursor.insertBlock(blockFmt); cursor.insertText("Foo"); blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore | QTextFormat::PageBreak_AlwaysAfter); cursor.insertBlock(blockFmt); cursor.insertText("Bar"); QTextTableFormat tableFmt; tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter); cursor.insertTable(1, 1, tableFmt); QTest::newRow("pagebreak") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("

Foo

" "\n

Bar

" "\n\n\n
"); } { CREATE_DOC_AND_CURSOR(); QTextListFormat listFmt; listFmt.setStyle(QTextListFormat::ListDisc); cursor.insertList(listFmt); cursor.insertText("Blah"); QTest::newRow("list-ul-margin") << QTextDocumentFragment(&doc) << QString("EMPTYBLOCK") + QString("
    \n
  • Blah
"); } { CREATE_DOC_AND_CURSOR(); const QString listHtml = "
  • item-1
  • item-2
    • item-2.1
    • item-2.2" "
      • item-2.2.1
    • item-2.3
      • item-2.3.1" "
  • item-3
"; cursor.insertHtml(listHtml); QTest::newRow("nested-lists-one") << QTextDocumentFragment(&doc) << QString("
    \n
  • " "item-1
  • \n
  • item-2\n
      \n
    • item-2.1
    • \n
    • item-2.2\n
        \n
      • item-2.2.1
    • \n" "
    • item-2.3\n
        \n
      • " "item-2.3.1
  • \n
  • item-3
"); } { CREATE_DOC_AND_CURSOR(); const QString listHtml = "
  • item-1
  • item-2
    • item-2.1
"; cursor.insertHtml(listHtml); QTest::newRow("nested-lists-two") << QTextDocumentFragment(&doc) << QString("
    \n
  • " "item-1
  • \n
  • item-2\n
      \n
    • item-2.1
"); } { CREATE_DOC_AND_CURSOR(); const QString listHtml = "
  • item-1
  • item-2
    • item-2.1
    • item-2.2" "
"; cursor.insertHtml(listHtml); QTest::newRow("nested-lists-three") << QTextDocumentFragment(&doc) << QString("
    \n
  • " "item-1
  • \n
  • item-2\n
      \n
    • item-2.1
    • \n
    • item-2.2
    " "
"); } { CREATE_DOC_AND_CURSOR(); const QString listHtml = "
  • item-1.1
  • item-1.2
" "
  • item-2.1
"; cursor.insertHtml(listHtml); QTest::newRow("not-nested-list") << QTextDocumentFragment(&doc) << QString("
    \n
  • " "item-1.1
  • \n
  • item-1.2
\n
    \n" "
  • item-2.1
"); } { CREATE_DOC_AND_CURSOR(); const QString listHtml = "
  • bullet
  • unchecked item
  • checked item
"; cursor.insertHtml(listHtml); QTest::newRow("list with and without checkboxes") << QTextDocumentFragment(&doc) << QString("
    \n" "
  • bullet
  • \n" "
  • unchecked item
  • \n" "
  • checked item
"); } } void tst_QTextDocument::toHtml() { QFETCH(QTextDocumentFragment, input); QFETCH(QString, expectedOutput); cursor.insertFragment(input); expectedOutput.prepend(htmlHead); expectedOutput.replace("OPENDEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"); expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\""); expectedOutput.replace("EMPTYBLOCK", "


\n"); expectedOutput.replace("DEFAULTULSTYLE", "style=\"margin-top: 0px; margin-bottom: 0px; " "margin-left: 0px; margin-right: 0px; -qt-list-indent:"); expectedOutput.replace("DEFAULTLASTLISTYLE", "style=\" margin-top:0px; margin-bottom:12px; " "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\""); if (expectedOutput.endsWith(QLatin1Char('\n'))) expectedOutput.chop(1); expectedOutput.append(htmlTail); QString output = doc->toHtml(); writeActualAndExpected(QTest::currentDataTag(), output, expectedOutput); QCOMPARE(output, expectedOutput); QDomDocument document; QEXPECT_FAIL("charfmt-for-list-item", "The attribute \"style\" is redefined in the generated HTML, which is not valid " "according to XML standard. The new QDomDocument implementation follows the XML " "standard.", Continue); QVERIFY2(document.setContent(output), "Output was not valid XML"); } void tst_QTextDocument::toHtml2() { QTextDocument doc; doc.setHtml("

text text

"); // 4 spaces before the second 'text' QTextBlock block = doc.firstBlock(); QTextBlock::Iterator iter = block.begin(); QTextFragment f = iter.fragment(); QVERIFY(f.isValid()); QCOMPARE(f.position(), 0); QCOMPARE(f.length(), 5); //qDebug() << block.text().mid(f.position(), f.length()); iter++; f = iter.fragment(); QVERIFY(f.isValid()); QCOMPARE(f.position(), 5); QCOMPARE(f.length(), 1); //qDebug() << block.text().mid(f.position(), f.length()); iter++; f = iter.fragment(); //qDebug() << block.text().mid(f.position(), f.length()); QVERIFY(f.isValid()); QCOMPARE(f.position(), 6); QCOMPARE(f.length(), 5); // 1 space should be preserved. QCOMPARE(block.text().mid(f.position(), f.length()), QString(" text")); doc.setHtml("
foo
text"); // 4 spaces before the second 'text' block = doc.firstBlock().next(); //qDebug() << block.text(); QCOMPARE(block.text(), QString("foo")); block = block.next(); //qDebug() << block.text(); QCOMPARE(block.text(), QString("text")); } void tst_QTextDocument::setFragmentMarkersInHtmlExport() { { CREATE_DOC_AND_CURSOR(); cursor.insertText("Leadin"); const int startPos = cursor.position(); cursor.insertText("Test"); QTextCharFormat fmt; fmt.setForeground(QColor("#00ff00")); cursor.insertText("Blah", fmt); const int endPos = cursor.position(); cursor.insertText("Leadout", QTextCharFormat()); cursor.setPosition(startPos); cursor.setPosition(endPos, QTextCursor::KeepAnchor); QTextDocumentFragment fragment(cursor); QString expected = htmlHead; expected.replace(QRegularExpression(""), QString("")); expected += QString("

TestBlah

") + htmlTail; QCOMPARE(fragment.toHtml(), expected); } { CREATE_DOC_AND_CURSOR(); cursor.insertText("Leadin"); const int startPos = cursor.position(); cursor.insertText("Test"); const int endPos = cursor.position(); cursor.insertText("Leadout", QTextCharFormat()); cursor.setPosition(startPos); cursor.setPosition(endPos, QTextCursor::KeepAnchor); QTextDocumentFragment fragment(cursor); QString expected = htmlHead; expected.replace(QRegularExpression(""), QString("")); expected += QString("

Test

") + htmlTail; QCOMPARE(fragment.toHtml(), expected); } } void tst_QTextDocument::setMediaRule() { { CREATE_DOC_AND_CURSOR(); doc.setDefaultStyleSheet("@media screen { p { background:#000000 } } @media print { p { background:#ffffff } }"); doc.setHtml("

Hello World

"); QString expected = htmlHead; expected += QString("

Hello World

") + htmlTail; QCOMPARE(doc.toHtml(), expected); } { CREATE_DOC_AND_CURSOR(); doc.setDefaultStyleSheet("@media screen { p { background:#000000 } } @media print { p { background:#ffffff } }"); doc.setMetaInformation(QTextDocument::CssMedia, "screen"); doc.setHtml("

Hello World

"); QString expected = htmlHead; expected += QString("

Hello World

") + htmlTail; QCOMPARE(doc.toHtml(), expected); } { CREATE_DOC_AND_CURSOR(); doc.setDefaultStyleSheet("@media screen { p { background:#000000 } } @media print { p { background:#ffffff } }"); doc.setMetaInformation(QTextDocument::CssMedia, "print"); doc.setHtml("

Hello World

"); QString expected = htmlHead; expected += QString("

Hello World

") + htmlTail; QCOMPARE(doc.toHtml(), expected); } } void tst_QTextDocument::toHtmlBodyBgColor() { CREATE_DOC_AND_CURSOR(); cursor.insertText("Blah"); QTextFrameFormat fmt = doc.rootFrame()->frameFormat(); fmt.setBackground(QColor("#0000ff")); doc.rootFrame()->setFrameFormat(fmt); QString expectedHtml = htmlHead; expectedHtml.insert(htmlHead.size() - 2, " bgcolor=\"#0000ff\""); expectedHtml += "

Blah

" + htmlTail; writeActualAndExpected(QTest::currentDataTag(), doc.toHtml(), expectedHtml); QCOMPARE(doc.toHtml(), expectedHtml); } void tst_QTextDocument::toHtmlBodyBgColorRgba() { CREATE_DOC_AND_CURSOR(); cursor.insertText("Blah"); QTextFrameFormat fmt = doc.rootFrame()->frameFormat(); fmt.setBackground(QColor(255, 0, 0, 51)); doc.rootFrame()->setFrameFormat(fmt); QString expectedHtml = htmlHead; expectedHtml.insert(htmlHead.size() - 2, " bgcolor=\"rgba(255,0,0,0.2)\""); expectedHtml += "

Blah

" + htmlTail; writeActualAndExpected(QTest::currentDataTag(), doc.toHtml(), expectedHtml); QCOMPARE(doc.toHtml(), expectedHtml); } void tst_QTextDocument::toHtmlBodyBgColorTransparent() { CREATE_DOC_AND_CURSOR(); cursor.insertText("Blah"); QTextFrameFormat fmt = doc.rootFrame()->frameFormat(); fmt.setBackground(QColor(255, 0, 0, 0)); doc.rootFrame()->setFrameFormat(fmt); QString expectedHtml = htmlHead; expectedHtml.insert(htmlHead.size() - 2, " bgcolor=\"transparent\""); expectedHtml += "

Blah

" + htmlTail; writeActualAndExpected(QTest::currentDataTag(), doc.toHtml(), expectedHtml); QCOMPARE(doc.toHtml(), expectedHtml); } void tst_QTextDocument::toHtmlRootFrameProperties() { CREATE_DOC_AND_CURSOR(); QTextFrameFormat fmt = doc.rootFrame()->frameFormat(); fmt.setTopMargin(10); fmt.setLeftMargin(10); fmt.setBorder(2); doc.rootFrame()->setFrameFormat(fmt); cursor.insertText("Blah"); QString expectedOutput("\n" "\n
\n" "

Blah

"); expectedOutput.prepend(htmlHead); expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\""); expectedOutput.append(htmlTail); writeActualAndExpected(QTest::currentTestFunction(), doc.toHtml(), expectedOutput); QCOMPARE(doc.toHtml(), expectedOutput); } void tst_QTextDocument::toHtmlLineHeightProperties() { CREATE_DOC_AND_CURSOR(); QTextBlock block = doc.firstBlock(); QTextBlockFormat blockFormat = block.blockFormat(); blockFormat.setLineHeight(200, QTextBlockFormat::ProportionalHeight); cursor.setBlockFormat(blockFormat); cursor.insertText("Blah"); QString expectedOutput("

Blah

"); expectedOutput.prepend(htmlHead); expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"); expectedOutput.append(htmlTail); QCOMPARE(doc.toHtml(), expectedOutput); } void tst_QTextDocument::toHtmlDefaultFontSpacingProperties() { CREATE_DOC_AND_CURSOR(); cursor.insertText("Blah"); QFont fnt = doc.defaultFont(); fnt.setLetterSpacing(QFont::AbsoluteSpacing, 13); fnt.setWordSpacing(15); doc.setDefaultFont(fnt); QString expectedOutput = htmlHead; expectedOutput.insert(htmlHead.size() - 3, " letter-spacing:13px; word-spacing:15px;"); expectedOutput += "

Blah

" + htmlTail; writeActualAndExpected(QTest::currentTestFunction(), doc.toHtml(), expectedOutput); QCOMPARE(doc.toHtml(), expectedOutput); } void tst_QTextDocument::toHtmlTextDecorationUnderline() { CREATE_DOC_AND_CURSOR(); cursor.insertText("Some text"); QFont fnt = doc.defaultFont(); fnt.setUnderline(true); doc.setDefaultFont(fnt); QString expectedOutput = htmlHead; expectedOutput.insert(htmlHead.size() - 3, " text-decoration: underline;"); expectedOutput += "

Some text

" + htmlTail; writeActualAndExpected("toHtmlTextDecorationUnderline1", doc.toHtml(), expectedOutput); QCOMPARE(doc.toHtml(), expectedOutput); QTextCharFormat format; format.setFontUnderline(false); cursor.select(QTextCursor::Document); cursor.mergeCharFormat(format); expectedOutput = htmlHead; expectedOutput.insert(htmlHead.size() - 3, " text-decoration: underline;"); expectedOutput += "

" "Some text

" + htmlTail; writeActualAndExpected("toHtmlTextDecorationUnderline2", doc.toHtml(), expectedOutput); QCOMPARE(doc.toHtml(), expectedOutput); } void tst_QTextDocument::capitalizationHtmlInExport() { doc->setPlainText("Test"); QRegularExpression re(".*span style=\"(.*)\">Test.*"); QCOMPARE(re.captureCount(), 1); QVERIFY(!re.match(doc->toHtml()).hasMatch()); // no span QTextCursor cursor(doc); cursor.setPosition(4, QTextCursor::KeepAnchor); QTextCharFormat cf; cf.setFontCapitalization(QFont::SmallCaps); cursor.mergeCharFormat(cf); const QString smallcaps = doc->toHtml(); auto match = re.match(doc->toHtml()); QVERIFY(match.hasMatch()); QCOMPARE(match.captured(1).trimmed(), QString("font-variant:small-caps;")); cf.setFontCapitalization(QFont::AllUppercase); cursor.mergeCharFormat(cf); const QString uppercase = doc->toHtml(); match = re.match(doc->toHtml()); QVERIFY(match.hasMatch()); QCOMPARE(match.captured(1).trimmed(), QString("text-transform:uppercase;")); cf.setFontCapitalization(QFont::AllLowercase); cursor.mergeCharFormat(cf); const QString lowercase = doc->toHtml(); match = re.match(doc->toHtml()); QVERIFY(match.hasMatch()); QCOMPARE(match.captured(1).trimmed(), QString("text-transform:lowercase;")); doc->setHtml(smallcaps); cursor.setPosition(1); QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::SmallCaps); doc->setHtml(uppercase); QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllUppercase); doc->setHtml(lowercase); QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllLowercase); } void tst_QTextDocument::wordspacingHtmlExport() { doc->setPlainText("Test"); QRegularExpression re(".*span style=\"(.*)\">Test.*"); QCOMPARE(re.captureCount(), 1); QVERIFY(!re.match(doc->toHtml()).hasMatch()); // no span QTextCursor cursor(doc); cursor.setPosition(4, QTextCursor::KeepAnchor); QTextCharFormat cf; cf.setFontWordSpacing(4); cursor.mergeCharFormat(cf); auto match = re.match(doc->toHtml()); QVERIFY(match.hasMatch()); QCOMPARE(match.captured(1).trimmed(), QString("word-spacing:4px;")); cf.setFontWordSpacing(-8.5); cursor.mergeCharFormat(cf); match = re.match(doc->toHtml()); QVERIFY(match.hasMatch()); QCOMPARE(match.captured(1).trimmed(), QString("word-spacing:-8.5px;")); } class CursorPosSignalSpy : public QObject { Q_OBJECT public: CursorPosSignalSpy(QTextDocument *doc) { calls = 0; connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), this, SLOT(cursorPositionChanged(QTextCursor))); } int calls; private slots: void cursorPositionChanged(const QTextCursor &) { ++calls; } }; void tst_QTextDocument::cursorPositionChanged() { CursorPosSignalSpy spy(doc); cursor.insertText("Test"); QCOMPARE(spy.calls, 1); spy.calls = 0; QTextCursor unrelatedCursor(doc); unrelatedCursor.insertText("Blah"); QCOMPARE(spy.calls, 2); spy.calls = 0; cursor.insertText("Blah"); QCOMPARE(spy.calls, 1); spy.calls = 0; cursor.movePosition(QTextCursor::PreviousCharacter); QCOMPARE(spy.calls, 0); } void tst_QTextDocument::cursorPositionChangedOnSetText() { CursorPosSignalSpy spy(doc); // doc has one QTextCursor stored in the // cursor member variable, thus the signal // gets emitted once. doc->setPlainText("Foo\nBar\nBaz\nBlub\nBlah"); QCOMPARE(spy.calls, 1); spy.calls = 0; doc->setHtml("

Foo

Bar

Baz

Blah"); QCOMPARE(spy.calls, 1); } void tst_QTextDocument::textFrameIterator() { cursor.insertTable(1, 1); int blockCount = 0; int frameCount = 0; for (QTextFrame::Iterator frameIt = doc->rootFrame()->begin(); !frameIt.atEnd(); ++frameIt) { if (frameIt.currentFrame()) ++frameCount; else if (frameIt.currentBlock().isValid()) ++blockCount; } QEXPECT_FAIL("", "This is currently worked around in the html export but needs fixing!", Continue); QCOMPARE(blockCount, 0); QCOMPARE(frameCount, 1); } class TestSyntaxHighlighter : public QObject { Q_OBJECT public: inline TestSyntaxHighlighter(QTextDocument *doc) : QObject(doc), ok(false) {} bool ok; private slots: inline void markBlockDirty(int from, int charsRemoved, int charsAdded) { Q_UNUSED(charsRemoved); Q_UNUSED(charsAdded); QTextDocument *doc = static_cast(parent()); QTextBlock block = doc->findBlock(from); QTestDocumentLayout *lout = qobject_cast(doc->documentLayout()); lout->called = false; doc->markContentsDirty(block.position(), block.length()); ok = (lout->called == false); } inline void modifyBlockAgain(int from, int charsRemoved, int charsAdded) { Q_UNUSED(charsRemoved); Q_UNUSED(charsAdded); QTextDocument *doc = static_cast(parent()); QTextBlock block = doc->findBlock(from); QTextCursor cursor(block); QTestDocumentLayout *lout = qobject_cast(doc->documentLayout()); lout->called = false; cursor.insertText("Foo"); ok = (lout->called == true); } }; void tst_QTextDocument::markContentsDirty() { QTestDocumentLayout *lout = new QTestDocumentLayout(doc); doc->setDocumentLayout(lout); TestSyntaxHighlighter *highlighter = new TestSyntaxHighlighter(doc); connect(doc, SIGNAL(contentsChange(int,int,int)), highlighter, SLOT(markBlockDirty(int,int,int))); highlighter->ok = false; cursor.insertText("Some dummy text blah blah"); QVERIFY(highlighter->ok); disconnect(doc, SIGNAL(contentsChange(int,int,int)), highlighter, SLOT(markBlockDirty(int,int,int))); connect(doc, SIGNAL(contentsChange(int,int,int)), highlighter, SLOT(modifyBlockAgain(int,int,int))); highlighter->ok = false; cursor.insertText("FooBar"); QVERIFY(highlighter->ok); lout->called = false; doc->markContentsDirty(1, 4); QVERIFY(lout->called); } void tst_QTextDocument::clonePreservesMetaInformation() { const QString title("Foobar"); const QString url("about:blank"); const QString media("print"); doc->setHtml("" + title + "Hrm"); doc->setMetaInformation(QTextDocument::DocumentUrl, url); doc->setMetaInformation(QTextDocument::CssMedia, media); QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), title); QCOMPARE(doc->metaInformation(QTextDocument::DocumentUrl), url); QCOMPARE(doc->metaInformation(QTextDocument::CssMedia), media); QTextDocument *clone = doc->clone(); QCOMPARE(clone->metaInformation(QTextDocument::DocumentTitle), title); QCOMPARE(clone->metaInformation(QTextDocument::DocumentUrl), url); QCOMPARE(clone->metaInformation(QTextDocument::CssMedia), media); delete clone; } void tst_QTextDocument::clonePreservesPageSize() { QSizeF sz(100., 100.); doc->setPageSize(sz); QTextDocument *clone = doc->clone(); QCOMPARE(clone->pageSize(), sz); delete clone; } void tst_QTextDocument::clonePreservesPageBreakPolicies() { QTextTableFormat tableFmt; tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter); QTextBlockFormat blockFmt; blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore); QTextCursor cursor(doc); cursor.setBlockFormat(blockFmt); cursor.insertText("foo"); cursor.insertTable(2, 2, tableFmt); QTextDocument *clone = doc->clone(); QCOMPARE(clone->begin().blockFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysBefore); QVERIFY(!clone->rootFrame()->childFrames().isEmpty()); QCOMPARE(clone->rootFrame()->childFrames().first()->frameFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysAfter); delete clone; } void tst_QTextDocument::clonePreservesDefaultFont() { QFont f = doc->defaultFont(); QVERIFY(f.pointSize() != 100); f.setPointSize(100); doc->setDefaultFont(f); QTextDocument *clone = doc->clone(); QCOMPARE(clone->defaultFont(), f); delete clone; } void tst_QTextDocument::clonePreservesResources() { QUrl testUrl(":/foobar"); QVariant testResource("hello world"); doc->addResource(QTextDocument::ImageResource, testUrl, testResource); QTextDocument *clone = doc->clone(); QVERIFY(clone->resource(QTextDocument::ImageResource, testUrl) == testResource); delete clone; } void tst_QTextDocument::clonePreservesUserStates() { QTextCursor cursor(doc); cursor.insertText("bla bla bla"); cursor.block().setUserState(1); cursor.insertBlock(); cursor.insertText("foo bar"); cursor.block().setUserState(2); cursor.insertBlock(); cursor.insertText("no user state"); QTextDocument *clone = doc->clone(); QTextBlock b1 = doc->begin(), b2 = clone->begin(); while (b1 != doc->end()) { b1 = b1.next(); b2 = b2.next(); QCOMPARE(b1.userState(), b2.userState()); } QCOMPARE(b2, clone->end()); delete clone; } void tst_QTextDocument::clonePreservesRootFrameFormat() { doc->setPlainText("Hello"); QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); fmt.setMargin(200); doc->rootFrame()->setFrameFormat(fmt); QCOMPARE(doc->rootFrame()->frameFormat().margin(), qreal(200)); QTextDocument *copy = doc->clone(); QCOMPARE(copy->rootFrame()->frameFormat().margin(), qreal(200)); delete copy; } void tst_QTextDocument::clonePreservesIndentWidth() { doc->setIndentWidth(42); QTextDocument *clone = doc->clone(); QCOMPARE(clone->indentWidth(), qreal(42)); delete clone; } void tst_QTextDocument::clonePreservesFormatsWhenEmpty() { QTextDocument document; QTextCursor cursor(&document); // Change a few char format attributes QTextCharFormat charFormat; charFormat.setFontPointSize(charFormat.fontPointSize() + 1); charFormat.setFontWeight(charFormat.fontWeight() + 1); cursor.setBlockCharFormat(charFormat); // Change a few block format attributes QTextBlockFormat blockFormat; blockFormat.setAlignment(Qt::AlignRight); // The default is Qt::AlignLeft blockFormat.setIndent(blockFormat.indent() + 1); cursor.setBlockFormat(blockFormat); auto clone = document.clone(); QTextCursor cloneCursor(clone); QCOMPARE(cloneCursor.blockCharFormat().fontPointSize(), charFormat.fontPointSize()); QCOMPARE(cloneCursor.blockCharFormat().fontWeight(), charFormat.fontWeight()); QCOMPARE(cloneCursor.blockFormat().alignment(), blockFormat.alignment()); QCOMPARE(cloneCursor.blockFormat().indent(), blockFormat.indent()); delete clone; } void tst_QTextDocument::blockCount() { QCOMPARE(doc->blockCount(), 1); cursor.insertBlock(); QCOMPARE(doc->blockCount(), 2); cursor.insertBlock(); QCOMPARE(doc->blockCount(), 3); cursor.insertText("blah blah"); QCOMPARE(doc->blockCount(), 3); doc->undo(); doc->undo(); QCOMPARE(doc->blockCount(), 2); doc->undo(); QCOMPARE(doc->blockCount(), 1); } void tst_QTextDocument::resolvedFontInEmptyFormat() { QFont font; font.setPointSize(42); doc->setDefaultFont(font); QTextCharFormat fmt = doc->begin().charFormat(); QVERIFY(fmt.properties().isEmpty()); QCOMPARE(fmt.font(), font); } void tst_QTextDocument::defaultRootFrameMargin() { QCOMPARE(doc->rootFrame()->frameFormat().margin(), 4.0); } class TestDocument : public QTextDocument { public: inline TestDocument(const QUrl &testUrl, const QString &testString) : url(testUrl), string(testString), resourceLoaded(false) {} bool hasResourceCached(); protected: virtual QVariant loadResource(int type, const QUrl &name) override; private: QUrl url; QString string; bool resourceLoaded; }; bool TestDocument::hasResourceCached() { resourceLoaded = false; resource(QTextDocument::ImageResource, url); return !resourceLoaded; } QVariant TestDocument::loadResource(int type, const QUrl &name) { if (type == QTextDocument::ImageResource && name == url) { resourceLoaded = true; return string; } return QTextDocument::loadResource(type, name); } void tst_QTextDocument::clearResources() { // regular resource for QTextDocument QUrl testUrl(":/foobar"); QVariant testResource("hello world"); // implicitly cached resource, initially loaded through TestDocument::loadResource() QUrl cacheUrl(":/blub"); QString cacheResource("mah"); TestDocument doc(cacheUrl, cacheResource); doc.addResource(QTextDocument::ImageResource, testUrl, testResource); QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource); doc.setPlainText("Hah"); QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource); doc.setHtml("Mooo"); QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource); QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource); doc.clear(); QVERIFY(!doc.resource(QTextDocument::ImageResource, testUrl).isValid()); QVERIFY(!doc.hasResourceCached()); doc.clear(); doc.setHtml("Mooo"); QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource); doc.setPlainText("Foob"); QVERIFY(!doc.hasResourceCached()); } void tst_QTextDocument::setPlainText() { doc->setPlainText("Hello World"); QString s(""); doc->setPlainText(s); QCOMPARE(doc->toPlainText(), s); } void tst_QTextDocument::toPlainText_data() { QTest::addColumn("html"); QTest::addColumn("expectedPlainText"); QTest::newRow("nbsp") << "Hello World" << "Hello World"; QTest::newRow("empty_div") << "

hello" << "hello"; QTest::newRow("br_and_p") << "

first

second

" << "first\n\nsecond\n"; QTest::newRow("div") << "first
second
third
fourth" << "first\nsecond\nthird\nfourth"; //
and
become newlines... QTest::newRow("br_text_end_of_div") << "
first
moretext
second
" << "first\nmoretext\nsecond\n"; // ... when there is text before
QTest::newRow("br_end_of_div_like_gmail") << "
first
second
third
" << "first\nsecond\nthird\n"; // ... and when there is text before
QTest::newRow("p_and_div") << "
first

second

third
" << "first\nsecond\nthird"; } void tst_QTextDocument::toPlainText() { QFETCH(QString, html); QFETCH(QString, expectedPlainText); doc->setHtml(html); QCOMPARE(doc->toPlainText(), expectedPlainText); } void tst_QTextDocument::toRawText() { doc->setHtml(" "); QString rawText = doc->toRawText(); QCOMPARE(rawText.size(), 1); QCOMPARE(rawText.at(0).unicode(), ushort(QChar::Nbsp)); } void tst_QTextDocument::deleteTextObjectsOnClear() { QPointer table = cursor.insertTable(2, 2); QVERIFY(!table.isNull()); doc->clear(); QVERIFY(table.isNull()); } void tst_QTextDocument::defaultStyleSheet() { const QColor green("green"); const QString sheet("p { background-color: green; }"); QVERIFY(doc->defaultStyleSheet().isEmpty()); doc->setDefaultStyleSheet(sheet); QCOMPARE(doc->defaultStyleSheet(), sheet); cursor.insertHtml("

test"); QTextBlockFormat fmt = doc->begin().blockFormat(); QCOMPARE(fmt.background().color(), green); doc->clear(); cursor.insertHtml("

test"); fmt = doc->begin().blockFormat(); QCOMPARE(fmt.background().color(), green); QTextDocument *clone = doc->clone(); QCOMPARE(clone->defaultStyleSheet(), sheet); cursor = QTextCursor(clone); cursor.insertHtml("

test"); fmt = clone->begin().blockFormat(); QCOMPARE(fmt.background().color(), green); delete clone; cursor = QTextCursor(doc); cursor.insertHtml("

test"); fmt = doc->begin().blockFormat(); QCOMPARE(fmt.background().color(), green); doc->clear(); cursor.insertHtml("

test"); fmt = doc->begin().blockFormat(); QCOMPARE(fmt.background().color(), QColor(Qt::red)); doc->clear(); doc->setDefaultStyleSheet("invalid style sheet...."); cursor.insertHtml("

test"); fmt = doc->begin().blockFormat(); QVERIFY(fmt.background().color() != QColor("green")); } void tst_QTextDocument::defaultTableStyle_data() { QTest::addColumn("css"); QTest::addColumn("html"); QTest::addColumn>("borderBrushes"); const QString htmlHeader(R"( 1 2 )"); const QString htmlCells(R"( A B )"); const QString cssEachSide = R"({ border-top-color: red; border-bottom-color: blue; border-left-color: green; border-right-color: yellow; })"; const QString cssOneColor = R"({ border-color: red; })"; const QString cssFourColors = R"({ border-color: red blue green yellow; })"; QTest::addRow("td, each side") << QString("td ") + cssEachSide << htmlCells << QList{Qt::red, Qt::blue, QColor("green"), Qt::yellow}; QTest::addRow("th, each side") << QString("th ") + cssEachSide << htmlHeader << QList{Qt::red, Qt::blue, QColor("green"), Qt::yellow}; QTest::addRow("th+td, each side") << QString("th %1 td %1").arg(cssEachSide) << htmlHeader + htmlCells << QList{Qt::red, Qt::blue, QColor("green"), Qt::yellow}; QTest::addRow("td, one color") << QString("td ") + cssOneColor << htmlCells << QList{Qt::red, Qt::red, Qt::red, Qt::red}; QTest::addRow("th, one color") << QString("th ") + cssOneColor << htmlHeader << QList{Qt::red, Qt::red, Qt::red, Qt::red}; QTest::addRow("th+td, one color") << QString("th %1 td %1").arg(cssOneColor) << htmlHeader + htmlCells << QList{Qt::red, Qt::red, Qt::red, Qt::red}; // css order is top, right, bottom, left QTest::addRow("td, four colors") << QString("td ") + cssFourColors << htmlCells << QList{Qt::red, QColor("green"), Qt::yellow, Qt::blue}; } void tst_QTextDocument::defaultTableStyle() { QFETCH(QString, css); QFETCH(QString, html); QFETCH(QList, borderBrushes); CREATE_DOC_AND_CURSOR(); doc.setDefaultStyleSheet(css); doc.setHtml(QString("%1
").arg(html)); const QTextFrame *frame = doc.rootFrame(); const QTextTable *table = nullptr; for (auto it = frame->begin(); !table && !it.atEnd(); ++it) table = qobject_cast(it.currentFrame()); QVERIFY(table); const QList brushProperties = { QTextFormat::TableCellTopBorderBrush, QTextFormat::TableCellBottomBorderBrush, QTextFormat::TableCellLeftBorderBrush, QTextFormat::TableCellRightBorderBrush }; for (int row = 0; row < table->rows(); ++row) { for (int column = 0; column < table->columns(); ++column) { auto cellDetails = qScopeGuard([&]{ qWarning("Failure was in cell %d/%d", row, column); }); const QTextTableCell cell = table->cellAt(row, column); const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); QList brushes; for (const auto side : brushProperties) { QVariant sideProperty = cellFormat.property(side); QVERIFY(sideProperty.isValid()); QVERIFY(sideProperty.typeId() == qMetaTypeId()); brushes << sideProperty.value(); } auto errorDetails = qScopeGuard([&]{ if (brushes.size() != borderBrushes.size()) { qWarning("Different count: %lld vs %lld", brushes.size(), borderBrushes.size()); return; } for (int i = 0; i < brushes.size(); ++i) { QString side; QDebug(&side) << QTextFormat::Property(QTextFormat::TableCellTopBorderBrush + i); QString actual; QDebug(&actual) << brushes.at(i); QString expected; QDebug(&expected) << borderBrushes.at(i); if (expected != actual) { qWarning("\n %s:\n\tActual: %s\n\tExpected:%s", qPrintable(side), qPrintable(actual), qPrintable(expected)); } } }); QVERIFY2(borderBrushes == brushes, // QCOMPARE doesn't generate helpful output anyway qPrintable(QString("for cell %1/%2").arg(row).arg(column))); errorDetails.dismiss(); cellDetails.dismiss(); } } } void tst_QTextDocument::maximumBlockCount() { QCOMPARE(doc->maximumBlockCount(), 0); QVERIFY(doc->isUndoRedoEnabled()); cursor.insertBlock(); cursor.insertText("Blah"); cursor.insertBlock(); cursor.insertText("Foo"); QCOMPARE(doc->blockCount(), 3); QCOMPARE(doc->toPlainText(), QString("\nBlah\nFoo")); doc->setMaximumBlockCount(1); QVERIFY(!doc->isUndoRedoEnabled()); QCOMPARE(doc->blockCount(), 1); QCOMPARE(doc->toPlainText(), QString("Foo")); cursor.insertBlock(); cursor.insertText("Hello"); doc->setMaximumBlockCount(1); QCOMPARE(doc->blockCount(), 1); QCOMPARE(doc->toPlainText(), QString("Hello")); doc->setMaximumBlockCount(100); for (int i = 0; i < 1000; ++i) { cursor.insertBlock(); cursor.insertText("Blah)"); QVERIFY(doc->blockCount() <= 100); } cursor.movePosition(QTextCursor::End); QCOMPARE(cursor.blockNumber(), 99); QTextCharFormat fmt; fmt.setFontItalic(true); cursor.setBlockCharFormat(fmt); cursor.movePosition(QTextCursor::Start); QVERIFY(!cursor.blockCharFormat().fontItalic()); doc->setMaximumBlockCount(1); QVERIFY(cursor.blockCharFormat().fontItalic()); cursor.insertTable(2, 2); QCOMPARE(doc->blockCount(), 6); cursor.insertBlock(); QCOMPARE(doc->blockCount(), 1); } void tst_QTextDocument::adjustSize() { // avoid ugly tooltips like in task 125583 QString text("Test Text"); doc->setPlainText(text); doc->rootFrame()->setFrameFormat(QTextFrameFormat()); doc->adjustSize(); QCOMPARE(doc->size().width(), doc->idealWidth()); } void tst_QTextDocument::initialUserData() { doc->setPlainText("Hello"); QTextBlock block = doc->begin(); block.setUserData(new QTextBlockUserData); QVERIFY(block.userData()); doc->documentLayout(); QVERIFY(block.userData()); doc->setDocumentLayout(new QTestDocumentLayout(doc)); QVERIFY(!block.userData()); } void tst_QTextDocument::html_defaultFont() { QFont f; f.setItalic(true); f.setWeight(QFont::Bold); doc->setDefaultFont(f); doc->setPlainText("Test"); QString bodyPart = QString::fromLatin1("") .arg(f.family()) .arg(cssFontSizeString(f)) .arg(f.weight()); QString html = doc->toHtml(); if (!html.contains(bodyPart)) { qDebug() << "html:" << html; qDebug() << "expected body:" << bodyPart; QVERIFY(html.contains(bodyPart)); } if (html.contains("span")) qDebug() << "html:" << html; QVERIFY(!html.contains("setPlainText("Foo"); QCOMPARE(doc->blockCount(), 1); QCOMPARE(spy.size(), 0); spy.clear(); doc->setPlainText("Foo\nBar"); QCOMPARE(doc->blockCount(), 2); QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).value(0).toInt(), 2); spy.clear(); cursor.movePosition(QTextCursor::End); cursor.insertText("Blahblah"); QCOMPARE(spy.size(), 0); cursor.insertBlock(); QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).value(0).toInt(), 3); spy.clear(); doc->undo(); QCOMPARE(spy.size(), 1); QCOMPARE(spy.at(0).value(0).toInt(), 2); } void tst_QTextDocument::nonZeroDocumentLengthOnClear() { QTestDocumentLayout *lout = new QTestDocumentLayout(doc); doc->setDocumentLayout(lout); doc->clear(); QVERIFY(lout->called); QVERIFY(!lout->lastDocumentLengths.contains(0)); } void tst_QTextDocument::setTextPreservesUndoRedoEnabled() { QVERIFY(doc->isUndoRedoEnabled()); doc->setPlainText("Test"); QVERIFY(doc->isUndoRedoEnabled()); doc->setUndoRedoEnabled(false); QVERIFY(!doc->isUndoRedoEnabled()); doc->setPlainText("Test2"); QVERIFY(!doc->isUndoRedoEnabled()); doc->setHtml("

hello"); QVERIFY(!doc->isUndoRedoEnabled()); } void tst_QTextDocument::firstLast() { QCOMPARE(doc->blockCount(), 1); QCOMPARE(doc->firstBlock(), doc->lastBlock()); doc->setPlainText("Hello\nTest\nWorld"); QCOMPARE(doc->blockCount(), 3); QVERIFY(doc->firstBlock() != doc->lastBlock()); QCOMPARE(doc->firstBlock().text(), QString("Hello")); QCOMPARE(doc->lastBlock().text(), QString("World")); // manual forward loop QTextBlock block = doc->firstBlock(); QVERIFY(block.isValid()); QCOMPARE(block.text(), QString("Hello")); block = block.next(); QVERIFY(block.isValid()); QCOMPARE(block.text(), QString("Test")); block = block.next(); QVERIFY(block.isValid()); QCOMPARE(block.text(), QString("World")); block = block.next(); QVERIFY(!block.isValid()); // manual backward loop block = doc->lastBlock(); QVERIFY(block.isValid()); QCOMPARE(block.text(), QString("World")); block = block.previous(); QVERIFY(block.isValid()); QCOMPARE(block.text(), QString("Test")); block = block.previous(); QVERIFY(block.isValid()); QCOMPARE(block.text(), QString("Hello")); block = block.previous(); QVERIFY(!block.isValid()); } const QString backgroundImage_html("
Blah
"); void tst_QTextDocument::backgroundImage_checkExpectedHtml(const QTextDocument &doc) { QString expectedHtml = htmlHead + "" "\n\n
" "\n

Blah

" "
" + htmlTail; writeActualAndExpected(QTest::currentTestFunction(), doc.toHtml(), expectedHtml); QCOMPARE(doc.toHtml(), expectedHtml); } void tst_QTextDocument::buildRegExpData() { QTest::addColumn("haystack"); QTest::addColumn("needle"); QTest::addColumn("flags"); QTest::addColumn("from"); QTest::addColumn("anchor"); QTest::addColumn("position"); // match integers 0 to 99 QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2; // match ampersands but not & QTest::newRow("2") << "His & hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16; //backward search QTest::newRow("3") << QString::fromLatin1("HelloBlahWorld Blah Hah") << "h" << int(QTextDocument::FindBackward) << 18 << 8 << 9; } void tst_QTextDocument::backgroundImage_toHtml() { CREATE_DOC_AND_CURSOR(); doc.setHtml(backgroundImage_html); backgroundImage_checkExpectedHtml(doc); } void tst_QTextDocument::backgroundImage_toHtml2() { CREATE_DOC_AND_CURSOR(); cursor.insertHtml(backgroundImage_html); backgroundImage_checkExpectedHtml(doc); } void tst_QTextDocument::backgroundImage_clone() { CREATE_DOC_AND_CURSOR(); doc.setHtml(backgroundImage_html); QTextDocument *clone = doc.clone(); backgroundImage_checkExpectedHtml(*clone); delete clone; } void tst_QTextDocument::backgroundImage_copy() { CREATE_DOC_AND_CURSOR(); doc.setHtml(backgroundImage_html); QTextDocumentFragment fragment(&doc); { CREATE_DOC_AND_CURSOR(); cursor.insertFragment(fragment); backgroundImage_checkExpectedHtml(doc); } } void tst_QTextDocument::documentCleanup() { QTextDocument doc; QTextCursor cursor(&doc); cursor.insertText("d\nfoo\nbar\n"); doc.documentLayout(); // forces relayout // remove char 1 cursor.setPosition(0); QSizeF size = doc.documentLayout()->documentSize(); cursor.deleteChar(); // the size should be unchanged. QCOMPARE(doc.documentLayout()->documentSize(), size); } void tst_QTextDocument::characterAt() { QTextDocument doc; QTextCursor cursor(&doc); QString text("12345\n67890"); cursor.insertText(text); int length = doc.characterCount(); QCOMPARE(length, text.size() + 1); QCOMPARE(doc.characterAt(length-1), QChar(QChar::ParagraphSeparator)); QCOMPARE(doc.characterAt(-1), QChar()); QCOMPARE(doc.characterAt(length), QChar()); QCOMPARE(doc.characterAt(length + 1), QChar()); for (int i = 0; i < text.size(); ++i) { QChar c = text.at(i); if (c == QLatin1Char('\n')) c = QChar(QChar::ParagraphSeparator); QCOMPARE(doc.characterAt(i), c); } } void tst_QTextDocument::revisions() { QTextDocument doc; QTextCursor cursor(&doc); QString text("Hello World"); QCOMPARE(doc.firstBlock().revision(), 0); cursor.insertText(text); QCOMPARE(doc.firstBlock().revision(), 1); cursor.setPosition(6); cursor.insertBlock(); QCOMPARE(cursor.block().previous().revision(), 2); QCOMPARE(cursor.block().revision(), 2); cursor.insertText("candle"); QCOMPARE(cursor.block().revision(), 3); cursor.movePosition(QTextCursor::EndOfBlock); cursor.insertBlock(); // we are at the block end QCOMPARE(cursor.block().previous().revision(), 3); QCOMPARE(cursor.block().revision(), 4); cursor.insertText("lightbulb"); QCOMPARE(cursor.block().revision(), 5); cursor.movePosition(QTextCursor::StartOfBlock); cursor.insertBlock(); // we are the block start QCOMPARE(cursor.block().previous().revision(), 6); QCOMPARE(cursor.block().revision(), 5); } void tst_QTextDocument::revisionWithUndoCompressionAndUndo() { QTextDocument doc; QTextCursor cursor(&doc); cursor.insertText("This is the beginning of it all."); QCOMPARE(doc.firstBlock().revision(), 1); QCOMPARE(doc.revision(), 1); cursor.insertBlock(); QCOMPARE(doc.revision(), 2); cursor.insertText("this"); QCOMPARE(doc.revision(), 3); cursor.insertText("is"); QCOMPARE(doc.revision(), 4); cursor.insertText("compressed"); QCOMPARE(doc.revision(), 5); doc.undo(); QCOMPARE(doc.revision(), 6); QCOMPARE(doc.toPlainText(), QString("This is the beginning of it all.\n")) ; cursor.setPosition(0); QCOMPARE(doc.firstBlock().revision(), 1); cursor.insertText("Very beginnig"); QCOMPARE(doc.firstBlock().revision(), 7); doc.undo(); QCOMPARE(doc.revision(), 8); QCOMPARE(doc.firstBlock().revision(), 1); cursor.beginEditBlock(); cursor.insertText("Hello"); cursor.insertBlock(); cursor.insertText("world"); cursor.endEditBlock(); QCOMPARE(doc.revision(), 9); doc.undo(); QCOMPARE(doc.revision(), 10); } void tst_QTextDocument::testUndoCommandAdded() { QVERIFY(doc); QSignalSpy spy(doc, SIGNAL(undoCommandAdded())); QVERIFY(spy.isValid()); QVERIFY(spy.isEmpty()); cursor.insertText("a"); QCOMPARE(spy.size(), 1); cursor.insertText("b"); // should be merged QCOMPARE(spy.size(), 1); cursor.insertText("c"); // should be merged QCOMPARE(spy.size(), 1); QCOMPARE(doc->toPlainText(), QString("abc")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("")); doc->clear(); spy.clear(); cursor.insertText("aaa"); QCOMPARE(spy.size(), 1); spy.clear(); cursor.insertText("aaaa\nbcd"); QCOMPARE(spy.size(), 1); spy.clear(); cursor.beginEditBlock(); cursor.insertText("aa"); cursor.insertText("bbb\n"); cursor.setCharFormat(QTextCharFormat()); cursor.insertText("\nccc"); QVERIFY(spy.isEmpty()); cursor.endEditBlock(); QCOMPARE(spy.size(), 1); spy.clear(); cursor.insertBlock(); QCOMPARE(spy.size(), 1); spy.clear(); cursor.setPosition(5); QVERIFY(spy.isEmpty()); cursor.setCharFormat(QTextCharFormat()); QVERIFY(spy.isEmpty()); cursor.setPosition(10, QTextCursor::KeepAnchor); QVERIFY(spy.isEmpty()); QTextCharFormat cf; cf.setFontItalic(true); cursor.mergeCharFormat(cf); QCOMPARE(spy.size(), 1); spy.clear(); doc->undo(); QCOMPARE(spy.size(), 0); doc->undo(); QCOMPARE(spy.size(), 0); spy.clear(); doc->redo(); QCOMPARE(spy.size(), 0); doc->redo(); QCOMPARE(spy.size(), 0); } void tst_QTextDocument::testUndoBlocks() { QVERIFY(doc); cursor.insertText("Hello World"); cursor.insertText("period"); doc->undo(); QCOMPARE(doc->toPlainText(), QString("")); cursor.insertText("Hello World"); cursor.insertText("One\nTwo\nThree"); QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("Hello World")); cursor.insertText("One\nTwo\nThree"); cursor.insertText("Trailing text"); doc->undo(); QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("Hello World")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("")); cursor.insertText("town"); cursor.beginEditBlock(); // Edit block 1 - Deletion/Insertion cursor.setPosition(0, QTextCursor::KeepAnchor); cursor.insertText("r"); cursor.endEditBlock(); cursor.insertText("est"); // Merged into edit block 1 QCOMPARE(doc->toPlainText(), QString("rest")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("town")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("")); // This case would not happen in practice. If the user typed out this text, it would all be part of one // edit block. This would cause the undo to clear all text. But for the purpose of testing the beginEditBlock // and endEditBlock calls with respect to qtextdocument this is tested. cursor.insertText("quod"); cursor.beginEditBlock(); // Edit block 1 - Insertion cursor.insertText(" erat"); cursor.endEditBlock(); cursor.insertText(" demonstrandum"); // Merged into edit block 1 QCOMPARE(doc->toPlainText(), QString("quod erat demonstrandum")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("quod")); doc->undo(); QCOMPARE(doc->toPlainText(), QString("")); } class Receiver : public QObject { Q_OBJECT public: QString first; public slots: void cursorPositionChanged() { if (first.isEmpty()) first = QLatin1String("cursorPositionChanged"); } void contentsChange() { if (first.isEmpty()) first = QLatin1String("contentsChanged"); } }; void tst_QTextDocument::receiveCursorPositionChangedAfterContentsChange() { QVERIFY(doc); doc->setDocumentLayout(new MyAbstractTextDocumentLayout(doc)); Receiver rec; connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), &rec, SLOT(cursorPositionChanged())); connect(doc, SIGNAL(contentsChange(int,int,int)), &rec, SLOT(contentsChange())); cursor.insertText("Hello World"); QCOMPARE(rec.first, QString("contentsChanged")); } void tst_QTextDocument::QTBUG25778_pixelSizeFromHtml() { QTextDocument document1; QTextDocument document2; document1.setHtml("Foobar"); document2.setHtml(document1.toHtml()); QTextCursor cursor(&document2); QCOMPARE(cursor.charFormat().font().pixelSize(), 24); } void tst_QTextDocument::copiedFontSize() { QTextDocument documentInput; QTextDocument documentOutput; QFont fontInput; fontInput.setPixelSize(24); QTextCursor cursorInput(&documentInput); QTextCharFormat formatInput = cursorInput.charFormat(); formatInput.setFont(fontInput); cursorInput.insertText("Should be the same font", formatInput); cursorInput.select(QTextCursor::Document); QTextDocumentFragment fragmentInput(cursorInput); QString html = fragmentInput.toHtml(); QTextCursor cursorOutput(&documentOutput); QTextDocumentFragment fragmentOutput = QTextDocumentFragment::fromHtml(html); cursorOutput.insertFragment(fragmentOutput); QCOMPARE(cursorOutput.charFormat().font().pixelSize(), 24); } void tst_QTextDocument::htmlExportImportBlockCount() { QTextDocument document; { QTextCursor cursor(&document); cursor.insertText("Foo"); cursor.insertBlock(); cursor.insertBlock(); cursor.insertBlock(); cursor.insertBlock(); cursor.insertText("Bar"); } QCOMPARE(document.blockCount(), 5); QString html = document.toHtml(); document.clear(); document.setHtml(html); QCOMPARE(document.blockCount(), 5); } void tst_QTextDocument::QTBUG27354_spaceAndSoftSpace() { QTextDocument document; { QTextCursor cursor(&document); QTextBlockFormat blockFormat; blockFormat.setAlignment(Qt::AlignJustify); cursor.mergeBlockFormat(blockFormat); cursor.insertText("ac"); cursor.insertBlock(); cursor.insertText(" "); cursor.insertText(QChar(0x2028)); } // Trigger justification of text QImage image(1000, 1000, QImage::Format_ARGB32); image.fill(0); { QPainter p(&image); document.drawContents(&p, image.rect()); } { // If no p tag is specified it should not be inheriting it QTextDocument td; td.setHtml("Foo

  • First
"); QTextBlock block = td.begin(); while (block.isValid()) { QTextBlockFormat fmt = block.blockFormat(); QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::SingleHeight)); QCOMPARE(fmt.lineHeight(), qreal(0)); block = block.next(); } } { QTextDocument td; td.setHtml("

Foo

  • First
"); QList originalMargins; QTextBlock block = td.begin(); while (block.isValid()) { originalMargins << block.blockFormat().topMargin(); block = block.next(); } originalMargins[0] = 85; td.setHtml("

Foo

  • First
"); block = td.begin(); int count = 0; while (block.isValid()) { QTextBlockFormat fmt = block.blockFormat(); QCOMPARE(fmt.topMargin(), originalMargins.at(count++)); block = block.next(); } } } class BaseDocument : public QTextDocument { public: QUrl loadedResource() const { return resourceUrl; } QVariant loadResource(int type, const QUrl &name) override { resourceUrl = name; return QTextDocument::loadResource(type, name); } private: QUrl resourceUrl; }; void tst_QTextDocument::baseUrl_data() { QTest::addColumn("base"); QTest::addColumn("resource"); QTest::addColumn("loaded"); QTest::newRow("1") << QUrl() << QUrl("images/logo.png") << QUrl("images/logo.png"); QTest::newRow("2") << QUrl("file:///path/to/content") << QUrl("images/logo.png") << QUrl("file:///path/to/images/logo.png"); QTest::newRow("3") << QUrl("file:///path/to/content/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png"); QTest::newRow("4") << QUrl("file:///path/to/content/images") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png"); QTest::newRow("5") << QUrl("file:///path/to/content/images/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/images/logo.png"); QTest::newRow("6") << QUrl("file:///path/to/content/images") << QUrl("../images/logo.png") << QUrl("file:///path/to/images/logo.png"); QTest::newRow("7") << QUrl("file:///path/to/content/images/") << QUrl("../images/logo.png") << QUrl("file:///path/to/content/images/logo.png"); QTest::newRow("8") << QUrl("file:///path/to/content/index.html") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png"); } void tst_QTextDocument::baseUrl() { QFETCH(QUrl, base); QFETCH(QUrl, resource); QFETCH(QUrl, loaded); BaseDocument document; QVERIFY(!document.baseUrl().isValid()); document.setBaseUrl(base); QCOMPARE(document.baseUrl(), base); document.setHtml(QLatin1String("")); document.resource(QTextDocument::ImageResource, resource); QCOMPARE(document.loadedResource(), loaded); } void tst_QTextDocument::QTBUG28998_linkColor() { QPalette pal; pal.setColor(QPalette::Link, QColor("tomato")); QGuiApplication::setPalette(pal); QTextDocument doc; doc.setHtml("Qt"); QCOMPARE(doc.blockCount(), 1); QTextBlock block = doc.firstBlock(); QVERIFY(block.isValid()); QTextFragment fragment = block.begin().fragment(); QVERIFY(fragment.isValid()); QTextCharFormat format = fragment.charFormat(); QVERIFY(format.isValid()); QVERIFY(format.isAnchor()); QCOMPARE(format.anchorHref(), QStringLiteral("http://www.qt-project.org")); QCOMPARE(format.foreground(), pal.link()); } class ContentsChangeHandler : public QObject { Q_OBJECT public: ContentsChangeHandler(QTextDocument *doc) : verticalMovementX(-1) , doc(doc) { connect(doc, SIGNAL(contentsChange(int,int,int)), this, SLOT(saveModifiedText(int, int, int))); } private slots: void saveModifiedText(int from, int /*charsRemoved*/, int charsAdded) { QTextCursor tmp(doc); tmp.setPosition(from); tmp.setPosition(from + charsAdded, QTextCursor::KeepAnchor); text = tmp.selectedText(); verticalMovementX = tmp.verticalMovementX(); } public: QString text; int verticalMovementX; private: QTextDocument *doc; }; void tst_QTextDocument::textCursorUsageWithinContentsChange() { // force creation of layout doc->documentLayout(); QTextCursor cursor(doc); cursor.insertText("initial text"); ContentsChangeHandler handler(doc); cursor.insertText("new text"); QCOMPARE(handler.text, QString("new text")); QCOMPARE(handler.verticalMovementX, -1); } void tst_QTextDocument::cssInheritance() { { QTextDocument td; td.setHtml("" "

Foo

Bar

Baz

"); QTextBlock block = td.begin(); while (block.isValid()) { QTextBlockFormat fmt = block.blockFormat(); QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(fmt.lineHeight(), qreal(200)); block = block.next(); } } { QTextDocument td; td.setHtml("" "

Foo

Bar

Baz

"); QTextBlock block = td.begin(); QTextBlockFormat fmt = block.blockFormat(); QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::MinimumHeight)); QCOMPARE(fmt.lineHeight(), qreal(40)); block = block.next(); fmt = block.blockFormat(); QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(fmt.lineHeight(), qreal(300)); } { QTextDocument td; td.setHtml("" "

Foo

Bar

Baz

"); QTextBlock block = td.begin(); while (block.isValid()) { QCOMPARE(block.blockFormat().background(), QBrush()); QVERIFY(block.charFormat().font().bold()); block = block.next(); } } { QTextDocument td; td.setHtml("" "
Foo
Bar
"); QTextBlock block = td.begin(); // First is the table QTextCharFormat fmt = block.charFormat(); QVERIFY(!fmt.font().bold()); QVERIFY(fmt.font().italic()); // Then the th block = block.next(); fmt = block.charFormat(); QVERIFY(fmt.font().bold()); QVERIFY(fmt.font().italic()); // Then the td block = block.next(); fmt = block.charFormat(); QVERIFY(!fmt.font().bold()); QVERIFY(fmt.font().italic()); } { QTextDocument td; td.setHtml("" "

This should be bold

"); QTextBlock block = td.begin(); // First is the p QTextCharFormat fmt = block.charFormat(); QVERIFY(!fmt.font().bold()); QTextBlock::iterator it = block.begin(); // The non bold text is first QTextFragment currentFragment = it.fragment(); QVERIFY(currentFragment.isValid()); fmt = currentFragment.charFormat(); QVERIFY(!fmt.font().bold()); ++it; QVERIFY(!it.atEnd()); // Now check the "bold" text currentFragment = it.fragment(); QVERIFY(currentFragment.isValid()); fmt = currentFragment.charFormat(); QVERIFY(!fmt.font().bold()); QVERIFY(fmt.font().italic()); } { QTextDocument td; td.setHtml("" "

This should be bold

"); QTextBlock block = td.begin(); // First is the p QTextCharFormat fmt = block.charFormat(); QVERIFY(!fmt.font().bold()); QTextBlock::iterator it = block.begin(); // The non bold text is first QTextFragment currentFragment = it.fragment(); QVERIFY(currentFragment.isValid()); fmt = currentFragment.charFormat(); QVERIFY(!fmt.font().bold()); ++it; QVERIFY(!it.atEnd()); // Now check the bold text currentFragment = it.fragment(); QVERIFY(currentFragment.isValid()); fmt = currentFragment.charFormat(); QVERIFY(fmt.font().bold()); } } void tst_QTextDocument::lineHeightType() { { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight)); QCOMPARE(format.lineHeight(), 0.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight)); QCOMPARE(format.lineHeight(), 40.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(format.lineHeight(), 200.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight)); QCOMPARE(format.lineHeight(), 200.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(format.lineHeight(), 40.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); QCOMPARE(format.lineHeight(), 10.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); QCOMPARE(format.lineHeight(), 10.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(format.lineHeight(), 3.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(format.lineHeight(), 2.5); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight)); QCOMPARE(format.lineHeight(), 33.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); QCOMPARE(format.lineHeight(), 200.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight)); QCOMPARE(format.lineHeight(), 200.0); } } void tst_QTextDocument::cssLineHeightMultiplier() { { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(format.lineHeight(), 1000.0); } { QTextDocument td; td.setHtml("Foobar"); QTextBlock block = td.begin(); QTextBlockFormat format = block.blockFormat(); QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight)); QCOMPARE(format.lineHeight(), 138.0); } } void tst_QTextDocument::fontTagFace() { { QTextDocument td; td.setHtml("Foobar"); QTextFragment fragment = td.begin().begin().fragment(); QTextCharFormat format = fragment.charFormat(); QCOMPARE(format.fontFamilies().toStringList().value(0, QString()), QLatin1String("Times")); } { QTextDocument td; td.setHtml("Foobar"); QTextFragment fragment = td.begin().begin().fragment(); QTextCharFormat format = fragment.charFormat(); QCOMPARE(format.fontFamilies().toStringList().value(0, QString()), QLatin1String("Times")); QStringList expectedFamilies = { QLatin1String("Times"), QLatin1String("serif") }; QCOMPARE(format.fontFamilies().toStringList(), expectedFamilies); } } void tst_QTextDocument::mergeFontFamilies() { QTextDocument td; td.setHtml(QLatin1String( "" "Hello world" "")); QTextCharFormat newFormat; newFormat.setFontFamilies({QLatin1String("Jokerman")}); QTextCursor cursor = QTextCursor(&td); cursor.setPosition(0); cursor.setPosition(QByteArray("Hello World").size(), QTextCursor::KeepAnchor); cursor.mergeCharFormat(newFormat); QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Jokerman';"))); QTextCharFormat newFormatFamilies; newFormatFamilies.setFontFamilies({ QLatin1String("Arial"), QLatin1String("Helvetica") }); cursor.mergeCharFormat(newFormatFamilies); QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Arial','Helvetica'"))); newFormatFamilies.setFontFamilies({ QLatin1String("Arial"), QLatin1String("Jokerman"), QLatin1String("Helvetica") }); cursor.mergeCharFormat(newFormatFamilies); QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Arial','Jokerman','Helvetica'"))); } void tst_QTextDocument::clearUndoRedoStacks() { QTextDocument doc; QTextCursor c(&doc); c.insertText(QStringLiteral("lorem ipsum")); QVERIFY(doc.isUndoAvailable()); doc.clearUndoRedoStacks(QTextDocument::UndoStack); // Don't crash QVERIFY(!doc.isUndoAvailable()); } void tst_QTextDocument::resourceProvider() { int providerCalled = 0; QUrl providerUrl; auto provider = [&](const QUrl &url){ providerUrl = url; ++providerCalled; return QVariant(42); }; const QUrl url("test://img"); const QString html = QLatin1String("").arg(url.toString()); QTextDocument doc; QVERIFY(!doc.resourceProvider()); doc.setResourceProvider(provider); QVERIFY(doc.resourceProvider()); doc.setHtml(html); const QVariant res = doc.resource(QTextDocument::UserResource, url); QVERIFY(res.isValid()); QCOMPARE(providerUrl, url); QCOMPARE(providerCalled, 1); QVERIFY(!QTextDocument::defaultResourceProvider()); QTextDocument::setDefaultResourceProvider(provider); QVERIFY(QTextDocument::defaultResourceProvider()); QTextDocument doc2; QVERIFY(!doc2.resourceProvider()); doc2.setHtml(html); QVariant res2 = doc2.resource(QTextDocument::UserResource, url); QCOMPARE(res2, res); QCOMPARE(providerCalled, 2); } void tst_QTextDocument::contentsChangeIndices_data() { QTest::addColumn("html"); // adding list entries change the entire block, so change position is // not the same as the cursor position if this value is >= 0 QTest::addColumn("expectedBegin"); QTest::addRow("text") << "Test" << -1; QTest::addRow("unnumbered list") << "
  • Test
" << 0; QTest::addRow("numbered list") << "
  1. Test
" << 0; QTest::addRow("table") << "
Test
" << -1; } void tst_QTextDocument::contentsChangeIndices() { QFETCH(QString, html); QFETCH(int, expectedBegin); QTextDocument doc; QTestDocumentLayout *layout = new QTestDocumentLayout(&doc); doc.setDocumentLayout(layout); doc.setHtml(QString("%1").arg(html)); int documentLength = 0; int cursorLength = 0; int changeBegin = 0; int changeRemoved = 0; int changeAdded = 0; connect(&doc, &QTextDocument::contentsChange, this, [&](int pos, int removed, int added){ documentLength = doc.characterCount(); QTextCursor cursor(&doc); cursor.movePosition(QTextCursor::End); // includes end-of-paragraph character cursorLength = cursor.position() + 1; changeBegin = pos; changeRemoved = removed; changeAdded = added; }); QTextCursor cursor(&doc); cursor.movePosition(QTextCursor::End); if (expectedBegin < 0) expectedBegin = cursor.position(); cursor.insertBlock(); const int changeEnd = changeBegin + changeAdded; QVERIFY(documentLength > 0); QCOMPARE(documentLength, cursorLength); QVERIFY(documentLength >= changeEnd); QCOMPARE(changeBegin, expectedBegin); QCOMPARE(changeAdded - changeRemoved, 1); } void tst_QTextDocument::insertHtmlWithComments_data() { QTest::addColumn("html"); QTest::addColumn("expectedBlocks"); QTest::newRow("commentless") << "

first

second

third

" << QStringList { "first", "second", "third" }; QTest::newRow("normal") << "

first

third

" << QStringList { "first", "third" }; QTest::newRow("nonClosing") << "

first

second

third

" << QStringList { "first", "second", "third" }; QTest::newRow("fake") << "

first

second

third

" << QStringList { "first", "second", "third" }; QTest::newRow("endingNonExistant") << "

first

-->

second

third

" << QStringList { "first", "-->", "second", "third" }; } void tst_QTextDocument::insertHtmlWithComments() { QFETCH(QString, html); QFETCH(QStringList, expectedBlocks); QTextDocument doc; doc.setHtml(html); QCOMPARE(doc.blockCount(), expectedBlocks.size()); QStringList blockContent; auto currentBlock = doc.begin(); while (currentBlock != doc.end()) { blockContent.append(currentBlock.text()); currentBlock = currentBlock.next(); } QCOMPARE(blockContent, expectedBlocks); } void tst_QTextDocument::delayedLayout() { QTextDocument doc; doc.setHtml("Foobar"); QCOMPARE(doc.blockCount(), 1); doc.setLayoutEnabled(false); // Force creation of a layout QVERIFY(doc.documentLayout()); QTextBlock block = doc.begin(); QTextLayout *layout = block.layout(); QCOMPARE(layout->lineCount(), 0); // layout didn't happen yet doc.setLayoutEnabled(true); QCOMPARE(layout->lineCount(), 1); // layout happened } QTEST_MAIN(tst_QTextDocument) #include "tst_qtextdocument.moc"