/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <config_pdfimport.h>
#include <swmodeltestbase.hxx>

#include <set>

#include <officecfg/Office/Common.hxx>
#include <officecfg/Office/Writer.hxx>
#include <com/sun/star/document/XEmbeddedObjectSupplier2.hpp>
#include <com/sun/star/embed/EmbedStates.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <COKit/COKit.hxx>
#include <vcl/scheduler.hxx>

#include <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/table/TableBorder2.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XDocumentIndex.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/text/XTextFrame.hpp>
#include <com/sun/star/text/XTextSection.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/text/XPageCursor.hpp>
#include <com/sun/star/text/XParagraphCursor.hpp>

#include <com/sun/star/util/SearchAlgorithms2.hpp>
#include <com/sun/star/util/SearchFlags.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>

#include <comphelper/kit.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/configuration.hxx>
#include <swdtflvr.hxx>
#include <i18nutil/searchopt.hxx>
#include <o3tl/string_view.hxx>
#include <editeng/acorrcfg.hxx>
#include <swacorr.hxx>
#include <sfx2/linkmgr.hxx>

#include <scriptinfo.hxx>
#include <txtfrm.hxx>
#include <edtwin.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <unotxdoc.hxx>
#include <itabenum.hxx>
#include <ndtxt.hxx>
#include <toxmgr.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentLinksAdministration.hxx>
#include <IDocumentUndoRedo.hxx>
#include <fmtanchr.hxx>
#include <fmtfsize.hxx>
#include <fmtinfmt.hxx>
#include <rootfrm.hxx>
#include <dbmgr.hxx>
#include <mmconfigitem.hxx>
#include <svx/svxids.hrc>
#include <pagefrm.hxx>
#include <svx/svdview.hxx>
#include <svx/svdmark.hxx>
#include <test/commontesttools.hxx>

using namespace css;
using namespace css::uno;

namespace
{
class SwUiWriterTest9 : public SwModelTestBase
{
public:
    SwUiWriterTest9()
        : SwModelTestBase(u"/sw/qa/extras/uiwriter/data/"_ustr)
    {
    }
};

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf158785)
{
    // given a document with a hyperlink surrounded by N-dashes (–www.dordt.edu–)
    createSwDoc("tdf158785_hyperlink.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // go to the end of the hyperlink
    pWrtShell->SttEndDoc(/*bStart=*/false);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    // get last point that will be part of the hyperlink (current position 1pt wide).
    Point aLogicL(pWrtShell->GetCharRect().Center());
    Point aLogicR(aLogicL);

    // sanity check - we really are right by the hyperlink
    aLogicL.AdjustX(-1);
    SwContentAtPos aContentAtPos(IsAttrAtPos::InetAttr);
    pWrtShell->GetContentAtPos(aLogicL, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::InetAttr, aContentAtPos.eContentAtPos);

    // The test: the position of the N-dash should not indicate hyperlink properties
    // cursor pos would NOT be considered part of the hyperlink, but increase for good measure...
    aLogicR.AdjustX(1);
    pWrtShell->GetContentAtPos(aLogicR, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::NONE, aContentAtPos.eContentAtPos);

    /*
     * tdf#111969: the beginning of the hyperlink should allow the right-click menu to remove it
     */
    // move cursor (with no selection) to the start of the hyperlink - after the N-dash
    pWrtShell->SttEndDoc(/*bStart=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    aLogicL = pWrtShell->GetCharRect().Center();
    aLogicR = aLogicL;

    // sanity check - we really are right in front of the hyperlink
    aLogicL.AdjustX(-1);
    aContentAtPos = IsAttrAtPos::InetAttr;
    pWrtShell->GetContentAtPos(aLogicL, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::NONE, aContentAtPos.eContentAtPos);
    aLogicR.AdjustX(1);
    aContentAtPos = IsAttrAtPos::InetAttr;
    pWrtShell->GetContentAtPos(aLogicR, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::InetAttr, aContentAtPos.eContentAtPos);

    // Remove the hyperlink
    dispatchCommand(mxComponent, u".uno:RemoveHyperlink"_ustr, {});

    // The test: was the hyperlink actually removed?
    aContentAtPos = IsAttrAtPos::InetAttr;
    pWrtShell->GetContentAtPos(aLogicR, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::NONE, aContentAtPos.eContentAtPos);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenSectionShape)
{
    createSwDoc("section-shape.fodt");

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    auto xTextSectionsSupplier = mxComponent.queryThrow<css::text::XTextSectionsSupplier>();
    auto xSections = xTextSectionsSupplier->getTextSections();
    CPPUNIT_ASSERT(xSections);
    CPPUNIT_ASSERT(xSections->getByName(u"Section1"_ustr)
                       .queryThrow<css::beans::XPropertySet>()
                       ->getPropertyValue(u"IsVisible"_ustr)
                       .get<bool>());
    CPPUNIT_ASSERT(!xSections->getByName(u"Section2"_ustr)
                        .queryThrow<css::beans::XPropertySet>()
                        ->getPropertyValue(u"IsVisible"_ustr)
                        .get<bool>());

    // shape is anchored in hidden section
    auto xShape = getShape(1).queryThrow<text::XTextContent>();
    CPPUNIT_ASSERT_EQUAL(u"Section2"_ustr, getProperty<uno::Reference<container::XNamed>>(
                                               xShape->getAnchor(), u"TextSection"_ustr)
                                               ->getName());

    // prevent the warning dialog which is automatically cancelled
    ScopedConfigValue<officecfg::Office::Writer::Content::Display::ShowWarningHiddenSection> aCfg(
        false);

    // backspace should delete the hidden section
    pWrtShell->Down(/*bSelect=*/false, /*nCount=*/1);
    pWrtShell->DelLeft();

    CPPUNIT_ASSERT_THROW(xSections->getByName(u"Section2"_ustr), container::NoSuchElementException);

    CPPUNIT_ASSERT_EQUAL(0, getShapes());

    // this would assert/crash because the shape was anchored to SwEndNode
    save(TestFilter::ODT);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testSetStringDeletesShape)
{
    createSwDoc();
    SwDoc* const pDoc = getSwDoc();
    SwWrtShell* const pWrtShell = getSwDocShell()->GetWrtShell();

    IDocumentUndoRedo& rUndoManager = pDoc->GetIDocumentUndoRedo();

    sw::UnoCursorPointer pCursor(
        pDoc->CreateUnoCursor(SwPosition(pDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1))));

    pDoc->getIDocumentContentOperations().InsertString(*pCursor, u"foo"_ustr);
    pDoc->getIDocumentContentOperations().SplitNode(*pCursor->GetPoint(), false);
    pDoc->getIDocumentContentOperations().SplitNode(*pCursor->GetPoint(), false);
    pDoc->getIDocumentContentOperations().InsertString(*pCursor, u"end"_ustr);

    {
        SfxItemSet flySet(pDoc->GetAttrPool(),
                          svl::Items<RES_FRM_SIZE, RES_FRM_SIZE, RES_ANCHOR, RES_ANCHOR>);
        SwFormatAnchor anchor(RndStdIds::FLY_AT_CHAR);
        pWrtShell->StartOfSection(false);
        pWrtShell->Down(/*bSelect=*/false, 1, /*bBasicCall=*/false);
        anchor.SetAnchor(pWrtShell->GetCursor()->GetPoint());
        flySet.Put(anchor);
        SwFormatFrameSize size(SwFrameSize::Minimum, 1000, 1000);
        flySet.Put(size); // set a size, else we get 1 char per line...
        SwFrameFormat const* pFly = pWrtShell->NewFlyFrame(flySet, /*bAnchValid=*/true);
        CPPUNIT_ASSERT(pFly != nullptr);
    }
    CPPUNIT_ASSERT_EQUAL(size_t(1), pDoc->GetFlyCount(FLYCNTTYPE_FRM));

    auto xCursor = mxComponent.queryThrow<text::XTextDocument>()->getText()->createTextCursor();
    xCursor->gotoStart(false);
    xCursor->goRight(3, false);
    xCursor->gotoEnd(true);
    xCursor->setString(u"bar"_ustr); // replace multi-paragraph selection

    // the problem was that the fly on the middle node was not deleted
    CPPUNIT_ASSERT_EQUAL(size_t(0), pDoc->GetFlyCount(FLYCNTTYPE_FRM));

    rUndoManager.Undo();

    CPPUNIT_ASSERT_EQUAL(size_t(1), pDoc->GetFlyCount(FLYCNTTYPE_FRM));

    rUndoManager.Redo();

    CPPUNIT_ASSERT_EQUAL(size_t(0), pDoc->GetFlyCount(FLYCNTTYPE_FRM));

    rUndoManager.Undo();

    CPPUNIT_ASSERT_EQUAL(size_t(1), pDoc->GetFlyCount(FLYCNTTYPE_FRM));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159377)
{
    createSwDoc();

    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
    pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/2);
    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);

    dispatchCommand(mxComponent, u".uno:SelectTable"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    pWrtShell->InsertFootnote(u""_ustr);
    CPPUNIT_ASSERT(pWrtShell->IsCursorInFootnote());

    CPPUNIT_ASSERT_EQUAL(SwNodeOffset(28), pDoc->GetNodes().Count());

    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});

    // this pasted the 4 text nodes in the table, but no table nodes
    // as currently tables aren't allowed in footnotes

    CPPUNIT_ASSERT_EQUAL(SwNodeOffset(32), pDoc->GetNodes().Count());

    pWrtShell->Undo();

    CPPUNIT_ASSERT(pWrtShell->IsCursorInFootnote());
    // problem was that this was 29 with an extra text node in the footnote
    CPPUNIT_ASSERT_EQUAL(SwNodeOffset(28), pDoc->GetNodes().Count());

    pWrtShell->Redo();

    CPPUNIT_ASSERT_EQUAL(SwNodeOffset(32), pDoc->GetNodes().Count());

    pWrtShell->Undo();

    CPPUNIT_ASSERT_EQUAL(SwNodeOffset(28), pDoc->GetNodes().Count());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testSplitFloatingTable)
{
    createSwDoc("floattable-split.docx");

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    {
        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
        // apparently follow fly is on the text frame of page 1 even though
        // positioned on page 2...
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 2);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    5);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    4);
    }

    pWrtShell->GotoFly(UIName{ u"Frame1"_ustr });
    pWrtShell->Down(/*bSelect=*/false, 3);

    pWrtShell->SplitTable(SplitTable_HeadlineOption::BorderCopy);

    {
        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 1);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/tab", 1);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/tab/row", 3);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/anchored/fly", 2);

        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    2);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    4);
    }

    pWrtShell->Do(SwWrtShell::UNDO);

    {
        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
        // apparently follow fly is on the text frame of page 1 even though
        // positioned on page 2...
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 2);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    5);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[2]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    4);
    }

    pWrtShell->Do(SwWrtShell::REDO);

    {
        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 1);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/tab", 1);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/tab/row", 3);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/anchored/fly", 2);

        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    2);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    4);
    }

    // now check that these round-trip as floating tables
    saveAndReload(TestFilter::DOCX);

    {
        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 1);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/tab", 1);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly/tab/row", 3);
        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/anchored/fly", 2);

        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/anchored/fly/txt", 0);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab",
                    1);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top < "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    2);
        assertXPath(pXmlDoc,
                    "/root/page[1]/body/txt[3]/anchored/fly[infos/bounds/@top > "
                    "/root/page[2]/infos/bounds/@top]/tab/row",
                    4);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testPasteTableInMiddleOfParagraph)
{
    createSwDoc();

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
    pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/2);
    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);

    dispatchCommand(mxComponent, u".uno:SelectTable"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    pWrtShell->Undo();

    pWrtShell->Insert(u"AB"_ustr);

    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);

    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});

    pWrtShell->Undo();

    // the problem was that the A was missing
    CPPUNIT_ASSERT_EQUAL(u"AB"_ustr,
                         pWrtShell->GetCursor()->GetPointNode().GetTextNode()->GetText());

    pWrtShell->Redo();
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(u"AB"_ustr,
                         pWrtShell->GetCursor()->GetPointNode().GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenSectionPDFExport)
{
    // this uses biblio.odb
    createSwDoc("database-display-hidden-section.fodt");

    uno::Reference<text::XTextSectionsSupplier> xTextSectionsSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xSections(xTextSectionsSupplier->getTextSections(),
                                                      uno::UNO_QUERY);
    uno::Reference<text::XTextSection> xSection(xSections->getByIndex(0), uno::UNO_QUERY);

    // apparently without a record it evaluates to hidden?
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsVisible"_ustr));
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    // somehow a timer that constructs toolbars isn't run in unit tests -
    // it would query state of .uno:MailMergeEmailDocuments which as a side
    // effect (!) calls this:
    getSwDocShell()->GetView()->EnsureMailMergeConfigItem();

    dispatchCommand(mxComponent, u".uno:MailMergeNextEntry"_ustr, {});
    getParagraph(1, u"English "_ustr);
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    dispatchCommand(mxComponent, u".uno:MailMergeNextEntry"_ustr, {});
    getParagraph(1, u"English "_ustr);
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    cpo::uno::Sequence<css::beans::PropertyValue> args{
        comphelper::makePropertyValue(u"SynchronMode"_ustr, true),
        comphelper::makePropertyValue(u"URL"_ustr, maTempFile.GetURL())
    };
    dispatchCommand(mxComponent, u".uno:ExportDirectToPDF"_ustr, args);

    // fetch again in case it was deleted and undo
    xSection.set(xSections->getByIndex(0), uno::UNO_QUERY);
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();

    int nObjectsHidden;
    if (pPDFium)
    {
        SvFileStream aPDFFile(maTempFile.GetURL(), StreamMode::READ);
        SvMemoryStream aMemory;
        aMemory.WriteStream(aPDFFile);

        std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
            = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
        CPPUNIT_ASSERT(pPdfDocument);
        CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());

        std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
        CPPUNIT_ASSERT(pPdfPage);
        nObjectsHidden = pPdfPage->getObjectCount();
    }

    dispatchCommand(mxComponent, u".uno:MailMergeNextEntry"_ustr, {});
    getParagraph(1, u"Deutsch "_ustr);
    CPPUNIT_ASSERT(getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    dispatchCommand(mxComponent, u".uno:ExportDirectToPDF"_ustr, args);

    // fetch again in case it was deleted and undo
    xSection.set(xSections->getByIndex(0), uno::UNO_QUERY);
    // the problem was that PDF export hid the section
    CPPUNIT_ASSERT(getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    if (pPDFium)
    {
        SvFileStream aPDFFile(maTempFile.GetURL(), StreamMode::READ);
        SvMemoryStream aMemory;
        aMemory.WriteStream(aPDFFile);

        std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
            = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
        CPPUNIT_ASSERT(pPdfDocument);
        CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());

        std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
        CPPUNIT_ASSERT(pPdfPage);
        // there must be more objects on the page now (currently goes 2->3)
        CPPUNIT_ASSERT_LESS(pPdfPage->getObjectCount(), nObjectsHidden);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenSectionPDFExport2)
{
    // this uses biblio.odb
    createSwDoc("database-display-hidden-section2.fodt");

    uno::Reference<text::XTextSectionsSupplier> xTextSectionsSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xSections(xTextSectionsSupplier->getTextSections(),
                                                      uno::UNO_QUERY);
    uno::Reference<text::XTextSection> xSection(xSections->getByIndex(0), uno::UNO_QUERY);

    // apparently without a record it evaluates to hidden?
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsVisible"_ustr));
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    // somehow a timer that constructs toolbars isn't run in unit tests -
    // it would query state of .uno:MailMergeEmailDocuments which as a side
    // effect (!) calls this:
    auto pMMConfig = getSwDocShell()->GetView()->EnsureMailMergeConfigItem();

    // ... but the file has wrong DB name so we first have to fix it
    // in the same way as SwChangeDBDlg
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->GetDBManager()->RegisterConnection(u"Bibliography"_ustr);
    SwDBData data;
    data.sDataSource = u"Bibliography"_ustr;
    data.sCommand = u"biblio"_ustr;
    pWrtShell->ChgDBData(data);
    // this would be called via a SwXTextView listener
    pMMConfig->updateCurrentDBDataFromDocument();
    pWrtShell->ChangeDBFields({ u"Bibliography2ÿbiblioÿ0"_ustr }, u"Bibliographyÿbiblioÿ0"_ustr);

    dispatchCommand(mxComponent, u".uno:MailMergeNextEntry"_ustr, {});
    getParagraph(1, u"English "_ustr);
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    dispatchCommand(mxComponent, u".uno:MailMergeNextEntry"_ustr, {});
    getParagraph(1, u"English "_ustr);
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    cpo::uno::Sequence<css::beans::PropertyValue> args{
        comphelper::makePropertyValue(u"SynchronMode"_ustr, true),
        comphelper::makePropertyValue(u"URL"_ustr, maTempFile.GetURL())
    };
    dispatchCommand(mxComponent, u".uno:ExportDirectToPDF"_ustr, args);

    // fetch again in case it was deleted and undo
    xSection.set(xSections->getByIndex(0), uno::UNO_QUERY);
    CPPUNIT_ASSERT(!getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();

    int nObjectsHidden;
    if (pPDFium)
    {
        SvFileStream aPDFFile(maTempFile.GetURL(), StreamMode::READ);
        SvMemoryStream aMemory;
        aMemory.WriteStream(aPDFFile);

        std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
            = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
        CPPUNIT_ASSERT(pPdfDocument);
        CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());

        std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
        CPPUNIT_ASSERT(pPdfPage);
        nObjectsHidden = pPdfPage->getObjectCount();
    }

    dispatchCommand(mxComponent, u".uno:MailMergeNextEntry"_ustr, {});
    getParagraph(1, u"Deutsch "_ustr);
    CPPUNIT_ASSERT(getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    dispatchCommand(mxComponent, u".uno:ExportDirectToPDF"_ustr, args);

    // fetch again in case it was deleted and undo
    xSection.set(xSections->getByIndex(0), uno::UNO_QUERY);
    // the problem was that PDF export hid the section
    CPPUNIT_ASSERT(getProperty<bool>(xSection, u"IsCurrentlyVisible"_ustr));

    if (pPDFium)
    {
        SvFileStream aPDFFile(maTempFile.GetURL(), StreamMode::READ);
        SvMemoryStream aMemory;
        aMemory.WriteStream(aPDFFile);

        std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
            = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
        CPPUNIT_ASSERT(pPdfDocument);
        CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());

        std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
        CPPUNIT_ASSERT(pPdfPage);
        // there must be more objects on the page now (currently goes 2->3)
        CPPUNIT_ASSERT_LESS(pPdfPage->getObjectCount(), nObjectsHidden);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenTextFieldPDFExport)
{
    createSwDoc("set-variable-hidden-text.fodt");

    getParagraph(1, u"2"_ustr);
    getParagraph(2, u"Expected"_ustr);

    cpo::uno::Sequence<css::beans::PropertyValue> args{
        comphelper::makePropertyValue(u"SynchronMode"_ustr, true),
        comphelper::makePropertyValue(u"URL"_ustr, maTempFile.GetURL())
    };
    dispatchCommand(mxComponent, u".uno:ExportDirectToPDF"_ustr, args);

    getParagraph(1, u"2"_ustr);
    // the problem was that this changed to Wrong
    getParagraph(2, u"Expected"_ustr);

    std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
    if (pPDFium)
    {
        SvFileStream aPDFFile(maTempFile.GetURL(), StreamMode::READ);
        SvMemoryStream aMemory;
        aMemory.WriteStream(aPDFFile);

        std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
            = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
        CPPUNIT_ASSERT(pPdfDocument);
        CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());

        std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
        CPPUNIT_ASSERT(pPdfPage);
        std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
        CPPUNIT_ASSERT(pPdfTextPage);
        std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
        OUString sText = pPageObject->getText(pPdfTextPage);
        // the problem was that this changed to Wrong
        CPPUNIT_ASSERT_EQUAL(u"Expected"_ustr, sText);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf111969)
{
    // given a document with a field surrounded by N-dashes (–date–)
    createSwDoc("tdf111969_field.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // go to the end of the field
    pWrtShell->SttEndDoc(/*bStart=*/false);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    // get last point that will be part of the field (current position 1pt wide).
    Point aLogicL(pWrtShell->GetCharRect().Center());
    Point aLogicR(aLogicL);

    // sanity check - we really are at the right edge of the field
    aLogicR.AdjustX(1);
    SwContentAtPos aContentAtPos(IsAttrAtPos::Field);
    pWrtShell->GetContentAtPos(aLogicR, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::NONE, aContentAtPos.eContentAtPos);
    aLogicL.AdjustX(-1);
    aContentAtPos = IsAttrAtPos::Field;
    pWrtShell->GetContentAtPos(aLogicL, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::Field, aContentAtPos.eContentAtPos);

    // the test: simulate a right-click of a mouse which sets the cursor and then acts on that pos.
    pWrtShell->SwCursorShell::SetCursor(aLogicL, false, /*Block=*/false, /*FieldInfo=*/true);
    CPPUNIT_ASSERT(pWrtShell->GetCurField(true));

    /*
     * An edge case at the start of a field - don't start the field menu on the first N-dash
     */
    // go to the start of the field
    pWrtShell->SttEndDoc(/*bStart=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    // get first point that will be part of the field (current position 1pt wide).
    aLogicL = pWrtShell->GetCharRect().Center();
    aLogicR = aLogicL;

    // sanity check - we really are at the left edge of the field
    aLogicR.AdjustX(1);
    aContentAtPos = IsAttrAtPos::Field;
    pWrtShell->GetContentAtPos(aLogicR, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::Field, aContentAtPos.eContentAtPos);
    aLogicL.AdjustX(-1);
    aContentAtPos = IsAttrAtPos::Field;
    pWrtShell->GetContentAtPos(aLogicL, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::NONE, aContentAtPos.eContentAtPos);

    // the test: simulate a right-click of a mouse (at the end-edge of the N-dash)
    // which sets the cursor and then acts on that pos.
    pWrtShell->SwCursorShell::SetCursor(aLogicL, false, /*Block=*/false, /*FieldInfo=*/true);
    CPPUNIT_ASSERT(!pWrtShell->GetCurField(true));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf111969B)
{
    // given a document with a field surrounded by two N-dashes (––date––)
    createSwDoc("tdf111969_fieldB.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // go to the start of the field
    pWrtShell->SttEndDoc(/*bStart=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false);
    // get first point that will be part of the field (current position 1pt wide).
    Point aLogicL(pWrtShell->GetCharRect().Center());
    Point aLogicR(aLogicL);

    // sanity check - we really are at the left edge of the field
    aLogicR.AdjustX(1);
    SwContentAtPos aContentAtPos(IsAttrAtPos::Field);
    pWrtShell->GetContentAtPos(aLogicR, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::Field, aContentAtPos.eContentAtPos);
    aLogicL.AdjustX(-1);
    aContentAtPos = IsAttrAtPos::Field;
    pWrtShell->GetContentAtPos(aLogicL, aContentAtPos);
    CPPUNIT_ASSERT_EQUAL(IsAttrAtPos::NONE, aContentAtPos.eContentAtPos);

    // the test: simulate a right-click of a mouse (at the end-edge of the second N-dash)
    // which sets the cursor and then acts on that pos.
    pWrtShell->SwCursorShell::SetCursor(aLogicL, false, /*Block=*/false, /*FieldInfo=*/true);
    //CPPUNIT_ASSERT(!pWrtShell->GetCurField(true));
}

#if ENABLE_PDFIMPORT
CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testPDFExportCrash)
{
    createSwDoc("section-table-section.fodt");

    cpo::uno::Sequence<beans::PropertyValue> aFilterData(
        comphelper::InitPropertySequence({ { "PDFUACompliance", cpo::uno::Any(true) } }));
    cpo::uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence(
        { { "FilterName", cpo::uno::Any(u"writer_pdf_Export"_ustr) },
          { "FilterData", cpo::uno::Any(aFilterData) },
          { "URL", cpo::uno::Any(maTempFile.GetURL()) } }));

    // Without the fix in place, this test would have crashed here
    dispatchCommand(mxComponent, u".uno:ExportToPDF"_ustr, aDescriptor);
}
#endif

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159049)
{
    // The document contains a shape which has a text with a line break. When copying the text to
    // clipboard the line break was missing in the RTF flavor of the clipboard.
    createSwDoc("tdf159049_LineBreakRTFClipboard.fodt");
    CPPUNIT_ASSERT_EQUAL(1, getShapes());
    selectShape(1);

    // Bring shape into text edit mode
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 0, KEY_RETURN);
    pTextDoc->postKeyEvent(COKitKeyEventType::UP, 0, KEY_RETURN);
    Scheduler::ProcessEventsToIdle();
    // Copy text
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    // Deactivate text edit mode ...
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 0, KEY_ESCAPE);
    pTextDoc->postKeyEvent(COKitKeyEventType::UP, 0, KEY_ESCAPE);
    Scheduler::ProcessEventsToIdle();
    // ... and deselect shape.
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 0, KEY_ESCAPE);
    pTextDoc->postKeyEvent(COKitKeyEventType::UP, 0, KEY_ESCAPE);
    Scheduler::ProcessEventsToIdle();

    // Paste special as RTF
    cpo::uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence(
        { { "SelectedFormat",
            cpo::uno::Any(static_cast<sal_uInt32>(SotClipboardFormatId::RTF)) } }));
    dispatchCommand(mxComponent, u".uno:ClipboardFormatItems"_ustr, aArgs);
    // Without fix Actual was "Abreakhere", the line break \n was missing.
    CPPUNIT_ASSERT_EQUAL(u"Abreak\nhere"_ustr, getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf135083)
{
    createSwDoc("tdf135083-simple-text-plus-list.fodt");

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    // Paste special as RTF
    cpo::uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence(
        { { u"SelectedFormat"_ustr,
            cpo::uno::Any(static_cast<sal_uInt32>(SotClipboardFormatId::RTF)) } }));
    dispatchCommand(mxComponent, u".uno:ClipboardFormatItems"_ustr, aArgs);

    auto xLastPara = getParagraph(3);
    CPPUNIT_ASSERT_EQUAL(u"dolor"_ustr, xLastPara->getString());
    // Without the fix in place, the last paragraph would loose its settings. ListId would be empty.
    CPPUNIT_ASSERT(!getProperty<OUString>(xLastPara, u"ListId"_ustr).isEmpty());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenSectionsAroundPageBreak)
{
    createSwDoc("hiddenSectionsAroundPageBreak.fodt");

    CPPUNIT_ASSERT_EQUAL(1, getPages());

    auto xModel(mxComponent.queryThrow<frame::XModel>());
    auto xTextViewCursorSupplier(
        xModel->getCurrentController().queryThrow<text::XTextViewCursorSupplier>());
    auto xCursor(xTextViewCursorSupplier->getViewCursor().queryThrow<text::XPageCursor>());

    // Make sure that the page style is set correctly
    xCursor->jumpToFirstPage();
    CPPUNIT_ASSERT_EQUAL(u"Landscape"_ustr, getProperty<OUString>(xCursor, u"PageStyleName"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159565)
{
    // Given a document with a hidden section in the beginning, additionally containing a frame
    createSwDoc("FrameInHiddenSection.fodt");

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});

    // Check that the selection covers the whole visible text
    auto xModel(mxComponent.queryThrow<css::frame::XModel>());
    auto xSelSupplier(xModel->getCurrentController().queryThrow<css::view::XSelectionSupplier>());
    auto xSelections(xSelSupplier->getSelection().queryThrow<css::container::XIndexAccess>());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSelections->getCount());
    auto xSelection(xSelections->getByIndex(0).queryThrow<css::text::XTextRange>());

    // Without the fix, this would fail - there was no selection
    CPPUNIT_ASSERT_EQUAL(u"" SAL_NEWLINE_STRING SAL_NEWLINE_STRING "ipsum"_ustr,
                         xSelection->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf163949)
{
    // Given a document with three text frames, each holding the search term:
    createSwDoc();
    auto xMSF(mxComponent.queryThrow<lang::XMultiServiceFactory>());
    auto xText(mxComponent.queryThrow<text::XTextDocument>()->getText());

    static constexpr OUString texts[] = { u"1 match"_ustr, u"2 match"_ustr, u"3 match"_ustr };
    for (const OUString& rFrameText : texts)
    {
        auto xFrame(xMSF->createInstance(u"com.sun.star.text.TextFrame"_ustr)
                        .queryThrow<text::XTextFrame>());
        xFrame.queryThrow<beans::XPropertySet>()->setPropertyValue(
            u"AnchorType"_ustr, cpo::uno::Any(text::TextContentAnchorType_AT_PARAGRAPH));
        auto xCursor(xText->createTextCursor());
        xText->insertTextContent(xCursor, xFrame, false);
        xFrame->getText()->setString(rFrameText);
    }

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    i18nutil::SearchOptions2 aSearchOpt;
    aSearchOpt.searchFlag = css::util::SearchFlags::ALL_IGNORE_CASE;
    aSearchOpt.searchString = u"match"_ustr;
    aSearchOpt.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;

    // When repeatedly using "Find Previous" through the frames (backward search in the
    // "other" area, where frames live), collect which frame each hit lands in - the
    // frames carry distinct text, so the paragraph text identifies the frame:
    std::set<OUString> aVisitedFrames;
    for (size_t i = 0; i < std::size(texts); ++i)
    {
        if (!pWrtShell->SearchPattern(aSearchOpt, /*bSearchInNotes=*/false, SwDocPositions::Curr,
                                      SwDocPositions::Start, FindRanges::InOther))
            break;
        aVisitedFrames.insert(pWrtShell->GetCursor()->GetPointNode().GetTextNode()->GetText());
    }

    // Then the match in every frame must be reached in turn. Without the fix, only the last frame
    // was ever highlighted.
    CPPUNIT_ASSERT_EQUAL(std::size(texts), aVisitedFrames.size());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159816)
{
    createSwDoc();

    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // Add 5 empty paragraphs
    pWrtShell->SplitNode();
    pWrtShell->SplitNode();
    pWrtShell->SplitNode();
    pWrtShell->SplitNode();
    pWrtShell->SplitNode();

    // Add a bookmark at the very end
    IDocumentMarkAccess& rIDMA(*pDoc->getIDocumentMarkAccess());
    rIDMA.makeMark(*pWrtShell->GetCursor(), SwMarkName(u"Mark"_ustr),
                   IDocumentMarkAccess::MarkType::BOOKMARK, sw::mark::InsertMode::New);

    // Get coordinates of the end point in the document
    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
    SwFrame* pPage = pLayout->Lower();
    SwFrame* pBody = pPage->GetLower();
    SwFrame* pLastPara = pBody->GetLower()->GetNext()->GetNext()->GetNext()->GetNext()->GetNext();
    Point ptTo = pLastPara->getFrameArea().BottomRight();

    pWrtShell->SelAll();

    // Drag-n-drop to its own end
    rtl::Reference<SwTransferable> xTransfer = new SwTransferable(*pWrtShell);
    // Without the fix, this would crash: either in CopyFlyInFlyImpl (tdf#159813):
    // Assertion failed: !pCopiedPaM || pCopiedPaM->End()->GetNode() == rRg.aEnd.GetNode()
    // or in BigPtrArray::operator[] (tdf#159816):
    // Assertion failed: idx < m_nSize
    xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf34804)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    // Simulate a keyboard shortcut to SID_ATTR_CHAR_COLOR2 (which must use the shared button color)
    dispatchCommand(mxComponent, u".uno:FontColor"_ustr, {});
    pWrtShell->Insert(u"New World!"_ustr);

    const uno::Reference<text::XTextRange> xRun = getRun(getParagraph(1, u"New World!"_ustr), 1);
    // (This test assumes that nothing in the unit tests has modified the app's recent font color)
    // COL_DEFAULT_FONT is the default red color for the fontColor button on the toolbar.
    CPPUNIT_ASSERT_EQUAL(COL_DEFAULT_FONT, getProperty<Color>(xRun, u"CharColor"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf139631)
{
    // Unit test for tdf#139631
    // Test to see if preceding space is cut when cutting a word with track changes (redline) on
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pWrtShell->Insert(u"New World!\""_ustr);
    // Assert that the string, New World!", is inserted correctly into the document
    CPPUNIT_ASSERT_EQUAL(u"New World!\""_ustr, getParagraph(1)->getString());

    // Enable redline
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    CPPUNIT_ASSERT(pDoc->getIDocumentRedlineAccess().IsRedlineOn());
    // Hide redline changes
    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});
    CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines());

    pWrtShell->Left(SwCursorSkipMode::Chars, false, 2, false);
    // Select and cut "World" from string
    pWrtShell->Left(SwCursorSkipMode::Chars, true, 5, false);
    dispatchCommand(mxComponent, u".uno:Cut"_ustr, {});
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    pXmlDoc = parseLayoutDump();
    // Verifies that the leading space before "World" was also cut
    assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout/SwParaPortion",
                "portion", u"New!\"");

    // Reset to initial string
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout/SwParaPortion",
                "portion", u"New World!\"");

    pWrtShell->EndPara(false);
    pWrtShell->Left(SwCursorSkipMode::Chars, false, 1, false);
    // Replace ! with .
    pWrtShell->Left(SwCursorSkipMode::Chars, true, 1, false);
    pWrtShell->Delete();
    pWrtShell->Insert(u"."_ustr);
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout/SwParaPortion",
                "portion", u"New World.\"");

    pWrtShell->Left(SwCursorSkipMode::Chars, false, 1, false);
    // Select and cut "World" from string
    pWrtShell->Left(SwCursorSkipMode::Chars, true, 5, false);
    dispatchCommand(mxComponent, u".uno:Cut"_ustr, {});

    pXmlDoc = parseLayoutDump();
    // Without the test in place, the leading space before "World" is not also cut.
    // Expected: New."
    // Actual: New ."
    assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout/SwParaPortion",
                "portion", u"New.\"");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf165351)
{
    createSwDoc("tdf165351.fodt");

    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    // Move the cursor into the fly frame
    pWrtShell->GotoFly(UIName(u"Frame1"_ustr), FLYCNTTYPE_FRM, false);
    pWrtShell->EndOfSection();
    pWrtShell->GoNextCell(/*bAppendLine=*/true);
    pWrtShell->Undo();
    // getting this item crashed
    SfxItemSet temp{ pDoc->GetAttrPool(), svl::Items<SID_RULER_LR_MIN_MAX, SID_RULER_LR_MIN_MAX> };
    pWrtShell->GetView().StateTabWin(temp);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf151710)
{
    // Turn "Enclose with characters" on temporarily
    ScopedConfigValue<officecfg::Office::Writer::FmtAidsAutocomplete::EncloseWithCharacters> aCfg(
        true);

    createSwDoc();

    const SwViewOption* pVwOpt = getSwDocShell()->GetWrtShell()->GetViewOptions();
    CPPUNIT_ASSERT(pVwOpt);
    CPPUNIT_ASSERT(pVwOpt->IsEncloseWithCharactersOn());

    // Localized quotation marks
    SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
    CPPUNIT_ASSERT(pACorr);
    LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
    OUString sStartSingleQuote{ pACorr->GetQuote('\'', true, eLang) };
    OUString sEndSingleQuote{ pACorr->GetQuote('\'', false, eLang) };
    OUString sStartDoubleQuote{ pACorr->GetQuote('\"', true, eLang) };
    OUString sEndDoubleQuote{ pACorr->GetQuote('\"', false, eLang) };

    // Insert some text to work with
    cpo::uno::Sequence<beans::PropertyValue> aArgsInsert(
        comphelper::InitPropertySequence({ { "Text", cpo::uno::Any(u"abcd"_ustr) } }));
    dispatchCommand(mxComponent, u".uno:InsertText"_ustr, aArgsInsert);
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(u"abcd"_ustr, xTextDocument->getText()->getString());

    // Successfully enclose the text; afterwards the selection should exist with the new
    // enclosed text
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '(', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"(abcd)"_ustr, xTextDocument->getText()->getString());

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '[', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"[(abcd)]"_ustr, xTextDocument->getText()->getString());

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '{', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"{[(abcd)]}"_ustr, xTextDocument->getText()->getString());

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '\'', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(OUString(sStartSingleQuote + "{[(abcd)]}" + sEndSingleQuote),
                         xTextDocument->getText()->getString());

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '\"', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(OUString(sStartDoubleQuote + sStartSingleQuote + "{[(abcd)]}"
                                  + sEndSingleQuote + sEndDoubleQuote),
                         xTextDocument->getText()->getString());

    // Disable the setting and check that enclosing doesn't happen anymore
    const_cast<SwViewOption*>(pVwOpt)->SetEncloseWithCharactersOn(false);
    CPPUNIT_ASSERT(!pVwOpt->IsEncloseWithCharactersOn());

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '(', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"("_ustr, xTextDocument->getText()->getString());

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '[', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"["_ustr, xTextDocument->getText()->getString());

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '{', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"{"_ustr, xTextDocument->getText()->getString());

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '\'', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(sStartSingleQuote, xTextDocument->getText()->getString());

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '\"', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(sStartDoubleQuote, xTextDocument->getText()->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf167132)
{
    // Turn "Enclose with characters" on temporarily
    ScopedConfigValue<officecfg::Office::Writer::FmtAidsAutocomplete::EncloseWithCharacters> aCfg(
        true);

    // Given a document with several paragraphs, and a formula object
    createSwDoc("text-with-formula.fodt");
    CPPUNIT_ASSERT_EQUAL(3, getParagraphs());
    SwXTextDocument* pTextDoc = getSwTextDoc();
    auto& rObjectContainer = pTextDoc->GetObjectShell()->GetEmbeddedObjectContainer();
    CPPUNIT_ASSERT(rObjectContainer.HasEmbeddedObjects());
    auto xMathObject = rObjectContainer.GetEmbeddedObject(rObjectContainer.GetObjectNames()[0]);
    CPPUNIT_ASSERT(xMathObject);

    auto xSelSupplier = pTextDoc->getCurrentController().queryThrow<view::XSelectionSupplier>();
    auto xCursor = pTextDoc->getText()->createTextCursor();
    // 1. Select almost whole text, except the first and the last characters (to test the stability
    // of the selection boundaries) - meaning that the selection spans across three paragraphs:
    xCursor->gotoRange(pTextDoc->getText()->getStart(), false);
    xCursor->goRight(1, false);
    xCursor->gotoRange(pTextDoc->getText()->getEnd(), true);
    xCursor->goLeft(1, true);
    xSelSupplier->select(cpo::uno::Any(xCursor));

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '(', 0);
    Scheduler::ProcessEventsToIdle();
    // The formula must not be removed from the document
    CPPUNIT_ASSERT(rObjectContainer.HasEmbeddedObject(xMathObject));

    auto xSelections = xSelSupplier->getSelection().queryThrow<css::container::XIndexAccess>();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSelections->getCount());
    auto xSelection = xSelections->getByIndex(0).queryThrow<css::text::XTextRange>();
    // Check that selection includes parentheses
    CPPUNIT_ASSERT_EQUAL(u"(aragraph one" SAL_NEWLINE_STRING "Paragraph two: " SAL_NEWLINE_STRING
                         "Paragraph thre)"_ustr,
                         xSelection->getString());
    // Check that the selection includes the formula
    auto xContentEnumAccess = xSelection.queryThrow<css::container::XContentEnumerationAccess>();
    auto xContentEnum
        = xContentEnumAccess->createContentEnumeration(u"com.sun.star.text.TextContent"_ustr);
    CPPUNIT_ASSERT(xContentEnum->hasMoreElements());

    // 2. Select part of the second paragraph, starting with the bold word, to the end (including
    // the formula), to test that the added characters assume the formatting of selection:
    auto xPara2 = getParagraph(2);
    xCursor->gotoRange(xPara2->getStart(), false);
    xCursor->goRight(10, false); // to start of "two", which is bold
    xCursor->gotoRange(xPara2->getEnd(), true);
    CPPUNIT_ASSERT_EQUAL(u"two: "_ustr, xCursor->getString());
    xContentEnumAccess.set(xCursor, css::uno::UNO_QUERY_THROW);
    xContentEnum
        = xContentEnumAccess->createContentEnumeration(u"com.sun.star.text.TextContent"_ustr);
    CPPUNIT_ASSERT(xContentEnum->hasMoreElements()); // The selection contains the formula
    xSelSupplier->select(cpo::uno::Any(xCursor));

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '[', 0);
    Scheduler::ProcessEventsToIdle();
    // The formula must not be removed from the document
    CPPUNIT_ASSERT(rObjectContainer.HasEmbeddedObject(xMathObject));

    // The paragraph now must consist of five runs: first, the non-bold "Paragraph ";
    // then bold "[two"; then non-bold ": "; then the formula; and then non-bold "]":
    auto xRun = getRun(xPara2, 1, u"Paragraph "_ustr);
    CPPUNIT_ASSERT_EQUAL(100.0f, getProperty<float>(xRun, u"CharWeight"_ustr));
    xRun = getRun(xPara2, 2, u"[two"_ustr);
    CPPUNIT_ASSERT_EQUAL(150.0f, getProperty<float>(xRun, u"CharWeight"_ustr));
    xRun = getRun(xPara2, 3, u": "_ustr);
    CPPUNIT_ASSERT_EQUAL(100.0f, getProperty<float>(xRun, u"CharWeight"_ustr));
    xRun = getRun(xPara2, 4, u""_ustr);
    // Check that the run includes the formula
    xContentEnumAccess.set(xRun, css::uno::UNO_QUERY_THROW);
    xContentEnum
        = xContentEnumAccess->createContentEnumeration(u"com.sun.star.text.TextContent"_ustr);
    CPPUNIT_ASSERT(xContentEnum->hasMoreElements()); // The selection contains the formula
    xRun = getRun(xPara2, 5, u"]"_ustr);
    CPPUNIT_ASSERT_EQUAL(100.0f, getProperty<float>(xRun, u"CharWeight"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf167133)
{
    // Turn "Enclose with characters" on temporarily
    ScopedConfigValue<officecfg::Office::Writer::FmtAidsAutocomplete::EncloseWithCharacters> aCfg(
        true);

    // Given a document with a single paragraph, having a formula object
    createSwDoc("text-with-formula-one-paragraph.fodt");
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
    pDoc->getIDocumentContentOperations().ReplaceRange(*pWrtShell->GetCursor(), u""_ustr, false);
    Scheduler::ProcessEventsToIdle();
    // This must not crash!
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159054_disableOutlineNumbering)
{
    createSwDoc("tdf159054_disableOutlineNumbering.docx");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    const uno::Reference<text::XTextRange> xPara1 = getParagraph(1, u"Heading A"_ustr);
    const uno::Reference<text::XTextRange> xPara2 = getParagraph(2, u"Heading B"_ustr);
    const uno::Reference<text::XTextRange> xPara3 = getParagraph(3, u"Heading C"_ustr);

    CPPUNIT_ASSERT_EQUAL(u"A."_ustr, getProperty<OUString>(xPara1, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"B."_ustr, getProperty<OUString>(xPara2, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"C."_ustr, getProperty<OUString>(xPara3, u"ListLabelString"_ustr));

    // select (at least parts) of the first two paragraphs
    pWrtShell->Down(/*bSelect=*/true, /*nCount=*/1, /*bBasicCall=*/true);

    // on the selection, simulate pressing the toolbar button to toggle OFF numbering
    dispatchCommand(mxComponent, u".uno:DefaultNumbering"_ustr, {});

    // the selected paragraphs should definitely have the list label removed
    CPPUNIT_ASSERT_EQUAL(u""_ustr, getProperty<OUString>(xPara1, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u""_ustr, getProperty<OUString>(xPara2, u"ListLabelString"_ustr));
    // the third paragraph must retain the existing numbering format
    CPPUNIT_ASSERT_EQUAL(u"A."_ustr, getProperty<OUString>(xPara3, u"ListLabelString"_ustr));

    // on the selection, simulate pressing the toolbar button to toggle ON numbering again
    dispatchCommand(mxComponent, u".uno:DefaultNumbering"_ustr, {});

    // the outline numbering format must be re-applied to the first two paragraphs
    CPPUNIT_ASSERT_EQUAL(u"A."_ustr, getProperty<OUString>(xPara1, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"B."_ustr, getProperty<OUString>(xPara2, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"C."_ustr, getProperty<OUString>(xPara3, u"ListLabelString"_ustr));

    // on the selection, simulate a right click - list - No list
    dispatchCommand(mxComponent, u".uno:RemoveBullets"_ustr, {});

    // the selected paragraphs should definitely have the list label removed
    CPPUNIT_ASSERT_EQUAL(u""_ustr, getProperty<OUString>(xPara1, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u""_ustr, getProperty<OUString>(xPara2, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"A."_ustr, getProperty<OUString>(xPara3, u"ListLabelString"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf158375_dde_disable)
{
    ScopedConfigValue<officecfg::Office::Common::Security::Scripting::DisableActiveContent> aCfg(
        true);

    createSwDoc();
    SwDoc* pDoc = getSwDoc();

    // force the AppName to enable DDE, it is not there for test runs
    Application::SetAppName(u"soffice"_ustr);

    // temp copy for the file that will be used as a reference for DDE link
    // this file includes a section named "Section1" with text inside
    createTempCopy(u"tdf158375_dde_reference.fodt");

    comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer
        = getSwDocShell()->getEmbeddedObjectContainer();
    rEmbeddedObjectContainer.setUserAllowsLinkUpdate(true);

    // create a section with DDE link
    uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xTextSectionProps(
        xFactory->createInstance(u"com.sun.star.text.TextSection"_ustr), uno::UNO_QUERY);

    cpo::uno::Sequence<OUString> aNames{ u"DDECommandFile"_ustr, u"DDECommandType"_ustr,
                                         u"DDECommandElement"_ustr, u"IsAutomaticUpdate"_ustr,
                                         u"IsProtected"_ustr };
    cpo::uno::Sequence<cpo::uno::Any> aValues{ cpo::uno::Any(u"soffice"_ustr),
                                               cpo::uno::Any(maTempFile.GetURL()),
                                               cpo::uno::Any(u"Section1"_ustr), cpo::uno::Any(true),
                                               cpo::uno::Any(true) };
    uno::Reference<beans::XMultiPropertySet> rMultiPropSet(xTextSectionProps, uno::UNO_QUERY);
    rMultiPropSet->setPropertyValues(aNames, aValues);

    // insert the TextSection with DDE link
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextRange> xTextRange = xTextDocument->getText();
    uno::Reference<text::XText> xText = xTextRange->getText();
    uno::Reference<text::XParagraphCursor> xCursor(xText->createTextCursor(), uno::UNO_QUERY);
    xText->insertTextContent(
        xCursor, uno::Reference<text::XTextContent>(xTextSectionProps, uno::UNO_QUERY), false);

    CPPUNIT_ASSERT_EQUAL(
        size_t(1), pDoc->getIDocumentLinksAdministration().GetLinkManager().GetLinks().size());

    pDoc->getIDocumentLinksAdministration().GetLinkManager().UpdateAllLinks(false, nullptr,
                                                                            u""_ustr);

    uno::Reference<text::XTextSectionsSupplier> xTextSectionsSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xSections(xTextSectionsSupplier->getTextSections(),
                                                      uno::UNO_QUERY);
    uno::Reference<text::XTextSection> xSection(xSections->getByIndex(0), uno::UNO_QUERY);

    // make sure there's no text in the section after UpdateAllLinks, since
    // DisableActiveContent disables DDE links.
    CPPUNIT_ASSERT_EQUAL(u""_ustr, xSection->getAnchor()->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf158375_ole_object_disable)
{
    ScopedConfigValue<officecfg::Office::Common::Security::Scripting::DisableActiveContent> aCfg(
        true);

    // Enable COKit mode, otherwise OCommonEmbeddedObject::SwitchStateTo_Impl() will throw when it
    // finds out that the test runs headless.
    comphelper::COKit::setActive();

    // Load a document with a Draw doc in it.
    createSwDoc("ole-save-while-edit.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    selectShape(1);

    // attempt to edit the OLE object.
    pWrtShell->LaunchOLEObj();

    // it shouldn't switch because the current configuration
    // (DisableActiveContent) prohibits OLE objects changing to states other
    // then LOADED
    auto xShape = getShape(1);
    uno::Reference<document::XEmbeddedObjectSupplier2> xEmbedSupplier(xShape, uno::UNO_QUERY);
    auto xEmbeddedObj = xEmbedSupplier->getExtendedControlOverEmbeddedObject();
    CPPUNIT_ASSERT_EQUAL(embed::EmbedStates::LOADED, xEmbeddedObj->getCurrentState());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf146190)
{
    // Given a document with a number rule at the start of a paragraph and two drawing objects:
    createSwDoc("tdf146190.odt");
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();

    const SdrMarkList& rMrkList = pWrtShell->GetDrawView()->GetMarkedObjectList();

    // Assert the current cursor position has a number rule and is at the start of a paragraph:
    pWrtShell->SttEndDoc(/*bStt=*/true);
    CPPUNIT_ASSERT(pWrtShell->GetNumRuleAtCurrCursorPos());
    CPPUNIT_ASSERT(pWrtShell->IsSttOfPara());

    // Then go to "Shape 1" drawing object using the GotoDrawingObject function:
    pWrtShell->GotoDrawingObject(u"Shape 1");
    CPPUNIT_ASSERT_EQUAL(u"Shape 1"_ustr, rMrkList.GetMark(0)->GetMarkedSdrObj()->GetName());

    // Move to the next drawing object by Tab key press:
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 0, KEY_TAB);
    Scheduler::ProcessEventsToIdle();
    // Without the fix in place, this test would have failed with:
    // equality assertion failed
    // - Expected: Shape 2
    // - Actual  : Shape 1
    // i.e. Tab did not move to the next drawing object
    CPPUNIT_ASSERT_EQUAL(u"Shape 2"_ustr, rMrkList.GetMark(0)->GetMarkedSdrObj()->GetName());

    // Tab key press should now select 'Shape 1':
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 0, KEY_TAB);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"Shape 1"_ustr, rMrkList.GetMark(0)->GetMarkedSdrObj()->GetName());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf160898)
{
    // Given a document with a 1-cell table in another 1-cell table:
    createSwDoc("table-in-table.fodt");
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();

    // Move to the normally hidden paragraph inside the outer table cell, following the inner table
    pWrtShell->Down(false, 2);
    // Without the fix, this would crash:
    pWrtShell->SelAll();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf164949)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
    pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/2);
    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);

    dispatchCommand(mxComponent, u".uno:SelectTable"_ustr, {});

    cpo::uno::Sequence aArgs{ comphelper::makePropertyValue(u"PersistentCopy"_ustr,
                                                            cpo::uno::Any(false)) };

    // Without the fix in place, this test would have crashed here
    dispatchCommand(mxComponent, u".uno:FormatPaintbrush"_ustr, aArgs);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testParagraphStyleCloneFormatting)
{
    createSwDoc();
    emulateTyping(u"First Line");

    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 0, KEY_RETURN);
    pTextDoc->postKeyEvent(COKitKeyEventType::UP, 0, KEY_RETURN);
    Scheduler::ProcessEventsToIdle();

    emulateTyping(u"Second Line");

    CPPUNIT_ASSERT_EQUAL(u"Standard"_ustr,
                         getProperty<OUString>(getParagraph(1), u"ParaStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Standard"_ustr,
                         getProperty<OUString>(getParagraph(2), u"ParaStyleName"_ustr));

    cpo::uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence({
        { "Style", cpo::uno::Any(u"Heading 1"_ustr) },
        { "FamilyName", cpo::uno::Any(u"ParagraphStyles"_ustr) },
    });
    dispatchCommand(mxComponent, u".uno:StyleApply"_ustr, aPropertyValues);

    CPPUNIT_ASSERT_EQUAL(u"Standard"_ustr,
                         getProperty<OUString>(getParagraph(1), u"ParaStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Heading 1"_ustr,
                         getProperty<OUString>(getParagraph(2), u"ParaStyleName"_ustr));

    cpo::uno::Sequence aArgs{ comphelper::makePropertyValue(u"PersistentCopy"_ustr,
                                                            cpo::uno::Any(false)) };
    dispatchCommand(mxComponent, u".uno:FormatPaintbrush"_ustr, aArgs);

    // Disable map mode, so that it's possible to send mouse event coordinates
    // directly in twips.
    SwEditWin& rEditWin = getSwDocShell()->GetView()->GetEditWin();
    rEditWin.EnableMapMode(false);

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);

    // move to first line
    pWrtShell->Up(/*bSelect=*/false, 1);
    Point aPoint = pShellCursor->GetSttPos();

    // click on first line
    pTextDoc->postMouseEvent(COKitMouseEventType::BUTTONDOWN, aPoint.getX(), aPoint.getY(), 1,
                             MOUSE_LEFT, 0);
    pTextDoc->postMouseEvent(COKitMouseEventType::BUTTONUP, aPoint.getX(), aPoint.getY(), 1,
                             MOUSE_LEFT, 0);
    Scheduler::ProcessEventsToIdle();

    CPPUNIT_ASSERT_EQUAL(u"Heading 1"_ustr,
                         getProperty<OUString>(getParagraph(1), u"ParaStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Heading 1"_ustr,
                         getProperty<OUString>(getParagraph(2), u"ParaStyleName"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf122756)
{
    createSwDoc("tdf122756.odt");

    uno::Reference<text::XTextTable> xTable(getParagraphOrTable(1), uno::UNO_QUERY);

    uno::Reference<text::XTextRange> xCellA1(xTable->getCellByName(u"A1"_ustr), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(u"€ 100,00"_ustr, xCellA1->getString());

    uno::Reference<text::XTextRange> xCellA2(xTable->getCellByName(u"A2"_ustr), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(u"100"_ustr, xCellA2->getString());

    // Cursor is already on cell A1
    cpo::uno::Sequence aArgs{ comphelper::makePropertyValue(u"PersistentCopy"_ustr,
                                                            cpo::uno::Any(false)) };
    dispatchCommand(mxComponent, u".uno:FormatPaintbrush"_ustr, aArgs);

    // Disable map mode, so that it's possible to send mouse event coordinates
    // directly in twips.
    SwEditWin& rEditWin = getSwDocShell()->GetView()->GetEditWin();
    rEditWin.EnableMapMode(false);

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);

    // move to cell A2
    pWrtShell->Down(/*bSelect=*/false, 1);
    Point aPoint = pShellCursor->GetSttPos();

    // click on cell A2
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postMouseEvent(COKitMouseEventType::BUTTONDOWN, aPoint.getX(), aPoint.getY(), 1,
                             MOUSE_LEFT, 0);
    pTextDoc->postMouseEvent(COKitMouseEventType::BUTTONUP, aPoint.getX(), aPoint.getY(), 1,
                             MOUSE_LEFT, 0);
    Scheduler::ProcessEventsToIdle();

    CPPUNIT_ASSERT_EQUAL(u"€ 100,00"_ustr, xCellA1->getString());

    // Without the fix in place, this test would have failed with
    // - Expected: € 100,00
    // - Actual  : 100
    CPPUNIT_ASSERT_EQUAL(u"€ 100,00"_ustr, xCellA2->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf161172)
{
    // Given a paragraph manually made a member of a list:
    createSwDoc("tdf161172.fodt");
    auto para = getParagraph(1);

    // Check initial state: the first paragraph has "No_list" para style, "Num_1" numbering style,
    // numbering level 0, and "Num1_lvl1_1" numbering label.
    CPPUNIT_ASSERT_EQUAL(u"No_list"_ustr, getProperty<OUString>(para, u"ParaStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Num_1"_ustr, getProperty<OUString>(para, u"NumberingStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Num1_lvl1_1"_ustr, getProperty<OUString>(para, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(sal_Int16(0), getProperty<sal_Int16>(para, u"NumberingLevel"_ustr));

    // Assign "Num_1_lvl2" paragraph style to the first paragraph. The style is associated with
    // "Num_1" numbering style, level 1.
    dispatchCommand(mxComponent, u".uno:StyleApply"_ustr,
                    { comphelper::makePropertyValue(u"FamilyName"_ustr, u"ParagraphStyles"_ustr),
                      comphelper::makePropertyValue(u"Style"_ustr, u"Num_1_lvl2"_ustr) });

    // Check that the respective properties got correctly applied
    CPPUNIT_ASSERT_EQUAL(u"Num_1_lvl2"_ustr, getProperty<OUString>(para, u"ParaStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Num_1"_ustr, getProperty<OUString>(para, u"NumberingStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Num1_lvl2_1"_ustr, getProperty<OUString>(para, u"ListLabelString"_ustr));
    CPPUNIT_ASSERT_EQUAL(sal_Int16(1), getProperty<sal_Int16>(para, u"NumberingLevel"_ustr));

    // Undo
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});

    // Check that the numbering properties got correctly restored
    CPPUNIT_ASSERT_EQUAL(u"No_list"_ustr, getProperty<OUString>(para, u"ParaStyleName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"Num_1"_ustr, getProperty<OUString>(para, u"NumberingStyleName"_ustr));
    // Without the fix, this would fail with
    // - Expected: Num1_lvl1_1
    // - Actual  : Num1_lvl2_1
    CPPUNIT_ASSERT_EQUAL(u"Num1_lvl1_1"_ustr, getProperty<OUString>(para, u"ListLabelString"_ustr));
    // Without the fix, this would fail with
    // - Expected: 0
    // - Actual  : 1
    CPPUNIT_ASSERT_EQUAL(sal_Int16(0), getProperty<sal_Int16>(para, u"NumberingLevel"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf144752)
{
    // Undoing/redoing a replacement must select the new text
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    emulateTyping(u"Some Text");
    CPPUNIT_ASSERT(!pWrtShell->HasSelection());
    // Select "Text", and replace with "Word"
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect*/ true, 4, /*bBasicCall*/ false);
    pWrtShell->Replace(u"Word"_ustr, false);
    pWrtShell->EndOfSection();
    CPPUNIT_ASSERT(!pWrtShell->HasSelection());

    // Undo and check, that the "Text" is selected
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    // Without the fix, this would fail
    CPPUNIT_ASSERT(pWrtShell->HasSelection());
    CPPUNIT_ASSERT_EQUAL(u"Text"_ustr, pWrtShell->GetSelText());

    // Redo and check, that the "Word" is selected
    dispatchCommand(mxComponent, u".uno:Redo"_ustr, {});
    CPPUNIT_ASSERT(pWrtShell->HasSelection());
    CPPUNIT_ASSERT_EQUAL(u"Word"_ustr, pWrtShell->GetSelText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf162326_Paragraph)
{
    createSwDoc("tdf162326.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD,
                         getProperty<float>(getRun(getParagraph(1), 1), u"CharWeight"_ustr));
    CPPUNIT_ASSERT_EQUAL(
        awt::FontSlant_ITALIC,
        getProperty<awt::FontSlant>(getRun(getParagraph(2), 2), u"CharPosture"_ustr));
    CPPUNIT_ASSERT_EQUAL(short(1),
                         getProperty<short>(getRun(getParagraph(3), 2), u"CharUnderline"_ustr));

    pWrtShell->Down(/*bSelect=*/true, 3);

    dispatchCommand(
        mxComponent, u".uno:StyleApply"_ustr,
        { comphelper::makePropertyValue(u"FamilyName"_ustr, u"ParagraphStyles"_ustr),
          comphelper::makePropertyValue(u"Style"_ustr, u"Footnote"_ustr),
          comphelper::makePropertyValue(u"KeyModifier"_ustr, cpo::uno::Any(KEY_MOD1)) });

    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::NORMAL,
                         getProperty<float>(getRun(getParagraph(1), 1), u"CharWeight"_ustr));
    CPPUNIT_ASSERT_THROW(getRun(getParagraph(2), 2), css::container::NoSuchElementException);
    CPPUNIT_ASSERT_THROW(getRun(getParagraph(3), 2), css::container::NoSuchElementException);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf162326_Character)
{
    createSwDoc("tdf162326.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD,
                         getProperty<float>(getRun(getParagraph(1), 1), u"CharWeight"_ustr));
    CPPUNIT_ASSERT_EQUAL(
        awt::FontSlant_ITALIC,
        getProperty<awt::FontSlant>(getRun(getParagraph(2), 2), u"CharPosture"_ustr));
    CPPUNIT_ASSERT_EQUAL(short(1),
                         getProperty<short>(getRun(getParagraph(3), 2), u"CharUnderline"_ustr));

    pWrtShell->Down(/*bSelect=*/true, 3);

    //add Ctrl/MOD_1
    dispatchCommand(
        mxComponent, u".uno:StyleApply"_ustr,
        { comphelper::makePropertyValue(u"FamilyName"_ustr, u"CharacterStyles"_ustr),
          comphelper::makePropertyValue(u"Style"_ustr, u"Definition"_ustr),
          comphelper::makePropertyValue(u"KeyModifier"_ustr, cpo::uno::Any(KEY_MOD1)) });

    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::NORMAL,
                         getProperty<float>(getRun(getParagraph(1), 1), u"CharWeight"_ustr));
    CPPUNIT_ASSERT_THROW(getRun(getParagraph(2), 2), css::container::NoSuchElementException);
    //last runs are not changed because the selection ends at the beginning of that paragraph
    CPPUNIT_ASSERT_EQUAL(short(1),
                         getProperty<short>(getRun(getParagraph(3), 2), u"CharUnderline"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf162326_List)
{
    createSwDoc("tdf162326_list.odt");
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XParagraphCursor> xParaCursor(xTextDocument->getText()->createTextCursor(),
                                                       uno::UNO_QUERY);

    CPPUNIT_ASSERT_EQUAL(u"A)"_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));

    dispatchCommand(mxComponent, u".uno:StyleApply"_ustr,
                    { comphelper::makePropertyValue(u"FamilyName"_ustr, u"ParagraphStyles"_ustr),
                      comphelper::makePropertyValue(u"Style"_ustr, u"Footnote"_ustr) });

    //hard list attribute unchanged
    CPPUNIT_ASSERT_EQUAL(u"A)"_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));

    dispatchCommand(
        mxComponent, u".uno:StyleApply"_ustr,
        { comphelper::makePropertyValue(u"FamilyName"_ustr, u"ParagraphStyles"_ustr),
          comphelper::makePropertyValue(u"Style"_ustr, u"Footnote"_ustr),
          comphelper::makePropertyValue(u"KeyModifier"_ustr, cpo::uno::Any(KEY_MOD1)) });

    //list replaced by para style list setting
    CPPUNIT_ASSERT_EQUAL(u"1."_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf163340)
{
    createSwDoc("tdf163340.odt");
    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XText> xText = xTextDocument->getText();
    uno::Reference<view::XSelectionSupplier> xSelSupplier(xModel->getCurrentController(),
                                                          uno::UNO_QUERY_THROW);
    uno::Reference<text::XParagraphCursor> xParaCursor(xTextDocument->getText()->createTextCursor(),
                                                       uno::UNO_QUERY);

    for (int i = 0; i < 14; i++)
        xParaCursor->gotoNextParagraph(false);
    xParaCursor->gotoEndOfParagraph(true);
    xSelSupplier->select(cpo::uno::Any(xParaCursor));

    CPPUNIT_ASSERT_EQUAL(u"A."_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    xParaCursor = uno::Reference<text::XParagraphCursor>(xText->createTextCursor(), uno::UNO_QUERY);
    for (int i = 0; i < 3; i++)
        xParaCursor->gotoNextParagraph(false);
    xParaCursor->gotoEndOfParagraph(true);
    CPPUNIT_ASSERT_EQUAL(u"1."_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));
    xSelSupplier->select(cpo::uno::Any(xParaCursor));
    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(u"A."_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf163340_2)
{
    //selects and copies a single paragraph with a list (bullets)
    //and pastes it into an empty paragraph with a different list (numbers)
    //checks that the resulting paragraph keeps that different list
    createSwDoc("tdf163340_2.odt");
    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XText> xText = xTextDocument->getText();
    uno::Reference<view::XSelectionSupplier> xSelSupplier(xModel->getCurrentController(),
                                                          uno::UNO_QUERY_THROW);
    uno::Reference<text::XParagraphCursor> xParaCursor(xTextDocument->getText()->createTextCursor(),
                                                       uno::UNO_QUERY);

    for (int i = 0; i < 2; i++)
        xParaCursor->gotoNextParagraph(false);
    xParaCursor->gotoEndOfParagraph(true);
    xSelSupplier->select(cpo::uno::Any(xParaCursor));

    xParaCursor = uno::Reference<text::XParagraphCursor>(xText->createTextCursor(), uno::UNO_QUERY);
    for (int i = 0; i < 10; i++)
        xParaCursor->gotoNextParagraph(false);
    xParaCursor->gotoEndOfParagraph(true);
    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(u"5."_ustr, getProperty<OUString>(xParaCursor, u"ListLabelString"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159023)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
    pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/2);
    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);

    dispatchCommand(mxComponent, u".uno:SelectTable"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    pWrtShell->InsertFootnote(u""_ustr);
    CPPUNIT_ASSERT(pWrtShell->IsCursorInFootnote());

    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});
    dispatchCommand(mxComponent, u".uno:GoLeft"_ustr, {});
    dispatchCommand(mxComponent, u".uno:GoLeft"_ustr, {});

    // Without the fix in place, this test would have crashed here
    CPPUNIT_ASSERT(pWrtShell->IsCursorInFootnote());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf162195)
{
    // Given a document, which has some index entries in a hidden section
    createSwDoc("IndexElementsInHiddenSections.fodt");

    auto xIndexSupplier(mxComponent.queryThrow<css::text::XDocumentIndexesSupplier>());
    auto xIndexes = xIndexSupplier->getDocumentIndexes();
    CPPUNIT_ASSERT(xIndexes);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexes->getCount()); // A ToC and a table index

    auto xToC(xIndexes->getByIndex(0).queryThrow<css::text::XDocumentIndex>());
    xToC->update();
    // Without the fix, all the elements from the hidden section appeared in the index
    CPPUNIT_ASSERT_EQUAL(u"Table of Contents" SAL_NEWLINE_STRING "Section Visible\t1"_ustr,
                         xToC->getAnchor()->getString());

    auto xTables(xIndexes->getByIndex(1).queryThrow<css::text::XDocumentIndex>());
    xTables->update();
    // Without the fix, all the elements from the hidden section appeared in the index
    CPPUNIT_ASSERT_EQUAL(u"Index of Tables" SAL_NEWLINE_STRING "Table1\t1"_ustr,
                         xTables->getAnchor()->getString());

    // Show the hidden section
    auto xTextSectionsSupplier = mxComponent.queryThrow<css::text::XTextSectionsSupplier>();
    auto xSections = xTextSectionsSupplier->getTextSections();
    CPPUNIT_ASSERT(xSections);
    auto xSection
        = xSections->getByName(u"Section Hidden"_ustr).queryThrow<css::beans::XPropertySet>();
    xSection->setPropertyValue(u"IsVisible"_ustr, cpo::uno::Any(true));

    xToC->update();
    CPPUNIT_ASSERT_EQUAL(u"Table of Contents" SAL_NEWLINE_STRING
                         "Section Visible\t1" SAL_NEWLINE_STRING
                         "Section Hidden\t1" SAL_NEWLINE_STRING "entry\t1" SAL_NEWLINE_STRING
                         "CustomTOCStyle paragraph\t1"_ustr,
                         xToC->getAnchor()->getString());

    xTables->update();
    CPPUNIT_ASSERT_EQUAL(u"Index of Tables" SAL_NEWLINE_STRING "Table1\t1" SAL_NEWLINE_STRING
                         "Table2\t1"_ustr,
                         xTables->getAnchor()->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf164140)
{
    createSwDoc("tdf164140.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    SwTextFrame& pTextFrame
        = dynamic_cast<SwTextFrame&>(*pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower());
    const SwScriptInfo* pSI = pTextFrame.GetScriptInfo();

    // Prior to editing, there should be no kashida
    auto stBeforeKashida = pSI->GetKashidaPositions();
    CPPUNIT_ASSERT_EQUAL(size_t(0), stBeforeKashida.size());

    // Insert text at the beginning of the document
    pWrtShell->Insert(u"A"_ustr);

    // After editing, there should still be no room for kashida
    // Without the fix, this will be non-zero
    auto stAfterKashida = pSI->GetKashidaPositions();
    CPPUNIT_ASSERT_EQUAL(size_t(0), stAfterKashida.size());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf71583)
{
    //create a document, multiple pages, some restart page counting
    // insert page count in section fields and check the calculated
    // values

    auto insertParagraphAndBreak = [](sal_Int32& nParagraph, SwWrtShell& rWrtSh,
                                      uno::Reference<lang::XComponent> xComponent,
                                      const sal_uInt16 nPages) {
        for (sal_uInt16 nPage = 0; nPage < nPages; ++nPage)
        {
            rWrtSh.Insert(u"Paragraph "_ustr);
            rWrtSh.Insert(OUString::number(nParagraph));
            ++nParagraph;
            auto xModel(xComponent.queryThrow<frame::XModel>());
            auto xFactory(xComponent.queryThrow<lang::XMultiServiceFactory>());
            auto xTextViewCursorSupplier(
                xModel->getCurrentController().queryThrow<text::XTextViewCursorSupplier>());
            auto xTextRangeCursor(
                xTextViewCursorSupplier->getViewCursor().queryThrow<text::XTextRange>());

            uno::Reference<text::XTextField> xTextField(
                xFactory->createInstance(u"com.sun.star.text.TextField.PageCountRange"_ustr),
                uno::UNO_QUERY);
            xTextRangeCursor->getText()->insertTextContent(xTextRangeCursor, xTextField, false);

            rWrtSh.SttEndDoc(false);
            rWrtSh.SplitNode();
            if (nPage < nPages - 1)
                rWrtSh.InsertPageBreak();
            else
            {
                std::optional<sal_uInt16> oPageNumber = 1;
                UIName sPageStyle(u"Default Page Style"_ustr);
                rWrtSh.InsertPageBreak(&sPageStyle, oPageNumber);
            }
        }
    };

    auto checkDocument = [this]() {
        auto checkFieldContent = [this](uno::Reference<text::XText>& xBodyText, sal_uInt16 nPara,
                                        rtl::OUString sSymbol) {
            uno::Reference<text::XTextRange> xPara(getParagraphOfText(nPara, xBodyText));
            const uno::Reference<text::XTextRange> xRun = getRun(xPara, 2);
            uno::Reference<text::XTextField> xTextField
                = getProperty<uno::Reference<text::XTextField>>(xRun, u"TextField"_ustr);
            OUString sPresentation = xTextField->getPresentation(false);
            CPPUNIT_ASSERT_EQUAL(sSymbol, sPresentation);
        };

        auto xModel(mxComponent.queryThrow<text::XTextDocument>());
        uno::Reference<text::XText> xBodyText = xModel->getText();
        OUString sCompare = u"B"_ustr;

        for (sal_uInt16 nPara = 1; nPara < 25; nPara += 2)
        {
            if (nPara == 5)
                sCompare = u"D"_ustr;
            if (nPara == 13)
                sCompare = u"F"_ustr;
            checkFieldContent(xBodyText, nPara, sCompare);
        }
    };
    createSwDoc();
    SwWrtShell* pWrtShell = nullptr;
    {
        pWrtShell = getSwDocShell()->GetWrtShell();
        CPPUNIT_ASSERT(pWrtShell);

        sal_Int32 nParagraph = 0;
        for (int i = 1; i <= 3; ++i)
            insertParagraphAndBreak(nParagraph, *pWrtShell, mxComponent, i * 2);
        checkDocument();
    }

    {
        saveAndReload(TestFilter::ODT);
        checkDocument();
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf167006)
{
    createSwDoc("embeddedPPTX.docx");
    selectShape(1);
    // attempt to edit the OLE object. This must not crash.
    getSwDocShell()->GetWrtShell()->LaunchOLEObj();
}

// tdf#168355 Page break with page style with page number in table properties is wrong
CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf168355)
{
    createSwDoc("tdf168355.odt");
    SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
    CPPUNIT_ASSERT(pTextDoc);
    SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
    const SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
    const SwPageFrame* pPageFrm = static_cast<const SwPageFrame*>(pLayout->Lower());
    CPPUNIT_ASSERT(pPageFrm);
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pPageFrm->GetVirtPageNum());
    pPageFrm = static_cast<const SwPageFrame*>(pPageFrm->GetNext());
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), pPageFrm->GetVirtPageNum());
    pPageFrm = static_cast<const SwPageFrame*>(pPageFrm->GetNext());
    // this used to be page 3
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pPageFrm->GetVirtPageNum());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testSplitFloattableGetCurWord)
{
    // Given a document with a floating table that splits across pages: the anchor paragraph has
    // text; it's split together with its floating table; and its first frame has no SwParaPortion
    // (only the last follow frame has it).
    createSwDoc("floattable-nosection-7in.docx");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Go to end (the anchor paragraph for the floating table)
    pWrtShell->SttEndDoc(/*bStt=*/false);

    // Without the fix this would crash
    CPPUNIT_ASSERT_EQUAL(u"End"_ustr, pWrtShell->GetCurWord());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testStyleApplyForwardsOriginalName)
{
    // Given a fresh document, the first paragraph is "Standard".
    createSwDoc();
    emulateTyping(u"some text");
    CPPUNIT_ASSERT_EQUAL(u"Standard"_ustr,
                         getProperty<OUString>(getParagraph(1), u"ParaStyleName"_ustr));

    // Dispatch StyleApply with a family name the XStyleFamiliesSupplier
    // doesn't recognize. The try/catch in ExecStyleSheet swallows the
    // resulting throw, which is the same code path the user-visible bug
    // takes when the caller passes an already-localized DisplayName
    // instead of the programmatic name (in a German build, "Überschrift 1"
    // is not resolvable through XStyleFamily::getByName, which only knows
    // programmatic names). The downstream lookup can still resolve it via
    // SwStyleNameMapper.
    dispatchCommand(mxComponent, u".uno:StyleApply"_ustr,
                    comphelper::InitPropertySequence({
                        { "Style", cpo::uno::Any(u"Heading 1"_ustr) },
                        { "FamilyName", cpo::uno::Any(u"BogusFamily"_ustr) },
                    }));
    // Without the fix in place, this would have failed with
    // - Expected: Heading 1
    // - Actual  : Standard
    // because the catch left no SID_STYLE_APPLY arg for the fallthrough
    // and ApplyStyleSheetRequest returned without applying anything.
    CPPUNIT_ASSERT_EQUAL(u"Heading 1"_ustr,
                         getProperty<OUString>(getParagraph(1), u"ParaStyleName"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testAutocorrectSingleQuoteUndo)
{
    // Typing an apostrophe autocorrects it to a typographic one. Undoing the
    // autocorrect must remove the whole quote in a single step, not leave a
    // straight quote behind.
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwXTextDocument* pTextDoc = getSwTextDoc();

    auto getText = [&]() -> OUString {
        return pWrtShell->getShellCursor(false)->GetPoint()->GetNode().GetTextNode()->GetText();
    };

    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 'c', 0);
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 'e', 0);
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '\'', 0);
    Scheduler::ProcessEventsToIdle();

    // The straight apostrophe became a typographic one (U+2019).
    CPPUNIT_ASSERT_EQUAL(u"ce\u2019"_ustr, getText());

    pWrtShell->Undo();
    Scheduler::ProcessEventsToIdle();

    // One undo removes the quote entirely. Without the fix the quote turned
    // back into a straight one (U+0027) and only a second undo removed it.
    CPPUNIT_ASSERT_EQUAL(u"ce"_ustr, getText());

    // The same holds when more is typed after the quote and it is undone: the
    // undo that reaches the quote removes it rather than exposing a straight
    // quote in between.
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, '\'', 0);
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 'u', 0);
    pTextDoc->postKeyEvent(COKitKeyEventType::DOWN, 'n', 0);
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"ce\u2019un"_ustr, getText());

    pWrtShell->Undo();
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"ce\u2019"_ustr, getText());

    pWrtShell->Undo();
    Scheduler::ProcessEventsToIdle();
    CPPUNIT_ASSERT_EQUAL(u"ce"_ustr, getText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenRedlineDeleteWordUndo)
{
    // Deleting a word that sits inside an existing tracked formatting change,
    // with change tracking on but tracked changes hidden, then undoing the
    // deletion must show the word again in the layout.
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwDoc* pDoc = getSwDoc();

    pWrtShell->Insert(u"foo Gegenstand bar"_ustr);

    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    CPPUNIT_ASSERT(pDoc->getIDocumentRedlineAccess().IsRedlineOn());

    // A tracked formatting change over the whole paragraph, so deleting the
    // word later splits that change into three parts.
    pWrtShell->SelAll();
    dispatchCommand(mxComponent, u".uno:Bold"_ustr, {});

    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});
    CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines());

    // Select the word "Gegenstand" ("foo " is 4 characters, the word is 10).
    pWrtShell->SttEndDoc(/*bStart=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 10, /*bBasicCall=*/false);

    auto bWordShown = [this]() {
        return countXPathNodes(parseLayoutDump(), "//*[contains(@portion,'Gegenstand')]") > 0;
    };
    CPPUNIT_ASSERT(bWordShown());

    dispatchCommand(mxComponent, u".uno:Delete"_ustr, {});
    // With the tracked deletion hidden, the word is no longer rendered.
    CPPUNIT_ASSERT(!bWordShown());

    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    // Undo removes the deletion, so the word is rendered again without having
    // to toggle the display of tracked changes. Before the fix the merged
    // paragraph kept its stale extents and the word stayed hidden.
    CPPUNIT_ASSERT(bWordShown());
}

} // end of anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
