/* -*- 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 <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/document/XDocumentInsertable.hpp>
#include <com/sun/star/drawing/GraphicExportFilter.hpp>
#include <com/sun/star/i18n/TextConversionOption.hpp>
#include <swmodeltestbase.hxx>
#include <ndtxt.hxx>
#include <wrtsh.hxx>
#include <shellio.hxx>
#include <expfld.hxx>
#include <drawdoc.hxx>
#include <redline.hxx>
#include <fmtclds.hxx>
#include <dcontact.hxx>
#include <view.hxx>
#include <hhcwrp.hxx>
#include <swacorr.hxx>
#include <swmodule.hxx>
#include <modcfg.hxx>
#include <editeng/acorrcfg.hxx>
#include <unotools/streamwrap.hxx>
#include <unocrsrhelper.hxx>
#include <com/sun/star/text/VertOrientation.hpp>
#include <com/sun/star/text/XDefaultNumberingProvider.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <vcl/TypeSerializer.hxx>

#include <svx/svdpage.hxx>
#include <svx/svdview.hxx>
#include <svx/svxids.hrc>

#include <editeng/eeitem.hxx>
#include <editeng/scriptsetitem.hxx>
#include <editeng/wghtitem.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <UndoManager.hxx>
#include <frmatr.hxx>

#include <com/sun/star/text/TextMarkupType.hpp>
#include <osl/file.hxx>
#include <comphelper/propertysequence.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/dispatch.hxx>
#include <vcl/scheduler.hxx>
#include <sfx2/watermarkitem.hxx>
#include <sfx2/docfile.hxx>
#include <fmthdft.hxx>
#include <iodetect.hxx>
#include <comphelper/processfactory.hxx>
#include <unotxdoc.hxx>
#include <swdtflvr.hxx>
#include <sortedobjs.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>

using namespace css;
using namespace css::uno;

namespace
{
void lcl_selectCharacters(SwPaM& rPaM, sal_Int32 first, sal_Int32 end)
{
    rPaM.GetPoint()->nContent.Assign(rPaM.GetPointContentNode(), first);
    rPaM.SetMark();
    rPaM.GetPoint()->nContent.Assign(rPaM.GetPointContentNode(), end);
}
} //namespace

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

    std::unique_ptr<SwTextBlocks> readDOCXAutotext(
        std::u16string_view sFileName, bool bEmpty = false);
    void testRedlineFrame(char const*const file);
};

std::unique_ptr<SwTextBlocks> SwUiWriterTest::readDOCXAutotext(std::u16string_view sFileName, bool bEmpty)
{
    createTempCopy(sFileName);

    SfxMedium aSrcMed(maTempFile.GetURL(), StreamMode::STD_READ);
    createSwDoc();
    SwDoc* pDoc = getSwDoc();

    SwReader aReader(aSrcMed, maTempFile.GetURL(), pDoc);
    Reader* pDOCXReader = SwReaderWriter::GetDOCXReader();
    auto pGlossary = std::make_unique<SwTextBlocks>(maTempFile.GetURL());

    CPPUNIT_ASSERT(pDOCXReader != nullptr);
    CPPUNIT_ASSERT_EQUAL(!bEmpty, aReader.ReadGlossaries(*pDOCXReader, *pGlossary, false));

    return pGlossary;
}

void SwUiWriterTest::testRedlineFrame(char const*const file)
{
    createSwDoc(file);
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    // there is exactly one frame
    CPPUNIT_ASSERT_EQUAL(1, getShapes());

    RedlineFlags nMode = pWrtShell->GetRedlineFlags();
    CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete);

    // hide delete redlines
    pWrtShell->SetRedlineFlags(nMode & ~RedlineFlags::ShowDelete);

    // there is still exactly one frame
    CPPUNIT_ASSERT_EQUAL(1, getShapes());

    pWrtShell->SetRedlineFlags(nMode); // show again

    // there is still exactly one frame
    CPPUNIT_ASSERT_EQUAL(1, getShapes());
}

//Replacement tests

constexpr OUString ORIGINAL_REPLACE_CONTENT(u"toto titi tutu"_ustr);
constexpr OUString EXPECTED_REPLACE_CONTENT(u"toto toto tutu"_ustr);

// Chinese conversion tests

const sal_Unicode CHINESE_TRADITIONAL_CONTENT(0x9F8D);
const sal_Unicode CHINESE_SIMPLIFIED_CONTENT(0x9F99);
constexpr OUString NON_CHINESE_CONTENT(u"Hippopotamus"_ustr);

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testReplaceForward)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();

    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();

    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);

    pDoc->getIDocumentContentOperations().InsertString(aPaM, ORIGINAL_REPLACE_CONTENT);

    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    lcl_selectCharacters(aPaM, 5, 9);
    pDoc->getIDocumentContentOperations().ReplaceRange(aPaM, u"toto"_ustr, false);

    CPPUNIT_ASSERT_EQUAL(EXPECTED_REPLACE_CONTENT, pTextNode->GetText());

    rUndoManager.Undo();

    CPPUNIT_ASSERT_EQUAL(ORIGINAL_REPLACE_CONTENT, pTextNode->GetText());
}


CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtCharStartOutside0)
{
    testRedlineFrame("redlineFrame.fodt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtCharStartOutside)
{
    testRedlineFrame("redlineFrame_at_char_start_outside.fodt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtCharStartInside)
{
    testRedlineFrame("redlineFrame_at_char_start_inside.fodt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtParaStartOutside)
{
    testRedlineFrame("redline_fly_duplication_at_para_start_outside.fodt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtParaEndInside)
{
    testRedlineFrame("redline_fly_duplication_at_para_end_inside.fodt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtParaOneParagraph)
{
    // test ALLFLYS flag: oddly enough it didn't fail as fodt but failed as odt...
    testRedlineFrame("redline_fly_at_para_one_paragraph.odt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testRedlineFrameAtPara2ndParagraph)
{
    // lost via the buggy increment in Copy
    testRedlineFrame("redline_fly_duplication_at_para_2nd_paragraph.fodt");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testThreadedException)
{
    SvFileStream aFileStream(createFileURL(u"threadedException.fodt"), StreamMode::READ);

    //threaded reading only kicks in if there is sufficient buffer to make it worthwhile, so read
    //from a SvFileStream to ensure that
    bool bRes = TestImportFODT(aFileStream);

    CPPUNIT_ASSERT(!bRes);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf149595)
{
    createSwDoc("demo91.fodt");

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

    // all 4 shapes are on the 2nd paragraph
    CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() == nullptr);
    CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
    CPPUNIT_ASSERT_EQUAL(size_t(4), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());

    {
        pWrtShell->Down(false);
        pWrtShell->EndPara(/*bSelect=*/true);
        dispatchCommand(mxComponent, u".uno:Cut"_ustr, {});

        // one shape is anchored in the middle, others at the start/end/at-para
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() == nullptr);
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(3), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());

        pWrtShell->Up(false);
        dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});

        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs()->size());
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(3), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());

        pWrtShell->Undo();
        pWrtShell->Undo();

        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() == nullptr);
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(4), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());
    }

    // now try the same with redlining enabled - should be the same result
    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    {
        pWrtShell->Down(false);
        pWrtShell->SttPara(/*bSelect=*/false);
        pWrtShell->EndPara(/*bSelect=*/true);
        dispatchCommand(mxComponent, u".uno:Cut"_ustr, {});

        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() == nullptr);
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
        // problem was that this deleted all at-char flys, even at the start/end
        CPPUNIT_ASSERT_EQUAL(size_t(3), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());

        pWrtShell->Up(false);
        dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});

        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs()->size());
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(3), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());

        pWrtShell->Undo();
        pWrtShell->Undo();

        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs() == nullptr);
        CPPUNIT_ASSERT(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs() != nullptr);
        CPPUNIT_ASSERT_EQUAL(size_t(4), pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetNext()->GetDrawObjs()->size());
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf149548)
{
    createSwDoc("forum-mso-en-13192-min.docx");
    SwDoc* pDoc = getSwDoc();

    for (SwRangeRedline const*const pRedline : pDoc->getIDocumentRedlineAccess().GetRedlineTable())
    {
        if (pRedline->GetType() == RedlineType::Delete)
        {
            int nLevel(0);
            for (SwNodeIndex index = pRedline->Start()->nNode; index <= pRedline->End()->nNode; ++index)
            {
                switch (index.GetNode().GetNodeType())
                {
                    case SwNodeType::Start:
                    case SwNodeType::Table:
                    case SwNodeType::Section:
                        ++nLevel;
                        break;
                    case SwNodeType::End:
                        CPPUNIT_ASSERT_MESSAGE("bad overlapping redline", nLevel != 0);
                        --nLevel;
                        break;
                    default:
                        break;
                }
            }
            CPPUNIT_ASSERT_EQUAL_MESSAGE("bad overlapping redline", int(0), nLevel);
        }
    }

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

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

    // this was a use-after-free on nodes deleted by Copy
    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testPasteTableAtFlyAnchor)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    SwFormatAnchor anchor(RndStdIds::FLY_AT_CHAR);
    anchor.SetAnchor(pWrtShell->GetCursor()->GetPoint());
    SfxItemSet flySet(pDoc->GetAttrPool(), svl::Items<RES_ANCHOR, RES_ANCHOR>);
    flySet.Put(anchor);
    SwFlyFrameFormat const* pFly = dynamic_cast<SwFlyFrameFormat const*>(
            pWrtShell->NewFlyFrame(flySet, /*bAnchValid=*/true));
    CPPUNIT_ASSERT(pFly != nullptr);
    CPPUNIT_ASSERT(pFly->GetFrame() != nullptr);
    pWrtShell->SelFlyGrabCursor();
    pWrtShell->GetDrawView()->UnmarkAll();
    CPPUNIT_ASSERT(pWrtShell->GetCurrFlyFrame() != nullptr);

    // insert table in fly
    SwInsertTableOptions tableOpt(SwInsertTableFlags::DefaultBorder, 0);
    pWrtShell->InsertTable(tableOpt, 2, 2);

    // select table
    pWrtShell->SelAll();

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

    // move cursor back to body
    pWrtShell->ClearMark();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    CPPUNIT_ASSERT(!pWrtShell->GetCurrFlyFrame());

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

    pWrtShell->SttEndDoc(/*bStt=*/true);
    CPPUNIT_ASSERT(pWrtShell->IsCursorInTable());
    CPPUNIT_ASSERT(!pFly->GetAnchor().GetContentAnchor()->GetNode().FindTableNode());

    pWrtShell->Undo();

    pWrtShell->SttEndDoc(/*bStt=*/true);
    CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable());
    CPPUNIT_ASSERT(!pFly->GetAnchor().GetContentAnchor()->GetNode().FindTableNode());

    // the problem was that Redo moved the fly anchor into the first table cell
    pWrtShell->Redo();

    pWrtShell->SttEndDoc(/*bStt=*/true);
    CPPUNIT_ASSERT(pWrtShell->IsCursorInTable());
    CPPUNIT_ASSERT(!pFly->GetAnchor().GetContentAnchor()->GetNode().FindTableNode());

    pWrtShell->Undo();

    pWrtShell->SttEndDoc(/*bStt=*/true);
    CPPUNIT_ASSERT(!pWrtShell->IsCursorInTable());
    CPPUNIT_ASSERT(!pFly->GetAnchor().GetContentAnchor()->GetNode().FindTableNode());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testCopyPastePageBreak)
{
    createSwDoc("pagebreak-source.fodt");

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT_EQUAL(tools::Long(5669), pWrtShell->GetLayout()->GetLower()->getFramePrintArea().Top());

    pWrtShell->SelAll();
    dispatchCommand(mxComponent, u".uno:Copy"_ustr, {});

    createSwDoc("pagebreak-target.fodt");
    SwDoc* pDoc = getSwDoc();
    pWrtShell = getSwDocShell()->GetWrtShell();

    CPPUNIT_ASSERT_EQUAL(1, getParagraphs());
    CPPUNIT_ASSERT_EQUAL(u"WithMargin"_ustr, getProperty<OUString>(getParagraph(1), u"PageDescName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"TargetSection"_ustr, pWrtShell->GetCurrSection()->GetSectionName().toString());
    // page style WithMargin is used
    CPPUNIT_ASSERT_EQUAL(tools::Long(5669), pWrtShell->GetLayout()->GetLower()->getFramePrintArea().Top());

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

    CPPUNIT_ASSERT_EQUAL(2, getParagraphs());
    CPPUNIT_ASSERT_EQUAL(u"WithMargin"_ustr, getProperty<OUString>(getParagraph(1), u"PageDescName"_ustr));
    CPPUNIT_ASSERT_EQUAL(size_t(2), pDoc->GetSections().size());
    CPPUNIT_ASSERT_EQUAL(u"SourceSection"_ustr, pWrtShell->GetCurrSection()->GetSectionName().toString());
    // the problem was that there was a page break now
    CPPUNIT_ASSERT_EQUAL(1, getPages());
    // page style WithMargin is used
    CPPUNIT_ASSERT_EQUAL(tools::Long(5669), pWrtShell->GetLayout()->GetLower()->getFramePrintArea().Top());

    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(1, getParagraphs());
    CPPUNIT_ASSERT_EQUAL(u"WithMargin"_ustr, getProperty<OUString>(getParagraph(1), u"PageDescName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"TargetSection"_ustr, pWrtShell->GetCurrSection()->GetSectionName().toString());
    CPPUNIT_ASSERT_EQUAL(1, getPages());
    // page style WithMargin is used
    CPPUNIT_ASSERT_EQUAL(tools::Long(5669), pWrtShell->GetLayout()->GetLower()->getFramePrintArea().Top());

    pWrtShell->Redo();
    CPPUNIT_ASSERT_EQUAL(2, getParagraphs());
    CPPUNIT_ASSERT_EQUAL(u"WithMargin"_ustr, getProperty<OUString>(getParagraph(1), u"PageDescName"_ustr));
    CPPUNIT_ASSERT_EQUAL(size_t(2), pDoc->GetSections().size());
    CPPUNIT_ASSERT_EQUAL(u"SourceSection"_ustr, pWrtShell->GetCurrSection()->GetSectionName().toString());
    CPPUNIT_ASSERT_EQUAL(1, getPages());
    // page style WithMargin is used
    CPPUNIT_ASSERT_EQUAL(tools::Long(5669), pWrtShell->GetLayout()->GetLower()->getFramePrintArea().Top());

    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(1, getParagraphs());
    CPPUNIT_ASSERT_EQUAL(u"WithMargin"_ustr, getProperty<OUString>(getParagraph(1), u"PageDescName"_ustr));
    CPPUNIT_ASSERT_EQUAL(u"TargetSection"_ustr, pWrtShell->GetCurrSection()->GetSectionName().toString());
    CPPUNIT_ASSERT_EQUAL(1, getPages());
    // page style WithMargin is used
    CPPUNIT_ASSERT_EQUAL(tools::Long(5669), pWrtShell->GetLayout()->GetLower()->getFramePrintArea().Top());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testBookmarkCopy)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();

    // add text and bookmark
    IDocumentMarkAccess & rIDMA(*pDoc->getIDocumentMarkAccess());
    IDocumentContentOperations & rIDCO(pDoc->getIDocumentContentOperations());
    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwCursor aPaM(SwPosition(aIdx), nullptr);
    rIDCO.InsertString(aPaM, u"foo"_ustr);
    rIDCO.SplitNode(*aPaM.GetPoint(), false);
    rIDCO.InsertString(aPaM, u"bar"_ustr);
    aPaM.SetMark();
    aPaM.MovePara(GoCurrPara, fnParaStart);
    rIDMA.makeMark(aPaM, SwMarkName(u"Mark"_ustr), IDocumentMarkAccess::MarkType::BOOKMARK,
            ::sw::mark::InsertMode::New);
    aPaM.Exchange();
    aPaM.DeleteMark();
    rIDCO.SplitNode(*aPaM.GetPoint(), false);
    rIDCO.InsertString(aPaM, u"baz"_ustr);

    // copy range
    rIDCO.SplitNode(*aPaM.GetPoint(), false);
    SwPosition target(*aPaM.GetPoint());
    aPaM.Move(fnMoveBackward, GoInContent);
    aPaM.SetMark();
    aPaM.SttEndDoc(true/*start*/);
    aPaM.Move(fnMoveForward, GoInContent); // partially select 1st para

    rIDCO.CopyRange(aPaM, target, SwCopyFlags::CheckPosInFly);

    // check bookmark was copied to correct position
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), rIDMA.getBookmarksCount());
    for (auto it(rIDMA.getBookmarksBegin()); it != rIDMA.getBookmarksEnd(); ++it)
    {
        OUString markText(SwPaM((*it)->GetMarkPos(), (*it)->GetOtherMarkPos()).GetText());
        CPPUNIT_ASSERT_EQUAL(u"bar"_ustr, markText);
    }

    // copy 2nd time, such that bCanMoveBack is false in CopyImpl
    SwPaM aCopyPaM(*aPaM.GetMark(), *aPaM.GetPoint());
    aPaM.SttEndDoc(true/*start*/);
    rIDCO.SplitNode(*aPaM.GetPoint(), false);
    aPaM.SttEndDoc(true/*start*/);

    rIDCO.CopyRange(aCopyPaM, *aPaM.GetPoint(), SwCopyFlags::CheckPosInFly);

    // check bookmark was copied to correct position
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), rIDMA.getBookmarksCount());
    for (auto it(rIDMA.getBookmarksBegin()); it != rIDMA.getBookmarksEnd(); ++it)
    {
        OUString markText(SwPaM((*it)->GetMarkPos(), (*it)->GetOtherMarkPos()).GetText());
        CPPUNIT_ASSERT_EQUAL(u"bar"_ustr, markText);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFormulaNumberWithGroupSeparator)
{
    createSwDoc("tdf125154.odt");
    dispatchCommand(mxComponent, u".uno:UpdateAll"_ustr, {});
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SttEndDoc(true);
    SwField const* pField;

    pField = pWrtShell->GetCurField();
    CPPUNIT_ASSERT_EQUAL(u"1000"_ustr, pField->GetFormula());
    CPPUNIT_ASSERT_EQUAL(u"1.000"_ustr, pField->ExpandField(true, nullptr));
    pWrtShell->GoNextCell();
    CPPUNIT_ASSERT_EQUAL(u"10000"_ustr, pWrtShell->GetCursor()->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
    pWrtShell->GoNextCell();
    pField = pWrtShell->GetCurField();
    CPPUNIT_ASSERT_EQUAL(u"test"_ustr, pField->GetFormula());
    CPPUNIT_ASSERT_EQUAL(u"1.000"_ustr, pField->ExpandField(true, nullptr));
    pWrtShell->GoNextCell();
    // the problem was that this was 0
    CPPUNIT_ASSERT_EQUAL(u"10000"_ustr, pWrtShell->GetCursor()->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
    pWrtShell->Down(false);
    pWrtShell->SttPara(false);
    pField = pWrtShell->GetCurField();
    CPPUNIT_ASSERT_EQUAL(u"1000*10%"_ustr, pField->GetFormula());
    CPPUNIT_ASSERT_EQUAL(u"100"_ustr, pField->ExpandField(true, nullptr));
    pWrtShell->Down(false);
    pField = pWrtShell->GetCurField();
    CPPUNIT_ASSERT_EQUAL(u"5.000*10%"_ustr, pField->GetFormula());
    // the problem was that this was 0
    CPPUNIT_ASSERT_EQUAL(u"500"_ustr, pField->ExpandField(true, nullptr));
    pWrtShell->Down(false);
    pField = pWrtShell->GetCurField();
    CPPUNIT_ASSERT_EQUAL(u"5.000*10%"_ustr, pField->GetFormula());
    // the problem was that this was
    CPPUNIT_ASSERT_EQUAL(u"500"_ustr, pField->ExpandField(true, nullptr));
    pWrtShell->Down(false);
    pField = pWrtShell->GetCurField();
    CPPUNIT_ASSERT_EQUAL(u"5000*10%"_ustr, pField->GetFormula());
    CPPUNIT_ASSERT_EQUAL(u"500"_ustr, pField->ExpandField(true, nullptr));
    pWrtShell->Down(false);
    CPPUNIT_ASSERT_EQUAL(u"-100,00 €"_ustr, pWrtShell->GetCursor()->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
    pWrtShell->GoNextCell();
    // tdf#42518 the problem was that this was 1.900,00 €
    CPPUNIT_ASSERT_EQUAL(u"** Expression is faulty **"_ustr, pWrtShell->GetCursor()->GetPoint()->nNode.GetNode().GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testInsertFileInInputFieldException)
{
    createSwDoc();
    uno::Reference<text::XTextDocument> const xTextDoc(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XText> const xBody(xTextDoc->getText());
    uno::Reference<lang::XMultiServiceFactory> const xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextCursor> const xCursor(xBody->createTextCursor());
    uno::Reference<document::XDocumentInsertable> const xInsertable(xCursor, uno::UNO_QUERY);
    uno::Reference<text::XTextContent> const xContent(
        xFactory->createInstance(u"com.sun.star.text.textfield.Input"_ustr), uno::UNO_QUERY);
    xBody->insertTextContent(xCursor, xContent, false);
    xCursor->goLeft(1, false);
    // try to insert some random file
    // inserting even asserts in debug builds - document model goes invalid with input field split across 2 nodes
    CPPUNIT_ASSERT_THROW(xInsertable->insertDocumentFromURL(createFileURL(u"fdo75110.odt"), {}), uno::RuntimeException);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf67238)
{
    //create a new writer document
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    //insert a 3X3 table in the newly created document
    SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0);
    const SwTable& rTable = pWrtShell->InsertTable(TableOpt, 3, 3);
    //checking for the rows and columns
    uno::Reference<text::XTextTable> xTable(getParagraphOrTable(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable->getRows()->getCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable->getColumns()->getCount());
    //selecting the table
    pWrtShell->StartOfSection();
    pWrtShell->SelTable();
    //making the table protected
    pWrtShell->ProtectCells();
    //checking each cell's protection, it should be protected
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    //undo the changes, make cells [un]protected
    rUndoManager.Undo();
    //checking each cell's protection, it should be [un]protected
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    //redo the changes, make cells protected
    rUndoManager.Redo();
    //checking each cell's protection, it should be protected
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    //moving the cursor to the starting of the document
    pWrtShell->StartOfSection();
    //making the table [un]protected
    pWrtShell->SelTable();
    pWrtShell->UnProtectCells();
    //checking each cell's protection, it should be [un]protected
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    //undo the changes, make cells protected
    rUndoManager.Undo();
    //checking each cell's protection, it should be protected
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"A3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"B3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(((rTable.GetTableBox(u"C3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    //redo the changes, make cells [un]protected
    rUndoManager.Redo();
    //checking each cell's protection, it should be [un]protected
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"A3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"B3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C1"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C2"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
    CPPUNIT_ASSERT(!((rTable.GetTableBox(u"C3"_ustr))->GetFrameFormat()->GetProtect()).IsContentProtected());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf155685)
{
    createSwDoc("table-at-end-of-cell.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->GoNextCell();
    pWrtShell->GoNextCell();
    pWrtShell->GoNextCell();
    pWrtShell->SelAll();
    pWrtShell->Delete();
    // this crashed
    pWrtShell->Undo();
    pWrtShell->Undo();
    pWrtShell->Redo();
    // this crashed
    pWrtShell->Redo();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf147220)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pWrtShell->Insert(u"él"_ustr);

    // hide and enable
    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    CPPUNIT_ASSERT(pDoc->getIDocumentRedlineAccess().IsRedlineOn());
    CPPUNIT_ASSERT(
        IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags()));
    CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines());

    pWrtShell->GoStartSentence();
    pWrtShell->SetMark();
    pWrtShell->GoEndSentence();

    // this did not remove the original text from the layout
    pWrtShell->Replace(u"Él"_ustr, false);

    // currently the deleted text is before the replacement text, not sure if
    // that is really required
    CPPUNIT_ASSERT_EQUAL(u"élÉl"_ustr,
        pWrtShell->GetCursor()->GetPoint()->GetNode().GetTextNode()->GetText());
    CPPUNIT_ASSERT_EQUAL(u"Él"_ustr,
        static_cast<SwTextFrame const*>(pWrtShell->GetCursor()->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(nullptr))->GetText());

    SwRedlineTable const& rRedlines(pDoc->getIDocumentRedlineAccess().GetRedlineTable());
    CPPUNIT_ASSERT_EQUAL(SwRedlineTable::size_type(2), rRedlines.size());
    CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlines[0]->GetType());
    CPPUNIT_ASSERT_EQUAL(u"él"_ustr, rRedlines[0]->GetText());
    CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlines[1]->GetType());
    CPPUNIT_ASSERT_EQUAL(u"Él"_ustr, rRedlines[1]->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf135978)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pWrtShell->Insert(u"foobar"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"bazquux"_ustr);

    CPPUNIT_ASSERT(pWrtShell->IsEndOfDoc());

    SwFormatAnchor anchor(RndStdIds::FLY_AT_CHAR);
    anchor.SetAnchor(pWrtShell->GetCursor()->GetPoint());
    SfxItemSet flySet(pDoc->GetAttrPool(), svl::Items<RES_ANCHOR, RES_ANCHOR>);
    flySet.Put(anchor);
    SwFlyFrameFormat const* pFly = dynamic_cast<SwFlyFrameFormat const*>(
            pWrtShell->NewFlyFrame(flySet, /*bAnchValid=*/true));
    CPPUNIT_ASSERT(pFly != nullptr);
    CPPUNIT_ASSERT(pFly->GetFrame() != nullptr);
    // move cursor back to body
    pWrtShell->SttEndDoc(/*bStt=*/false);

    // hide and enable
    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});

    CPPUNIT_ASSERT(pDoc->getIDocumentRedlineAccess().IsRedlineOn());
    CPPUNIT_ASSERT(
        IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags()));
    CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines());

    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 6, /*bBasicCall=*/false);
    pWrtShell->Delete();

    // now split
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->SplitNode();
    CPPUNIT_ASSERT(pFly->GetFrame() != nullptr);

    // the problem was that undo removed the fly frame from the layout
    pWrtShell->Undo();
    CPPUNIT_ASSERT(pFly->GetFrame() != nullptr);

    pWrtShell->Redo();
    CPPUNIT_ASSERT(pFly->GetFrame() != nullptr);

    pWrtShell->Undo();
    CPPUNIT_ASSERT(pFly->GetFrame() != nullptr);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo75110)
{
    createSwDoc("fdo75110.odt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pWrtShell->SelAll();
    // The problem was that SwEditShell::DeleteSel() what this Delete() invokes took the wrong selection...
    pWrtShell->Delete();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    // ... so this Undo() call resulted in a crash.
    rUndoManager.Undo();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo75898)
{
    createSwDoc("fdo75898.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SelAll();
    pWrtShell->InsertRow(1, true);
    pWrtShell->InsertRow(1, true);

    // Now check if the table has 3 lines.
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
    SwTableNode* pTableNode = pShellCursor->Start()->GetNode().FindTableNode();
    // This was 1, when doing the same using the UI, Writer even crashed.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pTableNode->GetTable().GetTabLines().size());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testReplaceBackward)
{
    //Regression test of fdo#70143
    //EDITING: undo search&replace corrupt text when searching backward
    createSwDoc();
    SwDoc* pDoc = getSwDoc();

    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();

    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);

    pDoc->getIDocumentContentOperations().InsertString(aPaM, u"toto titi tutu"_ustr);
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    lcl_selectCharacters(aPaM, 9, 5);

    pDoc->getIDocumentContentOperations().ReplaceRange(aPaM, u"toto"_ustr, false);

    CPPUNIT_ASSERT_EQUAL(EXPECTED_REPLACE_CONTENT, pTextNode->GetText());

    rUndoManager.Undo();

    CPPUNIT_ASSERT_EQUAL(ORIGINAL_REPLACE_CONTENT, pTextNode->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo69893)
{
    createSwDoc("fdo69893.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pWrtShell->SelAll(); // A1 is empty -> selects the whole table.
    pWrtShell->SelAll(); // Selects the whole document.

    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
    SwTextNode& rEnd = dynamic_cast<SwTextNode&>(pShellCursor->End()->GetNode());
    // Selection did not include the para after table, this was "B1".
    CPPUNIT_ASSERT_EQUAL(u"Para after table."_ustr, rEnd.GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo70807)
{
    createSwDoc("fdo70807.odt");

    uno::Reference<container::XIndexAccess> xStylesIter(getStyles(u"PageStyles"_ustr), uno::UNO_QUERY);

    for (sal_Int32 i = 0; i < xStylesIter->getCount(); ++i)
    {
        uno::Reference<style::XStyle> xStyle(xStylesIter->getByIndex(i), uno::UNO_QUERY);

        bool expectedUsedStyle = false;
        bool expectedUserDefined = false;

        OUString styleName(xStyle->getName());

        // just these styles are user defined styles
        if (styleName == "pagestyle1" || styleName == "pagestyle2")
            expectedUserDefined = true;

        // just these styles are used in the document
        if (styleName == "Right Page" || styleName == "pagestyle1" || styleName == "pagestyle2")
            expectedUsedStyle = true;

        CPPUNIT_ASSERT_EQUAL(expectedUserDefined, xStyle->isUserDefined());
        CPPUNIT_ASSERT_EQUAL(expectedUsedStyle, xStyle->isInUse());
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testImportRTF)
{
    // Insert "foobar" and position the cursor between "foo" and "bar".
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"foobar"_ustr);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 3, /*bBasicCall=*/false);

    // Insert the RTF at the cursor position.
    OString aData = "{\\rtf1 Hello world!\\par}"_ostr;
    SvMemoryStream aStream(const_cast<char*>(aData.getStr()), aData.getLength(), StreamMode::READ);
    SwReader aReader(aStream, OUString(), OUString(), *pWrtShell->GetCursor());
    Reader* pRTFReader = SwReaderWriter::GetRtfReader();
    CPPUNIT_ASSERT(pRTFReader != nullptr);
    CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aReader.Read(*pRTFReader).GetCode());

    SwNodeOffset nIndex = pWrtShell->GetCursor()->GetPointNode().GetIndex();
    CPPUNIT_ASSERT_EQUAL(u"fooHello world!"_ustr, pDoc->GetNodes()[nIndex - 1]->GetTextNode()->GetText());
    CPPUNIT_ASSERT_EQUAL(u"bar"_ustr, pDoc->GetNodes()[nIndex]->GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testExportRTF)
{
    // Insert "aaabbbccc" and select "bbb".
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"aaabbbccc"_ustr);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 3, /*bBasicCall=*/false);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);

    // Create the clipboard document.
    rtl::Reference<SwDoc> xClpDoc(new SwDoc());
    xClpDoc->SetClipBoard(true);
    pWrtShell->Copy(*xClpDoc);

    // And finally export it as RTF.
    WriterRef xWrt;
    SwReaderWriter::GetWriter(u"RTF", OUString(), xWrt);
    SvMemoryStream aStream;
    SwWriter aWrt(aStream, *xClpDoc);
    aWrt.Write(xWrt);

    OString aData(static_cast<const char*>(aStream.GetData()), aStream.GetSize());

    //Amusingly eventually there was a commit id with "ccc" in it, and so the rtf contained
    //{\*\generator LibreOfficeDev/4.4.0.0.alpha0$Linux_X86_64 LibreOffice_project/f70664ccc6837f2cc21a29bb4f44e41e100efe6b}
    //so the test fell over. so strip the generator tag
    sal_Int32 nGeneratorStart = aData.indexOf("{\\*\\generator ");
    CPPUNIT_ASSERT(nGeneratorStart != -1);
    sal_Int32 nGeneratorEnd = aData.indexOf('}', nGeneratorStart + 1);
    CPPUNIT_ASSERT(nGeneratorEnd != -1);
    aData = aData.replaceAt(nGeneratorStart, nGeneratorEnd-nGeneratorStart+1, "");

    CPPUNIT_ASSERT(aData.startsWith("{\\rtf1"));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aData.indexOf("aaa"));
    CPPUNIT_ASSERT(aData.indexOf("bbb") != -1);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aData.indexOf("ccc"));
    // Ensure there's no extra newline
    CPPUNIT_ASSERT(aData.endsWith("bbb}" SAL_NEWLINE_STRING "}"));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testDOCXAutoTextEmpty)
{
    // file contains normal content but no AutoText
    std::unique_ptr<SwTextBlocks> pGlossary = readDOCXAutotext(u"autotext-empty.dotx", true);
    CPPUNIT_ASSERT(pGlossary != nullptr);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testDOCXAutoTextMultiple)
{
    // file contains three AutoText entries
    std::unique_ptr<SwTextBlocks> pGlossary = readDOCXAutotext(u"autotext-multiple.dotx");

    // check entries count
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), pGlossary->GetCount());

    // check names of entries, sorted order
    CPPUNIT_ASSERT_EQUAL(u"Anothercomplex"_ustr, pGlossary->GetLongName(0));
    CPPUNIT_ASSERT_EQUAL(u"Multiple"_ustr, pGlossary->GetLongName(1));
    CPPUNIT_ASSERT_EQUAL(u"Second  Autotext"_ustr, pGlossary->GetLongName(2));

    // check if previously loaded content is correct (eg. doesn't contain title)
    SwDoc* pDoc = pGlossary->GetDoc();
    CPPUNIT_ASSERT(pDoc != nullptr);

    SwNodeIndex aDocEnd(pDoc->GetNodes().GetEndOfContent());
    SwNodeIndex aStart(*aDocEnd.GetNode().StartOfSectionNode(), 1);

    CPPUNIT_ASSERT(aStart < aDocEnd);

    // first line
    SwNode& rNode = aStart.GetNode();
    CPPUNIT_ASSERT(rNode.IsTextNode());
    SwTextNode& rTextNode = *rNode.GetTextNode();
    CPPUNIT_ASSERT_EQUAL(u"Another "_ustr, rTextNode.GetText());

    // Make sure that autotext does not set a custom page style, leading to an unexpected page break
    // on insertion.
    // Without the accompanying fix in place, this test would have failed: the text node had an
    // attribute set containing a page style item.
    CPPUNIT_ASSERT(!rTextNode.HasSwAttrSet() || !rTextNode.GetSwAttrSet().HasItem(RES_PAGEDESC));

    // last line
    SwNodeIndex aLast(*aDocEnd.GetNode().EndOfSectionNode(), -1);
    SwNode& rLastNode = aLast.GetNode();
    CPPUNIT_ASSERT_EQUAL(u"complex"_ustr, rLastNode.GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testDOTMAutoText)
{
    // this is dotm file difference is that in the dotm
    // there are no empty paragraphs at the end of each entry
    std::unique_ptr<SwTextBlocks> pGlossary = readDOCXAutotext(u"autotext-dotm.dotm");

    SwDoc* pDoc = pGlossary->GetDoc();
    CPPUNIT_ASSERT(pDoc != nullptr);

    // check if content is correct
    SwNodeIndex aDocEnd(pDoc->GetNodes().GetEndOfContent());
    SwNodeIndex aStart(*aDocEnd.GetNode().StartOfSectionNode(), 1);
    SwNode& rNode = aStart.GetNode();
    CPPUNIT_ASSERT_EQUAL(u"paragraph"_ustr, rNode.GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testDOCXAutoTextGallery)
{
    // this file contains one AutoText entry and other
    // entries which are not AutoText (have different "gallery" value)
    std::unique_ptr<SwTextBlocks> pGlossary = readDOCXAutotext(u"autotext-gallery.dotx");

    SwDoc* pDoc = pGlossary->GetDoc();
    CPPUNIT_ASSERT(pDoc != nullptr);

    // check entries count
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pGlossary->GetCount());

    // check entry name (if not contains gallery type)
    CPPUNIT_ASSERT_EQUAL(u"Multiple"_ustr, pGlossary->GetLongName(0));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testWatermarkDOCX)
{
    createSwDoc("watermark.docx");
    SwDocShell* pDocShell = getSwDocShell();
    SfxPoolItemHolder aResult;
    SfxItemState eState = pDocShell->GetViewShell()->GetViewFrame().GetDispatcher()->QueryState(SID_WATERMARK, aResult);
    const SfxWatermarkItem* pWatermark(static_cast<const SfxWatermarkItem*>(aResult.getItem()));

    CPPUNIT_ASSERT(eState >= SfxItemState::DEFAULT);
    CPPUNIT_ASSERT(pWatermark);
    CPPUNIT_ASSERT_EQUAL(static_cast<unsigned short>(SID_WATERMARK), pWatermark->Which());

    CPPUNIT_ASSERT_EQUAL(u"CustomWatermark"_ustr, pWatermark->GetText());
    CPPUNIT_ASSERT_EQUAL(u"DejaVu Sans Light"_ustr, pWatermark->GetFont());
    CPPUNIT_ASSERT_EQUAL(sal_Int16(45), pWatermark->GetAngle());
    CPPUNIT_ASSERT_EQUAL(Color(0x548dd4), pWatermark->GetColor());
    CPPUNIT_ASSERT_EQUAL(sal_Int16(50), pWatermark->GetTransparency());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testWatermarkPosition)
{
    // tdf#108494 Watermark inserted in the document with page break was outside the first page
    const int aPagesInDocument = 2;
    const int aAdditionalPagesCount[] = {    0,     0,    1,     1,    5,     5,   20,    20 };
    const bool aChangeHeader[]        = { true, false, true, false, true, false, true, false };

    for (tools::ULong i = 0; i < sizeof(aAdditionalPagesCount) / sizeof(int); ++i)
    {
        int aPages = aPagesInDocument + aAdditionalPagesCount[i];

        // Empty document with one Page Break
        createSwDoc("watermark-position.odt");
        SwDoc* pDoc = getSwDoc();
        SwEditShell* pEditShell = pDoc->GetEditShell();
        CPPUNIT_ASSERT(pEditShell);
        SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
        uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(mxComponent,
                                                                         uno::UNO_QUERY);
        uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();

        // 1. Add additional page breaks
        for (int j = 0; j < aAdditionalPagesCount[i]; ++j)
            pWrtShell->InsertPageBreak();

        // 2. Change header state (On, Off, On)
        if (aChangeHeader[i])
        {
            SwPageDesc aDesc(pDoc->GetPageDesc(0));
            SwFrameFormat& rMaster = aDesc.GetMaster();
            rMaster.SetFormatAttr(SwFormatHeader(true));
            pDoc->ChgPageDesc(0, aDesc);

            aDesc = pDoc->GetPageDesc(0);
            SwFrameFormat& rMaster2 = aDesc.GetMaster();
            rMaster2.SetFormatAttr(SwFormatHeader(false));
            pDoc->ChgPageDesc(0, aDesc);

            aDesc = pDoc->GetPageDesc(0);
            SwFrameFormat& rMaster3 = aDesc.GetMaster();
            rMaster3.SetFormatAttr(SwFormatHeader(true));
            pDoc->ChgPageDesc(0, aDesc);
        }

        // 3. Insert Watermark
        SfxWatermarkItem aWatermark;
        aWatermark.SetText(u"Watermark"_ustr);
        aWatermark.SetFont(u"DejaVu Sans"_ustr);

        pEditShell->SetWatermark(aWatermark);

        uno::Reference<css::drawing::XShape> xShape = getShape(1);
        CPPUNIT_ASSERT(xShape.is());

        SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);

        // Get Watermark object
        SdrObject* pObject = pPage->GetObj(0);
        pObject->RecalcBoundRect();
        const tools::Rectangle& rRect = pObject->GetSnapRect();
        Size rSize = pPage->GetSize();

        // Page break, calculate height of a page
        const int nPageHeight = rSize.getHeight() / aPages;

        std::stringstream aMessage;
        aMessage << "Case: " << i << ", nPageHeight = " << nPageHeight << ", rRect.Bottom = " << rRect.Bottom();

        // Check if Watermark is inside a page
        CPPUNIT_ASSERT_MESSAGE(aMessage.str(), nPageHeight >= rRect.Bottom());

        // Check if Watermark is centered
        CPPUNIT_ASSERT_EQUAL(text::HoriOrientation::CENTER, getProperty<sal_Int16>(xShape, u"HoriOrient"_ustr));
        CPPUNIT_ASSERT_EQUAL(text::VertOrientation::CENTER, getProperty<sal_Int16>(xShape, u"VertOrient"_ustr));
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo74981)
{
    // create a document with an input field
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwInputField aField(static_cast<SwInputFieldType*>(pWrtShell->GetFieldType(0, SwFieldIds::Input)), u"foo"_ustr, u"bar"_ustr, SwInputFieldSubType::None, false);
    pWrtShell->InsertField2(aField);

    {
        // expect hints
        SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
        SwTextNode* pTextNode = aIdx.GetNode().GetTextNode();
        CPPUNIT_ASSERT(pTextNode->HasHints());
    }

    // go to the begin of the paragraph and split this node
    pWrtShell->Left(SwCursorSkipMode::Chars, false, 100, false);
    pWrtShell->SplitNode();

    {
        // expect only the second paragraph to have hints
        SwNodeIndex aIdx(SwNodeIndex(pDoc->GetNodes().GetEndOfContent(), -1));
        SwTextNode* pTextNode = aIdx.GetNode().GetTextNode();
        CPPUNIT_ASSERT(pTextNode->HasHints());
        --aIdx;
        pTextNode = aIdx.GetNode().GetTextNode();
        CPPUNIT_ASSERT(!pTextNode->HasHints());
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf98512)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwInputFieldType *const pType(static_cast<SwInputFieldType*>(
                pWrtShell->GetFieldType(0, SwFieldIds::Input)));
    SwInputField aField1(pType, u"foo"_ustr, u"bar"_ustr, SwInputFieldSubType::Text, false);
    pWrtShell->InsertField2(aField1);
    pWrtShell->SttEndDoc(/*bStt=*/true);
    SwInputField aField2(pType, u"baz"_ustr, u"quux"_ustr, SwInputFieldSubType::Text, false);
    pWrtShell->InsertField2(aField2);
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->SetMark();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    OUString const expected1(
        OUStringChar(CH_TXT_ATR_INPUTFIELDSTART) + "foo" + OUStringChar(CH_TXT_ATR_INPUTFIELDEND));
    OUString const expected2(
        OUStringChar(CH_TXT_ATR_INPUTFIELDSTART) + "baz" + OUStringChar(CH_TXT_ATR_INPUTFIELDEND)
        + expected1);
    CPPUNIT_ASSERT_EQUAL(expected2, pWrtShell->getShellCursor(false)->GetText());
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    rUndoManager.Undo();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->SetMark();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    CPPUNIT_ASSERT_EQUAL(expected1, pWrtShell->getShellCursor(false)->GetText());
    rUndoManager.Redo();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->SetMark();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    CPPUNIT_ASSERT_EQUAL(expected2, pWrtShell->getShellCursor(false)->GetText());
    rUndoManager.Undo();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->SetMark();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    CPPUNIT_ASSERT_EQUAL(expected1, pWrtShell->getShellCursor(false)->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testShapeTextboxSelect)
{
    createSwDoc("shape-textbox.odt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    SdrObject* pObject = pPage->GetObj(1);
    SwContact* pTextBox = static_cast<SwContact*>(pObject->GetUserCall());
    // First, make sure that pTextBox is a fly frame (textbox of a shape).
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_FLYFRMFMT), pTextBox->GetFormat()->Which());

    // Then select it.
    pWrtShell->SelectObj(Point(), 0, pObject);
    const SdrMarkList& rMarkList = pWrtShell->GetDrawView()->GetMarkedObjectList();
    SwDrawContact* pShape = static_cast<SwDrawContact*>(rMarkList.GetMark(0)->GetMarkedSdrObj()->GetUserCall());
    // And finally make sure the shape got selected, not just the textbox itself.
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_DRAWFRMFMT), pShape->GetFormat()->Which());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testShapeTextboxDelete)
{
    createSwDoc("shape-textbox.odt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    SdrObject* pObject = pPage->GetObj(0);
    pWrtShell->SelectObj(Point(), 0, pObject);
    size_t nActual = pPage->GetObjCount();
    // Two objects on the draw page: the shape and its textbox.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), nActual);

    pWrtShell->DelSelectedObj();
    nActual = pPage->GetObjCount();
    // Both (not only the shape) should be removed by now (the textbox wasn't removed, so this was 1).
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), nActual);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testAnchorChangeSelection)
{
    createSwDoc("test_anchor_as_character.odt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    SdrObject* pObject = pPage->GetObj(0);
    CPPUNIT_ASSERT(pObject);

    // Then select it.
    pWrtShell->SelectObj(Point(), 0, pObject);
    const SdrMarkList& rMarkList = pWrtShell->GetDrawView()->GetMarkedObjectList();
    CPPUNIT_ASSERT_EQUAL(pObject, rMarkList.GetMark(0)->GetMarkedSdrObj());

    pWrtShell->ChgAnchor(RndStdIds::FLY_AS_CHAR);

    // tdf#125039 shape must still be selected, extensions depend on that
    CPPUNIT_ASSERT_EQUAL(pObject, rMarkList.GetMark(0)->GetMarkedSdrObj());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testCp1000071)
{
    createSwDoc("cp1000071.odt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL( SwRedlineTable::size_type( 2 ), rTable.size());
    SwNodeOffset redlineStart0NodeIndex = rTable[ 0 ]->Start()->GetNodeIndex();
    sal_Int32 redlineStart0Index = rTable[ 0 ]->Start()->GetContentIndex();
    SwNodeOffset redlineEnd0NodeIndex = rTable[ 0 ]->End()->GetNodeIndex();
    sal_Int32 redlineEnd0Index = rTable[ 0 ]->End()->GetContentIndex();
    SwNodeOffset redlineStart1NodeIndex = rTable[ 1 ]->Start()->GetNodeIndex();
    sal_Int32 redlineStart1Index = rTable[ 1 ]->Start()->GetContentIndex();
    SwNodeOffset redlineEnd1NodeIndex = rTable[ 1 ]->End()->GetNodeIndex();
    sal_Int32 redlineEnd1Index = rTable[ 1 ]->End()->GetContentIndex();

    // Change the document layout to be 2 columns, and then undo.
    pWrtShell->SelAll();
    SwSectionData section(SectionType::Content, UIName(pWrtShell->GetUniqueSectionName()));
    SfxItemSet set( getSwDocShell()->GetPool(), svl::Items<RES_COL, RES_COL> );
    SwFormatCol col;
    col.Init( 2, 0, 10000 );
    set.Put( col );
    pWrtShell->InsertSection( section, &set );
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    rUndoManager.Undo();

    // Check that redlines are the same like at the beginning.
    CPPUNIT_ASSERT_EQUAL( SwRedlineTable::size_type( 2 ), rTable.size());
    CPPUNIT_ASSERT_EQUAL( redlineStart0NodeIndex, rTable[ 0 ]->Start()->GetNodeIndex());
    CPPUNIT_ASSERT_EQUAL( redlineStart0Index, rTable[ 0 ]->Start()->GetContentIndex());
    CPPUNIT_ASSERT_EQUAL( redlineEnd0NodeIndex, rTable[ 0 ]->End()->GetNodeIndex());
    CPPUNIT_ASSERT_EQUAL( redlineEnd0Index, rTable[ 0 ]->End()->GetContentIndex());
    CPPUNIT_ASSERT_EQUAL( redlineStart1NodeIndex, rTable[ 1 ]->Start()->GetNodeIndex());
    CPPUNIT_ASSERT_EQUAL( redlineStart1Index, rTable[ 1 ]->Start()->GetContentIndex());
    CPPUNIT_ASSERT_EQUAL( redlineEnd1NodeIndex, rTable[ 1 ]->End()->GetNodeIndex());
    CPPUNIT_ASSERT_EQUAL( redlineEnd1Index, rTable[ 1 ]->End()->GetContentIndex());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testShapeTextboxVertadjust)
{
    createSwDoc("shape-textbox-vertadjust.odt");
    SwDoc* pDoc = getSwDoc();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    SdrObject* pObject = pPage->GetObj(1);
    SwFrameFormat* pFormat = static_cast<SwContact*>(pObject->GetUserCall())->GetFormat();
    // This was SDRTEXTVERTADJUST_TOP.
    CPPUNIT_ASSERT_EQUAL(SDRTEXTVERTADJUST_CENTER, pFormat->GetTextVertAdjust().GetValue());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testShapeTextboxAutosize)
{
    createSwDoc("shape-textbox-autosize.odt");
    SwDoc* pDoc = getSwDoc();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    // 0-1 is the first UI-visible shape+textbox.
    SdrObject* pFirst = pPage->GetObj(0);
    CPPUNIT_ASSERT_EQUAL(u"1st"_ustr, pFirst->GetName());

    // 2-3 is the second UI-visible shape+textbox.
    SdrObject* pSecond = pPage->GetObj(2);
    CPPUNIT_ASSERT_EQUAL(u"2nd"_ustr, pSecond->GetName());

    // Shape -> textbox synchronization was missing, the second shape had the
    // same height as the first, even though the first contained 1 paragraph
    // and the other 2 ones.
    CPPUNIT_ASSERT(pFirst->GetSnapRect().getOpenHeight() < pSecond->GetSnapRect().getOpenHeight());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo82191)
{
    createSwDoc("fdo82191.odt");
    SwDoc* pDoc = getSwDoc();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    // Make sure we have a single draw shape.
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), SwTextBoxHelper::getCount(pPage));

    SwDoc aClipboard;
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SdrObject* pObject = pPage->GetObj(0);
    // Select it, then copy and paste.
    pWrtShell->SelectObj(Point(), 0, pObject);
    pWrtShell->Copy(aClipboard);
    pWrtShell->Paste(aClipboard);

    // This was one: the textbox of the shape wasn't copied.
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), SwTextBoxHelper::getCount(*pDoc));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testCommentedWord)
{
    // This word is commented. <- string in document
    // 123456789 <- character positions
    createSwDoc("commented-word.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Move the cursor into the second word.
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false);
    // Select the word.
    pWrtShell->SelWrd();

    // Make sure that not only the word, but its comment anchor is also selected.
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
    // This was 9, only "word", not "word<anchor character>" was selected.
    CPPUNIT_ASSERT_EQUAL(sal_Int32(10), pShellCursor->End()->GetContentIndex());

    // Test that getAnchor() points to "word", not to an empty string.
    uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XEnumerationAccess> xFieldsAccess(xTextFieldsSupplier->getTextFields());
    uno::Reference<container::XEnumeration> xFields(xFieldsAccess->createEnumeration());
    uno::Reference<text::XTextContent> xField(xFields->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(u"word"_ustr, xField->getAnchor()->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTextFieldGetAnchorGetTextInFooter)
{
    createSwDoc("textfield-getanchor-gettext-in-footer.odt");

    uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XEnumerationAccess> xFieldsAccess(xTextFieldsSupplier->getTextFields());
    uno::Reference<container::XEnumeration> xFields(xFieldsAccess->createEnumeration());
    uno::Reference<text::XTextContent> xField(xFields->nextElement(), uno::UNO_QUERY);

    OUString value = xField->getAnchor()->getText()->getString();
    CPPUNIT_ASSERT_EQUAL(u"userfield_in_footer"_ustr, value );
}

// Tests that a blank document is still blank after conversion
CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testChineseConversionBlank)
{

    // Given
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwView* pView = getSwDocShell()->GetView();
    const uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);

    // When
    SwHHCWrapper aWrap( *pView, xContext, LANGUAGE_CHINESE_TRADITIONAL, LANGUAGE_CHINESE_SIMPLIFIED, nullptr,
                        i18n::TextConversionOption::CHARACTER_BY_CHARACTER, false,
                        true, false, false );
    aWrap.Convert();

    // Then
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    CPPUNIT_ASSERT_EQUAL(OUString(), pTextNode->GetText());

}

// Tests that non Chinese text is unchanged after conversion
CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testChineseConversionNonChineseText)
{

    // Given
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwView* pView = getSwDocShell()->GetView();
    const uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);
    pDoc->getIDocumentContentOperations().InsertString(aPaM, NON_CHINESE_CONTENT);

    // When
    SwHHCWrapper aWrap( *pView, xContext, LANGUAGE_CHINESE_TRADITIONAL, LANGUAGE_CHINESE_SIMPLIFIED, nullptr,
                        i18n::TextConversionOption::CHARACTER_BY_CHARACTER, false,
                        true, false, false );
    aWrap.Convert();

    // Then
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    CPPUNIT_ASSERT_EQUAL(NON_CHINESE_CONTENT, pTextNode->GetText());

}

// Tests conversion of traditional Chinese characters to simplified Chinese
CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testChineseConversionTraditionalToSimplified)
{

    // Given
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwView* pView = getSwDocShell()->GetView();
    const uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);
    pDoc->getIDocumentContentOperations().InsertString(aPaM, OUString(CHINESE_TRADITIONAL_CONTENT));

    // When
    SwHHCWrapper aWrap( *pView, xContext, LANGUAGE_CHINESE_TRADITIONAL, LANGUAGE_CHINESE_SIMPLIFIED, nullptr,
                        i18n::TextConversionOption::CHARACTER_BY_CHARACTER, false,
                        true, false, false );
    aWrap.Convert();

    // Then
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    CPPUNIT_ASSERT_EQUAL(OUString(CHINESE_SIMPLIFIED_CONTENT), pTextNode->GetText());

}

// Tests conversion of simplified Chinese characters to traditional Chinese
CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testChineseConversionSimplifiedToTraditional)
{

    // Given
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwView* pView = getSwDocShell()->GetView();
    const uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);
    pDoc->getIDocumentContentOperations().InsertString(aPaM, OUString(CHINESE_SIMPLIFIED_CONTENT));

    // When
    SwHHCWrapper aWrap( *pView, xContext, LANGUAGE_CHINESE_SIMPLIFIED, LANGUAGE_CHINESE_TRADITIONAL, nullptr,
                        i18n::TextConversionOption::CHARACTER_BY_CHARACTER, false,
                        true, false, false );
    aWrap.Convert();

    // Then
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    CPPUNIT_ASSERT_EQUAL(OUString(CHINESE_TRADITIONAL_CONTENT), pTextNode->GetText());

}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo85554)
{
    // Load the document, it contains one shape with a textbox.
    createSwDoc("fdo85554.odt");

    // Add a second shape to the document.
    uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XShape> xShape(xFactory->createInstance(u"com.sun.star.drawing.RectangleShape"_ustr), uno::UNO_QUERY);
    xShape->setSize(awt::Size(10000, 10000));
    xShape->setPosition(awt::Point(1000, 1000));
    uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
    xDrawPage->add(xShape);

    // Save it and load it back.
    saveAndReload(TestFilter::ODT);

    // This was 1, we lost a shape on export.
    CPPUNIT_ASSERT_EQUAL(2, getShapes());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testMergeDoc)
{
    createSwDoc("merge-change1.odt");
    SwDoc* pDoc = getSwDoc();

    mxComponent2 = loadFromDesktop(
            createFileURL(u"merge-change2.odt"),
            u"com.sun.star.text.TextDocument"_ustr);
    auto pxDoc2Document(
            dynamic_cast<SwXTextDocument *>(mxComponent2.get()));
    CPPUNIT_ASSERT(pxDoc2Document);
    SwDoc* const pDoc2(pxDoc2Document->GetDocShell()->GetDoc());

    SwEditShell* const pEditShell(pDoc->GetEditShell());
    CPPUNIT_ASSERT(pEditShell);
    pEditShell->MergeDoc(*pDoc2);

    // accept all redlines
    while(pEditShell->GetRedlineCount())
        pEditShell->AcceptRedline(0);

    CPPUNIT_ASSERT_EQUAL(7, getParagraphs());
    getParagraph(1, u"Para One: Two Three Four Five"_ustr);
    getParagraph(2, u"Para Two: One Three Four Five"_ustr);
    getParagraph(3, u"Para Three: One Two Four Five"_ustr);
    getParagraph(4, u"Para Four: One Two Three Four Five"_ustr);
    getParagraph(5, u"Para Six: One Three Four Five"_ustr);
    getParagraph(6, u""_ustr);
    getParagraph(7, u""_ustr);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testCreatePortions)
{
    createSwDoc("uno-cycle.odt");
    uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextContent> xText(xBookmarksSupplier->getBookmarks()->getByName(u"Mark"_ustr), uno::UNO_QUERY);
    uno::Reference<container::XEnumerationAccess> xTextCursor(xText->getAnchor(), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xTextCursor.is());

    uno::Reference<container::XEnumerationAccess> xParagraph(
            xTextCursor->createEnumeration()->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xParagraph.is());
    // This looped forever in lcl_CreatePortions
    xParagraph->createEnumeration();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testBookmarkUndo)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess();
    SwPaM aPaM( SwNodeIndex(pDoc->GetNodes().GetEndOfContent(), -1) );

    pMarkAccess->makeMark(aPaM, SwMarkName(u"Mark"_ustr), IDocumentMarkAccess::MarkType::BOOKMARK,
            ::sw::mark::InsertMode::New);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    rUndoManager.Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pMarkAccess->getAllMarksCount());
    rUndoManager.Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());

    auto ppBkmk = pMarkAccess->findMark(SwMarkName(u"Mark"_ustr));
    CPPUNIT_ASSERT(ppBkmk != pMarkAccess->getAllMarksEnd());

    pMarkAccess->renameMark(*ppBkmk, SwMarkName(u"Mark_"_ustr));
    CPPUNIT_ASSERT(bool(pMarkAccess->findMark(SwMarkName(u"Mark"_ustr)) == pMarkAccess->getAllMarksEnd()));
    CPPUNIT_ASSERT(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)) != pMarkAccess->getAllMarksEnd());
    rUndoManager.Undo();
    CPPUNIT_ASSERT(pMarkAccess->findMark(SwMarkName(u"Mark"_ustr)) != pMarkAccess->getAllMarksEnd());
    CPPUNIT_ASSERT(bool(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)) == pMarkAccess->getAllMarksEnd()));
    rUndoManager.Redo();
    CPPUNIT_ASSERT(bool(pMarkAccess->findMark(SwMarkName(u"Mark"_ustr)) == pMarkAccess->getAllMarksEnd()));
    CPPUNIT_ASSERT(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)) != pMarkAccess->getAllMarksEnd());

    pMarkAccess->deleteMark(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)), false);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pMarkAccess->getAllMarksCount());
    rUndoManager.Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    rUndoManager.Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pMarkAccess->getAllMarksCount());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf148389_Left)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);
    pWrtShell->Insert(u"foo bar baz"_ustr);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);
    IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess();

    auto pMark = pMarkAccess->makeMark(*pWrtShell->GetCursor(), SwMarkName(u"Mark"_ustr),
        IDocumentMarkAccess::MarkType::BOOKMARK, ::sw::mark::InsertMode::New);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    pWrtShell->DelLeft();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->DelLeft();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(6), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->DelLeft();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->DelLeft();
    // historically it wasn't deleted if empty, not sure if it should be
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    // the problem was that the end position was not restored
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    // this undo is no longer grouped, to prevent Redo deleting bookmark
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pMark->GetOtherMarkPos().GetContentIndex());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf148389_Right)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);
    pWrtShell->Insert(u"foo bar baz"_ustr);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);
    IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess();

    auto pMark = pMarkAccess->makeMark(*pWrtShell->GetCursor(), SwMarkName(u"Mark"_ustr),
        IDocumentMarkAccess::MarkType::BOOKMARK, ::sw::mark::InsertMode::New);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false);
    pWrtShell->DelRight();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(6), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->DelRight();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->DelRight();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->DelRight();
    // historically it wasn't deleted if empty, not sure if it should be
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    // the problem was that the start position was not restored
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    // this undo is no longer grouped, to prevent Redo deleting bookmark
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(6), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    // this undo is no longer grouped, to prevent Redo deleting bookmark
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(6), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(6), pMark->GetOtherMarkPos().GetContentIndex());
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    // Undo re-creates the mark...
    pMark = *pMarkAccess->getAllMarksBegin();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMark->GetMarkPos().GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pMark->GetOtherMarkPos().GetContentIndex());
}

static void lcl_setWeight(SwWrtShell* pWrtShell, FontWeight aWeight)
{
    SvxWeightItem aWeightItem(aWeight, EE_CHAR_WEIGHT);
    SvxScriptSetItem aScriptSetItem(SID_ATTR_CHAR_WEIGHT, pWrtShell->GetAttrPool());
    aScriptSetItem.PutItemForScriptType(SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX, aWeightItem);
    pWrtShell->SetAttrSet(aScriptSetItem.GetItemSet());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo85876)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    lcl_setWeight(pWrtShell, WEIGHT_BOLD);
    pWrtShell->Insert(u"test"_ustr);
    lcl_setWeight(pWrtShell, WEIGHT_NORMAL);
    pWrtShell->SplitNode();
    pWrtShell->SplitNode();
    pWrtShell->Up(false);
    pWrtShell->Insert(u"test"_ustr);
    auto xText = getParagraph(1)->getText();
    CPPUNIT_ASSERT(xText.is());
    {
        auto xCursor(xText->createTextCursorByRange(getParagraph(1)));
        CPPUNIT_ASSERT(xCursor.is());
        xCursor->collapseToStart();
        CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD, getProperty<float>(xCursor, u"CharWeight"_ustr));
    }
    {
        auto xCursor(xText->createTextCursorByRange(getParagraph(2)));
        CPPUNIT_ASSERT(xCursor.is());
        xCursor->collapseToStart();
        // this used to be BOLD too with fdo#85876
        CPPUNIT_ASSERT_EQUAL(awt::FontWeight::NORMAL, getProperty<float>(xCursor, u"CharWeight"_ustr));
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testCaretPositionMovingUp)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"after"_ustr);
    pWrtShell->InsertLineBreak();
    pWrtShell->Up(false);
    pWrtShell->Insert(u"before"_ustr);

    CPPUNIT_ASSERT_EQUAL(OUString(u"beforeAfter" + OUStringChar(CH_TXTATR_NEWLINE)), getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf93441)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"Hello"_ustr);
    pWrtShell->InsertLineBreak();
    pWrtShell->Insert(u"Hello World"_ustr);
    pWrtShell->Up(false);
    pWrtShell->Insert(u" World"_ustr);

    // Without the fix in place, this test would have failed with
    // - Expected: Hello World\nHello World
    // - Actual  :  WorldHello\nHello World
    CPPUNIT_ASSERT_EQUAL(OUString(u"Hello World" + OUStringChar(CH_TXTATR_NEWLINE) + u"Hello World"), getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf81226)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"before"_ustr);
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    pWrtShell->Down(false);
    pWrtShell->Insert(u"after"_ustr);

    // Without the fix in place, this test would have failed with
    // - Expected: beforeafter
    // - Actual  : beafterfore
    CPPUNIT_ASSERT_EQUAL(u"beforeafter"_ustr, getParagraph(1)->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf137532)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert(u"test"_ustr);

    //Select the word and change it to bold
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 4, /*bBasicCall=*/false);
    lcl_setWeight(pWrtShell, WEIGHT_BOLD);

    // Select first character and replace it
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, /*bBasicCall=*/false);
    pWrtShell->Insert(u"x"_ustr);

    auto xText = getParagraph(1)->getText();
    CPPUNIT_ASSERT(xText.is());
    auto xCursor(xText->createTextCursorByRange(getRun(getParagraph(1), 1)));

    CPPUNIT_ASSERT(xCursor.is());
    CPPUNIT_ASSERT_EQUAL(u"xest"_ustr, xCursor->getString());
    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD, getProperty<float>(xCursor, u"CharWeight"_ustr));

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

    xCursor.set(xText->createTextCursorByRange(getRun(getParagraph(1), 1)));
    CPPUNIT_ASSERT(xCursor.is());
    CPPUNIT_ASSERT_EQUAL(u"test"_ustr, xCursor->getString());

    // Without the fix in place, this test would have failed in
    // - Expected: 150
    // - Actual  : 100
    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD, getProperty<float>(xCursor, u"CharWeight"_ustr));

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

    xCursor.set(xText->createTextCursorByRange(getRun(getParagraph(1), 1)));
    CPPUNIT_ASSERT(xCursor.is());
    CPPUNIT_ASSERT_EQUAL(u"test"_ustr, xCursor->getString());
    CPPUNIT_ASSERT_EQUAL(awt::FontWeight::NORMAL, getProperty<float>(xCursor, u"CharWeight"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testFdo87448)
{
    createSwDoc("fdo87448.odt");

    // Save the first shape to a metafile.
    uno::Reference<drawing::XGraphicExportFilter> xGraphicExporter = drawing::GraphicExportFilter::create(comphelper::getProcessComponentContext());
    uno::Reference<lang::XComponent> xSourceDoc(getShape(1), uno::UNO_QUERY);
    xGraphicExporter->setSourceDocument(xSourceDoc);

    SvMemoryStream aStream;
    uno::Reference<io::XOutputStream> xOutputStream(new utl::OStreamWrapper(aStream));
    cpo::uno::Sequence<beans::PropertyValue> aDescriptor( comphelper::InitPropertySequence({
            { "OutputStream", cpo::uno::Any(xOutputStream) },
            { "FilterName", cpo::uno::Any(u"SVM"_ustr) }
        }));
    xGraphicExporter->filter(aDescriptor);
    aStream.Seek(STREAM_SEEK_TO_BEGIN);

    // Read it back and dump it as an XML file.
    Graphic aGraphic;
    TypeSerializer aSerializer(aStream);
    aSerializer.readGraphic(aGraphic);
    const GDIMetaFile& rMetaFile = aGraphic.GetGDIMetaFile();
    MetafileXmlDump dumper;
    xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, rMetaFile);

    // The first polyline in the document has a number of points to draw arcs,
    // the last one jumps back to the start, so we call "end" the last but one.
    sal_Int32 nFirstEnd = getXPath(pXmlDoc, "(//polyline)[1]/point[last()-1]", "x").toInt32();
    // The second polyline has a different start point, but the arc it draws
    // should end at the ~same position as the first polyline.
    sal_Int32 nSecondEnd = getXPath(pXmlDoc, "(//polyline)[2]/point[last()]", "x").toInt32();

    // nFirstEnd was 6023 and nSecondEnd was 6648, now they should be much closer, e.g. nFirstEnd = 6550, nSecondEnd = 6548
    OString aMsg = "nFirstEnd is " + OString::number(nFirstEnd) + ", nSecondEnd is " + OString::number(nSecondEnd);
    // Assert that the difference is less than half point.
    CPPUNIT_ASSERT_MESSAGE(aMsg.getStr(), abs(nFirstEnd - nSecondEnd) < 10);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTextCursorInvalidation)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    uno::Reference<beans::XPropertySet> xPageStyle(getStyles(u"PageStyles"_ustr)->getByName(u"Standard"_ustr), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xPageStyle.is());
    xPageStyle->setPropertyValue(u"HeaderIsOn"_ustr, cpo::uno::Any(true));
    uno::Reference<text::XText> xHeader(getProperty<uno::Reference<text::XText>>(xPageStyle, u"HeaderText"_ustr));
    CPPUNIT_ASSERT(xHeader.is());
    // create cursor inside the header text
    uno::Reference<text::XTextCursor> xCursor(xHeader->createTextCursor());
    // can't go right in empty header
    CPPUNIT_ASSERT(!xCursor->goRight(1, false));
// this does not actually delete the header:    xPageStyle->setPropertyValue("HeaderIsOn", uno::makeAny(false));
    pWrtShell->ChangeHeaderOrFooter(UIName(u"Default Page Style"_ustr), true, false, false);
    // must be disposed after deleting header
    // cursor ends up in body
    // UPDATE: this behaviour has been corrected as a side effect of the fix to tdf#46561:
    //CPPUNIT_ASSERT_THROW(xCursor->goRight(1, false), uno::RuntimeException);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf68183)
{
    // First disable RSID and check if indeed no such attribute is inserted.
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwModule* mod = SwModule::get();
    mod->GetModuleConfig()->SetStoreRsid(false);
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Insert2(u"X"_ustr);

    SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
    SwPaM aPaM(aIdx);
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();
    CPPUNIT_ASSERT_EQUAL(false, pTextNode->GetSwAttrSet().HasItem(RES_PARATR_RSID));

    // Then enable storing of RSID and make sure that the attribute is inserted.
    mod->GetModuleConfig()->SetStoreRsid(true);

    pWrtShell->DelToStartOfLine();
    pWrtShell->Insert2(u"X"_ustr);

    CPPUNIT_ASSERT_EQUAL(true, pTextNode->GetSwAttrSet().HasItem(RES_PARATR_RSID));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testCp1000115)
{
    createSwDoc("cp1000115.fodt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // This was 1: the long paragraph in the B1 cell did flow over to the
    // second page, so there was only one paragraph in the second cell of the
    // second page.
    assertXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell[2]/txt", 2);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf63214)
{
    //This is a crash test
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    pWrtShell->Insert(u"V"_ustr);
    {   //limiting the lifetime of SwPaM with a nested scope
        //the shell cursor are automatically adjusted when nodes are deleted, but the shell doesn't know about an SwPaM on the stack
        IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess();
        SwPaM aPaM( SwNodeIndex(pDoc->GetNodes().GetEndOfContent(), -1) );
        aPaM.SetMark();
        aPaM.Move(fnMoveForward, GoInContent);
        //Inserting a crossRefBookmark
        pMarkAccess->makeMark(aPaM, SwMarkName(u"Bookmark"_ustr),
            IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK,
            ::sw::mark::InsertMode::New);
        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    }
    //moving cursor to the end of paragraph
    pWrtShell->EndPara();
    //inserting paragraph break
    pWrtShell->SplitNode();
    rUndoManager.Undo();
    rUndoManager.Redo();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf90003)
{
    createSwDoc("tdf90003.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    CPPUNIT_ASSERT(pXmlDoc);
    // This was 1: an unexpected fly portion was created, resulting in too
    // large x position for the empty paragraph marker.
    assertXPath(pXmlDoc, "//SwFixPortion[@type='PortionType::Fly']", 0);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf51741)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess();
    SwPaM aPaM( SwNodeIndex(pDoc->GetNodes().GetEndOfContent(), -1) );
    //Modification 1
    pMarkAccess->makeMark(aPaM, SwMarkName(u"Mark"_ustr), IDocumentMarkAccess::MarkType::BOOKMARK,
            ::sw::mark::InsertMode::New);
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    //Modification 2
    rUndoManager.Undo();
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pMarkAccess->getAllMarksCount());
    //Modification 3
    rUndoManager.Redo();
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    auto ppBkmk = pMarkAccess->findMark(SwMarkName(u"Mark"_ustr));
    CPPUNIT_ASSERT(ppBkmk != pMarkAccess->getAllMarksEnd());
    //Modification 4
    pMarkAccess->renameMark(*ppBkmk, SwMarkName(u"Mark_"_ustr));
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT(bool(pMarkAccess->findMark(SwMarkName(u"Mark"_ustr)) == pMarkAccess->getAllMarksEnd()));
    CPPUNIT_ASSERT(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)) != pMarkAccess->getAllMarksEnd());
    //Modification 5
    rUndoManager.Undo();
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT(pMarkAccess->findMark(SwMarkName(u"Mark"_ustr)) != pMarkAccess->getAllMarksEnd());
    CPPUNIT_ASSERT(bool(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)) == pMarkAccess->getAllMarksEnd()));
    //Modification 6
    rUndoManager.Redo();
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT(bool(pMarkAccess->findMark(SwMarkName(u"Mark"_ustr)) == pMarkAccess->getAllMarksEnd()));
    CPPUNIT_ASSERT(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)) != pMarkAccess->getAllMarksEnd());
    //Modification 7
    pMarkAccess->deleteMark(pMarkAccess->findMark(SwMarkName(u"Mark_"_ustr)), false);
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pMarkAccess->getAllMarksCount());
    //Modification 8
    rUndoManager.Undo();
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    //Modification 9
    rUndoManager.Redo();
    CPPUNIT_ASSERT(pWrtShell->IsModified());
    pWrtShell->ResetModified();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pMarkAccess->getAllMarksCount());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testDefaultsOfOutlineNumbering)
{
    uno::Reference<text::XDefaultNumberingProvider> xDefNum(m_xSFactory->createInstance(u"com.sun.star.text.DefaultNumberingProvider"_ustr), uno::UNO_QUERY);
    css::lang::Locale alocale;
    alocale.Language = u"en"_ustr;
    alocale.Country = u"US"_ustr;
    const cpo::uno::Sequence<beans::PropertyValues> aPropVal(xDefNum->getDefaultContinuousNumberingLevels(alocale));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPropVal.getLength());
    for(const auto& rPropValues : aPropVal)
    {
        CPPUNIT_ASSERT_EQUAL(sal_Int32(5), rPropValues.getLength());
        for(const auto& rPropVal : rPropValues)
        {
            cpo::uno::Any aAny = rPropVal.Value;
            if(rPropVal.Name == "Prefix" || rPropVal.Name == "Suffix" || rPropVal.Name == "Transliteration")
                CPPUNIT_ASSERT_EQUAL(u"string"_ustr, aAny.getValueTypeName());
            else if(rPropVal.Name == "NumberingType")
                CPPUNIT_ASSERT_EQUAL(u"short"_ustr, aAny.getValueTypeName());
            else if(rPropVal.Name == "NatNum")
                CPPUNIT_ASSERT_EQUAL(u"short"_ustr, aAny.getValueTypeName());
                //It is expected to be long but right now its short !error!
            else
                CPPUNIT_FAIL("Property Name not matched");
        }
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testDeleteTableRedlines)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0);
    const SwTable& rTable = pWrtShell->InsertTable(TableOpt, 1, 3);
    uno::Reference<text::XTextTable> xTable(getParagraphOrTable(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable->getRows()->getCount());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable->getColumns()->getCount());
    cpo::uno::Sequence<beans::PropertyValue> aDescriptor;
    SwUnoCursorHelper::makeTableCellRedline((*const_cast<SwTableBox*>(rTable.GetTableBox(u"A1"_ustr))), u"TableCellInsert", aDescriptor);
    SwUnoCursorHelper::makeTableCellRedline((*const_cast<SwTableBox*>(rTable.GetTableBox(u"B1"_ustr))), u"TableCellInsert", aDescriptor);
    SwUnoCursorHelper::makeTableCellRedline((*const_cast<SwTableBox*>(rTable.GetTableBox(u"C1"_ustr))), u"TableCellInsert", aDescriptor);
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwExtraRedlineTable& rExtras = rIDRA.GetExtraRedlineTable();
    rExtras.DeleteAllTableRedlines(*pDoc, rTable, false, RedlineType::Any);
    CPPUNIT_ASSERT_EQUAL(o3tl::narrowing<sal_uInt16>(0), rExtras.GetSize());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testXFlatParagraph)
{
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    //Inserting some text in the document
    pWrtShell->Insert(u"This is sample text"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"This is another sample text"_ustr);
    pWrtShell->SplitNode();
    pWrtShell->Insert(u"This is yet another sample text"_ustr);
    //retrieving the XFlatParagraphs
    uno::Reference<text::XFlatParagraphIteratorProvider> xFPIP(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XFlatParagraphIterator> xFPIterator(xFPIP->getFlatParagraphIterator(sal_Int32(text::TextMarkupType::SPELLCHECK), true));
    uno::Reference<text::XFlatParagraph> xFlatPara(xFPIterator->getFirstPara());
    CPPUNIT_ASSERT_EQUAL(u"This is sample text"_ustr, xFlatPara->getText());
    //checking modified status
    CPPUNIT_ASSERT(!xFlatPara->isModified());
    //checking "checked" status, modifying it and asserting the changes
    CPPUNIT_ASSERT(!xFlatPara->isChecked(sal_Int32(text::TextMarkupType::SPELLCHECK)));
    xFlatPara->setChecked((sal_Int32(text::TextMarkupType::SPELLCHECK)), true);
    CPPUNIT_ASSERT(xFlatPara->isChecked(sal_Int32(text::TextMarkupType::SPELLCHECK)));
    //getting other XFlatParagraphs and asserting their contents
    uno::Reference<text::XFlatParagraph> xFlatPara2(xFPIterator->getParaAfter(xFlatPara));
    CPPUNIT_ASSERT_EQUAL(u"This is another sample text"_ustr, xFlatPara2->getText());
    uno::Reference<text::XFlatParagraph> xFlatPara3(xFPIterator->getParaAfter(xFlatPara2));
    CPPUNIT_ASSERT_EQUAL(u"This is yet another sample text"_ustr, xFlatPara3->getText());
    uno::Reference<text::XFlatParagraph> xFlatPara4(xFPIterator->getParaBefore(xFlatPara3));
    CPPUNIT_ASSERT_EQUAL(xFlatPara2->getText(), xFlatPara4->getText());
    //changing the attributes of last para
    cpo::uno::Sequence<beans::PropertyValue> aDescriptor( comphelper::InitPropertySequence({
         { "CharWeight", cpo::uno::Any(css::awt::FontWeight::BOLD) }
    }));
    xFlatPara3->changeAttributes(sal_Int32(0), sal_Int32(5), aDescriptor);
    //checking Language Portions
    cpo::uno::Sequence<::sal_Int32> aLangPortions(xFlatPara4->getLanguagePortions());
    CPPUNIT_ASSERT(!aLangPortions.hasElements());
    //examining Language of text
    css::lang::Locale alocale = xFlatPara4->getLanguageOfText(sal_Int32(0), sal_Int32(4));
    CPPUNIT_ASSERT_EQUAL(u"en"_ustr, alocale.Language);
    CPPUNIT_ASSERT_EQUAL(u"US"_ustr, alocale.Country);
    //examining Primary Language of text
    css::lang::Locale aprimarylocale = xFlatPara4->getPrimaryLanguageOfText(sal_Int32(0), sal_Int32(20));
    CPPUNIT_ASSERT_EQUAL(u"en"_ustr, aprimarylocale.Language);
    CPPUNIT_ASSERT_EQUAL(u"US"_ustr, aprimarylocale.Country);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf81995)
{
    uno::Reference<text::XDefaultNumberingProvider> xDefNum(m_xSFactory->createInstance(u"com.sun.star.text.DefaultNumberingProvider"_ustr), uno::UNO_QUERY);
    css::lang::Locale alocale;
    alocale.Language = u"en"_ustr;
    alocale.Country = u"US"_ustr;
    const cpo::uno::Sequence<uno::Reference<container::XIndexAccess>> aIndexAccess(xDefNum->getDefaultOutlineNumberings(alocale));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aIndexAccess.getLength());
    for(const auto& rIndexAccess : aIndexAccess)
    {
        CPPUNIT_ASSERT_EQUAL(sal_Int32(6), rIndexAccess->getCount());
        for(int j=0;j<rIndexAccess->getCount();j++)
        {
            cpo::uno::Sequence<beans::PropertyValue> aProps;
            rIndexAccess->getByIndex(j) >>= aProps;
            CPPUNIT_ASSERT_EQUAL(sal_Int32(12), aProps.getLength());
            for (const beans::PropertyValue& rProp : aProps)
            {
                cpo::uno::Any aAny = rProp.Value;
                if(rProp.Name == "Prefix" || rProp.Name == "Suffix" || rProp.Name == "BulletChar" || rProp.Name == "BulletFontName" || rProp.Name == "Transliteration")
                    CPPUNIT_ASSERT_EQUAL(u"string"_ustr, aAny.getValueTypeName());
                else if(rProp.Name == "NumberingType" || rProp.Name == "ParentNumbering" || rProp.Name == "Adjust")
                    CPPUNIT_ASSERT_EQUAL(u"short"_ustr, aAny.getValueTypeName());
                else if(rProp.Name == "LeftMargin" || rProp.Name == "SymbolTextDistance" || rProp.Name == "FirstLineOffset" || rProp.Name == "NatNum")
                    CPPUNIT_ASSERT_EQUAL(u"long"_ustr, aAny.getValueTypeName());
                else
                    CPPUNIT_FAIL("Property Name not matched");
            }
        }
    }
}

CPPUNIT_PLUGIN_IMPLEMENT();

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