// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only


#include <QTest>
#include <QLoggingCategory>

#include <qfontinfo.h>
#include <qtextdocument.h>
#include <qtexttable.h>
#include <qvariant.h>
#include <qtextdocumentfragment.h>
#include <qabstracttextdocumentlayout.h>
#include <qtextlayout.h>
#include <qtextcursor.h>
#include <qtextobject.h>
#include <qdebug.h>

#include <private/qtextcursor_p.h>

Q_LOGGING_CATEGORY(lcTests, "qt.gui.tests")

QT_FORWARD_DECLARE_CLASS(QTextDocument)

class tst_QTextCursor : public QObject
{
    Q_OBJECT

private slots:
    void init();
    void cleanup();
    void navigation1();
    void navigation2_data();
    void navigation2();
    void navigation3();
    void navigation4();
    void navigation5();
    void navigation6();
    void navigation7();
    void navigation8();
    void navigation9();
#ifndef QT_NO_TEXTHTMLPARSER
    void navigation10();
#endif
    void movePositionEndOfLine();
    void insertBlock();
    void insertWithBlockSeparator1();
    void insertWithBlockSeparator2();
    void insertWithBlockSeparator3();
    void insertWithBlockSeparator4();
    void clearObjectType1();
    void clearObjectType2();
    void clearObjectType3();
    void comparisonOperators1();
    void comparisonOperators2();
    void selection1();
    void dontCopyTableAttributes();

    void checkFrame1();
    void checkFrame2();

    void tableMovement();
    void selectionsInTable();

    void insertBlockToUseCharFormat();

    void selectedText();

    void insertBlockShouldRemoveSelection();
    void insertBlockShouldRemoveSelection2();
    void mergeCellShouldUpdateSelection();

    void joinPreviousEditBlock();

    void setBlockFormatInTable();

    void blockCharFormat();
    void blockCharFormat2();
    void blockCharFormat3();
    void blockCharFormatOnSelection();

    void anchorInitialized1();
    void anchorInitialized2();
    void anchorInitialized3();

    void selectWord();
    void selectWordWithSeparators_data();
    void selectWordWithSeparators();
    void startOfWord();
    void selectBlock();
    void selectVisually();

    void insertText();
#ifndef QT_NO_TEXTHTMLPARSER
    void insertHtml_data();
    void insertHtml();
#endif
#if QT_CONFIG(textmarkdownreader)
    void insertMarkdown_data();
    void insertMarkdown();
#endif

    void insertFragmentShouldUseCurrentCharFormat();

    void endOfLine();

    void editBlocksDuringRemove();
    void selectAllDuringRemove();

    void update_data();
    void update();

    void disallowSettingObjectIndicesOnCharFormats();

    void blockAndColumnNumber();

    void clearCells();

    void task244408_wordUnderCursor_data();
    void task244408_wordUnderCursor();

    void adjustCursorsOnInsert();

    void cursorPositionWithBlockUndoAndRedo();
    void cursorPositionWithBlockUndoAndRedo2();
    void cursorPositionWithBlockUndoAndRedo3();

    void joinNonEmptyRemovedBlockUserState();
    void crashOnDetachingDanglingCursor();

    void deletePreviousGraphemeCluster_data();
    void deletePreviousGraphemeCluster();

private:
    int blockCount();

    QTextDocument *doc;
    QTextCursor cursor;
};

void tst_QTextCursor::init()
{
    doc = new QTextDocument;
    cursor = QTextCursor(doc);
}

void tst_QTextCursor::cleanup()
{
    cursor = QTextCursor();
    delete doc;
    doc = 0;
}

void tst_QTextCursor::navigation1()
{

    cursor.insertText("Hello World");
    QCOMPARE(doc->toPlainText(), QLatin1String("Hello World"));

    cursor.movePosition(QTextCursor::End);
    QCOMPARE(cursor.position(), 11);
    cursor.deletePreviousChar();
    QCOMPARE(cursor.position(), 10);
    cursor.deletePreviousChar();
    cursor.deletePreviousChar();
    cursor.deletePreviousChar();
    cursor.deletePreviousChar();
    cursor.deletePreviousChar();
    QCOMPARE(doc->toPlainText(), QLatin1String("Hello"));

    QTextCursor otherCursor(doc);
    otherCursor.movePosition(QTextCursor::Start);
    otherCursor.movePosition(QTextCursor::Right);
    cursor = otherCursor;
    cursor.movePosition(QTextCursor::Right);
    QVERIFY(cursor != otherCursor);
    otherCursor.insertText("Hey");
    QCOMPARE(cursor.position(), 5);

    doc->undo();
    QCOMPARE(cursor.position(), 2);
    doc->redo();
    QCOMPARE(cursor.position(), 5);

    doc->undo();

    doc->undo();
    QCOMPARE(doc->toPlainText(), QLatin1String("Hello World"));

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 6);
    QCOMPARE(cursor.position(), 6);
    otherCursor = cursor;
    otherCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2);
    otherCursor.deletePreviousChar();
    otherCursor.deletePreviousChar();
    otherCursor.deletePreviousChar();
    QCOMPARE(cursor.position(), 5);

    cursor.movePosition(QTextCursor::End);
    cursor.insertBlock();
    {
        int oldPos = cursor.position();
        cursor.movePosition(QTextCursor::End);
        QCOMPARE(cursor.position(), oldPos);
    }
    QVERIFY(cursor.atBlockStart());
    QCOMPARE(cursor.position(), 9);

    QTextCharFormat fmt;
    fmt.setForeground(Qt::blue);
    cursor.insertText("Test", fmt);
    QCOMPARE(fmt, cursor.charFormat());
    QCOMPARE(cursor.position(), 13);
}

void tst_QTextCursor::navigation2_data()
{
    QTest::addColumn<QStringList>("sl");
    QTest::addColumn<QList<QVariant> >("movement");
    QTest::addColumn<int>("finalPos");

    QTest::newRow("startBlock1") << QStringList("Happy happy happy joy joy joy")
                              << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)) << 0;
    QTest::newRow("endBlock1") << QStringList("Happy happy happy joy joy joy")
                            << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)
                                     << QVariant(QTextCursor::EndOfBlock)) << 29;
    QTest::newRow("startBlock2") << QStringList("Happy happy happy joy joy joy")
                              << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)
                                     << QVariant(QTextCursor::EndOfBlock)
                                     << QVariant(QTextCursor::StartOfBlock)) << 0;
    QTest::newRow("endBlock2") << QStringList("Happy happy happy joy joy joy")
                            << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)
                                     << QVariant(QTextCursor::EndOfBlock)
                                     << QVariant(QTextCursor::StartOfBlock)
                                     << QVariant(QTextCursor::EndOfBlock)
                                     ) << 29;
    QTest::newRow("multiBlock1") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock))
                             << 18;
    QTest::newRow("multiBlock2") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)
                                                   << QVariant(QTextCursor::EndOfBlock))
                             << 29;
    QTest::newRow("multiBlock3") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)
                                                   << QVariant(QTextCursor::StartOfBlock))
                             << 18;
    QTest::newRow("multiBlock4") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::Start)
                                                   << QVariant(QTextCursor::EndOfBlock))
                             << 17;
    QTest::newRow("multiBlock5") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::Start)
                                                   << QVariant(QTextCursor::EndOfBlock)
                                                   << QVariant(QTextCursor::EndOfBlock))
                             << 17;
    QTest::newRow("multiBlock6") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::End)
                                                   << QVariant(QTextCursor::StartOfBlock))
                             << 18;
    QTest::newRow("multiBlock7") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock))
                             << 0;
    QTest::newRow("multiBlock8") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock)
                                                   << QVariant(QTextCursor::EndOfBlock))
                             << 17;
    QTest::newRow("multiBlock9") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock)
                                                   << QVariant(QTextCursor::NextBlock))
                             << 18;
    QTest::newRow("multiBlock10") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                               << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock)
                                                     << QVariant(QTextCursor::NextBlock)
                                                     << QVariant(QTextCursor::NextBlock))
                               << 18;
    QTest::newRow("multiBlock11") << (QStringList() << QString("Happy happy happy")
                                                << QString("Joy Joy Joy"))
                               << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock)
                                                     << QVariant(QTextCursor::NextBlock)
                                                     << QVariant(QTextCursor::EndOfBlock))
                               << 29;
    QTest::newRow("PreviousWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                                << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord))
                                << 26;
    QTest::newRow("PreviousWord2") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                                << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord)
                                                      << QVariant(QTextCursor::PreviousWord))
                                << 22;
    QTest::newRow("EndWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                                << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord)
                                                      << QVariant(QTextCursor::PreviousWord)
                                                      << QVariant(QTextCursor::EndOfWord))
                                << 25;
    QTest::newRow("NextWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                                << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord)
                                                      << QVariant(QTextCursor::PreviousWord)
                                                      << QVariant(QTextCursor::NextWord))
                                << 26;
    QTest::newRow("NextWord2") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                                << (QList<QVariant>() << QVariant(QTextCursor::Start)
                                                      << QVariant(QTextCursor::NextWord)
                                                      << QVariant(QTextCursor::EndOfWord))
                                << 11;
    QTest::newRow("StartWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord)
                                                   << QVariant(QTextCursor::PreviousWord)
                                                   << QVariant(QTextCursor::StartOfWord))
                             << 22;
    QTest::newRow("StartWord3") << (QStringList() << QString("Happy happy happy Joy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::Start)
                                                   << QVariant(QTextCursor::NextWord)
                                                   << QVariant(QTextCursor::EndOfWord)
                                                   << QVariant(QTextCursor::StartOfWord))
                             << 6;

    QTest::newRow("PreviousCharacter") << (QStringList() << QString("Happy happy Joy Joy"))
                             << (QList<QVariant>() << QVariant(QTextCursor::PreviousCharacter)
                                                   << QVariant(QTextCursor::PreviousCharacter))
                             << 17;
}

void tst_QTextCursor::navigation2()
{
    QFETCH(QStringList, sl);
    QFETCH(QList<QVariant>, movement);
    int i;
    for (i = 0; i < sl.size(); ++i) {
        cursor.insertText(sl.at(i));
        if (i < sl.size() - 1)
            cursor.insertBlock();
    }

    for (i = 0; i < movement.size(); ++i)
        cursor.movePosition(QTextCursor::MoveOperation(movement.at(i).toInt()));
    QTEST(cursor.position(), "finalPos");
}

void tst_QTextCursor::navigation3()
{
    cursor.insertText("a");
    cursor.deletePreviousChar();
    QCOMPARE(cursor.position(), 0);
    QVERIFY(doc->toPlainText().isEmpty());
}

void tst_QTextCursor::navigation4()
{
    cursor.insertText("  Test  ");

    cursor.setPosition(4);
    cursor.movePosition(QTextCursor::EndOfWord);
    QCOMPARE(cursor.position(), 6);
}

void tst_QTextCursor::navigation5()
{
    cursor.insertText("Test");
    cursor.insertBlock();
    cursor.insertText("Test");

    cursor.setPosition(0);
    cursor.movePosition(QTextCursor::EndOfBlock);
    QCOMPARE(cursor.position(), 4);
}

void tst_QTextCursor::navigation6()
{
    // triger creation of document layout, so that QTextLines are there
    doc->documentLayout();
    doc->setTextWidth(1000);

    cursor.insertText("Test    ");

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::EndOfLine);
    QCOMPARE(cursor.position(), 8);
}

void tst_QTextCursor::navigation7()
{
    QVERIFY(doc->isEmpty());
    for (int i = QTextCursor::Start; i <= QTextCursor::WordRight; ++i)
        QVERIFY(!cursor.movePosition(QTextCursor::MoveOperation(i)));

    doc->setPlainText("Hello World");
    cursor.movePosition(QTextCursor::Start);
    do {
    } while (cursor.movePosition(QTextCursor::NextCharacter));
    QVERIFY(true /*reached*/);
}

void tst_QTextCursor::navigation8()
{
    cursor.insertList(QTextListFormat::ListDecimal);
    QCOMPARE(cursor.position(), 1);
    cursor.insertText("foo");
    QCOMPARE(cursor.position(), 4);

    cursor.insertList(QTextListFormat::ListCircle);
    QCOMPARE(cursor.position(), 5);
    cursor.insertText("something");
    QCOMPARE(cursor.position(), 14);

    cursor.movePosition(QTextCursor::PreviousCharacter);
    QCOMPARE(cursor.position(), 13);

    cursor.setPosition(2);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.position(), 3);
}

void tst_QTextCursor::navigation9()
{
    cursor.insertText("Hello  &-=+\t   World");
    cursor.movePosition(QTextCursor::PreviousWord);
    QCOMPARE(cursor.position(), 15);
    cursor.movePosition(QTextCursor::PreviousWord);
    QCOMPARE(cursor.position(), 7);
    cursor.movePosition(QTextCursor::PreviousWord);
    QCOMPARE(cursor.position(), 0);
    cursor.movePosition(QTextCursor::NextWord);
    QCOMPARE(cursor.position(), 7);
    cursor.movePosition(QTextCursor::NextWord);
    QCOMPARE(cursor.position(), 15);
}

#ifndef QT_NO_TEXTHTMLPARSER
void tst_QTextCursor::navigation10()
{
    doc->setHtml("<html><p>just a simple paragraph.</p>"
        "<table>"
          "<tr><td>Cell number 1</td><td>another cell</td><td></td><td>previous</br>is</br>empty</td></tr>"
          "<tr><td>row 2</td><td colspan=\"2\">foo bar</td><td>last cell</td></tr>"
          "<tr><td colspan=\"3\">row 3</td><td>a</td></tr>"
        "</table></html");
    QCOMPARE(cursor.position(), 101); // end of document
    cursor.setPosition(0);
    QCOMPARE(cursor.position(), 0);
    bool ok = cursor.movePosition(QTextCursor::EndOfLine);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 24);
    ok = cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.position(), 25); // cell 1
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 39); // another..
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 52); // empty
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 53); // last on row 1
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 69); // row 2
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 75);
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 83);
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 93); // row 3
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 99);
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(!ok);
    QCOMPARE(cursor.position(), 99); // didn't move.
    QVERIFY(cursor.currentTable());

    // same thing in reverse...
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 93);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 83);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 75);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 69);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 53);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 52);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 39);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 25);
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(!ok);
    QCOMPARE(cursor.position(), 25); // can't leave the table

    ok = cursor.movePosition(QTextCursor::NextRow);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 69);
    ok = cursor.movePosition(QTextCursor::NextRow);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 93);
    ok = cursor.movePosition(QTextCursor::NextRow);
    QVERIFY(!ok);
    QCOMPARE(cursor.position(), 93); // didn't move

    ok = cursor.movePosition(QTextCursor::PreviousRow);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 83); // last col in row 2
    ok = cursor.movePosition(QTextCursor::PreviousRow);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 53); // last col in row 1
    ok = cursor.movePosition(QTextCursor::PreviousRow);
    QVERIFY(!ok);
    QCOMPARE(cursor.position(), 53);

    // test usecase of jumping over a cell
    doc->clear();
    doc->setHtml("<html><table>tr><td rowspan=\"2\">a</td><td>b</td></tr><tr><td>c</td></tr></table></html>");
    cursor.setPosition(1); // a
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 3); // b
    ok = cursor.movePosition(QTextCursor::NextCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 5); // c
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 3); // b
    ok = cursor.movePosition(QTextCursor::PreviousCell);
    QVERIFY(ok);
    QCOMPARE(cursor.position(), 1); // a
}
#endif

void tst_QTextCursor::insertBlock()
{
    QTextBlockFormat fmt;
    fmt.setTopMargin(100);
    cursor.insertBlock(fmt);
    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.blockFormat(), fmt);
}

void tst_QTextCursor::insertWithBlockSeparator1()
{
    QString text = "Hello" + QString(QChar::ParagraphSeparator) + "World";

    cursor.insertText(text);

    cursor.movePosition(QTextCursor::PreviousBlock);
    QCOMPARE(cursor.position(), 0);

    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.position(), 6);
}

void tst_QTextCursor::insertWithBlockSeparator2()
{
    cursor.insertText(QString(QChar::ParagraphSeparator));
    QCOMPARE(cursor.position(), 1);
}

void tst_QTextCursor::insertWithBlockSeparator3()
{
    cursor.insertText(QString(QChar::ParagraphSeparator) + "Hi" + QString(QChar::ParagraphSeparator));
    QCOMPARE(cursor.position(), 4);
}

void tst_QTextCursor::insertWithBlockSeparator4()
{
    cursor.insertText(QString(QChar::ParagraphSeparator) + QString(QChar::ParagraphSeparator));
    QCOMPARE(cursor.position(), 2);
}

void tst_QTextCursor::clearObjectType1()
{
    cursor.insertImage("test.png");
    QVERIFY(cursor.charFormat().isValid());
    QVERIFY(cursor.charFormat().isImageFormat());
    cursor.insertText("Hey");
    QVERIFY(cursor.charFormat().isValid());
    QVERIFY(!cursor.charFormat().isImageFormat());
}

void tst_QTextCursor::clearObjectType2()
{
    cursor.insertImage("test.png");
    QVERIFY(cursor.charFormat().isValid());
    QVERIFY(cursor.charFormat().isImageFormat());
    cursor.insertBlock();
    QVERIFY(cursor.charFormat().isValid());
    QVERIFY(!cursor.charFormat().isImageFormat());
}

void tst_QTextCursor::clearObjectType3()
{
    // like clearObjectType2 but tests different insertBlock overload
    cursor.insertImage("test.png");
    QVERIFY(cursor.charFormat().isValid());
    QVERIFY(cursor.charFormat().isImageFormat());
    QTextBlockFormat bfmt;
    bfmt.setAlignment(Qt::AlignRight);
    cursor.insertBlock(bfmt);
    QVERIFY(cursor.charFormat().isValid());
    QVERIFY(!cursor.charFormat().isImageFormat());
}

void tst_QTextCursor::comparisonOperators1()
{
    cursor.insertText("Hello World");

    cursor.movePosition(QTextCursor::PreviousWord);

    QTextCursor startCursor = cursor;
    startCursor.movePosition(QTextCursor::Start);

    QVERIFY(startCursor < cursor);

    QTextCursor midCursor = startCursor;
    midCursor.movePosition(QTextCursor::NextWord);

    QVERIFY(midCursor <= cursor);
    QCOMPARE(midCursor, cursor);
    QVERIFY(midCursor >= cursor);

    QVERIFY(midCursor > startCursor);

    QVERIFY(midCursor != startCursor);
    QVERIFY(!(midCursor == startCursor));

    QTextCursor nullCursor;

    QVERIFY(!(startCursor < nullCursor));
    QVERIFY(!(nullCursor < nullCursor));
    QVERIFY(nullCursor < startCursor);

    QVERIFY(nullCursor <= startCursor);
    QVERIFY(!(startCursor <= nullCursor));

    QVERIFY(!(nullCursor >= startCursor));
    QVERIFY(startCursor >= nullCursor);

    QVERIFY(!(nullCursor > startCursor));
    QVERIFY(!(nullCursor > nullCursor));
    QVERIFY(startCursor > nullCursor);
}

void tst_QTextCursor::comparisonOperators2()
{
    QTextDocument doc1;
    QTextDocument doc2;

    QTextCursor cursor1(&doc1);
    QTextCursor cursor2(&doc2);

    QVERIFY(cursor1 != cursor2);
    QCOMPARE(cursor1, QTextCursor(&doc1));
}

void tst_QTextCursor::selection1()
{
    cursor.insertText("Hello World");

    cursor.setPosition(0);
    cursor.clearSelection();
    cursor.setPosition(4, QTextCursor::KeepAnchor);

    QCOMPARE(cursor.selectionStart(), 0);
    QCOMPARE(cursor.selectionEnd(), 4);
}

void tst_QTextCursor::dontCopyTableAttributes()
{
    /* when pressing 'enter' inside a cell it shouldn't
     * enlarge the table by adding another cell but just
     * extend the cell */
    QTextTable *table = cursor.insertTable(2, 2);
    QVERIFY(cursor == table->cellAt(0, 0).firstCursorPosition());
    cursor.insertBlock();
    QCOMPARE(table->columns(), 2);
}

void tst_QTextCursor::checkFrame1()
{
    QCOMPARE(cursor.position(), 0);
    QPointer<QTextFrame> frame = cursor.insertFrame(QTextFrameFormat());
    QVERIFY(frame != nullptr);

    QTextFrame *root = frame->parentFrame();
    QVERIFY(root != nullptr);

    QCOMPARE(frame->firstPosition(), 1);
    QCOMPARE(frame->lastPosition(), 1);
    QVERIFY(frame->parentFrame() != nullptr);
    QCOMPARE(root->childFrames().size(), 1);

    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 1);

    doc->undo();

    QVERIFY(!frame);
    QCOMPARE(root->childFrames().size(), 0);

    QCOMPARE(cursor.position(), 0);
    QCOMPARE(cursor.selectionStart(), 0);
    QCOMPARE(cursor.selectionEnd(), 0);

    doc->redo();

    frame = doc->frameAt(1);

    QVERIFY(frame);
    QCOMPARE(frame->firstPosition(), 1);
    QCOMPARE(frame->lastPosition(), 1);
    QVERIFY(frame->parentFrame() != 0);
    QCOMPARE(root->childFrames().size(), 1);

    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 1);

//     cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
//     QCOMPARE(cursor.position(), 2);
//     QCOMPARE(cursor.selectionStart(), 0);
//     QCOMPARE(cursor.selectionEnd(), 2);
}

void tst_QTextCursor::checkFrame2()
{
    QCOMPARE(cursor.position(), 0);
    cursor.insertText("A");
    QCOMPARE(cursor.position(), 1);
    cursor.movePosition(QTextCursor::Start, QTextCursor::KeepAnchor);

    QPointer<QTextFrame> frame = cursor.insertFrame(QTextFrameFormat());
    QTextFrame *root = frame->parentFrame();

    QCOMPARE(frame->firstPosition(), 1);
    QCOMPARE(frame->lastPosition(), 2);
    QVERIFY(frame->parentFrame() != 0);
    QCOMPARE(root->childFrames().size(), 1);

    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 2);

    doc->undo();

    QVERIFY(!frame);
    QCOMPARE(root->childFrames().size(), 0);

    QCOMPARE(cursor.position(), 0);
    QCOMPARE(cursor.selectionStart(), 0);
    QCOMPARE(cursor.selectionEnd(), 1);

    doc->redo();

    frame = doc->frameAt(1);

    QVERIFY(frame);
    QCOMPARE(frame->firstPosition(), 1);
    QCOMPARE(frame->lastPosition(), 2);
    QVERIFY(frame->parentFrame() != 0);
    QCOMPARE(root->childFrames().size(), 1);

    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 2);

    cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
    QCOMPARE(cursor.position(), 0);
    QCOMPARE(cursor.selectionStart(), 0);
    QCOMPARE(cursor.selectionEnd(), 3);
}

void tst_QTextCursor::insertBlockToUseCharFormat()
{
    QTextCharFormat fmt;
    fmt.setForeground(Qt::blue);
    cursor.insertText("Hello", fmt);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));

    cursor.insertBlock();
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));

    fmt.setForeground(Qt::red);
    cursor.insertText("Hello\nWorld", fmt);
    cursor.insertText("Blah");
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));

    // ### we might want a testcase for createTable, too, as it calls insertBlock, too,
    // and we might want to have the char format copied (the one that gets inserted
    // as table separators, that are undeletable)
}

void tst_QTextCursor::tableMovement()
{
    QCOMPARE(cursor.position(), 0);
    cursor.insertText("AA");
    QCOMPARE(cursor.position(), 2);
    cursor.movePosition(QTextCursor::Left);

    cursor.insertTable(3, 3);
    QCOMPARE(cursor.position(), 2);

    cursor.movePosition(QTextCursor::Down);
    QCOMPARE(cursor.position(), 5);

    cursor.movePosition(QTextCursor::Right);
    QCOMPARE(cursor.position(), 6);

    cursor.movePosition(QTextCursor::Up);
    QCOMPARE(cursor.position(), 3);

    cursor.movePosition(QTextCursor::Right);
    QCOMPARE(cursor.position(), 4);

    cursor.movePosition(QTextCursor::Right);
    QCOMPARE(cursor.position(), 5);

    cursor.movePosition(QTextCursor::Up);
    QCOMPARE(cursor.position(), 2);

    cursor.movePosition(QTextCursor::Up);
    QCOMPARE(cursor.position(), 0);

}

void tst_QTextCursor::selectionsInTable()
{
    QTextTable *table = cursor.insertTable(3, 3);
    table->cellAt(0, 0).firstCursorPosition().insertText("A a"); // first = 1
    table->cellAt(0, 1).firstCursorPosition().insertText("B b"); // first = 5
    table->cellAt(0, 2).firstCursorPosition().insertText("C c"); // first = 9
    table->cellAt(1, 0).firstCursorPosition().insertText("D d"); // first = 13
    table->cellAt(1, 1).firstCursorPosition().insertText("E e"); // first = 17
    table->cellAt(1, 2).firstCursorPosition().insertText("F f"); // first = 21
    table->cellAt(2, 0).firstCursorPosition().insertText("G g"); // first = 25
    table->cellAt(2, 1).firstCursorPosition().insertText("H h"); // first = 29
    table->cellAt(2, 2).firstCursorPosition().insertText("I i"); // first = 33

    cursor = table->cellAt(0, 0).lastCursorPosition();
    QCOMPARE(cursor.position(), 4);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor) == false);
    QCOMPARE(cursor.position(), 1);

    cursor = table->cellAt(1, 0).lastCursorPosition();
    QCOMPARE(cursor.position(), 16);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor) == false);
    QCOMPARE(cursor.position(), 13);

    cursor = table->cellAt(0, 2).firstCursorPosition();
    QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor) == false);

    cursor = table->cellAt(1, 2).firstCursorPosition();
    QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor));
    QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor) == false);

    // Next let's test selecting entire cells one at a time
    cursor = table->cellAt(0, 0).firstCursorPosition();
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 5);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 9);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 13);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 17);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 21);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 25);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 29);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 33);
    QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor) == false);

    // And now lets walk all the way back
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 29);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 25);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 21);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 17);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 13);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 9);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 5);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor));
    QCOMPARE(cursor.position(), 1);
    QVERIFY(cursor.movePosition(QTextCursor::PreviousCell, QTextCursor::KeepAnchor) == false);

    QTextCursor::MoveOperation leftMovements[5] = {
          QTextCursor::PreviousBlock
        , QTextCursor::PreviousCharacter
        , QTextCursor::PreviousWord
        , QTextCursor::Left
        , QTextCursor::WordLeft
    };

    QTextCursor::MoveOperation rightMovements[5] = {
        QTextCursor::NextBlock
        , QTextCursor::NextCharacter
        , QTextCursor::NextWord
        , QTextCursor::Right
        , QTextCursor::WordRight
    };

    for (int i = 0; i < 5; ++i) {
        QTextCursor::MoveOperation left = leftMovements[i];
        QTextCursor::MoveOperation right = rightMovements[i];

        // Lets walk circle around anchor placed at 1,1 using up, down, left and right
        cursor = table->cellAt(1, 1).firstCursorPosition();
        QCOMPARE(cursor.position(), 17);
        QVERIFY(cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 18); // First right should not jump more than one char
        QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 21); // Lets jump to the next cell
        QVERIFY(cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 33);
        QVERIFY(cursor.movePosition(left, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 29);
        QVERIFY(cursor.movePosition(left, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 25);
        QVERIFY(cursor.movePosition(QTextCursor::Up, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 13);
        QVERIFY(cursor.movePosition(QTextCursor::Up, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 1);
        QVERIFY(cursor.movePosition(right, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 5);
        QVERIFY(cursor.movePosition(right, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 9);
        QVERIFY(cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 21);

        // Lets walk to the side 2 cells and back, first right
        cursor = table->cellAt(0, 0).firstCursorPosition();
        QVERIFY(cursor.movePosition(QTextCursor::NextCell, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 5); // Lets jump to the next cell
        QVERIFY(cursor.movePosition(right, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 9);
        QVERIFY(cursor.movePosition(left, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 5);
        QVERIFY(cursor.movePosition(left, QTextCursor::KeepAnchor));
        QVERIFY(cursor.position() < 5);

        // Then left
        cursor = table->cellAt(0, 2).firstCursorPosition();
        QCOMPARE(cursor.position(), 9);
        QVERIFY(cursor.movePosition(left, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 5); // A single left should do
        QVERIFY(cursor.movePosition(left, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 1);
        QVERIFY(cursor.movePosition(right, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 5);
        QVERIFY(cursor.movePosition(right, QTextCursor::KeepAnchor));
        QCOMPARE(cursor.position(), 9);
    }
}

void tst_QTextCursor::selectedText()
{
    cursor.insertText("Hello World");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);

    QCOMPARE(cursor.selectedText(), QString("Hello World"));
}

void tst_QTextCursor::insertBlockShouldRemoveSelection()
{
    cursor.insertText("Hello World");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectedText(), QString("Hello"));

    cursor.insertBlock();

    QVERIFY(!cursor.hasSelection());
    QCOMPARE(doc->toPlainText().indexOf("Hello"), -1);
}

void tst_QTextCursor::insertBlockShouldRemoveSelection2()
{
    cursor.insertText("Hello World");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectedText(), QString("Hello"));

    QTextBlockFormat fmt = cursor.blockFormat();
    cursor.insertBlock(fmt);

    QVERIFY(!cursor.hasSelection());
    QCOMPARE(doc->toPlainText().indexOf("Hello"), -1);
}

void tst_QTextCursor::mergeCellShouldUpdateSelection()
{
    QTextTable *table = cursor.insertTable(4, 4);
    cursor.setPosition(table->cellAt(0, 0).firstPosition());
    cursor.setPosition(table->cellAt(3, 0).firstPosition(), QTextCursor::KeepAnchor); // aka bottom left
    int firstRow, numRows, firstColumn, numColumns;
    cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
    QCOMPARE(firstRow, 0);
    QCOMPARE(numRows, 4);
    QCOMPARE(firstColumn, 0);
    QCOMPARE(numColumns, 1);

    table->removeColumns(firstColumn, numColumns);

    QCOMPARE(cursor.anchor(), table->cellAt(0, 0).firstPosition());
    QCOMPARE(cursor.position(), table->cellAt(0, 0).firstPosition());
    QCOMPARE(cursor.position(), cursor.anchor()); // empty. I don't really care where it ends up.

    // prepare for another test with multiple cursors.
    // note we have a 4 rows, 3 cols table now.
    cursor.setPosition(table->cellAt(0, 0).firstPosition());
    cursor.setPosition(table->cellAt(0, 2).firstPosition(), QTextCursor::KeepAnchor);

    // now create a selection of a whole row.
    QTextCursor c2 = table->cellAt(2, 0).firstCursorPosition();
    c2.setPosition(table->cellAt(2, 2).firstPosition(), QTextCursor::KeepAnchor);

    // just for good measure, another one for a block of cells.
    QTextCursor c3 = table->cellAt(2, 1).firstCursorPosition();
    c3.setPosition(table->cellAt(3, 2).firstPosition(), QTextCursor::KeepAnchor);

    table->removeRows(2, 1);

    QCOMPARE(cursor.anchor(), table->cellAt(0, 0).firstPosition());
    QCOMPARE(cursor.position(), table->cellAt(0, 2).firstPosition());

    QCOMPARE(c2.position(), c2.anchor()); // empty. I don't really care where it ends up.

    QCOMPARE(c3.anchor(), table->cellAt(2, 1).firstPosition());
    QCOMPARE(c3.position(), table->cellAt(2, 2).firstPosition());


    // prepare for another test where we remove a column
    // note we have a 3 rows, 3 cols table now.
    cursor.setPosition(table->cellAt(0, 0).firstPosition());
    cursor.setPosition(table->cellAt(2, 1).firstPosition(), QTextCursor::KeepAnchor);

    c2.setPosition(table->cellAt(0, 1).firstPosition());
    c2.setPosition(table->cellAt(2, 2).firstPosition(), QTextCursor::KeepAnchor);

    table->removeColumns(1, 1);

    QCOMPARE(cursor.anchor(), table->cellAt(0, 0).firstPosition());
    QCOMPARE(cursor.position(), table->cellAt(2, 0).firstPosition());

    QCOMPARE(c2.anchor(), table->cellAt(0, 1).firstPosition());
    QCOMPARE(c2.position(), table->cellAt(2, 1).firstPosition());

    // test for illegal cursor positions.
    // note we have a 3 rows, 2 cols table now.
    cursor.setPosition(table->cellAt(2, 0).firstPosition());
    cursor.setPosition(table->cellAt(2, 1).firstPosition(), QTextCursor::KeepAnchor);

    c2.setPosition(table->cellAt(0, 0).firstPosition());
    c2.setPosition(table->cellAt(2, 1).firstPosition(), QTextCursor::KeepAnchor);

    c3.setPosition(table->cellAt(2, 1).firstPosition());

    table->removeRows(2, 1);

    QCOMPARE(cursor.anchor(), table->cellAt(1, 1).lastPosition()+1);
    QCOMPARE(cursor.position(), cursor.anchor());

    QCOMPARE(c2.anchor(), table->cellAt(0, 0).firstPosition());
    QCOMPARE(c2.position(), table->cellAt(1, 1).firstPosition());

    QCOMPARE(c3.anchor(), table->cellAt(1, 1).firstPosition());
    QCOMPARE(c3.position(), table->cellAt(1, 1).firstPosition());
}

void tst_QTextCursor::joinPreviousEditBlock()
{
    cursor.beginEditBlock();
    cursor.insertText("Hello");
    cursor.insertText("World");
    cursor.endEditBlock();
    QVERIFY(doc->toPlainText().startsWith("HelloWorld"));

    cursor.joinPreviousEditBlock();
    cursor.insertText("Hey");
    cursor.endEditBlock();
    QVERIFY(doc->toPlainText().startsWith("HelloWorldHey"));

    doc->undo();
    QVERIFY(!doc->toPlainText().contains("HelloWorldHey"));
}

void tst_QTextCursor::setBlockFormatInTable()
{
    // someone reported this on qt4-preview-feedback
    QTextBlockFormat fmt;
    fmt.setBackground(Qt::blue);
    cursor.setBlockFormat(fmt);

    QTextTable *table = cursor.insertTable(2, 2);
    cursor = table->cellAt(0, 0).firstCursorPosition();
    fmt.setBackground(Qt::red);
    cursor.setBlockFormat(fmt);

    cursor.movePosition(QTextCursor::Start);
    QCOMPARE(cursor.blockFormat().background().color(), QColor(Qt::blue));
}

void tst_QTextCursor::blockCharFormat2()
{
    QTextCharFormat fmt;
    fmt.setForeground(Qt::green);
    cursor.mergeBlockCharFormat(fmt);

    fmt.setForeground(Qt::red);

    cursor.insertText("Test", fmt);
    cursor.movePosition(QTextCursor::Start);
    cursor.insertText("Red");
    cursor.movePosition(QTextCursor::PreviousCharacter);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
}

void tst_QTextCursor::blockCharFormat3()
{
    QVERIFY(cursor.atBlockStart());
    QVERIFY(cursor.atBlockEnd());
    QVERIFY(cursor.atStart());

    QTextCharFormat fmt;
    fmt.setForeground(Qt::green);
    cursor.setBlockCharFormat(fmt);
    cursor.insertText("Test");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextCharacter);
    const QColor red(Qt::red);
    const QColor green(Qt::green);
    QCOMPARE(cursor.charFormat().foreground().color(), green);

    cursor.movePosition(QTextCursor::Start);
    QCOMPARE(cursor.charFormat().foreground().color(), green);

    fmt.setForeground(Qt::red);
    cursor.setBlockCharFormat(fmt);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), red);

    cursor.movePosition(QTextCursor::End);
    cursor.movePosition(QTextCursor::Start);
    QCOMPARE(cursor.charFormat().foreground().color(), green);

    cursor.insertText("Test");
    QCOMPARE(cursor.charFormat().foreground().color(), green);

    cursor.select(QTextCursor::Document);
    cursor.removeSelectedText();
    QVERIFY(cursor.atBlockStart());
    QVERIFY(cursor.atBlockEnd());
    QVERIFY(cursor.atStart());

    cursor.insertText("Test");
    QCOMPARE(cursor.charFormat().foreground().color(), red);
}

void tst_QTextCursor::blockCharFormat()
{
    QTextCharFormat fmt;
    fmt.setForeground(Qt::blue);
    cursor.insertBlock(QTextBlockFormat(), fmt);
    cursor.insertText("Hm");

    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::blue));

    fmt.setForeground(Qt::red);

    cursor.setBlockCharFormat(fmt);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::red));
}

void tst_QTextCursor::blockCharFormatOnSelection()
{
    QTextCharFormat fmt;
    fmt.setForeground(Qt::blue);
    cursor.insertBlock(QTextBlockFormat(), fmt);

    fmt.setForeground(Qt::green);
    cursor.insertText("Hm", fmt);

    fmt.setForeground(Qt::red);
    cursor.insertBlock(QTextBlockFormat(), fmt);
    cursor.insertText("Ah");

    fmt.setForeground(Qt::white);
    cursor.insertBlock(QTextBlockFormat(), fmt);
    cursor.insertText("bleh");

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::blue));
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::red));
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::white));

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextBlock);
    cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);

    fmt.setForeground(Qt::cyan);
    cursor.setBlockCharFormat(fmt);

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::cyan));

    cursor.movePosition(QTextCursor::Right);
    cursor.movePosition(QTextCursor::Right);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::green));

    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::cyan));

    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockCharFormat().foreground().color(), QColor(Qt::white));
}

void tst_QTextCursor::anchorInitialized1()
{
    cursor.insertBlock();
    cursor = QTextCursor(cursor.block());
    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.anchor(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 1);
}

void tst_QTextCursor::anchorInitialized2()
{
    cursor.insertBlock();
    cursor = QTextCursorPrivate::fromPosition(const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(cursor.block())), 1);
    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.anchor(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 1);
}

void tst_QTextCursor::anchorInitialized3()
{
    QTextFrame *frame = cursor.insertFrame(QTextFrameFormat());
    cursor = QTextCursor(frame);
    QCOMPARE(cursor.position(), 1);
    QCOMPARE(cursor.anchor(), 1);
    QCOMPARE(cursor.selectionStart(), 1);
    QCOMPARE(cursor.selectionEnd(), 1);
}

void tst_QTextCursor::selectWord()
{
    cursor.insertText("first second     third");
    cursor.insertBlock();
    cursor.insertText("words in second paragraph");

    cursor.setPosition(9);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectionStart(), 6);
    QCOMPARE(cursor.selectionEnd(), 12);

    cursor.setPosition(5);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectionStart(), 0);
    QCOMPARE(cursor.selectionEnd(), 5);

    cursor.setPosition(6);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectionStart(), 6);
    QCOMPARE(cursor.selectionEnd(), 12);

    cursor.setPosition(14);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectionStart(), 6);
    QCOMPARE(cursor.selectionEnd(), 12);

    cursor.movePosition(QTextCursor::Start);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectionStart(), 0);
    QCOMPARE(cursor.selectionEnd(), 5);

    cursor.movePosition(QTextCursor::EndOfBlock);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selectionStart(), 17);
    QCOMPARE(cursor.selectionEnd(), 22);
}

void tst_QTextCursor::selectWordWithSeparators_data()
{
    QTest::addColumn<QString>("text");
    QTest::addColumn<int>("initialPosition");
    QTest::addColumn<QString>("expectedSelectedText");

    QTest::newRow("dereference") << QString::fromLatin1("foo->bar()") << 1 << QString::fromLatin1("foo");
    QTest::newRow("funcsignature") << QString::fromLatin1("bar(int x);") << 1 << QString::fromLatin1("bar");
    QTest::newRow("def") << QString::fromLatin1("foo *f;") << 1 << QString::fromLatin1("foo");
}

void tst_QTextCursor::selectWordWithSeparators()
{
    QFETCH(QString, text);
    QFETCH(int, initialPosition);
    QFETCH(QString, expectedSelectedText);

    cursor.insertText(text);
    cursor.setPosition(initialPosition);
    cursor.select(QTextCursor::WordUnderCursor);

    QCOMPARE(cursor.selectedText(), expectedSelectedText);
}

void tst_QTextCursor::startOfWord()
{
    cursor.insertText("first     second");
    cursor.setPosition(7);
    cursor.movePosition(QTextCursor::StartOfWord);
    QCOMPARE(cursor.position(), 0);
}

void tst_QTextCursor::selectBlock()
{
    cursor.insertText("foobar");
    QTextBlockFormat blockFmt;
    blockFmt.setAlignment(Qt::AlignHCenter);
    cursor.insertBlock(blockFmt);
    cursor.insertText("blah");
    cursor.insertBlock(QTextBlockFormat());

    cursor.movePosition(QTextCursor::PreviousBlock);
    QCOMPARE(cursor.block().text(), QString("blah"));

    cursor.select(QTextCursor::BlockUnderCursor);
    QVERIFY(cursor.hasSelection());

    QTextDocumentFragment fragment(cursor);
    doc->clear();
    cursor.insertFragment(fragment);
    QCOMPARE(blockCount(), 2);

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.blockFormat().alignment(), Qt::AlignHCenter);
    QCOMPARE(cursor.block().text(), QString("blah"));
}

void tst_QTextCursor::selectVisually()
{
    cursor.insertText("Foo\nlong line which is probably going to be cut in two when shown in a widget\nparagraph 3\n");

    cursor.setPosition(6); // somewhere in the long paragraph.
    cursor.select(QTextCursor::LineUnderCursor);
    // since we are not yet laid-out, we expect the whole paragraph to be selected.
    QCOMPARE(cursor.position(), 77);
    QCOMPARE(cursor.anchor(), 4);
}

void tst_QTextCursor::insertText()
{
    QString txt = "Foo\nBar\r\nMeep";
    txt += QChar::LineSeparator;
    txt += "Baz";
    txt += QChar::ParagraphSeparator;
    txt += "yoyodyne";
    cursor.insertText(txt);
    QCOMPARE(blockCount(), 4);
    cursor.movePosition(QTextCursor::Start);
    QCOMPARE(cursor.block().text(), QString("Foo"));
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.block().text(), QString("Bar"));
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.block().text(), QString(QString("Meep") + QChar(QChar::LineSeparator) + QString("Baz")));
    cursor.movePosition(QTextCursor::NextBlock);
    QCOMPARE(cursor.block().text(), QString("yoyodyne"));
}


#ifndef QT_NO_TEXTHTMLPARSER

void tst_QTextCursor::insertHtml_data()
{
    QTest::addColumn<QString>("initialText");
    QTest::addColumn<int>("expectedInitialBlockCount");
    QTest::addColumn<bool>("insertBlock");
    QTest::addColumn<bool>("insertAsPlainText");
    QTest::addColumn<int>("insertPosition");
    QTest::addColumn<QString>("insertText");
    QTest::addColumn<QString>("expectedSelText");
    QTest::addColumn<QString>("expectedText");
    QTest::addColumn<QString>("expectedMarkdown");

    const QString htmlHeadingString("<h1>Hello World</h1>");

    QTest::newRow("insert as html at end of heading")
            << htmlHeadingString << 1
            << false << false << 11 << QString("Other\ntext")
            << QString("Hello WorldOther text")
            << QString("Hello WorldOther text")
            << QString("# Hello WorldOther text\n\n");

    QTest::newRow("insert as html in new block at end of heading")
            << htmlHeadingString << 1
            << false << true << 11 << QString("Other\ntext")
            << QString("Hello WorldOther\u2029text")
            << QString("Hello WorldOther\ntext")
            << QString("# Hello WorldOther\n\n# text\n\n");

    QTest::newRow("insert as html in middle of heading")
            << htmlHeadingString << 1
            << false << false << 6 << QString("\n\nOther\ntext\n\n")
            << QString("Hello Other text World")
            << QString("Hello Other text World")
            << QString("# Hello Other text World\n\n");

    QTest::newRow("insert as text at end of heading")
            << htmlHeadingString << 1
            << true << false << 11 << QString("\n\nOther\ntext")
            << QString("Hello World\u2029Other text")
            << QString("Hello World\nOther text")
            << QString("# Hello World\n\nOther text\n\n");

    QTest::newRow("insert as text in new block at end of heading")
            << htmlHeadingString << 1
            << true << true << 11 << QString("\n\nOther\ntext")
            << QString("Hello World\u2029\u2029\u2029Other\u2029text")
            << QString("Hello World\n\n\nOther\ntext")
            << QString("# Hello World\n\n**Other**\n\n**text**\n\n");

    QTest::newRow("insert as text in middle of heading")
            << htmlHeadingString << 1
            << true << false << 6 << QString("Other\ntext")
            << QString("Hello \u2029Other textWorld")
            << QString("Hello \nOther textWorld")
            << QString("# Hello \n\nOther text**World**\n\n");
}

void tst_QTextCursor::insertHtml()
{
    QFETCH(QString, initialText);
    QFETCH(int, expectedInitialBlockCount);
    QFETCH(bool, insertBlock);
    QFETCH(bool, insertAsPlainText);
    QFETCH(int, insertPosition);
    QFETCH(QString, insertText);
    QFETCH(QString, expectedSelText);
    QFETCH(QString, expectedText);
    QFETCH(QString, expectedMarkdown);

    cursor.insertHtml(initialText);
    QCOMPARE(blockCount(), expectedInitialBlockCount);
    cursor.setPosition(insertPosition);
    if (insertBlock)
        cursor.insertBlock(QTextBlockFormat());
    qCDebug(lcTests) << "pos" << cursor.position() << "block" << cursor.blockNumber()
                     << "heading" << cursor.blockFormat().headingLevel();
    if (insertAsPlainText)
        cursor.insertText(insertText);
    else
        cursor.insertHtml(insertText);
    cursor.select(QTextCursor::Document);
    qCDebug(lcTests) << "sel text after insertion" << cursor.selectedText();
    qCDebug(lcTests) << "text after insertion" << cursor.document()->toPlainText();
    qCDebug(lcTests) << "html after insertion" << cursor.document()->toHtml();
#if QT_CONFIG(textmarkdownwriter)
    qCDebug(lcTests) << "markdown after insertion" << cursor.document()->toMarkdown();
#endif
    QCOMPARE(cursor.selectedText(), expectedSelText);
    QCOMPARE(cursor.document()->toPlainText(), expectedText);
    if (auto defaultFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFontInfo(defaultFont).fixedPitch()) {
        qWarning() << defaultFont << "is QFontDatabase::GeneralFont, and is fixedPitch";
        QSKIP("cannot reliably distinguish normal and monospace markdown spans on this system (QTBUG-103484)");
    }
#if QT_CONFIG(textmarkdownwriter)
    QCOMPARE(cursor.document()->toMarkdown(), expectedMarkdown);
#endif
}

#endif // QT_NO_TEXTHTMLPARSER

#if QT_CONFIG(textmarkdownreader)

void tst_QTextCursor::insertMarkdown_data()
{
    QTest::addColumn<QString>("initialText");
    QTest::addColumn<int>("expectedInitialBlockCount");
    QTest::addColumn<int>("insertPosition");
    QTest::addColumn<QString>("insertText");
    QTest::addColumn<QString>("expectedSelText");
    QTest::addColumn<QString>("expectedText");
    QTest::addColumn<QString>("expectedMarkdown");

    QTest::newRow("bold fragment in italic span")
            << "someone said *hello world*" << 1
            << 19 << QString(" **crazy** ")
            << QString("someone said hello crazyworld")
            << QString("someone said hello crazyworld")
            << QString("someone said *hello ***crazy***world*\n\n"); // explicit B+I: not necessary but OK

    QTest::newRow("list in a paragraph")
            << "hello list with 3 items" << 1
            << 10 << QString("1. one\n2. two\n")
            << QString("hello list\u2029one\u2029two\u2029 with 3 items")
            << QString("hello list\none\ntwo\n with 3 items")
            << QString("hello list\n\n1.  one\n2.  two\n3.   with 3 items\n");

    QTest::newRow("list in a list")
            << "1) bread\n2) milk\n" << 2
            << 6 << QString("0) eggs\n1) maple syrup\n")
            << QString("bread\u2029eggs\u2029maple syrup\u2029milk")
            << QString("bread\neggs\nmaple syrup\nmilk")
            << QString("1)  bread\n2)  eggs\n0)  maple syrup\n1)  milk\n");
    // Renumbering would happen if we re-read the whole document.
    // Currently insertion only uses the new list format after a paragraph separator.
    // For that reason "bread" and "eggs" use the original list format, while "maple syrup" and
    // "milk" use the format from the just inserted list.

    QTest::newRow("list after a list")
            << "1) bread\n2) milk\n\n" << 2
            << 13 << QString("\n0) eggs\n1) maple syrup\n")
            << QString("bread\u2029milk\u2029eggs\u2029maple syrup")
            << QString("bread\nmilk\neggs\nmaple syrup")
            << QString("1)  bread\n2)  milk\n3)  eggs\n0)  maple syrup\n");
    // Same behavior as above. "eggs" uses the original list format, but "maple syrup" uses the
    // format of the inserted list, which means "maple syrup" now has a start of 0.

    const QString markdownHeadingString("# Hello\nWorld\n");

    QTest::newRow("markdown heading at end of markdown heading")
            << markdownHeadingString << 2
            << 11 << QString("\n\n## Other text")
            << QString("Hello\u2029World\u2029Other text")
            << QString("Hello\nWorld\nOther text")
            << QString("# Hello\n\nWorld\n\n## Other text\n\n");

    QTest::newRow("markdown heading into middle of markdown heading")
            << markdownHeadingString << 2
            << 6 << QString("## Other\ntext\n\n")
            << QString("Hello\u2029Other\u2029text\u2029World")
            << QString("Hello\nOther\ntext\nWorld")
            << QString("# Hello\n\n**Other**\n\ntext\n\nWorld\n\n");

    QTest::newRow("markdown heading without trailing newline into middle of markdown heading")
            << markdownHeadingString << 2
            << 6 << QString("## Other\ntext")
            << QString("Hello\u2029Other\u2029textWorld")
            << QString("Hello\nOther\ntextWorld")
            << QString("# Hello\n\n**Other**\n\ntextWorld\n\n");

    QTest::newRow("text into middle of markdown heading after newline")
            << markdownHeadingString << 2
            << 6 << QString("Other ")
            << QString("Hello\u2029OtherWorld")
            << QString("Hello\nOtherWorld")
            << QString("# Hello\n\nOtherWorld\n\n");

    QTest::newRow("text into middle of markdown heading before newline")
            << markdownHeadingString << 2
            << 5 << QString(" Other ")
            << QString("HelloOther\u2029World")
            << QString("HelloOther\nWorld")
            << QString("# HelloOther\n\nWorld\n\n");
}

void tst_QTextCursor::insertMarkdown()
{
    QFETCH(QString, initialText);
    QFETCH(int, expectedInitialBlockCount);
    QFETCH(int, insertPosition);
    QFETCH(QString, insertText);
    QFETCH(QString, expectedSelText);
    QFETCH(QString, expectedText);
    QFETCH(QString, expectedMarkdown);

    cursor.insertMarkdown(initialText);
    QCOMPARE(blockCount(), expectedInitialBlockCount);
    cursor.setPosition(insertPosition);
    qCDebug(lcTests) << "pos" << cursor.position() << "block" << cursor.blockNumber()
                     << "heading" << cursor.blockFormat().headingLevel();
    cursor.insertMarkdown(insertText);
    cursor.select(QTextCursor::Document);
    qCDebug(lcTests) << "sel text after insertion" << cursor.selectedText();
    qCDebug(lcTests) << "text after insertion" << cursor.document()->toPlainText();
    qCDebug(lcTests) << "html after insertion" << cursor.document()->toHtml();
    qCDebug(lcTests) << "markdown after insertion" << cursor.document()->toMarkdown();
    QCOMPARE(cursor.selectedText(), expectedSelText);
    QCOMPARE(cursor.document()->toPlainText(), expectedText);
    if (auto defaultFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFontInfo(defaultFont).fixedPitch()) {
        qWarning() << defaultFont << "is QFontDatabase::GeneralFont, and is fixedPitch";
        QSKIP("cannot reliably distinguish normal and monospace markdown spans on this system (QTBUG-103484)");
    }
    QCOMPARE(cursor.document()->toMarkdown(), expectedMarkdown);
}

#endif // textmarkdownreader

void tst_QTextCursor::insertFragmentShouldUseCurrentCharFormat()
{
    QTextDocumentFragment fragment = QTextDocumentFragment::fromPlainText("Hello World");
    QTextCharFormat fmt;
    fmt.setFontUnderline(true);

    cursor.clearSelection();
    cursor.setCharFormat(fmt);
    cursor.insertFragment(fragment);
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.charFormat(), fmt);
}

int tst_QTextCursor::blockCount()
{
    int cnt = 0;
    for (QTextBlock blk = doc->begin(); blk.isValid(); blk = blk.next())
        ++cnt;
    return cnt;
}

void tst_QTextCursor::endOfLine()
{
    doc->setPageSize(QSizeF(100000, INT_MAX));

    QString text("First Line    \nSecond Line  ");
    text.replace(QLatin1Char('\n'), QChar(QChar::LineSeparator));
    cursor.insertText(text);

    // ensure layouted
    doc->documentLayout()->documentSize();

    cursor.movePosition(QTextCursor::Start);

    QCOMPARE(cursor.block().layout()->lineCount(), 2);

    cursor.movePosition(QTextCursor::EndOfLine);
    QCOMPARE(cursor.position(), 14);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.position(), 15);
    cursor.movePosition(QTextCursor::EndOfLine);
    QCOMPARE(cursor.position(), 28);
}

class CursorListener : public QObject
{
    Q_OBJECT
public:
    CursorListener(QTextCursor *_cursor) : lastRecordedPosition(-1), lastRecordedAnchor(-1), recordingCount(0), cursor(_cursor) {}

    int lastRecordedPosition;
    int lastRecordedAnchor;
    int recordingCount;

public slots:
    void recordCursorPosition()
    {
        lastRecordedPosition = cursor->position();
        lastRecordedAnchor = cursor->anchor();
        ++recordingCount;
    }

    void selectAllContents()
    {
        // Only test the first time
        if (!recordingCount) {
            recordingCount++;
            cursor->select(QTextCursor::Document);
            lastRecordedPosition = cursor->position();
            lastRecordedAnchor = cursor->anchor();
        }
    }

private:
    QTextCursor *cursor;
};

void tst_QTextCursor::editBlocksDuringRemove()
{
    CursorListener listener(&cursor);

    cursor.insertText("Hello World");
    cursor.movePosition(QTextCursor::Start, QTextCursor::KeepAnchor);
    QCOMPARE(cursor.selectedText(), QString("Hello World"));

    connect(doc, SIGNAL(contentsChanged()), &listener, SLOT(recordCursorPosition()));
    listener.recordingCount = 0;
    cursor.deleteChar();

    QCOMPARE(listener.recordingCount, 1);
    QCOMPARE(listener.lastRecordedPosition, 0);
    QCOMPARE(listener.lastRecordedAnchor, 0);

    QVERIFY(doc->toPlainText().isEmpty());
}

void tst_QTextCursor::selectAllDuringRemove()
{
    CursorListener listener(&cursor);

    cursor.insertText("Hello World");
    cursor.movePosition(QTextCursor::End);

    connect(doc, SIGNAL(contentsChanged()), &listener, SLOT(selectAllContents()));
    listener.recordingCount = 0;
    QTextCursor localCursor = cursor;
    localCursor.deletePreviousChar();

    QCOMPARE(listener.lastRecordedPosition, 10);
    QCOMPARE(listener.lastRecordedAnchor, 0);
}

void tst_QTextCursor::update_data()
{
    QTest::addColumn<QString>("text");
    QTest::addColumn<int>("position");
    QTest::addColumn<int>("anchor");
    QTest::addColumn<int>("modifyPosition");
    QTest::addColumn<int>("modifyAnchor");
    QTest::addColumn<QString>("insertText");
    QTest::addColumn<int>("expectedPosition");
    QTest::addColumn<int>("expectedAnchor");

    QString text("Hello big world");
    int charsToDelete = 3;
    QTest::newRow("removeInsideSelection")
        << text
        << /*position*/ 0
        << /*anchor*/ int(text.size())
        // delete 'big'
        << 6
        << 6 + charsToDelete
        << QString() // don't insert anything, just remove
        << /*expectedPosition*/ 0
        << /*expectedAnchor*/ int(text.size() - charsToDelete)
        ;

    text = "Hello big world";
    charsToDelete = 3;
    QTest::newRow("removeInsideSelectionWithSwappedAnchorAndPosition")
        << text
        << /*position*/ int(text.size())
        << /*anchor*/ 0
        // delete 'big'
        << 6
        << 6 + charsToDelete
        << QString() // don't insert anything, just remove
        << /*expectedPosition*/ int(text.size() - charsToDelete)
        << /*expectedAnchor*/ 0
        ;


    text = "Hello big world";
    charsToDelete = 3;
    QString textToInsert("small");
    QTest::newRow("replaceInsideSelection")
        << text
        << /*position*/ 0
        << /*anchor*/ int(text.size())
        // delete 'big' ...
        << 6
        << 6 + charsToDelete
        << textToInsert // ... and replace 'big' with 'small'
        << /*expectedPosition*/ 0
        << /*expectedAnchor*/ int(text.size() - charsToDelete + textToInsert.size())
        ;

    text = "Hello big world";
    charsToDelete = 3;
    textToInsert = "small";
    QTest::newRow("replaceInsideSelectionWithSwappedAnchorAndPosition")
        << text
        << /*position*/ int(text.size())
        << /*anchor*/ 0
        // delete 'big' ...
        << 6
        << 6 + charsToDelete
        << textToInsert // ... and replace 'big' with 'small'
        << /*expectedPosition*/ int(text.size() - charsToDelete + textToInsert.size())
        << /*expectedAnchor*/ 0
        ;


    text = "Hello big world";
    charsToDelete = 3;
    QTest::newRow("removeBeforeSelection")
        << text
        << /*position*/ int(text.size() - 5)
        << /*anchor*/ int(text.size())
        // delete 'big'
        << 6
        << 6 + charsToDelete
        << QString() // don't insert anything, just remove
        << /*expectedPosition*/ int(text.size() - 5 - charsToDelete)
        << /*expectedAnchor*/ int(text.size() - charsToDelete)
        ;

    text = "Hello big world";
    charsToDelete = 3;
    QTest::newRow("removeAfterSelection")
        << text
        << /*position*/ 0
        << /*anchor*/ 4
        // delete 'big'
        << 6
        << 6 + charsToDelete
        << QString() // don't insert anything, just remove
        << /*expectedPosition*/ 0
        << /*expectedAnchor*/ 4
        ;

}

void tst_QTextCursor::update()
{
    QFETCH(QString, text);

    doc->setPlainText(text);

    QFETCH(int, position);
    QFETCH(int, anchor);

    cursor.setPosition(anchor);
    cursor.setPosition(position, QTextCursor::KeepAnchor);

    QCOMPARE(cursor.position(), position);
    QCOMPARE(cursor.anchor(), anchor);

    QFETCH(int, modifyPosition);
    QFETCH(int, modifyAnchor);

    QTextCursor modifyCursor = cursor;
    modifyCursor.setPosition(modifyAnchor);
    modifyCursor.setPosition(modifyPosition, QTextCursor::KeepAnchor);

    QCOMPARE(modifyCursor.position(), modifyPosition);
    QCOMPARE(modifyCursor.anchor(), modifyAnchor);

    QFETCH(QString, insertText);
    modifyCursor.insertText(insertText);

    QFETCH(int, expectedPosition);
    QFETCH(int, expectedAnchor);

    QCOMPARE(cursor.position(), expectedPosition);
    QCOMPARE(cursor.anchor(), expectedAnchor);
}

void tst_QTextCursor::disallowSettingObjectIndicesOnCharFormats()
{
    QTextCharFormat fmt;
    fmt.setObjectIndex(42);
    cursor.insertText("Hey", fmt);
    QCOMPARE(cursor.charFormat().objectIndex(), -1);

    cursor.select(QTextCursor::Document);
    cursor.mergeCharFormat(fmt);
    QCOMPARE(doc->begin().begin().fragment().charFormat().objectIndex(), -1);

    cursor.select(QTextCursor::Document);
    cursor.setCharFormat(fmt);
    QCOMPARE(doc->begin().begin().fragment().charFormat().objectIndex(), -1);

    cursor.setBlockCharFormat(fmt);
    QCOMPARE(cursor.blockCharFormat().objectIndex(), -1);

    cursor.movePosition(QTextCursor::End);
    cursor.insertBlock(QTextBlockFormat(), fmt);
    QCOMPARE(cursor.blockCharFormat().objectIndex(), -1);

    doc->clear();

    QTextTable *table = cursor.insertTable(1, 1);
    cursor.select(QTextCursor::Document);
    cursor.setCharFormat(fmt);

    cursor = table->cellAt(0, 0).firstCursorPosition();
    QVERIFY(!cursor.isNull());
    QCOMPARE(cursor.blockCharFormat().objectIndex(), table->objectIndex());
}

void tst_QTextCursor::blockAndColumnNumber()
{
    QCOMPARE(QTextCursor().columnNumber(), 0);
    QCOMPARE(QTextCursor().blockNumber(), 0);

    QCOMPARE(cursor.columnNumber(), 0);
    QCOMPARE(cursor.blockNumber(), 0);
    cursor.insertText("Hello");
    QCOMPARE(cursor.columnNumber(), 5);
    QCOMPARE(cursor.blockNumber(), 0);

    cursor.insertBlock();
    QCOMPARE(cursor.columnNumber(), 0);
    QCOMPARE(cursor.blockNumber(), 1);
    cursor.insertText("Blah");
    QCOMPARE(cursor.blockNumber(), 1);

    // trigger a layout
    doc->documentLayout();

    cursor.insertBlock();
    QCOMPARE(cursor.columnNumber(), 0);
    QCOMPARE(cursor.blockNumber(), 2);
    cursor.insertText("Test");
    QCOMPARE(cursor.columnNumber(), 4);
    QCOMPARE(cursor.blockNumber(), 2);
    cursor.insertText(QString(QChar(QChar::LineSeparator)));
    QCOMPARE(cursor.columnNumber(), 0);
    QCOMPARE(cursor.blockNumber(), 2);
    cursor.insertText("A");
    QCOMPARE(cursor.columnNumber(), 1);
    QCOMPARE(cursor.blockNumber(), 2);
}

void tst_QTextCursor::movePositionEndOfLine()
{
    cursor.insertText("blah\nblah\n");
    // Select part of the second line ("la")
    cursor.setPosition(6);
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2);
    QCOMPARE(cursor.selectedText(), QLatin1String("la"));

    // trigger a layout
    doc->documentLayout();

    // Remove "la" and append "something" to the end in one undo operation
    cursor.beginEditBlock();
    cursor.removeSelectedText();
    QTextCursor c2(doc);
    c2.setPosition(7);
    c2.insertText("foo"); // append to doc without touching the cursor.

    QCOMPARE(cursor.position(), 6);
    cursor.movePosition(QTextCursor::EndOfLine); // in an edit block visual movement is moved to the end of the paragraph
    QCOMPARE(cursor.position(), 10);
    cursor.endEditBlock();
}

void tst_QTextCursor::clearCells()
{
    QTextTable *table = cursor.insertTable(3, 5);
    cursor.setPosition(table->cellAt(0,0).firstPosition()); // select cell 1 and cell 2
    cursor.setPosition(table->cellAt(0,1).firstPosition(), QTextCursor::KeepAnchor);
    cursor.deleteChar(); // should clear the cells, and not crash ;)
}

void tst_QTextCursor::task244408_wordUnderCursor_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<QString>("expected");
    QTest::newRow("trailingSpace") << QString::fromLatin1("foo ") << QString::fromLatin1("");
    QTest::newRow("noTrailingSpace") << QString::fromLatin1("foo") << QString::fromLatin1("foo");
}

void tst_QTextCursor::task244408_wordUnderCursor()
{
    QFETCH(QString, input);
    QFETCH(QString, expected);
    cursor.insertText(input);
    cursor.movePosition(QTextCursor::End);
    cursor.select(QTextCursor::WordUnderCursor);
    QCOMPARE(cursor.selectedText(), expected);
}

void tst_QTextCursor::adjustCursorsOnInsert()
{
    cursor.insertText("Some text before ");
    int posBefore = cursor.position();
    cursor.insertText("selected text");
    int posAfter = cursor.position();
    cursor.insertText(" some text afterwards");

    QTextCursor selection = cursor;
    selection.setPosition(posBefore);
    selection.setPosition(posAfter, QTextCursor::KeepAnchor);

    cursor.setPosition(posBefore-1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.anchor(), posBefore+1);
    QCOMPARE(selection.position(), posAfter+1);
    doc->undo();

    cursor.setPosition(posBefore);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.anchor(), posBefore+1);
    QCOMPARE(selection.position(), posAfter+1);
    doc->undo();

    cursor.setPosition(posBefore+1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.anchor(), posBefore);
    QCOMPARE(selection.position(), posAfter+1);
    doc->undo();

    cursor.setPosition(posAfter-1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.anchor(), posBefore);
    QCOMPARE(selection.position(), posAfter+1);
    doc->undo();

    selection.setKeepPositionOnInsert(true);
    cursor.setPosition(posAfter);
    cursor.insertText(QLatin1String("x"));
    selection.setKeepPositionOnInsert(false);
    QCOMPARE(selection.anchor(), posBefore);
    QCOMPARE(selection.position(), posAfter);
    doc->undo();

    cursor.setPosition(posAfter+1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.anchor(), posBefore);
    QCOMPARE(selection.position(), posAfter);
    doc->undo();

    selection.setPosition(posAfter);
    selection.setPosition(posBefore, QTextCursor::KeepAnchor);

    cursor.setPosition(posBefore-1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.position(), posBefore+1);
    QCOMPARE(selection.anchor(), posAfter+1);
    doc->undo();

    cursor.setPosition(posBefore);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.position(), posBefore+1);
    QCOMPARE(selection.anchor(), posAfter+1);
    doc->undo();

    cursor.setPosition(posBefore+1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.position(), posBefore);
    QCOMPARE(selection.anchor(), posAfter+1);
    doc->undo();

    cursor.setPosition(posAfter-1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.position(), posBefore);
    QCOMPARE(selection.anchor(), posAfter+1);
    doc->undo();

    cursor.setPosition(posAfter);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.position(), posBefore);
    QCOMPARE(selection.anchor(), posAfter+1);
    doc->undo();

    cursor.setPosition(posAfter+1);
    cursor.insertText(QLatin1String("x"));
    QCOMPARE(selection.position(), posBefore);
    QCOMPARE(selection.anchor(), posAfter);
    doc->undo();

}
void tst_QTextCursor::cursorPositionWithBlockUndoAndRedo()
{
    cursor.insertText("AAAABBBBCCCCDDDD");
    cursor.setPosition(12);
    int cursorPositionBefore = cursor.position();
    cursor.beginEditBlock();
    cursor.insertText("*");
    cursor.setPosition(8);
    cursor.insertText("*");
    cursor.setPosition(4);
    cursor.insertText("*");
    cursor.setPosition(0);
    cursor.insertText("*");
    int cursorPositionAfter = cursor.position();
    cursor.endEditBlock();

    QCOMPARE(doc->toPlainText(), QLatin1String("*AAAA*BBBB*CCCC*DDDD"));
    QCOMPARE(12, cursorPositionBefore);
    QCOMPARE(1, cursorPositionAfter);

    doc->undo(&cursor);
    QCOMPARE(doc->toPlainText(), QLatin1String("AAAABBBBCCCCDDDD"));
    QCOMPARE(cursor.position(), cursorPositionBefore);
    doc->redo(&cursor);
    QCOMPARE(doc->toPlainText(), QLatin1String("*AAAA*BBBB*CCCC*DDDD"));
    QCOMPARE(cursor.position(), cursorPositionAfter);
}

void tst_QTextCursor::cursorPositionWithBlockUndoAndRedo2()
{
    cursor.insertText("AAAABBBB");
    int cursorPositionBefore = cursor.position();
    cursor.setPosition(0, QTextCursor::KeepAnchor);
    cursor.beginEditBlock();
    cursor.removeSelectedText();
    cursor.insertText("AAAABBBBCCCCDDDD");
    cursor.endEditBlock();
    doc->undo(&cursor);
    QCOMPARE(doc->toPlainText(), QLatin1String("AAAABBBB"));
    QCOMPARE(cursor.position(), cursorPositionBefore);

    cursor.insertText("CCCC");
    QCOMPARE(doc->toPlainText(), QLatin1String("AAAABBBBCCCC"));

    cursorPositionBefore = cursor.position();
    cursor.setPosition(0, QTextCursor::KeepAnchor);
    cursor.beginEditBlock();
    cursor.removeSelectedText();
    cursor.insertText("AAAABBBBCCCCDDDD");
    cursor.endEditBlock();

    /* this undo now implicitely reinserts two segments, first "CCCCC", then
       "AAAABBBB". The test ensures that the two are combined in order to
       reconstruct the correct cursor position */
    doc->undo(&cursor);


    QCOMPARE(doc->toPlainText(), QLatin1String("AAAABBBBCCCC"));
    QCOMPARE(cursor.position(), cursorPositionBefore);
}

void tst_QTextCursor::cursorPositionWithBlockUndoAndRedo3()
{
    // verify that it's the position of the beginEditBlock that counts, and not the last edit position
    cursor.insertText("AAAABBBB");
    int cursorPositionBefore = cursor.position();
    cursor.beginEditBlock();
    cursor.setPosition(4);
    QVERIFY(cursor.position() != cursorPositionBefore);
    cursor.insertText("*");
    cursor.endEditBlock();
    QCOMPARE(cursor.position(), 5);
    doc->undo(&cursor);
    QCOMPARE(cursor.position(), cursorPositionBefore);
}

void tst_QTextCursor::joinNonEmptyRemovedBlockUserState()
{
    cursor.insertText("Hello");
    cursor.insertBlock();
    cursor.insertText("World");
    cursor.block().setUserState(10);

    cursor.movePosition(QTextCursor::EndOfBlock);
    cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::KeepAnchor);
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    cursor.removeSelectedText();

    QCOMPARE(cursor.block().userState(), 10);
}

void tst_QTextCursor::crashOnDetachingDanglingCursor()
{
    QTextDocument *document = new QTextDocument;
    QTextCursor cursor(document);
    QTextCursor cursor2 = cursor;
    delete document;
    cursor2.setPosition(0); // Don't crash here
}

void tst_QTextCursor::deletePreviousGraphemeCluster_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<QString>("expected");

#if !defined(QT_NO_EMOJISEGMENTER)
    // === Emoji sequences: entire cluster should be deleted ===

    // Emoji with skin tone modifier: U+1F44D U+1F3FD (👍🏽)
    QTest::newRow("skin tone")
        << QString::fromUcs4(U"\U0001F44D\U0001F3FD")
        << QString();

    // ZWJ family: U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 (👨‍👩‍👧‍👦)
    QTest::newRow("ZWJ family")
        << QString::fromUcs4(U"\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466")
        << QString();

    // ZWJ profession: U+1F469 U+200D U+1F680 (👩‍🚀)
    QTest::newRow("ZWJ profession")
        << QString::fromUcs4(U"\U0001F469\u200D\U0001F680")
        << QString();

    // Flag (regional indicators): U+1F1EA U+1F1FA (🇪🇺)
    QTest::newRow("flag")
        << QString::fromUcs4(U"\U0001F1EA\U0001F1FA")
        << QString();

    // Simple surrogate pair (no modifiers): U+1F600 (😀)
    QTest::newRow("simple emoji")
        << QString::fromUcs4(U"\U0001F600")
        << QString();

    // Two consecutive grapheme clusters — only the last one should be deleted
    QTest::newRow("two skin tone emoji")
        << (QString::fromUcs4(U"\U0001F44D\U0001F3FB") + QString::fromUcs4(U"\U0001F44D\U0001F3FD"))
        << QString::fromUcs4(U"\U0001F44D\U0001F3FB");

    QTest::newRow("two ZWJ sequences")
        << (QString::fromUcs4(U"\U0001F468\u200D\U0001F52C") + QString::fromUcs4(U"\U0001F469\u200D\U0001F680"))
        << QString::fromUcs4(U"\U0001F468\u200D\U0001F52C");

    QTest::newRow("two flags")
        << (QString::fromUcs4(U"\U0001F1E8\U0001F1ED") + QString::fromUcs4(U"\U0001F1EA\U0001F1FA"))
        << QString::fromUcs4(U"\U0001F1E8\U0001F1ED");

    // Keycap sequence: U+0033 U+FE0F U+20E3 (3️⃣)
    QTest::newRow("keycap")
        << QString::fromUcs4(U"\u0033\uFE0F\u20E3")
        << QString();

    // Tag sequence (Scotland flag): U+1F3F4 + tag chars + U+E007F
    QTest::newRow("tag sequence")
        << QString::fromUcs4(U"\U0001F3F4\U000E0067\U000E0062\U000E0073\U000E0063\U000E0074\U000E007F")
        << QString();
#endif // !QT_NO_EMOJISEGMENTER

    // === Non-emoji grapheme clusters: only one codepoint should be deleted ===

    // Devanagari: consonant + vowel sign (U+092F U+093E = या)
    // Backspace should delete only the vowel sign, not the whole cluster
    QTest::newRow("devanagari consonant+vowel")
        << QString::fromUcs4(U"\u092F\u093E")
        << QString(QChar(0x092F));

    // Thai: base + combining (U+0E01 U+0E34 = กิ)
    QTest::newRow("thai base+combining")
        << QString::fromUcs4(U"\u0E01\u0E34")
        << QString(QChar(0x0E01));
}

void tst_QTextCursor::deletePreviousGraphemeCluster()
{
    QFETCH(QString, input);
    QFETCH(QString, expected);

    // Insert prefix + grapheme cluster + suffix, position cursor after it, deletePreviousChar
    const QString prefix = QStringLiteral("ab");
    const QString suffix = QStringLiteral("cd");
    const QString full = prefix + input + suffix;

    cursor.insertText(full);
    // Position cursor right after the input (before suffix)
    cursor.setPosition(prefix.size() + input.size());

    cursor.deletePreviousChar();

    const QString result = doc->toPlainText();
    const QString expectedFull = prefix + expected + suffix;
    QCOMPARE(result, expectedFull);
}

QTEST_MAIN(tst_QTextCursor)
#include "tst_qtextcursor.moc"
