This should be extended with checking that we receive "EMPTY" when there is no cursor shown - that would require e.g. simulating keyboard input to hide the cell cursor. Change-Id: Ia7be5ec3e158f21967b4c307ac10abb2b5e2a56a Reviewed-on: https://gerrit.libreoffice.org/19828 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Andrzej Hunt <andrzej@ahunt.org> Tested-by: Andrzej Hunt <andrzej@ahunt.org>
495 lines
16 KiB
C++
495 lines
16 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <com/sun/star/frame/Desktop.hpp>
|
|
#include <com/sun/star/lang/XComponent.hpp>
|
|
#include <com/sun/star/frame/XComponentLoader.hpp>
|
|
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <sfx2/objsh.hxx>
|
|
#include <sfx2/lokhelper.hxx>
|
|
#include <test/unoapi_test.hxx>
|
|
#include <comphelper/lok.hxx>
|
|
#include <comphelper/dispatchcommand.hxx>
|
|
#include <comphelper/propertysequence.hxx>
|
|
#include <osl/conditn.hxx>
|
|
#include <svl/srchitem.hxx>
|
|
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
|
|
#include <unotools/tempfile.hxx>
|
|
|
|
#include "../../inc/lib/init.hxx"
|
|
|
|
using namespace com::sun::star;
|
|
using namespace desktop;
|
|
|
|
class DesktopLOKTest : public UnoApiTest
|
|
{
|
|
public:
|
|
DesktopLOKTest() : UnoApiTest("/desktop/qa/data/")
|
|
{
|
|
}
|
|
|
|
virtual ~DesktopLOKTest()
|
|
{
|
|
}
|
|
|
|
virtual void setUp() override
|
|
{
|
|
UnoApiTest::setUp();
|
|
mxDesktop.set(frame::Desktop::create(comphelper::getComponentContext(getMultiServiceFactory())));
|
|
};
|
|
|
|
virtual void tearDown() override
|
|
{
|
|
closeDoc();
|
|
UnoApiTest::tearDown();
|
|
};
|
|
|
|
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 testPaintTile();
|
|
void testSaveAs();
|
|
void testSaveAsCalc();
|
|
void testPasteWriter();
|
|
void testRowColumnHeaders();
|
|
void testCellCursor();
|
|
void testCommandResult();
|
|
|
|
CPPUNIT_TEST_SUITE(DesktopLOKTest);
|
|
CPPUNIT_TEST(testGetStyles);
|
|
CPPUNIT_TEST(testGetFonts);
|
|
CPPUNIT_TEST(testCreateView);
|
|
CPPUNIT_TEST(testGetFilterTypes);
|
|
CPPUNIT_TEST(testGetPartPageRectangles);
|
|
CPPUNIT_TEST(testSearchCalc);
|
|
CPPUNIT_TEST(testPaintTile);
|
|
CPPUNIT_TEST(testSaveAs);
|
|
CPPUNIT_TEST(testSaveAsCalc);
|
|
CPPUNIT_TEST(testPasteWriter);
|
|
CPPUNIT_TEST(testRowColumnHeaders);
|
|
CPPUNIT_TEST(testCellCursor);
|
|
CPPUNIT_TEST(testCommandResult);
|
|
CPPUNIT_TEST_SUITE_END();
|
|
|
|
uno::Reference<lang::XComponent> mxComponent;
|
|
OString m_aTextSelection;
|
|
std::vector<OString> m_aSearchResultSelection;
|
|
std::vector<int> m_aSearchResultPart;
|
|
|
|
// for testCommandResult
|
|
osl::Condition m_aCommandResultCondition;
|
|
OString m_aCommandResult;
|
|
};
|
|
|
|
LibLODocument_Impl* DesktopLOKTest::loadDoc(const char* pName, LibreOfficeKitDocumentType eType)
|
|
{
|
|
OUString aFileURL;
|
|
createFileURL(OUString::createFromAscii(pName), aFileURL);
|
|
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;
|
|
default:
|
|
CPPUNIT_ASSERT(false);
|
|
break;
|
|
}
|
|
mxComponent = loadFromDesktop(aFileURL, aService);
|
|
if (!mxComponent.is())
|
|
{
|
|
CPPUNIT_ASSERT(false);
|
|
}
|
|
return new LibLODocument_Impl(mxComponent);
|
|
}
|
|
|
|
void DesktopLOKTest::closeDoc()
|
|
{
|
|
if (mxComponent.is())
|
|
{
|
|
closeDocument(mxComponent);
|
|
mxComponent.clear();
|
|
}
|
|
}
|
|
|
|
void DesktopLOKTest::callback(int nType, const char* pPayload, void* pData)
|
|
{
|
|
static_cast<DesktopLOKTest*>(pData)->callbackImpl(nType, pPayload);
|
|
}
|
|
|
|
void DesktopLOKTest::callbackImpl(int nType, const char* pPayload)
|
|
{
|
|
switch (nType)
|
|
{
|
|
case LOK_CALLBACK_TEXT_SELECTION:
|
|
{
|
|
m_aTextSelection = 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 (boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection"))
|
|
{
|
|
m_aSearchResultSelection.push_back(rValue.second.get<std::string>("rectangles").c_str());
|
|
m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str()));
|
|
}
|
|
}
|
|
break;
|
|
case LOK_CALLBACK_UNO_COMMAND_RESULT:
|
|
{
|
|
m_aCommandResult = pPayload;
|
|
m_aCommandResultCondition.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.size() > 0 );
|
|
CPPUNIT_ASSERT( aTree.get_child("commandName").get_value<std::string>() == ".uno:StyleApply" );
|
|
|
|
boost::property_tree::ptree aValues = aTree.get_child("commandValues");
|
|
CPPUNIT_ASSERT( aValues.size() > 0 );
|
|
for (const std::pair<std::string, boost::property_tree::ptree>& rPair : aValues)
|
|
{
|
|
CPPUNIT_ASSERT( rPair.second.size() > 0);
|
|
if (rPair.first != "CharacterStyles" &&
|
|
rPair.first != "ParagraphStyles" &&
|
|
rPair.first != "FrameStyles" &&
|
|
rPair.first != "PageStyles" &&
|
|
rPair.first != "NumberingStyles" &&
|
|
rPair.first != "CellStyles" &&
|
|
rPair.first != "ShapeStyles")
|
|
{
|
|
CPPUNIT_FAIL("Unknown style family: " + rPair.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DesktopLOKTest::testGetFonts()
|
|
{
|
|
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
|
|
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.size() > 0 );
|
|
CPPUNIT_ASSERT( aTree.get_child("commandName").get_value<std::string>() == ".uno:CharFontName" );
|
|
|
|
boost::property_tree::ptree aValues = aTree.get_child("commandValues");
|
|
CPPUNIT_ASSERT( aValues.size() > 0 );
|
|
for (const std::pair<std::string, boost::property_tree::ptree>& rPair : aValues)
|
|
{
|
|
// check that we have font sizes available for each font
|
|
CPPUNIT_ASSERT( rPair.second.size() > 0);
|
|
}
|
|
}
|
|
|
|
void DesktopLOKTest::testCreateView()
|
|
{
|
|
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
|
|
CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViews(pDocument));
|
|
|
|
int nId = pDocument->m_pDocumentClass->createView(pDocument);
|
|
CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViews(pDocument));
|
|
|
|
// Make sure the created view is the active one, then switch to the old
|
|
// one.
|
|
CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getView(pDocument));
|
|
pDocument->m_pDocumentClass->setView(pDocument, 0);
|
|
CPPUNIT_ASSERT_EQUAL(0, pDocument->m_pDocumentClass->getView(pDocument));
|
|
|
|
pDocument->m_pDocumentClass->destroyView(pDocument, nId);
|
|
CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViews(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<OUString> 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<size_t>(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.size() > 0);
|
|
CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text"), aTree.get_child("writer8").get_child("MediaType").get_value<std::string>());
|
|
free(pJSON);
|
|
}
|
|
|
|
void DesktopLOKTest::testSearchCalc()
|
|
{
|
|
LibLibreOffice_Impl aOffice;
|
|
comphelper::LibreOfficeKit::setActive();
|
|
LibLODocument_Impl* pDocument = loadDoc("search.ods");
|
|
pDocument->pClass->initializeForRendering(pDocument);
|
|
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this);
|
|
|
|
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
|
|
{
|
|
{"SearchItem.SearchString", uno::makeAny(OUString("foo"))},
|
|
{"SearchItem.Backward", uno::makeAny(false)},
|
|
{"SearchItem.Command", uno::makeAny(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))},
|
|
}));
|
|
comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues);
|
|
|
|
std::vector<OString> 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<size_t>(2), aSelections.size());
|
|
// Make sure that we get exactly as many rectangle lists as matches.
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), m_aSearchResultSelection.size());
|
|
// Result is on the first sheet.
|
|
CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]);
|
|
|
|
comphelper::LibreOfficeKit::setActive(false);
|
|
}
|
|
|
|
void DesktopLOKTest::testPaintTile()
|
|
{
|
|
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt");
|
|
int nCanvasWidth = 100;
|
|
int nCanvasHeight = 300;
|
|
std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4);
|
|
int nTilePosX = 0;
|
|
int nTilePosY = 0;
|
|
int nTileWidth = 1000;
|
|
int nTileHeight = 3000;
|
|
|
|
// This used to crash: painTile() 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", 0));
|
|
}
|
|
|
|
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", 0));
|
|
}
|
|
|
|
void DesktopLOKTest::testPasteWriter()
|
|
{
|
|
comphelper::LibreOfficeKit::setActive();
|
|
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", 0, false);
|
|
char* pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8", 0);
|
|
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()));
|
|
|
|
comphelper::LibreOfficeKit::setActive(false);
|
|
}
|
|
|
|
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");
|
|
boost::property_tree::ptree aTree;
|
|
char* pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:ViewRowColumnHeaders");
|
|
std::stringstream aStream(pJSON);
|
|
free(pJSON);
|
|
CPPUNIT_ASSERT(!aStream.str().empty());
|
|
|
|
boost::property_tree::read_json(aStream, aTree);
|
|
sal_Int32 nPrevious = 0;
|
|
for (boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows"))
|
|
{
|
|
sal_Int32 nSize = OString(rValue.second.get<std::string>("size").c_str()).toInt32();
|
|
CPPUNIT_ASSERT(nSize > 0);
|
|
OString aText(rValue.second.get<std::string>("text").c_str());
|
|
if (!nPrevious)
|
|
// This failed, as the first item did not contain the text of the first row.
|
|
CPPUNIT_ASSERT_EQUAL(OString("1"), aText);
|
|
else
|
|
{
|
|
// Make sure that size is absolute: the first two items have the same relative size.
|
|
CPPUNIT_ASSERT(nPrevious < nSize);
|
|
break;
|
|
}
|
|
nPrevious = nSize;
|
|
}
|
|
|
|
nPrevious = 0;
|
|
for (boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns"))
|
|
{
|
|
sal_Int32 nSize = OString(rValue.second.get<std::string>("size").c_str()).toInt32();
|
|
CPPUNIT_ASSERT(nSize > 0);
|
|
OString aText(rValue.second.get<std::string>("text").c_str());
|
|
if (!nPrevious)
|
|
CPPUNIT_ASSERT_EQUAL(OString("A"), aText);
|
|
else
|
|
{
|
|
CPPUNIT_ASSERT(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<std::string>("commandValues").c_str());
|
|
CPPUNIT_ASSERT_EQUAL(aRectangle, OString("0, 0, 1278, 254"));
|
|
}
|
|
|
|
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", 0, true);
|
|
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", 0, true);
|
|
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(aTree.get_child("commandName").get_value<std::string>(), std::string(".uno:Bold"));
|
|
CPPUNIT_ASSERT_EQUAL(aTree.get_child("success").get_value<bool>(), true);
|
|
}
|
|
|
|
CPPUNIT_TEST_SUITE_REGISTRATION(DesktopLOKTest);
|
|
|
|
CPPUNIT_PLUGIN_IMPLEMENT();
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|