From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sw/qa/extras/uiwriter/uiwriter6.cxx | 2352 +++++++++++++++++++++++++++++++++++ 1 file changed, 2352 insertions(+) create mode 100644 sw/qa/extras/uiwriter/uiwriter6.cxx (limited to 'sw/qa/extras/uiwriter/uiwriter6.cxx') diff --git a/sw/qa/extras/uiwriter/uiwriter6.cxx b/sw/qa/extras/uiwriter/uiwriter6.cxx new file mode 100644 index 000000000..f12f8b57e --- /dev/null +++ b/sw/qa/extras/uiwriter/uiwriter6.cxx @@ -0,0 +1,2352 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/extras/uiwriter/data/"; + +sal_Int32 lcl_getAttributeIDFromHints(const SwpHints& hints) +{ + for (size_t i = 0; i < hints.Count(); ++i) + { + const SwTextAttr* hint = hints.Get(i); + if (hint->Which() == RES_TXTATR_AUTOFMT) + { + const SwFormatAutoFormat& rFmt = hint->GetAutoFormat(); + SfxItemIter aIter(*rFmt.GetStyleHandle()); + return aIter.GetCurItem()->Which(); + } + } + return -1; +} + +void emulateTyping(SwXTextDocument& rXTextDocument, const std::u16string_view& rStr) +{ + for (const char16_t c : rStr) + { + rXTextDocument.postKeyEvent(LOK_KEYEVENT_KEYINPUT, c, 0); + rXTextDocument.postKeyEvent(LOK_KEYEVENT_KEYUP, c, 0); + Scheduler::ProcessEventsToIdle(); + } +} +} //namespace + +class SwUiWriterTest6 : public SwModelTestBase, public HtmlTestTools +{ +}; + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116640) +{ + createSwDoc(); + + uno::Sequence aArgs( + comphelper::InitPropertySequence({ { "Columns", uno::Any(sal_Int32(2)) } })); + + dispatchCommand(mxComponent, ".uno:InsertSection", aArgs); + Scheduler::ProcessEventsToIdle(); + + uno::Reference xTextSectionsSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xSections(xTextSectionsSupplier->getTextSections(), + uno::UNO_QUERY); + uno::Reference xTextSection(xSections->getByIndex(0), uno::UNO_QUERY); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + + uno::Reference xTextColumns + = getProperty>(xTextSection, "TextColumns"); + CPPUNIT_ASSERT_EQUAL(sal_Int16(2), xTextColumns->getColumnCount()); + + dispatchCommand(mxComponent, ".uno:Undo", {}); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); + + dispatchCommand(mxComponent, ".uno:Redo", {}); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSections->getCount()); + + dispatchCommand(mxComponent, ".uno:Undo", {}); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xSections->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf108524) +{ + createSwDoc(DATA_DIRECTORY, "tdf108524.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // In total we expect two cells containing a section. + assertXPath(pXmlDoc, "/root/page/body/tab/row/cell/section", 2); + + assertXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell/section", 1); + // This was 0, section wasn't split, instead it was only on the first page + // and it was cut off. + assertXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell/section", 1); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testLinesInSectionInTable) +{ + // This is similar to testTdf108524(), but the page boundary now is not in + // the middle of a multi-line paragraph: the section only contains oneliner + // paragraphs instead. + createSwDoc(DATA_DIRECTORY, "lines-in-section-in-table.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // In total we expect two cells containing a section. + assertXPath(pXmlDoc, "/root/page/body/tab/row/cell/section", 2); + + assertXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell/section", 1); + // This was 0, section wasn't split, instead it was only on the first page + // and it was cut off. + assertXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell/section", 1); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testLinesMoveBackwardsInSectionInTable) +{ +#if HAVE_MORE_FONTS + // Assert that paragraph "4" is on page 1 and "5" is on page 2. + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "lines-in-section-in-table.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page", 2); + SwNodeOffset nPara4Node( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/section/txt[last()]", "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("4"), pDoc->GetNodes()[nPara4Node]->GetTextNode()->GetText()); + SwNodeOffset nPara5Node( + getXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell[1]/section/txt[1]", "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("5"), pDoc->GetNodes()[nPara5Node]->GetTextNode()->GetText()); + + // Remove paragraph "4". + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->GetNode().GetIndex() < nPara4Node) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + pWrtShell->Up(/*bSelect=*/true); + pWrtShell->DelLeft(); + + // Assert that paragraph "5" is now moved back to page 1 and is the last paragraph there. + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + SwNodeOffset nPage1LastNode( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/section/txt[last()]", "txtNodeIndex") + .toUInt32()); + // This was "3", paragraph "4" was deleted, but "5" was not moved backwards from page 2. + CPPUNIT_ASSERT_EQUAL(OUString("5"), pDoc->GetNodes()[nPage1LastNode]->GetTextNode()->GetText()); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTableInSection) +{ +#if HAVE_MORE_FONTS + // The document has a section, containing a table that spans over 2 pages. + createSwDoc(DATA_DIRECTORY, "table-in-sect.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // In total we expect 4 cells. + assertXPath(pXmlDoc, "/root/page/body/section/tab/row/cell", 4); + + // Assert that on both pages the section contains 2 cells. + assertXPath(pXmlDoc, "/root/page[1]/body/section/tab/row/cell", 2); + assertXPath(pXmlDoc, "/root/page[2]/body/section/tab/row/cell", 2); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTableInNestedSection) +{ +#if HAVE_MORE_FONTS + // The document has a nested section, containing a table that spans over 2 pages. + // This crashed the layout. + createSwDoc(DATA_DIRECTORY, "rhbz739252-3.odt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // Make sure the table is inside a section and spans over 2 pages. + assertXPath(pXmlDoc, "//page[1]//section/tab", 1); + assertXPath(pXmlDoc, "//page[2]//section/tab", 1); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112741) +{ +#if HAVE_MORE_FONTS + createSwDoc(DATA_DIRECTORY, "tdf112741.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // This was 5 pages. + assertXPath(pXmlDoc, "//page", 4); + assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell/section", 1); + assertXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row/cell/section", 1); + // This failed, 3rd page contained no sections. + assertXPath(pXmlDoc, "//page[3]/body/tab/row/cell/tab/row/cell/section", 1); + assertXPath(pXmlDoc, "//page[4]/body/tab/row/cell/tab/row/cell/section", 1); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112860) +{ +#if HAVE_MORE_FONTS + // The document has a split section inside a nested table, and also a table + // in the footer. + // This crashed the layout. + createSwDoc(DATA_DIRECTORY, "tdf112860.fodt"); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113287) +{ +#if HAVE_MORE_FONTS + createSwDoc(DATA_DIRECTORY, "tdf113287.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//page", 2); + sal_uInt32 nCellTop + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell[1]/infos/bounds", "top").toUInt32(); + sal_uInt32 nSectionTop + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell[1]/section/infos/bounds", "top") + .toUInt32(); + // Make sure section frame is inside the cell frame. + // Expected greater than 4593, was only 3714. + CPPUNIT_ASSERT_GREATER(nCellTop, nSectionTop); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113445) +{ +#if HAVE_MORE_FONTS + // Force multiple-page view. + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf113445.fodt"); + SwDocShell* pDocShell = pDoc->GetDocShell(); + SwView* pView = pDocShell->GetView(); + pView->SetViewLayout(/*nColumns=*/2, /*bBookMode=*/false); + calcLayout(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//page", 2); + sal_uInt32 nPage1Left = getXPath(pXmlDoc, "//page[1]/infos/bounds", "left").toUInt32(); + sal_uInt32 nPage2Left = getXPath(pXmlDoc, "//page[2]/infos/bounds", "left").toUInt32(); + // Make sure that page 2 is on the right hand side of page 1, not below it. + CPPUNIT_ASSERT_GREATER(nPage1Left, nPage2Left); + + // Insert a new paragraph at the start of the document. + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->StartOfSection(); + pWrtShell->SplitNode(); + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + + // Make sure that Table2:C5 and Table2:D5 has its section frame inside the cell frame. + sal_uInt32 nCell3Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[3]/infos/bounds", "top") + .toUInt32(); + sal_uInt32 nSection3Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[3]/section/infos/bounds", + "top") + .toUInt32(); + CPPUNIT_ASSERT_GREATER(nCell3Top, nSection3Top); + sal_uInt32 nCell4Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[4]/infos/bounds", "top") + .toUInt32(); + sal_uInt32 nSection4Top + = getXPath(pXmlDoc, "//page[2]/body/tab/row/cell/tab/row[4]/cell[4]/section/infos/bounds", + "top") + .toUInt32(); + CPPUNIT_ASSERT_GREATER(nCell4Top, nSection4Top); + // Also check if the two cells in the same row have the same top position. + // This was 4818, expected only 1672. + CPPUNIT_ASSERT_EQUAL(nCell3Top, nCell4Top); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113686) +{ +#if HAVE_MORE_FONTS + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf113686.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page", 2); + SwNodeOffset nPage1LastNode( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/tab/row/cell[1]/txt[last()]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("Table2:A1-P10"), + pDoc->GetNodes()[nPage1LastNode]->GetTextNode()->GetText()); + SwNodeOffset nPage2FirstNode( + getXPath(pXmlDoc, "/root/page[2]/body/tab/row/cell[1]/section/txt[1]", "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("Table1:A1"), + pDoc->GetNodes()[nPage2FirstNode]->GetTextNode()->GetText()); + + // Remove page 2. + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->Start()->nNode.GetIndex() < nPage1LastNode) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + for (int i = 0; i < 3; ++i) + pWrtShell->Up(/*bSelect=*/true); + pWrtShell->DelLeft(); + + // Assert that the second page is removed. + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + // This was still 2, content from 2nd page was not moved. + assertXPath(pXmlDoc, "/root/page", 1); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTableInSectionInTable) +{ +#if HAVE_MORE_FONTS + // The document has a table, containing a section, containing a nested + // table. + // This crashed the layout. + createSwDoc(DATA_DIRECTORY, "i95698.odt"); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable) +{ +#if HAVE_MORE_FONTS + // The document has a nested table, containing a multi-line section at a + // page boundary. + // This crashed the layout later in SwFrame::IsFootnoteAllowed(). + createSwDoc(DATA_DIRECTORY, "tdf112109.fodt"); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable2) +{ +#if HAVE_MORE_FONTS + createSwDoc(DATA_DIRECTORY, "split-section-in-nested-table.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + sal_uInt32 nSection1 + = getXPath(pXmlDoc, "//page[1]//body/tab/row/cell/tab/row/cell/section", "id").toUInt32(); + sal_uInt32 nSection1Follow + = getXPath(pXmlDoc, "//page[1]//body/tab/row/cell/tab/row/cell/section", "follow") + .toUInt32(); + // This failed, the section wasn't split inside a nested table. + sal_uInt32 nSection2 + = getXPath(pXmlDoc, "//page[2]//body/tab/row/cell/tab/row/cell/section", "id").toUInt32(); + sal_uInt32 nSection2Precede + = getXPath(pXmlDoc, "//page[2]//body/tab/row/cell/tab/row/cell/section", "precede") + .toUInt32(); + + // Make sure that the first's follow and the second's precede is correct. + CPPUNIT_ASSERT_EQUAL(nSection2, nSection1Follow); + CPPUNIT_ASSERT_EQUAL(nSection1, nSection2Precede); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable3) +{ +#if HAVE_MORE_FONTS + createSwDoc(DATA_DIRECTORY, "tdf113153.fodt"); + + uno::Reference xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + uno::Reference xTable(xTables->getByIndex(1), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("Table16"), xTable->getName()); + + uno::Reference xRowSupplier(xTable, uno::UNO_QUERY); + uno::Reference xRows = xRowSupplier->getRows(); + uno::Reference xRow(xRows->getByIndex(1), uno::UNO_QUERY); + xRow->setPropertyValue("IsSplitAllowed", uno::Any(true)); + // This never returned. + calcLayout(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + sal_uInt32 nTable1 = getXPath(pXmlDoc, "//page[1]//body/tab", "id").toUInt32(); + sal_uInt32 nTable1Follow = getXPath(pXmlDoc, "//page[1]//body/tab", "follow").toUInt32(); + sal_uInt32 nTable2 = getXPath(pXmlDoc, "//page[2]//body/tab", "id").toUInt32(); + sal_uInt32 nTable2Precede = getXPath(pXmlDoc, "//page[2]//body/tab", "precede").toUInt32(); + sal_uInt32 nTable2Follow = getXPath(pXmlDoc, "//page[2]//body/tab", "follow").toUInt32(); + sal_uInt32 nTable3 = getXPath(pXmlDoc, "//page[3]//body/tab", "id").toUInt32(); + sal_uInt32 nTable3Precede = getXPath(pXmlDoc, "//page[3]//body/tab", "precede").toUInt32(); + + // Make sure the outer table frames are linked together properly. + CPPUNIT_ASSERT_EQUAL(nTable2, nTable1Follow); + CPPUNIT_ASSERT_EQUAL(nTable1, nTable2Precede); + CPPUNIT_ASSERT_EQUAL(nTable3, nTable2Follow); + CPPUNIT_ASSERT_EQUAL(nTable2, nTable3Precede); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSectionInTableInTable4) +{ +#if HAVE_MORE_FONTS + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf113520.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page", 3); + SwNodeOffset nPage1LastNode( + getXPath(pXmlDoc, "/root/page[1]/body/tab/row/cell[1]/tab/row/cell[1]/section/txt[last()]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("Section1:P10"), + pDoc->GetNodes()[nPage1LastNode]->GetTextNode()->GetText()); + SwNodeOffset nPage3FirstNode( + getXPath(pXmlDoc, "/root/page[3]/body/tab/row/cell[1]/tab/row/cell[1]/section/txt[1]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("Section1:P23"), + pDoc->GetNodes()[nPage3FirstNode]->GetTextNode()->GetText()); + + // Remove page 2. + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->Start()->nNode.GetIndex() < nPage1LastNode) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + while (pWrtShell->GetCursor()->End()->nNode.GetIndex() < nPage3FirstNode) + pWrtShell->Down(/*bSelect=*/true); + pWrtShell->EndPara(/*bSelect=*/true); + pWrtShell->DelLeft(); + + // Assert that the page is removed. + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + // This was 3, page 2 was emptied, but it wasn't removed. + assertXPath(pXmlDoc, "/root/page", 2); + + // Make sure the outer table frames are linked together properly. + sal_uInt32 nTable1 = getXPath(pXmlDoc, "//page[1]//body/tab", "id").toUInt32(); + sal_uInt32 nTable1Follow = getXPath(pXmlDoc, "//page[1]//body/tab", "follow").toUInt32(); + sal_uInt32 nTable2 = getXPath(pXmlDoc, "//page[2]//body/tab", "id").toUInt32(); + sal_uInt32 nTable2Precede = getXPath(pXmlDoc, "//page[2]//body/tab", "precede").toUInt32(); + CPPUNIT_ASSERT_EQUAL(nTable2, nTable1Follow); + CPPUNIT_ASSERT_EQUAL(nTable1, nTable2Precede); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112160) +{ +#if HAVE_MORE_FONTS + // Assert that the A2 cell is on page 1. + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf112160.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + SwNodeOffset nA2CellNode(getXPath(pXmlDoc, + "/root/page[1]/body/tab/row[2]/cell[1]/section/txt[last()]", + "txtNodeIndex") + .toUInt32()); + CPPUNIT_ASSERT_EQUAL(OUString("Table1.A2"), + pDoc->GetNodes()[nA2CellNode]->GetTextNode()->GetText()); + + // Append a new paragraph to the end of the A2 cell. + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + while (pWrtShell->GetCursor()->GetNode().GetIndex() < nA2CellNode) + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->EndPara(); + pWrtShell->SplitNode(); + + // Assert that after A2 got extended, D2 stays on page 1. + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + sal_uInt32 nD2CellNode + = getXPath(pXmlDoc, "/root/page[1]/body/tab/row[2]/cell[last()]/section/txt[last()]", + "txtNodeIndex") + .toUInt32(); + // This was Table1.C2, Table1.D2 was moved to the next page, unexpected. + CPPUNIT_ASSERT_EQUAL(OUString("Table1.D2"), + pDoc->GetNodes()[SwNodeOffset(nD2CellNode)]->GetTextNode()->GetText()); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf114536) +{ + // This crashed in SwTextFormatter::MergeCharacterBorder() due to a + // use after free. + createSwDoc(DATA_DIRECTORY, "tdf114536.odt"); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testParagraphOfTextRange) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "paragraph-of-text-range.odt"); + + // Enter the table. + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->Down(/*bSelect=*/false); + CPPUNIT_ASSERT(pWrtShell->IsCursorInTable()); + // Enter the section. + pWrtShell->Down(/*bSelect=*/false); + CPPUNIT_ASSERT(pWrtShell->IsDirectlyInSection()); + + // Assert that we get the right paragraph object. + uno::Reference xModel(mxComponent, uno::UNO_QUERY); + uno::Reference xController(xModel->getCurrentController(), + uno::UNO_QUERY); + uno::Reference xViewCursor = xController->getViewCursor(); + // This failed as there were no TextParagraph property. + auto xParagraph + = getProperty>(xViewCursor->getStart(), "TextParagraph"); + CPPUNIT_ASSERT_EQUAL(OUString("In section"), xParagraph->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf99689TableOfContents) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf99689.odt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoNextTOXBase(); + const SwTOXBase* pTOXBase = pWrtShell->GetCurTOX(); + pWrtShell->UpdateTableOf(*pTOXBase); + SwCursorShell* pShell(pDoc->GetEditShell()); + SwTextNode* pTitleNode = pShell->GetCursor()->GetNode().GetTextNode(); + SwNodeIndex aIdx(*pTitleNode); + // skip the title + pDoc->GetNodes().GoNext(&aIdx); + + // skip the first header. No attributes there. + // next node should contain superscript + SwTextNode* pNext = static_cast(pDoc->GetNodes().GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + sal_uInt16 nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); + + // next node should contain subscript + pNext = static_cast(pDoc->GetNodes().GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf99689TableOfFigures) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf99689_figures.odt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoNextTOXBase(); + const SwTOXBase* pTOXBase = pWrtShell->GetCurTOX(); + pWrtShell->UpdateTableOf(*pTOXBase); + SwCursorShell* pShell(pDoc->GetEditShell()); + SwTextNode* pTitleNode = pShell->GetCursor()->GetNode().GetTextNode(); + SwNodeIndex aIdx(*pTitleNode); + + // skip the title + // next node should contain subscript + SwTextNode* pNext = static_cast(pDoc->GetNodes().GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + sal_uInt16 nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); + + // next node should contain superscript + pNext = static_cast(pDoc->GetNodes().GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf99689TableOfTables) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf99689_tables.odt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GotoNextTOXBase(); + const SwTOXBase* pTOXBase = pWrtShell->GetCurTOX(); + pWrtShell->UpdateTableOf(*pTOXBase); + SwCursorShell* pShell(pDoc->GetEditShell()); + SwTextNode* pTitleNode = pShell->GetCursor()->GetNode().GetTextNode(); + SwNodeIndex aIdx(*pTitleNode); + + // skip the title + // next node should contain superscript + SwTextNode* pNext = static_cast(pDoc->GetNodes().GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + sal_uInt16 nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); + + // next node should contain subscript + pNext = static_cast(pDoc->GetNodes().GoNext(&aIdx)); + CPPUNIT_ASSERT(pNext->HasHints()); + nAttrType = lcl_getAttributeIDFromHints(pNext->GetSwpHints()); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(RES_CHRATR_ESCAPEMENT), nAttrType); +} + +// tdf#112448: Fix: take correct line height +// +// When line metrics is not calculated we need to call CalcRealHeight() +// before usage of the Height() and GetRealHeight(). +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf112448) +{ + createSwDoc(DATA_DIRECTORY, "tdf112448.odt"); + + // check actual number of line breaks in the paragraph + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page/body/txt/LineBreak", 2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113790) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf113790.docx"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + // Create the clipboard document. + SwDoc aClipboard; + aClipboard.SetClipBoard(true); + + // Go to fourth line - to "ABCD" bulleted list item + pWrtShell->Down(/*bSelect=*/false, 4); + pWrtShell->SelPara(nullptr); + CPPUNIT_ASSERT_EQUAL(OUString("ABCD"), pWrtShell->GetSelText()); + pWrtShell->Copy(aClipboard); + + // Go down to next-to-last (empty) line above "Title3" + pWrtShell->Down(/*bSelect=*/false, 4); + pWrtShell->Paste(aClipboard); + + // Save it as DOCX & load it again + reload("Office Open XML Text", "tdf113790.docx"); + CPPUNIT_ASSERT(dynamic_cast(mxComponent.get())); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf108048) +{ + createSwDoc(); + + uno::Sequence aPropertyValues = comphelper::InitPropertySequence({ + { "Kind", uno::Any(sal_Int16(3)) }, + { "TemplateName", uno::Any(OUString("Default Page Style")) }, + { "PageNumber", uno::Any(sal_uInt16(6)) }, // Even number to avoid auto-inserted blank page + { "PageNumberFilled", uno::Any(true) }, + }); + dispatchCommand(mxComponent, ".uno:InsertBreak", aPropertyValues); + CPPUNIT_ASSERT_EQUAL(2, getParagraphs()); + CPPUNIT_ASSERT_EQUAL(2, getPages()); + + // The inserted page must have page number set to 6 + uno::Reference xPara = getParagraph(2); + sal_uInt16 nPageNumber = getProperty(xPara, "PageNumberOffset"); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(6), nPageNumber); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf113481) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf113481-IVS.odt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + // One backspace should completely remove the CJK ideograph variation sequence + pWrtShell->EndPara(); + // Before: U+8FBA U+E0102. After: empty + pWrtShell->DelLeft(); + const uno::Reference xPara1 = getParagraph(1); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), xPara1->getString().getLength()); + + // In case that weak script is treated as CJK script, remove one character. + pWrtShell->Down(false); + pWrtShell->EndPara(); + // Before: U+4E2D U+2205 U+FE00. After: U+4E2D U+2205 + if (pWrtShell->GetScriptType() == SvtScriptType::ASIAN) + { + pWrtShell->DelLeft(); + const uno::Reference xPara2 = getParagraph(2); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xPara2->getString().getLength()); + CPPUNIT_ASSERT_EQUAL(u'\x2205', xPara2->getString()[1]); + } + + // Characters of other scripts, remove one character. + pWrtShell->Down(false); + pWrtShell->EndPara(); + // Before: U+1820 U+180B. After: U+1820 + pWrtShell->DelLeft(); + const uno::Reference xPara3 = getParagraph(3); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xPara3->getString().getLength()); + CPPUNIT_ASSERT_EQUAL(u'\x1820', xPara3->getString()[0]); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf115013) +{ + const OUString sColumnName("Name with spaces, \"quotes\" and \\backslashes"); + + utl::TempFile aTempDir(nullptr, true); + aTempDir.EnableKillingFile(); + const OUString aWorkDir = aTempDir.GetURL(); + + //create new writer document + SwDoc* pDoc = createSwDoc(); + + { + // Load and register data source + const OUString aDataSourceURI(m_directories.getURLFromSrc(DATA_DIRECTORY) + + "datasource.ods"); + OUString sDataSource = SwDBManager::LoadAndRegisterDataSource(aDataSourceURI, &aWorkDir); + CPPUNIT_ASSERT(!sDataSource.isEmpty()); + + // Insert a new field type for the mailmerge field + SwDBData aDBData; + aDBData.sDataSource = sDataSource; + aDBData.sCommand = "Sheet1"; + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwDBFieldType* pFieldType = static_cast( + pWrtShell->InsertFieldType(SwDBFieldType(pDoc, sColumnName, aDBData))); + CPPUNIT_ASSERT(pFieldType); + + // Insert the field into document + SwDBField aField(pFieldType); + pWrtShell->InsertField2(aField); + } + // Save it as DOCX & load it again + reload("Office Open XML Text", "mm-field.docx"); + + auto pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + pDoc = pXTextDocument->GetDocShell()->GetDoc(); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwPaM* pCursor = pDoc->GetEditShell()->GetCursor(); + CPPUNIT_ASSERT(pCursor); + + // Get the field at the beginning of the document + SwDBField* pField = dynamic_cast(SwCursorShell::GetFieldAtCursor(pCursor, true)); + CPPUNIT_ASSERT(pField); + OUString sColumn = static_cast(pField->GetTyp())->GetColumnName(); + // The column name must come correct after round trip + CPPUNIT_ASSERT_EQUAL(sColumnName, sColumn); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf115065) +{ + // In the document, the tables have table style assigned + // Source table (first one) has two rows; + // destination (second one) has only one row + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf115065.odt"); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + pWrtShell->GotoTable("Table2"); + SwRect aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Destination point is the middle of the first cell of second table + Point ptTo(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + pWrtShell->GotoTable("Table1"); + aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Source point is the middle of the first cell of first table + Point ptFrom(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + pWrtShell->SelTableCol(); + // The copy operation (or closing document after that) segfaulted + pWrtShell->Copy(*pWrtShell, ptFrom, ptTo); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf84806_MovingMultipleTableRows) +{ + // Moving of multiple table rows. + // Source table (first one) has two rows; + // destination (second one) has only one row + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf115065.odt"); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + uno::Reference xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables->getCount()); + uno::Reference xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName("Table1")); + CPPUNIT_ASSERT(xTableNames->hasByName("Table2")); + uno::Reference xTable1(xTableNames->getByName("Table1"), uno::UNO_QUERY); + uno::Reference xTable2(xTableNames->getByName("Table2"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2->getRows()->getCount()); + + // without redlining + CPPUNIT_ASSERT_MESSAGE("redlining should be off", + !pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + + pWrtShell->GotoTable("Table2"); + SwRect aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Destination point is the middle of the first cell of second table + Point ptTo(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + // Move rows of the first table into the second table + pWrtShell->GotoTable("Table1"); + pWrtShell->SelTable(); + rtl::Reference xTransfer = new SwTransferable(*pWrtShell); + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + // This was 2 tables + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getRows()->getCount()); + + // Undo results 2 tables + rUndoManager.Undo(); + uno::Reference xTables2(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables2->getCount()); + uno::Reference xTable1b(xTableNames->getByName("Table1"), uno::UNO_QUERY); + uno::Reference xTable2b(xTableNames->getByName("Table2"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1b->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2b->getRows()->getCount()); + + // FIXME assert with Redo() +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf147181_TrackedMovingOfMultipleTableRows) +{ + // Tracked moving of multiple table rows. + // Source table (first one) has two rows; + // destination (second one) has only one row + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf115065.odt"); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + uno::Reference xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables->getCount()); + uno::Reference xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName("Table1")); + CPPUNIT_ASSERT(xTableNames->hasByName("Table2")); + uno::Reference xTable1(xTableNames->getByName("Table1"), uno::UNO_QUERY); + uno::Reference xTable2(xTableNames->getByName("Table2"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2->getRows()->getCount()); + + // FIXME: doesn't work with empty rows, yet + pWrtShell->Insert("x"); + pWrtShell->Down(false); + pWrtShell->Insert("x"); + + // enable redlining + dispatchCommand(mxComponent, ".uno:TrackChanges", {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // show changes + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + + pWrtShell->GotoTable("Table2"); + SwRect aRect = pWrtShell->GetCurrFrame()->getFrameArea(); + // Destination point is the middle of the first cell of second table + Point ptTo(aRect.Left() + aRect.Width() / 2, aRect.Top() + aRect.Height() / 2); + + // Move rows of the first table into the second table + pWrtShell->GotoTable("Table1"); + pWrtShell->SelTable(); + rtl::Reference xTransfer = new SwTransferable(*pWrtShell); + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + // still 2 tables, but the second one has got 3 rows + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getRows()->getCount()); + + // accept changes results 1 table (removing moved table) + dispatchCommand(mxComponent, ".uno:AcceptAllTrackedChanges", {}); + Scheduler::ProcessEventsToIdle(); + uno::Reference xTables2(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables2->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable2->getRows()->getCount()); + + // Undo results 2 tables + rUndoManager.Undo(); + rUndoManager.Undo(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables2->getCount()); + uno::Reference xTable1b(xTableNames->getByName("Table1"), uno::UNO_QUERY); + uno::Reference xTable2b(xTableNames->getByName("Table2"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1b->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2b->getRows()->getCount()); + + // reject changes results 2 table again, with the original row counts + dispatchCommand(mxComponent, ".uno:RejectAllTrackedChanges", {}); + uno::Reference xTables3(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTables3->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable1b->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTable2b->getRows()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf154599_MovingColumn) +{ + //create new writer document + SwDoc* pDoc = createSwDoc(); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + // Create a table with less columns, than row + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + (void)&pWrtShell->InsertTable(TableOpt, 4, 3); + + uno::Reference xTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTablesSupplier->getTextTables(), + uno::UNO_QUERY); + uno::Reference xTableNames = xTablesSupplier->getTextTables(); + CPPUNIT_ASSERT(xTableNames->hasByName("Table1")); + uno::Reference xTable1(xTableNames->getByName("Table1"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable1->getColumns()->getCount()); + + // without redlining + CPPUNIT_ASSERT_MESSAGE("redlining should be off", + !pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + // Move first column of the table before the third column by drag & drop + + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + SwFrame* pPage = pLayout->Lower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pTable = pBody->GetLower(); + SwFrame* pRow1 = pTable->GetLower(); + SwFrame* pCellA1 = pRow1->GetLower(); + SwFrame* pCellC1 = pCellA1->GetNext()->GetNext(); + const SwRect& rCellA1Rect = pCellA1->getFrameArea(); + const SwRect& rCellC1Rect = pCellC1->getFrameArea(); + Point ptTo(rCellC1Rect.Left() + rCellC1Rect.Width() / 2, + rCellC1Rect.Top() + rCellC1Rect.Height() / 2); + // select first table column by using the middle point of the top border of column A + Point ptColumn(rCellA1Rect.Left() + rCellA1Rect.Width() / 2, rCellA1Rect.Top() - 5); + pWrtShell->SelectTableRowCol(ptColumn); + + // This crashed here before the fix. + rtl::Reference xTransfer = new SwTransferable(*pWrtShell); + + xTransfer->PrivateDrop(*pWrtShell, ptTo, /*bMove=*/true, /*bXSelection=*/true); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), xTable1->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable1->getColumns()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf115132) +{ + SwDoc* pDoc = createSwDoc(); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + std::vector vTestTableNames; + + // Create an empty paragraph that will separate first table from the rest + pWrtShell->SplitNode(); + pWrtShell->StartOfSection(); + // Create a table at the start of document body + SwInsertTableOptions TableOpt(SwInsertTableFlags::DefaultBorder, 0); + const SwTable* pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + const SwTableFormat* pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName()); + pWrtShell->EndOfSection(); + // Create a table after a paragraph + pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName()); + // Create a table immediately after the previous + pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName()); + // Create a nested table in the middle of last row + pWrtShell->GotoTable(vTestTableNames.back()); + for (int i = 0; i < 4; ++i) + pWrtShell->GoNextCell(false); + pTable = &pWrtShell->InsertTable(TableOpt, 2, 3); + pFormat = pTable->GetFrameFormat(); + CPPUNIT_ASSERT(pFormat); + vTestTableNames.push_back(pFormat->GetName()); + + // Now check that in any cell in all tables we don't go out of a cell + // using Delete or Backspace. We test cases when a table is the first node; + // when we are in a first/middle/last cell in a row; when there's a paragraph + // before/after this cell; when there's another table before/after this cell; + // in nested table. + for (const auto& rTableName : vTestTableNames) + { + pWrtShell->GotoTable(rTableName); + do + { + const SwStartNode* pNd = pWrtShell->GetCursor()->GetNode().FindTableBoxStartNode(); + pWrtShell->DelRight(); + CPPUNIT_ASSERT_EQUAL(pNd, pWrtShell->GetCursor()->GetNode().FindTableBoxStartNode()); + pWrtShell->DelLeft(); + CPPUNIT_ASSERT_EQUAL(pNd, pWrtShell->GetCursor()->GetNode().FindTableBoxStartNode()); + } while (pWrtShell->GoNextCell(false)); + } +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testXDrawPagesSupplier) +{ + createSwDoc(); + uno::Reference xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); + CPPUNIT_ASSERT_MESSAGE("XDrawPagesSupplier interface is unavailable", xDrawPagesSupplier.is()); + uno::Reference xDrawPages = xDrawPagesSupplier->getDrawPages(); + CPPUNIT_ASSERT(xDrawPages.is()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("There must be only a single DrawPage in Writer documents", + sal_Int32(1), xDrawPages->getCount()); + uno::Any aDrawPage = xDrawPages->getByIndex(0); + uno::Reference xDrawPageFromXDrawPages(aDrawPage, uno::UNO_QUERY); + CPPUNIT_ASSERT(xDrawPageFromXDrawPages.is()); + + uno::Reference xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xDrawPage = xDrawPageSupplier->getDrawPage(); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "The DrawPage accessed using XDrawPages must be the same as using XDrawPageSupplier", + xDrawPage.get(), xDrawPageFromXDrawPages.get()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116403) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf116403-considerborders.odt"); + // Check that before ToX update, the tab stop position is the old one + uno::Reference xParagraph = getParagraph(2, "1\t1"); + auto aTabs = getProperty>(xParagraph, "ParaTabStops"); + CPPUNIT_ASSERT_EQUAL(static_cast(1), aTabs.getLength()); + CPPUNIT_ASSERT_EQUAL(static_cast(17000), aTabs[0].Position); + + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + const SwTOXBase* pTOX = pWrtShell->GetTOX(0); + CPPUNIT_ASSERT(pTOX); + pWrtShell->UpdateTableOf(*pTOX); + + xParagraph = getParagraph(2, "1\t1"); + aTabs = getProperty>(xParagraph, "ParaTabStops"); + CPPUNIT_ASSERT_EQUAL(static_cast(1), aTabs.getLength()); + // This was still 17000, refreshing ToX didn't take borders spacings and widths into account + CPPUNIT_ASSERT_EQUAL_MESSAGE("Page borders must be considered for right-aligned tabstop", + static_cast(17000 - 2 * 500 - 2 * 1), + aTabs[0].Position); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testHtmlCopyImages) +{ + // Load a document with an image. + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "image.odt"); + + // Trigger the copy part of HTML copy&paste. + WriterRef xWrt = new SwHTMLWriter(/*rBaseURL=*/OUString()); + CPPUNIT_ASSERT(xWrt.is()); + + xWrt->m_bWriteClipboardDoc = true; + xWrt->m_bWriteOnlyFirstTable = false; + xWrt->SetShowProgress(false); + { + SvFileStream aStream(maTempFile.GetURL(), StreamMode::WRITE | StreamMode::TRUNC); + SwWriter aWrt(aStream, *pDoc); + aWrt.Write(xWrt); + } + htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile); + CPPUNIT_ASSERT(pHtmlDoc); + + // This failed, image was lost during HTML copy. + OUString aImage = getXPath(pHtmlDoc, "/html/body/p/img", "src"); + // Also make sure that the image is not embedded (e.g. Word doesn't handle + // embedded images). + CPPUNIT_ASSERT(aImage.startsWith("file:///")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116789) +{ + createSwDoc(DATA_DIRECTORY, "tdf116789.fodt"); + uno::Reference xBookmarksSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xText1; + uno::Reference xText2; + { + uno::Reference xBookmark( + xBookmarksSupplier->getBookmarks()->getByName("Bookmark 1"), uno::UNO_QUERY); + xText1 = xBookmark->getAnchor()->getText(); + } + { + uno::Reference xBookmark( + xBookmarksSupplier->getBookmarks()->getByName("Bookmark 1"), uno::UNO_QUERY); + xText2 = xBookmark->getAnchor()->getText(); + } + // This failed, we got two different SwXCell for the same bookmark anchor text. + CPPUNIT_ASSERT_EQUAL(xText1, xText2); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf91801) +{ + // Tests calculation with several user field variables without prior user fields + createSwDoc(DATA_DIRECTORY, "tdf91801.fodt"); + uno::Reference xTable(getParagraphOrTable(1), uno::UNO_QUERY); + uno::Reference xCell(xTable->getCellByName("A1")); + CPPUNIT_ASSERT_EQUAL(555.0, xCell->getValue()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf51223) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + SwNodeOffset nIndex = pWrtShell->GetCursor()->GetNode().GetIndex(); + pWrtShell->Insert("i"); + pWrtShell->SplitNode(true); + CPPUNIT_ASSERT_EQUAL(OUString("I"), + static_cast(pDoc->GetNodes()[nIndex])->GetText()); + rUndoManager.Undo(); + CPPUNIT_ASSERT_EQUAL(OUString("i"), + static_cast(pDoc->GetNodes()[nIndex])->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testFontEmbedding) +{ +#if HAVE_MORE_FONTS && !defined(MACOSX) + createSwDoc(DATA_DIRECTORY, "testFontEmbedding.odt"); + + OString aContentBaseXpath("/office:document-content/office:font-face-decls"); + OString aSettingsBaseXpath("/office:document-settings/office:settings/config:config-item-set"); + + xmlDocUniquePtr pXmlDoc; + uno::Sequence aDescriptor; + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + + // Get document settings + uno::Reference xFactory(mxComponent, uno::UNO_QUERY_THROW); + uno::Reference xProps( + xFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY_THROW); + + // Check font embedding state + CPPUNIT_ASSERT_EQUAL(false, xProps->getPropertyValue("EmbedFonts").get()); + CPPUNIT_ASSERT_EQUAL(false, xProps->getPropertyValue("EmbedOnlyUsedFonts").get()); + // Font scripts should be enabled by default, however this has no effect unless "EmbedOnlyUsedFonts" is enabled + CPPUNIT_ASSERT_EQUAL(true, xProps->getPropertyValue("EmbedLatinScriptFonts").get()); + CPPUNIT_ASSERT_EQUAL(true, xProps->getPropertyValue("EmbedAsianScriptFonts").get()); + CPPUNIT_ASSERT_EQUAL(true, xProps->getPropertyValue("EmbedComplexScriptFonts").get()); + + // CASE 1 - no font embedding enabled + + // Save the document + uno::Reference xStorable(mxComponent, uno::UNO_QUERY); + xStorable->storeToURL(aTempFile.GetURL(), aDescriptor); + CPPUNIT_ASSERT(aTempFile.IsValid()); + + // Check setting - No font embedding should be enabled + pXmlDoc = parseExportInternal(aTempFile.GetURL(), "settings.xml"); + CPPUNIT_ASSERT(pXmlDoc); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedFonts']", "false"); + + // Check content - No font-face-src nodes should be present + pXmlDoc = parseExportInternal(aTempFile.GetURL(), "content.xml"); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face", 6); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans']"); + assertXPath( + pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans']/svg:font-face-src", 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans1']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Sans1']/svg:font-face-src", + 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Serif']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Serif']/svg:font-face-src", + 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Serif1']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Serif1']/svg:font-face-src", + 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Carlito']"); + assertXPath(pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Carlito']/svg:font-face-src", 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Caladea']"); + assertXPath(pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Caladea']/svg:font-face-src", 0); + + // CASE 2 - font embedding enabled, but embed used fonts disabled + + // Enable font embedding, disable embedding used font only + xProps->setPropertyValue("EmbedFonts", uno::Any(true)); + xProps->setPropertyValue("EmbedOnlyUsedFonts", uno::Any(false)); + + // Save the document again + xStorable->storeToURL(aTempFile.GetURL(), aDescriptor); + CPPUNIT_ASSERT(aTempFile.IsValid()); + + // Check setting - font embedding should be enabled + embed only used fonts and scripts + pXmlDoc = parseExportInternal(aTempFile.GetURL(), "settings.xml"); + CPPUNIT_ASSERT(pXmlDoc); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedFonts']", "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedOnlyUsedFonts']", + "false"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedLatinScriptFonts']", + "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedAsianScriptFonts']", + "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedComplexScriptFonts']", + "true"); + + // Check content - font-face-src should be present only for "Liberation Sans" fonts + + pXmlDoc = parseExportInternal(aTempFile.GetURL(), "content.xml"); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face", 6); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans']"); + assertXPath( + pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans']/svg:font-face-src", 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans1']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Sans1']/svg:font-face-src", + 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Serif']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Serif']/svg:font-face-src", + 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Serif1']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Serif1']/svg:font-face-src", + 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Carlito']"); + assertXPath(pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Carlito']/svg:font-face-src", 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Caladea']"); + assertXPath(pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Caladea']/svg:font-face-src", 1); + + // CASE 3 - font embedding enabled, embed only used fonts enabled + + // Enable font embedding and setting to embed used fonts only + xProps->setPropertyValue("EmbedFonts", uno::Any(true)); + xProps->setPropertyValue("EmbedOnlyUsedFonts", uno::Any(true)); + xProps->setPropertyValue("EmbedLatinScriptFonts", uno::Any(true)); + xProps->setPropertyValue("EmbedAsianScriptFonts", uno::Any(true)); + xProps->setPropertyValue("EmbedComplexScriptFonts", uno::Any(true)); + + // Save the document again + xStorable->storeToURL(aTempFile.GetURL(), aDescriptor); + CPPUNIT_ASSERT(aTempFile.IsValid()); + + // Check setting - font embedding should be enabled + embed only used fonts and scripts + pXmlDoc = parseExportInternal(aTempFile.GetURL(), "settings.xml"); + CPPUNIT_ASSERT(pXmlDoc); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedFonts']", "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedOnlyUsedFonts']", + "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedLatinScriptFonts']", + "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedAsianScriptFonts']", + "true"); + assertXPathContent( + pXmlDoc, aSettingsBaseXpath + "/config:config-item[@config:name='EmbedComplexScriptFonts']", + "true"); + + // Check content - font-face-src should be present only for "Liberation Sans" fonts + + pXmlDoc = parseExportInternal(aTempFile.GetURL(), "content.xml"); + CPPUNIT_ASSERT(pXmlDoc); + + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face", 6); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans']"); + assertXPath( + pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans']/svg:font-face-src", 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Sans1']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Sans1']/svg:font-face-src", + 0); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Serif']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Serif']/svg:font-face-src", + 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Liberation Serif1']"); + assertXPath(pXmlDoc, + aContentBaseXpath + + "/style:font-face[@style:name='Liberation Serif1']/svg:font-face-src", + 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Carlito']"); + assertXPath(pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Carlito']/svg:font-face-src", 1); + assertXPath(pXmlDoc, aContentBaseXpath + "/style:font-face[@style:name='Caladea']"); + assertXPath(pXmlDoc, + aContentBaseXpath + "/style:font-face[@style:name='Caladea']/svg:font-face-src", 0); +#endif +} + +// Unit test for fix inconsistent bookmark behavior around at-char/as-char anchored frames +// +// We have a placeholder character in the sw doc model for as-char anchored frames, +// so it's possible to have a bookmark before/after the frame or a non-collapsed bookmark +// which covers the frame. The same is not true for at-char anchored frames, +// where the anchor points to a doc model position, but there is no placeholder character. +// If a bookmark is created covering the start and end of the anchor of the frame, +// internally we create a collapsed bookmark which has the same position as the anchor of the frame. +// When this doc model is handled by SwXParagraph::createEnumeration(), +// first the frame and then the bookmark is appended to the text portion enumeration, +// so your bookmark around the frame is turned into a collapsed bookmark after the frame. +// (The same happens when we roundtrip an ODT document representing this doc model.) +// +// Fix the problem by inserting collapsed bookmarks with affected anchor positions +// (same position is the anchor for an at-char frame) into the enumeration in two stages: +// first the start of them before frames and then the end of them + other bookmarks. +// This way UNO API users get their non-collapsed bookmarks around at-char anchored frames, +// similar to as-char ones. +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testInconsistentBookmark) +{ + // create test document with text and bookmark + { + SwDoc* pDoc(createSwDoc(DATA_DIRECTORY, "testInconsistentBookmark.ott")); + IDocumentMarkAccess& rIDMA(*pDoc->getIDocumentMarkAccess()); + SwNodeIndex aIdx(pDoc->GetNodes().GetEndOfContent(), -1); + SwCursor aPaM(SwPosition(aIdx), nullptr); + aPaM.SetMark(); + aPaM.MovePara(GoCurrPara, fnParaStart); + aPaM.MovePara(GoCurrPara, fnParaEnd); + rIDMA.makeMark(aPaM, "Mark", IDocumentMarkAccess::MarkType::BOOKMARK, + ::sw::mark::InsertMode::New); + aPaM.Exchange(); + aPaM.DeleteMark(); + } + + // save document and verify the bookmark scoup + { + // save document + utl::TempFile aTempFile; + save("writer8", aTempFile); + + // load only content.xml + if (xmlDocUniquePtr pXmlDoc = parseExportInternal(aTempFile.GetURL(), "content.xml")) + { + const OString aPath("/office:document-content/office:body/office:text/text:p"); + + const int pos1 = getXPathPosition(pXmlDoc, aPath, "bookmark-start"); + const int pos2 = getXPathPosition(pXmlDoc, aPath, "control"); + const int pos3 = getXPathPosition(pXmlDoc, aPath, "bookmark-end"); + + CPPUNIT_ASSERT_GREATER(pos1, pos2); + CPPUNIT_ASSERT_GREATER(pos2, pos3); + } + } +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testSpellOnlineParameter) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + bool bSet = pOpt->IsOnlineSpell(); + + uno::Sequence params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(!bSet) } }); + dispatchCommand(mxComponent, ".uno:SpellOnline", params); + CPPUNIT_ASSERT_EQUAL(!bSet, pOpt->IsOnlineSpell()); + + // set the same state as now and we don't expect any change (no-toggle) + params = comphelper::InitPropertySequence({ { "Enable", uno::Any(!bSet) } }); + dispatchCommand(mxComponent, ".uno:SpellOnline", params); + CPPUNIT_ASSERT_EQUAL(!bSet, pOpt->IsOnlineSpell()); +} + +// missing spelling dictionary on Windows test platform? +#if !defined(_WIN32) +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf124603) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + uno::Sequence params + = comphelper::InitPropertySequence({ { "Enable", uno::Any(true) } }); + dispatchCommand(mxComponent, ".uno:SpellOnline", params); + + // Automatic Spell Checking is enabled + + CPPUNIT_ASSERT(pOpt->IsOnlineSpell()); + + // Type a correct word + + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + emulateTyping(*pXTextDocument, u"the "); + SwCursorShell* pShell(pDoc->GetEditShell()); + SwTextNode* pNode = pShell->GetCursor()->GetNode().GetTextNode(); + // no bad word + CPPUNIT_ASSERT_EQUAL(static_cast(nullptr), pNode->GetWrong()); + + // Create a bad word from the good: "the" -> "thex" + + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); + emulateTyping(*pXTextDocument, u"x"); + CPPUNIT_ASSERT(pNode->GetWrong()); + // tdf#92036 pending spell checking + bool bPending = !pNode->GetWrong() || !pNode->GetWrong()->Count(); + CPPUNIT_ASSERT(bPending); + + // Move right, leave the bad word + + pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); + CPPUNIT_ASSERT(pNode->GetWrong()); + // tdf#92036 still pending spell checking + bPending = !pNode->GetWrong() || !pNode->GetWrong()->Count(); + CPPUNIT_ASSERT(bPending); + + // Move down to trigger spell checking + + pWrtShell->Down(/*bSelect=*/false, 1); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(pNode->GetWrong()); + // This was 0 (pending spell checking) + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pNode->GetWrong()->Count()); +} +#endif + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testRedlineAutoCorrect) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "redline-autocorrect.fodt"); + + dispatchCommand(mxComponent, ".uno:GoToEndOfDoc", {}); + + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + // show tracked deletion with enabled change tracking + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + + emulateTyping(*pXTextDocument, u" "); + + // tdf#83419 This was "Ts " removing the deletion of "t" silently by sentence capitalization + OUString sReplaced("ts "); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // hide delete redlines + pWrtShell->SetRedlineFlags(nMode & ~RedlineFlags::ShowDelete); + + // repeat it with not visible redlining + dispatchCommand(mxComponent, ".uno:Undo", {}); + + emulateTyping(*pXTextDocument, u" "); + + sReplaced = "S "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // show delete redlines + pWrtShell->SetRedlineFlags(nMode); + + // This still keep the tracked deletion, capitalize only the visible text "s" + // with tracked deletion of the original character + sReplaced = "tsS "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // repeat it with visible redlining and word auto replacement of "tset" + dispatchCommand(mxComponent, ".uno:Undo", {}); + dispatchCommand(mxComponent, ".uno:Undo", {}); + + emulateTyping(*pXTextDocument, u"et "); + // This was "Ttest" removing the tracked deletion silently. + // Don't replace, if a redline starts or ends within the text. + sReplaced = "tset "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Otherwise replace it + emulateTyping(*pXTextDocument, u"tset "); + sReplaced = "tset test "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Including capitalization + emulateTyping(*pXTextDocument, u"end. word "); + sReplaced = "tset test end. Word "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tracked deletions after the correction point doesn't affect autocorrect + dispatchCommand(mxComponent, ".uno:GoToStartOfDoc", {}); + emulateTyping(*pXTextDocument, u"a "); + sReplaced = "A tset test end. Word "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testRedlineAutoCorrect2) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "redline-autocorrect2.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + dispatchCommand(mxComponent, ".uno:GoToEndOfDoc", {}); + + // show tracked deletion + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + emulateTyping(*pXTextDocument, u"... "); + + // This was "LoremLorem,…," (duplicating the deleted comma, but without deletion) + // Don't replace, if a redline starts or ends within the text. + OUString sReplaced = "Lorem,... "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // Continue it: + emulateTyping(*pXTextDocument, u"Lorem,... "); + sReplaced = u"Lorem,... Lorem,… "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testEmojiAutoCorrect) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "redline-autocorrect2.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + // Emoji replacement (:snowman: -> ☃) + + // without change tracking + CPPUNIT_ASSERT(!(pWrtShell->GetRedlineFlags() & RedlineFlags::On)); + emulateTyping(*pXTextDocument, u":snowman:"); + OUString sReplaced = u"☃Lorem,"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // with change tracking (showing redlines) + RedlineFlags const nMode(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + CPPUNIT_ASSERT(nMode & (RedlineFlags::ShowDelete | RedlineFlags::ShowInsert)); + pWrtShell->SetRedlineFlags(nMode); + CPPUNIT_ASSERT(nMode & RedlineFlags::On); + CPPUNIT_ASSERT(nMode & RedlineFlags::ShowDelete); + + emulateTyping(*pXTextDocument, u":snowman:"); + sReplaced = u"☃☃Lorem,"; + + // tdf#140674 This was ":snowman:" instead of autocorrect + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf108423) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // testing autocorrect of i' -> I' on start of first paragraph + emulateTyping(*pXTextDocument, u"i'"); + // The word "i" should be capitalized due to autocorrect, followed by a typographical apostrophe + OUString sIApostrophe(u"I\u2019"); + CPPUNIT_ASSERT_EQUAL(sIApostrophe, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u" i'"); + OUString sText(sIApostrophe + u" " + sIApostrophe); + CPPUNIT_ASSERT_EQUAL(sText, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf106164) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // testing autocorrect of we're -> We're on start of first paragraph + emulateTyping(*pXTextDocument, u"we're "); + CPPUNIT_ASSERT_EQUAL(OUString(u"We\u2019re "), getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf54409) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // testing autocorrect of "tset -> "test with typographical double quotation mark U+201C + emulateTyping(*pXTextDocument, u"\"test "); + OUString sReplaced(u"\u201Ctest "); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // testing autocorrect of test" -> test" with typographical double quotation mark U+201D + emulateTyping(*pXTextDocument, u"and tset\" "); + OUString sReplaced2(sReplaced + u"and test\u201D "); + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + // testing autocorrect of "tset" -> "test" with typographical double quotation mark U+201C and U+201D + emulateTyping(*pXTextDocument, u"\"tset\" "); + OUString sReplaced3(sReplaced2 + u"\u201Ctest\u201D "); + CPPUNIT_ASSERT_EQUAL(sReplaced3, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf38394) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf38394.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // testing autocorrect of French l'" -> l'« (instead of l'») + emulateTyping(*pXTextDocument, u"l'\""); + OUString sReplaced(u"l\u2019« "); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // tdf#132301 autocorrect of qu'« + emulateTyping(*pXTextDocument, u" qu'\""); + sReplaced += u" qu\u2019« "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf59666) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // testing missing autocorrect of single Greek letters + emulateTyping(*pXTextDocument, u"π "); + CPPUNIT_ASSERT_EQUAL(OUString(u"\u03C0 "), getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf133524) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf133524.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // 1. Testing autocorrect of >> and << + // Example: »word« + emulateTyping(*pXTextDocument, u">>"); + OUString sReplaced(u"»"); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // << + emulateTyping(*pXTextDocument, u"word<<"); + sReplaced += u"word«"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // 2. Testing autocorrect of " to >> and << inside „...” + // Example: „Sentence and »word«.” + // opening primary level quote + emulateTyping(*pXTextDocument, u" \""); + sReplaced += u" „"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // opening second level quote + emulateTyping(*pXTextDocument, u"Sentence and \""); + sReplaced += u"Sentence and »"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // closing second level quote + emulateTyping(*pXTextDocument, u"word\""); + sReplaced += u"word«"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // closing primary level quote + emulateTyping(*pXTextDocument, u".\""); + sReplaced += u".”"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // tdf#134940 avoid premature replacement of "--" in "-->" + emulateTyping(*pXTextDocument, u" -->"); + OUString sReplaced2(sReplaced + u" -->"); + // This was "–>" instead of "-->" + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u" "); + sReplaced += u" → "; + // This was "–>" instead of "→" + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#83037 + emulateTyping(*pXTextDocument, u"-> "); + sReplaced += u"→ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u"<- "); + sReplaced += u"← "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u"<-- "); + sReplaced += u"← "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u"<--> "); + sReplaced += u"↔ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf133524_Romanian) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf133524_ro.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // 1. Testing autocorrect of " to << and >> inside „...” + // Example: „Sentence and «word».” + // opening primary level quote + emulateTyping(*pXTextDocument, u"\""); + OUString sReplaced(u"„"); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // opening second level quote + emulateTyping(*pXTextDocument, u"Sentence and \""); + sReplaced += u"Sentence and «"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // closing second level quote + emulateTyping(*pXTextDocument, u"word\""); + sReplaced += u"word»"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // closing primary level quote + emulateTyping(*pXTextDocument, u".\""); + sReplaced += u".”"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // 2. Testing recognition of closing double quotation mark ” + emulateTyping(*pXTextDocument, u" \""); + sReplaced += u" „"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // 3. Testing recognition of alternative closing double quotation mark “ + emulateTyping(*pXTextDocument, u"Alternative.“ \""); + sReplaced += u"Alternative.“ „"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf128860) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf128860.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // Second level ending quote: ‚word' -> ,word‘ + emulateTyping(*pXTextDocument, u",word'"); + OUString sReplaced(u",word\u2019"); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // Us apostrophe without preceding starting quote: word' -> word’ + emulateTyping(*pXTextDocument, u" word'"); + sReplaced += u" word\u2019"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // But only after letters: word.' -> word.‘ + emulateTyping(*pXTextDocument, u" word.'"); + sReplaced += u" word.‘"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf123786) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf123786.fodt"); + + // On Windows, it will detect that system input language is en-US (despite "typing" e.g. Cyrillic characters), + // and will change Russian into English (US); in the latter language, + // the replacement from single quote will not become “, but ’. + SvtSysLocaleOptions aOptions; + aOptions.SetIgnoreLanguageChange(true); + aOptions.Commit(); + + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // Second level ending quote: „word' -> „word“ + emulateTyping(*pXTextDocument, u"„слово'"); + OUString sReplaced(u"„слово“"); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // Us apostrophe without preceding starting quote: word' -> word’ + emulateTyping(*pXTextDocument, u" слово'"); + sReplaced += u" слово’"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // But only after letters: word.' -> word.“ + emulateTyping(*pXTextDocument, u" слово.'"); + sReplaced += u" слово.“"; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf133589) +{ + // Hungarian test document with right-to-left paragraph setting + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf133589.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + // translitere words to Old Hungarian + emulateTyping(*pXTextDocument, u"székely "); + OUString sReplaced(u"𐳥𐳋𐳓𐳉𐳗 "); + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // disambiguate consonants: asszony -> asz|szony + emulateTyping(*pXTextDocument, u"asszony "); + sReplaced += u"𐳀𐳥𐳥𐳛𐳚 "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // disambiguate consonants: kosszarv -> kos|szarv + // (add explicit ZWSP temporarily for consonant disambiguation, because the requested + // hu_HU hyphenation dictionary isn't installed on all testing platform) + // pWrtShell->Insert(u"kosszarv"); + emulateTyping(*pXTextDocument, u"kos\u200Bszarv "); + sReplaced += u"𐳓𐳛𐳤𐳥𐳀𐳢𐳮 "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // transliterate numbers to Old Hungarian + emulateTyping(*pXTextDocument, u"2020 "); + sReplaced += u"𐳺𐳺𐳿𐳼𐳼 "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#147546 transliterate punctuation marks + + // question mark + emulateTyping(*pXTextDocument, u"Kérdőjel?"); + sReplaced += u"𐲓𐳋𐳢𐳇𐳟𐳒𐳉𐳖"; + OUString sReplaced2(sReplaced + "?"); + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u" "); + sReplaced += u"⸮ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // comma + emulateTyping(*pXTextDocument, u"Vessző,"); + sReplaced += u"𐲮𐳉𐳥𐳥𐳟"; + sReplaced2 = sReplaced + ","; + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u" "); + sReplaced += u"⹁ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // semicolon + emulateTyping(*pXTextDocument, u"pontosvessző;"); + sReplaced += u"𐳠𐳛𐳙𐳦𐳛𐳤𐳮𐳉𐳥𐳥𐳟"; + sReplaced2 = sReplaced + ";"; + CPPUNIT_ASSERT_EQUAL(sReplaced2, getParagraph(1)->getString()); + emulateTyping(*pXTextDocument, u" "); + sReplaced += u"⁏ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + // quotation marks + emulateTyping(*pXTextDocument, u"„idézőjel” "); + sReplaced += u"⹂𐳐𐳇𐳋𐳯𐳟𐳒𐳉𐳖‟ "; + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#148672 transliterate word with closing bracket + emulateTyping(*pXTextDocument, u"word] "); + sReplaced += u"𐳮𐳛𐳢𐳇] "; // This was "word]" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + // tdf#148672 transliterate words with parenthesis (libnumbertext 1.0.11) + emulateTyping(*pXTextDocument, u"(word) "); + sReplaced += u"(𐳮𐳛𐳢𐳇) "; // This was "(word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"(word "); + sReplaced += u"(𐳮𐳛𐳢𐳇 "; // This was "(word" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"word) "); + sReplaced += u"𐳮𐳛𐳢𐳇) "; // This was "word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"{word} "); + sReplaced += u"{𐳮𐳛𐳢𐳇} "; // This was "(word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"{word "); + sReplaced += u"{𐳮𐳛𐳢𐳇 "; // This was "(word" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"word} "); + sReplaced += u"𐳮𐳛𐳢𐳇} "; // This was "word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"[word] "); + sReplaced += u"[𐳮𐳛𐳢𐳇] "; // This was "(word)" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); + + emulateTyping(*pXTextDocument, u"[word "); + sReplaced += u"[𐳮𐳛𐳢𐳇 "; // This was "(word" (no transliteration) + CPPUNIT_ASSERT_EQUAL(sReplaced, getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testAutoCorr) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + //Normal AutoCorrect + emulateTyping(*pXTextDocument, u"tset "); + CPPUNIT_ASSERT_EQUAL(OUString("Test "), getParagraph(1)->getString()); + + //AutoCorrect with change style to bolt + emulateTyping(*pXTextDocument, u"Bolt "); + const uno::Reference xRun = getRun(getParagraph(1), 2); + CPPUNIT_ASSERT_EQUAL(OUString("Bolt"), xRun->getString()); + CPPUNIT_ASSERT_EQUAL(OUString("Arial"), getProperty(xRun, "CharFontName")); + + //AutoCorrect inserts Table with 2 rows and 3 columns + emulateTyping(*pXTextDocument, u"4xx "); + const uno::Reference xTable(getParagraphOrTable(2), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTable->getRows()->getCount()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), xTable->getColumns()->getCount()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf130274) +{ + SwDoc* const pDoc(createSwDoc()); + SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + CPPUNIT_ASSERT(!pWrtShell->GetLayout()->IsHideRedlines()); + CPPUNIT_ASSERT( + !IDocumentRedlineAccess::IsRedlineOn(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + // "tset" may be replaced by the AutoCorrect in the test profile + emulateTyping(*pXTextDocument, u"tset"); + // select from left to right + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 4, /*bBasicCall=*/false); + pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/true, 4, /*bBasicCall=*/false); + + pWrtShell->SetRedlineFlags(pWrtShell->GetRedlineFlags() | RedlineFlags::On); + // this would crash in AutoCorrect + emulateTyping(*pXTextDocument, u"."); + + CPPUNIT_ASSERT(!pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf83260) +{ + SwDoc* const pDoc(createSwDoc(DATA_DIRECTORY, "tdf83260-1.odt")); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + // enabled but not shown + CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines()); +#if 0 + CPPUNIT_ASSERT(IDocumentRedlineAccess::IsHideChanges( + pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); +#endif + CPPUNIT_ASSERT( + IDocumentRedlineAccess::IsRedlineOn(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + CPPUNIT_ASSERT(!pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()); + + // the document contains redlines that are combined with CompressRedlines() + // if that happens during AutoCorrect then indexes in Undo are off -> crash + emulateTyping(*pXTextDocument, u"tset "); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + auto const nActions(rUndoManager.GetUndoActionCount()); + for (auto i = nActions; 0 < i; --i) + { + rUndoManager.Undo(); + } + // check that every text node has a layout frame + for (SwNodeOffset i(0); i < pDoc->GetNodes().Count(); ++i) + { + if (SwTextNode const* const pNode = pDoc->GetNodes()[i]->GetTextNode()) + { + CPPUNIT_ASSERT(pNode->getLayoutFrame(nullptr, nullptr, nullptr)); + } + } + for (auto i = nActions; 0 < i; --i) + { + rUndoManager.Redo(); + } + for (SwNodeOffset i(0); i < pDoc->GetNodes().Count(); ++i) + { + if (SwTextNode const* const pNode = pDoc->GetNodes()[i]->GetTextNode()) + { + CPPUNIT_ASSERT(pNode->getLayoutFrame(nullptr, nullptr, nullptr)); + } + } + for (auto i = nActions; 0 < i; --i) + { + rUndoManager.Undo(); + } + for (SwNodeOffset i(0); i < pDoc->GetNodes().Count(); ++i) + { + if (SwTextNode const* const pNode = pDoc->GetNodes()[i]->GetTextNode()) + { + CPPUNIT_ASSERT(pNode->getLayoutFrame(nullptr, nullptr, nullptr)); + } + } +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf74363) +{ + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + //testing autocorrect of initial capitals on start of first paragraph + //Inserting one all-lowercase word into the first paragraph + emulateTyping(*pXTextDocument, u"testing "); + //The word should be capitalized due to autocorrect + CPPUNIT_ASSERT_EQUAL(OUString("Testing "), getParagraph(1)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf139922) +{ + createSwDoc(); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pTextDoc); + + pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + emulateTyping(*pTextDoc, u"this is a SEntence. this is a SEntence."); + + // Without the fix in place, this test would have failed with + // - Expected: This is a Sentence. This is a Sentence. + // - Actual : this is a Sentence. This is a Sentence. + CPPUNIT_ASSERT_EQUAL(OUString("This is a Sentence. This is a Sentence."), + getParagraph(2)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf143176) +{ + // Hungarian test document with right-to-left paragraph setting + createSwDoc(DATA_DIRECTORY, "tdf143176.fodt"); + + // transliterate the document to Old Hungarian (note: it only works + // with right-to-left text direction and Default Paragraph Style) + dispatchCommand(mxComponent, ".uno:AutoFormatApply", {}); + + // This was the original "Lorem ipsum..." + CPPUNIT_ASSERT_EQUAL(OUString(u"𐲖𐳛𐳢𐳉𐳘 𐳐𐳠𐳤𐳪𐳘 𐳇𐳛𐳖𐳛𐳢 " + u"𐳤𐳐𐳦 𐳀𐳘𐳉𐳦⹁"), + getParagraph(1)->getString()); + CPPUNIT_ASSERT_EQUAL(OUString(u"𐳄𐳛𐳙𐳤𐳉𐳄𐳦𐳉𐳦𐳪𐳢 " + u"𐳀𐳇𐳐𐳠𐳐𐳤𐳄𐳐𐳙𐳍 𐳉𐳖𐳐𐳦."), + getParagraph(2)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testInsertLongDateFormat) +{ + // only for Hungarian, yet + createSwDoc(DATA_DIRECTORY, "tdf133524.fodt"); + dispatchCommand(mxComponent, ".uno:InsertDateField", {}); + // Make sure that the document starts with a field now, and its expanded string value contains space + const uno::Reference xField = getRun(getParagraph(1), 1); + CPPUNIT_ASSERT_EQUAL(OUString("TextField"), getProperty(xField, "TextPortionType")); + // the date format was "YYYY-MM-DD", but now "YYYY. MMM DD." + CPPUNIT_ASSERT(xField->getString().indexOf(" ") > -1); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf129270) +{ + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf129270.odt"); + CPPUNIT_ASSERT(pDoc); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + + // Go to document end + pWrtShell->SttEndDoc(/*bStt=*/false); + + // Press enter + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // Numbering for previous outline should remain the same "2" + CPPUNIT_ASSERT_EQUAL(OUString("2"), getProperty(getParagraph(4), "ListLabelString")); + + // Numbering for newly created outline should be "2.1" + CPPUNIT_ASSERT_EQUAL(OUString("2.1"), + getProperty(getParagraph(5), "ListLabelString")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testInsertPdf) +{ + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + { + return; + } + + createSwDoc(); + + // insert the PDF into the document + uno::Sequence aArgs(comphelper::InitPropertySequence( + { { "FileName", + uno::Any(m_directories.getURLFromSrc(DATA_DIRECTORY) + "hello-world.pdf") } })); + dispatchCommand(mxComponent, ".uno:InsertGraphic", aArgs); + + // Save and load cycle + reload("writer8", "testInsertPdf.odt"); + + uno::Reference xShape = getShape(1); + // Assert that we have a replacement graphics + auto xReplacementGraphic + = getProperty>(xShape, "ReplacementGraphic"); + CPPUNIT_ASSERT(xReplacementGraphic.is()); + + auto xGraphic = getProperty>(xShape, "Graphic"); + CPPUNIT_ASSERT(xGraphic.is()); + // Assert that the graphic is a PDF + CPPUNIT_ASSERT_EQUAL(OUString("application/pdf"), getProperty(xGraphic, "MimeType")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf143760WrapContourToOff) +{ + // Actually, this is an ooxmlexport test. It is here because here is a ready environment + // to change a shape by dispatchCommand. + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf143760_ContourToWrapOff.docx"); + CPPUNIT_ASSERT(pDoc); + CPPUNIT_ASSERT_EQUAL(true, getProperty(getShape(1), "SurroundContour")); + + // Mark the object + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pDoc->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + CPPUNIT_ASSERT(pObject); + SdrView* pView = pWrtShell->GetDrawView(); + pView->MarkObj(pObject, pView->GetSdrPageView()); + + // Set "wrap off" + dispatchCommand(mxComponent, ".uno:WrapOff", {}); + CPPUNIT_ASSERT_EQUAL(false, getProperty(getShape(1), "SurroundContour")); + + // Without fix this had failed, because the shape was written to file with contour. + reload("Office Open XML Text", "tdf143760_ContourToWrapOff.docx"); + CPPUNIT_ASSERT_EQUAL(false, getProperty(getShape(1), "SurroundContour")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testHatchFill) +{ + createSwDoc(); + + // Add a rectangle shape to the document. + uno::Reference xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference xShape( + xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY); + xShape->setSize(awt::Size(10000, 10000)); + xShape->setPosition(awt::Point(1000, 1000)); + uno::Reference xShapeProps(xShape, uno::UNO_QUERY); + xShapeProps->setPropertyValue("FillStyle", uno::Any(drawing::FillStyle_HATCH)); + xShapeProps->setPropertyValue("FillHatchName", uno::Any(OUString("Black 0 Degrees"))); + xShapeProps->setPropertyValue("FillBackground", uno::Any(false)); + xShapeProps->setPropertyValue("FillTransparence", uno::Any(sal_Int32(30))); + uno::Reference xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xDrawPage = xDrawPageSupplier->getDrawPage(); + xDrawPage->add(xShape); + + // Save it as DOCX and load it again. + reload("Office Open XML Text", "hatchFill.docx"); + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + + // tdf#127989 Without fix this had failed, because the background of the hatch was not set as 'no background'. + CPPUNIT_ASSERT(!getProperty(getShape(1), "FillBackground")); + + // tdf#146822 Without fix this had failed, because the transparency value of the hatch was not exported. + CPPUNIT_ASSERT_EQUAL(sal_Int32(30), getProperty(getShape(1), "FillTransparence")); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testNestedGroupTextBoxCopyCrash) +{ + createSwDoc(DATA_DIRECTORY, "tdf149550.docx"); + + dispatchCommand(mxComponent, ".uno:SelectAll", {}); + Scheduler::ProcessEventsToIdle(); + dispatchCommand(mxComponent, ".uno:Copy", {}); + Scheduler::ProcessEventsToIdle(); + // This crashed here before the fix. + SwXTextDocument* pXTextDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXTextDocument); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_ESCAPE); + Scheduler::ProcessEventsToIdle(); + dispatchCommand(mxComponent, ".uno:Paste", {}); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_MESSAGE("Where is the doc, it crashed, isn't it?!", mxComponent); + + auto pLayout = parseLayoutDump(); + // There must be 2 textboxes! + assertXPath(pLayout, "/root/page/body/txt/anchored/fly[2]"); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testCrashOnExit) +{ + // Load the bugdoc with a table and a textbox shape inside. + CPPUNIT_ASSERT(createSwDoc(DATA_DIRECTORY, "tdf142715.odt")); + + // Get the textbox selected + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + auto xShape = getShape(1); + CPPUNIT_ASSERT(xShape); + uno::Reference xModel(mxComponent, uno::UNO_QUERY); + CPPUNIT_ASSERT(xModel); + uno::Reference xController = xModel->getCurrentController(); + CPPUNIT_ASSERT(xController); + uno::Reference xSelection(xController, uno::UNO_QUERY); + CPPUNIT_ASSERT(xSelection); + CPPUNIT_ASSERT(xSelection->select(uno::Any(xShape))); + CPPUNIT_ASSERT(xSelection->getSelection().hasValue()); + uno::Reference xProperties(xShape, uno::UNO_QUERY); + + // Check if the textbox is selected + CPPUNIT_ASSERT_EQUAL(true, xProperties->getPropertyValue("TextBox").get()); + + // Remove the textbox + dispatchCommand(mxComponent, ".uno:RemoveTextBox", {}); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(false, xProperties->getPropertyValue("TextBox").get()); + + // Readd the textbox (to run the textboxhelper::create() method) + dispatchCommand(mxComponent, ".uno:AddTextBox", {}); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL(true, xProperties->getPropertyValue("TextBox").get()); + + // save and reload + reload("writer8", "tdf142715_.odt"); + + // Before the fix this crashed here and could not reopen. + CPPUNIT_ASSERT_MESSAGE("Crash on exit, isn't it?", mxComponent); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testCaptionShape) +{ + createSwDoc(); + + // Add a caption shape to the document. + uno::Reference xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference xShape( + xFactory->createInstance("com.sun.star.drawing.CaptionShape"), uno::UNO_QUERY); + xShape->setSize(awt::Size(10000, 10000)); + xShape->setPosition(awt::Point(1000, 1000)); + uno::Reference xDrawPageSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xDrawPage = xDrawPageSupplier->getDrawPage(); + xDrawPage->add(xShape); + + // Save it as DOCX and load it again. + reload("Office Open XML Text", "captionshape.docx"); + + // Without fix in place, the shape was lost on export. + CPPUNIT_ASSERT_EQUAL(1, getShapes()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf148799) +{ + // load a document with table formulas with comma delimiter, + // but with a document language with default point delimiter + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf148799.docx"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + // check formula update + // put cursor in the first table row + pWrtShell->Down(/*bSelect=*/false, /*nCount=*/1); + uno::Reference xTextTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTextTablesSupplier->getTextTables(), + uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables->getCount()); + uno::Reference xTextTable(xTables->getByIndex(0), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), xTextTable->getRows()->getCount()); + // These were "** Expression is faulty **" + uno::Reference xCellA1(xTextTable->getCellByName("D3"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("2.3"), xCellA1->getString()); + uno::Reference xCellA3(xTextTable->getCellByName("D4"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("2345"), xCellA3->getString()); + uno::Reference xCellA4(xTextTable->getCellByName("D5"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("23684.5"), xCellA4->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf151993) +{ + // load a document with table formulas with comma delimiter + // (with a document language with default comma delimiter) + SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf151993.docx"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + // check formula update + + // put cursor in the first table row + pWrtShell->Down(/*bSelect=*/false, /*nCount=*/1); + + uno::Reference xTextTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xTables(xTextTablesSupplier->getTextTables(), + uno::UNO_QUERY); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables->getCount()); + + uno::Reference xTextTable(xTables->getByIndex(0), uno::UNO_QUERY); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xTextTable->getRows()->getCount()); + + // This was 0 + uno::Reference xCellA1(xTextTable->getCellByName("A2"), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("30"), xCellA1->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf151462) +{ + createSwDoc(DATA_DIRECTORY, "tdf151462.odt"); + // xmlDocUniquePtr pLayout = parseLayoutDump(); + dispatchCommand(mxComponent, ".uno:UpdateAllIndexes", {}); + Scheduler::ProcessEventsToIdle(); + xmlDocUniquePtr pLayout = parseLayoutDump(); + // tdf#151462 - without the fix in place, there would be just the first index entry + assertXPath(pLayout, + "/root/page[1]/body/txt[2]/anchored/fly/section/txt[1]/SwParaPortion/" + "SwLineLayout[1]/SwLinePortion[1]", + "portion", "sub one"); + assertXPath(pLayout, + "/root/page[1]/body/txt[2]/anchored/fly/section/txt[2]/SwParaPortion/" + "SwLineLayout[1]/SwLinePortion[1]", + "portion", "sub two"); + assertXPath(pLayout, + "/root/page[1]/body/txt[2]/anchored/fly/section/txt[3]/SwParaPortion/" + "SwLineLayout[1]/SwLinePortion[1]", + "portion", "sub three"); + // tdf#151462 - without the fix in place, there would be just the first index entry + assertXPath(pLayout, + "/root/page[1]/body/txt[6]/anchored/fly/section/txt[1]/SwParaPortion/" + "SwLineLayout[1]/SwLinePortion[1]", + "portion", "another sub one"); + assertXPath(pLayout, + "/root/page[1]/body/txt[6]/anchored/fly/section/txt[2]/SwParaPortion/" + "SwLineLayout[1]/SwLinePortion[1]", + "portion", "another sub two"); + assertXPath(pLayout, + "/root/page[1]/body/txt[6]/anchored/fly/section/txt[3]/SwParaPortion/" + "SwLineLayout[1]/SwLinePortion[1]", + "portion", "another sub three"); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3