diff options
Diffstat (limited to 'tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp')
-rw-r--r-- | tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp | 525 |
1 files changed, 510 insertions, 15 deletions
diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp index 63d4f24a54..535f20e7bb 100644 --- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp +++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp @@ -1,17 +1,30 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> #include <QtTest/QtTest> -#include <QtQuick/QQuickTextDocument> #include <QtQuick/QQuickItem> -#include <QtQuick/private/qquicktextedit_p.h> +#include <QtQuick/QQuickTextDocument> +#include <QtQuick/QQuickView> #include <QtQuick/private/qquicktextdocument_p.h> +#include <QtQuick/private/qquicktextedit_p.h> +#include <QtQuick/private/qquicktextedit_p_p.h> +#include <QtCore/QStringConverter> +#include <QtGui/QTextBlock> #include <QtGui/QTextDocument> #include <QtGui/QTextDocumentWriter> -#include <QtQml/QQmlEngine> #include <QtQml/QQmlComponent> +#include <QtQml/QQmlEngine> +#include <QtQml/QQmlFile> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> +#ifdef Q_OS_UNIX +#include <unistd.h> +#endif + +Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") + +using namespace Qt::StringLiterals; class tst_qquicktextdocument : public QQmlDataTest { @@ -19,18 +32,91 @@ class tst_qquicktextdocument : public QQmlDataTest public: tst_qquicktextdocument(); +private: + QPair<int, int> fragmentsAndItalics(const QTextDocument *doc); + bool isMainFontFixed(); + private slots: void textDocumentWriter(); - void textDocumentWithImage(); + void customDocument(); + void sourceAndSave_data(); + void sourceAndSave(); + void loadErrorNoSuchFile(); + void loadErrorPermissionDenied(); + void overrideTextFormat_data(); + void overrideTextFormat(); + void independentDocumentsSameSource_data(); + void independentDocumentsSameSource(); }; QString text = QStringLiteral("foo bar"); +// similar to TestDocument in tst_qtextdocument.cpp +class FakeImageDocument : public QTextDocument +{ +public: + inline FakeImageDocument(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 FakeImageDocument::hasResourceCached() +{ + return resourceLoaded; +} + +QVariant FakeImageDocument::loadResource(int type, const QUrl &name) +{ + qCDebug(lcTests) << type << name << ": expecting" << int(QTextDocument::ImageResource) << url; + if (type == QTextDocument::ImageResource && name == url) { + resourceLoaded = true; + return string; + } + return QTextDocument::loadResource(type, name); +} + tst_qquicktextdocument::tst_qquicktextdocument() : QQmlDataTest(QT_QMLTEST_DATADIR) { } +/*! \internal + Returns {fragmentCount, italicFragmentIndex}. If no italic fragment is found, + italicFragmentIndex is -1. +*/ +QPair<int, int> tst_qquicktextdocument::fragmentsAndItalics(const QTextDocument *doc) +{ + int fragmentCount = 0; + int italicFragment = -1; + for (QTextBlock::iterator it = doc->firstBlock().begin(); !(it.atEnd()); ++it) { + QTextFragment currentFragment = it.fragment(); + if (currentFragment.charFormat().fontItalic()) + italicFragment = fragmentCount; + ++fragmentCount; + qCDebug(lcTests) << (currentFragment.charFormat().fontItalic() ? "italic" : "roman") << currentFragment.text(); + } + return {fragmentCount, italicFragment}; +} + +bool tst_qquicktextdocument::isMainFontFixed() +{ + bool ret = QFontInfo(QGuiApplication::font()).fixedPitch(); + if (ret) { + qCWarning(lcTests) << "QFontDatabase::GeneralFont is monospaced: markdown writing is likely to use too many backticks" + << QFontDatabase::systemFont(QFontDatabase::GeneralFont); + } + return ret; +} + void tst_qquicktextdocument::textDocumentWriter() { QQmlEngine e; @@ -54,18 +140,427 @@ void tst_qquicktextdocument::textDocumentWriter() delete o; } -void tst_qquicktextdocument::textDocumentWithImage() +/*! \internal + Verify that it's OK to replace the default QTextDocument that TextEdit creates + with a user-created QTextDocument. + + Also verify that the user can still override QTextDocument::loadResource(). + QTextDocument::loadResource() can call its QObject parent's loadResource(); + the default QTextDocument's parent is the QQuickTextEdit, which provides an + implementation of QQuickTextEdit::loadResource(), which uses QQuickPixmap + to load and cache images. This will be bypassed if the user overrides + loadResource() to do something different. +*/ +void tst_qquicktextdocument::customDocument() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("text.qml")); + QScopedPointer<QQuickTextEdit> textEdit(qobject_cast<QQuickTextEdit*>(c.create())); + QCOMPARE(textEdit.isNull(), false); + auto *textEditPriv = QQuickTextEditPrivate::get(textEdit.get()); + QVERIFY(textEditPriv->ownsDocument); + + QQuickTextDocument *quickDocument = textEdit->property("textDocument").value<QQuickTextDocument*>(); + QVERIFY(quickDocument); + QPointer<QTextDocument> defaultDocument(quickDocument->textDocument()); + const QString imageUrl = "https://www.qt.io/hubfs/Qt-logo-neon-small.png"; + const QString fakeImageData = "foo!"; + + FakeImageDocument fdoc(QUrl(imageUrl), fakeImageData); + quickDocument->setTextDocument(&fdoc); + QVERIFY(defaultDocument.isNull()); // deleted because of being replaced (don't leak) + QCOMPARE(textEditPriv->ownsDocument, false); + + // QQuickTextEdit::setText() -> QQuickTextControl::setHtml() -> + // QQuickTextControlPrivate::setContent() -> fdoc->setHtml() + // and eventually fdoc->loadResource() which substitutes a string for the requested image + textEdit->setTextFormat(QQuickTextEdit::RichText); + textEdit->setText("an image: <img src='" + imageUrl + "'/>"); + QCOMPARE(fdoc.hasResourceCached(), true); + + auto firstBlock = fdoc.firstBlock(); + // check that image loading has been bypassed by FakeImageDocument + bool foundImage = false; + int fragmentCount = 0; + for (QTextBlock::iterator it = firstBlock.begin(); !(it.atEnd()); ++it) { + QTextFragment currentFragment = it.fragment(); + QVERIFY(currentFragment.isValid()); + ++fragmentCount; + const QString imageName = currentFragment.charFormat().stringProperty(QTextFormat::ImageName); + if (!imageName.isEmpty()) { + QCOMPARE(imageName, imageUrl); + foundImage = true; + QCOMPARE(fdoc.resource(QTextDocument::ImageResource, imageUrl).toString(), fakeImageData); + } + } + QVERIFY(foundImage); + QCOMPARE(fragmentCount, 2); +} + +void tst_qquicktextdocument::sourceAndSave_data() +{ + QTest::addColumn<QQuickTextEdit::TextFormat>("textFormat"); + QTest::addColumn<QString>("source"); + QTest::addColumn<std::optional<QStringConverter::Encoding>>("expectedEncoding"); + QTest::addColumn<Qt::TextFormat>("expectedTextFormat"); + QTest::addColumn<int>("minCharCount"); + QTest::addColumn<QString>("expectedPlainText"); + + const std::optional<QStringConverter::Encoding> nullEnc; + + QTest::newRow("plain") << QQuickTextEdit::PlainText << "hello.txt" + << nullEnc << Qt::PlainText << 15 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("markdown") << QQuickTextEdit::MarkdownText << "hello.md" + << nullEnc << Qt::MarkdownText << 15 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("html") << QQuickTextEdit::RichText << "hello.html" + << std::optional<QStringConverter::Encoding>(QStringConverter::Utf8) + << Qt::RichText << 15 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("html-utf16be") << QQuickTextEdit::AutoText << "hello-utf16be.html" + << std::optional<QStringConverter::Encoding>(QStringConverter::Utf16BE) + << Qt::RichText << 15 << u"Γειά σου Κόσμε!"_s; +} + +void tst_qquicktextdocument::sourceAndSave() +{ + QFETCH(QQuickTextEdit::TextFormat, textFormat); + QFETCH(QString, source); + QFETCH(std::optional<QStringConverter::Encoding>, expectedEncoding); + QFETCH(Qt::TextFormat, expectedTextFormat); + QFETCH(int, minCharCount); + QFETCH(QString, expectedPlainText); + + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("text.qml")); + QScopedPointer<QQuickTextEdit> textEdit(qobject_cast<QQuickTextEdit*>(c.create())); + QCOMPARE(textEdit.isNull(), false); + QQuickTextDocument *qqdoc = textEdit->property("textDocument").value<QQuickTextDocument*>(); + QVERIFY(qqdoc); + const QQmlContext *ctxt = e.rootContext(); + // text.qml has text: "" but that's not a real change; QQuickTextEdit::setText() returns early + // QQuickTextEditPrivate::init() also modifies defaults and then resets the modified state + QCOMPARE(qqdoc->isModified(), false); + // no stray signals should be visible to QML during init + QCOMPARE(textEdit->property("modifiedChangeCount").toInt(), 0); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 0); + QTextDocument *doc = qqdoc->textDocument(); + QVERIFY(doc); + QSignalSpy sourceChangedSpy(qqdoc, &QQuickTextDocument::sourceChanged); + QSignalSpy modifiedChangedSpy(qqdoc, &QQuickTextDocument::modifiedChanged); + QSignalSpy statusChangedSpy(qqdoc, &QQuickTextDocument::statusChanged); + + QTemporaryDir tmpDir; + QVERIFY(tmpDir.isValid()); + QFile sf(QQmlFile::urlToLocalFileOrQrc(ctxt->resolvedUrl(testFileUrl(source)))); + qCDebug(lcTests) << source << "orig ->" << sf.fileName(); + QVERIFY(sf.exists()); + QString tmpPath = tmpDir.filePath(source); + QVERIFY(sf.copy(tmpPath)); + qCDebug(lcTests) << source << "copy ->" << tmpDir.path() << ":" << tmpPath; + + QCOMPARE(statusChangedSpy.size(), 0); + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Null); + textEdit->setTextFormat(textFormat); + qqdoc->setProperty("source", QUrl::fromLocalFile(tmpPath)); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 1); + QCOMPARE(statusChangedSpy.size(), 2); // Loading, then Loaded + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Loaded); + QVERIFY(qqdoc->errorString().isEmpty()); + const auto *qqdp = QQuickTextDocumentPrivate::get(qqdoc); + QCOMPARE(qqdp->detectedFormat, expectedTextFormat); + QCOMPARE_GE(doc->characterCount(), minCharCount); + QCOMPARE(doc->toPlainText().trimmed(), expectedPlainText); + QCOMPARE(qqdp->encoding, expectedEncoding); + + textEdit->setText("hello"); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE_GE(textEdit->property("modifiedChangeCount").toInt(), 1); + QCOMPARE(textEdit->property("modifiedChangeCount").toInt(), modifiedChangedSpy.size()); + modifiedChangedSpy.clear(); + textEdit->insert(5, "!"); + QCOMPARE_GE(modifiedChangedSpy.size(), 1); + QCOMPARE(qqdoc->isModified(), true); + + qqdoc->save(); + QCOMPARE(statusChangedSpy.size(), 4); // Saving, then Saved + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Saved); + QVERIFY(qqdoc->errorString().isEmpty()); + QFile tf(tmpPath); + QVERIFY(tf.open(QIODeviceBase::ReadOnly)); + auto readBack = tf.readAll(); + if (expectedTextFormat == Qt::RichText) { + QStringDecoder dec(*expectedEncoding); + const QString decStr = dec(readBack); + QVERIFY(decStr.contains("hello!</p>")); + } else { + QVERIFY(readBack.contains("hello!")); + } + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), sourceChangedSpy.size()); +} + +void tst_qquicktextdocument::loadErrorNoSuchFile() { - QQuickTextDocumentWithImageResources document(nullptr); - QImage image(1, 1, QImage::Format_Mono); - image.fill(1); + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("text.qml")); + QScopedPointer<QQuickTextEdit> textEdit(qobject_cast<QQuickTextEdit*>(c.create())); + QCOMPARE(textEdit.isNull(), false); + QQuickTextDocument *qqdoc = textEdit->property("textDocument").value<QQuickTextDocument*>(); + QVERIFY(qqdoc); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 0); + QTextDocument *doc = qqdoc->textDocument(); + QVERIFY(doc); + QSignalSpy sourceChangedSpy(qqdoc, &QQuickTextDocument::sourceChanged); + QSignalSpy statusChangedSpy(qqdoc, &QQuickTextDocument::statusChanged); + + QCOMPARE(statusChangedSpy.size(), 0); + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Null); + const QRegularExpression err(".*does not exist"); + QTest::ignoreMessage(QtWarningMsg, err); + qqdoc->setProperty("source", testFileUrl("nosuchfile.md")); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 1); + qCDebug(lcTests) << "status history" << textEdit->property("statusHistory").toList(); + QCOMPARE(statusChangedSpy.size(), 1); + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::ReadError); + QVERIFY(qqdoc->errorString().contains(err)); +} + +void tst_qquicktextdocument::loadErrorPermissionDenied() +{ +#ifdef Q_OS_UNIX + if (geteuid() == 0) + QSKIP("Permission will not be denied with root privileges."); +#endif + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("text.qml")); + QScopedPointer<QQuickTextEdit> textEdit(qobject_cast<QQuickTextEdit*>(c.create())); + QCOMPARE(textEdit.isNull(), false); + QQuickTextDocument *qqdoc = textEdit->property("textDocument").value<QQuickTextDocument*>(); + QVERIFY(qqdoc); + const QQmlContext *ctxt = e.rootContext(); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 0); + QTextDocument *doc = qqdoc->textDocument(); + QVERIFY(doc); + QSignalSpy sourceChangedSpy(qqdoc, &QQuickTextDocument::sourceChanged); + QSignalSpy statusChangedSpy(qqdoc, &QQuickTextDocument::statusChanged); + + QTemporaryDir tmpDir; + QVERIFY(tmpDir.isValid()); + const QString source("hello.md"); + QFile sf(QQmlFile::urlToLocalFileOrQrc(ctxt->resolvedUrl(testFileUrl(source)))); + qCDebug(lcTests) << source << "orig ->" << sf.fileName(); + QVERIFY(sf.exists()); + QString tmpPath = tmpDir.filePath(source); + QVERIFY(sf.copy(tmpPath)); + qCDebug(lcTests) << source << "copy ->" << tmpDir.path() << ":" << tmpPath; + if (!QFile::setPermissions(tmpPath, QFileDevice::Permissions{})) // no permissions at all + QSKIP("Failed to change permissions of temporary file: cannot continue."); + + QCOMPARE(statusChangedSpy.size(), 0); + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Null); + const QRegularExpression err(".*Failed to read: Permission denied"); + QTest::ignoreMessage(QtWarningMsg, err); + qqdoc->setProperty("source", QUrl::fromLocalFile(tmpPath)); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 1); + qCDebug(lcTests) << "status history" << textEdit->property("statusHistory").toList(); + QCOMPARE(statusChangedSpy.size(), 1); + QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::ReadError); + QVERIFY(qqdoc->errorString().contains(err)); +} + +void tst_qquicktextdocument::overrideTextFormat_data() +{ + QTest::addColumn<QUrl>("qmlfile"); + QTest::addColumn<QQuickTextEdit::TextFormat>("initialFormat"); + QTest::addColumn<QUrl>("source"); + QTest::addColumn<int>("expectedInitialFragmentCount"); + QTest::addColumn<int>("expectedInitialItalicFragment"); + // first part of TextEdit.text after loading + QTest::addColumn<QString>("expectedTextPrefix"); + + QTest::addColumn<QQuickTextEdit::TextFormat>("replacementFormat"); + QTest::addColumn<int>("expectedFragmentCount"); + QTest::addColumn<int>("expectedItalicFragment"); + // first part of TextEdit.text after switching to replacementFormat + QTest::addColumn<QString>("expectedReplacementPrefix"); + QTest::addColumn<int>("expectedTextChangedSignalsAfterReplacement"); + + QTest::addColumn<QQuickTextEdit::TextFormat>("finalFormat"); + QTest::addColumn<int>("expectedFinalFragmentCount"); + QTest::addColumn<int>("expectedFinalItalicFragment"); + // first part of TextEdit.text after switching to finalFormat + QTest::addColumn<QString>("expectedFinalPrefix"); + + QTest::newRow("load md, switch to plain, back to md") + << testFileUrl("text.qml") << QQuickTextEdit::MarkdownText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s << 2 + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md, switch to plain, then auto") + << testFileUrl("text.qml") << QQuickTextEdit::MarkdownText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s << 2 + << QQuickTextEdit::AutoText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md, switch to html, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::MarkdownText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s << 2 + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load md as plain text, switch to md, back to plain") + << testFileUrl("text.qml") << QQuickTextEdit::PlainText << testFileUrl("hello.md") + << 1 << -1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s << 2 + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md as autotext, switch to plain, back to auto") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s << 2 + << QQuickTextEdit::AutoText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md as autotext, switch to md, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + // going from AutoText to a matching explicit format does not cause extra textChanged() + << 1 + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md as autotext, switch to html, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s << 2 + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + + QTest::newRow("load html, switch to plain, back to rich") + << testFileUrl("text.qml") << QQuickTextEdit::RichText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s << 2 + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as plain text, switch to html, back to plain") + << testFileUrl("text.qml") << QQuickTextEdit::PlainText << testFileUrl("hello.html") + << 1 << -1 << u"Γειά σου <i>Κόσμε</i>!"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s << 2 + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as autotext, switch to plain, back to auto") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s << 2 + << QQuickTextEdit::AutoText << 3 << 1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as autotext, switch to html, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s + // going from AutoText to a matching explicit format does not cause extra textChanged() + << 1 + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as autotext, switch to markdown, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s << 2 + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s; +} + +void tst_qquicktextdocument::overrideTextFormat() // QTBUG-120772 +{ + if (isMainFontFixed()) + QSKIP("fixed-pitch main font (QTBUG-103484)"); + + QFETCH(QUrl, qmlfile); + QFETCH(QQuickTextEdit::TextFormat, initialFormat); + QFETCH(QUrl, source); + QFETCH(int, expectedInitialFragmentCount); + QFETCH(int, expectedInitialItalicFragment); + QFETCH(QString, expectedTextPrefix); + + QFETCH(QQuickTextEdit::TextFormat, replacementFormat); + QFETCH(int, expectedFragmentCount); + QFETCH(int, expectedItalicFragment); + QFETCH(QString, expectedReplacementPrefix); + QFETCH(int, expectedTextChangedSignalsAfterReplacement); + + QFETCH(QQuickTextEdit::TextFormat, finalFormat); + QFETCH(int, expectedFinalFragmentCount); + QFETCH(int, expectedFinalItalicFragment); + QFETCH(QString, expectedFinalPrefix); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, qmlfile)); + QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit *>(window.rootObject()); + QVERIFY(textEdit); + QQuickTextDocument *qqdoc = textEdit->property("textDocument").value<QQuickTextDocument*>(); + QVERIFY(qqdoc); + QTextDocument *doc = qqdoc->textDocument(); + QVERIFY(doc); + + textEdit->setTextFormat(initialFormat); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 0); + QSignalSpy sourceChangedSpy(qqdoc, &QQuickTextDocument::sourceChanged); + QSignalSpy textChangedSpy(textEdit, &QQuickTextEdit::textChanged); + + qqdoc->setProperty("source", source); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 1); + QCOMPARE_GE(textChangedSpy.size(), 1); + auto fragCountAndItalic = fragmentsAndItalics(doc); + QCOMPARE(fragCountAndItalic.first, expectedInitialFragmentCount); + QCOMPARE(fragCountAndItalic.second, expectedInitialItalicFragment); + QString textPropValue = textEdit->text(); + qCDebug(lcTests) << "expect text()" << textPropValue.first(qMin(20, textPropValue.size() - 1)) + << "to start with" << expectedTextPrefix; + QVERIFY(textPropValue.startsWith(expectedTextPrefix)); + + textEdit->setTextFormat(replacementFormat); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE_GE(textChangedSpy.size(), expectedTextChangedSignalsAfterReplacement); + fragCountAndItalic = fragmentsAndItalics(doc); + QCOMPARE(fragCountAndItalic.first, expectedFragmentCount); + QCOMPARE(fragCountAndItalic.second, expectedItalicFragment); + textPropValue = textEdit->text(); + qCDebug(lcTests) << "expect text()" << textPropValue.first(qMin(20, textPropValue.size() - 1)) + << "to start with" << expectedReplacementPrefix; + QVERIFY(textPropValue.startsWith(expectedReplacementPrefix)); + + textEdit->setTextFormat(finalFormat); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE_GE(textChangedSpy.size(), expectedTextChangedSignalsAfterReplacement + 1); + fragCountAndItalic = fragmentsAndItalics(doc); + QCOMPARE(fragCountAndItalic.first, expectedFinalFragmentCount); + QCOMPARE(fragCountAndItalic.second, expectedFinalItalicFragment); + textPropValue = textEdit->text(); + qCDebug(lcTests) << "expect text()" << textPropValue.first(qMin(20, textPropValue.size() - 1)) + << "to start with" << expectedFinalPrefix; + QVERIFY(textPropValue.startsWith(expectedFinalPrefix)); +} + +void tst_qquicktextdocument::independentDocumentsSameSource_data() +{ + QTest::addColumn<QUrl>("qmlfile"); + + QTest::newRow("textFormat above source") << testFileUrl("sideBySideIndependent.qml"); + QTest::newRow("source above textFormat") << testFileUrl("sideBySideIndependentReverse.qml"); +} + +// ensure that two TextEdits' textFormat properties take effect, regardless of qml init order +void tst_qquicktextdocument::independentDocumentsSameSource() // QTBUG-120772 +{ + QFETCH(QUrl, qmlfile); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, qmlfile)); + QQuickTextEdit *textEditPlain = window.rootObject()->findChild<QQuickTextEdit *>("plain"); + QVERIFY(textEditPlain); + QQuickTextEdit *textEditMarkdown = window.rootObject()->findChild<QQuickTextEdit *>("markdown"); + QVERIFY(textEditMarkdown); + + auto fragCountAndItalic = fragmentsAndItalics(textEditPlain->textDocument()->textDocument()); + QCOMPARE(fragCountAndItalic.first, 1); + QCOMPARE(fragCountAndItalic.second, -1); - QString name = "image"; - document.addResource(QTextDocument::ImageResource, name, image); - QTextImageFormat format; - format.setName(name); - QCOMPARE(image, document.image(format)); - QCOMPARE(image, document.resource(QTextDocument::ImageResource, name).value<QImage>()); + fragCountAndItalic = fragmentsAndItalics(textEditMarkdown->textDocument()->textDocument()); + QCOMPARE(fragCountAndItalic.first, 3); + QCOMPARE(fragCountAndItalic.second, 1); } QTEST_MAIN(tst_qquicktextdocument) |