/* -*- 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 #include #include #include #include #include #include #include #include #include using namespace com::sun::star; using namespace desktop; class DesktopLOKTest : public UnoApiTest { class Resetter { private: std::function m_Func; public: Resetter(std::function const& rFunc) : m_Func(rFunc) { } ~Resetter() { try { m_Func(); } catch (...) // has to be reliable { fprintf(stderr, "resetter failed with exception\n"); abort(); } } }; public: DesktopLOKTest() : UnoApiTest("/desktop/qa/data/"), m_nSelectionBeforeSearchResult(0), m_nSelectionAfterSearchResult(0), m_bModified(false), m_nTrackChanges(0) { } void readFileIntoByteVector(OUString const & sFilename, std::vector & rByteVector); virtual void setUp() override { comphelper::LibreOfficeKit::setActive(true); UnoApiTest::setUp(); mxDesktop.set(frame::Desktop::create(comphelper::getComponentContext(getMultiServiceFactory()))); SfxApplication::GetOrCreate(); }; virtual void tearDown() override { if (m_pDocument) m_pDocument->pClass->registerCallback(m_pDocument.get(), nullptr, nullptr); closeDoc(); UnoApiTest::tearDown(); comphelper::LibreOfficeKit::setActive(false); }; LibLODocument_Impl* loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType); LibLODocument_Impl* loadDoc(const char* pName, LibreOfficeKitDocumentType eType = LOK_DOCTYPE_TEXT); void closeDoc(); static void callback(int nType, const char* pPayload, void* pData); void callbackImpl(int nType, const char* pPayload); void testGetStyles(); void testGetFonts(); void testCreateView(); void testGetFilterTypes(); void testGetPartPageRectangles(); void testSearchCalc(); void testSearchAllNotificationsCalc(); void testPaintTile(); void testSaveAs(); void testSaveAsCalc(); void testPasteWriter(); void testPasteWriterJPEG(); void testUndoWriter(); void testRowColumnHeaders(); void testHiddenRowHeaders(); void testCellCursor(); void testCommandResult(); void testWriterComments(); void testSheetOperations(); void testSheetSelections(); void testContextMenuCalc(); void testContextMenuWriter(); void testContextMenuImpress(); void testNotificationCompression(); void testTileInvalidationCompression(); void testPartInInvalidation(); void testInput(); void testRedlineWriter(); void testTrackChanges(); void testRedlineCalc(); void testPaintPartTile(); void testWriterCommentInsertCursor(); #if HAVE_MORE_FONTS void testGetFontSubset(); #endif void testCommentsWriter(); void testCommentsCalc(); void testCommentsImpress(); void testCommentsCallbacksWriter(); void testRunMacro(); void testExtractParameter(); void testGetSignatureState_NonSigned(); void testGetSignatureState_Signed(); void testInsertCertificate_DER_ODT(); void testInsertCertificate_PEM_ODT(); void testInsertCertificate_PEM_DOCX(); void testSignDocument_PEM_PDF(); void testTextSelectionHandles(); void testComplexSelection(); void testSpellcheckerMultiView(); void testDialogPaste(); void testShowHideDialog(); void testDialogInput(); void testCalcSaveAs(); void testControlState(); void testMetricField(); void testABI(); CPPUNIT_TEST_SUITE(DesktopLOKTest); CPPUNIT_TEST(testGetStyles); CPPUNIT_TEST(testGetFonts); CPPUNIT_TEST(testCreateView); CPPUNIT_TEST(testGetFilterTypes); CPPUNIT_TEST(testGetPartPageRectangles); CPPUNIT_TEST(testSearchCalc); CPPUNIT_TEST(testSearchAllNotificationsCalc); CPPUNIT_TEST(testPaintTile); CPPUNIT_TEST(testSaveAs); CPPUNIT_TEST(testSaveAsCalc); CPPUNIT_TEST(testPasteWriter); CPPUNIT_TEST(testPasteWriterJPEG); CPPUNIT_TEST(testUndoWriter); CPPUNIT_TEST(testRowColumnHeaders); CPPUNIT_TEST(testHiddenRowHeaders); CPPUNIT_TEST(testCellCursor); CPPUNIT_TEST(testCommandResult); CPPUNIT_TEST(testWriterComments); CPPUNIT_TEST(testSheetOperations); CPPUNIT_TEST(testSheetSelections); CPPUNIT_TEST(testContextMenuCalc); CPPUNIT_TEST(testContextMenuWriter); CPPUNIT_TEST(testContextMenuImpress); CPPUNIT_TEST(testNotificationCompression); CPPUNIT_TEST(testTileInvalidationCompression); CPPUNIT_TEST(testPartInInvalidation); CPPUNIT_TEST(testInput); CPPUNIT_TEST(testRedlineWriter); CPPUNIT_TEST(testTrackChanges); CPPUNIT_TEST(testRedlineCalc); CPPUNIT_TEST(testPaintPartTile); CPPUNIT_TEST(testWriterCommentInsertCursor); #if HAVE_MORE_FONTS CPPUNIT_TEST(testGetFontSubset); #endif CPPUNIT_TEST(testCommentsWriter); CPPUNIT_TEST(testCommentsCalc); CPPUNIT_TEST(testCommentsImpress); CPPUNIT_TEST(testCommentsCallbacksWriter); CPPUNIT_TEST(testRunMacro); CPPUNIT_TEST(testExtractParameter); CPPUNIT_TEST(testGetSignatureState_Signed); CPPUNIT_TEST(testGetSignatureState_NonSigned); #if !MPL_HAVE_SUBSET CPPUNIT_TEST(testInsertCertificate_DER_ODT); CPPUNIT_TEST(testInsertCertificate_PEM_ODT); CPPUNIT_TEST(testInsertCertificate_PEM_DOCX); CPPUNIT_TEST(testSignDocument_PEM_PDF); #endif CPPUNIT_TEST(testTextSelectionHandles); CPPUNIT_TEST(testComplexSelection); CPPUNIT_TEST(testSpellcheckerMultiView); CPPUNIT_TEST(testDialogPaste); CPPUNIT_TEST(testShowHideDialog); CPPUNIT_TEST(testDialogInput); CPPUNIT_TEST(testCalcSaveAs); CPPUNIT_TEST(testControlState); CPPUNIT_TEST(testMetricField); CPPUNIT_TEST(testABI); CPPUNIT_TEST_SUITE_END(); uno::Reference mxComponent; OString m_aTextSelection; OString m_aTextSelectionStart; OString m_aTextSelectionEnd; std::vector m_aSearchResultSelection; std::vector m_aSearchResultPart; int m_nSelectionBeforeSearchResult; int m_nSelectionAfterSearchResult; // for testCommandResult osl::Condition m_aCommandResultCondition; OString m_aCommandResult; // for testModifiedStatus osl::Condition m_aStateChangedCondition; bool m_bModified; int m_nTrackChanges; // for testContextMenu{Calc, Writer} osl::Condition m_aContextMenuCondition; boost::property_tree::ptree m_aContextMenuResult; std::unique_ptr m_pDocument; }; static Control* GetFocusControl(vcl::Window const * pParent) { sal_uInt16 nChildren = pParent->GetChildCount(); for (sal_uInt16 nChild = 0; nChild < nChildren; ++nChild) { vcl::Window* pChild = pParent->GetChild( nChild ); Control* pCtrl = dynamic_cast(pChild); if (pCtrl && pCtrl->HasControlFocus()) return pCtrl; Control* pSubCtrl = GetFocusControl( pChild ); if (pSubCtrl) return pSubCtrl; } return nullptr; } LibLODocument_Impl* DesktopLOKTest::loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType) { OUString aService; switch (eType) { case LOK_DOCTYPE_TEXT: aService = "com.sun.star.text.TextDocument"; break; case LOK_DOCTYPE_SPREADSHEET: aService = "com.sun.star.sheet.SpreadsheetDocument"; break; case LOK_DOCTYPE_PRESENTATION: aService = "com.sun.star.presentation.PresentationDocument"; break; default: CPPUNIT_ASSERT(false); break; } mxComponent = loadFromDesktop(rFileURL, aService); if (!mxComponent.is()) { CPPUNIT_ASSERT(false); } m_pDocument.reset(new LibLODocument_Impl(mxComponent)); return m_pDocument.get(); } LibLODocument_Impl* DesktopLOKTest::loadDoc(const char* pName, LibreOfficeKitDocumentType eType) { OUString aFileURL; createFileURL(OUString::createFromAscii(pName), aFileURL); return loadDocUrl(aFileURL, eType); } void DesktopLOKTest::closeDoc() { if (mxComponent.is()) { closeDocument(mxComponent); mxComponent.clear(); } } void DesktopLOKTest::callback(int nType, const char* pPayload, void* pData) { static_cast(pData)->callbackImpl(nType, pPayload); } void DesktopLOKTest::callbackImpl(int nType, const char* pPayload) { switch (nType) { case LOK_CALLBACK_TEXT_SELECTION: { m_aTextSelection = pPayload; if (m_aSearchResultSelection.empty()) ++m_nSelectionBeforeSearchResult; else ++m_nSelectionAfterSearchResult; } break; case LOK_CALLBACK_TEXT_SELECTION_START: m_aTextSelectionStart = pPayload; break; case LOK_CALLBACK_TEXT_SELECTION_END: m_aTextSelectionEnd = pPayload; break; case LOK_CALLBACK_SEARCH_RESULT_SELECTION: { m_aSearchResultSelection.clear(); boost::property_tree::ptree aTree; std::stringstream aStream(pPayload); boost::property_tree::read_json(aStream, aTree); for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection")) { m_aSearchResultSelection.emplace_back(rValue.second.get("rectangles").c_str()); m_aSearchResultPart.push_back(std::atoi(rValue.second.get("part").c_str())); } } break; case LOK_CALLBACK_UNO_COMMAND_RESULT: { m_aCommandResult = pPayload; m_aCommandResultCondition.set(); } break; case LOK_CALLBACK_STATE_CHANGED: { OString aPayload(pPayload); OString aPrefix(".uno:ModifiedStatus="); if (aPayload.startsWith(aPrefix)) { m_bModified = aPayload.copy(aPrefix.getLength()).toBoolean(); m_aStateChangedCondition.set(); } else if (aPayload.startsWith(".uno:TrackChanges=") && aPayload.endsWith("=true")) ++m_nTrackChanges; } break; case LOK_CALLBACK_CONTEXT_MENU: { m_aContextMenuResult.clear(); std::stringstream aStream(pPayload); boost::property_tree::read_json(aStream, m_aContextMenuResult); m_aContextMenuCondition.set(); } break; } } void DesktopLOKTest::testGetStyles() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:StyleApply"); std::stringstream aStream(pJSON); boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT( !aTree.empty() ); CPPUNIT_ASSERT_EQUAL( std::string(".uno:StyleApply"), aTree.get_child("commandName").get_value() ); boost::property_tree::ptree aValues = aTree.get_child("commandValues"); CPPUNIT_ASSERT( !aValues.empty() ); for (const auto& rPair : aValues) { if( rPair.first != "ClearStyle") { CPPUNIT_ASSERT( !rPair.second.empty()); } if (rPair.first != "CharacterStyles" && rPair.first != "ParagraphStyles" && rPair.first != "FrameStyles" && rPair.first != "PageStyles" && rPair.first != "NumberingStyles" && rPair.first != "CellStyles" && rPair.first != "ShapeStyles" && rPair.first != "TableStyles" && rPair.first != "HeaderFooter" && rPair.first != "Commands") { CPPUNIT_FAIL("Unknown style family: " + rPair.first); } } } void DesktopLOKTest::testGetFonts() { LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp"); boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CharFontName"); std::stringstream aStream(pJSON); boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT( !aTree.empty() ); CPPUNIT_ASSERT_EQUAL( std::string(".uno:CharFontName"), aTree.get_child("commandName").get_value() ); boost::property_tree::ptree aValues = aTree.get_child("commandValues"); CPPUNIT_ASSERT( !aValues.empty() ); for (const auto& rPair : aValues) { // check that we have font sizes available for each font CPPUNIT_ASSERT( !rPair.second.empty()); } free(pJSON); } void DesktopLOKTest::testCreateView() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); int nId0 = pDocument->m_pDocumentClass->getView(pDocument); int nId1 = pDocument->m_pDocumentClass->createView(pDocument); CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument)); // Test getViewIds(). std::vector aViewIds(2); CPPUNIT_ASSERT(pDocument->m_pDocumentClass->getViewIds(pDocument, aViewIds.data(), aViewIds.size())); CPPUNIT_ASSERT_EQUAL(nId0, aViewIds[0]); CPPUNIT_ASSERT_EQUAL(nId1, aViewIds[1]); // Make sure the created view is the active one, then switch to the old // one. CPPUNIT_ASSERT_EQUAL(nId1, pDocument->m_pDocumentClass->getView(pDocument)); pDocument->m_pDocumentClass->setView(pDocument, nId0); CPPUNIT_ASSERT_EQUAL(nId0, pDocument->m_pDocumentClass->getView(pDocument)); pDocument->m_pDocumentClass->destroyView(pDocument, nId1); CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); } void DesktopLOKTest::testGetPartPageRectangles() { // Test that we get as many page rectangles as expected: blank document is // one page. LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); char* pRectangles = pDocument->pClass->getPartPageRectangles(pDocument); OUString sRectangles = OUString::fromUtf8(pRectangles); std::vector aRectangles; sal_Int32 nIndex = 0; do { OUString aRectangle = sRectangles.getToken(0, ';', nIndex); if (!aRectangle.isEmpty()) aRectangles.push_back(aRectangle); } while (nIndex >= 0); CPPUNIT_ASSERT_EQUAL(static_cast(1), aRectangles.size()); free(pRectangles); } void DesktopLOKTest::testGetFilterTypes() { LibLibreOffice_Impl aOffice; char* pJSON = aOffice.m_pOfficeClass->getFilterTypes(&aOffice); std::stringstream aStream(pJSON); boost::property_tree::ptree aTree; boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT(!aTree.empty()); CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text"), aTree.get_child("writer8").get_child("MediaType").get_value()); free(pJSON); } void DesktopLOKTest::testSearchCalc() { LibLibreOffice_Impl aOffice; LibLODocument_Impl* pDocument = loadDoc("search.ods"); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); uno::Sequence aPropertyValues(comphelper::InitPropertySequence( { {"SearchItem.SearchString", uno::makeAny(OUString("foo"))}, {"SearchItem.Backward", uno::makeAny(false)}, {"SearchItem.Command", uno::makeAny(static_cast(SvxSearchCmd::FIND_ALL))}, })); comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); Scheduler::ProcessEventsToIdle(); std::vector aSelections; sal_Int32 nIndex = 0; do { OString aToken = m_aTextSelection.getToken(0, ';', nIndex); aSelections.push_back(aToken); } while (nIndex >= 0); // This was 1, find-all only found one match. CPPUNIT_ASSERT_EQUAL(static_cast(2), aSelections.size()); // Make sure that we get exactly as many rectangle lists as matches. CPPUNIT_ASSERT_EQUAL(static_cast(2), m_aSearchResultSelection.size()); // Result is on the first sheet. CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]); } void DesktopLOKTest::testSearchAllNotificationsCalc() { LibLibreOffice_Impl aOffice; LibLODocument_Impl* pDocument = loadDoc("search.ods"); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); uno::Sequence aPropertyValues(comphelper::InitPropertySequence( { {"SearchItem.SearchString", uno::makeAny(OUString("foo"))}, {"SearchItem.Backward", uno::makeAny(false)}, {"SearchItem.Command", uno::makeAny(static_cast(SvxSearchCmd::FIND_ALL))}, })); comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); Scheduler::ProcessEventsToIdle(); // This was 1, make sure that we get no notifications about selection changes during search. CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult); // But we do get the selection afterwards. CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0); } void DesktopLOKTest::testPaintTile() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); int nCanvasWidth = 100; int nCanvasHeight = 300; sal_Int32 nStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nCanvasWidth); std::vector aBuffer(nStride * nCanvasHeight); int nTilePosX = 0; int nTilePosY = 0; int nTileWidth = 1000; int nTileHeight = 3000; // This used to crash: paintTile() implementation did not handle // nCanvasWidth != nCanvasHeight correctly, as usually both are just always // 256. pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); // This crashed in OutputDevice::DrawDeviceAlphaBitmap(). nCanvasWidth = 200; nCanvasHeight = 200; nTileWidth = 4000; nTileHeight = 4000; aBuffer.resize(nCanvasWidth * nCanvasHeight * 4); pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); } void DesktopLOKTest::testSaveAs() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); utl::TempFile aTempFile; aTempFile.EnableKillingFile(); CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "png", nullptr)); } void DesktopLOKTest::testSaveAsCalc() { LibLODocument_Impl* pDocument = loadDoc("search.ods"); utl::TempFile aTempFile; aTempFile.EnableKillingFile(); CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "png", nullptr)); } void DesktopLOKTest::testPasteWriter() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); OString aText("hello"); CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); Scheduler::ProcessEventsToIdle(); char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); CPPUNIT_ASSERT_EQUAL(OString("hello"), OString(pText)); free(pText); // textt/plain should be rejected. CPPUNIT_ASSERT(!pDocument->pClass->paste(pDocument, "textt/plain;charset=utf-8", aText.getStr(), aText.getLength())); // Writer is expected to support text/html. CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aText.getStr(), aText.getLength())); // Overwrite doc contents with a HTML paste. pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); Scheduler::ProcessEventsToIdle(); OString aComment("foo baz"); CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html", aComment.getStr(), aComment.getLength())); // Check if we have a comment. uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); uno::Reference xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY); uno::Reference xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration(); uno::Reference xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY); uno::Reference xTextPortionEnumeration = xParagraph->createEnumeration(); uno::Reference xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(OUString("Text"), xTextPortion->getPropertyValue("TextPortionType").get()); // Without the accompanying fix in place, this test would have failed, as we had a comment // between "foo" and "baz". CPPUNIT_ASSERT(!xTextPortionEnumeration->hasMoreElements()); } void DesktopLOKTest::testPasteWriterJPEG() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); OUString aFileURL; createFileURL("paste.jpg", aFileURL); std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr()); std::vector aImageContents((std::istreambuf_iterator(aImageStream)), std::istreambuf_iterator()); CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size())); uno::Reference xDrawPageSupplier(mxComponent, uno::UNO_QUERY); uno::Reference xDrawPage = xDrawPageSupplier->getDrawPage(); // This was 0, JPEG was not handled as a format for clipboard paste. CPPUNIT_ASSERT_EQUAL(static_cast(1), xDrawPage->getCount()); uno::Reference xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY); // This was text::TextContentAnchorType_AT_PARAGRAPH. CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER, xShape->getPropertyValue("AnchorType").get()); // Delete the pasted picture, and paste again with a custom anchor type. uno::Reference(xShape, uno::UNO_QUERY_THROW)->dispose(); uno::Sequence aPropertyValues(comphelper::InitPropertySequence( { {"AnchorType", uno::makeAny(static_cast(text::TextContentAnchorType_AT_CHARACTER))}, })); comphelper::dispatchCommand(".uno:Paste", aPropertyValues); xShape.set(xDrawPage->getByIndex(0), uno::UNO_QUERY); // This was text::TextContentAnchorType_AS_CHARACTER, AnchorType argument was ignored. CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER, xShape->getPropertyValue("AnchorType").get()); } void DesktopLOKTest::testUndoWriter() { // Load a Writer document and press a key. LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); Scheduler::ProcessEventsToIdle(); // Get undo info. boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:Undo"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); // Make sure that pressing a key creates exactly one undo action. CPPUNIT_ASSERT_EQUAL(static_cast(1), aTree.get_child("actions").size()); } void DesktopLOKTest::testRowColumnHeaders() { /* * Payload example: * * { * "rows": [ * { * "size": "254.987250637468", * "text": "1" * }, * { * "size": "509.974501274936", * "text": "2" * } * ], * "columns": [ * { * "size": "1274.93625318734", * "text": "A" * }, * { * "size": "2549.87250637468", * "text": "B" * } * ] * } * * "size" defines the bottom/right boundary of a row/column in twips (size between 0 and boundary) * "text" has the header label in UTF-8 */ LibLODocument_Impl* pDocument = loadDoc("search.ods"); pDocument->pClass->initializeForRendering(pDocument, nullptr); long nWidth = 0; long nHeight = 0; pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); long nX = rtl::math::round(nWidth / 4.0); long nY = rtl::math::round(nHeight / 4.0); nWidth = rtl::math::round(nWidth / 2.0); nHeight = rtl::math::round(nHeight / 2.0); std::stringstream aPayload; aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight; boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str()); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); sal_Int32 nPrevious = 0; bool bFirstHeader = true; bool bNotEnoughHeaders = true; for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows")) { sal_Int32 nSize = OString(rValue.second.get("size").c_str()).toInt32(); nSize *= 15; /* TWIPS_PER_PIXEL */ OString aText(rValue.second.get("text").c_str()); if (bFirstHeader) { CPPUNIT_ASSERT(nSize <= nY); CPPUNIT_ASSERT_EQUAL(OString("10"), aText); bFirstHeader = false; } else { CPPUNIT_ASSERT(nSize > 0); CPPUNIT_ASSERT(nPrevious < nSize); if (nSize > nY + nHeight) { bNotEnoughHeaders = false; break; } } nPrevious = nSize; } CPPUNIT_ASSERT(!bNotEnoughHeaders); nPrevious = 0; bFirstHeader = true; bNotEnoughHeaders = true; for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns")) { sal_Int32 nSize = OString(rValue.second.get("size").c_str()).toInt32(); nSize *= 15; /* TWIPS_PER_PIXEL */ OString aText(rValue.second.get("text").c_str()); if (bFirstHeader) { CPPUNIT_ASSERT(nSize <= nX); CPPUNIT_ASSERT_EQUAL(OString("3"), aText); bFirstHeader = false; } else { CPPUNIT_ASSERT(nSize > 0); CPPUNIT_ASSERT(nPrevious < nSize); if (nSize > nX + nWidth) { bNotEnoughHeaders = false; break; } } nPrevious = nSize; } CPPUNIT_ASSERT(!bNotEnoughHeaders); } void DesktopLOKTest::testHiddenRowHeaders() { LibLODocument_Impl* pDocument = loadDoc("hidden-row.ods"); pDocument->pClass->initializeForRendering(pDocument, nullptr); long const nX = 0; long const nY = 0; long nWidth = 0; long nHeight = 0; pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); std::stringstream aPayload; aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight; boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str()); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); sal_Int32 nPrevious = 0; sal_Int32 nIndex = 0; for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows")) { sal_Int32 nSize = OString(rValue.second.get("size").c_str()).toInt32(); if (nIndex++ == 2) { // nSize was 510, nPrevious was 255, i.e. hidden row wasn't reported as 0 height. CPPUNIT_ASSERT_EQUAL(nPrevious, nSize); break; } nPrevious = nSize; } } void DesktopLOKTest::testCellCursor() { LibLODocument_Impl* pDocument = loadDoc("search.ods"); boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CellCursor?tileWidth=1&tileHeight=1&outputWidth=1&outputHeight=1"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); OString aRectangle(aTree.get("commandValues").c_str()); // cell cursor geometry + col + row CPPUNIT_ASSERT_EQUAL(OString("0, 0, 1274, 254, 0, 0"), aRectangle); } void DesktopLOKTest::testCommandResult() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); // the postUnoCommand() is supposed to be async, let's test it safely // [no idea if it is async in reality - most probably we are operating // under some solar mutex or something anyway ;-) - but...] TimeValue aTimeValue = { 2 , 0 }; // 2 seconds max // nothing is triggered when we have no callback yet, we just time out on // the condition var. m_aCommandResultCondition.reset(); pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true); Scheduler::ProcessEventsToIdle(); m_aCommandResultCondition.wait(aTimeValue); CPPUNIT_ASSERT(m_aCommandResult.isEmpty()); // but we get some real values when the callback is set up pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); m_aCommandResultCondition.reset(); pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold", nullptr, true); Scheduler::ProcessEventsToIdle(); m_aCommandResultCondition.wait(aTimeValue); boost::property_tree::ptree aTree; std::stringstream aStream(m_aCommandResult.getStr()); boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), aTree.get_child("commandName").get_value()); CPPUNIT_ASSERT_EQUAL(true, aTree.get_child("success").get_value()); } void DesktopLOKTest::testWriterComments() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); uno::Reference xToolkit = com::sun::star::awt::Toolkit::create(comphelper::getProcessComponentContext()); // Insert a comment at the beginning of the document and wait till the main // loop grabs the focus, so characters end up in the annotation window. TimeValue const aTimeValue = {2 , 0}; // 2 seconds max m_aCommandResultCondition.reset(); pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", nullptr, true); Scheduler::ProcessEventsToIdle(); m_aCommandResultCondition.wait(aTimeValue); CPPUNIT_ASSERT(!m_aCommandResult.isEmpty()); xToolkit->reschedule(); // Test that we have a comment. uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); uno::Reference xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY); uno::Reference xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration(); uno::Reference xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY); uno::Reference xTextPortionEnumeration = xParagraph->createEnumeration(); uno::Reference xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(OUString("Annotation"), xTextPortion->getPropertyValue("TextPortionType").get()); // Type "test" and finish editing. pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'e', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 's', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); // Test that the typed characters ended up in the right window. auto xTextField = xTextPortion->getPropertyValue("TextField").get< uno::Reference >(); // This was empty, typed characters ended up in the body text. CPPUNIT_ASSERT_EQUAL(OUString("test"), xTextField->getPropertyValue("Content").get()); } void DesktopLOKTest::testTrackChanges() { // Load a document and create two views. LibLibreOffice_Impl aOffice; LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); pDocument->pClass->createView(pDocument); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); Scheduler::ProcessEventsToIdle(); // Enable track changes and assert that both views get notified. m_nTrackChanges = 0; pDocument->pClass->postUnoCommand(pDocument, ".uno:TrackChanges", nullptr, false); Scheduler::ProcessEventsToIdle(); // This was 1, only the active view was notified. CPPUNIT_ASSERT_EQUAL(2, m_nTrackChanges); } void DesktopLOKTest::testSheetOperations() { LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); // insert the last sheet pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert", "{ \"Name\": { \"type\": \"string\", \"value\": \"LastSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 0 } }", false); // insert the first sheet pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert", "{ \"Name\": { \"type\": \"string\", \"value\": \"FirstSheet\" }, \"Index\": { \"type\": \"long\", \"value\": 1 } }", false); // rename the \"Sheet1\" (2nd now) to \"Renamed\" pDocument->pClass->postUnoCommand(pDocument, ".uno:Name", "{ \"Name\": { \"type\": \"string\", \"value\": \"Renamed\" }, \"Index\": { \"type\": \"long\", \"value\": 2 } }", false); // delete the \"Sheet2\" (3rd) pDocument->pClass->postUnoCommand(pDocument, ".uno:Remove", "{ \"Index\": { \"type\": \"long\", \"value\": 3 } }", false); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(6, pDocument->pClass->getParts(pDocument)); std::vector aExpected = { "FirstSheet", "Renamed", "Sheet3", "Sheet4", "Sheet5", "LastSheet" }; for (int i = 0; i < 6; ++i) { CPPUNIT_ASSERT_EQUAL(aExpected[i], OString(pDocument->pClass->getPartName(pDocument, i))); } } void DesktopLOKTest::testSheetSelections() { LibLODocument_Impl* pDocument = loadDoc("sheets.ods", LOK_DOCTYPE_SPREADSHEET); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); /* * Check if selection data is correct */ // Values in twips int row5 = 1150; int col1 = 1100; int const col2 = 2200; int const col3 = 3300; int col4 = 4400; int col5 = 5500; // Select row 5 from column 1 through column 5 pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, col1, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEMOVE, col2, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEMOVE, col3, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEMOVE, col4, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEMOVE, col5, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONUP, col5, row5, 1, 1, 0); Scheduler::ProcessEventsToIdle(); // Copy the contents and check if matches expected data { char* pUsedMimeType = nullptr; char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType); std::vector aExpected = {5, 6, 7, 8, 9}; std::istringstream iss(pCopiedContent); for (size_t i = 0; i < aExpected.size(); i++) { std::string token; iss >> token; CPPUNIT_ASSERT_EQUAL(aExpected[i], strtol(token.c_str(), nullptr, 10)); } free(pUsedMimeType); free(pCopiedContent); } /* * Check if clicking inside the selection deselects the whole selection */ int const row10 = 2400; // Select starting from row5, col1 to row10, col5 pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, col1, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEMOVE, col5, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONUP, col5, row10, 1, 1, 0); // Click at row5, col4 pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, col4, row5, 1, 1, 0); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONUP, col4, row5, 1, 1, 0); Scheduler::ProcessEventsToIdle(); // Selected text should get deselected and copying should give us // content of only one cell, now { char* pUsedMimeType = nullptr; char* pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType); std::vector aExpected = { 8 }; std::istringstream iss(pCopiedContent); for (size_t i = 0; i < aExpected.size(); i++) { std::string token; iss >> token; CPPUNIT_ASSERT_EQUAL(aExpected[i], strtol(token.c_str(), nullptr, 10)); } free(pUsedMimeType); free(pCopiedContent); } } namespace { void verifyContextMenuStructure(boost::property_tree::ptree& aRoot) { for (const auto& aItemPair: aRoot) { // This is an array, so no key CPPUNIT_ASSERT_EQUAL(aItemPair.first, std::string("")); boost::property_tree::ptree aItemValue = aItemPair.second; boost::optional aText = aItemValue.get_child_optional("text"); boost::optional aType = aItemValue.get_child_optional("type"); boost::optional aCommand = aItemValue.get_child_optional("command"); boost::optional aSubmenu = aItemValue.get_child_optional("menu"); boost::optional aEnabled = aItemValue.get_child_optional("enabled"); boost::optional aChecktype = aItemValue.get_child_optional("checktype"); boost::optional aChecked = aItemValue.get_child_optional("checked"); // type is omnipresent CPPUNIT_ASSERT( aType ); // separator doesn't have any other attribs if ( aType.get().data() == "separator" ) { CPPUNIT_ASSERT( !aText && !aCommand && !aSubmenu && !aEnabled && !aChecktype && !aChecked ); } else if ( aType.get().data() == "command" ) { CPPUNIT_ASSERT( aCommand && aText ); } else if ( aType.get().data() == "menu") { CPPUNIT_ASSERT( aSubmenu && aText ); verifyContextMenuStructure( aSubmenu.get() ); } if ( aChecktype ) { CPPUNIT_ASSERT( aChecktype.get().data() == "radio" || aChecktype.get().data() == "checkmark" || aChecktype.get().data() == "auto" ); CPPUNIT_ASSERT( aChecked && ( aChecked.get().data() == "true" || aChecked.get().data() == "false" ) ); } } } boost::optional getContextMenuItem(boost::property_tree::ptree& aMenu, std::string const & unoSelector) { boost::optional aMenuItem; for (const auto& aItemPair: aMenu) { boost::property_tree::ptree aItemValue = aItemPair.second; boost::optional aCommand = aItemValue.get_child_optional("command"); if (aCommand && aCommand.get().data() == unoSelector ) { aMenuItem = aItemValue; break; } } return aMenuItem; } } // end anonymous namespace void DesktopLOKTest::testContextMenuCalc() { LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); // Values in twips Point aPointOnImage(1150, 1100); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aPointOnImage.X(), aPointOnImage.Y(), 1, 4, 0); Scheduler::ProcessEventsToIdle(); TimeValue const aTimeValue = {2 , 0}; // 2 seconds max m_aContextMenuCondition.wait(aTimeValue); CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); boost::optional aMenu = m_aContextMenuResult.get_child_optional("menu"); CPPUNIT_ASSERT( aMenu ); verifyContextMenuStructure( aMenu.get() ); // tests for calc specific context menu // Cut is enabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); } // Copy is enabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); } // Paste is enabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); } // Remove hyperlink is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:DeleteShapeHyperlink"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // open hyperlink is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:OpenHyperlinkOnCursor"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // checkbutton tests { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:AnchorMenu"); CPPUNIT_ASSERT(aMenuItem); boost::optional aSubmenu = aMenuItem.get().get_child_optional("menu"); CPPUNIT_ASSERT(aSubmenu); boost::optional aMenuItemToPage = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToPage"); CPPUNIT_ASSERT(aMenuItemToPage); boost::optional aMenuItemToCell = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToCell"); CPPUNIT_ASSERT(aMenuItemToCell); // these are radio buttons boost::optional aChecktypeToPage = aMenuItemToPage.get().get_child_optional("checktype"); CPPUNIT_ASSERT(aChecktypeToPage); CPPUNIT_ASSERT_EQUAL(aChecktypeToPage.get().data(), std::string("radio")); boost::optional aChecktypeToCell = aMenuItemToCell.get().get_child_optional("checktype"); CPPUNIT_ASSERT(aChecktypeToCell); CPPUNIT_ASSERT_EQUAL(aChecktypeToCell.get().data(), std::string("radio")); // ToPage is checked boost::optional aCheckedToPage = aMenuItemToPage.get().get_child_optional("checked"); CPPUNIT_ASSERT(aCheckedToPage); CPPUNIT_ASSERT_EQUAL(aCheckedToPage.get().data(), std::string("true")); // ToCell is unchecked boost::optional aCheckedToCell = aMenuItemToCell.get().get_child_optional("checked"); CPPUNIT_ASSERT(aCheckedToCell); CPPUNIT_ASSERT_EQUAL(aCheckedToCell.get().data(), std::string("false")); } } void DesktopLOKTest::testContextMenuWriter() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); Point aRandomPoint(1150, 1100); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aRandomPoint.X(), aRandomPoint.Y(), 1, 4, 0); Scheduler::ProcessEventsToIdle(); TimeValue const aTimeValue = {2 , 0}; // 2 seconds max m_aContextMenuCondition.wait(aTimeValue); CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); boost::optional aMenu = m_aContextMenuResult.get_child_optional("menu"); CPPUNIT_ASSERT( aMenu ); verifyContextMenuStructure( aMenu.get() ); // tests for writer specific context menu // Cut is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // Copy is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // Paste is enabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); } } void DesktopLOKTest::testContextMenuImpress() { LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp", LOK_DOCTYPE_PRESENTATION); pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); // random point where we don't hit an underlying comment or text box Point aRandomPoint(10, 1150); pDocument->pClass->postMouseEvent(pDocument, LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aRandomPoint.X(), aRandomPoint.Y(), 1, 4, 0); Scheduler::ProcessEventsToIdle(); TimeValue const aTimeValue = {2 , 0}; // 2 seconds max m_aContextMenuCondition.wait(aTimeValue); CPPUNIT_ASSERT( !m_aContextMenuResult.empty() ); boost::optional aMenu = m_aContextMenuResult.get_child_optional("menu"); CPPUNIT_ASSERT( aMenu ); verifyContextMenuStructure( aMenu.get() ); // tests for impress specific context menu // Cut is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // Copy is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // Paste is enabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true")); } // SaveBackground is disabled { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SaveBackground"); CPPUNIT_ASSERT(aMenuItem); boost::optional aEnabled = aMenuItem.get().get_child_optional("enabled"); CPPUNIT_ASSERT(aEnabled); CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false")); } // checkbutton tests { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:ShowRuler"); CPPUNIT_ASSERT(aMenuItem); boost::optional aChecktype = aMenuItem.get().get_child_optional("checktype"); CPPUNIT_ASSERT(aChecktype); CPPUNIT_ASSERT_EQUAL(aChecktype.get().data(), std::string("checkmark")); boost::optional aChecked = aMenuItem.get().get_child_optional("checked"); CPPUNIT_ASSERT(aChecked); CPPUNIT_ASSERT_EQUAL(aChecked.get().data(), std::string("false")); } // Checkbutton tests inside SnapLines submenu { boost::optional aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SnapLinesMenu"); CPPUNIT_ASSERT(aMenuItem); boost::optional aSubmenu = aMenuItem.get().get_child_optional("menu"); CPPUNIT_ASSERT(aSubmenu); boost::optional aMenuItemHelpVis = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesVisible"); CPPUNIT_ASSERT(aMenuItemHelpVis); boost::optional aMenuItemHelpUse = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesUse"); CPPUNIT_ASSERT(aMenuItemHelpUse); boost::optional aMenuItemHelpFront = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesFront"); CPPUNIT_ASSERT(aMenuItemHelpFront); // these are checkmarks boost::optional aChecktypeHelpVis = aMenuItemHelpVis.get().get_child_optional("checktype"); CPPUNIT_ASSERT(aChecktypeHelpVis); CPPUNIT_ASSERT_EQUAL(aChecktypeHelpVis.get().data(), std::string("checkmark")); boost::optional aChecktypeHelpUse = aMenuItemHelpUse.get().get_child_optional("checktype"); CPPUNIT_ASSERT(aChecktypeHelpUse); CPPUNIT_ASSERT_EQUAL(aChecktypeHelpUse.get().data(), std::string("checkmark")); boost::optional aChecktypeHelpFront = aMenuItemHelpFront.get().get_child_optional("checktype"); CPPUNIT_ASSERT(aChecktypeHelpFront); CPPUNIT_ASSERT_EQUAL(aChecktypeHelpFront.get().data(), std::string("checkmark")); // HelplineVisible is unchecked boost::optional aCheckedHelpVis = aMenuItemHelpVis.get().get_child_optional("checked"); CPPUNIT_ASSERT(aCheckedHelpVis); CPPUNIT_ASSERT_EQUAL(aCheckedHelpVis.get().data(), std::string("false")); // HelplineUse is checked boost::optional aCheckedHelpUse = aMenuItemHelpUse.get().get_child_optional("checked"); CPPUNIT_ASSERT(aCheckedHelpUse); CPPUNIT_ASSERT_EQUAL(aCheckedHelpUse.get().data(), std::string("true")); // HelplineFront is checked boost::optional aCheckedHelpFront = aMenuItemHelpFront.get().get_child_optional("checked"); CPPUNIT_ASSERT(aCheckedHelpFront); CPPUNIT_ASSERT_EQUAL(aCheckedHelpFront.get().data(), std::string("true")); } } static void callbackCompressionTest(const int type, const char* payload, void* data) { std::vector>* notifs = static_cast>*>(data); notifs->emplace_back(type, std::string(payload ? payload : "(nil)")); } void DesktopLOKTest::testNotificationCompression() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""); // 0 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"); // Superseded. handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, ""); // Should be dropped. handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"); // 1 handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"); // Should be dropped. handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""); // Superseded. handler->queue(LOK_CALLBACK_STATE_CHANGED, ""); // 2 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:Bold"); // 3 handler->queue(LOK_CALLBACK_STATE_CHANGED, ""); // 4 handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"); // 5 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"); // Should be dropped. handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10"); // Should be dropped. handler->queue(LOK_CALLBACK_MOUSE_POINTER, "text"); // Should be dropped. handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"); // Superseded. handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"); // Superseded. handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10"); // Superseded. handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"); // Should be dropped. handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"); // Should be dropped. handler->queue(LOK_CALLBACK_TEXT_SELECTION, ""); // 7 handler->queue(LOK_CALLBACK_TEXT_SELECTION_START, "15, 25, 15, 10"); // 8 handler->queue(LOK_CALLBACK_TEXT_SELECTION_END, "15, 25, 15, 10"); // 9 handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"); // 10 handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""); // 11 handler->queue(LOK_CALLBACK_CELL_CURSOR, "15, 25, 15, 10"); // Should be dropped. handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"); // 12 handler->queue(LOK_CALLBACK_SET_PART, "1"); // 13 handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=20"); // Superseded handler->queue(LOK_CALLBACK_CURSOR_VISIBLE, ""); // Should be dropped. handler->queue(LOK_CALLBACK_CELL_FORMULA, "blah"); // Should be dropped. handler->queue(LOK_CALLBACK_SET_PART, "1"); // Should be dropped. handler->queue(LOK_CALLBACK_STATE_CHANGED, ".uno:AssignLayout=1"); // 14 Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(14), notifs.size()); size_t i = 0; CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_MOUSE_POINTER), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("text"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_START), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_TEXT_SELECTION_END), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_CURSOR), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("15, 25, 15, 10"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CURSOR_VISIBLE), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(""), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_CELL_FORMULA), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("blah"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_SET_PART), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("1"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_STATE_CHANGED), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string(".uno:AssignLayout=1"), std::get<1>(notifs[i++])); } void DesktopLOKTest::testTileInvalidationCompression() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); comphelper::LibreOfficeKit::setPartInInvalidation(true); comphelper::ScopeGuard aGuard([]() { comphelper::LibreOfficeKit::setPartInInvalidation(false); }); // Single part merging { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -50, 500, 650, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "100, 100, 200, 200, 0"); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(1), notifs.size()); size_t i = 0; CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 400, 600, 0"), std::get<1>(notifs[i++])); } // Part Number { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1"); // Different part handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, 2"); // Invalid handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, 0"); // Inside first handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 1"); // Invalid Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(2), notifs.size()); size_t i = 0; CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 1"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 239, 239, 0"), std::get<1>(notifs[i++])); } // All Parts { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0"); // 0 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 1"); // 1: Different part handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1"); // Invalid handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 200, 200, -1"); // 0: All parts handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1"); // Invalid handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-100, -100, 1200, 1200, -1"); // 0: All parts handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 3"); // Overlapped handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 2"); // 1: Unique region Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(2), notifs.size()); size_t i = 0; CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1100, 1100, -1"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 2"), std::get<1>(notifs[i++])); } // All Parts (partial) { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 0"); // 0 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 100, 100, 1"); // 1: Different part handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 0, 0, -1"); // Invalid handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 50, 50, -1"); // 2: All-parts handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, -1"); // Invalid handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "150, 150, 40, 40, 3"); // Overlapped w/ 2 handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 200, 200, 4"); // 3: Unique handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "1000, 1000, 1239, 1239, 1"); // 4: Unique Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(5), notifs.size()); size_t i = 0; CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 0"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 100, 100, 1"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("150, 150, 50, 50, -1"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 200, 200, 4"), std::get<1>(notifs[i++])); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("1000, 1000, 1239, 1239, 1"), std::get<1>(notifs[i++])); } // Merge with "EMPTY" { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 239, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "EMPTY, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, 239, 240, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "-121, -121, 300, 300, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "0, 0, -32767, -32767, 0"); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(1), notifs.size()); size_t i = 0; CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[i])); CPPUNIT_ASSERT_EQUAL(std::string("0, 0, 1000000000, 1000000000, 0"), std::get<1>(notifs[i++])); } } void DesktopLOKTest::testPartInInvalidation() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); // No part in invalidation: merge. { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10"); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(1), notifs.size()); CPPUNIT_ASSERT_EQUAL(int(LOK_CALLBACK_INVALIDATE_TILES), std::get<0>(notifs[0])); CPPUNIT_ASSERT_EQUAL(std::string("10, 10, 30, 10"), std::get<1>(notifs[0])); } // No part in invalidation: don't merge. { std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "40, 10, 20, 10"); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(2), notifs.size()); } // Part in invalidation, intersection and parts match -> merge. { comphelper::LibreOfficeKit::setPartInInvalidation(true); comphelper::ScopeGuard aGuard([]() { comphelper::LibreOfficeKit::setPartInInvalidation(false); }); std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 0"); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(static_cast(1), notifs.size()); } // Part in invalidation, intersection and parts don't match -> don't merge. { comphelper::LibreOfficeKit::setPartInInvalidation(true); comphelper::ScopeGuard aGuard([]() { comphelper::LibreOfficeKit::setPartInInvalidation(false); }); std::vector> notifs; std::unique_ptr handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs)); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "10, 10, 20, 10, 0"); handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "20, 10, 20, 10, 1"); Scheduler::ProcessEventsToIdle(); // This failed as RectangleAndPart::Create() always assumed no part in // payload, so this was merged -> it was 1. CPPUNIT_ASSERT_EQUAL(static_cast(2), notifs.size()); } } void DesktopLOKTest::testDialogInput() { comphelper::LibreOfficeKit::setActive(); LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false); Scheduler::ProcessEventsToIdle(); SfxViewShell* pViewShell = SfxViewShell::Current(); pViewShell->GetViewFrame()->GetBindings().Update(); VclPtr pWindow(Application::GetActiveTopWindow()); CPPUNIT_ASSERT(pWindow); Control* pCtrlFocused = GetFocusControl(pWindow.get()); CPPUNIT_ASSERT(pCtrlFocused); CPPUNIT_ASSERT_EQUAL(WindowType::COMBOBOX, pCtrlFocused->GetType()); CPPUNIT_ASSERT_EQUAL(OUString(""), pCtrlFocused->GetText()); vcl::LOKWindowId nDialogId = pWindow->GetLOKWindowId(); pDocument->pClass->postWindowExtTextInputEvent(pDocument, nDialogId, LOK_EXT_TEXTINPUT, "wiki."); pDocument->pClass->postWindowExtTextInputEvent(pDocument, nDialogId, LOK_EXT_TEXTINPUT_END, "wiki."); pDocument->pClass->removeTextContext(pDocument, nDialogId, 1, 0); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(OUString("wiki"), pCtrlFocused->GetText()); static_cast(pWindow.get())->Close(); Scheduler::ProcessEventsToIdle(); } void DesktopLOKTest::testInput() { // Load a Writer document, enable change recording and press a key. LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); uno::Reference xPropertySet(mxComponent, uno::UNO_QUERY); Scheduler::ProcessEventsToIdle(); // Get focus & other bits setup. pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "far"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "far"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "beyond"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "beyond"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); // Mis-spelled ... pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "kovely"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "kovely"); // Remove it again pDocument->pClass->removeTextContext(pDocument, 0, 6, 0); // Replace it with lovely pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, "lovely"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, "lovely"); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT, " "); pDocument->pClass->postWindowExtTextInputEvent(pDocument, 0, LOK_EXT_TEXTINPUT_END, " "); // get the text ... pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); Scheduler::ProcessEventsToIdle(); char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); CPPUNIT_ASSERT(pText != nullptr); OString aLovely("far beyond lovely "); CPPUNIT_ASSERT_EQUAL(aLovely, OString(pText)); free(pText); } void DesktopLOKTest::testRedlineWriter() { // Load a Writer document, enable change recording and press a key. LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); uno::Reference xPropertySet(mxComponent, uno::UNO_QUERY); xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true)); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); Scheduler::ProcessEventsToIdle(); // Get redline info. boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); // Make sure that pressing a key creates exactly one redline. CPPUNIT_ASSERT_EQUAL(static_cast(1), aTree.get_child("redlines").size()); for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines")) // This failed with boost::property_tree::ptree_bad_path, as there were no description field. CPPUNIT_ASSERT_EQUAL(std::string("Insert \xE2\x80\x9Ct\xE2\x80\x9D"), rRedline.second.get("description")); // U+201C LEFT DOUBLE QUOTATION MARK, U+201D RIGHT DOUBLE QUOTATION // MARK } void DesktopLOKTest::testRedlineCalc() { // Load a Writer document, enable change recording and press a key. LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); uno::Reference xPropertySet(mxComponent, uno::UNO_QUERY); xPropertySet->setPropertyValue("RecordChanges", uno::makeAny(true)); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); Scheduler::ProcessEventsToIdle(); // Get redline info. boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:AcceptTrackedChanges"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); // Make sure that pressing a key creates exactly one redline. CPPUNIT_ASSERT_EQUAL(static_cast(1), aTree.get_child("redlines").size()); for (const boost::property_tree::ptree::value_type& rRedline : aTree.get_child("redlines")) // This failed with boost::property_tree::ptree_bad_path, as there were no description field. CPPUNIT_ASSERT_EQUAL(std::string("Cell B4 changed from '5' to 't'"), rRedline.second.get("description")); } namespace { class ViewCallback { LibLODocument_Impl* mpDocument; int mnView; public: OString m_aCellFormula; bool m_bTilesInvalidated; tools::Rectangle m_aOwnCursor; boost::property_tree::ptree m_aCommentCallbackResult; boost::property_tree::ptree m_aCallbackWindowResult; bool m_bWindowHidden; ViewCallback(LibLODocument_Impl* pDocument) : mpDocument(pDocument), m_bTilesInvalidated(false) { mnView = SfxLokHelper::getView(); mpDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, this); } ~ViewCallback() { mpDocument->m_pDocumentClass->setView(mpDocument, mnView); mpDocument->m_pDocumentClass->registerCallback(mpDocument, nullptr, nullptr); } static void callback(int nType, const char* pPayload, void* pData) { static_cast(pData)->callbackImpl(nType, pPayload); } void callbackImpl(int nType, const char* pPayload) { OString aPayload(pPayload); switch (nType) { case LOK_CALLBACK_INVALIDATE_TILES: { m_bTilesInvalidated = true; } break; case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: { uno::Sequence aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aPayload)); if (OString("EMPTY") == pPayload) return; CPPUNIT_ASSERT_EQUAL(static_cast(4), aSeq.getLength()); m_aOwnCursor.setX(aSeq[0].toInt32()); m_aOwnCursor.setY(aSeq[1].toInt32()); m_aOwnCursor.setWidth(aSeq[2].toInt32()); m_aOwnCursor.setHeight(aSeq[3].toInt32()); } break; case LOK_CALLBACK_COMMENT: { m_aCommentCallbackResult.clear(); std::stringstream aStream(pPayload); boost::property_tree::read_json(aStream, m_aCommentCallbackResult); m_aCommentCallbackResult = m_aCommentCallbackResult.get_child("comment"); } break; case LOK_CALLBACK_WINDOW: { m_aCallbackWindowResult.clear(); std::stringstream aStream(pPayload); boost::property_tree::read_json(aStream, m_aCallbackWindowResult); std::string sAction = m_aCallbackWindowResult.get("action"); if (sAction == "hide") m_bWindowHidden = true; } break; case LOK_CALLBACK_CELL_FORMULA: { m_aCellFormula = aPayload; } break; } } }; } void DesktopLOKTest::testPaintPartTile() { // Load an impress doc of 2 slides. // ViewCallback aView1; // ViewCallback aView2; LibLODocument_Impl* pDocument = loadDoc("2slides.odp"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); // pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView1); int nView1 = pDocument->m_pDocumentClass->getView(pDocument); // Create a second view. pDocument->m_pDocumentClass->createView(pDocument); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); // pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView2); // Go to the second slide in the second view. pDocument->m_pDocumentClass->setPart(pDocument, 1); // Switch back to the first view and start typing. pDocument->m_pDocumentClass->setView(pDocument, nView1); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, awt::Key::TAB); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, awt::Key::TAB); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); // Call paintPartTile() to paint the second part (in whichever view it finds suitable for this). unsigned char pPixels[256 * 256 * 4]; pDocument->m_pDocumentClass->paintPartTile(pDocument, pPixels, 1, 256, 256, 0, 0, 256, 256); // Type again. Scheduler::ProcessEventsToIdle(); // aView1.m_bTilesInvalidated = false; pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); // This failed: paintPartTile() (as a side-effect) ended the text edit of // the first view, so there were no invalidations. //CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); } void DesktopLOKTest::testWriterCommentInsertCursor() { // Load a document and type a character into the body text of the second view. LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); ViewCallback aView1(pDocument); pDocument->m_pDocumentClass->createView(pDocument); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); ViewCallback aView2(pDocument); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'x', 0); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'x', 0); Scheduler::ProcessEventsToIdle(); tools::Rectangle aBodyCursor = aView2.m_aOwnCursor; // Now insert a comment and make sure that the comment's cursor is shown, // not the body text's one. aView1.m_aOwnCursor.SetEmpty(); const int nCtrlAltC = KEY_MOD1 + KEY_MOD2 + 512 + 'c' - 'a'; pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'c', nCtrlAltC); pDocument->m_pDocumentClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'c', nCtrlAltC); Scheduler::ProcessEventsToIdle(); // Wait for SfxBindings to actually update the state, which updated the // cursor as well. osl::Thread::wait(std::chrono::seconds(1)); Scheduler::ProcessEventsToIdle(); // This failed: the body cursor was shown right after inserting a comment. CPPUNIT_ASSERT(aView2.m_aOwnCursor.getX() > aBodyCursor.getX()); // This failed, the first view's cursor also jumped when the second view // inserted the comment. CPPUNIT_ASSERT(aView1.m_aOwnCursor.IsEmpty()); Scheduler::ProcessEventsToIdle(); } #if HAVE_MORE_FONTS void DesktopLOKTest::testGetFontSubset() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); OUString aFontName = rtl::Uri::encode( OUString("Liberation Sans"), rtl_UriCharClassRelSegment, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8 ); OString aCommand = OUStringToOString(".uno:FontSubset&name=" + aFontName, RTL_TEXTENCODING_UTF8); boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aCommand.getStr()); std::stringstream aStream(pJSON); boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT( !aTree.empty() ); CPPUNIT_ASSERT_EQUAL( std::string(".uno:FontSubset"), aTree.get_child("commandName").get_value() ); boost::property_tree::ptree aValues = aTree.get_child("commandValues"); CPPUNIT_ASSERT( !aValues.empty() ); free(pJSON); } #endif void DesktopLOKTest::testCommentsWriter() { // Disable tiled rendering for comments comphelper::LibreOfficeKit::setTiledAnnotations(false); LibLODocument_Impl* pDocument = loadDoc("comments.odt"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); long nWidth, nHeight; pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight); // Document width alongwith without sidebar comes to be < 13000 CPPUNIT_ASSERT( nWidth < 13000 ); // Can we get all the comments using .uno:ViewAnnotations command ? boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); // There are 3 comments in the document already CPPUNIT_ASSERT_EQUAL(static_cast(3), aTree.get_child("comments").size()); int nComment2Id = 0; // Check if all comment fields have valid data for (const auto& rComment : aTree.get_child("comments")) { CPPUNIT_ASSERT(rComment.second.get("id") > 0); CPPUNIT_ASSERT(!rComment.second.get("author").empty()); CPPUNIT_ASSERT(!rComment.second.get("text").empty()); // Has a valid iso 8601 date time string css::util::DateTime aDateTime; OUString aDateTimeString = OUString::createFromAscii(rComment.second.get("dateTime").c_str()); CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); // This comment has a marked text range if (rComment.second.get("text") == "Comment 2") { CPPUNIT_ASSERT(!rComment.second.get("textRange").empty()); nComment2Id = rComment.second.get("id"); } // This is a reply comment else if (rComment.second.get("text") == "Reply to Comment 2") { CPPUNIT_ASSERT_EQUAL(nComment2Id, rComment.second.get("parent")); } } comphelper::LibreOfficeKit::setTiledAnnotations(true); } void DesktopLOKTest::testCommentsCalc() { // Disable tiled rendering for comments comphelper::LibreOfficeKit::setTiledAnnotations(false); LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); // Can we get all the comments using .uno:ViewAnnotations command ? boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); // There are 2 comments in the document already CPPUNIT_ASSERT_EQUAL(static_cast(2), aTree.get_child("comments").size()); // Check if all comment fields have valid data int nIdx = 0; for (const auto& rComment : aTree.get_child("comments")) { switch(nIdx) { case 0: { CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get("tab")); CPPUNIT_ASSERT_EQUAL(std::string("Comment1"), rComment.second.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("7650, 3570, 1274, 254"), rComment.second.get("cellPos")); } break; case 1: { CPPUNIT_ASSERT_EQUAL(std::string("4"), rComment.second.get("tab")); CPPUNIT_ASSERT_EQUAL(std::string("Comment2"), rComment.second.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("8925, 4335, 1274, 254"), rComment.second.get("cellPos")); } break; } ++nIdx; } // We checked all the comments CPPUNIT_ASSERT_EQUAL(2, nIdx); comphelper::LibreOfficeKit::setTiledAnnotations(true); } void DesktopLOKTest::testCommentsImpress() { // Disable tiled rendering for comments comphelper::LibreOfficeKit::setTiledAnnotations(false); LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, nullptr); // Can we get all the comments using .uno:ViewAnnotations command ? boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); // There are 2 comments in the document already CPPUNIT_ASSERT_EQUAL(static_cast(2), aTree.get_child("comments").size()); // Check if all comment fields have valid data int nIdx = 0; for (const auto& rComment : aTree.get_child("comments")) { switch(nIdx) { case 0: { CPPUNIT_ASSERT(rComment.second.get("id") > 0); CPPUNIT_ASSERT_EQUAL(std::string("This is comment1"), rComment.second.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), rComment.second.get("author")); css::util::DateTime aDateTime; OUString aDateTimeString = OUString::createFromAscii(rComment.second.get("dateTime").c_str()); CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); } break; case 1: { CPPUNIT_ASSERT(rComment.second.get("id") > 0); CPPUNIT_ASSERT_EQUAL(std::string("This is comment2"), rComment.second.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), rComment.second.get("author")); css::util::DateTime aDateTime; OUString aDateTimeString = OUString::createFromAscii(rComment.second.get("dateTime").c_str()); CPPUNIT_ASSERT(utl::ISO8601parseDateTime(aDateTimeString, aDateTime)); } break; } ++nIdx; } // We checked all the comments CPPUNIT_ASSERT_EQUAL(2, nIdx); comphelper::LibreOfficeKit::setTiledAnnotations(true); } void DesktopLOKTest::testCommentsCallbacksWriter() { // Comments callback are emitted only if tiled annotations are off comphelper::LibreOfficeKit::setTiledAnnotations(false); LibLODocument_Impl* pDocument = loadDoc("comments.odt"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); ViewCallback aView1(pDocument); pDocument->m_pDocumentClass->createView(pDocument); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); ViewCallback aView2(pDocument); // Add a new comment OString aCommandArgs("{ \"Text\": { \"type\": \"string\", \"value\": \"Additional comment\" }, \"Author\": { \"type\": \"string\", \"value\": \"LOK User1\" } }"); pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation", aCommandArgs.getStr(), false); Scheduler::ProcessEventsToIdle(); // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get("action")); int nCommentId1 = aView1.m_aCommentCallbackResult.get("id"); // Reply to a comment just added aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment\" } }"; pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false); Scheduler::ProcessEventsToIdle(); // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get("parent")); CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get("parent")); CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("Reply comment"), aView2.m_aCommentCallbackResult.get("text")); int nCommentId2 = aView1.m_aCommentCallbackResult.get("id"); // Edit the previously added comment aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Edited comment\" } }"; pDocument->pClass->postUnoCommand(pDocument, ".uno:EditAnnotation", aCommandArgs.getStr(), false); Scheduler::ProcessEventsToIdle(); // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get("action")); // parent is unchanged still CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get("parent")); CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get("parent")); CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get("text")); // Delete the reply comment just added aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId2) + "\" } }"; pDocument->pClass->postUnoCommand(pDocument, ".uno:DeleteComment", aCommandArgs.getStr(), false); Scheduler::ProcessEventsToIdle(); // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(nCommentId2, aView1.m_aCommentCallbackResult.get("id")); CPPUNIT_ASSERT_EQUAL(nCommentId2, aView2.m_aCommentCallbackResult.get("id")); // Reply to nCommentId1 again aCommandArgs = "{ \"Id\": { \"type\": \"string\", \"value\": \"" + OString::number(nCommentId1) + "\" }, \"Text\": { \"type\": \"string\", \"value\": \"Reply comment again\" } }"; pDocument->pClass->postUnoCommand(pDocument, ".uno:ReplyComment", aCommandArgs.getStr(), false); Scheduler::ProcessEventsToIdle(); // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action and linked to its parent comment CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get("action")); CPPUNIT_ASSERT_EQUAL(nCommentId1, aView1.m_aCommentCallbackResult.get("parent")); CPPUNIT_ASSERT_EQUAL(nCommentId1, aView2.m_aCommentCallbackResult.get("parent")); CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView1.m_aCommentCallbackResult.get("text")); CPPUNIT_ASSERT_EQUAL(std::string("Reply comment again"), aView2.m_aCommentCallbackResult.get("text")); // .uno:ViewAnnotations returns total of 5 comments boost::property_tree::ptree aTree; char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewAnnotations"); std::stringstream aStream(pJSON); free(pJSON); CPPUNIT_ASSERT(!aStream.str().empty()); boost::property_tree::read_json(aStream, aTree); CPPUNIT_ASSERT_EQUAL(static_cast(5), aTree.get_child("comments").size()); } void DesktopLOKTest::testRunMacro() { LibLibreOffice_Impl aOffice; bool bGoodMacro, bNonExistentMacro; // Tools macros come pre-installed in system share/basic folder, bGoodMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, OString("macro:///Tools.Debug.ActivateReadOnlyFlag()").getStr()); CPPUNIT_ASSERT(bGoodMacro); bNonExistentMacro = aOffice.m_pOfficeClass->runMacro(&aOffice, OString("macro:///I.Am.Not(There)").getStr()); CPPUNIT_ASSERT(!bNonExistentMacro); } void DesktopLOKTest::testExtractParameter() { OUString aOptions("Language=de-DE"); OUString aValue = extractParameter(aOptions, "Language"); CPPUNIT_ASSERT_EQUAL(OUString("de-DE"), aValue); CPPUNIT_ASSERT_EQUAL(OUString(), aOptions); aOptions = "Language=en-US,Something"; aValue = extractParameter(aOptions, "Language"); CPPUNIT_ASSERT_EQUAL(OUString("en-US"), aValue); CPPUNIT_ASSERT_EQUAL(OUString("Something"), aOptions); aOptions = "SomethingElse,Language=cs-CZ"; aValue = extractParameter(aOptions, "Language"); CPPUNIT_ASSERT_EQUAL(OUString("cs-CZ"), aValue); CPPUNIT_ASSERT_EQUAL(OUString("SomethingElse"), aOptions); aOptions = "Something1,Language=hu-HU,Something2"; aValue = extractParameter(aOptions, "Language"); CPPUNIT_ASSERT_EQUAL(OUString("hu-HU"), aValue); CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2"), aOptions); aOptions = "Something1,Something2=blah,Something3"; aValue = extractParameter(aOptions, "Language"); CPPUNIT_ASSERT_EQUAL(OUString(), aValue); CPPUNIT_ASSERT_EQUAL(OUString("Something1,Something2=blah,Something3"), aOptions); } void DesktopLOKTest::readFileIntoByteVector(OUString const & sFilename, std::vector & rByteVector) { rByteVector.clear(); OUString aURL; createFileURL(sFilename, aURL); SvFileStream aStream(aURL, StreamMode::READ); rByteVector.resize(aStream.remainingSize()); aStream.ReadBytes(rByteVector.data(), aStream.remainingSize()); } void DesktopLOKTest::testGetSignatureState_Signed() { LibLODocument_Impl* pDocument = loadDoc("signed.odt"); Scheduler::ProcessEventsToIdle(); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); CPPUNIT_ASSERT_EQUAL(int(4), nState); std::vector aCertificate; { readFileIntoByteVector("rootCA.der", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("intermediateRootCA.der", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); CPPUNIT_ASSERT_EQUAL(int(1), nState); } void DesktopLOKTest::testGetSignatureState_NonSigned() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); Scheduler::ProcessEventsToIdle(); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); CPPUNIT_ASSERT_EQUAL(int(0), nState); } void DesktopLOKTest::testInsertCertificate_DER_ODT() { // Load the document, save it into a temp file and load that file again LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); utl::TempFile aTempFile; aTempFile.EnableKillingFile(); CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "odt", nullptr)); closeDoc(); mxComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument"); pDocument = new LibLODocument_Impl(mxComponent); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(mxComponent.is()); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); Scheduler::ProcessEventsToIdle(); std::vector aCertificate; std::vector aPrivateKey; { readFileIntoByteVector("rootCA.der", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("intermediateRootCA.der", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("certificate.der", aCertificate); readFileIntoByteVector("certificatePrivateKey.der", aPrivateKey); bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, aCertificate.data(), int(aCertificate.size()), aPrivateKey.data(), int(aPrivateKey.size())); CPPUNIT_ASSERT(bResult); } int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); CPPUNIT_ASSERT_EQUAL(int(1), nState); } void DesktopLOKTest::testInsertCertificate_PEM_ODT() { // Load the document, save it into a temp file and load that file again LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); utl::TempFile aTempFile; aTempFile.EnableKillingFile(); CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "odt", nullptr)); closeDoc(); mxComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument"); pDocument = new LibLODocument_Impl(mxComponent); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(mxComponent.is()); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); Scheduler::ProcessEventsToIdle(); std::vector aCertificate; std::vector aPrivateKey; { readFileIntoByteVector("test-cert-chain-1.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-chain-2.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-chain-3.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-signing.pem", aCertificate); readFileIntoByteVector("test-PK-signing.pem", aPrivateKey); bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, aCertificate.data(), int(aCertificate.size()), aPrivateKey.data(), int(aPrivateKey.size())); CPPUNIT_ASSERT(bResult); } int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); CPPUNIT_ASSERT_EQUAL(int(1), nState); } void DesktopLOKTest::testInsertCertificate_PEM_DOCX() { // Load the document, save it into a temp file and load that file again LibLODocument_Impl* pDocument = loadDoc("blank_text.docx"); utl::TempFile aTempFile; aTempFile.EnableKillingFile(); CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "docx", nullptr)); closeDoc(); mxComponent = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument"); pDocument = new LibLODocument_Impl(mxComponent); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(mxComponent.is()); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); Scheduler::ProcessEventsToIdle(); std::vector aCertificate; std::vector aPrivateKey; { readFileIntoByteVector("test-cert-chain-1.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-chain-2.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-chain-3.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-signing.pem", aCertificate); readFileIntoByteVector("test-PK-signing.pem", aPrivateKey); bool bResult = pDocument->m_pDocumentClass->insertCertificate(pDocument, aCertificate.data(), int(aCertificate.size()), aPrivateKey.data(), int(aPrivateKey.size())); CPPUNIT_ASSERT(bResult); } int nState = pDocument->m_pDocumentClass->getSignatureState(pDocument); CPPUNIT_ASSERT_EQUAL(int(5), nState); } void DesktopLOKTest::testSignDocument_PEM_PDF() { // Load the document, save it into a temp file and load that file again LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); utl::TempFile aTempFile; aTempFile.EnableKillingFile(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(mxComponent.is()); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); Scheduler::ProcessEventsToIdle(); std::vector aCertificate; std::vector aPrivateKey; { readFileIntoByteVector("test-cert-chain-1.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-chain-2.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } { readFileIntoByteVector("test-cert-chain-3.pem", aCertificate); bool bResult = pDocument->m_pDocumentClass->addCertificate( pDocument, aCertificate.data(), int(aCertificate.size())); CPPUNIT_ASSERT(bResult); } CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "pdf", nullptr)); closeDoc(); Scheduler::ProcessEventsToIdle(); readFileIntoByteVector("test-cert-signing.pem", aCertificate); readFileIntoByteVector("test-PK-signing.pem", aPrivateKey); LibLibreOffice_Impl aOffice; bool bResult = aOffice.m_pOfficeClass->signDocument(&aOffice, aTempFile.GetURL().toUtf8().getStr(), aCertificate.data(), int(aCertificate.size()), aPrivateKey.data(), int(aPrivateKey.size())); CPPUNIT_ASSERT(bResult); } void DesktopLOKTest::testTextSelectionHandles() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); OString aText("hello"); CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); // select the inserted text pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); Scheduler::ProcessEventsToIdle(); char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); free(pText); CPPUNIT_ASSERT_EQUAL(OString("1418, 1418, 0, 275"), m_aTextSelectionStart); CPPUNIT_ASSERT_EQUAL(OString("1898, 1418, 0, 275"), m_aTextSelectionEnd); // deselect & check m_aTextSelectionStart = ""; m_aTextSelectionEnd = ""; pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); Scheduler::ProcessEventsToIdle(); pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); CPPUNIT_ASSERT_EQUAL(OString(), OString(pText)); free(pText); CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionStart); CPPUNIT_ASSERT_EQUAL(OString(), m_aTextSelectionEnd); // select again; the positions of the selection handles have to be sent // again pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); Scheduler::ProcessEventsToIdle(); pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); free(pText); CPPUNIT_ASSERT_EQUAL(OString("1418, 1418, 0, 275"), m_aTextSelectionStart); CPPUNIT_ASSERT_EQUAL(OString("1898, 1418, 0, 275"), m_aTextSelectionEnd); } void DesktopLOKTest::testDialogPaste() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false); Scheduler::ProcessEventsToIdle(); SfxViewShell* pViewShell = SfxViewShell::Current(); pViewShell->GetViewFrame()->GetBindings().Update(); VclPtr pWindow(Application::GetActiveTopWindow()); CPPUNIT_ASSERT(pWindow); pDocument->pClass->postWindow(pDocument, pWindow->GetLOKWindowId(), LOK_WINDOW_PASTE, "{ \"MimeType\" : { \"type\" : \"string\", \"value\" : \"text/plain;charset=utf-8\" }, \"Data\" : { \"type\" : \"[]byte\", \"value\" : \"www.softwarelibre.org.bo\" } }"); Scheduler::ProcessEventsToIdle(); Control* pCtrlFocused = GetFocusControl(pWindow.get()); CPPUNIT_ASSERT(pCtrlFocused); CPPUNIT_ASSERT_EQUAL(WindowType::COMBOBOX, pCtrlFocused->GetType()); CPPUNIT_ASSERT_EQUAL(OUString("www.softwarelibre.org.bo"), pCtrlFocused->GetText()); static_cast(pWindow.get())->Close(); Scheduler::ProcessEventsToIdle(); } void DesktopLOKTest::testShowHideDialog() { LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); ViewCallback aView(pDocument); pDocument->pClass->postUnoCommand(pDocument, ".uno:HyperlinkDialog", nullptr, false); Scheduler::ProcessEventsToIdle(); VclPtr pWindow(Application::GetActiveTopWindow()); CPPUNIT_ASSERT(pWindow); aView.m_bWindowHidden = false; pWindow->Hide(); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(true, aView.m_bWindowHidden); static_cast(pWindow.get())->Close(); Scheduler::ProcessEventsToIdle(); } void DesktopLOKTest::testComplexSelection() { // Start with a blank text file and add contents. LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); static const OString aText("hello world"); // Certainly not complex. CPPUNIT_ASSERT_EQUAL(static_cast(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument)); // Paste text. CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8", aText.getStr(), aText.getLength())); // No selection. CPPUNIT_ASSERT_EQUAL(static_cast(LOK_SELTYPE_NONE), pDocument->pClass->getSelectionType(pDocument)); // Paste an image. OUString aFileURL; createFileURL("paste.jpg", aFileURL); std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr()); std::vector aImageContents((std::istreambuf_iterator(aImageStream)), std::istreambuf_iterator()); CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg", aImageContents.data(), aImageContents.size())); // Now select-all. pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll", nullptr, false); Scheduler::ProcessEventsToIdle(); // Export as plain text, we should get only the text part "hello". char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", nullptr); CPPUNIT_ASSERT(pText != nullptr); CPPUNIT_ASSERT_EQUAL(aText, OString(pText)); free(pText); // Export as rtf, we should also get the image. pText = pDocument->pClass->getTextSelection(pDocument, "text/rtf", nullptr); CPPUNIT_ASSERT(pText != nullptr); CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text. CPPUNIT_ASSERT(std::string(pText).find("pict{") != std::string::npos); // Must have the image as well. free(pText); // Export as html, we should also get the image. pText = pDocument->pClass->getTextSelection(pDocument, "text/html", nullptr); CPPUNIT_ASSERT(pText != nullptr); CPPUNIT_ASSERT(std::string(pText).find(aText.getStr()) != std::string::npos); // Must have the text. CPPUNIT_ASSERT(std::string(pText).find("(LOK_SELTYPE_COMPLEX), pDocument->pClass->getSelectionType(pDocument)); } void DesktopLOKTest::testCalcSaveAs() { comphelper::LibreOfficeKit::setActive(); LibLODocument_Impl* pDocument = loadDoc("sheets.ods"); CPPUNIT_ASSERT(pDocument); // Enter some text, but don't commit. pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'X', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 'X', 0); Scheduler::ProcessEventsToIdle(); // Save as a new file. OUString aNewFileUrl = "file:///tmp/saveas.ods"; pDocument->pClass->saveAs(pDocument, aNewFileUrl.toUtf8().getStr(), nullptr, nullptr); closeDoc(); // Load the new document and verify that the in-flight changes are saved. pDocument = loadDocUrl(aNewFileUrl, LOK_DOCTYPE_SPREADSHEET); CPPUNIT_ASSERT(pDocument); ViewCallback aView(pDocument); pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); pDocument->m_pDocumentClass->registerCallback(pDocument, &ViewCallback::callback, &aView); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, KEY_LEFT); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 0, KEY_LEFT); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(OString("X"), aView.m_aCellFormula); } void DesktopLOKTest::testSpellcheckerMultiView() { static const OUString aLangISO("en-US"); SvtSysLocaleOptions aSysLocaleOptions; aSysLocaleOptions.SetLocaleConfigString(aLangISO); aSysLocaleOptions.SetUILocaleConfigString(aLangISO); comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLangISO, true)); auto aSavedSettings = Application::GetSettings(); std::unique_ptr pResetter( new Resetter([&]() { Application::SetSettings(aSavedSettings); })); AllSettings aSettings(aSavedSettings); aSettings.SetLanguageTag(aLangISO, true); Application::SetSettings(aSettings); LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods", LOK_DOCTYPE_SPREADSHEET); pDocument->pClass->setViewLanguage(pDocument, 0, "en-US"); // For spellchecking. pDocument->pClass->initializeForRendering(pDocument, nullptr); pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'a', 0); pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, com::sun::star::awt::Key::ESCAPE); // Start spellchecking. pDocument->pClass->postUnoCommand(pDocument, ".uno:SpellDialog", nullptr, false); // Uncommenting this will result in a deadlock. // Because the language configuration above is not effective, and no // language is actually set, the spell-dialog finds no misspelled // words, and displays a message box, which must be dismissed to // continue. // Need to fix the language configuration issue to enable this. // Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); // Now create another view. const int nViewId = pDocument->m_pDocumentClass->createView(pDocument); CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument)); // And destroy it. pDocument->m_pDocumentClass->destroyView(pDocument, nViewId); // We should survive the destroyed view. CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument)); } void DesktopLOKTest::testControlState() { LibLODocument_Impl* pDocument = loadDoc("search.ods"); pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false); Scheduler::ProcessEventsToIdle(); boost::property_tree::ptree aState; SfxViewShell* pViewShell = SfxViewShell::Current(); pViewShell->GetViewFrame()->GetBindings().Update(); pViewShell->GetViewFrame()->GetBindings().QueryControlState(SID_ATTR_TRANSFORM_WIDTH, aState); CPPUNIT_ASSERT(!aState.empty()); } void DesktopLOKTest::testMetricField() { LibLODocument_Impl* pDocument = loadDoc("search.ods"); pDocument->pClass->postUnoCommand(pDocument, ".uno:StarShapes", nullptr, false); Scheduler::ProcessEventsToIdle(); SfxViewShell* pViewShell = SfxViewShell::Current(); CPPUNIT_ASSERT(pViewShell); SfxViewFrame* pViewFrame = pViewShell->GetViewFrame(); CPPUNIT_ASSERT(pViewFrame); SfxChildWindow* pSideBar = pViewFrame->GetChildWindow(SID_SIDEBAR); CPPUNIT_ASSERT(pSideBar); vcl::Window* pWin = pSideBar->GetWindow(); CPPUNIT_ASSERT(pWin); WindowUIObject aWinUI(pWin); std::unique_ptr pUIWin(aWinUI.get_child("selectwidth")); CPPUNIT_ASSERT(pUIWin.get()); StringMap aMap; aMap["VALUE"] = "75.06"; pUIWin->execute("VALUE", aMap); StringMap aRet = pUIWin->get_state(); CPPUNIT_ASSERT_EQUAL(aMap["VALUE"], aRet["Value"]); } namespace { constexpr size_t classOffset(int i) { return sizeof(static_cast(nullptr)->nSize) + i * sizeof(void*); } constexpr size_t documentClassOffset(int i) { return sizeof(static_cast(nullptr)->nSize) + i * sizeof(void*); } } void DesktopLOKTest::testABI() { // STABLE ABI, NEVER CHANGE (unless there's a very good reason, agreed by ESC, etc.) CPPUNIT_ASSERT_EQUAL(classOffset(0), offsetof(struct _LibreOfficeKitClass, destroy)); CPPUNIT_ASSERT_EQUAL(classOffset(1), offsetof(struct _LibreOfficeKitClass, documentLoad)); CPPUNIT_ASSERT_EQUAL(classOffset(2), offsetof(struct _LibreOfficeKitClass, getError)); CPPUNIT_ASSERT_EQUAL(classOffset(3), offsetof(struct _LibreOfficeKitClass, documentLoadWithOptions)); CPPUNIT_ASSERT_EQUAL(classOffset(4), offsetof(struct _LibreOfficeKitClass, freeError)); CPPUNIT_ASSERT_EQUAL(classOffset(5), offsetof(struct _LibreOfficeKitClass, registerCallback)); CPPUNIT_ASSERT_EQUAL(classOffset(6), offsetof(struct _LibreOfficeKitClass, getFilterTypes)); CPPUNIT_ASSERT_EQUAL(classOffset(7), offsetof(struct _LibreOfficeKitClass, setOptionalFeatures)); CPPUNIT_ASSERT_EQUAL(classOffset(8), offsetof(struct _LibreOfficeKitClass, setDocumentPassword)); CPPUNIT_ASSERT_EQUAL(classOffset(9), offsetof(struct _LibreOfficeKitClass, getVersionInfo)); CPPUNIT_ASSERT_EQUAL(classOffset(10), offsetof(struct _LibreOfficeKitClass, runMacro)); CPPUNIT_ASSERT_EQUAL(classOffset(11), offsetof(struct _LibreOfficeKitClass, signDocument)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs)); // Unstable ABI, but still think twice before changing this // Eg. can't you add your new member at the end of the struct instead of // in the middle? The thing you are changing - is it already part of some // release? CPPUNIT_ASSERT_EQUAL(documentClassOffset(2), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentType)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(3), offsetof(struct _LibreOfficeKitDocumentClass, getParts)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(4), offsetof(struct _LibreOfficeKitDocumentClass, getPartPageRectangles)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(5), offsetof(struct _LibreOfficeKitDocumentClass, getPart)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(6), offsetof(struct _LibreOfficeKitDocumentClass, setPart)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(7), offsetof(struct _LibreOfficeKitDocumentClass, getPartName)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(8), offsetof(struct _LibreOfficeKitDocumentClass, setPartMode)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(9), offsetof(struct _LibreOfficeKitDocumentClass, paintTile)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(10), offsetof(struct _LibreOfficeKitDocumentClass, getTileMode)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(11), offsetof(struct _LibreOfficeKitDocumentClass, getDocumentSize)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(12), offsetof(struct _LibreOfficeKitDocumentClass, initializeForRendering)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(13), offsetof(struct _LibreOfficeKitDocumentClass, registerCallback)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(14), offsetof(struct _LibreOfficeKitDocumentClass, postKeyEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(15), offsetof(struct _LibreOfficeKitDocumentClass, postMouseEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(16), offsetof(struct _LibreOfficeKitDocumentClass, postUnoCommand)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(17), offsetof(struct _LibreOfficeKitDocumentClass, setTextSelection)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(18), offsetof(struct _LibreOfficeKitDocumentClass, getTextSelection)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(19), offsetof(struct _LibreOfficeKitDocumentClass, paste)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(20), offsetof(struct _LibreOfficeKitDocumentClass, setGraphicSelection)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(21), offsetof(struct _LibreOfficeKitDocumentClass, resetSelection)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(22), offsetof(struct _LibreOfficeKitDocumentClass, getCommandValues)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(23), offsetof(struct _LibreOfficeKitDocumentClass, setClientZoom)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(24), offsetof(struct _LibreOfficeKitDocumentClass, setClientVisibleArea)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(25), offsetof(struct _LibreOfficeKitDocumentClass, createView)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(26), offsetof(struct _LibreOfficeKitDocumentClass, destroyView)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(27), offsetof(struct _LibreOfficeKitDocumentClass, setView)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(28), offsetof(struct _LibreOfficeKitDocumentClass, getView)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(29), offsetof(struct _LibreOfficeKitDocumentClass, getViewsCount)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(30), offsetof(struct _LibreOfficeKitDocumentClass, renderFont)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(31), offsetof(struct _LibreOfficeKitDocumentClass, getPartHash)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(32), offsetof(struct _LibreOfficeKitDocumentClass, paintPartTile)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(33), offsetof(struct _LibreOfficeKitDocumentClass, getViewIds)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(34), offsetof(struct _LibreOfficeKitDocumentClass, setOutlineState)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(35), offsetof(struct _LibreOfficeKitDocumentClass, paintWindow)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(36), offsetof(struct _LibreOfficeKitDocumentClass, postWindow)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(37), offsetof(struct _LibreOfficeKitDocumentClass, postWindowKeyEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(38), offsetof(struct _LibreOfficeKitDocumentClass, postWindowMouseEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(39), offsetof(struct _LibreOfficeKitDocumentClass, setViewLanguage)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(40), offsetof(struct _LibreOfficeKitDocumentClass, postWindowExtTextInputEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(41), offsetof(struct _LibreOfficeKitDocumentClass, getPartInfo)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(42), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowDPI)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(43), offsetof(struct _LibreOfficeKitDocumentClass, insertCertificate)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(44), offsetof(struct _LibreOfficeKitDocumentClass, addCertificate)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(45), offsetof(struct _LibreOfficeKitDocumentClass, getSignatureState)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(46), offsetof(struct _LibreOfficeKitDocumentClass, renderShapeSelection)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(47), offsetof(struct _LibreOfficeKitDocumentClass, postWindowGestureEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(48), offsetof(struct _LibreOfficeKitDocumentClass, createViewWithOptions)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(49), offsetof(struct _LibreOfficeKitDocumentClass, selectPart)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(50), offsetof(struct _LibreOfficeKitDocumentClass, moveSelectedParts)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(51), offsetof(struct _LibreOfficeKitDocumentClass, resizeWindow)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(52), offsetof(struct _LibreOfficeKitDocumentClass, getClipboard)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(53), offsetof(struct _LibreOfficeKitDocumentClass, setClipboard)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(54), offsetof(struct _LibreOfficeKitDocumentClass, getSelectionType)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(55), offsetof(struct _LibreOfficeKitDocumentClass, removeTextContext)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(56), offsetof(struct _LibreOfficeKitDocumentClass, sendDialogEvent)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(57), offsetof(struct _LibreOfficeKitDocumentClass, renderFontOrientation)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(58), offsetof(struct _LibreOfficeKitDocumentClass, paintWindowForView)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(59), offsetof(struct _LibreOfficeKitDocumentClass, completeFunction)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(60), offsetof(struct _LibreOfficeKitDocumentClass, setWindowTextSelection)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(61), offsetof(struct _LibreOfficeKitDocumentClass, sendFormFieldEvent)); // Extending is fine, update this, and add new assert for the offsetof the // new method CPPUNIT_ASSERT_EQUAL(documentClassOffset(62), sizeof(struct _LibreOfficeKitDocumentClass)); } CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest); CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */