Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sw/qa/extras/uiwriter/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 111 kB image not shown  

Quelle  uiwriter4.cxx

  Sprache: C
 


/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/drawing/FillStyle.hpp>
#include <com/sun/star/style/CaseMap.hpp>
#include <swmodeltestbase.hxx>
#include <ndtxt.hxx>
#include <wrtsh.hxx>
#include <drawdoc.hxx>
#include <redline.hxx>
#include <dcontact.hxx>
#include <view.hxx>
#include <IDocumentSettingAccess.hxx>

#include <editeng/brushitem.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdview.hxx>

#include <i18nutil/transliteration.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <UndoManager.hxx>
#include <tblafmt.hxx>

#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/linguistic2/XLinguProperties.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/text/XPageCursor.hpp>
#include <com/sun/star/text/XParagraphAppend.hpp>
#include <o3tl/cppunittraitshelper.hxx>
#include <osl/thread.hxx>
#include <hyp.hxx>
#include <swdtflvr.hxx>
#include <comphelper/propertysequence.hxx>
#include <sfx2/classificationhelper.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <sfx2/viewfrm.hxx>
#include <sfx2/dispatch.hxx>
#include <editeng/unolingu.hxx>
#include <vcl/scheduler.hxx>
#include <config_fonts.h>
#include <unotxdoc.hxx>
#include <unotools/transliterationwrapper.hxx>
#include <officecfg/Office/Writer.hxx>

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 SwUiWriterTest4 : public SwModelTestBase
{
public:
    SwUiWriterTest4()
        : SwModelTestBase(u"/sw/qa/extras/uiwriter/data/"_ustr)
    {
    }

    void mergeDocs(const char* aDestDoc, const char* aInsertDoc);
};

void SwUiWriterTest4::mergeDocs(const char* aDestDoc, const char* aInsertDoc)
{
    createSwDoc(aDestDoc);

    // set a page cursor into the end of the document
    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(
        xModel->getCurrentController(), uno::UNO_QUERY);
    uno::Reference<text::XPageCursor> xCursor(xTextViewCursorSupplier->getViewCursor(),
                                              uno::UNO_QUERY);
    xCursor->jumpToEndOfPage();

    // insert the same document at current cursor position
    {
        const OUString insertFileid = createFileURL(OUString::createFromAscii(aInsertDoc));
        uno::Sequence<beans::PropertyValue> aPropertyValues(
            comphelper::InitPropertySequence({ { "Name", uno::Any(insertFileid) } }));
        dispatchCommand(mxComponent, u".uno:InsertDoc"_ustr, aPropertyValues);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf96515)
{
    // Enable hide whitespace mode.
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
    aViewOptions.SetHideWhitespaceMode(true);
    pWrtShell->ApplyViewOptions(aViewOptions);
    CPPUNIT_ASSERT(pWrtShell->GetViewOptions()->IsWhitespaceHidden());

    // Insert a new paragraph at the end of the document.
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XParagraphAppend> xParagraphAppend(xTextDocument->getText(),
                                                            uno::UNO_QUERY);
    xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>());
    calcLayout();

    // This was 2, a new page was created for the new paragraph.
    CPPUNIT_ASSERT_EQUAL(1, getPages());
}

static OUString lcl_translitTest(SwDoc& rDoc, const SwPaM& rPaM, TransliterationFlags const nType)
{
    utl::TransliterationWrapper aTrans(::comphelper::getProcessComponentContext(), nType);
    rDoc.getIDocumentContentOperations().TransliterateText(rPaM, aTrans);
    //SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    return rPaM.GetMarkNode().GetTextNode()->GetText();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf146449)
{
    createSwDoc("tdf146449.odt");

    auto pShell = getSwDocShell()->GetFEShell();
    CPPUNIT_ASSERT(pShell);

    auto xTextBox = getShapeByName(u"Frame1");
    auto pObject = SdrObject::getSdrObjectFromXShape(xTextBox);

    CPPUNIT_ASSERT(pShell->SelectObj(Point(), 0, pObject));

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

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

    uno::Reference<beans::XPropertySet> xShapeProps(xTextBox, uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xFrameProps(
        xShapeProps->getPropertyValue(u"TextBoxContent"_ustr), uno::UNO_QUERY);

    const auto nShapeZOrder = pObject->GetOrdNum();
    const auto nFrameZOrder = xFrameProps->getPropertyValue(u"ZOrder"_ustr);

    CPPUNIT_ASSERT_MESSAGE("Wrong Zorder!", nShapeZOrder < nFrameZOrder.get<sal_uInt32>());
}

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

    // Insert the test text at the end of the document.
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"Mary Jones met joe Smith. Time Passed."_ustr);
    pWrtShell->StartOfSection();
    SwShellCursor* pCursor = pWrtShell->getShellCursor(false);

    using TF = TransliterationFlags;

    /* -- Test behavior when there is no selection -- */

    /* Move cursor between the 't' and the ' ' after 'met', nothing should change */
    for (int i = 0; i < 14; i++)
        pCursor->Move(fnMoveForward);

    CPPUNIT_ASSERT_EQUAL(false, pCursor->HasMark());
    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->IsSelection());
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Move cursor between the 'h' and the '.' after 'Smith', nothing should change */
    for (int i = 0; i < 10; i++)
        pCursor->Move(fnMoveForward);

    CPPUNIT_ASSERT_EQUAL(false, pCursor->HasMark());
    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->IsSelection());
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Move cursor between the 'm' and the 'e' in 'met' */
    for (int i = 0; i < 12; i++)
        pCursor->Move(fnMoveBackward);

    CPPUNIT_ASSERT_EQUAL(false, pCursor->HasMark());
    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->IsSelection());
    CPPUNIT_ASSERT_EQUAL(u"Mary jones met joe smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));

    /* Undo the sentence case change to reset for the following tests */
    pDoc->GetIDocumentUndoRedo().Undo();

    CPPUNIT_ASSERT_EQUAL(u"Mary Jones Met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones MET joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* -- Test behavior when there is a selection that does not cross a word boundary -- */
    pCursor->Move(fnMoveBackward);
    pWrtShell->SelWrd();
    CPPUNIT_ASSERT_EQUAL(true, pCursor->HasMark());
    CPPUNIT_ASSERT_EQUAL(true, pWrtShell->IsSelection());

    OUString currentSelectedText;
    pWrtShell->GetSelectedText(currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"met"_ustr, currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones Met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones Met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones MET joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* -- Test behavior when there is a selection that does not begin at a word boundary: "et" -- */
    for (int i = 0; i < 2; i++)
        pCursor->Move(fnMoveBackward);
    pCursor->SetMark();
    for (int i = 0; i < 2; i++)
        pCursor->Move(fnMoveForward);
    pWrtShell->GetSelectedText(currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"et"_ustr, currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones mEt joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    pDoc->GetIDocumentUndoRedo().Undo();
    CPPUNIT_ASSERT_EQUAL(u"et"_ustr, currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones mEt joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    pDoc->GetIDocumentUndoRedo().Undo();
    CPPUNIT_ASSERT_EQUAL(u"et"_ustr, currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones mET joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* -- Test behavior when there is a selection that crosses a word boundary -- */
    for (int i = 0; i < 7; i++)
        pCursor->Move(fnMoveBackward);
    pCursor->SetMark();
    for (int i = 0; i < 14; i++)
        pCursor->Move(fnMoveForward);

    pWrtShell->GetSelectedText(currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"nes met joe Sm"_ustr, currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary JoNes met joe smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary JoNes Met Joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary JoNES MET JOE SMith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Reset the 's' to upper-case for the next test */
    for (int i = 0; i < 2; i++)
        pCursor->Move(fnMoveBackward);
    pCursor->SetMark();
    pCursor->Move(fnMoveForward);
    pDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, OUString('S'), false);

    /* -- Test behavior when there is a selection that crosses a sentence boundary -- */
    for (int i = 0; i < 4; i++)
        pCursor->Move(fnMoveBackward);
    pCursor->SetMark();
    for (int i = 0; i < 22; i++)
        pCursor->Move(fnMoveForward);
    pWrtShell->GetSelectedText(currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"joe Smith. Time Passed"_ustr, currentSelectedText);

    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met Joe smith. Time passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));

    /* Undo the sentence case change to reset for the following tests */
    pDoc->GetIDocumentUndoRedo().Undo();

    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met Joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met JOE SMITH. TIME PASSED."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe smith. time passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Undo the previous changes to reset for the following tests */
    pDoc->GetIDocumentUndoRedo().Undo();
    pDoc->GetIDocumentUndoRedo().Undo();
    pDoc->GetIDocumentUndoRedo().Undo();

    /* -- Test behavior when there is a selection that does not reach end of sentence -- */
    for (int i = 0; i < 37; i++)
        pCursor->Move(fnMoveBackward);
    pCursor->SetMark();
    for (int i = 0; i < 10; i++)
        pCursor->Move(fnMoveForward);
    pWrtShell->GetSelectedText(currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones"_ustr, currentSelectedText);
    CPPUNIT_ASSERT_EQUAL(u"Mary jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"Mary Jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"MARY JONES met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"mary jones met joe Smith. Time Passed."_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf147196)
{
    using TF = TransliterationFlags;
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    // Insert the test text at the end of the document.
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(
        u"2.2 Publication of information - CAA\nSection 4.2 of a CA\'s Certificate Policy and/or "
        "Certification Practice Statement SHALL state the CA\'s policy or practice on processing "
        "CAA Records for Fully Qualified Domain Names; that policy shall be consistent with these "
        "Requirements. \n\nIt shall clearly specify the set of Issuer Domain Names that the CA "
        "recognises in CAA \"issue\" or \"issuewild\" records as permitting it to issue. The CA "
        "SHALL log all actions taken, if any, consistent with its processing practice."_ustr);

    pWrtShell->StartOfSection();
    SwShellCursor* pCursor = pWrtShell->getShellCursor(false);
    pCursor->SetMark();
    for (int i = 0; i < 510; i++)
    {
        pCursor->Move(fnMoveForward);
    }
    CPPUNIT_ASSERT_EQUAL(
        u"2.2 Publication Of Information - Caa\nSection 4.2 Of A Ca\'s Certificate Policy "
        "And/Or Certification Practice Statement Shall State The Ca\'s Policy Or Practice "
        "On Processing Caa Records For Fully Qualified Domain Names; That Policy Shall Be "
        "Consistent With These Requirements. \n\nIt Shall Clearly Specify The Set Of "
        "Issuer Domain Names That The Ca Recognises In Caa \"Issue\" Or \"Issuewild\" "
        "Records As Permitting It To Issue. The Ca Shall Log All Actions Taken, If Any, "
        "Consistent With Its Processing Practice."_ustr,
        lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf148148)
{
    using TF = TransliterationFlags;
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"   text"_ustr);

    /* Test what happens when node contains text but selection does not contain any text */
    pWrtShell->StartOfSection();
    SwShellCursor* pCursor = pWrtShell->getShellCursor(false);
    pCursor->SetMark();
    for (int i = 0; i < 3; i++)
    {
        pCursor->Move(fnMoveForward);
    }
    CPPUNIT_ASSERT_EQUAL(u"   text"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"   text"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"   text"_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"   text"_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Test what happens when node contains text but selection does not contain any text */
    createSwDoc();
    pDoc = getSwDoc();
    pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"text   "_ustr);

    pWrtShell->StartOfSection();
    pCursor = pWrtShell->getShellCursor(false);
    for (int i = 0; i < 4; i++)
    {
        pCursor->Move(fnMoveForward);
    }
    pCursor->SetMark();
    for (int i = 0; i < 2; i++)
    {
        pCursor->Move(fnMoveForward);
    }

    CPPUNIT_ASSERT_EQUAL(u"text   "_ustr, lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"text   "_ustr, lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"text   "_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"text   "_ustr,
                         lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Test what happens when node contains only non-word text but selection does not contain any text */
    createSwDoc();
    pDoc = getSwDoc();
    pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"-1   "_ustr);

    pWrtShell->StartOfSection();
    pCursor = pWrtShell->getShellCursor(false);
    for (int i = 0; i < 2; i++)
    {
        pCursor->Move(fnMoveForward);
    }
    pCursor->SetMark();
    for (int i = 0; i < 2; i++)
    {
        pCursor->Move(fnMoveForward);
    }

    CPPUNIT_ASSERT_EQUAL(u"-1   "_ustr, lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"-1   "_ustr, lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"-1   "_ustr, lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"-1   "_ustr, lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    createSwDoc();
    pDoc = getSwDoc();
    pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"   -1"_ustr);

    pWrtShell->StartOfSection();
    pCursor = pWrtShell->getShellCursor(false);
    pCursor->SetMark();
    for (int i = 0; i < 2; i++)
    {
        pCursor->Move(fnMoveForward);
    }

    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));

    /* Test what happens when node and selection contains only non-word text */
    createSwDoc();
    pDoc = getSwDoc();
    pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"   -1"_ustr);

    pWrtShell->StartOfSection();
    pCursor = pWrtShell->getShellCursor(false);
    pCursor->SetMark();
    for (int i = 0; i < 5; i++)
    {
        pCursor->Move(fnMoveForward);
    }

    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::LOWERCASE_UPPERCASE));
    CPPUNIT_ASSERT_EQUAL(u"   -1"_ustr, lcl_translitTest(*pDoc, *pCursor, TF::UPPERCASE_LOWERCASE));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf96943)
{
    // Enable hide whitespace mode.
    createSwDoc("tdf96943.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
    aViewOptions.SetHideWhitespaceMode(true);
    pWrtShell->ApplyViewOptions(aViewOptions);

    // Insert a new character at the end of the document.
    pWrtShell->SttEndDoc(/*bStt=*/false);
    pWrtShell->Insert(u"d"_ustr);

    // This was 2, a new page was created for the new layout line.
    CPPUNIT_ASSERT_EQUAL(1, getPages());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf96536)
{
    // Enable hide whitespace mode.
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
    aViewOptions.SetHideWhitespaceMode(true);
    pWrtShell->ApplyViewOptions(aViewOptions);
    CPPUNIT_ASSERT(pWrtShell->GetViewOptions()->IsWhitespaceHidden());

    // Insert a page break and go back to the first page.
    pWrtShell->InsertPageBreak();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    sal_Int32 nSingleParaPageHeight
        = getXPath(pXmlDoc, "/root/page[1]/infos/bounds""height").toInt32();

    // Insert a 2nd paragraph at the end of the first page, so the page height grows at least twice...
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XParagraphAppend> xParagraphAppend(xTextDocument->getText(),
                                                            uno::UNO_QUERY);
    const uno::Reference<text::XTextRange> xInsertPos = getRun(getParagraph(1), 1);
    xParagraphAppend->finishParagraphInsert(uno::Sequence<beans::PropertyValue>(), xInsertPos);
    calcLayout();
    pXmlDoc = parseLayoutDump();
    CPPUNIT_ASSERT(getXPath(pXmlDoc, "/root/page[1]/infos/bounds""height").toInt32()
                   >= 2 * nSingleParaPageHeight);

    // ... and then delete the 2nd paragraph, which shrinks the page to the previous size.
    uno::Reference<lang::XComponent> xParagraph(getParagraph(2), uno::UNO_QUERY);
    xParagraph->dispose();
    calcLayout();
    pXmlDoc = parseLayoutDump();
    CPPUNIT_ASSERT_EQUAL(nSingleParaPageHeight,
                         getXPath(pXmlDoc, "/root/page[1]/infos/bounds""height").toInt32());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf96479)
{
    // We want to verify the empty input text field in the bookmark
    static const OUString emptyInputTextField
        = OUStringChar(CH_TXT_ATR_INPUTFIELDSTART) + OUStringChar(CH_TXT_ATR_INPUTFIELDEND);

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

    // So we can clean up all references for reload
    {
        // Append bookmark
        SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
        SwPaM aPaM(aIdx);
        IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
        sw::mark::MarkBase* pMark
            = rIDMA.makeMark(aPaM, SwMarkName(u"original"_ustr),
                             IDocumentMarkAccess::MarkType::BOOKMARK, ::sw::mark::InsertMode::New);
        CPPUNIT_ASSERT(!pMark->IsExpanded());
        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());

        // Get helper objects
        uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);
        uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);

        // Create cursor from bookmark
        uno::Reference<text::XTextContent> xTextContent(
            xBookmarksSupplier->getBookmarks()->getByName(u"original"_ustr), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange = xTextContent->getAnchor();
        uno::Reference<text::XTextCursor> xCursor
            = xRange->getText()->createTextCursorByRange(xRange);
        CPPUNIT_ASSERT(xCursor->isCollapsed());

        // Remove bookmark
        xRange->getText()->removeTextContent(xTextContent);
        CPPUNIT_ASSERT_EQUAL(sal_Int32(0), rIDMA.getBookmarksCount());

        // Insert replacement bookmark
        uno::Reference<text::XTextContent> xBookmarkNew(
            xFactory->createInstance(u"com.sun.star.text.Bookmark"_ustr), uno::UNO_QUERY);
        uno::Reference<container::XNamed> xBookmarkName(xBookmarkNew, uno::UNO_QUERY);
        xBookmarkName->setName(u"replacement"_ustr);
        CPPUNIT_ASSERT(xCursor->isCollapsed());
        // Force bookmark expansion
        xCursor->getText()->insertString(xCursor, u"."_ustr, true);
        xCursor->getText()->insertTextContent(xCursor, xBookmarkNew, true);
        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        auto mark = *(rIDMA.getBookmarksBegin());
        CPPUNIT_ASSERT(mark->IsExpanded());

        // Create and insert input textfield with some content
        uno::Reference<text::XTextField> xTextField(
            xFactory->createInstance(u"com.sun.star.text.TextField.Input"_ustr), uno::UNO_QUERY);
        uno::Reference<text::XTextCursor> xCursorNew(
            xBookmarkNew->getAnchor()->getText()->createTextCursorByRange(
                xBookmarkNew->getAnchor()));
        CPPUNIT_ASSERT(!xCursorNew->isCollapsed());
        xCursorNew->getText()->insertTextContent(xCursorNew, xTextField, true);
        xBookmarkNew = uno::Reference<text::XTextContent>(
            xBookmarksSupplier->getBookmarks()->getByName(u"replacement"_ustr), uno::UNO_QUERY);
        xCursorNew = xBookmarkNew->getAnchor()->getText()->createTextCursorByRange(
            xBookmarkNew->getAnchor());
        CPPUNIT_ASSERT(!xCursorNew->isCollapsed());

        // Can't check the actual content of the text node via UNO
        mark = *(rIDMA.getBookmarksBegin());
        CPPUNIT_ASSERT(mark->IsExpanded());
        SwPaM pam(mark->GetMarkStart(), mark->GetMarkEnd());
        // Check for the actual bug, which didn't include CH_TXT_ATR_INPUTFIELDEND in the bookmark
        CPPUNIT_ASSERT_EQUAL(emptyInputTextField, pam.GetText());
    }

    {
        // Save and load cycle
        // Actually not needed, but the bug symptom of a missing bookmark
        // occurred because a broken bookmark was saved and loading silently
        // dropped the broken bookmark!
        saveAndReload(u"writer8"_ustr);
        pDoc = getSwDoc();

        // Lookup "replacement" bookmark
        IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);
        CPPUNIT_ASSERT(xBookmarksSupplier->getBookmarks()->hasByName(u"replacement"_ustr));

        uno::Reference<text::XTextContent> xTextContent(
            xBookmarksSupplier->getBookmarks()->getByName(u"replacement"_ustr), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange = xTextContent->getAnchor();
        uno::Reference<text::XTextCursor> xCursor
            = xRange->getText()->createTextCursorByRange(xRange);
        CPPUNIT_ASSERT(!xCursor->isCollapsed());

        // Verify bookmark content via text node / PaM
        auto mark = *(rIDMA.getBookmarksBegin());
        CPPUNIT_ASSERT(mark->IsExpanded());
        SwPaM pam(mark->GetMarkStart(), mark->GetMarkEnd());
        CPPUNIT_ASSERT_EQUAL(emptyInputTextField, pam.GetText());
    }
}

// If you resave original document the bookmark will be changed from
//
//  <text:p text:style-name="Standard">
//      <text:bookmark-start text:name="test"/>
//      <text:bookmark-end text:name="test"/>
//      def
//  </text:p>
//
// to
//
//  <text:p text:style-name="Standard">
//      <text:bookmark text:name="test"/>
//      def
//  </text:p>
//
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testBookmarkCollapsed)
{
    // load document
    createSwDoc("collapsed_bookmark.odt");

    // save original document
    save(u"writer8"_ustr);

    // load only content.xml from the resaved document
    xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);

    const charconst aPath("/office:document-content/office:body/office:text/text:p");

    const int pos1 = getXPathPosition(pXmlDoc, aPath, "bookmark");
    CPPUNIT_ASSERT_EQUAL(0, pos1); // found, and it is first

    CPPUNIT_ASSERT_ASSERTION_FAIL(getXPathPosition(pXmlDoc, aPath, "bookmark-start")); // not found
    CPPUNIT_ASSERT_ASSERTION_FAIL(getXPathPosition(pXmlDoc, aPath, "bookmark-end")); // not found
}

// 1. Open a new writer document
// 2. Enter the text "abcdef"
// 3. Select "abc"
// 4. Insert a bookmark on "abc" using Insert->Bookmark. Name the bookmark "test".
// 5. Open the navigator (F5)
//    Select the bookmark "test" using the navigator.
// 6. Hit Del, thus deleting "abc" (The bookmark "test" is still there).
// 7. Save the document:
//      <text:p text:style-name="Standard">
//          <text:bookmark text:name="test"/>
//          def
//      </text:p>
//
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRemoveBookmarkText)
{
    // create document
    {
        // create a text document with "abcdef"
        createSwDoc();
        SwDoc* pDoc = getSwDoc();

        {
            SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
            SwPaM aPaM(aIdx);
            pDoc->getIDocumentContentOperations().InsertString(aPaM, u"abcdef"_ustr);
        }

        // mark "abc" with "testBookmark" bookmark
        {
            SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
            SwPaM aPaM(aIdx);

            lcl_selectCharacters(aPaM, 0, 3);
            IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
            sw::mark::MarkBase* pMark = rIDMA.makeMark(aPaM, SwMarkName(u"testBookmark"_ustr),
                                                       IDocumentMarkAccess::MarkType::BOOKMARK,
                                                       ::sw::mark::InsertMode::New);

            // verify
            CPPUNIT_ASSERT(pMark->IsExpanded());
            CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        }

        // remove text marked with bookmark
        {
            SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
            SwPaM aPaM(aIdx);

            lcl_selectCharacters(aPaM, 0, 3);
            pDoc->getIDocumentContentOperations().DeleteRange(aPaM);

            // verify: bookmark is still exist
            IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
            CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        }
    }

    // save document
    save(u"writer8"_ustr);

    // load only content.xml from the resaved document
    xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);

    // Bookmark without text becomes collapsed
    assertXPath(pXmlDoc, "//office:body/office:text/text:p/text:bookmark", 1);
    assertXPath(pXmlDoc, "//office:body/office:text/text:p/text:bookmark-start", 0);
    assertXPath(pXmlDoc, "//office:body/office:text/text:p/text:bookmark-end", 0);
}

// 1. Open a new writer document
// 2. Enter the text "abcdef"
// 3. Select "abc"
// 4. Insert a bookmark on "abc" using Insert->Bookmark. Name the bookmark "test".
// 5. Open the navigator (F5)
//    Select the bookmark "test" using the navigator.
// 6. Hit Del, thus deleting "abc" (The bookmark "test" is still there).
// 7. Call our macro
//
//      Sub Main
//          bookmark = ThisComponent.getBookmarks().getByName("test")
//          bookmark.getAnchor().setString("abc")
//      End Sub
//
//    The text "abc" gets inserted inside the bookmark "test", and the document now contains the string "abcdef".
// 7. Save the document:
//      <text:p text:style-name="Standard">
//          <text:bookmark-start text:name="test"/>
//          abc
//          <text:bookmark-end text:name="test"/>
//          def
//      </text:p>
//
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRemoveBookmarkTextAndAddNew)
{
    // create document
    {
        // create a text document with "abcdef"
        createSwDoc();
        SwDoc* pDoc = getSwDoc();
        {
            SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
            SwPaM aPaM(aIdx);
            pDoc->getIDocumentContentOperations().InsertString(aPaM, u"abcdef"_ustr);
        }

        // mark "abc" with "testBookmark" bookmark
        {
            SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
            SwPaM aPaM(aIdx);

            lcl_selectCharacters(aPaM, 0, 3);
            IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
            sw::mark::MarkBase* pMark = rIDMA.makeMark(aPaM, SwMarkName(u"testBookmark"_ustr),
                                                       IDocumentMarkAccess::MarkType::BOOKMARK,
                                                       ::sw::mark::InsertMode::New);

            // verify
            CPPUNIT_ASSERT(pMark->IsExpanded());
            CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        }

        // remove text marked with bookmark
        {
            SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1);
            SwPaM aPaM(aIdx);

            lcl_selectCharacters(aPaM, 0, 3);
            pDoc->getIDocumentContentOperations().DeleteRange(aPaM);

            // verify: bookmark is still exist
            IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
            CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        }

        // write "abc" to area marked with "testBookmark" bookmark
        {
            // Get helper objects
            uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent,
                                                                        uno::UNO_QUERY);

            // Create cursor from bookmark
            uno::Reference<text::XTextContent> xTextContent(
                xBookmarksSupplier->getBookmarks()->getByName(u"testBookmark"_ustr),
                uno::UNO_QUERY);
            uno::Reference<text::XTextRange> xRange = xTextContent->getAnchor();
            CPPUNIT_ASSERT_EQUAL(u""_ustr, xRange->getString());

            // write "abc"
            xRange->setString(u"abc"_ustr);

            // verify: bookmark is still exist
            IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
            CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
        }
    }

    // save document
    save(u"writer8"_ustr);

    // load only content.xml from the resaved document
    xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);

    const charconst aPath("/office:document-content/office:body/office:text/text:p");

    CPPUNIT_ASSERT_ASSERTION_FAIL(getXPathPosition(pXmlDoc, aPath, "bookmark")); // not found
    const int pos2 = getXPathPosition(pXmlDoc, aPath, "bookmark-start");
    const int pos3 = getXPathPosition(pXmlDoc, aPath, "text");
    const int pos4 = getXPathPosition(pXmlDoc, aPath, "bookmark-end");

    CPPUNIT_ASSERT_EQUAL(0, pos2);
    CPPUNIT_ASSERT_EQUAL(1, pos3);
    CPPUNIT_ASSERT_EQUAL(2, pos4);
}

// 1. Load document:
//  <text:p text:style-name="Standard">
//      <text:bookmark-start text:name="test"/>
//      <text:bookmark-end text:name="test"/>
//      def
//  </text:p>
//
// 2. Call our macro
//
//      Sub Main
//          bookmark = ThisComponent.getBookmarks().getByName("test")
//          bookmark.getAnchor().setString("abc")
//      End Sub
//
//    The text "abc" gets inserted inside the bookmark "test", and the document now contains the string "abcdef".
// 3. Save the document:
//      <text:p text:style-name="Standard">
//          <text:bookmark text:name="test"/>
//          abcdef
//      </text:p>
//
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRemoveBookmarkTextAndAddNewAfterReload)
{
    // load document
    createSwDoc("collapsed_bookmark.odt");
    SwDoc* pDoc = getSwDoc();

    // write "abc" to area marked with "testBookmark" bookmark
    {
        // Get helper objects
        uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);

        // Create cursor from bookmark
        uno::Reference<text::XTextContent> xTextContent(
            xBookmarksSupplier->getBookmarks()->getByName(u"test"_ustr), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange = xTextContent->getAnchor();
        CPPUNIT_ASSERT_EQUAL(u""_ustr, xRange->getString());

        // write "abc"
        xRange->setString(u"abc"_ustr);

        // verify: bookmark is still exist
        IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
        CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rIDMA.getBookmarksCount());
    }

    // save original document
    save(u"writer8"_ustr);

    // load only content.xml from the resaved document
    xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);
    const charconst aPath("/office:document-content/office:body/office:text/text:p");

    const int pos1 = getXPathPosition(pXmlDoc, aPath, "bookmark");
    const int pos2 = getXPathPosition(pXmlDoc, aPath, "text");

    CPPUNIT_ASSERT_EQUAL(0, pos1);
    CPPUNIT_ASSERT_EQUAL(1, pos2);

    CPPUNIT_ASSERT_ASSERTION_FAIL(getXPathPosition(pXmlDoc, aPath, "bookmark-start")); // not found
    CPPUNIT_ASSERT_ASSERTION_FAIL(getXPathPosition(pXmlDoc, aPath, "bookmark-end")); // not found
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf96961)
{
    // Insert a page break.
    createSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->InsertPageBreak();

    // Enable hide whitespace mode.
    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
    aViewOptions.SetHideWhitespaceMode(true);
    pWrtShell->ApplyViewOptions(aViewOptions);

    calcLayout();

    // Assert that the height of the last page is larger than the height of other pages.
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    sal_Int32 nOther = getXPath(pXmlDoc, "/root/page[1]/infos/bounds""height").toInt32();
    sal_Int32 nLast = getXPath(pXmlDoc, "/root/page[2]/infos/bounds""height").toInt32();
    CPPUNIT_ASSERT(nLast > nOther);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf88453)
{
    createSwDoc("tdf88453.odt");
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // This was 0: the table does not fit the first page, but it wasn't split
    // to continue on the second page.
    assertXPath(pXmlDoc, "/root/page[2]/body/tab", 1);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf88453Table)
{
    createSwDoc("tdf88453-table.odt");
    calcLayout();
    // This was 2: layout could not split the large outer table in the document
    // into 3 pages.
    CPPUNIT_ASSERT_EQUAL(3, getPages());
}

namespace
{
int checkShells(const SwDocShell* pSource, const SwDocShell* pDestination)
{
    return int(SfxClassificationHelper::CheckPaste(pSource->getDocProperties(),
                                                   pDestination->getDocProperties()));
}
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testClassificationPaste)
{
    createSwDoc();
    SwDocShell* pSourceShell = getSwDocShell();
    uno::Reference<lang::XComponent> xSourceComponent = mxComponent;
    mxComponent.clear();

    createSwDoc();
    SwDocShell* pDestinationShell = getSwDocShell();

    // Not classified source, not classified destination.
    CPPUNIT_ASSERT_EQUAL(int(SfxClassificationCheckPasteResult::None),
                         checkShells(pSourceShell, pDestinationShell));

    // Classified source, not classified destination.
    uno::Sequence<beans::PropertyValue> aInternalOnly
        = comphelper::InitPropertySequence({ { "Name", uno::Any(u"Internal Only"_ustr) } });
    dispatchCommand(xSourceComponent, u".uno:ClassificationApply"_ustr, aInternalOnly);
    CPPUNIT_ASSERT_EQUAL(int(SfxClassificationCheckPasteResult::TargetDocNotClassified),
                         checkShells(pSourceShell, pDestinationShell));

    // Classified source and classified destination -- internal only has a higher level than confidential.
    uno::Sequence<beans::PropertyValue> aConfidential
        = comphelper::InitPropertySequence({ { "Name", uno::Any(u"Confidential"_ustr) } });
    dispatchCommand(mxComponent, u".uno:ClassificationApply"_ustr, aConfidential);
    CPPUNIT_ASSERT_EQUAL(int(SfxClassificationCheckPasteResult::DocClassificationTooLow),
                         checkShells(pSourceShell, pDestinationShell));

    xSourceComponent->dispose();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testSmallCaps)
{
    // Create a document, add some characters and select them.
    createSwDoc();
    createSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"text"_ustr);
    pWrtShell->SelAll();

    // Dispatch the command to make them formatted small capitals.
    dispatchCommand(mxComponent, u".uno:SmallCaps"_ustr, {});

    // This was css::style::CaseMap::NONE as the shell didn't handle the command.
    CPPUNIT_ASSERT_EQUAL(css::style::CaseMap::SMALLCAPS,
                         getProperty<sal_Int16>(getRun(getParagraph(1), 1), u"CharCaseMap"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf98987)
{
    createSwDoc("tdf98987.docx");
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[2]/SdrObject""name",
                u"Rectangle 1");
    sal_Int32 nRectangle1
        = getXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[2]/bounds""top")
              .toInt32();
    assertXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[1]/SdrObject""name",
                u"Rectangle 2");
    sal_Int32 nRectangle2
        = getXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[1]/bounds""top")
              .toInt32();
    CPPUNIT_ASSERT_GREATER(nRectangle1, nRectangle2);

    assertXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[3]/SdrObject""name",
                u"Rectangle 3");
    sal_Int32 nRectangle3
        = getXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[3]/bounds""top")
              .toInt32();
    // This failed: the 3rd rectangle had a smaller "top" value than the 2nd one, it even overlapped with the 1st one.
    CPPUNIT_ASSERT_GREATER(nRectangle2, nRectangle3);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf99004)
{
    createSwDoc("tdf99004.docx");
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    sal_Int32 nTextbox1Top
        = getXPath(pXmlDoc, "/root/page/body/txt/anchored/fly/infos/bounds""top").toInt32();
    sal_Int32 nTextBox1Height
        = getXPath(pXmlDoc, "/root/page/body/txt/anchored/fly/infos/bounds""height").toInt32();
    sal_Int32 nTextBox1Bottom = nTextbox1Top + nTextBox1Height;

    assertXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[1]/SdrObject""name",
                u"Rectangle 2");
    sal_Int32 nRectangle2Top
        = getXPath(pXmlDoc, "/root/page/body/txt/anchored/SwAnchoredDrawObject[1]/bounds""top")
              .toInt32();
    // This was 3291 and 2531, should be now around 2472 and 2531, i.e. the two rectangles should not overlap anymore.
    CPPUNIT_ASSERT(nTextBox1Bottom < nRectangle2Top);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf84695)
{
    createSwDoc("tdf84695.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);

    // Now Enter + a key should add some text.
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
    emulateTyping(u"a");

    uno::Reference<text::XTextRange> xShape(getShape(1), uno::UNO_QUERY);
    // This was empty, Enter did not start the fly frame edit mode.
    CPPUNIT_ASSERT_EQUAL(u"a"_ustr, xShape->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf84695NormalChar)
{
    createSwDoc("tdf84695.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);

    // Now pressing 'a' should add a character.
    emulateTyping(u"a");

    uno::Reference<text::XTextRange> xShape(getShape(1), uno::UNO_QUERY);
    // This was empty, pressing a normal character did not start the fly frame edit mode.
    CPPUNIT_ASSERT_EQUAL(u"a"_ustr, xShape->getString());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf84695Tab)
{
    createSwDoc("tdf84695-tab.odt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    SdrObject* pObject = pPage->GetObj(0);
    SwContact* pShape = static_cast<SwContact*>(pObject->GetUserCall());
    // First, make sure that pShape is a draw shape.
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_DRAWFRMFMT), pShape->GetFormat()->Which());

    // Then select it.
    pWrtShell->SelectObj(Point(), 0, pObject);

    // Now pressing 'tab' should jump to the other shape.
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_TAB);
    Scheduler::ProcessEventsToIdle();

    // And finally make sure the selection has changed.
    const SdrMarkList& rMarkList = pWrtShell->GetDrawView()->GetMarkedObjectList();
    SwContact* pOtherShape
        = static_cast<SwContact*>(rMarkList.GetMark(0)->GetMarkedSdrObj()->GetUserCall());
    // This failed, 'tab' didn't do anything -> the selected shape was the same.
    CPPUNIT_ASSERT(pOtherShape != pShape);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTableStyleUndo)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();

    sal_Int32 nStyleCount = pDoc->GetTableStyles().size();
    SwTableAutoFormat* pStyle = pDoc->MakeTableStyle(TableStyleName(u"Test Style"_ustr));
    SvxBrushItem aBackground(COL_LIGHTMAGENTA, RES_BACKGROUND);
    pStyle->GetBoxFormat(0).GetProps().SetBackground(aBackground);

    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount + 1);
    rUndoManager.Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount);
    rUndoManager.Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount + 1);
    // check if attributes are preserved
    pStyle = pDoc->GetTableStyles().FindAutoFormat(TableStyleName(u"Test Style"_ustr));
    CPPUNIT_ASSERT(pStyle);
    CPPUNIT_ASSERT(bool(pStyle->GetBoxFormat(0).GetProps().GetBackground() == aBackground));

    pDoc->DelTableStyle(TableStyleName(u"Test Style"_ustr));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount);
    rUndoManager.Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount + 1);
    pStyle = pDoc->GetTableStyles().FindAutoFormat(TableStyleName(u"Test Style"_ustr));
    // check if attributes are preserved
    CPPUNIT_ASSERT(pStyle);
    CPPUNIT_ASSERT(bool(pStyle->GetBoxFormat(0).GetProps().GetBackground() == aBackground));
    rUndoManager.Redo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount);

    // undo delete so we can replace the style
    rUndoManager.Undo();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(pDoc->GetTableStyles().size()), nStyleCount + 1);
    pStyle = pDoc->GetTableStyles().FindAutoFormat(TableStyleName(u"Test Style"_ustr));
    CPPUNIT_ASSERT(pStyle);
    CPPUNIT_ASSERT(bool(pStyle->GetBoxFormat(0).GetProps().GetBackground() == aBackground));

    SwTableAutoFormat aNewStyle(TableStyleName(u"Test Style2"_ustr));
    SvxBrushItem aBackground2(COL_LIGHTGREEN, RES_BACKGROUND);
    aNewStyle.GetBoxFormat(0).GetProps().SetBackground(aBackground2);

    pDoc->ChgTableStyle(TableStyleName(u"Test Style"_ustr), aNewStyle);
    pStyle = pDoc->GetTableStyles().FindAutoFormat(TableStyleName(u"Test Style"_ustr));
    CPPUNIT_ASSERT(pStyle);
    CPPUNIT_ASSERT(bool(pStyle->GetBoxFormat(0).GetProps().GetBackground() == aBackground2));
    rUndoManager.Undo();
    pStyle = pDoc->GetTableStyles().FindAutoFormat(TableStyleName(u"Test Style"_ustr));
    CPPUNIT_ASSERT(pStyle);
    CPPUNIT_ASSERT(bool(pStyle->GetBoxFormat(0).GetProps().GetBackground() == aBackground));
    rUndoManager.Redo();
    pStyle = pDoc->GetTableStyles().FindAutoFormat(TableStyleName(u"Test Style"_ustr));
    CPPUNIT_ASSERT(pStyle);
    CPPUNIT_ASSERT(bool(pStyle->GetBoxFormat(0).GetProps().GetBackground() == aBackground2));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRedlineCopyPaste)
{
    // regressed in tdf#106746
    createSwDoc();
    SwDoc* pDoc = getSwDoc();

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

    pDoc->getIDocumentContentOperations().InsertString(aPaM, u"abzdezgh"_ustr);
    SwTextNode* pTextNode = aPaM.GetPointNode().GetTextNode();

    // Turn on track changes, make changes, turn off track changes
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
    lcl_selectCharacters(aPaM, 2, 3);
    pDoc->getIDocumentContentOperations().ReplaceRange(aPaM, u"c"_ustr, false);
    lcl_selectCharacters(aPaM, 6, 7);
    pDoc->getIDocumentContentOperations().ReplaceRange(aPaM, u"f"_ustr, false);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(false));

    // Create the clipboard document.
    SwDoc aClipboard;
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    // Select the whole content, copy, delete the original and paste the copied content
    pWrtShell->SelAll();
    pWrtShell->Copy(aClipboard);
    pWrtShell->Delete();
    pWrtShell->Paste(aClipboard);

    // With the bug this is "abzcdefgh", ie. contains the first deleted piece, too
    CPPUNIT_ASSERT_EQUAL(u"abcdefgh"_ustr, pTextNode->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf135260)
{
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"test"_ustr);

    // Turn on track changes
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));

    for (int i = 0; i < 4; i++)
    {
        pWrtShell->DelLeft();
    }

    SwEditShell* const pEditShell(pDoc->GetEditShell());
    CPPUNIT_ASSERT(pEditShell);
    // accept all redlines
    while (pEditShell->GetRedlineCount())
        pEditShell->AcceptRedline(0);

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

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRedlineParam)
{
    // Create a document with minimal content.
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"middle"_ustr);

    // Turn on track changes, and add changes to the start and end of the document.
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
    pWrtShell->StartOfSection();
    pWrtShell->Insert(u"aaa"_ustr);
    pWrtShell->EndOfSection();
    pWrtShell->Insert(u"zzz"_ustr);

    const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), rTable.size());

    // Select the first redline.
    pWrtShell->StartOfSection();
    uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
        { { "NextTrackedChange", uno::Any(o3tl::narrowing<sal_uInt16>(rTable[0]->GetId())) } }));
    dispatchCommand(mxComponent, u".uno:NextTrackedChange"_ustr, aPropertyValues);
    SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
    // This failed: the parameter wasn't handled so the next change (zzz) was
    // selected, not the first one (aaa).
    CPPUNIT_ASSERT_EQUAL(u"aaa"_ustr, pShellCursor->GetText());

    // Select the second redline.
    pWrtShell->StartOfSection();
    aPropertyValues = comphelper::InitPropertySequence(
        { { "NextTrackedChange", uno::Any(o3tl::narrowing<sal_uInt16>(rTable[1]->GetId())) } });
    dispatchCommand(mxComponent, u".uno:NextTrackedChange"_ustr, aPropertyValues);
    pShellCursor = pWrtShell->getShellCursor(false);
    CPPUNIT_ASSERT_EQUAL(u"zzz"_ustr, pShellCursor->GetText());

    // Move the cursor to the start again, and reject the second change.
    pWrtShell->StartOfSection();
    aPropertyValues = comphelper::InitPropertySequence(
        { { "RejectTrackedChange", uno::Any(o3tl::narrowing<sal_uInt16>(rTable[1]->GetId())) } });
    dispatchCommand(mxComponent, u".uno:RejectTrackedChange"_ustr, aPropertyValues);
    pShellCursor = pWrtShell->getShellCursor(false);

    // This was 'middlezzz', the uno command rejected the redline under the
    // cursor, instead of the requested one.
    CPPUNIT_ASSERT_EQUAL(u"aaamiddle"_ustr,
                         pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRedlineViewAuthor)
{
    // Test that setting an author at an SwView level has effect.

    // Create a document with minimal content.
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"middle"_ustr);
    SwView* pView = pDocShell->GetView();
    static constexpr OUString aAuthor(u"A U. Thor"_ustr);
    pView->SetRedlineAuthor(aAuthor);
    pDocShell->SetView(pView);

    // Turn on track changes, and add changes to the start of the document.
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
    pWrtShell->StartOfSection();
    pWrtShell->Insert(u"aaa"_ustr);

    // Now assert that SwView::SetRedlineAuthor() had an effect.
    const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(1), rTable.size());
    SwRangeRedline* pRedline = rTable[0];
    // This was 'Unknown Author' instead of 'A U. Thor'.
    CPPUNIT_ASSERT_EQUAL(aAuthor, pRedline->GetAuthorString());

    // Insert a comment and assert that SwView::SetRedlineAuthor() affects this as well.
    dispatchCommand(mxComponent, u".uno:.uno:InsertAnnotation"_ustr, {});
    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<beans::XPropertySet> xField(xFields->nextElement(), uno::UNO_QUERY);
    // This was 'Unknown Author' instead of 'A U. Thor'.
    CPPUNIT_ASSERT_EQUAL(aAuthor, xField->getPropertyValue(u"Author"_ustr).get<OUString>());

    //Reset the redline author after using it, otherwise, it might interfere with other unittests
    pView->SetRedlineAuthor(u"Unknown Author"_ustr);
    pDocShell->SetView(pView);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf91292)
{
    createSwDoc("tdf91292_paraBackground.docx");
    uno::Reference<beans::XPropertySet> xPropertySet(getParagraph(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Solid background color", drawing::FillStyle_SOLID,
                                 getProperty<drawing::FillStyle>(xPropertySet, u"FillStyle"_ustr));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Background Color", Color(0x5C2D91),
                                 getProperty<Color>(xPropertySet, u"FillColor"_ustr));

    // remove background color
    xPropertySet->setPropertyValue(u"FillStyle"_ustr, uno::Any(drawing::FillStyle_NONE));

    // Save it and load it back.
    saveAndReload(u"Office Open XML Text"_ustr);

    xPropertySet.set(getParagraph(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("No background color", drawing::FillStyle_NONE,
                                 getProperty<drawing::FillStyle>(xPropertySet, u"FillStyle"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf78727)
{
    createSwDoc("tdf78727.docx");
    SwDoc* pDoc = getSwDoc();
    SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
    // This was 1: make sure we don't loose the TextBox anchored inside the
    // table that is moved inside a text frame.
    CPPUNIT_ASSERT(SwTextBoxHelper::getCount(pPage) > 1);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRedlineTimestamp)
{
    // Test that a redline timestamp's second is not always 0.

    // Create a document with minimal content.
    createSwDoc();
    SwDoc* pDoc = getSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"middle"_ustr);

    // Turn on track changes, and add changes to the start and to the end of
    // the document.
    uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY);
    xPropertySet->setPropertyValue(u"RecordChanges"_ustr, uno::Any(true));
    pWrtShell->StartOfSection();
    pWrtShell->Insert(u"aaa"_ustr);
    osl::Thread::wait(std::chrono::seconds(1));
    pWrtShell->EndOfSection();
    pWrtShell->Insert(u"zzz"_ustr);

    // Inserting additional characters at the start changed the table size to
    // 3, i.e. the first and the second "aaa" wasn't combined.
    pWrtShell->StartOfSection();
    pWrtShell->Insert(u"aaa"_ustr);

    // Now assert that at least one of the seconds are not 0.
    const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
    if (rTable.size() >= 2
        && rTable[0]->GetRedlineData().GetTimeStamp().GetMin()
               != rTable[1]->GetRedlineData().GetTimeStamp().GetMin())
        // The relatively rare case when waiting for a second also changes the minute.
        return;

    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), rTable.size());
    sal_uInt16 nSec1 = rTable[0]->GetRedlineData().GetTimeStamp().GetSec();
    sal_uInt16 nSec2 = rTable[1]->GetRedlineData().GetTimeStamp().GetSec();
    // This failed, seconds was always 0.
    CPPUNIT_ASSERT(nSec1 != 0 || nSec2 != 0);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testCursorWindows)
{
    // Create a new document with one window.
    createSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell1 = pDocShell->GetWrtShell();

    // Create a second view and type something.
    pDocShell->GetViewShell()->GetViewFrame().GetDispatcher()->Execute(
        SID_NEWWINDOW, SfxCallMode::SYNCHRON | SfxCallMode::RECORD);
    SwWrtShell* pWrtShell2 = pDocShell->GetWrtShell();
    OUString aText(u"foo"_ustr);
    pWrtShell2->Insert(aText);

    // Assert that only the cursor of the actual window move, not other cursors.
    SwShellCursor* pShellCursor1 = pWrtShell1->getShellCursor(false);
    SwShellCursor* pShellCursor2 = pWrtShell2->getShellCursor(false);
    // This was 3, not 0 -- cursor of the other window moved.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), pShellCursor1->Start()->GetContentIndex());
    CPPUNIT_ASSERT_EQUAL(aText.getLength(), pShellCursor2->Start()->GetContentIndex());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testLandscape)
{
    // Set page orientation to landscape.
    createSwDoc();
    uno::Sequence<beans::PropertyValue> aPropertyValues(
        comphelper::InitPropertySequence({ { "AttributePage.Landscape", uno::Any(true) } }));
    dispatchCommand(mxComponent, u".uno:AttributePage"_ustr, aPropertyValues);

    // Assert that the document model was modified.
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    size_t nPageDesc = pWrtShell->GetCurPageDesc();
    // This failed, page was still portrait.
    CPPUNIT_ASSERT(pWrtShell->GetPageDesc(nPageDesc).GetLandscape());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf95699)
{
    // Open the document with single FORMCHECKBOX field, select all and copy to clipboard
    // then check that clipboard contains the FORMCHECKBOX in text body.
    // Previously that failed.
    createSwDoc("tdf95699.odt");
    SwDoc* pDoc = getSwDoc();
    IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    SwDoc aClipboard;
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->SelAll();
    pWrtShell->Copy(aClipboard);
    pMarkAccess = aClipboard.getIDocumentMarkAccess();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMarkAccess->getAllMarksCount());
    ::sw::mark::Fieldmark* pFieldMark
        = pMarkAccess->getFieldmarkAfter(SwPosition(aClipboard.GetNodes().GetEndOfExtras()), false);
    CPPUNIT_ASSERT_EQUAL(u"vnd.oasis.opendocument.field.FORMCHECKBOX"_ustr,
                         pFieldMark->GetFieldname());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf151548_tabNavigation2)
{
    // given a form-protected doc with 2 unchecked legacy fieldmark checkboxes, 1 modern
    // checkbox, and a couple of other content controls that are not supposed to
    // have their contents selected upon entry into the control (i.e. no placeholder text).
    createSwDoc("tdf151548_tabNavigation2.docx");
    SwDoc* pDoc = getSwDoc();

    IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMarkAccess->getFieldmarksCount());

    // verify that the checkboxes start off in the unchecked state
    for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it)
    {
        sw::mark::CheckboxFieldmark* pCheckBox = dynamic_cast<::sw::mark::CheckboxFieldmark*>(*it);
        CPPUNIT_ASSERT(!pCheckBox->IsChecked());
    }

    // Toggle on the legacy checkbox
    SwXTextDocument* pTextDoc = getSwTextDoc();
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, KEY_SPACE);
    // Tab to the next control - the modern checkbox
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    // Tab to the next control - the second legacy checkbox, and toggle it on.
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, KEY_SPACE);
    // Tab to the next control - a plain text control without placeholder text
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    // Tab to the next control - a combobox with custom text
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    Scheduler::ProcessEventsToIdle();

    for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it)
    {
        sw::mark::CheckboxFieldmark* pCheckBox = dynamic_cast<::sw::mark::CheckboxFieldmark*>(*it);
        // verify that the legacy checkbox became checked by the first loop.
        CPPUNIT_ASSERT(pCheckBox->IsChecked());

        // This is where it was failing. Tab got stuck moving into the plain text/combobox,
        // so it could never loop around. At this point we are at the end of the loop,
        // so the next tab should take us back to the beginning with the first legacy checkbox.

        // Tab to the legacy checkbox, and toggle it off.
        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, KEY_SPACE);
        Scheduler::ProcessEventsToIdle();
        CPPUNIT_ASSERT(!pCheckBox->IsChecked());

        // Tab to the next content control
        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf151548_tabNavigation)
{
    // given a form-protected doc with 4 unchecked legacy fieldmark checkboxes (and several modern
    // content controls which all have a tabstop of -1 to disable tabstop navigation to them)
    // we want to test that tab navigation completes and loops around to continue at the beginning.
    createSwDoc("tdf151548_tabNavigation.docm");
    SwDoc* pDoc = getSwDoc();

    IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pMarkAccess->getFieldmarksCount());

    SwXTextDocument* pTextDoc = getSwTextDoc();
    // Tab and toggle 4 times, verifying beforehand that the state was unchecked
    for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it)
    {
        sw::mark::CheckboxFieldmark* pCheckBox = dynamic_cast<::sw::mark::CheckboxFieldmark*>(*it);
        CPPUNIT_ASSERT(!pCheckBox->IsChecked());

        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, KEY_SPACE); // toggle checkbox on
        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB); // move to next control
        Scheduler::ProcessEventsToIdle();
    }

    // Tab 4 more times, verifying beforehand that the checkbox had been toggle on, then toggles off
    // meaning that looping is working, and no other controls are reacting to the tab key.
    for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it)
    {
        sw::mark::CheckboxFieldmark* pCheckBox = dynamic_cast<::sw::mark::CheckboxFieldmark*>(*it);

        CPPUNIT_ASSERT(pCheckBox->IsChecked());
        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, KEY_SPACE); // toggle checkbox off
        Scheduler::ProcessEventsToIdle();

        CPPUNIT_ASSERT(!pCheckBox->IsChecked());
        pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB); // move to next control
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf104032)
{
    // Open the document with FORMCHECKBOX field, select it and copy to clipboard
    // Go to end of document and paste it, then undo
    // Previously that asserted in debug build.
    createSwDoc("tdf104032.odt");
    SwDoc* pDoc = getSwDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();
    SwDoc aClipboard;
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->StartOfSection();
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, /*bBasicCall=*/false);
    pWrtShell->Copy(aClipboard);
    pWrtShell->EndOfSection();
    pWrtShell->Paste(aClipboard);
    rUndoManager.Undo();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf104440)
{
    createSwDoc("tdf104440.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // This was 0: both Text Frames in the document were anchored to a
    // paragraph on page 1, while we expect that the second Text Frame is
    // anchored to a paragraph on page 2.
    assertXPath(pXmlDoc, "//page[2]/body/txt/anchored");
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf104425)
{
    createSwDoc("tdf104425.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // The document contains one top-level 1-cell table with minimum row height set to 70 cm,
    // and the cell contents does not exceed the minimum row height.
    // It should span over 3 pages.
    assertXPath(pXmlDoc, "//page", 3);
    sal_Int32 nHeight1
        = getXPath(pXmlDoc, "//page[1]/body/tab/row/infos/bounds", "height").toInt32();
    sal_Int32 nHeight2
        = getXPath(pXmlDoc, "//page[2]/body/tab/row/infos/bounds", "height").toInt32();
    sal_Int32 nHeight3
        = getXPath(pXmlDoc, "//page[3]/body/tab/row/infos/bounds", "height").toInt32();
    double fSumHeight_mm = o3tl::convert<double>(nHeight1 + nHeight2 + nHeight3, o3tl::Length::twip,
                                                 o3tl::Length::mm);
    CPPUNIT_ASSERT_DOUBLES_EQUAL(700.0, fSumHeight_mm, 0.05);
}

// accepting change tracking gets stuck on change
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf104814)
{
    createSwDoc("tdf104814.docx");
    SwDoc* pDoc = getSwDoc();

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

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

// crash at redo of accepting table change tracking imported from DOCX
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTableRedlineRedoCrash)
{
    createSwDoc("TC-table-del-add.docx");
    SwDoc* pDoc = getSwDoc();
    sw::UndoManager& rUndoManager = pDoc->GetUndoManager();

    // accept all redlines, Undo and accept all redlines again

    IDocumentRedlineAccess& rIDRA(pDoc->getIDocumentRedlineAccess());
    rIDRA.AcceptAllRedline(/*bAccept=*/true);

    rUndoManager.Undo();

    // without the fix, it crashes
    rIDRA.AcceptAllRedline(true);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTableRemoveHasTextChangesOnly)
{
    createSwDoc("TC-table-del-add.docx");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // disable Record Changes
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    CPPUNIT_ASSERT_MESSAGE("redlining should be off",
                           !pDoc->getIDocumentRedlineAccess().IsRedlineOn());

    // 4 rows in Show Changes mode
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);

    // Accepting tracked deletions results 3 rows
    IDocumentRedlineAccess& rIDRA(pDoc->getIDocumentRedlineAccess());
    rIDRA.AcceptAllRedline(/*bAccept=*/true);
    Scheduler::ProcessEventsToIdle();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 3);

    // Undo: 4 rows again
    pDoc->GetIDocumentUndoRedo().Undo();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);

    // Accepting again: 3 rows (Undo of HasTextChangesOnly is correct)
    rIDRA.AcceptAllRedline(/*bAccept=*/true);
    Scheduler::ProcessEventsToIdle();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 3);

    // Undo: 4 rows again
    pDoc->GetIDocumentUndoRedo().Undo();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);

    // Move the cursor after the redline, and insert some text without change tracking
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    pWrtShell->Insert(u"X"_ustr);

    // Accepting again: 4 rows (extra text keeps the deleted row)
    rIDRA.AcceptAllRedline(/*bAccept=*/true);
    Scheduler::ProcessEventsToIdle();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);

    // delete the extra text with change tracking:
    // this resulted tracked row deletion again, because of missing
    // removing of HasTextChangeOnly SwTabLine property at accepting deletions previously

    // disable Record Changes
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    CPPUNIT_ASSERT_MESSAGE("redlining should be on",
                           pDoc->getIDocumentRedlineAccess().IsRedlineOn());

    dispatchCommand(mxComponent, u".uno:SwBackSpace"_ustr, {});
    rIDRA.AcceptAllRedline(/*bAccept=*/true);
    Scheduler::ProcessEventsToIdle();
    pXmlDoc = parseLayoutDump();
    // This was 3
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTableRemoveHasTextChangesOnly2)
{
    createSwDoc("TC-table-del-add.docx");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // disable Record Changes
    dispatchCommand(mxComponent, u".uno:TrackChanges"_ustr, {});
    CPPUNIT_ASSERT_MESSAGE("redlining should be off",
                           !pDoc->getIDocumentRedlineAccess().IsRedlineOn());

    // check redline count
    SwEditShell* const pEditShell(pDoc->GetEditShell());
    CPPUNIT_ASSERT(pEditShell);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount());

    // 4 rows in Show Changes mode
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);

    // Move the cursor to the tracked insertion, after the first redline to activate the
    // acception of the whole table row insertion with a single "Accept Change"
    pWrtShell->Down(/*bSelect=*/false);
    pWrtShell->Down(/*bSelect=*/false);
    pWrtShell->Down(/*bSelect=*/false);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    Scheduler::ProcessEventsToIdle();
    dispatchCommand(mxComponent, u".uno:AcceptTrackedChange"_ustr, {});
    pXmlDoc = parseLayoutDump();
    // Accepting tracked insertion results still 4 rows, but less redlines
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(11), pEditShell->GetRedlineCount());

    // Undo: 4 rows again
    pDoc->GetIDocumentUndoRedo().Undo();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount());

    // To check Undo of HasTextChangesOnly reject the same row results 3 rows
    dispatchCommand(mxComponent, u".uno:Escape"_ustr, {});
    dispatchCommand(mxComponent, u".uno:RejectTrackedChange"_ustr, {});
    pXmlDoc = parseLayoutDump();
    // This was 4 (lost HasTextChangesOnly)
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 3);

    // Undo: 4 rows again
    pDoc->GetIDocumentUndoRedo().Undo();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf147182_AcceptAllChangesInTableSelection)
{
    createSwDoc("TC-table-del-add.docx");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

    // check redline count
    SwEditShell* const pEditShell(pDoc->GetEditShell());
    CPPUNIT_ASSERT(pEditShell);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount());

    // 4 rows in Show Changes mode
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);

    // Select the first table to get a table selection
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    dispatchCommand(mxComponent, u".uno:AcceptTrackedChange"_ustr, {});
    pXmlDoc = parseLayoutDump();
    // Accepting tracked changes in the selected table results 3 rows
    // This was 4 (only text changes of the first selected cell were accepted)
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 3);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(8), pEditShell->GetRedlineCount());

    // Undo: 4 rows again
    pDoc->GetIDocumentUndoRedo().Undo();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount());

    // To check Undo of HasTextChangesOnly reject the same row results 3 rows
    dispatchCommand(mxComponent, u".uno:Escape"_ustr, {});
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
    dispatchCommand(mxComponent, u".uno:RejectTrackedChange"_ustr, {});
    pXmlDoc = parseLayoutDump();
    // This was 4 (only text changes of the first selected cell were rejected)
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 3);

    // Undo: 4 rows again
    pDoc->GetIDocumentUndoRedo().Undo();
    pXmlDoc = parseLayoutDump();
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row", 4);
    CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(14), pEditShell->GetRedlineCount());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf66405)
{
    // Imported formula should have zero margins
    createSwDoc("tdf66405.docx");
    uno::Reference<text::XTextEmbeddedObjectsSupplier> xEmbeddedObjectsSupplier(mxComponent,
                                                                                uno::UNO_QUERY);
    uno::Reference<container::XNameAccess> xEmbeddedObjects
        = xEmbeddedObjectsSupplier->getEmbeddedObjects();
    uno::Reference<beans::XPropertySet> xFormula;
    xEmbeddedObjects->getByName(xEmbeddedObjects->getElementNames()[0]) >>= xFormula;
    uno::Reference<beans::XPropertySet> xComponent;
    xFormula->getPropertyValue(u"Component"_ustr) >>= xComponent;

    // Test embedded object's margins
    sal_Int32 nLeftMargin, nRightMargin, nTopMargin, nBottomMargin;
    xFormula->getPropertyValue(u"LeftMargin"_ustr) >>= nLeftMargin;
    xFormula->getPropertyValue(u"RightMargin"_ustr) >>= nRightMargin;
    xFormula->getPropertyValue(u"TopMargin"_ustr) >>= nTopMargin;
    xFormula->getPropertyValue(u"BottomMargin"_ustr) >>= nBottomMargin;
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nLeftMargin);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nRightMargin);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nTopMargin);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nBottomMargin);

    // Test embedded object component's margins
    xComponent->getPropertyValue(u"LeftMargin"_ustr) >>= nLeftMargin;
    xComponent->getPropertyValue(u"RightMargin"_ustr) >>= nRightMargin;
    xComponent->getPropertyValue(u"TopMargin"_ustr) >>= nTopMargin;
    xComponent->getPropertyValue(u"BottomMargin"_ustr) >>= nBottomMargin;
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nLeftMargin);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nRightMargin);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nTopMargin);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nBottomMargin);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf35021_tabOverMarginDemo)
{
#if HAVE_MORE_FONTS
    createSwDoc("tdf35021_tabOverMarginDemo.doc");
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // Tabs should go past the margin @ ~3381
    sal_Int32 nMargin = getXPath(pXmlDoc, "//body/txt[1]/infos/prtBounds", "width").toInt32();
    // left tab was 3381 because it got its own full line
    sal_Int32 nWidth
        = getXPath(pXmlDoc, "//SwFixPortion[@type='PortionType::TabLeft']", "width").toInt32();
    CPPUNIT_ASSERT_MESSAGE("Left Tab width is ~4479", nMargin < nWidth);
    // center tab was 842
    nWidth = getXPath(pXmlDoc, "//SwFixPortion[@type='PortionType::TabCenter']", "width").toInt32();
    CPPUNIT_ASSERT_MESSAGE("Center Tab width is ~3521", nMargin < nWidth);
    // right tab was probably the same as center tab.
    nWidth = getXPath(pXmlDoc, "//SwFixPortion[@type='PortionType::TabRight']", "width").toInt32();
    CPPUNIT_ASSERT_MESSAGE("Right Tab width is ~2907", sal_Int32(2500) < nWidth);
    // decimal tab was 266
    nWidth
        = getXPath(pXmlDoc, "//SwFixPortion[@type='PortionType::TabDecimal']", "width").toInt32();
    CPPUNIT_ASSERT_MESSAGE("Decimal Tab width is ~4096", nMargin < nWidth);
#endif
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf106701_tabOverMarginAutotab)
{
    createSwDoc("tdf106701_tabOverMarginAutotab.doc");
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // The right margin is ~3378
    sal_Int32 nRightMargin = getXPath(pXmlDoc, "//body/txt[1]/infos/prtBounds", "width").toInt32();
    // Automatic tabstops should never be affected by tabOverMargin compatibility
    // The 1st line's width previously was ~9506
    sal_Int32 nWidth = getXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "width").toInt32();
    CPPUNIT_ASSERT_MESSAGE("1st line's width is less than the right margin", nWidth < nRightMargin);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf104492)
{
    createSwDoc("tdf104492.docx");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // The document should split table over 3 pages.
    assertXPath(pXmlDoc, "//page", 3);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf107025)
{
    // Tdf107025 - characters advance with wrong distance, so that
    // they are cluttered because of negative value or
    // break into multiple lines because of overflow.
    // The test document uses DFKAI-SB shipped with Windows.
    createSwDoc("tdf107025.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // Verify the number of characters in each line.
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1),
                         getXPath(pXmlDoc, "(//SwLinePortion)[1]", "length").toInt32());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(9),
                         getXPath(pXmlDoc, "(//SwLinePortion)[2]", "length").toInt32());

    // Do the subsequent test only if the first line can be displayed,
    // in case that the required font does not exist.
    sal_Int32 nWidth1 = getXPath(pXmlDoc, "(//SwLinePortion)[1]", "width").toInt32();
    if (!nWidth1)
        return;

    CPPUNIT_ASSERT(!getXPath(pXmlDoc, "(//SwLinePortion)[2]", "width").isEmpty());
    sal_Int32 nWidth2 = getXPath(pXmlDoc, "(//SwLinePortion)[2]", "width").toInt32();
    sal_Int32 nRatio = nWidth2 / nWidth1;

    CPPUNIT_ASSERT(nRatio >= 9); // Occupy at least 9 cells.
    CPPUNIT_ASSERT(nRatio < 18); // Occupy at most 18 cells.
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf107362)
{
    createSwDoc("tdf107362.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    sal_Int32 nHeight
        = getXPath(pXmlDoc, "(//SwParaPortion/SwLineLayout/child::*[@type='PortionType::Text'])[1]",
                   "height")
              .toInt32();
    sal_Int32 nWidth1
        = getXPath(pXmlDoc, "(//SwParaPortion/SwLineLayout/child::*[@type='PortionType::Text'])[1]",
                   "width")
              .toInt32();
    sal_Int32 nWidth2
        = getXPath(pXmlDoc, "(//SwParaPortion/SwLineLayout/child::*[@type='PortionType::Text'])[2]",
                   "width")
              .toInt32();
    sal_Int32 nLineWidth = getXPath(pXmlDoc, "//SwParaPortion/SwLineLayout", "width").toInt32();
    sal_Int32 nKernWidth = nLineWidth - nWidth1 - nWidth2;
    // Test only if fonts are available
    if (nWidth1 > 500 && nWidth2 > 200)
    {
        // Kern width should be smaller than 1/3 of the CJK font height.
        CPPUNIT_ASSERT(nKernWidth * 3 < nHeight);
    }
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf105417)
{
    createSwDoc("tdf105417.odt");
    SwView* pView = getSwDocShell()->GetView();
    CPPUNIT_ASSERT(pView);
    uno::Reference<linguistic2::XHyphenator> xHyphenator = LinguMgr::GetHyphenator();
    CPPUNIT_ASSERT(xHyphenator.is());
    // If there are no English hyphenation rules installed, we can't test
    // hyphenation.
    if (!xHyphenator->hasLocale(lang::Locale(u"en"_ustr, u"US"_ustr, OUString())))
        return;

    uno::Reference<linguistic2::XLinguProperties> xLinguProperties(LinguMgr::GetLinguPropertySet());
    // Automatic hyphenation means not opening a dialog, but going ahead
    // non-interactively.
    xLinguProperties->setIsHyphAuto(true);
    SwHyphWrapper aWrap(*pView, xHyphenator, /*bStart=*/false, /*bOther=*/true,
                        /*bSelection=*/false);
    // This never returned, it kept trying to hyphenate the last word
    // (greenbacks) again and again.
    aWrap.SpellDocument();
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf105625)
{
    createSwDoc("tdf105625.fodt");
    SwDoc* pDoc = getSwDoc();
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Ensure correct initial setting
    std::shared_ptr<comphelper::ConfigurationChanges> batch(
        comphelper::ConfigurationChanges::create());
    officecfg::Office::Writer::Cursor::Option::IgnoreProtectedArea::set(false, batch);
    batch->commit();
    // We should be able to edit at positions adjacent to fields.
    // Check if the start and the end of the 1st paragraph are not protected
    // (they are adjacent to FORMCHECKBOX)
    pWrtShell->SttPara();
    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->HasReadonlySel());
    pWrtShell->EndPara();
    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->HasReadonlySel());
    // 2nd paragraph - FORMTEXT
    pWrtShell->Down(/*bSelect=*/false);
    // Check selection across FORMTEXT field boundary - must be read-only
    pWrtShell->SttPara();
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, /*bBasicCall=*/false);
    CPPUNIT_ASSERT_EQUAL(true, pWrtShell->HasReadonlySel());
    // Test deletion of whole field with single backspace
    // Previously it only removed right boundary of FORMTEXT, or failed removal at all
    const IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess();
    sal_Int32 nMarksBefore = pMarksAccess->getAllMarksCount();
    pWrtShell->EndPara();
    pWrtShell->DelLeft();
    sal_Int32 nMarksAfter = pMarksAccess->getAllMarksCount();
    CPPUNIT_ASSERT_EQUAL(nMarksBefore, nMarksAfter + 1);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf125151_protected)
{
    // Similar to testTdf105625 except this is in a protected section,
    // so read-only is already true when fieldmarks are considered.
    createSwDoc("tdf125151_protected.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Ensure correct initial setting
    std::shared_ptr<comphelper::ConfigurationChanges> batch(
        comphelper::ConfigurationChanges::create());
    officecfg::Office::Writer::Cursor::Option::IgnoreProtectedArea::set(false, batch);
    batch->commit();
    pWrtShell->Down(/*bSelect=*/false);
    // The cursor moved inside of the FieldMark textbox.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Readonly 1"false, pWrtShell->HasReadonlySel());
    // Move left to the start/definition of the textbox
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Readonly 2"true, pWrtShell->HasReadonlySel());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf125151_protectedB)
{
    // Similar to testTdf105625 except this is protected with the Protect_Form compat setting
    createSwDoc("tdf125151_protectedB.fodt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    // Ensure correct initial setting
    std::shared_ptr<comphelper::ConfigurationChanges> batch(
        comphelper::ConfigurationChanges::create());
    officecfg::Office::Writer::Cursor::Option::IgnoreProtectedArea::set(false, batch);
    batch->commit();
    // The cursor starts inside of the FieldMark textbox.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Readonly 1"false, pWrtShell->HasReadonlySel());
    // Move left to the start/definition of the textbox
    pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Readonly 2"true, pWrtShell->HasReadonlySel());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf106736)
{
    createSwDoc("tdf106736-grid.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    sal_Int32 nWidth
        = getXPath(pXmlDoc,
                   "(//SwParaPortion/SwLineLayout/child::*[@type='PortionType::TabLeft'])[1]",
                   "width")
              .toInt32();
    // In tdf106736, width of tab overflow so that it got
    // width value around 9200, expected value is around 103
    CPPUNIT_ASSERT_MESSAGE("Left Tab width is ~103", nWidth < 150);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testMsWordCompTrailingBlanks)
{
    // The option is true in settings.xml
    createSwDoc("MsWordCompTrailingBlanksTrue.odt");
    SwDoc* pDoc = getSwDoc();
    CPPUNIT_ASSERT_EQUAL(true, pDoc->getIDocumentSettingAccess().get(
                                   DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS));
    calcLayout();
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    // Check that trailing spaces spans are put into Hole portion if option is enabled

    assertXPath(pXmlDoc, "/root/page/body/txt", 3);

    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*", 4);
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]""portion",
                u"TEST ");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]""portion",
                u"   ");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]""portion",
                u"   T");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]""type",
                u"PortionType::Hole");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]""portion",
                u"         "); // All the trailing blanks

    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*", 4);
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]""portion",
                u"TEST ");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]""portion",
                u"   ");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]""portion",
                u"   T");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]""type",
                u"PortionType::Hole");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]""portion",
                u"         "); // All the trailing blanks

    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*", 4);
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]""portion",
                u"TEST ");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]""portion",
                u"   ");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]""portion",
                u"   T");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]""type",
                u"PortionType::Hole");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]""portion",
                u"         "); // All the trailing blanks

    // The option is false in settings.xml
    createSwDoc("MsWordCompTrailingBlanksFalse.odt");
    pDoc = getSwDoc();
    CPPUNIT_ASSERT_EQUAL(false, pDoc->getIDocumentSettingAccess().get(
                                    DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS));
    calcLayout();
    pXmlDoc = parseLayoutDump();
    // Check that trailing spaces spans are put into Text portions if option is disabled

    assertXPath(pXmlDoc, "/root/page/body/txt", 3);

    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*", 5);
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]""portion",
                u"TEST ");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]""portion",
                u"   ");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]""portion",
                u"   T   "); // first colored trailing blank span here
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]""portion",
                u"   "); // second colored trailing blank span here
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[5]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[5]""portion",
                u"   "); // third colored trailing blank span here

    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*", 5);
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]""portion",
                u"TEST ");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]""portion",
                u"   ");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]""portion",
                u"   T   "); // first colored trailing blank span here
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]""portion",
                u"   "); // second colored trailing blank span here
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[5]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[5]""portion",
                u"   "); // third colored trailing blank span here

    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*", 5);
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]""portion",
                u"TEST ");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]""portion",
                u"   ");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]""portion",
                u"   T   "); // first colored trailing blank span here
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]""portion",
                u"   "); // second colored trailing blank span here
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[5]""type",
                u"PortionType::Text");
    assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[5]""portion",
                u"   "); // third colored trailing blank span here

    // MsWordCompTrailingBlanks option should be false by default in new documents
    createSwDoc();
    pDoc = getSwDoc();
    CPPUNIT_ASSERT_EQUAL(false, pDoc->getIDocumentSettingAccess().get(
                                    DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS));

    // The option should be true if a .docx, .doc or .rtf document is opened
    createSwDoc("MsWordCompTrailingBlanks.docx");
    pDoc = getSwDoc();
    CPPUNIT_ASSERT_EQUAL(true, pDoc->getIDocumentSettingAccess().get(
                                   DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testCreateDocxAnnotation)
{
    createSwDoc();

    // insert an annotation with a text
    static constexpr OUString aSomeText(u"some text"_ustr);
    uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence({
        { "Text", uno::Any(aSomeText) },
        { "Author", uno::Any(u"me"_ustr) },
    });
    dispatchCommand(mxComponent, u".uno:InsertAnnotation"_ustr, aPropertyValues);

    // Save it as DOCX & load it again
    saveAndReload(u"Office Open XML Text"_ustr);

    // get the annotation
    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<beans::XPropertySet> xField(xFields->nextElement(), uno::UNO_QUERY);

    // this was empty instead of "some text"
    CPPUNIT_ASSERT_EQUAL(aSomeText, xField->getPropertyValue(u"Content"_ustr).get<OUString>());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf107976)
{
    // Create a document and create two transferables.
    createSwDoc();
    SwWrtShell& rShell = *getSwDocShell()->GetWrtShell();
    rtl::Reference<SwTransferable> pTransferable(new SwTransferable(rShell));
    rtl::Reference<SwTransferable> pTransferable2(new SwTransferable(rShell));
    // Now close the document.
    mxComponent->dispose();
    mxComponent.clear();
    // This failed: the first shell had a pointer to the deleted shell.
    CPPUNIT_ASSERT(!pTransferable->GetShell());
    CPPUNIT_ASSERT(!pTransferable2->GetShell());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf58604)
{
#ifdef _WIN32
    // Allow linebreak character follows hanging punctuation immediately instead of
    // breaking at the start of the next line.
    createSwDoc("tdf58604.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    CPPUNIT_ASSERT_EQUAL(
        OUString("PortionType::Break"),
        getXPath(pXmlDoc, "(/root/page/body/txt/SwParaPortion/SwLineLayout[1]/child::*)[last()]",
                 "type"));
#endif
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf112025)
{
    mergeDocs("fdo112025.odt""fdo112025-insert.docx");

    CPPUNIT_ASSERT_EQUAL(3, getParagraphs());

    uno::Reference<beans::XPropertySet> xStyle(
        getStyles(u"PageStyles"_ustr)->getByName(u"Standard"_ustr), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xStyle, u"IsLandscape"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf72942)
{
    mergeDocs("fdo72942.docx""fdo72942-insert.docx");
    CPPUNIT_ASSERT_EQUAL(7, getParagraphs());

    // check styles of paragraphs added from [fdo72942.docx]
    const uno::Reference<text::XTextRange> xRun1 = getRun(getParagraph(1), 1);
    CPPUNIT_ASSERT_EQUAL(u"Default English (Liberation serif) text with "_ustr, xRun1->getString());
    CPPUNIT_ASSERT_EQUAL(u"Liberation Serif"_ustr,
                         getProperty<OUString>(xRun1, u"CharFontName"_ustr));

    const uno::Reference<text::XTextRange> xRun2 = getRun(getParagraph(2), 1);
    CPPUNIT_ASSERT_EQUAL(u"Header 1 English text (Liberation sans) with "_ustr, xRun2->getString());
    CPPUNIT_ASSERT_EQUAL(u"Liberation Sans"_ustr,
                         getProperty<OUString>(xRun2, u"CharFontName"_ustr));

    // check styles of paragraphs added from [fdo72942-insert.docx]
    const uno::Reference<text::XTextRange> xRun3 = getRun(getParagraph(4), 1);
    CPPUNIT_ASSERT_EQUAL(u"Default German text (Calibri) with "_ustr, xRun3->getString());
    CPPUNIT_ASSERT_EQUAL(u"Liberation Serif"_ustr,
                         getProperty<OUString>(xRun3, u"CharFontName"_ustr));

    const uno::Reference<text::XTextRange> xRun4 = getRun(getParagraph(5), 1);
    CPPUNIT_ASSERT_EQUAL(u"Header 1 German text (Calibri Light) with "_ustr, xRun4->getString());
    CPPUNIT_ASSERT_EQUAL(u"Liberation Sans"_ustr,
                         getProperty<OUString>(xRun4, u"CharFontName"_ustr));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf114306)
{
    createSwDoc("fdo114306.odt");
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();

    // There are 2 long paragraphs in cell A1.
    // A part of paragraph 2 should flow over to the second page but
    // *not* the whole paragraph. There should be 2 paragraphs on
    // page 1 and 1 paragraph on page 2.
    assertXPath(pXmlDoc, "/root/page[1]/body/tab[1]/row[1]/cell[1]/txt", 2);
    assertXPath(pXmlDoc, "/root/page[2]/body/tab[1]/row[1]/cell[1]/txt", 1);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf114306_2)
{
    // tdf#114306 fix unexpected page break in row-spanned table
    // load regression document without writer crash
    createSwDoc("fdo114306_2.odt");

    // correct number of pages
    CPPUNIT_ASSERT_EQUAL(4, getPages());
}

// During insert of the document with list inside into the main document inside the list
// we should merge both lists into one, when they have the same list properties
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf113877)
{
    mergeDocs("tdf113877_insert_numbered_list.odt""tdf113877_insert_numbered_list.odt");

    const OUString listId1 = getProperty<OUString>(getParagraph(1), u"ListId"_ustr);
    const OUString listId4 = getProperty<OUString>(getParagraph(4), u"ListId"_ustr);
    const OUString listId5 = getProperty<OUString>(getParagraph(5), u"ListId"_ustr);
    const OUString listId6 = getProperty<OUString>(getParagraph(6), u"ListId"_ustr);
    const OUString listId7 = getProperty<OUString>(getParagraph(7), u"ListId"_ustr);

    // the initial list with 4 list items
    CPPUNIT_ASSERT_EQUAL(listId1, listId4);

    // the last of the first list, and the first of the inserted list
    CPPUNIT_ASSERT_EQUAL(listId4, listId5);
    CPPUNIT_ASSERT_EQUAL(listId5, listId6);
    CPPUNIT_ASSERT_EQUAL(listId6, listId7);
}

// The same test as testTdf113877() but merging of two list should not be performed.
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf113877NoMerge)
{
    mergeDocs("tdf113877_insert_numbered_list.odt""tdf113877_insert_numbered_list_abcd.odt");

    const OUString listId1 = getProperty<OUString>(getParagraph(1), u"ListId"_ustr);
    const OUString listId4 = getProperty<OUString>(getParagraph(4), u"ListId"_ustr);
    const OUString listId5 = getProperty<OUString>(getParagraph(5), u"ListId"_ustr);
    const OUString listId6 = getProperty<OUString>(getParagraph(6), u"ListId"_ustr);
    const OUString listId7 = getProperty<OUString>(getParagraph(7), u"ListId"_ustr);

    // the initial list with 4 list items
    CPPUNIT_ASSERT_EQUAL(listId1, listId4);

    // the last of the first list, and the first of the inserted list
    CPPUNIT_ASSERT(listId4 != listId5);
    CPPUNIT_ASSERT_EQUAL(listId5, listId6);
    CPPUNIT_ASSERT(listId6 != listId7);
}

// Related test to testTdf113877(): Inserting into empty document a new document with list.
// Insert position has NO its own paragraph style ("Standard" will be used).
//
// Resulting document should be the same for following tests:
// - testTdf113877_default_style()
// - testTdf113877_Standard_style()
//
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf113877_default_style)
{
    mergeDocs(nullptr, "tdf113877_insert_numbered_list_abcd.odt");

    const OUString listId1 = getProperty<OUString>(getParagraph(1), u"ListId"_ustr);
    const OUString listId2 = getProperty<OUString>(getParagraph(2), u"ListId"_ustr);
    const OUString listId3 = getProperty<OUString>(getParagraph(3), u"ListId"_ustr);

    CPPUNIT_ASSERT_EQUAL(listId1, listId2);
    CPPUNIT_ASSERT_EQUAL(listId1, listId3);
}

// Related test to testTdf113877(): Inserting into empty document a new document with list.
// Insert position has its own paragraph style derived from "Standard", but this style is the same as "Standard".
//
// Resulting document should be the same for following tests:
// - testTdf113877_default_style()
// - testTdf113877_Standard_style()
//
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf113877_Standard_style)
{
    mergeDocs("tdf113877_blank_ownStandard.odt""tdf113877_insert_numbered_list_abcd.odt");

    const OUString listId1 = getProperty<OUString>(getParagraph(1), u"ListId"_ustr);
    const OUString listId2 = getProperty<OUString>(getParagraph(2), u"ListId"_ustr);
    const OUString listId3 = getProperty<OUString>(getParagraph(3), u"ListId"_ustr);

    CPPUNIT_ASSERT_EQUAL(listId1, listId2);
    CPPUNIT_ASSERT_EQUAL(listId1, listId3);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf113877_blank_bold_on)
{
    mergeDocs("tdf113877_blank_bold_on.odt""tdf113877_insert_numbered_list_abcd.odt");

    const OUString listId1 = getProperty<OUString>(getParagraph(1), u"ListId"_ustr);
    const OUString listId2 = getProperty<OUString>(getParagraph(2), u"ListId"_ustr);
    const OUString listId3 = getProperty<OUString>(getParagraph(3), u"ListId"_ustr);

    CPPUNIT_ASSERT_EQUAL(listId1, listId2);
    CPPUNIT_ASSERT_EQUAL(listId1, listId3);
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf107975)
{
    // This test also covers tdf#117185 tdf#110442

    createSwDoc("tdf107975.odt");

    uno::Reference<text::XTextGraphicObjectsSupplier> xTextGraphicObjectsSupplier(mxComponent,
                                                                                  uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xIndexAccess(
        xTextGraphicObjectsSupplier->getGraphicObjects(), uno::UNO_QUERY);

    uno::Reference<drawing::XShape> xShape(xIndexAccess->getByIndex(0), uno::UNO_QUERY);

    CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER,
                         getProperty<text::TextContentAnchorType>(xShape, u"AnchorType"_ustr));

    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());

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

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

    //Position the mouse cursor (caret) after "ABC" below the blue image
    dispatchCommand(mxComponent, u".uno:GoRight"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});

    // without the fix, it crashes
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexAccess->getCount());
    CPPUNIT_ASSERT_EQUAL(u"ABC"_ustr, getParagraph(1)->getString());
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());
    dispatchCommand(mxComponent, u".uno:Redo"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexAccess->getCount());
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());
    dispatchCommand(mxComponent, u".uno:Redo"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexAccess->getCount());
    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());

    //try again with anchor at start of doc which is another special case
    xShape.set(xIndexAccess->getByIndex(0), uno::UNO_QUERY);
    uno::Reference<text::XTextContent> xShapeContent(xShape, uno::UNO_QUERY);
    uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
    uno::Reference<text::XTextRange> const xStart = xTextDocument->getText()->getStart();
    xShapeContent->attach(xStart);

    CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER,
                         getProperty<text::TextContentAnchorType>(xShape, u"AnchorType"_ustr));

    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());

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

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

    //Position the mouse cursor (caret) after "ABC" below the blue image
    dispatchCommand(mxComponent, u".uno:GoRight"_ustr, {});
    dispatchCommand(mxComponent, u".uno:Paste"_ustr, {});

// Fails quite a lot on multiple Jenkins slaves, but entirely reliably,
// with:
// sw/qa/extras/uiwriter/uiwriter4.cxx(2407) : error : Assertion
// - Expected: 2
// - Actual  : 1
// i.e. the xIndexAccess->getCount() line.
#if 0
    // without the fix, it crashes
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexAccess->getCount());
    CPPUNIT_ASSERT_EQUAL(OUString("ABC"), getParagraph(1)->getString());
    dispatchCommand(mxComponent, ".uno:Undo", {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());
    dispatchCommand(mxComponent, ".uno:Redo", {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexAccess->getCount());
    dispatchCommand(mxComponent, ".uno:Undo", {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());
    dispatchCommand(mxComponent, ".uno:Redo", {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xIndexAccess->getCount());
    dispatchCommand(mxComponent, ".uno:Undo", {});
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xIndexAccess->getCount());
#endif
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf113877_blank_bold_off)
{
    mergeDocs("tdf113877_blank_bold_off.odt""tdf113877_insert_numbered_list_abcd.odt");

    const OUString listId1 = getProperty<OUString>(getParagraph(1), u"ListId"_ustr);
    const OUString listId2 = getProperty<OUString>(getParagraph(2), u"ListId"_ustr);
    const OUString listId3 = getProperty<OUString>(getParagraph(3), u"ListId"_ustr);

    CPPUNIT_ASSERT_EQUAL(listId1, listId2);
    CPPUNIT_ASSERT_EQUAL(listId1, listId3);
}

// just care that this does crash/assert
CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testRhbz1810732) { mergeDocs(nullptr, "rhbz1810732.docx"); }

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf142157)
{
    mergeDocs(nullptr, "tdf142157.odt");

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

    // Without the fix in place, this test would have failed with
    // - Expected: 1
    // - Actual  : 0
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testTdf143320)
{
    createSwDoc("tdf143320.odt");
    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();

    CPPUNIT_ASSERT_EQUAL(1, getPages());
    CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("x"));

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

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

    // Create a new document
    createSwDoc();
    pWrtShell = getSwDocShell()->GetWrtShell();
    CPPUNIT_ASSERT(pWrtShell);

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

    CPPUNIT_ASSERT_EQUAL(1, getPages());
    CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("x"));

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

    CPPUNIT_ASSERT_EQUAL(1, getPages());
    CPPUNIT_ASSERT_EQUAL(u""_ustr, getParagraph(1)->getString());

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

    CPPUNIT_ASSERT_EQUAL(1, getPages());
    CPPUNIT_ASSERT(getParagraph(1)->getString().startsWith("x"));
}

CPPUNIT_PLUGIN_IMPLEMENT();

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

Messung V0.5 in Prozent
C=89 H=91 G=89

¤ Dauer der Verarbeitung: 0.32 Sekunden  (vorverarbeitet am  2026-04-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.