1
0
Fork 0
libreoffice/sc/qa/unit/ucalc.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

7141 lines
273 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 "helper/debughelper.hxx"
#include "helper/qahelper.hxx"
#include <svl/asiancfg.hxx>
#include <simpleformulacalc.hxx>
#include <stringutil.hxx>
#include <scmatrix.hxx>
#include <scitems.hxx>
#include <reffind.hxx>
#include <clipparam.hxx>
#include <undoblk.hxx>
#include <undotab.hxx>
#include <attrib.hxx>
#include <dbdata.hxx>
#include <reftokenhelper.hxx>
#include <userdat.hxx>
#include <refdata.hxx>
#include <docfunc.hxx>
#include <funcdesc.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <columniterator.hxx>
#include <scopetools.hxx>
#include <dociter.hxx>
#include <edittextiterator.hxx>
#include <editutil.hxx>
#include <asciiopt.hxx>
#include <impex.hxx>
#include <globalnames.hxx>
#include <columnspanset.hxx>
#include <editable.hxx>
#include <tabprotection.hxx>
#include <undomanager.hxx>
#include <formula/IFunctionDescription.hxx>
#include <editeng/borderline.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/lineitem.hxx>
#include <o3tl/nonstaticstring.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdocirc.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdocapt.hxx>
#include <svl/numformat.hxx>
#include <svl/srchitem.hxx>
#include <svl/sharedstringpool.hxx>
#include <unotools/collatorwrapper.hxx>
#include <sfx2/IDocumentModelAccessor.hxx>
#include <sfx2/sfxsids.hrc>
class ScUndoPaste;
class ScUndoCut;
using ::std::cerr;
using ::std::endl;
namespace {
struct HoriIterCheck
{
SCCOL nCol;
SCROW nRow;
const char* pVal;
};
}
class Test : public ScUcalcTestBase
{
protected:
void checkPrecisionAsShown(OUString& rCode, double fValue, double fExpectedRoundVal);
/** Get a separate new ScDocShell with ScDocument that suits unit test needs. */
void getNewDocShell(ScDocShellRef& rDocShellRef);
bool checkHorizontalIterator(ScDocument& rDoc, const std::vector<std::vector<const char*>>& rData,
const HoriIterCheck* pChecks, size_t nCheckCount);
};
void Test::getNewDocShell( ScDocShellRef& rDocShellRef )
{
rDocShellRef = new ScDocShell(
SfxModelFlags::EMBEDDED_OBJECT |
SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
rDocShellRef->DoInitUnitTest();
}
CPPUNIT_TEST_FIXTURE(Test, testCollator)
{
sal_Int32 nRes = ScGlobal::GetCollator().compareString(u"A"_ustr, u"B"_ustr);
CPPUNIT_ASSERT_MESSAGE("these strings are supposed to be different!", nRes != 0);
}
CPPUNIT_TEST_FIXTURE(Test, testUndoBackgroundColorInsertRow)
{
m_pDoc->InsertTab(0, u"Table1"_ustr);
ScMarkData aMark(m_pDoc->GetSheetLimits());
// Set Values to B1, C2, D5
m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5
// Add patterns
ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
m_pDoc->ApplyPatternAreaTab(0, 3, m_pDoc->MaxCol(), 3, 0, aCellBlueColor);
// Insert a new row at row 3
ScRange aRowOne(0, 2, 0, m_pDoc->MaxCol(), 2, 0);
aMark.SetMarkArea(aRowOne);
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
rFunc.InsertCells(aRowOne, &aMark, INS_INSROWS_BEFORE, true, true);
// Check patterns
const SfxPoolItem* pItem = nullptr;
m_pDoc->GetPattern(ScAddress(1000, 4, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
// Undo the new row
m_pDoc->GetUndoManager()->Undo();
// Check patterns
// Failed if row 3 is not blue all the way through
pItem = nullptr;
m_pDoc->GetPattern(ScAddress(1000, 3, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testUndoBackgroundColorInsertColumn)
{
m_pDoc->InsertTab(0, u"Table1"_ustr);
ScMarkData aMark(m_pDoc->GetSheetLimits());
// Set Values to B1, C2, D5
m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5
// Add patterns
ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
m_pDoc->ApplyPatternAreaTab(3, 0, 3, m_pDoc->MaxRow(), 0, aCellBlueColor);
// Insert a new column at column 3
ScRange aColumnOne(2, 0, 0, 2, m_pDoc->MaxRow(), 0);
aMark.SetMarkArea(aColumnOne);
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
rFunc.InsertCells(aColumnOne, &aMark, INS_INSCOLS_BEFORE, true, true);
// Check patterns
const SfxPoolItem* pItem = nullptr;
m_pDoc->GetPattern(ScAddress(4, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
// Undo the new column
m_pDoc->GetUndoManager()->Undo();
// Check patterns
// Failed if column 3 is not blue all the way through
pItem = nullptr;
m_pDoc->GetPattern(ScAddress(3, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMergedHyperlink)
{
m_pDoc->InsertTab(0, u"Table1"_ustr);
m_pDoc->InitDrawLayer(m_xDocShell.get());
ScFieldEditEngine& pEE = m_pDoc->GetEditEngine();
pEE.SetTextCurrentDefaults(u"https://libreoffice.org/"_ustr);
m_pDoc->SetEditText(ScAddress(1, 0, 0), pEE.CreateTextObject()); // B1
m_pDoc->DoMergeContents(0, 0, 1, 0, 0); // A1:B1
CPPUNIT_ASSERT_EQUAL(CELLTYPE_EDIT, m_pDoc->GetCellType(ScAddress(0, 0, 0))); // A1
const EditTextObject* pEditObj = m_pDoc->GetEditText(ScAddress(0, 0, 0)); // A1
CPPUNIT_ASSERT(pEditObj);
CPPUNIT_ASSERT_EQUAL(u"https://libreoffice.org/"_ustr, pEditObj->GetText(0));
}
CPPUNIT_TEST_FIXTURE(Test, testSharedStringPool)
{
m_pDoc->InsertTab(0, u"foo"_ustr);
svl::SharedStringPool& rPool = m_pDoc->GetSharedStringPool();
size_t extraCount = rPool.getCount(); // internal items such as SharedString::getEmptyString()
size_t extraCountIgnoreCase = rPool.getCountIgnoreCase();
// Strings that are identical.
m_pDoc->SetString(ScAddress(0,0,0), o3tl::nonStaticString(u"Andy")); // A1
m_pDoc->SetString(ScAddress(0,1,0), o3tl::nonStaticString(u"Andy")); // A2
m_pDoc->SetString(ScAddress(0,2,0), o3tl::nonStaticString(u"Bruce")); // A3
m_pDoc->SetString(ScAddress(0,3,0), o3tl::nonStaticString(u"andy")); // A4
m_pDoc->SetString(ScAddress(0,4,0), o3tl::nonStaticString(u"BRUCE")); // A5
{
// These two shared string objects must go out of scope before the purge test.
svl::SharedString aSS1 = m_pDoc->GetSharedString(ScAddress(0,0,0));
svl::SharedString aSS2 = m_pDoc->GetSharedString(ScAddress(0,1,0));
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid shared string.", aSS1.isValid());
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid shared string.", aSS2.isValid());
CPPUNIT_ASSERT_EQUAL(aSS1.getData(), aSS2.getData());
aSS2 = m_pDoc->GetSharedString(ScAddress(0,2,0));
CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());
aSS2 = m_pDoc->GetSharedString(ScAddress(0,3,0));
CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());
aSS2 = m_pDoc->GetSharedString(ScAddress(0,4,0));
CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());
// A3 and A5 should differ but should be equal case-insensitively.
aSS1 = m_pDoc->GetSharedString(ScAddress(0,2,0));
aSS2 = m_pDoc->GetSharedString(ScAddress(0,4,0));
CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());
CPPUNIT_ASSERT_EQUAL_MESSAGE("They must be equal when cases are ignored.", aSS1.getDataIgnoreCase(), aSS2.getDataIgnoreCase());
// A2 and A4 should be equal when ignoring cases.
aSS1 = m_pDoc->GetSharedString(ScAddress(0,1,0));
aSS2 = m_pDoc->GetSharedString(ScAddress(0,3,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("They must be equal when cases are ignored.", aSS1.getDataIgnoreCase(), aSS2.getDataIgnoreCase());
}
// Check the string counts after purging. Purging shouldn't remove any strings in this case.
rPool.purge();
CPPUNIT_ASSERT_EQUAL(5+extraCount, rPool.getCount());
CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, rPool.getCountIgnoreCase());
// Clear A1
clearRange(m_pDoc, ScRange(ScAddress(0,0,0)));
// Clear A2
clearRange(m_pDoc, ScRange(ScAddress(0,1,0)));
// Clear A3
clearRange(m_pDoc, ScRange(ScAddress(0,2,0)));
// Clear A4
clearRange(m_pDoc, ScRange(ScAddress(0,3,0)));
// Clear A5 and the pool should be completely empty.
clearRange(m_pDoc, ScRange(ScAddress(0,4,0)));
rPool.purge();
CPPUNIT_ASSERT_EQUAL(extraCount, rPool.getCount());
CPPUNIT_ASSERT_EQUAL(extraCountIgnoreCase, rPool.getCountIgnoreCase());
// Now, compare string and edit text cells.
m_pDoc->SetString(ScAddress(0,0,0), "Andy and Bruce"); // A1 // [-loplugin:ostr]
ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
rEE.SetTextCurrentDefaults(u"Andy and Bruce"_ustr);
{
// Set 'Andy' bold.
SfxItemSet aItemSet = rEE.GetEmptyItemSet();
SvxWeightItem aWeight(WEIGHT_BOLD, EE_CHAR_WEIGHT);
aItemSet.Put(aWeight);
rEE.QuickSetAttribs(aItemSet, ESelection(0, 0, 0, 4));
}
{
// Set 'Bruce' italic.
SfxItemSet aItemSet = rEE.GetEmptyItemSet();
SvxPostureItem aItalic(ITALIC_NORMAL, EE_CHAR_ITALIC);
aItemSet.Put(aItalic);
rEE.QuickSetAttribs(aItemSet, ESelection(0, 9, 0, 14));
}
m_pDoc->SetEditText(ScAddress(1,0,0), rEE.CreateTextObject()); // B1
// These two should be equal.
svl::SharedString aSS1 = m_pDoc->GetSharedString(ScAddress(0,0,0));
svl::SharedString aSS2 = m_pDoc->GetSharedString(ScAddress(1,0,0));
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS1.isValid());
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS2.isValid());
CPPUNIT_ASSERT_EQUAL(aSS1.getData(), aSS2.getData());
rEE.SetTextCurrentDefaults(u"ANDY and BRUCE"_ustr);
m_pDoc->SetEditText(ScAddress(2,0,0), rEE.CreateTextObject()); // C1
aSS2 = m_pDoc->GetSharedString(ScAddress(2,0,0));
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS2.isValid());
CPPUNIT_ASSERT_MESSAGE("These two should be different when cases are considered.", aSS1.getData() != aSS2.getData());
// But they should be considered equal when cases are ignored.
aSS1 = m_pDoc->GetSharedString(ScAddress(0,0,0));
aSS2 = m_pDoc->GetSharedString(ScAddress(2,0,0));
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS1.isValid());
CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS2.isValid());
CPPUNIT_ASSERT_EQUAL(aSS1.getDataIgnoreCase(), aSS2.getDataIgnoreCase());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testBackgroundColorDeleteColumn)
{
m_pDoc->InsertTab(0, u"Table1"_ustr);
ScMarkData aMark(m_pDoc->GetSheetLimits());
// Set Values to B1, C2, D5
m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5
// Add patterns
ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
m_pDoc->ApplyPatternAreaTab(3, 0, 3, m_pDoc->MaxRow(), 0, aCellBlueColor);
// Delete column 10
m_pDoc->DeleteCol(ScRange(9,0,0,9,m_pDoc->MaxRow(),0));
// Check patterns
const SfxPoolItem* pItem = nullptr;
m_pDoc->GetPattern(ScAddress(3, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
// Delete column 2
m_pDoc->DeleteCol(ScRange(1,0,0,1,m_pDoc->MaxRow(),0));
// Check patterns
pItem = nullptr;
m_pDoc->GetPattern(ScAddress(2, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testBackgroundColorDeleteRow)
{
m_pDoc->InsertTab(0, u"Table1"_ustr);
ScMarkData aMark(m_pDoc->GetSheetLimits());
// Set Values to B1, C2, D5
m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5
// Add patterns
ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
m_pDoc->ApplyPatternAreaTab(0, 3, m_pDoc->MaxCol(), 3, 0, aCellBlueColor);
// Delete row 10
m_pDoc->DeleteRow(ScRange(0,9,0,m_pDoc->MaxCol(),9,0));
// Check patterns
const SfxPoolItem* pItem = nullptr;
m_pDoc->GetPattern(ScAddress(1000, 3, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
// Delete row 2
m_pDoc->DeleteRow(ScRange(0,1,0,m_pDoc->MaxCol(),1,0));
// Check patterns
pItem = nullptr;
m_pDoc->GetPattern(ScAddress(1000, 2, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
CPPUNIT_ASSERT(pItem);
CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testSharedStringPoolUndoDoc)
{
struct
{
bool check( const ScDocument& rSrcDoc, ScDocument& rCopyDoc )
{
// Copy A1:A4 to the undo document.
for (SCROW i = 0; i <= 4; ++i)
{
ScAddress aPos(0,i,0);
rCopyDoc.SetString(aPos, rSrcDoc.GetString(aPos));
}
// String values in A1:A4 should have identical hash.
for (SCROW i = 0; i <= 4; ++i)
{
ScAddress aPos(0,i,0);
svl::SharedString aSS1 = rSrcDoc.GetSharedString(aPos);
svl::SharedString aSS2 = rCopyDoc.GetSharedString(aPos);
if (aSS1.getDataIgnoreCase() != aSS2.getDataIgnoreCase())
{
cerr << "String hash values are not equal at row " << (i+1)
<< " for string '" << aSS1.getString() << "'" << endl;
return false;
}
}
return true;
}
} aTest;
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(0,0,0), u"Header"_ustr);
m_pDoc->SetString(ScAddress(0,1,0), u"A1"_ustr);
m_pDoc->SetString(ScAddress(0,2,0), u"A2"_ustr);
m_pDoc->SetString(ScAddress(0,3,0), u"A3"_ustr);
ScDocument aUndoDoc(SCDOCMODE_UNDO);
aUndoDoc.InitUndo(*m_pDoc, 0, 0);
bool bSuccess = aTest.check(*m_pDoc, aUndoDoc);
CPPUNIT_ASSERT_MESSAGE("Check failed with undo document.", bSuccess);
// Test the clip document as well.
ScDocument aClipDoc(SCDOCMODE_CLIP);
aClipDoc.ResetClip(m_pDoc, static_cast<SCTAB>(0));
bSuccess = aTest.check(*m_pDoc, aClipDoc);
CPPUNIT_ASSERT_MESSAGE("Check failed with clip document.", bSuccess);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testRangeList)
{
m_pDoc->InsertTab(0, u"foo"_ustr);
ScRangeList aRL;
aRL.push_back(ScRange(1,1,0,3,10,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("List should have one range.", size_t(1), aRL.size());
const ScRange* p = &aRL[0];
CPPUNIT_ASSERT_MESSAGE("Failed to get the range object.", p);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong range.", ScAddress(1,1,0), p->aStart);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong range.", ScAddress(3,10,0), p->aEnd);
// TODO: Add more tests here.
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMarkData)
{
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
// Empty mark. Nothing is selected.
std::vector<sc::ColRowSpan> aSpans = aMarkData.GetMarkedRowSpans();
CPPUNIT_ASSERT_MESSAGE("Span should be empty.", aSpans.empty());
aSpans = aMarkData.GetMarkedColSpans();
CPPUNIT_ASSERT_MESSAGE("Span should be empty.", aSpans.empty());
// Select B3:F7.
aMarkData.SetMarkArea(ScRange(1,2,0,5,6,0));
aSpans = aMarkData.GetMarkedRowSpans();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected row span.", size_t(1), aSpans.size());
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(2), aSpans[0].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(6), aSpans[0].mnEnd);
aSpans = aMarkData.GetMarkedColSpans();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected column span.", size_t(1), aSpans.size());
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(1), aSpans[0].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(5), aSpans[0].mnEnd);
// Select A11:B13.
aMarkData.SetMultiMarkArea(ScRange(0,10,0,1,12,0));
aSpans = aMarkData.GetMarkedRowSpans();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be 2 selected row spans.", size_t(2), aSpans.size());
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(2), aSpans[0].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(6), aSpans[0].mnEnd);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(10), aSpans[1].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(12), aSpans[1].mnEnd);
aSpans = aMarkData.GetMarkedColSpans();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected column span.", size_t(1), aSpans.size());
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(0), aSpans[0].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(5), aSpans[0].mnEnd);
// Select C8:C10.
aMarkData.SetMultiMarkArea(ScRange(2,7,0,2,9,0));
aSpans = aMarkData.GetMarkedRowSpans();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected row span.", size_t(1), aSpans.size());
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(2), aSpans[0].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(12), aSpans[0].mnEnd);
aSpans = aMarkData.GetMarkedColSpans();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected column span.", size_t(1), aSpans.size());
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(0), aSpans[0].mnStart);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(5), aSpans[0].mnEnd);
}
CPPUNIT_TEST_FIXTURE(Test, testInput)
{
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
m_pDoc->InsertTab (0, u"foo"_ustr));
OUString test;
m_pDoc->SetString(0, 0, 0, u"'10.5"_ustr);
test = m_pDoc->GetString(0, 0, 0);
bool bTest = test == "10.5";
CPPUNIT_ASSERT_MESSAGE("String number should have the first apostrophe stripped.", bTest);
m_pDoc->SetString(0, 0, 0, u"'apple'"_ustr);
test = m_pDoc->GetString(0, 0, 0);
bTest = test == "apple'";
CPPUNIT_ASSERT_MESSAGE("Text content should have the first apostrophe stripped.", bTest);
// Customized string handling policy.
ScSetStringParam aParam;
aParam.setTextInput();
m_pDoc->SetString(0, 0, 0, u"000123"_ustr, &aParam);
test = m_pDoc->GetString(0, 0, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Text content should have been treated as string, not number.", u"000123"_ustr, test);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testColumnIterator) // tdf#118620
{
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
m_pDoc->InsertTab (0, u"foo"_ustr));
m_pDoc->SetString(0, 0, 0, u"'10.5"_ustr);
m_pDoc->SetString(0, m_pDoc->MaxRow()-5, 0, u"42.0"_ustr);
std::optional<sc::ColumnIterator> it = m_pDoc->GetColumnIterator(0, 0, m_pDoc->MaxRow() - 10, m_pDoc->MaxRow());
while (it->hasCell())
{
it->getCell();
it->next();
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf66613)
{
// Create different print ranges and col/row repetitions for two tabs
const SCTAB nFirstTab = 0;
CPPUNIT_ASSERT(m_pDoc->InsertTab(nFirstTab, u"FirstPrintRange"_ustr));
ScRange aFirstPrintRange(0, 0, nFirstTab, 2, 2, nFirstTab);
m_pDoc->AddPrintRange(nFirstTab, aFirstPrintRange);
ScRange aFirstRepeatColRange(0, 0, nFirstTab, 0, 0, nFirstTab);
m_pDoc->SetRepeatColRange(nFirstTab, aFirstRepeatColRange);
ScRange aFirstRepeatRowRange(1, 1, nFirstTab, 1, 1, nFirstTab);
m_pDoc->SetRepeatRowRange(nFirstTab, aFirstRepeatRowRange);
const SCTAB nSecondTab = 1;
CPPUNIT_ASSERT(m_pDoc->InsertTab(nSecondTab, u"SecondPrintRange"_ustr));
ScRange aSecondPrintRange(0, 0, nSecondTab, 3, 3, nSecondTab);
m_pDoc->AddPrintRange(nSecondTab, aSecondPrintRange);
ScRange aSecondRepeatColRange(1, 1, nSecondTab, 1, 1, nSecondTab);
m_pDoc->SetRepeatColRange(nSecondTab, aSecondRepeatColRange);
ScRange aSecondRepeatRowRange(2, 2, nSecondTab, 2, 2, nSecondTab);
m_pDoc->SetRepeatRowRange(nSecondTab, aSecondRepeatRowRange);
// Transfer generated tabs to a new document with different order
ScDocument aScDocument;
aScDocument.TransferTab(*m_pDoc, nSecondTab, nFirstTab);
aScDocument.TransferTab(*m_pDoc, nFirstTab, nSecondTab);
// Check the number of print ranges in both documents
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), m_pDoc->GetPrintRangeCount(nFirstTab));
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), m_pDoc->GetPrintRangeCount(nSecondTab));
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aScDocument.GetPrintRangeCount(nFirstTab));
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aScDocument.GetPrintRangeCount(nSecondTab));
// Check the print ranges and col/row repetitions in both documents
CPPUNIT_ASSERT_EQUAL(aFirstPrintRange, *m_pDoc->GetPrintRange(nFirstTab, 0));
CPPUNIT_ASSERT_EQUAL(aFirstRepeatColRange, *m_pDoc->GetRepeatColRange(nFirstTab));
CPPUNIT_ASSERT_EQUAL(aFirstRepeatRowRange, *m_pDoc->GetRepeatRowRange(nFirstTab));
CPPUNIT_ASSERT_EQUAL(aSecondPrintRange, *m_pDoc->GetPrintRange(nSecondTab, 0));
CPPUNIT_ASSERT_EQUAL(aSecondRepeatColRange, *m_pDoc->GetRepeatColRange(nSecondTab));
CPPUNIT_ASSERT_EQUAL(aSecondRepeatRowRange, *m_pDoc->GetRepeatRowRange(nSecondTab));
// Tabs have to be adjusted since the order of the tabs is inverted in the new document
std::vector<ScRange*> aScRanges
= { &aFirstPrintRange, &aFirstRepeatColRange, &aFirstRepeatRowRange,
&aSecondPrintRange, &aSecondRepeatColRange, &aSecondRepeatRowRange };
for (size_t i = 0; i < aScRanges.size(); i++)
{
const SCTAB nTab = i >= 3 ? nFirstTab : nSecondTab;
aScRanges[i]->aStart.SetTab(nTab);
aScRanges[i]->aEnd.SetTab(nTab);
}
// Without the fix in place, no print ranges and col/row repetitions would be present
CPPUNIT_ASSERT_EQUAL(aFirstPrintRange, *aScDocument.GetPrintRange(nSecondTab, 0));
CPPUNIT_ASSERT_EQUAL(aFirstRepeatColRange, *aScDocument.GetRepeatColRange(nSecondTab));
CPPUNIT_ASSERT_EQUAL(aFirstRepeatRowRange, *aScDocument.GetRepeatRowRange(nSecondTab));
CPPUNIT_ASSERT_EQUAL(aSecondPrintRange, *aScDocument.GetPrintRange(nFirstTab, 0));
CPPUNIT_ASSERT_EQUAL(aSecondRepeatColRange, *aScDocument.GetRepeatColRange(nFirstTab));
CPPUNIT_ASSERT_EQUAL(aSecondRepeatRowRange, *aScDocument.GetRepeatRowRange(nFirstTab));
m_pDoc->DeleteTab(nFirstTab);
m_pDoc->DeleteTab(nSecondTab);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf113027)
{
// Insert some sheets including a whitespace in their name and switch the grammar to R1C1
CPPUNIT_ASSERT(m_pDoc->InsertTab(0, u"Sheet 1"_ustr));
CPPUNIT_ASSERT(m_pDoc->InsertTab(1, u"Sheet 2"_ustr));
FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
// Add a formula containing a remote reference, i.e., to another sheet
const ScAddress aScAddress(0, 0, 0);
static constexpr OUString aFormula = u"='Sheet 2'!RC"_ustr;
m_pDoc->SetString(aScAddress, aFormula);
// Switch from relative to absolute cell reference
ScRefFinder aFinder(aFormula, aScAddress, *m_pDoc, m_pDoc->GetAddressConvention());
aFinder.ToggleRel(0, aFormula.getLength());
// Without the fix in place, this test would have failed with
// - Expected: ='Sheet 2'!R1C1
// - Actual : ='Sheet 2'!RC
// i.e. the cell reference was not changed from relative to absolute
CPPUNIT_ASSERT_EQUAL(u"='Sheet 2'!R1C1"_ustr, aFinder.GetText());
m_pDoc->DeleteTab(0);
m_pDoc->DeleteTab(1);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf90698)
{
CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
m_pDoc->SetString(ScAddress(0,0,0), u"=(1;2)"_ustr);
// Without the fix in place, this would have failed with
// - Expected: =(1;2)
// - Actual : =(1~2)
OUString aFormula = m_pDoc->GetFormula(0,0,0);
CPPUNIT_ASSERT_EQUAL(u"=(1;2)"_ustr, aFormula);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf114406)
{
CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
m_pDoc->SetString(ScAddress(0,0,0), u"5"_ustr);
m_pDoc->SetString(ScAddress(1,0,0), u"=A1/100%"_ustr);
// Without the fix in place, this would have failed with
// - Expected: =A1/100%
// - Actual : =A1/1
OUString aFormula = m_pDoc->GetFormula(1,0,0);
CPPUNIT_ASSERT_EQUAL(u"=A1/100%"_ustr, aFormula);
CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,0,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf93951)
{
CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
m_pDoc->SetString(ScAddress(0,0,0), u"=2*§*2"_ustr);
OUString aFormula = m_pDoc->GetFormula(0,0,0);
// Without the fix in place, this test would have failed with
// - Expected: =2*§*2
// - Actual : =2*
CPPUNIT_ASSERT_EQUAL(u"=2*§*2"_ustr, aFormula);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf134490)
{
CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
m_pDoc->SetString(ScAddress(0,0,0), u"--1"_ustr);
m_pDoc->SetString(ScAddress(0,1,0), u"---1"_ustr);
m_pDoc->SetString(ScAddress(0,2,0), u"+-1"_ustr);
m_pDoc->SetString(ScAddress(0,3,0), u"+--1"_ustr);
// Without the fix in place, this test would have failed with
// - Expected: --1
// - Actual : -1
CPPUNIT_ASSERT_EQUAL(u"--1"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(u"---1"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(u"+-1"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));
CPPUNIT_ASSERT_EQUAL(u"+--1"_ustr, m_pDoc->GetString(ScAddress(0,3,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf135249)
{
CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
m_pDoc->SetString(ScAddress(0,0,0), u"1:60"_ustr);
m_pDoc->SetString(ScAddress(0,1,0), u"1:123"_ustr);
m_pDoc->SetString(ScAddress(0,2,0), u"1:1:123"_ustr);
m_pDoc->SetString(ScAddress(0,3,0), u"0:123"_ustr);
m_pDoc->SetString(ScAddress(0,4,0), u"0:0:123"_ustr);
m_pDoc->SetString(ScAddress(0,5,0), u"0:123:59"_ustr);
// These are not valid duration inputs
CPPUNIT_ASSERT_EQUAL(u"1:60"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(u"1:123"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(u"1:1:123"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));
// These are valid duration inputs
// Without the fix in place, this test would have failed with
// - Expected: 02:03:00 AM
// - Actual : 0:123
CPPUNIT_ASSERT_EQUAL(u"02:03:00 AM"_ustr, m_pDoc->GetString(ScAddress(0,3,0)));
CPPUNIT_ASSERT_EQUAL(u"12:02:03 AM"_ustr, m_pDoc->GetString(ScAddress(0,4,0)));
CPPUNIT_ASSERT_EQUAL(u"02:03:59 AM"_ustr, m_pDoc->GetString(ScAddress(0,5,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testDocStatistics)
{
SCTAB nStartTabs = m_pDoc->GetTableCount();
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to increment sheet count.",
static_cast<SCTAB>(nStartTabs+1), m_pDoc->GetTableCount());
m_pDoc->InsertTab(1, u"Sheet2"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to increment sheet count.",
static_cast<SCTAB>(nStartTabs+2), m_pDoc->GetTableCount());
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(0), m_pDoc->GetCellCount());
m_pDoc->SetValue(ScAddress(0,0,0), 2.0);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetCellCount());
m_pDoc->SetValue(ScAddress(2,2,0), 2.5);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(2), m_pDoc->GetCellCount());
m_pDoc->SetString(ScAddress(1,1,1), u"Test"_ustr);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(3), m_pDoc->GetCellCount());
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(0), m_pDoc->GetFormulaGroupCount());
m_pDoc->SetString(ScAddress(3,0,1), u"=A1"_ustr);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetFormulaGroupCount());
m_pDoc->SetString(ScAddress(3,1,1), u"=A2"_ustr);
m_pDoc->SetString(ScAddress(3,2,1), u"=A3"_ustr);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetFormulaGroupCount());
m_pDoc->SetString(ScAddress(3,3,1), u"=A5"_ustr);
m_pDoc->SetString(ScAddress(3,4,1), u"=A6"_ustr);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(2), m_pDoc->GetFormulaGroupCount());
m_pDoc->SetString(ScAddress(3,1,1), u"=A3"_ustr);
CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(4), m_pDoc->GetFormulaGroupCount());
m_pDoc->DeleteTab(1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to decrement sheet count.",
static_cast<SCTAB>(nStartTabs+1), m_pDoc->GetTableCount());
m_pDoc->DeleteTab(0); // This may fail in case there is only one sheet in the document.
}
CPPUNIT_TEST_FIXTURE(Test, testRowForHeight)
{
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->SetRowHeightRange( 0, 9, 0, 100);
m_pDoc->SetRowHeightRange(10, 19, 0, 200);
m_pDoc->SetRowHeightRange(20, 29, 0, 300);
// Hide some rows.
m_pDoc->SetRowHidden(3, 5, 0, true);
m_pDoc->SetRowHidden(8, 12, 0, true);
struct Check
{
tools::Long nHeight;
SCROW nRow;
};
std::vector<Check> aChecks = {
{ -2000, 0 },
{ -1000, 0 },
{ -1, 0 },
{ 0, 0 }, // row 0 begins
{ 1, 0 },
{ 99, 0 }, // row 0 ends
{ 100, 1 }, // row 1 begins
{ 101, 1 },
{ 120, 1 },
{ 199, 1 }, // row 1 ends
{ 200, 2 }, // row 2 begins
{ 201, 2 },
{ 299, 2 }, // row 2 ends
{ 300, 6 }, // row 6 begins, because 3-5 are hidden
{ 330, 6 },
{ 399, 6 }, // row 6 ends
{ 400, 7 }, // row 7 begins
{ 401, 7 },
{ 420, 7 },
{ 499, 7 }, // row 7 ends
{ 500, 13 }, // row 13 begins, because 8-12 are hidden
{ 501, 13 },
{ 599, 13 },
{ 600, 13 },
{ 699, 13 }, // row 13 ends (row 13 is 200 pixels high)
{ 700, 14 }, // row 14 begins
{ 780, 14 },
{ 899, 14 }, // row 14 ends (row 14 is 200 pixels high)
{ 900, 15 }, // row 15 begins
{ 1860, 19 },
{ 4020, 27 },
};
for (const Check& rCheck : aChecks)
{
SCROW nRow = m_pDoc->GetRowForHeight(0, rCheck.nHeight);
OString sMessage = "for height " + OString::number(rCheck.nHeight) + " we expect row " + OString::number(rCheck.nRow);
CPPUNIT_ASSERT_EQUAL_MESSAGE(sMessage.getStr(), rCheck.nRow, nRow);
}
}
CPPUNIT_TEST_FIXTURE(Test, testDataEntries)
{
/**
* The 'data entries' data is a list of strings used for suggestions as
* the user types in new cell value.
*/
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(0,5,0), u"Andy"_ustr);
m_pDoc->SetString(ScAddress(0,6,0), u"Bruce"_ustr);
m_pDoc->SetString(ScAddress(0,7,0), u"Charlie"_ustr);
m_pDoc->SetString(ScAddress(0,10,0), u"Andy"_ustr);
std::vector<ScTypedStrData> aEntries;
m_pDoc->GetDataEntries(0, 0, 0, aEntries); // Try at the very top.
// Entries are supposed to be sorted in ascending order, and are all unique.
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aEntries.size());
std::vector<ScTypedStrData>::const_iterator it = aEntries.begin();
CPPUNIT_ASSERT_EQUAL(u"Andy"_ustr, it->GetString());
++it;
CPPUNIT_ASSERT_EQUAL(u"Bruce"_ustr, it->GetString());
++it;
CPPUNIT_ASSERT_EQUAL(u"Charlie"_ustr, it->GetString());
++it;
CPPUNIT_ASSERT_MESSAGE("The entries should have ended here.", bool(it == aEntries.end()));
aEntries.clear();
m_pDoc->GetDataEntries(0, m_pDoc->MaxRow(), 0, aEntries); // Try at the very bottom.
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aEntries.size());
// Make sure we get the same set of suggestions.
it = aEntries.begin();
CPPUNIT_ASSERT_EQUAL(u"Andy"_ustr, it->GetString());
++it;
CPPUNIT_ASSERT_EQUAL(u"Bruce"_ustr, it->GetString());
++it;
CPPUNIT_ASSERT_EQUAL(u"Charlie"_ustr, it->GetString());
++it;
CPPUNIT_ASSERT_MESSAGE("The entries should have ended here.", bool(it == aEntries.end()));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testSelectionFunction)
{
/**
* Selection function is responsible for displaying quick calculation
* results in the status bar.
*/
m_pDoc->InsertTab(0, u"Test"_ustr);
// Insert values into B2:B4.
m_pDoc->SetString(ScAddress(1,1,0), u"=1"_ustr); // formula
m_pDoc->SetValue(ScAddress(1,2,0), 2.0);
m_pDoc->SetValue(ScAddress(1,3,0), 3.0);
// Insert strings into B5:B8.
m_pDoc->SetString(ScAddress(1,4,0), u"A"_ustr);
m_pDoc->SetString(ScAddress(1,5,0), u"B"_ustr);
m_pDoc->SetString(ScAddress(1,6,0), u"=\"C\""_ustr); // formula
m_pDoc->SetString(ScAddress(1,7,0), u"D"_ustr);
// Insert values into D2:D4.
m_pDoc->SetValue(ScAddress(3,1,0), 4.0);
m_pDoc->SetValue(ScAddress(3,2,0), 5.0);
m_pDoc->SetValue(ScAddress(3,3,0), 6.0);
// Insert edit text into D5.
ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
rEE.SetTextCurrentDefaults(u"Rich Text"_ustr);
m_pDoc->SetEditText(ScAddress(3,4,0), rEE.CreateTextObject());
// Insert Another string into D6.
m_pDoc->SetString(ScAddress(3,5,0), u"E"_ustr);
// Select B2:B8 & D2:D8 disjoint region.
ScRangeList aRanges;
aRanges.push_back(ScRange(1,1,0,1,7,0)); // B2:B8
aRanges.push_back(ScRange(3,1,0,3,7,0)); // D2:D8
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.MarkFromRangeList(aRanges, true);
struct Check
{
ScSubTotalFunc meFunc;
double mfExpected;
};
{
static const Check aChecks[] =
{
{ SUBTOTAL_FUNC_AVE, 3.5 },
{ SUBTOTAL_FUNC_CNT2, 12.0 },
{ SUBTOTAL_FUNC_CNT, 6.0 },
{ SUBTOTAL_FUNC_MAX, 6.0 },
{ SUBTOTAL_FUNC_MIN, 1.0 },
{ SUBTOTAL_FUNC_SUM, 21.0 },
{ SUBTOTAL_FUNC_SELECTION_COUNT, 14.0 }
};
for (const auto& rCheck : aChecks)
{
double fRes = 0.0;
bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, ScAddress(), aMark, fRes);
CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
}
}
// Hide rows 4 and 6 and check the results again.
m_pDoc->SetRowHidden(3, 3, 0, true);
m_pDoc->SetRowHidden(5, 5, 0, true);
CPPUNIT_ASSERT_MESSAGE("This row should be hidden.", m_pDoc->RowHidden(3, 0));
CPPUNIT_ASSERT_MESSAGE("This row should be hidden.", m_pDoc->RowHidden(5, 0));
{
static const Check aChecks[] =
{
{ SUBTOTAL_FUNC_AVE, 3.0 },
{ SUBTOTAL_FUNC_CNT2, 8.0 },
{ SUBTOTAL_FUNC_CNT, 4.0 },
{ SUBTOTAL_FUNC_MAX, 5.0 },
{ SUBTOTAL_FUNC_MIN, 1.0 },
{ SUBTOTAL_FUNC_SUM, 12.0 },
{ SUBTOTAL_FUNC_SELECTION_COUNT, 10.0 }
};
for (const auto& rCheck : aChecks)
{
double fRes = 0.0;
bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, ScAddress(), aMark, fRes);
CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
}
}
// Make sure that when no selection is present, use the current cursor position.
ScMarkData aEmpty(m_pDoc->GetSheetLimits());
{
// D3 (numeric cell containing 5.)
ScAddress aPos(3, 2, 0);
static const Check aChecks[] =
{
{ SUBTOTAL_FUNC_AVE, 5.0 },
{ SUBTOTAL_FUNC_CNT2, 1.0 },
{ SUBTOTAL_FUNC_CNT, 1.0 },
{ SUBTOTAL_FUNC_MAX, 5.0 },
{ SUBTOTAL_FUNC_MIN, 5.0 },
{ SUBTOTAL_FUNC_SUM, 5.0 },
{ SUBTOTAL_FUNC_SELECTION_COUNT, 1.0 }
};
for (const auto& rCheck : aChecks)
{
double fRes = 0.0;
bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, aPos, aEmpty, fRes);
CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
}
}
{
// B7 (string formula cell containing ="C".)
ScAddress aPos(1, 6, 0);
static const Check aChecks[] =
{
{ SUBTOTAL_FUNC_CNT2, 1.0 },
{ SUBTOTAL_FUNC_SELECTION_COUNT, 1.0 }
};
for (const auto& rCheck : aChecks)
{
double fRes = 0.0;
bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, aPos, aEmpty, fRes);
CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
}
}
// Calculate function across selected sheets.
clearSheet(m_pDoc, 0);
m_pDoc->InsertTab(1, u"Test2"_ustr);
m_pDoc->InsertTab(2, u"Test3"_ustr);
// Set values at B2 and C3 on each sheet.
m_pDoc->SetValue(ScAddress(1,1,0), 1.0);
m_pDoc->SetValue(ScAddress(2,2,0), 2.0);
m_pDoc->SetValue(ScAddress(1,1,1), 4.0);
m_pDoc->SetValue(ScAddress(2,2,1), 8.0);
m_pDoc->SetValue(ScAddress(1,1,2), 16.0);
m_pDoc->SetValue(ScAddress(2,2,2), 32.0);
// Mark B2 and C3 on first sheet.
aRanges.RemoveAll();
aRanges.push_back(ScRange(1,1,0)); // B2
aRanges.push_back(ScRange(2,2,0)); // C3
aMark.MarkFromRangeList(aRanges, true);
// Additionally select third sheet.
aMark.SelectTable(2, true);
{
double fRes = 0.0;
bool bRes = m_pDoc->GetSelectionFunction( SUBTOTAL_FUNC_SUM, ScAddress(), aMark, fRes);
CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
CPPUNIT_ASSERT_EQUAL_MESSAGE("1+2+16+32=", 51.0, fRes);
}
m_pDoc->DeleteTab(2);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMarkedCellIteration)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// Insert cells to A1, A5, B2 and C3.
m_pDoc->SetString(ScAddress(0,0,0), u"California"_ustr);
m_pDoc->SetValue(ScAddress(0,4,0), 1.2);
m_pDoc->SetEditText(ScAddress(1,1,0), u"Boston"_ustr);
m_pDoc->SetFormula(ScAddress(2,2,0), u"=SUM(1,2,3)"_ustr, m_pDoc->GetGrammar());
// Select A1:C5.
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
aMarkData.SetMarkArea(ScRange(0,0,0,2,4,0));
aMarkData.MarkToMulti(); // TODO : we shouldn't have to do this.
struct Check
{
SCCOL mnCol;
SCROW mnRow;
};
const std::vector<Check> aChecks = {
{ 0, 0 }, // A1
{ 0, 4 }, // A5
{ 1, 1 }, // B2
{ 2, 2 }, // C3
};
SCROW nRow = -1; // Start from the imaginary row before A1.
SCCOL nCol = 0;
for (const Check& rCheck : aChecks)
{
bool bFound = m_pDoc->GetNextMarkedCell(nCol, nRow, 0, aMarkData);
if (!bFound)
{
std::ostringstream os;
os << ScAddress(rCheck.mnCol, rCheck.mnRow, 0).GetColRowString() << " was expected, but not found.";
CPPUNIT_FAIL(os.str());
}
CPPUNIT_ASSERT_EQUAL(rCheck.mnRow, nRow);
CPPUNIT_ASSERT_EQUAL(rCheck.mnCol, nCol);
}
// No more marked cells on this sheet.
bool bFound = m_pDoc->GetNextMarkedCell(nCol, nRow, 0, aMarkData);
CPPUNIT_ASSERT(!bFound);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testCopyToDocument)
{
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, u"src"_ustr));
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
m_pDoc->SetString(0, 0, 0, u"Header"_ustr);
m_pDoc->SetString(0, 1, 0, u"1"_ustr);
m_pDoc->SetString(0, 2, 0, u"2"_ustr);
m_pDoc->SetString(0, 3, 0, u"3"_ustr);
m_pDoc->SetString(0, 4, 0, u"=4/2"_ustr);
m_pDoc->CalcAll();
//note on A1
ScAddress aAdrA1 (0, 0, 0); // numerical cell content
ScPostIt* pNote = m_pDoc->GetOrCreateNote(aAdrA1);
pNote->SetText(aAdrA1, u"Hello world in A1"_ustr);
// Copy statically to another document.
ScDocShellRef xDocSh2;
getNewDocShell(xDocSh2);
ScDocument* pDestDoc = &xDocSh2->GetDocument();
pDestDoc->InsertTab(0, u"src"_ustr);
pDestDoc->InitDrawLayer(xDocSh2.get()); // for note caption objects
m_pDoc->CopyStaticToDocument(ScRange(0,1,0,0,3,0), 0, *pDestDoc); // Copy A2:A4
m_pDoc->CopyStaticToDocument(ScRange(ScAddress(0,0,0)), 0, *pDestDoc); // Copy A1
m_pDoc->CopyStaticToDocument(ScRange(0,4,0,0,7,0), 0, *pDestDoc); // Copy A5:A8
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,0,0), pDestDoc->GetString(0,0,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,1,0), pDestDoc->GetString(0,1,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,2,0), pDestDoc->GetString(0,2,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,3,0), pDestDoc->GetString(0,3,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,4,0), pDestDoc->GetString(0,4,0));
// verify note
CPPUNIT_ASSERT_MESSAGE("There should be a note in A1 destDocument", pDestDoc->HasNote(ScAddress(0, 0, 0)));
CPPUNIT_ASSERT_EQUAL_MESSAGE("The notes content should be the same on both documents",
m_pDoc->GetNote(ScAddress(0, 0, 0))->GetText(), pDestDoc->GetNote(ScAddress(0, 0, 0))->GetText());
pDestDoc->DeleteTab(0);
xDocSh2->DoClose();
xDocSh2.clear();
m_pDoc->DeleteTab(0);
}
bool Test::checkHorizontalIterator(ScDocument& rDoc, const std::vector<std::vector<const char*>>& rData, const HoriIterCheck* pChecks, size_t nCheckCount)
{
ScAddress aPos(0,0,0);
insertRangeData(&rDoc, aPos, rData);
ScHorizontalCellIterator aIter(rDoc, 0, 0, 0, 1, rData.size() - 1);
SCCOL nCol;
SCROW nRow;
size_t i = 0;
for (ScRefCellValue* pCell = aIter.GetNext(nCol, nRow); pCell; pCell = aIter.GetNext(nCol, nRow), ++i)
{
if (i >= nCheckCount)
{
cerr << "hit invalid check " << i << " of " << nCheckCount << endl;
CPPUNIT_FAIL("Iterator claims there is more data than there should be.");
return false;
}
if (pChecks[i].nCol != nCol)
{
cerr << "Column mismatch " << pChecks[i].nCol << " vs. " << nCol << endl;
return false;
}
if (pChecks[i].nRow != nRow)
{
cerr << "Row mismatch " << pChecks[i].nRow << " vs. " << nRow << endl;
return false;
}
if (OUString::createFromAscii(pChecks[i].pVal) != pCell->getString(&rDoc))
{
cerr << "String mismatch " << pChecks[i].pVal << " vs. " <<
pCell->getString(&rDoc) << endl;
return false;
}
}
return true;
}
CPPUNIT_TEST_FIXTURE(Test, testHorizontalIterator)
{
m_pDoc->InsertTab(0, u"test"_ustr);
{
// Raw data - mixed types
std::vector<std::vector<const char*>> aData = {
{ "A", "B" },
{ "C", "1" },
{ "D", "2" },
{ "E", "3" }
};
static const HoriIterCheck aChecks[] = {
{ 0, 0, "A" },
{ 1, 0, "B" },
{ 0, 1, "C" },
{ 1, 1, "1" },
{ 0, 2, "D" },
{ 1, 2, "2" },
{ 0, 3, "E" },
{ 1, 3, "3" },
};
bool bRes = checkHorizontalIterator(
*m_pDoc, aData, aChecks, std::size(aChecks));
if (!bRes)
CPPUNIT_FAIL("Failed on test mixed.");
}
{
// Raw data - 'hole' data
std::vector<std::vector<const char*>> aData = {
{ "A", "B" },
{ "C", nullptr },
{ "D", "E" },
};
static const HoriIterCheck aChecks[] = {
{ 0, 0, "A" },
{ 1, 0, "B" },
{ 0, 1, "C" },
{ 0, 2, "D" },
{ 1, 2, "E" },
};
bool bRes = checkHorizontalIterator(
*m_pDoc, aData, aChecks, std::size(aChecks));
if (!bRes)
CPPUNIT_FAIL("Failed on test hole.");
}
{
// Very holy data
std::vector<std::vector<const char*>> aData = {
{ nullptr, "A" },
{ nullptr, nullptr },
{ nullptr, "1" },
{ "B", nullptr },
{ "C", "2" },
{ "D", "3" },
{ "E", nullptr },
{ nullptr, "G" },
{ nullptr, nullptr },
};
static const HoriIterCheck aChecks[] = {
{ 1, 0, "A" },
{ 1, 2, "1" },
{ 0, 3, "B" },
{ 0, 4, "C" },
{ 1, 4, "2" },
{ 0, 5, "D" },
{ 1, 5, "3" },
{ 0, 6, "E" },
{ 1, 7, "G" },
};
bool bRes = checkHorizontalIterator(
*m_pDoc, aData, aChecks, std::size(aChecks));
if (!bRes)
CPPUNIT_FAIL("Failed on test holy.");
}
{
// Degenerate case
std::vector<std::vector<const char*>> aData = {
{ nullptr, nullptr },
{ nullptr, nullptr },
{ nullptr, nullptr },
};
bool bRes = checkHorizontalIterator(
*m_pDoc, aData, nullptr, 0);
if (!bRes)
CPPUNIT_FAIL("Failed on test degenerate.");
}
{
// Data at end
std::vector<std::vector<const char*>> aData = {
{ nullptr, nullptr },
{ nullptr, nullptr },
{ nullptr, "A" },
};
static const HoriIterCheck aChecks[] = {
{ 1, 2, "A" },
};
bool bRes = checkHorizontalIterator(
*m_pDoc, aData, aChecks, std::size(aChecks));
if (!bRes)
CPPUNIT_FAIL("Failed on test at end.");
}
{
// Data in middle
std::vector<std::vector<const char*>> aData = {
{ nullptr, nullptr },
{ nullptr, nullptr },
{ nullptr, "A" },
{ nullptr, "1" },
{ nullptr, nullptr },
};
static const HoriIterCheck aChecks[] = {
{ 1, 2, "A" },
{ 1, 3, "1" },
};
bool bRes = checkHorizontalIterator(
*m_pDoc, aData, aChecks, std::size(aChecks));
if (!bRes)
CPPUNIT_FAIL("Failed on test in middle.");
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testValueIterator)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// Turn on "precision as shown" option.
ScDocOptions aOpt = m_pDoc->GetDocOptions();
aOpt.SetCalcAsShown(true);
m_pDoc->SetDocOptions(aOpt);
ScInterpreterContext aContext(*m_pDoc, m_pDoc->GetFormatTable());
// Purely horizontal data layout with numeric data.
for (SCCOL i = 1; i <= 3; ++i)
m_pDoc->SetValue(ScAddress(i,2,0), i);
{
const double aChecks[] = { 1.0, 2.0, 3.0 };
size_t const nCheckLen = std::size(aChecks);
ScValueIterator aIter(aContext, ScRange(1,2,0,3,2,0));
bool bHas = false;
size_t nCheckPos = 0;
double fVal;
FormulaError nErr;
for (bHas = aIter.GetFirst(fVal, nErr); bHas; bHas = aIter.GetNext(fVal, nErr), ++nCheckPos)
{
CPPUNIT_ASSERT_MESSAGE("Iteration longer than expected.", nCheckPos < nCheckLen);
CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos], fVal);
CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(nErr));
}
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testHorizontalAttrIterator)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// Set the background color of B2:C3,D2,E3,C4:D4,B5:D5 to blue
ScPatternAttr aCellBackColor(m_pDoc->getCellAttributeHelper());
aCellBackColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
m_pDoc->ApplyPatternAreaTab(1, 1, 2, 2, 0, aCellBackColor);
m_pDoc->ApplyPatternAreaTab(3, 1, 3, 1, 0, aCellBackColor);
m_pDoc->ApplyPatternAreaTab(4, 2, 4, 2, 0, aCellBackColor);
m_pDoc->ApplyPatternAreaTab(2, 3, 3, 3, 0, aCellBackColor);
m_pDoc->ApplyPatternAreaTab(1, 4, 4, 4, 0, aCellBackColor);
// some numeric data
for (SCCOL i = 1; i <= 4; ++i)
for (SCROW j = 1; j <= 4; ++j)
m_pDoc->SetValue(ScAddress(i,j,0), i*10+j);
{
const int aChecks[][3] = { {1, 3, 1}, {1, 2, 2}, {4, 4, 2}, {2, 3, 3}, {1, 4, 4} };
const size_t nCheckLen = std::size(aChecks);
ScHorizontalAttrIterator aIter(*m_pDoc, 0, 0, 0, 5, 5);
SCCOL nCol1, nCol2;
SCROW nRow;
size_t nCheckPos = 0;
for (const ScPatternAttr* pAttr = aIter.GetNext(nCol1, nCol2, nRow); pAttr; pAttr = aIter.GetNext(nCol1, nCol2, nRow))
{
if (pAttr->isDefault())
continue;
CPPUNIT_ASSERT_MESSAGE("Iteration longer than expected.", nCheckPos < nCheckLen);
CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos][0], static_cast<int>(nCol1));
CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos][1], static_cast<int>(nCol2));
CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos][2], static_cast<int>(nRow));
++nCheckPos;
}
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testIteratorsUnallocatedColumnsAttributes)
{
m_pDoc->InsertTab(0, u"Tab1"_ustr);
// Set values in first two columns, to ensure allocation of those columns.
m_pDoc->SetValue(ScAddress(0,1,0), 1);
m_pDoc->SetValue(ScAddress(1,1,0), 2);
constexpr SCCOL allocatedColsCount = 2;
assert( allocatedColsCount >= INITIALCOLCOUNT );
CPPUNIT_ASSERT_EQUAL(allocatedColsCount, m_pDoc->GetAllocatedColumnsCount(0));
// Make entire second row and third row bold.
ScPatternAttr boldAttr(m_pDoc->getCellAttributeHelper());
boldAttr.GetItemSet().Put(SvxWeightItem(WEIGHT_BOLD, ATTR_FONT_WEIGHT));
m_pDoc->ApplyPatternAreaTab(0, 1, m_pDoc->MaxCol(), 2, 0, boldAttr);
// That shouldn't need allocating more columns, just changing the default attribute.
CPPUNIT_ASSERT_EQUAL(allocatedColsCount, m_pDoc->GetAllocatedColumnsCount(0));
vcl::Font aFont;
const ScPatternAttr* pattern = m_pDoc->GetPattern(m_pDoc->MaxCol(), 1, 0);
pattern->fillFontOnly(aFont);
CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be bold", WEIGHT_BOLD, aFont.GetWeight());
// Test iterators.
ScDocAttrIterator docit( *m_pDoc, 0, allocatedColsCount - 1, 1, allocatedColsCount, 2 );
SCCOL col1, col2;
SCROW row1, row2;
CPPUNIT_ASSERT_EQUAL( pattern, docit.GetNext( col1, row1, row2 ));
CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
CPPUNIT_ASSERT_EQUAL( SCROW(2), row2 );
CPPUNIT_ASSERT_EQUAL( pattern, docit.GetNext( col1, row1, row2 ));
CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col1 );
CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
CPPUNIT_ASSERT_EQUAL( SCROW(2), row2 );
CPPUNIT_ASSERT( docit.GetNext( col1, row1, row2 ) == nullptr );
ScAttrRectIterator rectit( *m_pDoc, 0, allocatedColsCount - 1, 1, allocatedColsCount, 2 );
CPPUNIT_ASSERT_EQUAL( pattern, rectit.GetNext( col1, col2, row1, row2 ));
CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col2 );
CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
CPPUNIT_ASSERT_EQUAL( SCROW(2), row2 );
CPPUNIT_ASSERT( rectit.GetNext( col1, col2, row1, row2 ) == nullptr );
ScHorizontalAttrIterator horit( *m_pDoc, 0, allocatedColsCount - 1, 1, allocatedColsCount, 2 );
CPPUNIT_ASSERT_EQUAL( pattern, horit.GetNext( col1, col2, row1 ));
CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col2 );
CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
CPPUNIT_ASSERT_EQUAL( pattern, horit.GetNext( col1, col2, row1 ));
CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col2 );
CPPUNIT_ASSERT_EQUAL( SCROW(2), row1 );
CPPUNIT_ASSERT( horit.GetNext( col1, col2, row1 ) == nullptr );
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testIteratorsDefPattern)
{
m_pDoc->InsertTab(0, u"Tab1"_ustr);
// The default pattern is the default style, which can be edited by the user.
// As such iterators should not ignore it by default, because it might contain
// some attributes set.
// Set cells as bold, default allocated, bold, default unallocated.
SCCOL firstCol = 100;
SCCOL lastCol = 103;
ScPatternAttr boldAttr(m_pDoc->getCellAttributeHelper());
boldAttr.GetItemSet().Put(SvxWeightItem(WEIGHT_BOLD, ATTR_FONT_WEIGHT));
m_pDoc->ApplyPattern(100, 0, 0, boldAttr);
m_pDoc->ApplyPattern(102, 0, 0, boldAttr);
CPPUNIT_ASSERT_EQUAL(SCCOL(102 + 1), m_pDoc->GetAllocatedColumnsCount(0));
const ScPatternAttr* pattern = m_pDoc->GetPattern(100, 0, 0);
const ScPatternAttr* defPattern(&m_pDoc->getCellAttributeHelper().getDefaultCellAttribute()); //GetDefPattern();
CPPUNIT_ASSERT(!ScPatternAttr::areSame(pattern, defPattern));
CPPUNIT_ASSERT_EQUAL(pattern, m_pDoc->GetPattern(102, 0, 0));
CPPUNIT_ASSERT_EQUAL(defPattern, m_pDoc->GetPattern(101, 0, 0));
CPPUNIT_ASSERT_EQUAL(defPattern, m_pDoc->GetPattern(103, 0, 0));
// Test iterators.
ScDocAttrIterator docit( *m_pDoc, 0, firstCol, 0, lastCol, 0 );
SCCOL col1, col2;
SCROW row1, row2;
CPPUNIT_ASSERT_EQUAL(pattern, docit.GetNext( col1, row1, row2 ));
CPPUNIT_ASSERT_EQUAL(defPattern, docit.GetNext( col1, row1, row2 ));
CPPUNIT_ASSERT_EQUAL(pattern, docit.GetNext( col1, row1, row2 ));
CPPUNIT_ASSERT_EQUAL(defPattern, docit.GetNext( col1, row1, row2 ));
CPPUNIT_ASSERT(docit.GetNext( col1, row1, row2 ) == nullptr );
ScAttrRectIterator rectit( *m_pDoc, 0, firstCol, 0, lastCol, 0 );
CPPUNIT_ASSERT_EQUAL(pattern, rectit.GetNext( col1, col2, row1, row2 ));
CPPUNIT_ASSERT_EQUAL(defPattern, rectit.GetNext( col1, col2, row1, row2 ));
CPPUNIT_ASSERT_EQUAL(pattern, rectit.GetNext( col1, col2, row1, row2 ));
CPPUNIT_ASSERT_EQUAL(defPattern, rectit.GetNext( col1, col2, row1, row2 ));
CPPUNIT_ASSERT(rectit.GetNext( col1, col2, row1, row2 ) == nullptr );
ScHorizontalAttrIterator horit( *m_pDoc, 0, firstCol, 0, lastCol, 0 );
CPPUNIT_ASSERT_EQUAL(pattern, horit.GetNext( col1, col2, row1 ));
CPPUNIT_ASSERT_EQUAL(defPattern, horit.GetNext( col1, col2, row1 ));
CPPUNIT_ASSERT_EQUAL(pattern, horit.GetNext( col1, col2, row1 ));
CPPUNIT_ASSERT_EQUAL(defPattern, horit.GetNext( col1, col2, row1 ));
CPPUNIT_ASSERT(horit.GetNext( col1, col2, row1 ) == nullptr );
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testLastChangedColFlagsWidth)
{
m_pDoc->InsertTab(0, u"Tab1"_ustr);
constexpr SCCOL firstChangedCol = 100;
assert( firstChangedCol > m_pDoc->GetAllocatedColumnsCount(0));
CPPUNIT_ASSERT_EQUAL(INITIALCOLCOUNT, m_pDoc->GetAllocatedColumnsCount(0));
for( SCCOL col = firstChangedCol; col <= m_pDoc->MaxCol(); ++col )
m_pDoc->SetColWidth( col, 0, 10 );
// That shouldn't need allocating more columns, just changing column flags.
CPPUNIT_ASSERT_EQUAL(INITIALCOLCOUNT, m_pDoc->GetAllocatedColumnsCount(0));
// But the flags are changed.
CPPUNIT_ASSERT_EQUAL(m_pDoc->MaxCol(), m_pDoc->GetLastChangedColFlagsWidth(0));
m_pDoc->DeleteTab(0);
}
namespace {
bool broadcasterShifted(const ScDocument& rDoc, const ScAddress& rFrom, const ScAddress& rTo)
{
const SvtBroadcaster* pBC = rDoc.GetBroadcaster(rFrom);
if (pBC)
{
cerr << "Broadcaster shouldn't be here." << endl;
return false;
}
pBC = rDoc.GetBroadcaster(rTo);
if (!pBC)
{
cerr << "Broadcaster should be here." << endl;
return false;
}
return true;
}
formula::FormulaToken* getSingleRefToken(ScDocument& rDoc, const ScAddress& rPos)
{
ScFormulaCell* pFC = rDoc.GetFormulaCell(rPos);
if (!pFC)
{
cerr << "Formula cell expected, but not found." << endl;
return nullptr;
}
ScTokenArray* pTokens = pFC->GetCode();
if (!pTokens)
{
cerr << "Token array is not present." << endl;
return nullptr;
}
formula::FormulaToken* pToken = pTokens->FirstToken();
if (!pToken || pToken->GetType() != formula::svSingleRef)
{
cerr << "Not a single reference token." << endl;
return nullptr;
}
return pToken;
}
bool checkRelativeRefToken(ScDocument& rDoc, const ScAddress& rPos, SCCOL nRelCol, SCROW nRelRow)
{
formula::FormulaToken* pToken = getSingleRefToken(rDoc, rPos);
if (!pToken)
return false;
ScSingleRefData& rRef = *pToken->GetSingleRef();
if (!rRef.IsColRel() || rRef.Col() != nRelCol)
{
cerr << "Unexpected relative column address." << endl;
return false;
}
if (!rRef.IsRowRel() || rRef.Row() != nRelRow)
{
cerr << "Unexpected relative row address." << endl;
return false;
}
return true;
}
bool checkDeletedRefToken(ScDocument& rDoc, const ScAddress& rPos)
{
formula::FormulaToken* pToken = getSingleRefToken(rDoc, rPos);
if (!pToken)
return false;
ScSingleRefData& rRef = *pToken->GetSingleRef();
if (!rRef.IsDeleted())
{
cerr << "Deleted reference is expected, but it's still a valid reference." << endl;
return false;
}
return true;
}
}
CPPUNIT_TEST_FIXTURE(Test, testCellBroadcaster)
{
/**
* More direct test for cell broadcaster management, used to track formula
* dependencies.
*/
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, u"foo"_ustr));
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation.
m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr); // B1 depends on A1.
double val = m_pDoc->GetValue(ScAddress(1,0,0)); // A1 is empty, so the result should be 0.
CPPUNIT_ASSERT_EQUAL(0.0, val);
const SvtBroadcaster* pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
CPPUNIT_ASSERT_MESSAGE("Cell A1 should have a broadcaster.", pBC);
// Change the value of A1 and make sure that B1 follows.
m_pDoc->SetValue(ScAddress(0,0,0), 1.23);
val = m_pDoc->GetValue(ScAddress(1,0,0));
CPPUNIT_ASSERT_EQUAL(1.23, val);
// Move column A down 5 cells. Make sure B1 now references A6, not A1.
m_pDoc->InsertRow(0, 0, 0, 0, 0, 5);
CPPUNIT_ASSERT_MESSAGE("Relative reference check failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 5));
// Make sure the broadcaster has also moved.
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,0,0), ScAddress(0,5,0)));
// Set new value to A6 and make sure B1 gets updated.
m_pDoc->SetValue(ScAddress(0,5,0), 45.6);
val = m_pDoc->GetValue(ScAddress(1,0,0));
CPPUNIT_ASSERT_EQUAL(45.6, val);
// Move column A up 3 cells, and make sure B1 now references A3, not A6.
m_pDoc->DeleteRow(0, 0, 0, 0, 0, 3);
CPPUNIT_ASSERT_MESSAGE("Relative reference check failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 2));
// The broadcaster should also have been relocated from A6 to A3.
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,5,0), ScAddress(0,2,0)));
// Insert cells over A1:A10 and shift cells to right.
m_pDoc->InsertCol(ScRange(0, 0, 0, 0, 10, 0));
CPPUNIT_ASSERT_MESSAGE("Relative reference check failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(2,0,0), -1, 2));
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,2,0), ScAddress(1,2,0)));
// Delete formula in C2, which should remove the broadcaster in B3.
pBC = m_pDoc->GetBroadcaster(ScAddress(1,2,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster in B3 should still exist.", pBC);
clearRange(m_pDoc, ScRange(ScAddress(2,0,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(2,0,0))); // C2 should be empty.
pBC = m_pDoc->GetBroadcaster(ScAddress(1,2,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster in B3 should have been removed.", !pBC);
// Clear everything and start over.
clearRange(m_pDoc, ScRange(0,0,0,10,100,0));
m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr); // B1 depends on A1.
pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A1.", pBC);
// While column A is still empty, move column A down 2 cells. This should
// move the broadcaster from A1 to A3.
m_pDoc->InsertRow(0, 0, 0, 0, 0, 2);
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,0,0), ScAddress(0,2,0)));
// Move it back while column A is still empty.
m_pDoc->DeleteRow(0, 0, 0, 0, 0, 2);
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,2,0), ScAddress(0,0,0)));
// Clear everything again
clearRange(m_pDoc, ScRange(0,0,0,10,100,0));
// B1:B3 depends on A1:A3
m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr);
m_pDoc->SetString(ScAddress(1,1,0), u"=A2"_ustr);
m_pDoc->SetString(ScAddress(1,2,0), u"=A3"_ustr);
CPPUNIT_ASSERT_MESSAGE("Relative reference check in B1 failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 0));
CPPUNIT_ASSERT_MESSAGE("Relative reference check in B2 failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,1,0), -1, 0));
CPPUNIT_ASSERT_MESSAGE("Relative reference check in B3 failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,2,0), -1, 0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A1.", m_pDoc->GetBroadcaster(ScAddress(0,0,0)));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A2.", m_pDoc->GetBroadcaster(ScAddress(0,1,0)));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A3.", m_pDoc->GetBroadcaster(ScAddress(0,2,0)));
// Insert Rows at row 2, down 5 rows.
m_pDoc->InsertRow(0, 0, 0, 0, 1, 5);
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A1.", m_pDoc->GetBroadcaster(ScAddress(0,0,0)));
CPPUNIT_ASSERT_MESSAGE("Relative reference check in B1 failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 0));
// Broadcasters in A2 and A3 should shift down by 5 rows.
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,1,0), ScAddress(0,6,0)));
CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
broadcasterShifted(*m_pDoc, ScAddress(0,2,0), ScAddress(0,7,0)));
// B2 and B3 should reference shifted cells.
CPPUNIT_ASSERT_MESSAGE("Relative reference check in B2 failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,1,0), -1, 5));
CPPUNIT_ASSERT_MESSAGE("Relative reference check in B2 failed.",
checkRelativeRefToken(*m_pDoc, ScAddress(1,2,0), -1, 5));
// Delete cells with broadcasters.
m_pDoc->DeleteRow(0, 0, 0, 0, 4, 6);
CPPUNIT_ASSERT_MESSAGE("Broadcaster should NOT exist in A7.", !m_pDoc->GetBroadcaster(ScAddress(0,6,0)));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should NOT exist in A8.", !m_pDoc->GetBroadcaster(ScAddress(0,7,0)));
// References in B2 and B3 should be invalid.
CPPUNIT_ASSERT_MESSAGE("Deleted reference check in B2 failed.",
checkDeletedRefToken(*m_pDoc, ScAddress(1,1,0)));
CPPUNIT_ASSERT_MESSAGE("Deleted reference check in B3 failed.",
checkDeletedRefToken(*m_pDoc, ScAddress(1,2,0)));
// Clear everything again
clearRange(m_pDoc, ScRange(0,0,0,10,100,0));
{
// Switch to R1C1 to make it easier to input relative references in multiple cells.
FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
// Have B1:B20 reference A1:A20.
val = 0.0;
for (SCROW i = 0; i < 20; ++i)
{
m_pDoc->SetValue(ScAddress(0,i,0), val++);
m_pDoc->SetString(ScAddress(1,i,0), u"=RC[-1]"_ustr);
}
}
// Ensure that the formula cells show correct values, and the referenced
// cells have broadcasters.
val = 0.0;
for (SCROW i = 0; i < 20; ++i, ++val)
{
CPPUNIT_ASSERT_EQUAL(val, m_pDoc->GetValue(ScAddress(1,i,0)));
pBC = m_pDoc->GetBroadcaster(ScAddress(0,i,0));
CPPUNIT_ASSERT_MESSAGE("Broadcast should exist here.", pBC);
}
// Delete formula cells in B2:B19.
clearRange(m_pDoc, ScRange(1,1,0,1,18,0));
// Ensure that A2:A19 no longer have broadcasters, but A1 and A20 still do.
CPPUNIT_ASSERT_MESSAGE("A1 should still have broadcaster.", m_pDoc->GetBroadcaster(ScAddress(0,0,0)));
CPPUNIT_ASSERT_MESSAGE("A20 should still have broadcaster.", m_pDoc->GetBroadcaster(ScAddress(0,19,0)));
for (SCROW i = 1; i <= 18; ++i)
{
pBC = m_pDoc->GetBroadcaster(ScAddress(0,i,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should have been deleted.", !pBC);
}
// Clear everything again
clearRange(m_pDoc, ScRange(0,0,0,10,100,0));
m_pDoc->SetValue(ScAddress(0,0,0), 2.0);
m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr);
m_pDoc->SetString(ScAddress(2,0,0), u"=B1"_ustr);
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(0,0,0));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(1,0,0));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(2,0,0));
pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist here.", pBC);
pBC = m_pDoc->GetBroadcaster(ScAddress(1,0,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist here.", pBC);
// Change the value of A1 and make sure everyone follows suit.
m_pDoc->SetValue(ScAddress(0,0,0), 3.5);
CPPUNIT_ASSERT_EQUAL(3.5, m_pDoc->GetValue(0,0,0));
CPPUNIT_ASSERT_EQUAL(3.5, m_pDoc->GetValue(1,0,0));
CPPUNIT_ASSERT_EQUAL(3.5, m_pDoc->GetValue(2,0,0));
// Insert a column at column B.
m_pDoc->InsertCol(ScRange(1,0,0,1,m_pDoc->MaxRow(),0));
pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist here.", pBC);
pBC = m_pDoc->GetBroadcaster(ScAddress(2,0,0));
CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist here.", pBC);
// Change the value of A1 again.
m_pDoc->SetValue(ScAddress(0,0,0), 5.5);
CPPUNIT_ASSERT_EQUAL(5.5, m_pDoc->GetValue(0,0,0));
CPPUNIT_ASSERT_EQUAL(5.5, m_pDoc->GetValue(2,0,0));
CPPUNIT_ASSERT_EQUAL(5.5, m_pDoc->GetValue(3,0,0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFuncParam)
{
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
m_pDoc->InsertTab (0, u"foo"_ustr));
// First, the normal case, with no missing parameters.
m_pDoc->SetString(0, 0, 0, u"=AVERAGE(1;2;3)"_ustr);
m_pDoc->CalcFormulaTree(false, false);
double val = m_pDoc->GetValue(0, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 2.0, val);
// Now function with missing parameters. Missing values should be treated
// as zeros.
m_pDoc->SetString(0, 0, 0, u"=AVERAGE(1;;;)"_ustr);
m_pDoc->CalcFormulaTree(false, false);
val = m_pDoc->GetValue(0, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 0.25, val);
// Conversion of string to numeric argument.
m_pDoc->SetString(0, 0, 0, u"=\"\"+3"_ustr); // empty string
m_pDoc->SetString(0, 1, 0, u"=\" \"+3"_ustr); // only blank
m_pDoc->SetString(0, 2, 0, u"=\" 4 \"+3"_ustr); // number in blanks
m_pDoc->SetString(0, 3, 0, u"=\" x \"+3"_ustr); // non-numeric
m_pDoc->SetString(0, 4, 0, u"=\"4.4\"+3"_ustr); // locale dependent
OUString aVal;
ScCalcConfig aConfig;
// With "Convert also locale dependent" and "Empty string as zero"=True option.
aConfig.meStringConversion = ScCalcConfig::StringConversion::LOCALE;
aConfig.mbEmptyStringAsZero = true;
m_pDoc->SetCalcConfig(aConfig);
m_pDoc->CalcAll();
val = m_pDoc->GetValue(0, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 1, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 2, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 7.0, val);
aVal = m_pDoc->GetString( 0, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
val = m_pDoc->GetValue(0, 4, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 7.4, val);
// With "Convert also locale dependent" and "Empty string as zero"=False option.
aConfig.meStringConversion = ScCalcConfig::StringConversion::LOCALE;
aConfig.mbEmptyStringAsZero = false;
m_pDoc->SetCalcConfig(aConfig);
m_pDoc->CalcAll();
aVal = m_pDoc->GetString( 0, 0, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 1, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
val = m_pDoc->GetValue(0, 2, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 7.0, val);
aVal = m_pDoc->GetString( 0, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
val = m_pDoc->GetValue(0, 4, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 7.4, val);
// With "Convert only unambiguous" and "Empty string as zero"=True option.
aConfig.meStringConversion = ScCalcConfig::StringConversion::UNAMBIGUOUS;
aConfig.mbEmptyStringAsZero = true;
m_pDoc->SetCalcConfig(aConfig);
m_pDoc->CalcAll();
val = m_pDoc->GetValue(0, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 1, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 2, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 7.0, val);
aVal = m_pDoc->GetString( 0, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 4, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
// With "Convert only unambiguous" and "Empty string as zero"=False option.
aConfig.meStringConversion = ScCalcConfig::StringConversion::UNAMBIGUOUS;
aConfig.mbEmptyStringAsZero = false;
m_pDoc->SetCalcConfig(aConfig);
m_pDoc->CalcAll();
aVal = m_pDoc->GetString( 0, 0, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 1, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
m_pDoc->GetValue(0, 2, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 7.0, val);
aVal = m_pDoc->GetString( 0, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 4, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
// With "Treat as zero" ("Empty string as zero" is ignored).
aConfig.meStringConversion = ScCalcConfig::StringConversion::ZERO;
aConfig.mbEmptyStringAsZero = true;
m_pDoc->SetCalcConfig(aConfig);
m_pDoc->CalcAll();
val = m_pDoc->GetValue(0, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 1, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 2, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 3, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
val = m_pDoc->GetValue(0, 4, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("incorrect result", 3.0, val);
// With "Generate #VALUE! error" ("Empty string as zero" is ignored).
aConfig.meStringConversion = ScCalcConfig::StringConversion::ILLEGAL;
aConfig.mbEmptyStringAsZero = false;
m_pDoc->SetCalcConfig(aConfig);
m_pDoc->CalcAll();
aVal = m_pDoc->GetString( 0, 0, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 1, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 2, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
aVal = m_pDoc->GetString( 0, 4, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", u"#VALUE!"_ustr, aVal);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNamedRange)
{
static const RangeNameDef aNames[] = {
{ "Divisor", "$Sheet1.$A$1:$A$1048576", 1 },
{ "MyRange1", "$Sheet1.$A$1:$A$100", 2 },
{ "MyRange2", "$Sheet1.$B$1:$B$100", 3 },
{ "MyRange3", "$Sheet1.$C$1:$C$100", 4 }
};
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, u"Sheet1"_ustr));
m_pDoc->SetValue (0, 0, 0, 101);
std::unique_ptr<ScRangeName> pNames(new ScRangeName);
bool bSuccess = insertRangeNames(m_pDoc, pNames.get(), aNames, aNames + std::size(aNames));
CPPUNIT_ASSERT_MESSAGE("Failed to insert range names.", bSuccess);
m_pDoc->SetRangeName(std::move(pNames));
ScRangeName* pNewRanges = m_pDoc->GetRangeName();
CPPUNIT_ASSERT(pNewRanges);
// Make sure the index lookup does the right thing.
for (const auto& rName : aNames)
{
const ScRangeData* p = pNewRanges->findByIndex(rName.mnIndex);
CPPUNIT_ASSERT_MESSAGE("lookup of range name by index failed.", p);
OUString aName = p->GetName();
CPPUNIT_ASSERT_MESSAGE("wrong range name is retrieved.", aName.equalsAscii(rName.mpName));
}
// Test usage in formula expression.
m_pDoc->SetString (1, 0, 0, u"=A1/Divisor"_ustr);
m_pDoc->CalcAll();
double result = m_pDoc->GetValue (1, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE ("calculation failed", 1.0, result);
// Test copy-ability of range names.
std::unique_ptr<ScRangeName> pCopiedRanges(new ScRangeName(*pNewRanges));
m_pDoc->SetRangeName(std::move(pCopiedRanges));
// Make sure the index lookup still works.
for (const auto& rName : aNames)
{
const ScRangeData* p = m_pDoc->GetRangeName()->findByIndex(rName.mnIndex);
CPPUNIT_ASSERT_MESSAGE("lookup of range name by index failed with the copied instance.", p);
OUString aName = p->GetName();
CPPUNIT_ASSERT_MESSAGE("wrong range name is retrieved with the copied instance.", aName.equalsAscii(rName.mpName));
}
// Test using another-sheet-local name, scope Sheet1.
ScRangeData* pLocal1 = new ScRangeData( *m_pDoc, u"local1"_ustr, ScAddress(0,0,0));
ScRangeData* pLocal2 = new ScRangeData( *m_pDoc, u"local2"_ustr, u"$Sheet1.$A$1"_ustr);
ScRangeData* pLocal3 = new ScRangeData( *m_pDoc, u"local3"_ustr, u"Sheet1.$A$1"_ustr);
ScRangeData* pLocal4 = new ScRangeData( *m_pDoc, u"local4"_ustr, u"$A$1"_ustr); // implicit relative sheet reference
std::unique_ptr<ScRangeName> pLocalRangeName1(new ScRangeName);
pLocalRangeName1->insert(pLocal1);
pLocalRangeName1->insert(pLocal2);
pLocalRangeName1->insert(pLocal3);
pLocalRangeName1->insert(pLocal4);
m_pDoc->SetRangeName(0, std::move(pLocalRangeName1));
CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (1, u"Sheet2"_ustr));
// Use other-sheet-local name of Sheet1 on Sheet2.
ScAddress aPos(1,0,1);
OUString aFormula(u"=Sheet1.local1+Sheet1.local2+Sheet1.local3+Sheet1.local4"_ustr);
m_pDoc->SetString(aPos, aFormula);
OUString aString = m_pDoc->GetFormula(1,0,1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("formula string should be equal", aFormula, aString);
double fValue = m_pDoc->GetValue(aPos);
ASSERT_DOUBLES_EQUAL_MESSAGE("value should be 4 times Sheet1.A1", 404.0, fValue);
m_pDoc->DeleteTab(1);
m_pDoc->SetRangeName(0,nullptr); // Delete the names.
m_pDoc->SetRangeName(nullptr); // Delete the names.
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testInsertNameList)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
static const RangeNameDef aNames[] = {
{ "MyRange1", "$Test.$A$1:$A$100", 1 },
{ "MyRange2", "$Test.$B$1:$B$100", 2 },
{ "MyRange3", "$Test.$C$1:$C$100", 3 }
};
std::unique_ptr<ScRangeName> pNames(new ScRangeName);
bool bSuccess = insertRangeNames(m_pDoc, pNames.get(), aNames, aNames + std::size(aNames));
CPPUNIT_ASSERT_MESSAGE("Failed to insert range names.", bSuccess);
m_pDoc->SetRangeName(std::move(pNames));
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
ScAddress aPos(1,1,0);
rDocFunc.InsertNameList(aPos, true);
for (auto const& rName : aNames)
{
OUString aName = m_pDoc->GetString(aPos);
CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(rName.mpName), aName);
ScAddress aExprPos = aPos;
aExprPos.IncCol();
OUString aExpr = m_pDoc->GetString(aExprPos);
OUString aExpected = "=" + OUString::createFromAscii(rName.mpExpr);
CPPUNIT_ASSERT_EQUAL(aExpected, aExpr);
aPos.IncRow();
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testCSV)
{
const int English = 0, European = 1;
struct {
const char *pStr; int eSep; bool bResult; double nValue;
} aTests[] = {
{ "foo", English, false, 0.0 },
{ "1.0", English, true, 1.0 },
{ "1,0", English, false, 0.0 },
{ "1.0", European, false, 0.0 },
{ "1.000", European, true, 1000.0 },
{ "1,000", European, true, 1.0 },
{ "1.000", English, true, 1.0 },
{ "1,000", English, true, 1000.0 },
{ " 1.0", English, true, 1.0 },
{ " 1.0 ", English, true, 1.0 },
{ "1.0 ", European, false, 0.0 },
{ "1.000", European, true, 1000.0 },
{ "1137.999", English, true, 1137.999 },
{ "1.000.00", European, false, 0.0 },
{ "+,123", English, false, 0.0 },
{ "-,123", English, false, 0.0 }
};
for (const auto& rTest : aTests) {
OUString aStr(rTest.pStr, strlen (rTest.pStr), RTL_TEXTENCODING_UTF8);
double nValue = 0.0;
bool bResult = ScStringUtil::parseSimpleNumber
(aStr, rTest.eSep == English ? '.' : ',',
rTest.eSep == English ? ',' : '.',
0,
nValue);
CPPUNIT_ASSERT_EQUAL_MESSAGE ("CSV numeric detection failure", rTest.bResult, bResult);
CPPUNIT_ASSERT_EQUAL_MESSAGE ("CSV numeric value failure", rTest.nValue, nValue);
}
}
template<typename Evaluator>
static void checkMatrixElements(const ScMatrix& rMat)
{
SCSIZE nC, nR;
rMat.GetDimensions(nC, nR);
Evaluator aEval;
for (SCSIZE i = 0; i < nC; ++i)
{
for (SCSIZE j = 0; j < nR; ++j)
{
aEval(i, j, rMat.Get(i, j));
}
}
}
namespace {
struct AllZeroMatrix
{
void operator() (SCSIZE /*nCol*/, SCSIZE /*nRow*/, const ScMatrixValue& rVal) const
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of numeric type", int(ScMatValType::Value), static_cast<int>(rVal.nType));
ASSERT_DOUBLES_EQUAL_MESSAGE("element value must be zero", 0.0, rVal.fVal);
}
};
struct PartiallyFilledZeroMatrix
{
void operator() (SCSIZE nCol, SCSIZE nRow, const ScMatrixValue& rVal) const
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of numeric type", int(ScMatValType::Value), static_cast<int>(rVal.nType));
if (1 <= nCol && nCol <= 2 && 2 <= nRow && nRow <= 8)
{
ASSERT_DOUBLES_EQUAL_MESSAGE("element value must be 3.0", 3.0, rVal.fVal);
}
else
{
ASSERT_DOUBLES_EQUAL_MESSAGE("element value must be zero", 0.0, rVal.fVal);
}
}
};
struct AllEmptyMatrix
{
void operator() (SCSIZE /*nCol*/, SCSIZE /*nRow*/, const ScMatrixValue& rVal) const
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of empty type", int(ScMatValType::Empty), static_cast<int>(rVal.nType));
ASSERT_DOUBLES_EQUAL_MESSAGE("value of \"empty\" element is expected to be zero", 0.0, rVal.fVal);
}
};
struct PartiallyFilledEmptyMatrix
{
void operator() (SCSIZE nCol, SCSIZE nRow, const ScMatrixValue& rVal) const
{
if (nCol == 1 && nRow == 1)
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of boolean type", int(ScMatValType::Boolean), static_cast<int>(rVal.nType));
ASSERT_DOUBLES_EQUAL_MESSAGE("element value is not what is expected", 1.0, rVal.fVal);
}
else if (nCol == 4 && nRow == 5)
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of value type", int(ScMatValType::Value), static_cast<int>(rVal.nType));
ASSERT_DOUBLES_EQUAL_MESSAGE("element value is not what is expected", -12.5, rVal.fVal);
}
else if (nCol == 8 && nRow == 2)
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of value type", int(ScMatValType::String), static_cast<int>(rVal.nType));
CPPUNIT_ASSERT_EQUAL_MESSAGE("element value is not what is expected", u"Test"_ustr, rVal.aStr.getString());
}
else if (nCol == 8 && nRow == 11)
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of empty path type", int(ScMatValType::EmptyPath), static_cast<int>(rVal.nType));
ASSERT_DOUBLES_EQUAL_MESSAGE("value of \"empty\" element is expected to be zero", 0.0, rVal.fVal);
}
else
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("element is not of empty type", int(ScMatValType::Empty), static_cast<int>(rVal.nType));
ASSERT_DOUBLES_EQUAL_MESSAGE("value of \"empty\" element is expected to be zero", 0.0, rVal.fVal);
}
}
};
}
CPPUNIT_TEST_FIXTURE(Test, testMatrix)
{
svl::SharedStringPool& rPool = m_pDoc->GetSharedStringPool();
ScMatrixRef pMat, pMat2;
// First, test the zero matrix type.
pMat = new ScMatrix(0, 0, 0.0);
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR);
CPPUNIT_ASSERT_EQUAL_MESSAGE("matrix is not empty", SCSIZE(0), nC);
CPPUNIT_ASSERT_EQUAL_MESSAGE("matrix is not empty", SCSIZE(0), nR);
pMat->Resize(4, 10, 0.0);
pMat->GetDimensions(nC, nR);
CPPUNIT_ASSERT_EQUAL_MESSAGE("matrix size is not as expected", SCSIZE(4), nC);
CPPUNIT_ASSERT_EQUAL_MESSAGE("matrix size is not as expected", SCSIZE(10), nR);
CPPUNIT_ASSERT_MESSAGE("both 'and' and 'or' should evaluate to false",
!pMat->And());
CPPUNIT_ASSERT_MESSAGE("both 'and' and 'or' should evaluate to false",
!pMat->Or());
// Resizing into a larger matrix should fill the void space with zeros.
checkMatrixElements<AllZeroMatrix>(*pMat);
pMat->FillDouble(3.0, 1, 2, 2, 8);
checkMatrixElements<PartiallyFilledZeroMatrix>(*pMat);
CPPUNIT_ASSERT_MESSAGE("matrix is expected to be numeric", pMat->IsNumeric());
CPPUNIT_ASSERT_MESSAGE("partially non-zero matrix should evaluate false on 'and' and true on 'or",
!pMat->And());
CPPUNIT_ASSERT_MESSAGE("partially non-zero matrix should evaluate false on 'and' and true on 'or",
pMat->Or());
pMat->FillDouble(5.0, 0, 0, nC-1, nR-1);
CPPUNIT_ASSERT_MESSAGE("fully non-zero matrix should evaluate true both on 'and' and 'or",
pMat->And());
CPPUNIT_ASSERT_MESSAGE("fully non-zero matrix should evaluate true both on 'and' and 'or",
pMat->Or());
// Test the AND and OR evaluations.
pMat = new ScMatrix(2, 2, 0.0);
// Only some of the elements are non-zero.
pMat->PutBoolean(true, 0, 0);
pMat->PutDouble(1.0, 1, 1);
CPPUNIT_ASSERT_MESSAGE("incorrect OR result", pMat->Or());
CPPUNIT_ASSERT_MESSAGE("incorrect AND result", !pMat->And());
// All of the elements are non-zero.
pMat->PutBoolean(true, 0, 1);
pMat->PutDouble(2.3, 1, 0);
CPPUNIT_ASSERT_MESSAGE("incorrect OR result", pMat->Or());
CPPUNIT_ASSERT_MESSAGE("incorrect AND result", pMat->And());
// Now test the empty matrix type.
pMat = new ScMatrix(10, 20);
pMat->GetDimensions(nC, nR);
CPPUNIT_ASSERT_EQUAL_MESSAGE("matrix size is not as expected", SCSIZE(10), nC);
CPPUNIT_ASSERT_EQUAL_MESSAGE("matrix size is not as expected", SCSIZE(20), nR);
checkMatrixElements<AllEmptyMatrix>(*pMat);
pMat->PutBoolean(true, 1, 1);
pMat->PutDouble(-12.5, 4, 5);
pMat->PutString(rPool.intern(u"Test"_ustr), 8, 2);
pMat->PutEmptyPath(8, 11);
checkMatrixElements<PartiallyFilledEmptyMatrix>(*pMat);
// Test resizing.
pMat = new ScMatrix(0, 0);
pMat->Resize(2, 2, 1.5);
pMat->PutEmpty(1, 1);
CPPUNIT_ASSERT_EQUAL(1.5, pMat->GetDouble(0, 0));
CPPUNIT_ASSERT_EQUAL(1.5, pMat->GetDouble(0, 1));
CPPUNIT_ASSERT_EQUAL(1.5, pMat->GetDouble(1, 0));
CPPUNIT_ASSERT_MESSAGE("PutEmpty() call failed.", pMat->IsEmpty(1, 1));
// Max and min values.
pMat = new ScMatrix(2, 2, 0.0);
pMat->PutDouble(-10, 0, 0);
pMat->PutDouble(-12, 0, 1);
pMat->PutDouble(-8, 1, 0);
pMat->PutDouble(-25, 1, 1);
CPPUNIT_ASSERT_EQUAL(-25.0, pMat->GetMinValue(false));
CPPUNIT_ASSERT_EQUAL(-8.0, pMat->GetMaxValue(false));
pMat->PutString(rPool.intern(u"Test"_ustr), 0, 0);
CPPUNIT_ASSERT_EQUAL(0.0, pMat->GetMaxValue(true)); // text as zero.
CPPUNIT_ASSERT_EQUAL(-8.0, pMat->GetMaxValue(false)); // ignore text.
pMat->PutBoolean(true, 0, 0);
CPPUNIT_ASSERT_EQUAL(1.0, pMat->GetMaxValue(false));
pMat = new ScMatrix(2, 2, 10.0);
pMat->PutBoolean(false, 0, 0);
pMat->PutDouble(12.5, 1, 1);
CPPUNIT_ASSERT_EQUAL(0.0, pMat->GetMinValue(false));
CPPUNIT_ASSERT_EQUAL(12.5, pMat->GetMaxValue(false));
// Convert matrix into a linear double array. String elements become NaN
// and empty elements become 0.
pMat = new ScMatrix(3, 3);
pMat->PutDouble(2.5, 0, 0);
pMat->PutDouble(1.2, 0, 1);
pMat->PutString(rPool.intern(u"A"_ustr), 1, 1);
pMat->PutDouble(2.3, 2, 1);
pMat->PutDouble(-20, 2, 2);
static const double fNaN = std::numeric_limits<double>::quiet_NaN();
std::vector<double> aDoubles;
pMat->GetDoubleArray(aDoubles);
{
const double pChecks[] = { 2.5, 1.2, 0, 0, fNaN, 0, 0, 2.3, -20 };
CPPUNIT_ASSERT_EQUAL(SAL_N_ELEMENTS(pChecks), aDoubles.size());
for (size_t i = 0, n = aDoubles.size(); i < n; ++i)
{
if (std::isnan(pChecks[i]))
CPPUNIT_ASSERT_MESSAGE("NaN is expected, but it's not.", std::isnan(aDoubles[i]));
else
CPPUNIT_ASSERT_EQUAL(pChecks[i], aDoubles[i]);
}
}
pMat2 = new ScMatrix(3, 3, 10.0);
pMat2->PutString(rPool.intern(u"B"_ustr), 1, 0);
pMat2->MergeDoubleArrayMultiply(aDoubles);
{
const double pChecks[] = { 25, 12, 0, fNaN, fNaN, 0, 0, 23, -200 };
CPPUNIT_ASSERT_EQUAL(SAL_N_ELEMENTS(pChecks), aDoubles.size());
for (size_t i = 0, n = aDoubles.size(); i < n; ++i)
{
if (std::isnan(pChecks[i]))
CPPUNIT_ASSERT_MESSAGE("NaN is expected, but it's not.", std::isnan(aDoubles[i]));
else
CPPUNIT_ASSERT_EQUAL(pChecks[i], aDoubles[i]);
}
}
}
CPPUNIT_TEST_FIXTURE(Test, testMatrixComparisonWithErrors)
{
m_pDoc->InsertTab(0, u"foo"_ustr);
// Insert the source values in A1:A2.
m_pDoc->SetString(0, 0, 0, u"=1/0"_ustr);
m_pDoc->SetValue( 0, 1, 0, 1.0);
// Create a matrix formula in B3:B4 referencing A1:A2 and doing a greater
// than comparison on it's values. Error value must be propagated.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
m_pDoc->InsertMatrixFormula(1, 2, 1, 3, aMark, u"=A1:A2>0"_ustr);
CPPUNIT_ASSERT_EQUAL(u"#DIV/0!"_ustr, m_pDoc->GetString(0,0,0));
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue( 0,1,0));
CPPUNIT_ASSERT_EQUAL(u"#DIV/0!"_ustr, m_pDoc->GetString(1,2,0));
CPPUNIT_ASSERT_EQUAL(u"TRUE"_ustr, m_pDoc->GetString(1,3,0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMatrixConditionalBooleanResult)
{
m_pDoc->InsertTab(0, u"foo"_ustr);
// Create matrix formulas in A1:B1,A2:B2,A3:B3,A4:B4 producing mixed
// boolean and numeric results in an unformatted area.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
m_pDoc->InsertMatrixFormula( 0,0, 1,0, aMark, u"=IF({1;0};TRUE();42)"_ustr); // {TRUE,42}
m_pDoc->InsertMatrixFormula( 0,1, 1,1, aMark, u"=IF({0;1};TRUE();42)"_ustr); // {42,1} aim for {42,TRUE}
m_pDoc->InsertMatrixFormula( 0,2, 1,2, aMark, u"=IF({1;0};42;FALSE())"_ustr); // {42,0} aim for {42,FALSE}
m_pDoc->InsertMatrixFormula( 0,3, 1,3, aMark, u"=IF({0;1};42;FALSE())"_ustr); // {FALSE,42}
CPPUNIT_ASSERT_EQUAL( u"TRUE"_ustr, m_pDoc->GetString(0,0,0));
CPPUNIT_ASSERT_EQUAL( u"42"_ustr, m_pDoc->GetString(1,0,0));
CPPUNIT_ASSERT_EQUAL( u"42"_ustr, m_pDoc->GetString(0,1,0));
//CPPUNIT_ASSERT_EQUAL( OUString("TRUE"), m_pDoc->GetString(1,1,0)); // not yet
CPPUNIT_ASSERT_EQUAL( u"42"_ustr, m_pDoc->GetString(0,2,0));
//CPPUNIT_ASSERT_EQUAL( OUString("FALSE"), m_pDoc->GetString(1,2,0)); // not yet
CPPUNIT_ASSERT_EQUAL( u"FALSE"_ustr, m_pDoc->GetString(0,3,0));
CPPUNIT_ASSERT_EQUAL( u"42"_ustr, m_pDoc->GetString(1,3,0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testEnterMixedMatrix)
{
m_pDoc->InsertTab(0, u"foo"_ustr);
// Insert the source values in A1:B2.
m_pDoc->SetString(0, 0, 0, u"A"_ustr);
m_pDoc->SetString(1, 0, 0, u"B"_ustr);
double val = 1.0;
m_pDoc->SetValue(0, 1, 0, val);
val = 2.0;
m_pDoc->SetValue(1, 1, 0, val);
// Create a matrix range in A4:B5 referencing A1:B2.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
m_pDoc->InsertMatrixFormula(0, 3, 1, 4, aMark, u"=A1:B2"_ustr);
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,0,0), m_pDoc->GetString(0,3,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(1,0,0), m_pDoc->GetString(1,3,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetValue(0,1,0), m_pDoc->GetValue(0,4,0));
CPPUNIT_ASSERT_EQUAL(m_pDoc->GetValue(1,1,0), m_pDoc->GetValue(1,4,0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMatrixEditable)
{
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on.
m_pDoc->InsertTab(0, u"Test"_ustr);
// Values in A1:B1.
m_pDoc->SetValue(ScAddress(0,0,0), 1.0);
m_pDoc->SetValue(ScAddress(1,0,0), 2.0);
// A2 is a normal formula.
m_pDoc->SetString(ScAddress(0,1,0), u"=5"_ustr);
// A3:A4 is a matrix.
ScRange aMatRange(0,2,0,0,3,0);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SetMarkArea(aMatRange);
m_pDoc->InsertMatrixFormula(0, 2, 0, 3, aMark, u"=TRANSPOSE(A1:B1)"_ustr);
// Check their values.
CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,2,0)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,3,0)));
// Make sure A3:A4 is a matrix.
ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0,2,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL_MESSAGE("A3 should be matrix origin.",
ScMatrixMode::Formula, pFC->GetMatrixFlag());
pFC = m_pDoc->GetFormulaCell(ScAddress(0,3,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL_MESSAGE("A4 should be matrix reference.",
ScMatrixMode::Reference, pFC->GetMatrixFlag());
// Check to make sure A3:A4 combined is editable.
ScEditableTester aTester;
aTester.TestSelection(*m_pDoc, aMark);
CPPUNIT_ASSERT(aTester.IsEditable());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testCellCopy)
{
m_pDoc->InsertTab(0, u"TestTab"_ustr);
ScAddress aSrc(0,0,0);
ScAddress aDest(0,1,0);
OUString aStr(u"please copy me"_ustr);
m_pDoc->SetString(aSrc, u"please copy me"_ustr);
CPPUNIT_ASSERT_EQUAL(aStr, m_pDoc->GetString(aSrc));
// copy to self - why not ?
m_pDoc->CopyCellToDocument(aSrc,aDest,*m_pDoc);
CPPUNIT_ASSERT_EQUAL(aStr, m_pDoc->GetString(aDest));
}
CPPUNIT_TEST_FIXTURE(Test, testSheetCopy)
{
m_pDoc->InsertTab(0, u"TestTab"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document should have one sheet to begin with.",
static_cast<SCTAB>(1), m_pDoc->GetTableCount());
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
// Insert text in A1.
m_pDoc->SetString(ScAddress(0,0,0), u"copy me"_ustr);
// Insert edit cells in B1:B3.
ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
rEE.SetTextCurrentDefaults(u"Edit 1"_ustr);
m_pDoc->SetEditText(ScAddress(1,0,0), rEE.CreateTextObject());
rEE.SetTextCurrentDefaults(u"Edit 2"_ustr);
m_pDoc->SetEditText(ScAddress(1,1,0), rEE.CreateTextObject());
rEE.SetTextCurrentDefaults(u"Edit 3"_ustr);
m_pDoc->SetEditText(ScAddress(1,2,0), rEE.CreateTextObject());
SCROW nRow1, nRow2;
bool bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("new sheet should have all rows visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", m_pDoc->MaxRow(), nRow2);
// insert a note
ScAddress aAdrA1 (0,2,0); // empty cell content.
ScPostIt *pNoteA1 = m_pDoc->GetOrCreateNote(aAdrA1);
pNoteA1->SetText(aAdrA1, u"Hello world in A3"_ustr);
// Copy and test the result.
m_pDoc->CopyTab(0, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document now should have two sheets.",
static_cast<SCTAB>(2), m_pDoc->GetTableCount());
bHidden = m_pDoc->RowHidden(0, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("copied sheet should also have all rows visible as the original.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("copied sheet should also have all rows visible as the original.", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("copied sheet should also have all rows visible as the original.", m_pDoc->MaxRow(), nRow2);
CPPUNIT_ASSERT_MESSAGE("There should be note on A3 in new sheet", m_pDoc->HasNote(ScAddress(0,2,1)));
CPPUNIT_ASSERT_EQUAL(u"copy me"_ustr, m_pDoc->GetString(ScAddress(0,0,1)));
// Check the copied edit cells.
const EditTextObject* pEditObj = m_pDoc->GetEditText(ScAddress(1,0,1));
CPPUNIT_ASSERT_MESSAGE("There should be an edit cell in B1.", pEditObj);
CPPUNIT_ASSERT_EQUAL(u"Edit 1"_ustr, pEditObj->GetText(0));
pEditObj = m_pDoc->GetEditText(ScAddress(1,1,1));
CPPUNIT_ASSERT_MESSAGE("There should be an edit cell in B2.", pEditObj);
CPPUNIT_ASSERT_EQUAL(u"Edit 2"_ustr, pEditObj->GetText(0));
pEditObj = m_pDoc->GetEditText(ScAddress(1,2,1));
CPPUNIT_ASSERT_MESSAGE("There should be an edit cell in B3.", pEditObj);
CPPUNIT_ASSERT_EQUAL(u"Edit 3"_ustr, pEditObj->GetText(0));
m_pDoc->DeleteTab(1);
m_pDoc->SetRowHidden(5, 10, 0, true);
bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 0 - 4 should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(4), nRow2);
bHidden = m_pDoc->RowHidden(5, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 5 - 10 should be hidden", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(5), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(10), nRow2);
bHidden = m_pDoc->RowHidden(11, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 11 - maxrow should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", SCROW(11), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", m_pDoc->MaxRow(), nRow2);
// Copy the sheet once again.
m_pDoc->CopyTab(0, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document now should have two sheets.",
static_cast<SCTAB>(2), m_pDoc->GetTableCount());
bHidden = m_pDoc->RowHidden(0, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 0 - 4 should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(4), nRow2);
bHidden = m_pDoc->RowHidden(5, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 5 - 10 should be hidden", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(5), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(10), nRow2);
bHidden = m_pDoc->RowHidden(11, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 11 - maxrow should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", SCROW(11), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", m_pDoc->MaxRow(), nRow2);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testSheetMove)
{
m_pDoc->InsertTab(0, u"TestTab1"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document should have one sheet to begin with.", static_cast<SCTAB>(1), m_pDoc->GetTableCount());
SCROW nRow1, nRow2;
bool bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("new sheet should have all rows visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", m_pDoc->MaxRow(), nRow2);
//test if inserting before another sheet works
m_pDoc->InsertTab(0, u"TestTab2"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document should have two sheets", static_cast<SCTAB>(2), m_pDoc->GetTableCount());
bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("new sheet should have all rows visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", m_pDoc->MaxRow(), nRow2);
// Move and test the result.
m_pDoc->MoveTab(0, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document now should have two sheets.", static_cast<SCTAB>(2), m_pDoc->GetTableCount());
bHidden = m_pDoc->RowHidden(0, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("copied sheet should also have all rows visible as the original.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("copied sheet should also have all rows visible as the original.", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("copied sheet should also have all rows visible as the original.", m_pDoc->MaxRow(), nRow2);
OUString aName;
m_pDoc->GetName(0, aName);
CPPUNIT_ASSERT_EQUAL_MESSAGE( "sheets should have changed places", u"TestTab1"_ustr, aName);
m_pDoc->SetRowHidden(5, 10, 0, true);
bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 0 - 4 should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(4), nRow2);
bHidden = m_pDoc->RowHidden(5, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 5 - 10 should be hidden", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(5), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(10), nRow2);
bHidden = m_pDoc->RowHidden(11, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 11 - maxrow should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", SCROW(11), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", m_pDoc->MaxRow(), nRow2);
// Move the sheet once again.
m_pDoc->MoveTab(1, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document now should have two sheets.", static_cast<SCTAB>(2), m_pDoc->GetTableCount());
bHidden = m_pDoc->RowHidden(0, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 0 - 4 should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 0 - 4 should be visible", SCROW(4), nRow2);
bHidden = m_pDoc->RowHidden(5, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 5 - 10 should be hidden", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(5), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 5 - 10 should be hidden", SCROW(10), nRow2);
bHidden = m_pDoc->RowHidden(11, 1, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 11 - maxrow should be visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", SCROW(11), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 11 - maxrow should be visible", m_pDoc->MaxRow(), nRow2);
m_pDoc->GetName(0, aName);
CPPUNIT_ASSERT_EQUAL_MESSAGE( "sheets should have changed places", u"TestTab2"_ustr, aName);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testDataArea)
{
m_pDoc->InsertTab(0, u"Data"_ustr);
// Totally empty sheet should be rightfully considered empty in all accounts.
CPPUNIT_ASSERT_MESSAGE("Sheet is expected to be empty.", m_pDoc->IsPrintEmpty(0, 0, 100, 100, 0));
CPPUNIT_ASSERT_MESSAGE("Sheet is expected to be empty.", m_pDoc->IsBlockEmpty(0, 0, 100, 100, 0));
// Now, set borders in some cells...
::editeng::SvxBorderLine aLine(nullptr, 50, SvxBorderLineStyle::SOLID);
SvxBoxItem aBorderItem(ATTR_BORDER);
aBorderItem.SetLine(&aLine, SvxBoxItemLine::LEFT);
aBorderItem.SetLine(&aLine, SvxBoxItemLine::RIGHT);
for (SCROW i = 0; i < 100; ++i)
// Set borders from row 1 to 100.
m_pDoc->ApplyAttr(0, i, 0, aBorderItem);
// Now the sheet is considered non-empty for printing purposes, but still
// be empty in all the other cases.
CPPUNIT_ASSERT_MESSAGE("Empty sheet with borders should be printable.",
!m_pDoc->IsPrintEmpty(0, 0, 0, 100, 100));
CPPUNIT_ASSERT_MESSAGE("But it should still be considered empty in all the other cases.",
m_pDoc->IsBlockEmpty(0, 0, 100, 100, 0));
// Adding a real cell content should turn the block non-empty.
m_pDoc->SetString(0, 0, 0, u"Some text"_ustr);
CPPUNIT_ASSERT_MESSAGE("Now the block should not be empty with a real cell content.",
!m_pDoc->IsBlockEmpty(0, 0, 100, 100, 0));
// TODO: Add more tests for normal data area calculation.
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testStreamValid)
{
/**
* Make sure the sheet streams are invalidated properly.
*/
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->InsertTab(1, u"Sheet2"_ustr);
m_pDoc->InsertTab(2, u"Sheet3"_ustr);
m_pDoc->InsertTab(3, u"Sheet4"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("We should have 4 sheet instances.", static_cast<SCTAB>(4), m_pDoc->GetTableCount());
OUString a1(u"A1"_ustr);
OUString a2(u"A2"_ustr);
OUString test;
// Put values into Sheet1.
m_pDoc->SetString(0, 0, 0, a1);
m_pDoc->SetString(0, 1, 0, a2);
test = m_pDoc->GetString(0, 0, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected value in Sheet1.A1", test, a1);
test = m_pDoc->GetString(0, 1, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected value in Sheet1.A2", test, a2);
// Put formulas into Sheet2 to Sheet4 to reference values from Sheet1.
m_pDoc->SetString(0, 0, 1, u"=Sheet1.A1"_ustr);
m_pDoc->SetString(0, 1, 1, u"=Sheet1.A2"_ustr);
m_pDoc->SetString(0, 0, 2, u"=Sheet1.A1"_ustr);
m_pDoc->SetString(0, 0, 3, u"=Sheet1.A2"_ustr);
test = m_pDoc->GetString(0, 0, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected value in Sheet2.A1", test, a1);
test = m_pDoc->GetString(0, 1, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected value in Sheet2.A2", test, a2);
test = m_pDoc->GetString(0, 0, 2);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected value in Sheet3.A1", test, a1);
test = m_pDoc->GetString(0, 0, 3);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected value in Sheet3.A1", test, a2);
// Set all sheet streams valid after all the initial cell values are in
// place. In reality we need to have real XML streams stored in order to
// claim they are valid, but we are just testing the flag values here.
m_pDoc->SetStreamValid(0, true);
m_pDoc->SetStreamValid(1, true);
m_pDoc->SetStreamValid(2, true);
m_pDoc->SetStreamValid(3, true);
CPPUNIT_ASSERT_MESSAGE("Stream is expected to be valid.", m_pDoc->IsStreamValid(0));
CPPUNIT_ASSERT_MESSAGE("Stream is expected to be valid.", m_pDoc->IsStreamValid(1));
CPPUNIT_ASSERT_MESSAGE("Stream is expected to be valid.", m_pDoc->IsStreamValid(2));
CPPUNIT_ASSERT_MESSAGE("Stream is expected to be valid.", m_pDoc->IsStreamValid(3));
// Now, insert a new row at row 2 position on Sheet1. This will move cell
// A2 downward but cell A1 remains unmoved.
m_pDoc->InsertRow(0, 0, m_pDoc->MaxCol(), 0, 1, 2);
test = m_pDoc->GetString(0, 0, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Cell A1 should not have moved.", test, a1);
test = m_pDoc->GetString(0, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("the old cell A2 should now be at A4.", test, a2);
ScRefCellValue aCell;
aCell.assign(*m_pDoc, ScAddress(0,1,0));
CPPUNIT_ASSERT_MESSAGE("Cell A2 should be empty.", aCell.isEmpty());
aCell.assign(*m_pDoc, ScAddress(0,2,0));
CPPUNIT_ASSERT_MESSAGE("Cell A3 should be empty.", aCell.isEmpty());
// After the move, Sheet1, Sheet2, and Sheet4 should have their stream
// invalidated, whereas Sheet3's stream should still be valid.
CPPUNIT_ASSERT_MESSAGE("Stream should have been invalidated.", !m_pDoc->IsStreamValid(0));
CPPUNIT_ASSERT_MESSAGE("Stream should have been invalidated.", !m_pDoc->IsStreamValid(1));
CPPUNIT_ASSERT_MESSAGE("Stream should have been invalidated.", !m_pDoc->IsStreamValid(3));
CPPUNIT_ASSERT_MESSAGE("Stream should still be valid.", m_pDoc->IsStreamValid(2));
m_pDoc->DeleteTab(3);
m_pDoc->DeleteTab(2);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFunctionLists)
{
/**
* Test built-in cell functions to make sure their categories and order
* are correct.
*/
const char* aDataBase[] = {
"DAVERAGE",
"DCOUNT",
"DCOUNTA",
"DGET",
"DMAX",
"DMIN",
"DPRODUCT",
"DSTDEV",
"DSTDEVP",
"DSUM",
"DVAR",
"DVARP",
nullptr
};
const char* aDateTime[] = {
"DATE",
"DATEDIF",
"DATEVALUE",
"DAY",
"DAYS",
"DAYS360",
"DAYSINMONTH",
"DAYSINYEAR",
"EASTERSUNDAY",
"HOUR",
"ISLEAPYEAR",
"ISOWEEKNUM",
"MINUTE",
"MONTH",
"MONTHS",
"NETWORKDAYS",
"NETWORKDAYS.INTL",
"NOW",
"SECOND",
"TIME",
"TIMEVALUE",
"TODAY",
"WEEKDAY",
"WEEKNUM",
"WEEKNUM_OOO",
"WEEKS",
"WEEKSINYEAR",
"WORKDAY.INTL",
"YEAR",
"YEARS",
nullptr
};
const char* aFinancial[] = {
"CUMIPMT",
"CUMPRINC",
"DB",
"DDB",
"EFFECT",
"FV",
"IPMT",
"IRR",
"ISPMT",
"MIRR",
"NOMINAL",
"NPER",
"NPV",
"OPT_BARRIER",
"OPT_PROB_HIT",
"OPT_PROB_INMONEY",
"OPT_TOUCH",
"PDURATION",
"PMT",
"PPMT",
"PV",
"RATE",
"RRI",
"SLN",
"SYD",
"VDB",
nullptr
};
const char* aInformation[] = {
"CELL",
"CURRENT",
"FORMULA",
"INFO",
"ISBLANK",
"ISERR",
"ISERROR",
"ISEVEN",
"ISFORMULA",
"ISLOGICAL",
"ISNA",
"ISNONTEXT",
"ISNUMBER",
"ISODD",
"ISREF",
"ISTEXT",
"N",
"NA",
"TYPE",
nullptr
};
const char* aLogical[] = {
"AND",
"FALSE",
"IF",
"IFERROR",
"IFNA",
"IFS",
"NOT",
"OR",
"SWITCH",
"TRUE",
"XOR",
nullptr
};
const char* aMathematical[] = {
"ABS",
"ACOS",
"ACOSH",
"ACOT",
"ACOTH",
"AGGREGATE",
"ASIN",
"ASINH",
"ATAN",
"ATAN2",
"ATANH",
"BITAND",
"BITLSHIFT",
"BITOR",
"BITRSHIFT",
"BITXOR",
"CEILING",
"CEILING.MATH",
"CEILING.PRECISE",
"CEILING.XCL",
"COLOR",
"COMBIN",
"COMBINA",
"CONVERT_OOO",
"COS",
"COSH",
"COT",
"COTH",
"CSC",
"CSCH",
"DEGREES",
"EUROCONVERT",
"EVEN",
"EXP",
"FACT",
"FLOOR",
"FLOOR.MATH",
"FLOOR.PRECISE",
"FLOOR.XCL",
"GCD",
"INT",
"ISO.CEILING",
"LCM",
"LN",
"LOG",
"LOG10",
"MOD",
"ODD",
"PI",
"POWER",
"PRODUCT",
"RADIANS",
"RAND",
"RAND.NV",
"RANDARRAY",
"RANDBETWEEN.NV",
"RAWSUBTRACT",
"ROUND",
"ROUNDDOWN",
"ROUNDSIG",
"ROUNDUP",
"SEC",
"SECH",
"SIGN",
"SIN",
"SINH",
"SQRT",
"SUBTOTAL",
"SUM",
"SUMIF",
"SUMIFS",
"SUMSQ",
"TAN",
"TANH",
"TRUNC",
nullptr
};
const char* aArray[] = {
"FOURIER",
"FREQUENCY",
"GROWTH",
"LINEST",
"LOGEST",
"MDETERM",
"MINVERSE",
"MMULT",
"MUNIT",
"SEQUENCE",
"SUMPRODUCT",
"SUMX2MY2",
"SUMX2PY2",
"SUMXMY2",
"TRANSPOSE",
"TREND",
nullptr
};
const char* aStatistical[] = {
"AVEDEV",
"AVERAGE",
"AVERAGEA",
"AVERAGEIF",
"AVERAGEIFS",
"B",
"BETA.DIST",
"BETA.INV",
"BETADIST",
"BETAINV",
"BINOM.DIST",
"BINOM.INV",
"BINOMDIST",
"CHIDIST",
"CHIINV",
"CHISQ.DIST",
"CHISQ.DIST.RT",
"CHISQ.INV",
"CHISQ.INV.RT",
"CHISQ.TEST",
"CHISQDIST",
"CHISQINV",
"CHITEST",
"CONFIDENCE",
"CONFIDENCE.NORM",
"CONFIDENCE.T",
"CORREL",
"COUNT",
"COUNTA",
"COUNTBLANK",
"COUNTIF",
"COUNTIFS",
"COVAR",
"COVARIANCE.P",
"COVARIANCE.S",
"CRITBINOM",
"DEVSQ",
"ERF.PRECISE",
"ERFC.PRECISE",
"EXPON.DIST",
"EXPONDIST",
"F.DIST",
"F.DIST.RT",
"F.INV",
"F.INV.RT",
"F.TEST",
"FDIST",
"FINV",
"FISHER",
"FISHERINV",
"FORECAST",
"FORECAST.ETS.ADD",
"FORECAST.ETS.MULT",
"FORECAST.ETS.PI.ADD",
"FORECAST.ETS.PI.MULT",
"FORECAST.ETS.SEASONALITY",
"FORECAST.ETS.STAT.ADD",
"FORECAST.ETS.STAT.MULT",
"FORECAST.LINEAR",
"FTEST",
"GAMMA",
"GAMMA.DIST",
"GAMMA.INV",
"GAMMADIST",
"GAMMAINV",
"GAMMALN",
"GAMMALN.PRECISE",
"GAUSS",
"GEOMEAN",
"HARMEAN",
"HYPGEOM.DIST",
"HYPGEOMDIST",
"INTERCEPT",
"KURT",
"LARGE",
"LOGINV",
"LOGNORM.DIST",
"LOGNORM.INV",
"LOGNORMDIST",
"MAX",
"MAXA",
"MAXIFS",
"MEDIAN",
"MIN",
"MINA",
"MINIFS",
"MODE",
"MODE.MULT",
"MODE.SNGL",
"NEGBINOM.DIST",
"NEGBINOMDIST",
"NORM.DIST",
"NORM.INV",
"NORM.S.DIST",
"NORM.S.INV",
"NORMDIST",
"NORMINV",
"NORMSDIST",
"NORMSINV",
"PEARSON",
"PERCENTILE",
"PERCENTILE.EXC",
"PERCENTILE.INC",
"PERCENTRANK",
"PERCENTRANK.EXC",
"PERCENTRANK.INC",
"PERMUT",
"PERMUTATIONA",
"PHI",
"POISSON",
"POISSON.DIST",
"PROB",
"QUARTILE",
"QUARTILE.EXC",
"QUARTILE.INC",
"RANK",
"RANK.AVG",
"RANK.EQ",
"RSQ",
"SKEW",
"SKEWP",
"SLOPE",
"SMALL",
"STANDARDIZE",
"STDEV",
"STDEV.P",
"STDEV.S",
"STDEVA",
"STDEVP",
"STDEVPA",
"STEYX",
"T.DIST",
"T.DIST.2T",
"T.DIST.RT",
"T.INV",
"T.INV.2T",
"T.TEST",
"TDIST",
"TINV",
"TRIMMEAN",
"TTEST",
"VAR",
"VAR.P",
"VAR.S",
"VARA",
"VARP",
"VARPA",
"WEIBULL",
"WEIBULL.DIST",
"Z.TEST",
"ZTEST",
nullptr
};
const char* aSpreadsheet[] = {
"ADDRESS",
"AREAS",
"CHOOSE",
"COLUMN",
"COLUMNS",
"DDE",
"ERROR.TYPE",
"ERRORTYPE",
"FILTER",
"GETPIVOTDATA",
"HLOOKUP",
"HYPERLINK",
"INDEX",
"INDIRECT",
"LET",
"LOOKUP",
"MATCH",
"OFFSET",
"ROW",
"ROWS",
"SHEET",
"SHEETS",
"SORT",
"SORTBY",
"STYLE",
"UNIQUE",
"VLOOKUP",
"XLOOKUP",
"XMATCH",
nullptr
};
const char* aText[] = {
"ARABIC",
"ASC",
"BAHTTEXT",
"BASE",
"CHAR",
"CLEAN",
"CODE",
"CONCAT",
"CONCATENATE",
"DECIMAL",
"DOLLAR",
"ENCODEURL",
"EXACT",
"FILTERXML",
"FIND",
"FINDB",
"FIXED",
"JIS",
"LEFT",
"LEFTB",
"LEN",
"LENB",
"LOWER",
"MID",
"MIDB",
"NUMBERVALUE",
"PROPER",
"REGEX",
"REPLACE",
"REPLACEB",
"REPT",
"RIGHT",
"RIGHTB",
"ROMAN",
"ROT13",
"SEARCH",
"SEARCHB",
"SUBSTITUTE",
"T",
"TEXT",
"TEXTJOIN",
"TRIM",
"UNICHAR",
"UNICODE",
"UPPER",
"VALUE",
"WEBSERVICE",
nullptr
};
struct {
const char* Category; const char** Functions;
} aTests[] = {
{ "Database", aDataBase },
{ "Date&Time", aDateTime },
{ "Financial", aFinancial },
{ "Information", aInformation },
{ "Logical", aLogical },
{ "Mathematical", aMathematical },
{ "Array", aArray },
{ "Statistical", aStatistical },
{ "Spreadsheet", aSpreadsheet },
{ "Text", aText },
{ "Add-in", nullptr },
{ nullptr, nullptr }
};
ScFunctionMgr* pFuncMgr = ScGlobal::GetStarCalcFunctionMgr();
sal_uInt32 n = pFuncMgr->getCount();
for (sal_uInt32 i = 0; i < n; ++i)
{
const formula::IFunctionCategory* pCat = pFuncMgr->getCategory(i);
CPPUNIT_ASSERT_MESSAGE("Unexpected category name", pCat->getName().equalsAscii(aTests[i].Category));
sal_uInt32 nFuncCount = pCat->getCount();
for (sal_uInt32 j = 0; j < nFuncCount; ++j)
{
const formula::IFunctionDescription* pFunc = pCat->getFunction(j);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected function name", OUString::createFromAscii(aTests[i].Functions[j]), pFunc->getFunctionName());
}
}
}
CPPUNIT_TEST_FIXTURE(Test, testGraphicsInGroup)
{
m_pDoc->InsertTab(0, u"TestTab"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("document should have one sheet to begin with.",
static_cast<SCTAB>(1), m_pDoc->GetTableCount());
SCROW nRow1, nRow2;
bool bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("new sheet should have all rows visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", m_pDoc->MaxRow(), nRow2);
m_pDoc->InitDrawLayer();
ScDrawLayer *pDrawLayer = m_pDoc->GetDrawLayer();
CPPUNIT_ASSERT_MESSAGE("must have a draw layer", pDrawLayer != nullptr);
SdrPage* pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("must have a draw page", pPage != nullptr);
{
//Add a square
tools::Rectangle aOrigRect(2,2,100,100);
rtl::Reference<SdrRectObj> pObj = new SdrRectObj(*pDrawLayer, aOrigRect);
pPage->InsertObject(pObj.get());
const tools::Rectangle &rNewRect = pObj->GetLogicRect();
CPPUNIT_ASSERT_EQUAL_MESSAGE("must have equal position and size",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
ScDrawLayer::SetPageAnchored(*pObj);
//Use a range of rows guaranteed to include all of the square
m_pDoc->ShowRows(0, 100, 0, false);
m_pDoc->SetDrawPageSize(0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Should not change when page anchored",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
m_pDoc->ShowRows(0, 100, 0, true);
m_pDoc->SetDrawPageSize(0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Should not change when page anchored",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, true);
CPPUNIT_ASSERT_EQUAL_MESSAGE("That shouldn't change size or positioning",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
m_pDoc->ShowRows(0, 100, 0, false);
m_pDoc->SetDrawPageSize(0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Hiding should not change the logic rectangle",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
CPPUNIT_ASSERT_MESSAGE("Hiding should make invisible", !pObj->IsVisible());
m_pDoc->ShowRows(0, 100, 0, true);
m_pDoc->SetDrawPageSize(0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Should not change when cell anchored",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
CPPUNIT_ASSERT_MESSAGE("Show should make visible", pObj->IsVisible());
}
{
// Add a circle.
tools::Rectangle aOrigRect(10,10,210,210); // 200 x 200
rtl::Reference<SdrCircObj> pObj = new SdrCircObj(*pDrawLayer, SdrCircKind::Full, aOrigRect);
pPage->InsertObject(pObj.get());
const tools::Rectangle& rNewRect = pObj->GetLogicRect();
CPPUNIT_ASSERT_EQUAL_MESSAGE("Position and size of the circle shouldn't change when inserted into the page.",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, false);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Size changed when cell anchored. Not good.",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
// Insert 2 rows at the top. This should push the circle object down.
m_pDoc->InsertRow(0, 0, m_pDoc->MaxCol(), 0, 0, 2);
m_pDoc->SetDrawPageSize(0);
// Make sure the size of the circle is still identical.
CPPUNIT_ASSERT_EQUAL_MESSAGE("Size of the circle has changed, but shouldn't!",
aOrigRect.GetSize(), rNewRect.GetSize());
// Delete 2 rows at the top. This should bring the circle object to its original position.
m_pDoc->DeleteRow(0, 0, m_pDoc->MaxCol(), 0, 0, 2);
m_pDoc->SetDrawPageSize(0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to move back to its original position.",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
}
{
// Add a line.
basegfx::B2DPolygon aTempPoly;
Point aStartPos(10,300), aEndPos(110,200); // bottom-left to top-right.
tools::Rectangle aOrigRect(10,200,110,300); // 100 x 100
aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y()));
aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y()));
rtl::Reference<SdrPathObj> pObj = new SdrPathObj(*pDrawLayer, SdrObjKind::Line, basegfx::B2DPolyPolygon(aTempPoly));
pObj->NbcSetLogicRect(aOrigRect);
pPage->InsertObject(pObj.get());
const tools::Rectangle& rNewRect = pObj->GetLogicRect();
CPPUNIT_ASSERT_EQUAL_MESSAGE("Size differ.",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, false);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Size changed when cell-anchored. Not good.",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
// Insert 2 rows at the top and delete them immediately.
m_pDoc->InsertRow(0, 0, m_pDoc->MaxCol(), 0, 0, 2);
m_pDoc->DeleteRow(0, 0, m_pDoc->MaxCol(), 0, 0, 2);
m_pDoc->SetDrawPageSize(0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Size of a line object changed after row insertion and removal.",
const_cast<const tools::Rectangle &>(aOrigRect), rNewRect);
sal_Int32 n = pObj->GetPointCount();
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly 2 points in a line object.", static_cast<sal_Int32>(2), n);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Line shape has changed.",
aStartPos, pObj->GetPoint(0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("Line shape has changed.",
aEndPos, pObj->GetPoint(1));
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testGraphicsOnSheetMove)
{
m_pDoc->InsertTab(0, u"Tab1"_ustr);
m_pDoc->InsertTab(1, u"Tab2"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be only 2 sheets to begin with", static_cast<SCTAB>(2), m_pDoc->GetTableCount());
m_pDoc->InitDrawLayer();
ScDrawLayer* pDrawLayer = m_pDoc->GetDrawLayer();
CPPUNIT_ASSERT_MESSAGE("No drawing layer.", pDrawLayer);
SdrPage* pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("No page instance for the 1st sheet.", pPage);
// Insert an object.
tools::Rectangle aObjRect(2,2,100,100);
rtl::Reference<SdrObject> pObj = new SdrRectObj(*pDrawLayer, aObjRect);
pPage->InsertObject(pObj.get());
ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, false);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one object on the 1st sheet.", static_cast<size_t>(1), pPage->GetObjCount());
const ScDrawObjData* pData = ScDrawLayer::GetObjData(pObj.get());
CPPUNIT_ASSERT_MESSAGE("Object meta-data doesn't exist.", pData);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(0), pData->maStart.Tab());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(0), pData->maEnd.Tab());
pPage = pDrawLayer->GetPage(1);
CPPUNIT_ASSERT_MESSAGE("No page instance for the 2nd sheet.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("2nd sheet shouldn't have any object.", static_cast<size_t>(0), pPage->GetObjCount());
// Insert a new sheet at left-end, and make sure the object has moved to
// the 2nd page.
m_pDoc->InsertTab(0, u"NewTab"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be 3 sheets.", static_cast<SCTAB>(3), m_pDoc->GetTableCount());
pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("1st sheet should have no object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("1st sheet should have no object.", size_t(0), pPage->GetObjCount());
pPage = pDrawLayer->GetPage(1);
CPPUNIT_ASSERT_MESSAGE("2nd sheet should have one object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("2nd sheet should have one object.", size_t(1), pPage->GetObjCount());
pPage = pDrawLayer->GetPage(2);
CPPUNIT_ASSERT_MESSAGE("3rd sheet should have no object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("3rd sheet should have no object.", size_t(0), pPage->GetObjCount());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(1), pData->maStart.Tab());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(1), pData->maEnd.Tab());
// Now, delete the sheet that just got inserted. The object should be back
// on the 1st sheet.
m_pDoc->DeleteTab(0);
pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("1st sheet should have one object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("1st sheet should have one object.", size_t(1), pPage->GetObjCount());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Size and position of the object shouldn't change.",
aObjRect, pObj->GetLogicRect());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(0), pData->maStart.Tab());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(0), pData->maEnd.Tab());
// Move the 1st sheet to the last position.
m_pDoc->MoveTab(0, 1);
pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("1st sheet should have no object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("1st sheet should have no object.", size_t(0), pPage->GetObjCount());
pPage = pDrawLayer->GetPage(1);
CPPUNIT_ASSERT_MESSAGE("2nd sheet should have one object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("2nd sheet should have one object.", size_t(1), pPage->GetObjCount());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(1), pData->maStart.Tab());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(1), pData->maEnd.Tab());
// Copy the 2nd sheet, which has one drawing object to the last position.
m_pDoc->CopyTab(1, 2);
pPage = pDrawLayer->GetPage(2);
CPPUNIT_ASSERT_MESSAGE("Copied sheet should have one object.", pPage);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Copied sheet should have one object.", size_t(1), pPage->GetObjCount());
pObj = pPage->GetObj(0);
CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object.", pObj);
pData = ScDrawLayer::GetObjData(pObj.get());
CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object meta-data.", pData);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(2), pData->maStart.Tab());
CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong sheet ID in cell anchor data!", SCTAB(2), pData->maEnd.Tab());
m_pDoc->DeleteTab(2);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testToggleRefFlag)
{
/**
* Test toggling relative/absolute flag of cell and cell range references.
* This corresponds with hitting Shift-F4 while the cursor is on a formula
* cell.
*/
// In this test, there is no need to insert formula string into a cell in
// the document, as ScRefFinder does not depend on the content of the
// document except for the sheet names.
m_pDoc->InsertTab(0, u"Test"_ustr);
{
// Calc A1: basic 2D reference
OUString aFormula(u"=B100"_ustr);
ScAddress aPos(1, 5, 0);
ScRefFinder aFinder(aFormula, aPos, *m_pDoc, formula::FormulaGrammar::CONV_OOO);
// Original
CPPUNIT_ASSERT_EQUAL_MESSAGE("Does not equal the original text.", aFormula, aFinder.GetText());
// column relative / row relative -> column absolute / row absolute
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", u"=$B$100"_ustr, aFormula );
// column absolute / row absolute -> column relative / row absolute
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", u"=B$100"_ustr, aFormula );
// column relative / row absolute -> column absolute / row relative
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", u"=$B100"_ustr, aFormula );
// column absolute / row relative -> column relative / row relative
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", u"=B100"_ustr, aFormula );
}
{
// Excel R1C1: basic 2D reference
OUString aFormula(u"=R2C1"_ustr);
ScAddress aPos(3, 5, 0);
ScRefFinder aFinder(aFormula, aPos, *m_pDoc, formula::FormulaGrammar::CONV_XL_R1C1);
// Original
CPPUNIT_ASSERT_EQUAL_MESSAGE("Does not equal the original text.", aFormula, aFinder.GetText());
// column absolute / row absolute -> column relative / row absolute
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R2C[-3]"_ustr, aFormula);
// column relative / row absolute - > column absolute / row relative
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R[-4]C1"_ustr, aFormula);
// column absolute / row relative -> column relative / row relative
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R[-4]C[-3]"_ustr, aFormula);
// column relative / row relative -> column absolute / row absolute
aFinder.ToggleRel(0, aFormula.getLength());
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R2C1"_ustr, aFormula);
}
{
// Excel R1C1: Selection at the end of the formula string and does not
// overlap the formula string at all (inspired by fdo#39135).
OUString aFormula(u"=R1C1"_ustr);
ScAddress aPos(1, 1, 0);
ScRefFinder aFinder(aFormula, aPos, *m_pDoc, formula::FormulaGrammar::CONV_XL_R1C1);
// Original
CPPUNIT_ASSERT_EQUAL(aFormula, aFinder.GetText());
// Make the column relative.
sal_Int32 n = aFormula.getLength();
aFinder.ToggleRel(n, n);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R1C[-1]"_ustr, aFormula);
// Make the row relative.
n = aFormula.getLength();
aFinder.ToggleRel(n, n);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R[-1]C1"_ustr, aFormula);
// Make both relative.
n = aFormula.getLength();
aFinder.ToggleRel(n, n);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R[-1]C[-1]"_ustr, aFormula);
// Back to the original.
n = aFormula.getLength();
aFinder.ToggleRel(n, n);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=R1C1"_ustr, aFormula);
}
{
// Calc A1:
OUString aFormula(u"=A1+4"_ustr);
ScAddress aPos(1, 1, 0);
ScRefFinder aFinder(aFormula, aPos, *m_pDoc, formula::FormulaGrammar::CONV_OOO);
// Original
CPPUNIT_ASSERT_EQUAL(aFormula, aFinder.GetText());
// Set the cursor over the 'A1' part and toggle.
aFinder.ToggleRel(2, 2);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=$A$1+4"_ustr, aFormula);
aFinder.ToggleRel(2, 2);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=A$1+4"_ustr, aFormula);
aFinder.ToggleRel(2, 2);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=$A1+4"_ustr, aFormula);
aFinder.ToggleRel(2, 2);
aFormula = aFinder.GetText();
CPPUNIT_ASSERT_EQUAL(u"=A1+4"_ustr, aFormula);
}
// TODO: Add more test cases esp. for 3D references, Excel A1 syntax, and
// partial selection within formula string.
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAutofilter)
{
m_pDoc->InsertTab( 0, u"Test"_ustr );
// cell contents (0 = empty cell)
const char* aData[][3] = {
{ "C1", "C2", "C3" },
{ "0", "1", "A" },
{ "1", "2", nullptr },
{ "1", "2", "B" },
{ "0", "2", "B" }
};
SCCOL nCols = std::size(aData[0]);
SCROW nRows = std::size(aData);
// Populate cells.
for (SCROW i = 0; i < nRows; ++i)
for (SCCOL j = 0; j < nCols; ++j)
if (aData[i][j])
m_pDoc->SetString(j, i, 0, OUString::createFromAscii(aData[i][j]));
ScDBData* pDBData = new ScDBData(u"NONAME"_ustr, 0, 0, 0, nCols-1, nRows-1);
m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(pDBData));
pDBData->SetAutoFilter(true);
ScRange aRange;
pDBData->GetArea(aRange);
m_pDoc->ApplyFlagsTab( aRange.aStart.Col(), aRange.aStart.Row(),
aRange.aEnd.Col(), aRange.aStart.Row(),
aRange.aStart.Tab(), ScMF::Auto);
//create the query param
ScQueryParam aParam;
pDBData->GetQueryParam(aParam);
ScQueryEntry& rEntry = aParam.GetEntry(0);
rEntry.bDoQuery = true;
rEntry.nField = 0;
rEntry.eOp = SC_EQUAL;
rEntry.GetQueryItem().mfVal = 0;
// add queryParam to database range.
pDBData->SetQueryParam(aParam);
// perform the query.
m_pDoc->Query(0, aParam, true);
//control output
SCROW nRow1, nRow2;
bool bHidden = m_pDoc->RowHidden(2, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 2 & 3 should be hidden", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 2 & 3 should be hidden", SCROW(2), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 2 & 3 should be hidden", SCROW(3), nRow2);
// Remove filtering.
rEntry.Clear();
m_pDoc->Query(0, aParam, true);
bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("All rows should be shown.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("All rows should be shown.", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("All rows should be shown.", m_pDoc->MaxRow(), nRow2);
// Filter for non-empty cells by column C.
rEntry.bDoQuery = true;
rEntry.nField = 2;
rEntry.SetQueryByNonEmpty();
m_pDoc->Query(0, aParam, true);
// only row 3 should be hidden. The rest should be visible.
bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 1 & 2 should be visible.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 1 & 2 should be visible.", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 1 & 2 should be visible.", SCROW(1), nRow2);
bHidden = m_pDoc->RowHidden(2, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("row 3 should be hidden.", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 3 should be hidden.", SCROW(2), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 3 should be hidden.", SCROW(2), nRow2);
bHidden = m_pDoc->RowHidden(3, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("row 4 and down should be visible.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 4 and down should be visible.", SCROW(3), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 4 and down should be visible.", m_pDoc->MaxRow(), nRow2);
// Now, filter for empty cells by column C.
rEntry.SetQueryByEmpty();
m_pDoc->Query(0, aParam, true);
// Now, only row 1 and 3, and 6 and down should be visible.
bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("row 1 should be visible.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 1 should be visible.", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 1 should be visible.", SCROW(0), nRow2);
bHidden = m_pDoc->RowHidden(1, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("row 2 should be hidden.", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 2 should be hidden.", SCROW(1), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 2 should be hidden.", SCROW(1), nRow2);
bHidden = m_pDoc->RowHidden(2, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("row 3 should be visible.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 3 should be visible.", SCROW(2), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("row 3 should be visible.", SCROW(2), nRow2);
bHidden = m_pDoc->RowHidden(3, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 4 & 5 should be hidden.", bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 4 & 5 should be hidden.", SCROW(3), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 4 & 5 should be hidden.", SCROW(4), nRow2);
bHidden = m_pDoc->RowHidden(5, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 6 and down should be all visible.", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 6 and down should be all visible.", SCROW(5), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 6 and down should be all visible.", m_pDoc->MaxRow(), nRow2);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAutoFilterTimeValue)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(0,0,0), u"Hours"_ustr);
m_pDoc->SetValue(ScAddress(0,1,0), 72.3604166666671);
m_pDoc->SetValue(ScAddress(0,2,0), 265);
ScDBData* pDBData = new ScDBData(STR_DB_GLOBAL_NONAME, 0, 0, 0, 0, 2);
m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(pDBData));
// Apply the "hour:minute:second" format to A2:A3.
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
sal_uInt32 nFormat = pFormatter->GetFormatIndex(NF_TIME_HH_MMSS, LANGUAGE_ENGLISH_US);
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
m_pDoc->ApplyPatternAreaTab(0, 1, 0, 2, 0, aNewAttrs); // apply it to A2:A3.
printRange(m_pDoc, ScRange(0,0,0,0,2,0), "Data"); // A1:A3
// Make sure the hour:minute:second format is really applied.
CPPUNIT_ASSERT_EQUAL(u"1736:39:00"_ustr, m_pDoc->GetString(ScAddress(0,1,0))); // A2
CPPUNIT_ASSERT_EQUAL(u"6360:00:00"_ustr, m_pDoc->GetString(ScAddress(0,2,0))); // A3
// Filter by the A2 value. Only A1 and A2 should be visible.
ScQueryParam aParam;
pDBData->GetQueryParam(aParam);
ScQueryEntry& rEntry = aParam.GetEntry(0);
rEntry.bDoQuery = true;
rEntry.nField = 0;
rEntry.eOp = SC_EQUAL;
rEntry.GetQueryItem().maString = m_pDoc->GetSharedStringPool().intern(u"1736:39:00"_ustr);
rEntry.GetQueryItem().meType = ScQueryEntry::ByString;
pDBData->SetQueryParam(aParam);
// perform the query.
m_pDoc->Query(0, aParam, true);
// A1:A2 should be visible while A3 should be filtered out.
CPPUNIT_ASSERT_MESSAGE("A1 should be visible.", !m_pDoc->RowFiltered(0,0));
CPPUNIT_ASSERT_MESSAGE("A2 should be visible.", !m_pDoc->RowFiltered(1,0));
CPPUNIT_ASSERT_MESSAGE("A3 should be filtered out.", m_pDoc->RowFiltered(2,0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAutofilterOptimizations)
{
m_pDoc->InsertTab( 0, u"Test"_ustr );
constexpr SCCOL nCols = 4;
constexpr SCROW nRows = 200;
m_pDoc->SetString(0, 0, 0, u"Column1"_ustr);
m_pDoc->SetString(1, 0, 0, u"Column2"_ustr);
m_pDoc->SetString(2, 0, 0, u"Column3"_ustr);
m_pDoc->SetString(3, 0, 0, u"Column4"_ustr);
// Fill 1st column with 0-199, 2nd with 1-200, 3rd with "1000"-"1199", 4th with "1001-1200"
// (the pairs are off by one to each other to check filtering out a value filters out
// only the relevant column).
for(SCROW i = 0; i < nRows; ++i)
{
m_pDoc->SetValue(0, i + 1, 0, i);
m_pDoc->SetValue(1, i + 1, 0, i+1);
m_pDoc->SetString(2, i + 1, 0, "val" + OUString::number(i+1000));
m_pDoc->SetString(3, i + 1, 0, "val" + OUString::number(i+1000+1));
}
ScDBData* pDBData = new ScDBData(u"NONAME"_ustr, 0, 0, 0, nCols, nRows);
m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(pDBData));
pDBData->SetAutoFilter(true);
ScRange aRange;
pDBData->GetArea(aRange);
m_pDoc->ApplyFlagsTab( aRange.aStart.Col(), aRange.aStart.Row(),
aRange.aEnd.Col(), aRange.aStart.Row(),
aRange.aStart.Tab(), ScMF::Auto);
//create the query param
ScQueryParam aParam;
pDBData->GetQueryParam(aParam);
ScQueryEntry& rEntry0 = aParam.GetEntry(0);
rEntry0.bDoQuery = true;
rEntry0.nField = 0;
rEntry0.eOp = SC_EQUAL;
rEntry0.GetQueryItems().resize(nRows);
ScQueryEntry& rEntry1 = aParam.GetEntry(1);
rEntry1.bDoQuery = true;
rEntry1.nField = 1;
rEntry1.eOp = SC_EQUAL;
rEntry1.GetQueryItems().resize(nRows);
ScQueryEntry& rEntry2 = aParam.GetEntry(2);
rEntry2.bDoQuery = true;
rEntry2.nField = 2;
rEntry2.eOp = SC_EQUAL;
rEntry2.GetQueryItems().resize(nRows);
ScQueryEntry& rEntry3 = aParam.GetEntry(3);
rEntry3.bDoQuery = true;
rEntry3.nField = 3;
rEntry3.eOp = SC_EQUAL;
rEntry3.GetQueryItems().resize(nRows);
// Set up autofilter to select all values except one in each column.
// This should only filter out 2nd, 3rd, 6th and 7th rows.
for( int i = 0; i < nRows; ++i )
{
if(i!= 1)
rEntry0.GetQueryItems()[i].mfVal = i;
if(i!= 2)
rEntry1.GetQueryItems()[i].mfVal = i + 1;
if(i!= 5)
{
rEntry2.GetQueryItems()[i].maString = m_pDoc->GetSharedStringPool().intern("val" + OUString::number(i+1000));
rEntry2.GetQueryItems()[i].meType = ScQueryEntry::ByString;
}
if(i!= 6)
{
rEntry3.GetQueryItems()[i].maString = m_pDoc->GetSharedStringPool().intern("val" + OUString::number(i+1000+1));
rEntry3.GetQueryItems()[i].meType = ScQueryEntry::ByString;
}
}
// add queryParam to database range.
pDBData->SetQueryParam(aParam);
// perform the query.
m_pDoc->Query(0, aParam, true);
// check that only rows with filtered out values are hidden, and not rows that share
// a value in a different column
SCROW nRow1, nRow2;
CPPUNIT_ASSERT_MESSAGE("row 2 should be visible", !m_pDoc->RowHidden(1, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 3 should be hidden", m_pDoc->RowHidden(2, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 4 should be hidden", m_pDoc->RowHidden(3, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 5 should be visible", !m_pDoc->RowHidden(4, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 6 should be visible", !m_pDoc->RowHidden(5, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 7 should be hidden", m_pDoc->RowHidden(6, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 8 should be hidden", m_pDoc->RowHidden(7, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_MESSAGE("row 9 should be visible", !m_pDoc->RowHidden(8, 0, &nRow1, &nRow2));
// Remove filtering.
rEntry0.Clear();
rEntry1.Clear();
rEntry2.Clear();
m_pDoc->Query(0, aParam, true);
CPPUNIT_ASSERT_MESSAGE("All rows should be shown.", !m_pDoc->RowHidden(0, 0, &nRow1, &nRow2));
CPPUNIT_ASSERT_EQUAL_MESSAGE("All rows should be shown.", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("All rows should be shown.", m_pDoc->MaxRow(), nRow2);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf76441)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// The result will be different depending on whether the format is set before
// or after inserting the string
OUString aCode = u"MM:SS"_ustr;
sal_Int32 nCheckPos;
SvNumFormatType nType;
sal_uInt32 nFormat;
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
{
// First insert the string, then the format
m_pDoc->SetString(ScAddress(0,0,0), u"01:20"_ustr);
m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs);
CPPUNIT_ASSERT_EQUAL(u"20:00"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
}
{
// First set the format, then insert the string
m_pDoc->ApplyPattern(0, 1, 0, aNewAttrs);
m_pDoc->SetString(ScAddress(0,1,0), u"01:20"_ustr);
// Without the fix in place, this test would have failed with
// - Expected: 01:20
// - Actual : 20:00
CPPUNIT_ASSERT_EQUAL(u"01:20"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf76836)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
OUString aCode = u"\"192.168.0.\"@"_ustr;
sal_Int32 nCheckPos;
SvNumFormatType nType;
sal_uInt32 nFormat;
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs);
m_pDoc->SetValue(0,0,0, 10.0);
// Without the fix in place, this test would have failed with
// - Expected: 10
// - Actual : 192.168.0.10
CPPUNIT_ASSERT_EQUAL(u"10"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
m_pDoc->ApplyPattern(0, 1, 0, aNewAttrs);
m_pDoc->SetString(ScAddress(0,1,0), u"10"_ustr);
CPPUNIT_ASSERT_EQUAL(u"192.168.0.10"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf151752)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(0,0,0), u"66000:00"_ustr);
// Without the fix in place, this test would have failed with
// - Expected: 66000:00:00
// - Actual : 464:00:00
CPPUNIT_ASSERT_EQUAL(u"66000:00:00"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf142186)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// The result will be different depending on whether the format is set before
// or after inserting the string
OUString aCode = u"0\".\"0"_ustr;
sal_Int32 nCheckPos;
SvNumFormatType nType;
sal_uInt32 nFormat;
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
{
// First insert the string, then the format
m_pDoc->SetString(ScAddress(0,0,0), u"123.45"_ustr);
m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs);
CPPUNIT_ASSERT_EQUAL(u"12.3"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
}
{
// First set the format, then insert the string
m_pDoc->ApplyPattern(0, 1, 0, aNewAttrs);
m_pDoc->SetString(ScAddress(0,1,0), u"123.45"_ustr);
// Without the fix in place, this test would have failed with
// - Expected: 12.3
// - Actual : 1234.5
CPPUNIT_ASSERT_EQUAL(u"12.3"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf137063)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetValue(0,0,0, 0.000000006);
m_pDoc->SetValue(0,1,0, 0.0000000006);
// Without the fix in place, this test would have failed with
// - Expected: 0.000000006
// - Actual : 6E-09
CPPUNIT_ASSERT_EQUAL(u"0.000000006"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(u"6E-10"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf126342)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
OUString aCode = u"YYYY-MM-DD"_ustr;
sal_Int32 nCheckPos;
SvNumFormatType nType;
sal_uInt32 nFormat;
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs);
m_pDoc->SetString(ScAddress(0,0,0), u"11/7/19"_ustr);
CPPUNIT_ASSERT_EQUAL(u"2019-11-07"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
// Overwrite the existing date with the exact same input
m_pDoc->SetString(ScAddress(0,0,0), u"11/7/19"_ustr);
// Without the fix in place, this test would have failed with
// - Expected: 2019-11-07
// - Actual : 2011-07-19
CPPUNIT_ASSERT_EQUAL(u"2019-11-07"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAdvancedFilter)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// cell contents (nullptr = empty cell)
std::vector<std::vector<const char*>> aData = {
{ "Value", "Tag" }, // A1:B11
{ "1", "R" },
{ "2", "R" },
{ "3", "R" },
{ "4", "C" },
{ "5", "C" },
{ "6", "C" },
{ "7", "R" },
{ "8", "R" },
{ "9", "R" },
{ "10", "C" },
{ nullptr },
{ "Value", "Tag" }, // A13:B14
{ "> 5", "R" },
};
// Populate cells.
for (size_t nRow = 0; nRow < aData.size(); ++nRow)
{
const std::vector<const char*>& rRowData = aData[nRow];
for (size_t nCol = 0; nCol < rRowData.size(); ++nCol)
{
const char* pCell = rRowData[nCol];
if (pCell)
m_pDoc->SetString(nCol, nRow, 0, OUString::createFromAscii(pCell));
}
}
ScDBData* pDBData = new ScDBData(STR_DB_GLOBAL_NONAME, 0, 0, 0, 1, 10);
m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(pDBData));
ScRange aDataRange(0,0,0,1,10,0);
ScRange aFilterRuleRange(0,12,0,1,13,0);
printRange(m_pDoc, aDataRange, "Data");
printRange(m_pDoc, aFilterRuleRange, "Filter Rule");
ScQueryParam aQueryParam;
aQueryParam.bHasHeader = true;
aQueryParam.nCol1 = aDataRange.aStart.Col();
aQueryParam.nRow1 = aDataRange.aStart.Row();
aQueryParam.nCol2 = aDataRange.aEnd.Col();
aQueryParam.nRow2 = aDataRange.aEnd.Row();
aQueryParam.nTab = aDataRange.aStart.Tab();
bool bGood = m_pDoc->CreateQueryParam(aFilterRuleRange, aQueryParam);
CPPUNIT_ASSERT_MESSAGE("failed to create query param.", bGood);
// First entry is for the 'Value' field, and is greater than 5.
ScQueryEntry aEntry = aQueryParam.GetEntry(0);
CPPUNIT_ASSERT(aEntry.bDoQuery);
CPPUNIT_ASSERT_EQUAL(SCCOLROW(0), aEntry.nField);
CPPUNIT_ASSERT_EQUAL(SC_GREATER, aEntry.eOp);
ScQueryEntry::QueryItemsType aItems = aEntry.GetQueryItems();
CPPUNIT_ASSERT_EQUAL(size_t(1), aItems.size());
CPPUNIT_ASSERT_EQUAL(ScQueryEntry::ByValue, aItems[0].meType);
CPPUNIT_ASSERT_EQUAL(5.0, aItems[0].mfVal);
// Second entry is for the 'Tag' field, and is == 'R'.
aEntry = aQueryParam.GetEntry(1);
CPPUNIT_ASSERT(aEntry.bDoQuery);
CPPUNIT_ASSERT_EQUAL(SCCOLROW(1), aEntry.nField);
CPPUNIT_ASSERT_EQUAL(SC_EQUAL, aEntry.eOp);
aItems = aEntry.GetQueryItems();
CPPUNIT_ASSERT_EQUAL(size_t(1), aItems.size());
CPPUNIT_ASSERT_EQUAL(ScQueryEntry::ByString, aItems[0].meType);
CPPUNIT_ASSERT_EQUAL(u"R"_ustr, aItems[0].maString.getString());
// perform the query.
m_pDoc->Query(0, aQueryParam, true);
// Only rows 1,8-10 should be visible.
bool bFiltered = m_pDoc->RowFiltered(0, 0);
CPPUNIT_ASSERT_MESSAGE("row 1 (header row) should be visible", !bFiltered);
SCROW nRow1 = -1, nRow2 = -1;
bFiltered = m_pDoc->RowFiltered(1, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 2-7 should be filtered out.", bFiltered);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 2-7 should be filtered out.", SCROW(1), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 2-7 should be filtered out.", SCROW(6), nRow2);
bFiltered = m_pDoc->RowFiltered(7, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("rows 8-10 should be visible.", !bFiltered);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 8-10 should be visible.", SCROW(7), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("rows 8-10 should be visible.", SCROW(9), nRow2);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testDateFilterContains)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
constexpr SCCOL nCols = 1;
constexpr SCROW nRows = 5;
m_pDoc->SetString(0, 0, 0, u"Date"_ustr);
m_pDoc->SetString(0, 1, 0, u"1/2/2021"_ustr);
m_pDoc->SetString(0, 2, 0, u"2/1/1999"_ustr);
m_pDoc->SetString(0, 3, 0, u"2/1/1997"_ustr);
m_pDoc->SetString(0, 4, 0, u"3/3/2001"_ustr);
m_pDoc->SetString(0, 5, 0, u"3/3/1996"_ustr);
// Set the fields as dates.
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
sal_uInt32 nFormat = pFormatter->GetFormatIndex(NF_DATE_DIN_YYMMDD, LANGUAGE_ENGLISH_US);
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
m_pDoc->ApplyPatternAreaTab(0, 1, 0, 5, 0, aNewAttrs); // apply it to A1:A6
ScDBData* pDBData = new ScDBData(u"NONAME"_ustr, 0, 0, 0, nCols, nRows);
m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(pDBData));
pDBData->SetAutoFilter(true);
ScRange aRange;
pDBData->GetArea(aRange);
m_pDoc->ApplyFlagsTab( aRange.aStart.Col(), aRange.aStart.Row(),
aRange.aEnd.Col(), aRange.aStart.Row(),
aRange.aStart.Tab(), ScMF::Auto);
//create the query param
ScQueryParam aParam;
pDBData->GetQueryParam(aParam);
ScQueryEntry& rEntry = aParam.GetEntry(0);
rEntry.bDoQuery = true;
rEntry.nField = 0;
rEntry.eOp = SC_CONTAINS;
rEntry.GetQueryItem().maString = m_pDoc->GetSharedStringPool().intern(u"2"_ustr);
pDBData->SetQueryParam(aParam);
// perform the query.
m_pDoc->Query(0, aParam, true);
// Dates in rows 2-4 contain '2', row 5 shows 2001 only as 01, and row 6 doesn't contain it at all.
CPPUNIT_ASSERT_MESSAGE("row 2 should be visible", !m_pDoc->RowHidden(1, 0));
CPPUNIT_ASSERT_MESSAGE("row 3 should be visible", !m_pDoc->RowHidden(2, 0));
CPPUNIT_ASSERT_MESSAGE("row 4 should be visible", !m_pDoc->RowHidden(3, 0));
CPPUNIT_ASSERT_MESSAGE("row 5 should be hidden", m_pDoc->RowHidden(4, 0));
CPPUNIT_ASSERT_MESSAGE("row 6 should be hidden", m_pDoc->RowHidden(5, 0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf98642)
{
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->SetString(0, 0, 0, u"test"_ustr);
ScRangeData* pName1 = new ScRangeData( *m_pDoc, u"name1"_ustr, u"$Sheet1.$A$1"_ustr);
ScRangeData* pName2 = new ScRangeData( *m_pDoc, u"name2"_ustr, u"$Sheet1.$A$1"_ustr);
std::unique_ptr<ScRangeName> pGlobalRangeName(new ScRangeName());
pGlobalRangeName->insert(pName1);
pGlobalRangeName->insert(pName2);
m_pDoc->SetRangeName(std::move(pGlobalRangeName));
m_pDoc->SetString(1, 0, 0, u"=name1"_ustr);
m_pDoc->SetString(1, 1, 0, u"=name2"_ustr);
CPPUNIT_ASSERT_EQUAL(u"test"_ustr, m_pDoc->GetString(1, 0, 0));
CPPUNIT_ASSERT_EQUAL(u"test"_ustr, m_pDoc->GetString(1, 1, 0));
OUString aFormula = m_pDoc->GetFormula(1,0,0);
CPPUNIT_ASSERT_EQUAL(u"=name1"_ustr, aFormula);
aFormula = m_pDoc->GetFormula(1,1,0);
// Without the fix in place, this test would have failed with
// - Expected: =name2
// - Actual : =name1
CPPUNIT_ASSERT_EQUAL(u"=name2"_ustr, aFormula);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMergedCells)
{
//test merge and unmerge
//TODO: an undo/redo test for this would be a good idea
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->DoMerge(1, 1, 3, 3, 0, false);
SCCOL nEndCol = 1;
SCROW nEndRow = 1;
m_pDoc->ExtendMerge( 1, 1, nEndCol, nEndRow, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("did not merge cells", SCCOL(3), nEndCol);
CPPUNIT_ASSERT_EQUAL_MESSAGE("did not merge cells", SCROW(3), nEndRow);
ScRange aRange(0,2,0,m_pDoc->MaxCol(),2,0);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SetMarkArea(aRange);
m_xDocShell->GetDocFunc().InsertCells(aRange, &aMark, INS_INSROWS_BEFORE, true, true);
m_pDoc->ExtendMerge(1, 1, nEndCol, nEndRow, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("did not increase merge area", SCCOL(3), nEndCol);
CPPUNIT_ASSERT_EQUAL_MESSAGE("did not increase merge area", SCROW(4), nEndRow);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testRenameTable)
{
//test set rename table
//TODO: set name1 and name2 and do an undo to check if name 1 is set now
//TODO: also check if new name for table is same as another table
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->InsertTab(1, u"Sheet2"_ustr);
//test case 1 , rename table2 to sheet 1, it should return error
OUString nameToSet = u"Sheet1"_ustr;
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
CPPUNIT_ASSERT_MESSAGE("name same as another table is being set", !rDocFunc.RenameTable(1,nameToSet,false,true) );
//test case 2 , simple rename to check name
nameToSet = "test1";
m_xDocShell->GetDocFunc().RenameTable(0,nameToSet,false,true);
OUString nameJustSet;
m_pDoc->GetName(0,nameJustSet);
CPPUNIT_ASSERT_EQUAL_MESSAGE("table not renamed", nameToSet, nameJustSet);
//test case 3 , rename again
OUString anOldName;
m_pDoc->GetName(0,anOldName);
nameToSet = "test2";
rDocFunc.RenameTable(0,nameToSet,false,true);
m_pDoc->GetName(0,nameJustSet);
CPPUNIT_ASSERT_EQUAL_MESSAGE("table not renamed", nameToSet, nameJustSet);
//test case 4 , check if undo works
SfxUndoAction* pUndo = new ScUndoRenameTab(m_xDocShell.get(),0,anOldName,nameToSet);
pUndo->Undo();
m_pDoc->GetName(0,nameJustSet);
CPPUNIT_ASSERT_EQUAL_MESSAGE("the correct name is not set after undo", nameJustSet, anOldName);
pUndo->Redo();
m_pDoc->GetName(0,nameJustSet);
CPPUNIT_ASSERT_EQUAL_MESSAGE("the correct color is not set after redo", nameJustSet, nameToSet);
delete pUndo;
m_pDoc->DeleteTab(0);
m_pDoc->DeleteTab(1);
}
CPPUNIT_TEST_FIXTURE(Test, testSetBackgroundColor)
{
//test set background color
//TODO: set color1 and set color2 and do an undo to check if color1 is set now.
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
Color aColor;
//test yellow
aColor=COL_YELLOW;
m_xDocShell->GetDocFunc().SetTabBgColor(0,aColor,false, true);
CPPUNIT_ASSERT_EQUAL_MESSAGE("the correct color is not set",
aColor, m_pDoc->GetTabBgColor(0));
Color aOldTabBgColor=m_pDoc->GetTabBgColor(0);
aColor = COL_BLUE;
m_xDocShell->GetDocFunc().SetTabBgColor(0,aColor,false, true);
CPPUNIT_ASSERT_EQUAL_MESSAGE("the correct color is not set the second time",
aColor, m_pDoc->GetTabBgColor(0));
//now check for undo
SfxUndoAction* pUndo = new ScUndoTabColor(m_xDocShell.get(), 0, aOldTabBgColor, aColor);
pUndo->Undo();
CPPUNIT_ASSERT_EQUAL_MESSAGE("the correct color is not set after undo", aOldTabBgColor, m_pDoc->GetTabBgColor(0));
pUndo->Redo();
CPPUNIT_ASSERT_EQUAL_MESSAGE("the correct color is not set after undo", aColor, m_pDoc->GetTabBgColor(0));
delete pUndo;
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testUpdateReference)
{
//test that formulas are correctly updated during sheet delete
//TODO: add tests for relative references, updating of named ranges, ...
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->InsertTab(1, u"Sheet2"_ustr);
m_pDoc->InsertTab(2, u"Sheet3"_ustr);
m_pDoc->InsertTab(3, u"Sheet4"_ustr);
m_pDoc->SetValue(0,0,2, 1);
m_pDoc->SetValue(1,0,2, 2);
m_pDoc->SetValue(1,1,3, 4);
m_pDoc->SetString(2,0,2, u"=A1+B1"_ustr);
m_pDoc->SetString(2,1,2, u"=Sheet4.B2+A1"_ustr);
double aValue;
aValue = m_pDoc->GetValue(2,0,2);
ASSERT_DOUBLES_EQUAL_MESSAGE("formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2,1,2);
ASSERT_DOUBLES_EQUAL_MESSAGE("formula does not return correct result", 5, aValue);
//test deleting both sheets: one is not directly before the sheet, the other one is
m_pDoc->DeleteTab(0);
aValue = m_pDoc->GetValue(2,0,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting first sheet formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2,1,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting first sheet formula does not return correct result", 5, aValue);
m_pDoc->DeleteTab(0);
aValue = m_pDoc->GetValue(2,0,0);
ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting second sheet formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2,1,0);
ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting second sheet formula does not return correct result", 5, aValue);
//test adding two sheets
m_pDoc->InsertTab(0, u"Sheet2"_ustr);
aValue = m_pDoc->GetValue(2,0,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting first sheet formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2,1,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting first sheet formula does not return correct result", 5, aValue);
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
aValue = m_pDoc->GetValue(2,0,2);
ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting second sheet formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2,1,2);
ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting second sheet formula does not return correct result", 5, aValue);
//test new DeleteTabs/InsertTabs methods
m_pDoc->DeleteTabs(0, 2);
aValue = m_pDoc->GetValue(2, 0, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting sheets formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2, 1, 0);
ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting sheets formula does not return correct result", 5, aValue);
std::vector<OUString> aSheets;
aSheets.emplace_back("Sheet1");
aSheets.emplace_back("Sheet2");
m_pDoc->InsertTabs(0, aSheets, true);
aValue = m_pDoc->GetValue(2, 0, 2);
ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting sheets formula does not return correct result", 3, aValue);
aValue = m_pDoc->GetValue(2, 1, 2);
ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting sheets formula does not return correct result", 5, aValue);
m_pDoc->DeleteTab(3);
m_pDoc->DeleteTab(2);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
// Test positional update and invalidation of lookup cache for insertion
// and deletion within entire column reference.
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->InsertTab(1, u"Sheet2"_ustr);
m_pDoc->SetString(0,1,0, u"s1"_ustr);
m_pDoc->SetString(0,0,1, u"=MATCH(\"s1\";Sheet1.A:A;0)"_ustr);
aValue = m_pDoc->GetValue(0,0,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("unexpected MATCH result", 2, aValue);
m_pDoc->InsertRow(0,0,m_pDoc->MaxCol(),0,0,1); // insert 1 row before row 1 in Sheet1
aValue = m_pDoc->GetValue(0,0,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("unexpected MATCH result", 3, aValue);
m_pDoc->DeleteRow(0,0,m_pDoc->MaxCol(),0,0,1); // delete row 1 in Sheet1
aValue = m_pDoc->GetValue(0,0,1);
ASSERT_DOUBLES_EQUAL_MESSAGE("unexpected MATCH result", 2, aValue);
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testSearchCells)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(0,0,0), u"A"_ustr);
m_pDoc->SetString(ScAddress(0,1,0), u"B"_ustr);
m_pDoc->SetString(ScAddress(0,2,0), u"A"_ustr);
// Leave A4 blank.
m_pDoc->SetString(ScAddress(0,4,0), u"A"_ustr);
m_pDoc->SetString(ScAddress(0,5,0), u"B"_ustr);
m_pDoc->SetString(ScAddress(0,6,0), u"C"_ustr);
SvxSearchItem aItem(SID_SEARCH_ITEM);
aItem.SetSearchString(u"A"_ustr);
aItem.SetCommand(SvxSearchCmd::FIND_ALL);
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
aMarkData.SelectOneTable(0);
SCCOL nCol = 0;
SCROW nRow = 0;
SCTAB nTab = 0;
ScRangeList aMatchedRanges;
OUString aUndoStr;
bool bMatchedRangesWereClamped = false;
bool bSuccess = m_pDoc->SearchAndReplace(aItem, nCol, nRow, nTab, aMarkData, aMatchedRanges, aUndoStr, nullptr, bMatchedRangesWereClamped);
CPPUNIT_ASSERT_MESSAGE("Search And Replace should succeed", bSuccess);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly 3 matching cells.", size_t(3), aMatchedRanges.size());
ScAddress aHit(0,0,0);
CPPUNIT_ASSERT_MESSAGE("A1 should be inside the matched range.", aMatchedRanges.Contains(ScRange(aHit)));
aHit.SetRow(2);
CPPUNIT_ASSERT_MESSAGE("A3 should be inside the matched range.", aMatchedRanges.Contains(ScRange(aHit)));
aHit.SetRow(4);
CPPUNIT_ASSERT_MESSAGE("A5 should be inside the matched range.", aMatchedRanges.Contains(ScRange(aHit)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFormulaPosition)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
ScAddress aPos(0,0,0); // A1
m_pDoc->SetString(aPos, u"=ROW()"_ustr);
aPos.IncRow(); // A2
m_pDoc->SetString(aPos, u"=ROW()"_ustr);
aPos.SetRow(3); // A4;
m_pDoc->SetString(aPos, u"=ROW()"_ustr);
{
SCROW aRows[] = { 0, 1, 3 };
bool bRes = checkFormulaPositions(*m_pDoc, aPos.Tab(), aPos.Col(), aRows, std::size(aRows));
CPPUNIT_ASSERT(bRes);
}
m_pDoc->InsertRow(0,0,0,0,1,5); // Insert 5 rows at A2.
{
SCROW aRows[] = { 0, 6, 8 };
bool bRes = checkFormulaPositions(*m_pDoc, aPos.Tab(), aPos.Col(), aRows, std::size(aRows));
CPPUNIT_ASSERT(bRes);
}
m_pDoc->DeleteTab(0);
}
namespace {
bool hasRange(const ScDocument* pDoc, const std::vector<ScTokenRef>& rRefTokens, const ScRange& rRange, const ScAddress& rPos)
{
for (const ScTokenRef& p : rRefTokens)
{
if (!ScRefTokenHelper::isRef(p) || ScRefTokenHelper::isExternalRef(p))
continue;
switch (p->GetType())
{
case formula::svSingleRef:
{
ScSingleRefData aData = *p->GetSingleRef();
if (rRange.aStart != rRange.aEnd)
break;
ScAddress aThis = aData.toAbs(*pDoc, rPos);
if (aThis == rRange.aStart)
return true;
}
break;
case formula::svDoubleRef:
{
ScComplexRefData aData = *p->GetDoubleRef();
ScRange aThis = aData.toAbs(*pDoc, rPos);
if (aThis == rRange)
return true;
}
break;
default:
;
}
}
return false;
}
}
CPPUNIT_TEST_FIXTURE(Test, testJumpToPrecedentsDependents)
{
/**
* Test to make sure correct precedent / dependent cells are obtained when
* preparing to jump to them.
*/
// Precedent is another cell that the cell references, while dependent is
// another cell that references it.
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(2, 0, 0, u"=A1+A2+B3"_ustr); // C1
m_pDoc->SetString(2, 1, 0, u"=A1"_ustr); // C2
m_pDoc->CalcAll();
std::vector<ScTokenRef> aRefTokens;
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
{
// C1's precedent should be A1:A2,B3.
ScAddress aC1(2, 0, 0);
ScRangeList aRange((ScRange(aC1)));
rDocFunc.DetectiveCollectAllPreds(aRange, aRefTokens);
CPPUNIT_ASSERT_MESSAGE("A1:A2 should be a precedent of C1.",
hasRange(m_pDoc, aRefTokens, ScRange(0, 0, 0, 0, 1, 0), aC1));
CPPUNIT_ASSERT_MESSAGE("B3 should be a precedent of C1.",
hasRange(m_pDoc, aRefTokens, ScRange(1, 2, 0), aC1));
}
{
// C2's precedent should be A1 only.
ScAddress aC2(2, 1, 0);
ScRangeList aRange((ScRange(aC2)));
rDocFunc.DetectiveCollectAllPreds(aRange, aRefTokens);
CPPUNIT_ASSERT_EQUAL_MESSAGE("there should only be one reference token.",
static_cast<size_t>(1), aRefTokens.size());
CPPUNIT_ASSERT_MESSAGE("A1 should be a precedent of C1.",
hasRange(m_pDoc, aRefTokens, ScRange(0, 0, 0), aC2));
}
{
// A1's dependent should be C1:C2.
ScAddress aA1(0, 0, 0);
ScRangeList aRange((ScRange(aA1)));
rDocFunc.DetectiveCollectAllSuccs(aRange, aRefTokens);
CPPUNIT_ASSERT_EQUAL_MESSAGE("C1:C2 should be the only dependent of A1.",
std::vector<ScTokenRef>::size_type(1), aRefTokens.size());
CPPUNIT_ASSERT_MESSAGE("C1:C2 should be the only dependent of A1.",
hasRange(m_pDoc, aRefTokens, ScRange(2, 0, 0, 2, 1, 0), aA1));
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf149665)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(0, 0, 0, u"''1"_ustr);
// Without the fix in place, this test would have failed with
// - Expected: '1
// - Actual : ''1
CPPUNIT_ASSERT_EQUAL( u"'1"_ustr, m_pDoc->GetString( 0, 0, 0 ) );
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf64001)
{
m_pDoc->InsertTab(0, u"test"_ustr);
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
aMarkData.SelectTable(0, true);
m_pDoc->SetString( 0, 0, 0, u"TRUE"_ustr );
m_pDoc->Fill( 0, 0, 0, 0, nullptr, aMarkData, 9, FILL_TO_BOTTOM, FILL_AUTO );
for (SCCOL i = 0; i < 10; ++i)
{
CPPUNIT_ASSERT_EQUAL( u"TRUE"_ustr, m_pDoc->GetString( 0, i, 0 ) );
}
m_pDoc->SetString( 0, 10, 0, u"FALSE"_ustr );
m_pDoc->SetString( 1, 0, 0, u"=COUNTIF(A1:A11;TRUE)"_ustr );
// Without the fix in place, this test would have failed with
// - Expected: 10
// - Actual : 1
CPPUNIT_ASSERT_EQUAL( 10.0, m_pDoc->GetValue( 1, 0, 0 ) );
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAutoFill)
{
m_pDoc->InsertTab(0, u"test"_ustr);
m_pDoc->SetValue(0,0,0,1);
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
aMarkData.SelectTable(0, true);
m_pDoc->Fill( 0, 0, 0, 0, nullptr, aMarkData, 5);
for (SCROW i = 0; i< 6; ++i)
ASSERT_DOUBLES_EQUAL(static_cast<double>(i+1.0), m_pDoc->GetValue(0, i, 0));
// check that hidden rows are not affected by autofill
// set values for hidden rows
m_pDoc->SetValue(0,1,0,10);
m_pDoc->SetValue(0,2,0,10);
m_pDoc->SetRowHidden(1, 2, 0, true);
m_pDoc->Fill( 0, 0, 0, 0, nullptr, aMarkData, 8);
ASSERT_DOUBLES_EQUAL(10.0, m_pDoc->GetValue(0,1,0));
ASSERT_DOUBLES_EQUAL(10.0, m_pDoc->GetValue(0,2,0));
for (SCROW i = 3; i< 8; ++i)
ASSERT_DOUBLES_EQUAL(static_cast<double>(i-1.0), m_pDoc->GetValue(0, i, 0));
m_pDoc->Fill( 0, 0, 0, 8, nullptr, aMarkData, 5, FILL_TO_RIGHT );
for (SCCOL i = 0; i < 5; ++i)
{
for(SCROW j = 0; j < 8; ++j)
{
if (j > 2)
{
ASSERT_DOUBLES_EQUAL(static_cast<double>(j-1+i), m_pDoc->GetValue(i, j, 0));
}
else if (j == 0)
{
ASSERT_DOUBLES_EQUAL(static_cast<double>(i+1), m_pDoc->GetValue(i, 0, 0));
}
else // j == 1 || j == 2
{
if(i == 0)
ASSERT_DOUBLES_EQUAL(10.0, m_pDoc->GetValue(0,j,0));
else
ASSERT_DOUBLES_EQUAL(0.0, m_pDoc->GetValue(i,j,0));
}
}
}
// test auto fill user data lists
m_pDoc->SetString( 0, 100, 0, u"January"_ustr );
m_pDoc->Fill( 0, 100, 0, 100, nullptr, aMarkData, 2, FILL_TO_BOTTOM, FILL_AUTO );
OUString aTestValue = m_pDoc->GetString( 0, 101, 0 );
CPPUNIT_ASSERT_EQUAL( u"February"_ustr, aTestValue );
aTestValue = m_pDoc->GetString( 0, 102, 0 );
CPPUNIT_ASSERT_EQUAL( u"March"_ustr, aTestValue );
// test that two same user data list entries will not result in incremental fill
m_pDoc->SetString( 0, 101, 0, u"January"_ustr );
m_pDoc->Fill( 0, 100, 0, 101, nullptr, aMarkData, 2, FILL_TO_BOTTOM, FILL_AUTO );
for ( SCROW i = 102; i <= 103; ++i )
{
aTestValue = m_pDoc->GetString( 0, i, 0 );
CPPUNIT_ASSERT_EQUAL( u"January"_ustr, aTestValue );
}
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
// Fill A1:A6 with 1,2,3,4,5,6.
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
m_pDoc->SetValue(ScAddress(0,0,0), 1.0);
ScRange aRange(0,0,0,0,5,0);
aMarkData.SetMarkArea(aRange);
rFunc.FillSeries(aRange, &aMarkData, FILL_TO_BOTTOM, FILL_AUTO, FILL_DAY, MAXDOUBLE, 1.0, MAXDOUBLE, true);
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0,2,0)));
CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0,3,0)));
CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0,4,0)));
CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(0,5,0)));
// Undo should clear the area except for the top cell.
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT(pUndoMgr);
pUndoMgr->Undo();
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
for (SCROW i = 1; i <= 5; ++i)
CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(0,i,0)));
// Redo should put the serial values back in.
pUndoMgr->Redo();
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0,2,0)));
CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0,3,0)));
CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0,4,0)));
CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(0,5,0)));
// test that filling formulas vertically up does the right thing
for(SCROW nRow = 0; nRow < 10; ++nRow)
m_pDoc->SetValue(100, 100 + nRow, 0, 1);
m_pDoc->SetString(100, 110, 0, u"=A111"_ustr);
m_pDoc->Fill(100, 110, 100, 110, nullptr, aMarkData, 10, FILL_TO_TOP, FILL_AUTO);
for(SCROW nRow = 110; nRow >= 100; --nRow)
{
OUString aExpected = "=A" + OUString::number(nRow +1);
OUString aFormula = m_pDoc->GetFormula(100, nRow, 0);
CPPUNIT_ASSERT_EQUAL(aExpected, aFormula);
}
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 100, 0, u"2012-10-31"_ustr );
m_pDoc->SetString( 0, 101, 0, u"2012-10-31"_ustr );
m_pDoc->Fill( 0, 100, 0, 101, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO );
// tdf#89754, Without the fix in place, this test would have failed with
// - Expected: 2012-10-31
// - Actual : 2012-11-01
CPPUNIT_ASSERT_EQUAL( u"2012-10-31"_ustr, m_pDoc->GetString( 0, 102, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"2012-10-31"_ustr, m_pDoc->GetString( 0, 103, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"2012-10-31"_ustr, m_pDoc->GetString( 0, 104, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0, 0, 0, 0, m_pDoc->MaxRow(), 0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString(0, 100, 0, u"2019-10-31"_ustr);
m_pDoc->SetString(0, 101, 0, u"2019-11-30"_ustr);
m_pDoc->SetString(0, 102, 0, u"2019-12-31"_ustr);
m_pDoc->Fill(0, 100, 0, 102, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO);
// tdf#58745, Without the fix in place, this test would have failed with
// - Expected: 2020-01-31
// - Actual : 2019-01-11
CPPUNIT_ASSERT_EQUAL(u"2020-01-31"_ustr, m_pDoc->GetString(0, 103, 0));
CPPUNIT_ASSERT_EQUAL(u"2020-02-29"_ustr, m_pDoc->GetString(0, 104, 0));
CPPUNIT_ASSERT_EQUAL(u"2020-03-31"_ustr, m_pDoc->GetString(0, 105, 0));
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 50, 0, u"1.0"_ustr );
m_pDoc->SetString( 0, 51, 0, u"1.1"_ustr );
m_pDoc->SetString( 0, 52, 0, u"1.2"_ustr );
m_pDoc->SetString( 0, 53, 0, u"1.3"_ustr );
m_pDoc->Fill( 0, 50, 0, 53, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO );
CPPUNIT_ASSERT_EQUAL( u"1.4"_ustr, m_pDoc->GetString( 0, 54, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"1.5"_ustr, m_pDoc->GetString( 0, 55, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"1.6"_ustr, m_pDoc->GetString( 0, 56, 0 ) );
m_pDoc->SetString( 0, 60, 0, u"4.0"_ustr );
m_pDoc->SetString( 0, 61, 0, u"4.1"_ustr );
m_pDoc->SetString( 0, 62, 0, u"4.2"_ustr );
m_pDoc->SetString( 0, 63, 0, u"4.3"_ustr );
m_pDoc->Fill( 0, 60, 0, 63, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO );
// tdf#37424: Without the fix in place, this test would have failed with
// - Expected: 4.4
// - Actual : 5
CPPUNIT_ASSERT_EQUAL( u"4.4"_ustr, m_pDoc->GetString( 0, 64, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"4.5"_ustr, m_pDoc->GetString( 0, 65, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"4.6"_ustr, m_pDoc->GetString( 0, 66, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 70, 0, u"001-001-001"_ustr );
m_pDoc->Fill( 0, 70, 0, 70, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO );
// tdf#105268: Without the fix in place, this test would have failed with
// - Expected: 001-001-002
// - Actual : 001-001000
CPPUNIT_ASSERT_EQUAL( u"001-001-002"_ustr, m_pDoc->GetString( 0, 71, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"001-001-003"_ustr, m_pDoc->GetString( 0, 72, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"001-001-004"_ustr, m_pDoc->GetString( 0, 73, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 80, 0, u"1%"_ustr );
m_pDoc->Fill( 0, 80, 0, 80, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO );
// tdf#89998: Without the fix in place, this test would have failed with
// - Expected: 2.00%
// - Actual : 101.00%
CPPUNIT_ASSERT_EQUAL( u"2.00%"_ustr, m_pDoc->GetString( 0, 81, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"3.00%"_ustr, m_pDoc->GetString( 0, 82, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"4.00%"_ustr, m_pDoc->GetString( 0, 83, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 0, 0, u"1"_ustr );
m_pDoc->SetString( 0, 1, 0, u"1.1"_ustr );
m_pDoc->Fill( 0, 0, 0, 1, nullptr, aMarkData, 60, FILL_TO_BOTTOM, FILL_AUTO );
// tdf#129606: Without the fix in place, this test would have failed with
// - Expected: 6
// - Actual : 6.00000000000001
CPPUNIT_ASSERT_EQUAL( u"6"_ustr, m_pDoc->GetString( 0, 50, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 0, 0, u"2022-10-01 00:00:00.000"_ustr );
m_pDoc->SetString( 0, 1, 0, u"2022-10-01 01:00:00.000"_ustr );
m_pDoc->Fill( 0, 0, 0, 1, nullptr, aMarkData, 25, FILL_TO_BOTTOM, FILL_AUTO );
// tdf#151460: Without the fix in place, this test would have failed with
// - Expected: 2022-10-01 20:00:00.000
// - Actual : 2022-10-01 19:59:59.999
CPPUNIT_ASSERT_EQUAL( u"2022-10-01 20:00:00.000"_ustr, m_pDoc->GetString( 0, 20, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 0, 0, u"1st"_ustr );
m_pDoc->Fill( 0, 0, 0, 0, nullptr, aMarkData, 5, FILL_TO_BOTTOM, FILL_AUTO );
CPPUNIT_ASSERT_EQUAL( u"1st"_ustr, m_pDoc->GetString( 0, 0, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"2nd"_ustr, m_pDoc->GetString( 0, 1, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"3rd"_ustr, m_pDoc->GetString( 0, 2, 0 ) );
// Clear column A for a new test.
clearRange(m_pDoc, ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
m_pDoc->SetRowHidden(0, m_pDoc->MaxRow(), 0, false); // Show all rows.
m_pDoc->SetString( 0, 200, 0, u"15:00"_ustr );
m_pDoc->SetString( 0, 201, 0, u"15:20"_ustr );
m_pDoc->Fill( 0, 200, 0, 201, nullptr, aMarkData, 25, FILL_TO_BOTTOM, FILL_AUTO );
CPPUNIT_ASSERT_EQUAL( u"03:00:00 PM"_ustr, m_pDoc->GetString( 0, 200, 0 ) );
// tdf#153517: Without the fix in place, this test would have failed with
// - Expected: 03:20:00 PM
// - Actual : 03:19:59 PM
CPPUNIT_ASSERT_EQUAL( u"03:20:00 PM"_ustr, m_pDoc->GetString( 0, 201, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"03:40:00 PM"_ustr, m_pDoc->GetString( 0, 202, 0 ) );
CPPUNIT_ASSERT_EQUAL( u"04:00:00 PM"_ustr, m_pDoc->GetString( 0, 203, 0 ) );
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAutoFillSimple)
{
m_pDoc->InsertTab(0, u"test"_ustr);
m_pDoc->SetValue(0, 0, 0, 1);
m_pDoc->SetString(0, 1, 0, u"=10"_ustr);
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
aMarkData.SelectTable(0, true);
m_pDoc->Fill( 0, 0, 0, 1, nullptr, aMarkData, 6, FILL_TO_BOTTOM, FILL_AUTO);
for(SCROW nRow = 0; nRow < 8; ++nRow)
{
if (nRow % 2 == 0)
{
double nVal = m_pDoc->GetValue(0, nRow, 0);
CPPUNIT_ASSERT_EQUAL((nRow+2)/2.0, nVal);
}
else
{
OString aMsg = "wrong value in row: " + OString::number(nRow);
double nVal = m_pDoc->GetValue(0, nRow, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), 10.0, nVal);
}
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFindAreaPosVertical)
{
std::vector<std::vector<const char*>> aData = {
{ nullptr, "1", "1" },
{ "1", nullptr, "1" },
{ "1", "1", "1" },
{ nullptr, "1", "1" },
{ "1", "1", "1" },
{ "1", nullptr, "1" },
{ "1", "1", "1" },
};
m_pDoc->InsertTab(0, u"Test1"_ustr);
clearRange( m_pDoc, ScRange(0, 0, 0, 1, aData.size(), 0));
ScAddress aPos(0,0,0);
ScRange aDataRange = insertRangeData( m_pDoc, aPos, aData);
CPPUNIT_ASSERT_EQUAL_MESSAGE("failed to insert range data at correct position", aPos, aDataRange.aStart);
m_pDoc->SetRowHidden(4,4,0,true);
bool bHidden = m_pDoc->RowHidden(4,0);
CPPUNIT_ASSERT(bHidden);
SCCOL nCol = 0;
SCROW nRow = 0;
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(0), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(0), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(5), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(0), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(0), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(m_pDoc->MaxRow(), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(0), nCol);
nCol = 1;
nRow = 2;
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_DOWN);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCol);
nCol = 2;
nRow = 6;
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_UP);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(2), nCol);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFindAreaPosColRight)
{
std::vector<std::vector<const char*>> aData = {
{ "", "1", "1", "", "1", "1", "1" },
{ "", "", "1", "1", "1", "", "1" },
};
m_pDoc->InsertTab(0, u"test1"_ustr);
clearRange( m_pDoc, ScRange(0, 0, 0, 7, aData.size(), 0));
ScAddress aPos(0,0,0);
ScRange aDataRange = insertRangeData( m_pDoc, aPos, aData);
CPPUNIT_ASSERT_EQUAL_MESSAGE("failed to insert range data at correct position", aPos, aDataRange.aStart);
m_pDoc->SetColHidden(4,4,0,true);
bool bHidden = m_pDoc->ColHidden(4,0);
CPPUNIT_ASSERT(bHidden);
SCCOL nCol = 0;
SCROW nRow = 0;
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(2), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(5), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(6), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), nRow);
CPPUNIT_ASSERT_EQUAL(m_pDoc->MaxCol(), nCol);
nCol = 2;
nRow = 1;
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(3), nCol);
m_pDoc->FindAreaPos(nCol, nRow, 0, SC_MOVE_RIGHT);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), nRow);
CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(6), nCol);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testShiftCells)
{
m_pDoc->InsertTab(0, u"foo"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
OUString aTestVal(u"Some Text"_ustr);
// Text into cell E5.
m_pDoc->SetString(4, 3, 0, aTestVal);
// put a Note in cell E5
ScAddress rAddr(4, 3, 0);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(rAddr);
pNote->SetText(rAddr, u"Hello"_ustr);
CPPUNIT_ASSERT_MESSAGE("there should be a note", m_pDoc->HasNote(4, 3, 0));
// Insert cell at D5. This should shift the string cell to right.
m_pDoc->InsertCol(3, 0, 3, 0, 3, 1);
OUString aStr = m_pDoc->GetString(5, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("We should have a string cell here.", aTestVal, aStr);
CPPUNIT_ASSERT_MESSAGE("D5 is supposed to be blank.", m_pDoc->IsBlockEmpty(3, 4, 3, 4, 0));
CPPUNIT_ASSERT_MESSAGE("there should be NO note", !m_pDoc->HasNote(4, 3, 0));
CPPUNIT_ASSERT_MESSAGE("there should be a note", m_pDoc->HasNote(5, 3, 0));
// Delete cell D5, to shift the text cell back into D5.
m_pDoc->DeleteCol(3, 0, 3, 0, 3, 1);
aStr = m_pDoc->GetString(4, 3, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("We should have a string cell here.", aTestVal, aStr);
CPPUNIT_ASSERT_MESSAGE("E5 is supposed to be blank.", m_pDoc->IsBlockEmpty(4, 4, 4, 4, 0));
CPPUNIT_ASSERT_MESSAGE("there should be NO note", !m_pDoc->HasNote(5, 3, 0));
CPPUNIT_ASSERT_MESSAGE("there should be a note", m_pDoc->HasNote(4, 3, 0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNoteDefaultStyle)
{
m_pDoc->InsertTab(0, u"PostIts"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
auto pNote = m_pDoc->GetOrCreateNote({0, 0, 0});
auto pCaption = pNote->GetCaption();
CPPUNIT_ASSERT(pCaption);
CPPUNIT_ASSERT_EQUAL(ScResId(STR_STYLENAME_NOTE), pCaption->GetStyleSheet()->GetName());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNoteBasic)
{
m_pDoc->InsertTab(0, u"PostIts"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
CPPUNIT_ASSERT(!m_pDoc->HasNotes());
// Check for note's presence in all tables before inserting any notes.
for (SCTAB i = 0; i <= MAXTAB; ++i)
{
bool bHasNotes = m_pDoc->HasTabNotes(i);
CPPUNIT_ASSERT(!bHasNotes);
}
ScAddress aAddr(2, 2, 0); // cell C3
ScPostIt *pNote = m_pDoc->GetOrCreateNote(aAddr);
pNote->SetText(aAddr, u"Hello world"_ustr);
pNote->SetAuthor(u"Jim Bob"_ustr);
ScPostIt *pGetNote = m_pDoc->GetNote(aAddr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("note should be itself", pNote, pGetNote);
// Insert one row at row 1.
bool bInsertRow = m_pDoc->InsertRow(0, 0, m_pDoc->MaxCol(), 0, 1, 1);
CPPUNIT_ASSERT_MESSAGE("failed to insert row", bInsertRow );
CPPUNIT_ASSERT_MESSAGE("note hasn't moved", !m_pDoc->GetNote(aAddr));
aAddr.IncRow(); // cell C4
CPPUNIT_ASSERT_EQUAL_MESSAGE("note not there", pNote, m_pDoc->GetNote(aAddr));
// Insert column at column A.
bool bInsertCol = m_pDoc->InsertCol(0, 0, m_pDoc->MaxRow(), 0, 1, 1);
CPPUNIT_ASSERT_MESSAGE("failed to insert column", bInsertCol );
CPPUNIT_ASSERT_MESSAGE("note hasn't moved", !m_pDoc->GetNote(aAddr));
aAddr.IncCol(); // cell D4
CPPUNIT_ASSERT_EQUAL_MESSAGE("note not there", pNote, m_pDoc->GetNote(aAddr));
// Insert a new sheet to shift the current sheet to the right.
m_pDoc->InsertTab(0, u"Table2"_ustr);
CPPUNIT_ASSERT_MESSAGE("note hasn't moved", !m_pDoc->GetNote(aAddr));
aAddr.IncTab(); // Move to the next sheet.
CPPUNIT_ASSERT_EQUAL_MESSAGE("note not there", pNote, m_pDoc->GetNote(aAddr));
m_pDoc->DeleteTab(0);
aAddr.IncTab(-1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("note not there", pNote, m_pDoc->GetNote(aAddr));
// Insert cell at C4. This should NOT shift the note position.
bInsertRow = m_pDoc->InsertRow(2, 0, 2, 0, 3, 1);
CPPUNIT_ASSERT_MESSAGE("Failed to insert cell at C4.", bInsertRow);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Note shouldn't have moved but it has.", pNote, m_pDoc->GetNote(aAddr));
// Delete cell at C4. Again, this should NOT shift the note position.
m_pDoc->DeleteRow(2, 0, 2, 0, 3, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Note shouldn't have moved but it has.", pNote, m_pDoc->GetNote(aAddr));
// Now, with the note at D4, delete cell D3. This should shift the note one cell up.
m_pDoc->DeleteRow(3, 0, 3, 0, 2, 1);
aAddr.IncRow(-1); // cell D3
CPPUNIT_ASSERT_EQUAL_MESSAGE("Note at D4 should have shifted up to D3.", pNote, m_pDoc->GetNote(aAddr));
// Delete column C. This should shift the note one cell left.
m_pDoc->DeleteCol(0, 0, m_pDoc->MaxRow(), 0, 2, 1);
aAddr.IncCol(-1); // cell C3
CPPUNIT_ASSERT_EQUAL_MESSAGE("Note at D3 should have shifted left to C3.", pNote, m_pDoc->GetNote(aAddr));
// Insert a text where the note is.
m_pDoc->SetString(aAddr, u"Note is here."_ustr);
// Delete row 1. This should shift the note from C3 to C2.
m_pDoc->DeleteRow(0, 0, m_pDoc->MaxCol(), 0, 0, 1);
aAddr.IncRow(-1); // C2
CPPUNIT_ASSERT_EQUAL_MESSAGE("Note at C3 should have shifted up to C2.", pNote, m_pDoc->GetNote(aAddr));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNoteDeleteRow)
{
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
ScAddress aPos(1, 1, 0);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(aPos);
pNote->SetText(aPos, u"Hello"_ustr);
pNote->SetAuthor(u"Jim Bob"_ustr);
CPPUNIT_ASSERT_MESSAGE("there should be a note", m_pDoc->HasNote(1, 1, 0));
// test with IsBlockEmpty
CPPUNIT_ASSERT_MESSAGE("The Block should be detected as empty (no Notes)", m_pDoc->IsEmptyData(0, 0, 100, 100, 0));
CPPUNIT_ASSERT_MESSAGE("The Block should NOT be detected as empty", !m_pDoc->IsBlockEmpty(0, 0, 100, 100, 0));
m_pDoc->DeleteRow(0, 0, m_pDoc->MaxCol(), 0, 1, 1);
CPPUNIT_ASSERT_MESSAGE("there should be no more note", !m_pDoc->HasNote(1, 1, 0));
// Set values and notes into B3:B4.
aPos = ScAddress(1,2,0); // B3
m_pDoc->SetString(aPos, u"First"_ustr);
ScNoteUtil::CreateNoteFromString(*m_pDoc, aPos, u"First Note"_ustr, false, false);
aPos = ScAddress(1,3,0); // B4
m_pDoc->SetString(aPos, u"Second"_ustr);
ScNoteUtil::CreateNoteFromString(*m_pDoc, aPos, u"Second Note"_ustr, false, false);
// Delete row 2.
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
rDocFunc.DeleteCells(ScRange(0,1,0,m_pDoc->MaxCol(),1,0), &aMark, DelCellCmd::CellsUp, true);
// Check to make sure the notes have shifted upward.
pNote = m_pDoc->GetNote(ScAddress(1,1,0));
CPPUNIT_ASSERT_MESSAGE("B2 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"First Note"_ustr, pNote->GetText());
pNote = m_pDoc->GetNote(ScAddress(1,2,0));
CPPUNIT_ASSERT_MESSAGE("B3 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"Second Note"_ustr, pNote->GetText());
pNote = m_pDoc->GetNote(ScAddress(1,3,0));
CPPUNIT_ASSERT_MESSAGE("B4 should NOT have a note.", !pNote);
// Undo.
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT_MESSAGE("Failed to get undo manager.", pUndoMgr);
m_pDoc->CreateAllNoteCaptions(); // to make sure that all notes have their corresponding caption objects...
pUndoMgr->Undo();
pNote = m_pDoc->GetNote(ScAddress(1,1,0));
CPPUNIT_ASSERT_MESSAGE("B2 should NOT have a note.", !pNote);
pNote = m_pDoc->GetNote(ScAddress(1,2,0));
CPPUNIT_ASSERT_MESSAGE("B3 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"First Note"_ustr, pNote->GetText());
pNote = m_pDoc->GetNote(ScAddress(1,3,0));
CPPUNIT_ASSERT_MESSAGE("B4 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"Second Note"_ustr, pNote->GetText());
// Delete row 3.
rDocFunc.DeleteCells(ScRange(0,2,0,m_pDoc->MaxCol(),2,0), &aMark, DelCellCmd::CellsUp, true);
pNote = m_pDoc->GetNote(ScAddress(1,2,0));
CPPUNIT_ASSERT_MESSAGE("B3 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"Second Note"_ustr, pNote->GetText());
pNote = m_pDoc->GetNote(ScAddress(1,3,0));
CPPUNIT_ASSERT_MESSAGE("B4 should NOT have a note.", !pNote);
// Undo and check the result.
pUndoMgr->Undo();
pNote = m_pDoc->GetNote(ScAddress(1,2,0));
CPPUNIT_ASSERT_MESSAGE("B3 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"First Note"_ustr, pNote->GetText());
pNote = m_pDoc->GetNote(ScAddress(1,3,0));
CPPUNIT_ASSERT_MESSAGE("B4 should have a note.", pNote);
CPPUNIT_ASSERT_EQUAL(u"Second Note"_ustr, pNote->GetText());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNoteDeleteCol)
{
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
ScAddress rAddr(1, 1, 0);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(rAddr);
pNote->SetText(rAddr, u"Hello"_ustr);
pNote->SetAuthor(u"Jim Bob"_ustr);
CPPUNIT_ASSERT_MESSAGE("there should be a note", m_pDoc->HasNote(1, 1, 0));
m_pDoc->DeleteCol(0, 0, m_pDoc->MaxRow(), 0, 1, 1);
CPPUNIT_ASSERT_MESSAGE("there should be no more note", !m_pDoc->HasNote(1, 1, 0));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNoteLifeCycle)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
ScAddress aPos(1,1,0);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(aPos);
CPPUNIT_ASSERT_MESSAGE("Failed to insert a new cell comment.", pNote);
pNote->SetText(aPos, u"New note"_ustr);
std::unique_ptr<ScPostIt> pNote2 = m_pDoc->ReleaseNote(aPos);
CPPUNIT_ASSERT_EQUAL_MESSAGE("This note instance is expected to be identical to the original.", pNote, pNote2.get());
CPPUNIT_ASSERT_MESSAGE("The note shouldn't be here after it's been released.", !m_pDoc->HasNote(aPos));
// Modify the internal state of the note instance to make sure it's really
// been released.
pNote->SetText(aPos, u"New content"_ustr);
// Re-insert the note back to the same place.
m_pDoc->SetNote(aPos, std::move(pNote2));
SdrCaptionObj* pCaption = pNote->GetOrCreateCaption(aPos);
CPPUNIT_ASSERT_MESSAGE("Failed to create a caption object.", pCaption);
CPPUNIT_ASSERT_EQUAL_MESSAGE("This caption should belong to the drawing layer of the document.",
m_pDoc->GetDrawLayer(), static_cast<ScDrawLayer*>(&pCaption->getSdrModelFromSdrObject()));
// Copy B2 with note to a clipboard.
ScClipParam aClipParam(ScRange(aPos), false);
ScDocument aClipDoc(SCDOCMODE_CLIP);
ScMarkData aMarkData(m_pDoc->GetSheetLimits());
aMarkData.SelectOneTable(0);
m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMarkData, false, true);
ScPostIt* pClipNote = aClipDoc.GetNote(aPos);
CPPUNIT_ASSERT_MESSAGE("Failed to copy note to the clipboard.", pClipNote);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Note on the clipboard should share the same caption object from the original.",
pCaption, pClipNote->GetCaption());
// Move B2 to B3 with note, which creates an ScUndoDragDrop, and Undo.
ScAddress aOrigPos(aPos);
ScAddress aMovePos(1,2,0);
ScPostIt* pOrigNote = m_pDoc->GetNote(aOrigPos);
const SdrCaptionObj* pOrigCaption = pOrigNote->GetOrCreateCaption(aOrigPos);
bool const bCut = true; // like Drag&Drop
bool bRecord = true; // record Undo
bool const bPaint = false; // don't care about
bool bApi = true; // API to prevent dialogs
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
bool bMoveDone = rDocFunc.MoveBlock(ScRange(aOrigPos, aOrigPos), aMovePos, bCut, bRecord, bPaint, bApi);
CPPUNIT_ASSERT_MESSAGE("Cells not moved", bMoveDone);
// Verify the note move.
ScPostIt* pGoneNote = m_pDoc->GetNote(aOrigPos);
CPPUNIT_ASSERT_MESSAGE("Failed to move the note from source.", !pGoneNote);
ScPostIt* pMoveNote = m_pDoc->GetNote(aMovePos);
CPPUNIT_ASSERT_MESSAGE("Failed to move the note to destination.", pMoveNote);
// The caption object should not be identical, it was newly created upon
// Drop from clipboard.
// pOrigCaption is a dangling pointer.
const SdrCaptionObj* pMoveCaption = pMoveNote->GetOrCreateCaption(aMovePos);
CPPUNIT_ASSERT_MESSAGE("Captions identical after move.", pOrigCaption != pMoveCaption);
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT(pUndoMgr);
pUndoMgr->Undo(); // this should not crash ... tdf#92995
// Verify the note move Undo.
pMoveNote = m_pDoc->GetNote(aMovePos);
CPPUNIT_ASSERT_MESSAGE("Failed to undo the note move from destination.", !pMoveNote);
pOrigNote = m_pDoc->GetNote(aOrigPos);
CPPUNIT_ASSERT_MESSAGE("Failed to undo the note move to source.", pOrigNote);
// The caption object still should not be identical.
// pMoveCaption is a dangling pointer.
pOrigCaption = pOrigNote->GetOrCreateCaption(aOrigPos);
CPPUNIT_ASSERT_MESSAGE("Captions identical after move undo.", pOrigCaption != pMoveCaption);
// Create a note at B4, merge B4 and B5 with ScUndoMerge, and Undo.
ScAddress aPosB4(1,3,0);
ScPostIt* pNoteB4 = m_pDoc->GetOrCreateNote(aPosB4);
CPPUNIT_ASSERT_MESSAGE("Failed to insert cell comment at B4.", pNoteB4);
const SdrCaptionObj* pCaptionB4 = pNoteB4->GetOrCreateCaption(aPosB4);
ScCellMergeOption aCellMergeOption(1,3,2,3);
rDocFunc.MergeCells( aCellMergeOption, true /*bContents*/, bRecord, bApi, false /*bEmptyMergedCells*/ );
SfxUndoManager* pMergeUndoManager = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT(pMergeUndoManager);
pMergeUndoManager->Undo(); // this should not crash ... tdf#105667
// Undo contained the original caption object pointer which was still alive
// at B4 after the merge and not cloned nor recreated during Undo.
ScPostIt* pUndoNoteB4 = m_pDoc->GetNote(aPosB4);
CPPUNIT_ASSERT_MESSAGE("No cell comment at B4 after Undo.", pUndoNoteB4);
const SdrCaptionObj* pUndoCaptionB4 = pUndoNoteB4->GetCaption();
CPPUNIT_ASSERT_EQUAL_MESSAGE("Captions not identical after Merge Undo.", pCaptionB4, pUndoCaptionB4);
// In a second document copy a note from B5 to clipboard, close the
// document and then paste the note into this document.
{
ScDocShellRef xDocSh2;
getNewDocShell(xDocSh2);
ScDocument* pDoc2 = &xDocSh2->GetDocument();
pDoc2->InsertTab(0, u"OtherSheet1"_ustr);
pDoc2->InitDrawLayer(xDocSh2.get());
ScAddress aPosB5(1,4,0);
ScPostIt* pOtherNoteB5 = pDoc2->GetOrCreateNote(aPosB5);
CPPUNIT_ASSERT_MESSAGE("Failed to insert cell comment at B5.", pOtherNoteB5);
const SdrCaptionObj* pOtherCaptionB5 = pOtherNoteB5->GetOrCreateCaption(aPosB5);
CPPUNIT_ASSERT_MESSAGE("No caption at B5.", pOtherCaptionB5);
ScDocument aClipDoc2(SCDOCMODE_CLIP);
copyToClip( pDoc2, ScRange(aPosB5), &aClipDoc2);
// There's no ScTransferObject involved in the "fake" clipboard copy
// and ScDocument dtor asking IsClipboardSource() gets no, so emulate
// the part that normally is responsible for forgetting the caption
// objects.
aClipDoc2.ClosingClipboardSource();
pDoc2->DeleteTab(0);
xDocSh2->DoClose();
xDocSh2.clear();
pasteFromClip( m_pDoc, ScRange(aPosB5), &aClipDoc2); // should not crash... tdf#104967
ScPostIt* pNoteB5 = m_pDoc->GetNote(aPosB5);
CPPUNIT_ASSERT_MESSAGE("Failed to paste cell comment at B5.", pNoteB5);
const SdrCaptionObj* pCaptionB5 = pNoteB5->GetOrCreateCaption(aPosB5);
CPPUNIT_ASSERT_MESSAGE("No caption at pasted B5.", pCaptionB5);
// Do not test if pCaptionB5 != pOtherCaptionB5 because since pDoc2
// has been closed and the caption been deleted objects *may* be
// allocated at the very same memory location.
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testNoteCopyPaste)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
// Insert in B2 a text and cell comment.
ScAddress aPos(1,1,0);
m_pDoc->SetString(aPos, u"Text"_ustr);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(aPos);
CPPUNIT_ASSERT(pNote);
pNote->SetText(aPos, u"Note1"_ustr);
// Insert in B4 a number and cell comment.
aPos.SetRow(3);
m_pDoc->SetValue(aPos, 1.1);
pNote = m_pDoc->GetOrCreateNote(aPos);
CPPUNIT_ASSERT(pNote);
pNote->SetText(aPos, u"Note2"_ustr);
// Copy B2:B4 to clipboard.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
ScRange aCopyRange(1,1,0,1,3,0);
ScDocument aClipDoc(SCDOCMODE_CLIP);
aClipDoc.ResetClip(m_pDoc, &aMark);
ScClipParam aClipParam(aCopyRange, false);
m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMark, false, false);
// Make sure the notes are in the clipboard.
pNote = aClipDoc.GetNote(ScAddress(1,1,0));
CPPUNIT_ASSERT(pNote);
CPPUNIT_ASSERT_EQUAL(u"Note1"_ustr, pNote->GetText());
pNote = aClipDoc.GetNote(ScAddress(1,3,0));
CPPUNIT_ASSERT(pNote);
CPPUNIT_ASSERT_EQUAL(u"Note2"_ustr, pNote->GetText());
// Paste to B6:B8 but only cell notes.
ScRange aDestRange(1,5,0,1,7,0);
m_pDoc->CopyFromClip(aDestRange, aMark, InsertDeleteFlags::NOTE, nullptr, &aClipDoc);
// Make sure the notes are there.
pNote = m_pDoc->GetNote(ScAddress(1,5,0));
CPPUNIT_ASSERT(pNote);
CPPUNIT_ASSERT_EQUAL(u"Note1"_ustr, pNote->GetText());
pNote = m_pDoc->GetNote(ScAddress(1,7,0));
CPPUNIT_ASSERT(pNote);
CPPUNIT_ASSERT_EQUAL(u"Note2"_ustr, pNote->GetText());
// Test that GetNotesInRange includes the end of its range
// and so can find the note
std::vector<sc::NoteEntry> aNotes;
m_pDoc->GetNotesInRange(ScRange(1,7,0), aNotes);
CPPUNIT_ASSERT_EQUAL(size_t(1), aNotes.size());
m_pDoc->DeleteTab(0);
}
// tdf#112454
CPPUNIT_TEST_FIXTURE(Test, testNoteContainsNotesInRange)
{
m_pDoc->InsertTab(0, u"PostIts"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
ScAddress aAddr(2, 2, 0); // cell C3
CPPUNIT_ASSERT_MESSAGE("Claiming there's notes in a document that doesn't have any.",
!m_pDoc->ContainsNotesInRange((ScRange(ScAddress(0, 0, 0), aAddr))));
m_pDoc->GetOrCreateNote(aAddr);
CPPUNIT_ASSERT_MESSAGE("Claiming there's notes in range that doesn't have any.",
!m_pDoc->ContainsNotesInRange(ScRange(ScAddress(0, 0, 0), ScAddress(0, 1, 0))));
CPPUNIT_ASSERT_MESSAGE("Note not detected that lies on border of range.",
m_pDoc->ContainsNotesInRange((ScRange(ScAddress(0, 0, 0), aAddr))));
CPPUNIT_ASSERT_MESSAGE("Note not detected that lies in inner area of range.",
m_pDoc->ContainsNotesInRange((ScRange(ScAddress(0, 0, 0), ScAddress(3, 3, 0)))));
}
CPPUNIT_TEST_FIXTURE(Test, testAreasWithNotes)
{
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
ScAddress rAddr(1, 5, 0);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(rAddr);
pNote->SetText(rAddr, u"Hello"_ustr);
pNote->SetAuthor(u"Jim Bob"_ustr);
ScAddress rAddrMin(2, 2, 0);
ScPostIt* pNoteMin = m_pDoc->GetOrCreateNote(rAddrMin);
pNoteMin->SetText(rAddrMin, u"Hello"_ustr);
SCCOL col;
SCROW row;
bool dataFound;
// only cell notes (empty content)
dataFound = m_pDoc->GetDataStart(0,col,row);
CPPUNIT_ASSERT_MESSAGE("No DataStart found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("DataStart wrong col for notes", static_cast<SCCOL>(1), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("DataStart wrong row for notes", static_cast<SCROW>(2), row);
dataFound = m_pDoc->GetCellArea(0,col,row);
CPPUNIT_ASSERT_MESSAGE("No CellArea found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("CellArea wrong col for notes", static_cast<SCCOL>(2), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("CellArea wrong row for notes", static_cast<SCROW>(5), row);
bool bNotes = true;
dataFound = m_pDoc->GetPrintArea(0,col,row, bNotes);
CPPUNIT_ASSERT_MESSAGE("No PrintArea found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintArea wrong col for notes", static_cast<SCCOL>(2), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintArea wrong row for notes", static_cast<SCROW>(5), row);
bNotes = false;
dataFound = m_pDoc->GetPrintArea(0,col,row, bNotes);
CPPUNIT_ASSERT_MESSAGE("No PrintArea should be found", !dataFound);
bNotes = true;
dataFound = m_pDoc->GetPrintAreaVer(0,0,1,row, bNotes); // cols 0 & 1
CPPUNIT_ASSERT_MESSAGE("No PrintAreaVer found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintAreaVer wrong row for notes", static_cast<SCROW>(5), row);
dataFound = m_pDoc->GetPrintAreaVer(0,2,3,row, bNotes); // cols 2 & 3
CPPUNIT_ASSERT_MESSAGE("No PrintAreaVer found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintAreaVer wrong row for notes", static_cast<SCROW>(2), row);
dataFound = m_pDoc->GetPrintAreaVer(0,20,21,row, bNotes); // cols 20 & 21
CPPUNIT_ASSERT_MESSAGE("PrintAreaVer found", !dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintAreaVer wrong row for notes", static_cast<SCROW>(0), row);
bNotes = false;
dataFound = m_pDoc->GetPrintAreaVer(0,0,1,row, bNotes); // col 0 & 1
CPPUNIT_ASSERT_MESSAGE("No PrintAreaVer should be found", !dataFound);
// now add cells with value, check that notes are taken into account in good cases
m_pDoc->SetString(0, 3, 0, u"Some Text"_ustr);
m_pDoc->SetString(3, 3, 0, u"Some Text"_ustr);
dataFound = m_pDoc->GetDataStart(0,col,row);
CPPUNIT_ASSERT_MESSAGE("No DataStart found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("DataStart wrong col", static_cast<SCCOL>(0), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("DataStart wrong row", static_cast<SCROW>(2), row);
dataFound = m_pDoc->GetCellArea(0,col,row);
CPPUNIT_ASSERT_MESSAGE("No CellArea found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("CellArea wrong col", static_cast<SCCOL>(3), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("CellArea wrong row", static_cast<SCROW>(5), row);
bNotes = true;
dataFound = m_pDoc->GetPrintArea(0,col,row, bNotes);
CPPUNIT_ASSERT_MESSAGE("No PrintArea found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintArea wrong col", static_cast<SCCOL>(3), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintArea wrong row", static_cast<SCROW>(5), row);
bNotes = false;
dataFound = m_pDoc->GetPrintArea(0,col,row, bNotes);
CPPUNIT_ASSERT_MESSAGE("No PrintArea found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintArea wrong col", static_cast<SCCOL>(3), col);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintArea wrong row", static_cast<SCROW>(3), row);
bNotes = true;
dataFound = m_pDoc->GetPrintAreaVer(0,0,1,row, bNotes); // cols 0 & 1
CPPUNIT_ASSERT_MESSAGE("No PrintAreaVer found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintAreaVer wrong row", static_cast<SCROW>(5), row);
dataFound = m_pDoc->GetPrintAreaVer(0,2,3,row, bNotes); // cols 2 & 3
CPPUNIT_ASSERT_MESSAGE("No PrintAreaVer found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintAreaVer wrong row", static_cast<SCROW>(3), row);
bNotes = false;
dataFound = m_pDoc->GetPrintAreaVer(0,0,1,row, bNotes); // cols 0 & 1
CPPUNIT_ASSERT_MESSAGE("No PrintAreaVer found", dataFound);
CPPUNIT_ASSERT_EQUAL_MESSAGE("PrintAreaVer wrong row", static_cast<SCROW>(3), row);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testAnchoredRotatedShape)
{
m_pDoc->InsertTab(0, u"TestTab"_ustr);
SCROW nRow1, nRow2;
bool bHidden = m_pDoc->RowHidden(0, 0, &nRow1, &nRow2);
CPPUNIT_ASSERT_MESSAGE("new sheet should have all rows visible", !bHidden);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", SCROW(0), nRow1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("new sheet should have all rows visible", m_pDoc->MaxRow(), nRow2);
m_pDoc->InitDrawLayer();
ScDrawLayer *pDrawLayer = m_pDoc->GetDrawLayer();
CPPUNIT_ASSERT_MESSAGE("must have a draw layer", pDrawLayer != nullptr);
SdrPage* pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("must have a draw page", pPage != nullptr);
m_pDoc->SetRowHeightRange(0, m_pDoc->MaxRow(), 0, o3tl::toTwips(1000, o3tl::Length::mm100));
constexpr tools::Long TOLERANCE = 30; //30 hmm
for ( SCCOL nCol = 0; nCol < m_pDoc->MaxCol(); ++nCol )
m_pDoc->SetColWidth(nCol, 0, o3tl::toTwips(1000, o3tl::Length::mm100));
{
//Add a rect
tools::Rectangle aRect( 4000, 5000, 10000, 7000 );
tools::Rectangle aRotRect( 6000, 3000, 8000, 9000 );
rtl::Reference<SdrRectObj> pObj = new SdrRectObj(*pDrawLayer, aRect);
pPage->InsertObject(pObj.get());
Point aRef1(pObj->GetSnapRect().Center());
Degree100 nAngle = 9000_deg100; //90 deg.
double nSin = 1.0; // sin(90 deg)
double nCos = 0.0; // cos(90 deg)
pObj->Rotate(aRef1,nAngle,nSin,nCos);
ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, true);
tools::Rectangle aSnap = pObj->GetSnapRect();
CPPUNIT_ASSERT_DOUBLES_EQUAL( aRotRect.GetHeight(), aSnap.GetHeight(), TOLERANCE );
CPPUNIT_ASSERT_DOUBLES_EQUAL( aRotRect.GetWidth(), aSnap.GetWidth(), TOLERANCE );
CPPUNIT_ASSERT_DOUBLES_EQUAL( aRotRect.Left(), aSnap.Left(), TOLERANCE );
CPPUNIT_ASSERT_DOUBLES_EQUAL( aRotRect.Top(), aSnap.Top(), TOLERANCE );
ScDrawObjData aAnchor;
ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj.get() );
CPPUNIT_ASSERT_MESSAGE("Failed to get drawing object meta-data.", pData);
aAnchor.maStart = pData->maStart;
aAnchor.maEnd = pData->maEnd;
m_pDoc->SetDrawPageSize(0);
// increase row 5 by 2000 hmm
m_pDoc->SetRowHeight(5, 0, o3tl::toTwips(3000, o3tl::Length::mm100));
// increase col 6 by 1000 hmm
m_pDoc->SetColWidth(6, 0, o3tl::toTwips(2000, o3tl::Length::mm100));
aRotRect.setWidth( aRotRect.GetWidth() + 1000 );
aRotRect.setHeight( aRotRect.GetHeight() + 2000 );
m_pDoc->SetDrawPageSize(0);
aSnap = pObj->GetSnapRect();
// ensure that width and height have been adjusted accordingly
CPPUNIT_ASSERT_DOUBLES_EQUAL( aRotRect.GetHeight(), aSnap.GetHeight(), TOLERANCE );
CPPUNIT_ASSERT_DOUBLES_EQUAL( aRotRect.GetWidth(), aSnap.GetWidth(), TOLERANCE );
// ensure that anchor start and end addresses haven't changed
CPPUNIT_ASSERT_EQUAL( aAnchor.maStart.Row(), pData->maStart.Row() ); // start row 0
CPPUNIT_ASSERT_EQUAL( aAnchor.maStart.Col(), pData->maStart.Col() ); // start column 5
CPPUNIT_ASSERT_EQUAL( aAnchor.maEnd.Row(), pData->maEnd.Row() ); // end row 3
CPPUNIT_ASSERT_EQUAL( aAnchor.maEnd.Col(), pData->maEnd.Col() ); // end col 7
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testCellTextWidth)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
ScAddress aTopCell(0, 0, 0);
// Sheet is empty.
std::unique_ptr<ScColumnTextWidthIterator> pIter(new ScColumnTextWidthIterator(*m_pDoc, aTopCell, m_pDoc->MaxRow()));
CPPUNIT_ASSERT_MESSAGE("Column should have no text widths stored.", !pIter->hasCell());
// Sheet only has one cell.
m_pDoc->SetString(0, 0, 0, u"Only one cell"_ustr);
pIter.reset(new ScColumnTextWidthIterator(*m_pDoc, aTopCell, m_pDoc->MaxRow()));
CPPUNIT_ASSERT_MESSAGE("Column should have a cell.", pIter->hasCell());
CPPUNIT_ASSERT_EQUAL(SCROW(0), pIter->getPos());
// Setting a text width here should commit it to the column.
sal_uInt16 nTestVal = 432;
pIter->setValue(nTestVal);
CPPUNIT_ASSERT_EQUAL(nTestVal, m_pDoc->GetTextWidth(aTopCell));
// Set values to row 2 through 6.
for (SCROW i = 2; i <= 6; ++i)
m_pDoc->SetString(0, i, 0, u"foo"_ustr);
// Set values to row 10 through 18.
for (SCROW i = 10; i <= 18; ++i)
m_pDoc->SetString(0, i, 0, u"foo"_ustr);
{
// Full range.
pIter.reset(new ScColumnTextWidthIterator(*m_pDoc, aTopCell, m_pDoc->MaxRow()));
SCROW aRows[] = { 0, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
for (const auto& rRow : aRows)
{
CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
CPPUNIT_ASSERT_EQUAL(rRow, pIter->getPos());
pIter->next();
}
CPPUNIT_ASSERT_MESSAGE("Iterator should have ended.", !pIter->hasCell());
}
{
// Specify start and end rows (6 - 16)
ScAddress aStart = aTopCell;
aStart.SetRow(6);
pIter.reset(new ScColumnTextWidthIterator(*m_pDoc, aStart, 16));
SCROW aRows[] = { 6, 10, 11, 12, 13, 14, 15, 16 };
for (const auto& rRow : aRows)
{
CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
CPPUNIT_ASSERT_EQUAL(rRow, pIter->getPos());
pIter->next();
}
CPPUNIT_ASSERT_MESSAGE("Iterator should have ended.", !pIter->hasCell());
}
// Clear from row 3 to row 17. After this, we should only have cells at rows 0, 2 and 18.
clearRange(m_pDoc, ScRange(0, 3, 0, 0, 17, 0));
{
// Full range again.
pIter.reset(new ScColumnTextWidthIterator(*m_pDoc, aTopCell, m_pDoc->MaxRow()));
SCROW aRows[] = { 0, 2, 18 };
for (const auto& rRow : aRows)
{
CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
CPPUNIT_ASSERT_EQUAL(rRow, pIter->getPos());
pIter->next();
}
CPPUNIT_ASSERT_MESSAGE("Iterator should have ended.", !pIter->hasCell());
}
// Delete row 2 which shifts all cells below row 2 upward. After this, we
// should only have cells at rows 0 and 17.
m_pDoc->DeleteRow(0, 0, m_pDoc->MaxCol(), MAXTAB, 2, 1);
{
// Full range again.
pIter.reset(new ScColumnTextWidthIterator(*m_pDoc, aTopCell, m_pDoc->MaxRow()));
SCROW aRows[] = { 0, 17 };
for (const auto& rRow : aRows)
{
CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
CPPUNIT_ASSERT_EQUAL(rRow, pIter->getPos());
pIter->next();
}
CPPUNIT_ASSERT_MESSAGE("Iterator should have ended.", !pIter->hasCell());
}
m_pDoc->DeleteTab(0);
}
static bool checkEditTextIterator(sc::EditTextIterator& rIter, const char** pChecks)
{
const EditTextObject* pText = rIter.first();
const char* p = *pChecks;
for (int i = 0; i < 100; ++i) // cap it to 100 loops.
{
if (!pText)
// No more edit cells. The check string array should end too.
return p == nullptr;
if (!p)
// More edit cell, but no more check string. Bad.
return false;
if (pText->GetParagraphCount() != 1)
// For this test, we don't handle multi-paragraph text.
return false;
if (pText->GetText(0) != OUString::createFromAscii(p))
// Text differs from what's expected.
return false;
pText = rIter.next();
++pChecks;
p = *pChecks;
}
return false;
}
CPPUNIT_TEST_FIXTURE(Test, testEditTextIterator)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
{
// First, try with an empty sheet.
sc::EditTextIterator aIter(*m_pDoc,0);
const char* pChecks[] = { nullptr };
CPPUNIT_ASSERT_MESSAGE("Wrong iterator behavior.", checkEditTextIterator(aIter, pChecks));
}
ScFieldEditEngine& rEditEngine = m_pDoc->GetEditEngine();
{
// Only set one edit cell.
rEditEngine.SetTextCurrentDefaults(u"A2"_ustr);
m_pDoc->SetEditText(ScAddress(0,1,0), rEditEngine.CreateTextObject());
sc::EditTextIterator aIter(*m_pDoc,0);
const char* pChecks[] = { "A2", nullptr };
CPPUNIT_ASSERT_MESSAGE("Wrong iterator behavior.", checkEditTextIterator(aIter, pChecks));
}
{
// Add a series of edit cells.
rEditEngine.SetTextCurrentDefaults(u"A5"_ustr);
m_pDoc->SetEditText(ScAddress(0,4,0), rEditEngine.CreateTextObject());
rEditEngine.SetTextCurrentDefaults(u"A6"_ustr);
m_pDoc->SetEditText(ScAddress(0,5,0), rEditEngine.CreateTextObject());
rEditEngine.SetTextCurrentDefaults(u"A7"_ustr);
m_pDoc->SetEditText(ScAddress(0,6,0), rEditEngine.CreateTextObject());
sc::EditTextIterator aIter(*m_pDoc,0);
const char* pChecks[] = { "A2", "A5", "A6", "A7", nullptr };
CPPUNIT_ASSERT_MESSAGE("Wrong iterator behavior.", checkEditTextIterator(aIter, pChecks));
}
{
// Add more edit cells to column C. Skip column B.
rEditEngine.SetTextCurrentDefaults(u"C1"_ustr);
m_pDoc->SetEditText(ScAddress(2,0,0), rEditEngine.CreateTextObject());
rEditEngine.SetTextCurrentDefaults(u"C3"_ustr);
m_pDoc->SetEditText(ScAddress(2,2,0), rEditEngine.CreateTextObject());
rEditEngine.SetTextCurrentDefaults(u"C4"_ustr);
m_pDoc->SetEditText(ScAddress(2,3,0), rEditEngine.CreateTextObject());
sc::EditTextIterator aIter(*m_pDoc,0);
const char* pChecks[] = { "A2", "A5", "A6", "A7", "C1", "C3", "C4", nullptr };
CPPUNIT_ASSERT_MESSAGE("Wrong iterator behavior.", checkEditTextIterator(aIter, pChecks));
}
{
// Add some numeric, string and formula cells. This shouldn't affect the outcome.
m_pDoc->SetString(ScAddress(0,99,0), u"=ROW()"_ustr);
m_pDoc->SetValue(ScAddress(1,3,0), 1.2);
m_pDoc->SetString(ScAddress(2,4,0), u"Simple string"_ustr);
sc::EditTextIterator aIter(*m_pDoc,0);
const char* pChecks[] = { "A2", "A5", "A6", "A7", "C1", "C3", "C4", nullptr };
CPPUNIT_ASSERT_MESSAGE("Wrong iterator behavior.", checkEditTextIterator(aIter, pChecks));
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testImportStream)
{
sc::AutoCalcSwitch aAC(*m_pDoc, true); // turn on auto calc.
sc::UndoSwitch aUndo(*m_pDoc, true); // enable undo.
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(0,1,0), u"=SUM(A1:C1)"_ustr); // A2
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0,1,0)));
// CSV import options.
ScAsciiOptions aOpt;
aOpt.SetFieldSeps(u","_ustr);
// Import values to A1:C1.
ScImportExport aObj(*m_pDoc, ScAddress(0,0,0));
aObj.SetImportBroadcast(true);
aObj.SetExtOptions(aOpt);
aObj.ImportString(u"1,2,3"_ustr, SotClipboardFormatId::STRING);
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,0,0)));
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,0,0)));
// Formula value should have been updated.
CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(0,1,0)));
// Undo, and check the result.
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT_MESSAGE("Failed to get the undo manager.", pUndoMgr);
pUndoMgr->Undo();
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1,0,0)));
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(2,0,0)));
CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0,1,0))); // formula
// Redo, and check the result.
pUndoMgr->Redo();
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,0,0)));
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,0,0)));
CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(0,1,0))); // formula
pUndoMgr->Clear();
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testDeleteContents)
{
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
sc::UndoSwitch aUndoSwitch(*m_pDoc, true); // enable undo.
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetValue(ScAddress(3,1,0), 1.0);
m_pDoc->SetValue(ScAddress(3,2,0), 1.0);
m_pDoc->SetValue(ScAddress(3,3,0), 1.0);
m_pDoc->SetValue(ScAddress(3,4,0), 1.0);
m_pDoc->SetValue(ScAddress(3,5,0), 1.0);
m_pDoc->SetValue(ScAddress(3,6,0), 1.0);
m_pDoc->SetValue(ScAddress(3,7,0), 1.0);
m_pDoc->SetValue(ScAddress(3,8,0), 1.0);
m_pDoc->SetString(ScAddress(3,15,0), u"=SUM(D2:D15)"_ustr);
CPPUNIT_ASSERT_EQUAL(8.0, m_pDoc->GetValue(ScAddress(3,15,0))); // formula
// Delete D2:D6.
ScRange aRange(3,1,0,3,5,0);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
aMark.SetMarkArea(aRange);
ScDocumentUniquePtr pUndoDoc(new ScDocument(SCDOCMODE_UNDO));
pUndoDoc->InitUndo(*m_pDoc, 0, 0);
m_pDoc->CopyToDocument(aRange, InsertDeleteFlags::CONTENTS, false, *pUndoDoc, &aMark);
ScUndoDeleteContents aUndo(m_xDocShell.get(), aMark, aRange, std::move(pUndoDoc), false, InsertDeleteFlags::CONTENTS, true);
clearRange(m_pDoc, aRange);
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(3,15,0))); // formula
aUndo.Undo();
CPPUNIT_ASSERT_EQUAL(8.0, m_pDoc->GetValue(ScAddress(3,15,0))); // formula
aUndo.Redo();
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(3,15,0))); // formula
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testTransliterateText)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// Set texts to A1:A3.
m_pDoc->SetString(ScAddress(0,0,0), u"Mike"_ustr);
m_pDoc->SetString(ScAddress(0,1,0), u"Noah"_ustr);
m_pDoc->SetString(ScAddress(0,2,0), u"Oscar"_ustr);
// Change them to uppercase.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SetMarkArea(ScRange(0,0,0,0,2,0));
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
rFunc.TransliterateText(
aMark, TransliterationFlags::LOWERCASE_UPPERCASE, true);
CPPUNIT_ASSERT_EQUAL(u"MIKE"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(u"NOAH"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(u"OSCAR"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));
// Test the undo and redo.
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT_MESSAGE("Failed to get undo manager.", pUndoMgr);
pUndoMgr->Undo();
CPPUNIT_ASSERT_EQUAL(u"Mike"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(u"Noah"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(u"Oscar"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));
pUndoMgr->Redo();
CPPUNIT_ASSERT_EQUAL(u"MIKE"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
CPPUNIT_ASSERT_EQUAL(u"NOAH"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
CPPUNIT_ASSERT_EQUAL(u"OSCAR"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFormulaToValue)
{
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
m_pDoc->InsertTab(0, u"Test"_ustr);
std::vector<std::vector<const char*>> aData = {
{ "=1", "=RC[-1]*2", "=ISFORMULA(RC[-1])" },
{ "=2", "=RC[-1]*2", "=ISFORMULA(RC[-1])" },
{ "=3", "=RC[-1]*2", "=ISFORMULA(RC[-1])" },
{ "=4", "=RC[-1]*2", "=ISFORMULA(RC[-1])" },
{ "=5", "=RC[-1]*2", "=ISFORMULA(RC[-1])" },
{ "=6", "=RC[-1]*2", "=ISFORMULA(RC[-1])" },
};
ScAddress aPos(1,2,0); // B3
ScRange aDataRange = insertRangeData(m_pDoc, aPos, aData);
CPPUNIT_ASSERT_EQUAL_MESSAGE("failed to insert range data at correct position", aPos, aDataRange.aStart);
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "2", "TRUE" },
{ "2", "4", "TRUE" },
{ "3", "6", "TRUE" },
{ "4", "8", "TRUE" },
{ "5", "10", "TRUE" },
{ "6", "12", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "Initial value");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
// Convert B5:C6 to static values, and check the result.
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
ScRange aConvRange(1,4,0,2,5,0); // B5:C6
rFunc.ConvertFormulaToValue(aConvRange, false);
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "2", "TRUE" },
{ "2", "4", "TRUE" },
{ "3", "6", "FALSE" },
{ "4", "8", "FALSE" },
{ "5", "10", "TRUE" },
{ "6", "12", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "Converted");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
// Make sure that B3:B4 and B7:B8 are formula cells.
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,2,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,3,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,6,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,7,0)));
// Make sure that B5:C6 are numeric cells.
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(1,4,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(1,5,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(2,4,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(2,5,0)));
// Make sure that formula cells in C3:C4 and C7:C8 are grouped.
const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(2,2,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedTopRow());
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
pFC = m_pDoc->GetFormulaCell(ScAddress(2,6,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedTopRow());
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
// Undo and check.
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT(pUndoMgr);
pUndoMgr->Undo();
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "2", "TRUE" },
{ "2", "4", "TRUE" },
{ "3", "6", "TRUE" },
{ "4", "8", "TRUE" },
{ "5", "10", "TRUE" },
{ "6", "12", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "After undo");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
// B3:B8 should all be (ungrouped) formula cells.
for (SCROW i = 2; i <= 7; ++i)
{
pFC = m_pDoc->GetFormulaCell(ScAddress(1,i,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT(!pFC->IsShared());
}
// C3:C8 should be shared formula cells.
pFC = m_pDoc->GetFormulaCell(ScAddress(2,2,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedTopRow());
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedLength());
// Redo and check.
pUndoMgr->Redo();
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "2", "TRUE" },
{ "2", "4", "TRUE" },
{ "3", "6", "FALSE" },
{ "4", "8", "FALSE" },
{ "5", "10", "TRUE" },
{ "6", "12", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "Converted");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
// Make sure that B3:B4 and B7:B8 are formula cells.
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,2,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,3,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,6,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_FORMULA, m_pDoc->GetCellType(ScAddress(1,7,0)));
// Make sure that B5:C6 are numeric cells.
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(1,4,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(1,5,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(2,4,0)));
CPPUNIT_ASSERT_EQUAL(CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(2,5,0)));
// Make sure that formula cells in C3:C4 and C7:C8 are grouped.
pFC = m_pDoc->GetFormulaCell(ScAddress(2,2,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedTopRow());
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
pFC = m_pDoc->GetFormulaCell(ScAddress(2,6,0));
CPPUNIT_ASSERT(pFC);
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedTopRow());
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
// Undo again and make sure the recovered formulas in C5:C6 still track B5:B6.
pUndoMgr->Undo();
m_pDoc->SetValue(ScAddress(1,4,0), 10);
m_pDoc->SetValue(ScAddress(1,5,0), 11);
CPPUNIT_ASSERT_EQUAL(20.0, m_pDoc->GetValue(ScAddress(2,4,0)));
CPPUNIT_ASSERT_EQUAL(22.0, m_pDoc->GetValue(ScAddress(2,5,0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFormulaToValue2)
{
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
m_pDoc->InsertTab(0, u"Test"_ustr);
std::vector<std::vector<const char*>> aData = {
{ "=1", "=ISFORMULA(RC[-1])" },
{ "=2", "=ISFORMULA(RC[-1])" },
{ "3", "=ISFORMULA(RC[-1])" },
{ "=4", "=ISFORMULA(RC[-1])" },
{ "=5", "=ISFORMULA(RC[-1])" },
};
// Insert data into B2:C6.
ScAddress aPos(1,1,0); // B2
ScRange aDataRange = insertRangeData(m_pDoc, aPos, aData);
CPPUNIT_ASSERT_EQUAL_MESSAGE("failed to insert range data at correct position", aPos, aDataRange.aStart);
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "TRUE" },
{ "2", "TRUE" },
{ "3", "FALSE" },
{ "4", "TRUE" },
{ "5", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "Initial value");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
// Convert B3:B5 to a value.
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
ScRange aConvRange(1,2,0,1,4,0); // B3:B5
rFunc.ConvertFormulaToValue(aConvRange, false);
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "TRUE" },
{ "2", "FALSE" },
{ "3", "FALSE" },
{ "4", "FALSE" },
{ "5", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "Initial value");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
// Undo and check.
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT(pUndoMgr);
pUndoMgr->Undo();
{
// Expected output table content. 0 = empty cell
std::vector<std::vector<const char*>> aOutputCheck = {
{ "1", "TRUE" },
{ "2", "TRUE" },
{ "3", "FALSE" },
{ "4", "TRUE" },
{ "5", "TRUE" },
};
bool bSuccess = checkOutput(m_pDoc, aDataRange, aOutputCheck, "Initial value");
CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess);
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testColumnFindEditCells)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// Test the basics with real edit cells, using Column A.
SCROW nResRow = m_pDoc->GetFirstEditTextRow(ScRange(0,0,0,0,m_pDoc->MaxRow(),0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be no edit cells.", SCROW(-1), nResRow);
nResRow = m_pDoc->GetFirstEditTextRow(ScRange(0,0,0,0,0,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be no edit cells.", SCROW(-1), nResRow);
nResRow = m_pDoc->GetFirstEditTextRow(ScRange(0,0,0,0,10,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be no edit cells.", SCROW(-1), nResRow);
ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
rEE.SetTextCurrentDefaults(u"Test"_ustr);
m_pDoc->SetEditText(ScAddress(0,0,0), rEE.CreateTextObject());
const EditTextObject* pObj = m_pDoc->GetEditText(ScAddress(0,0,0));
CPPUNIT_ASSERT_MESSAGE("There should be an edit cell here.", pObj);
ScRange aRange(0,0,0,0,0,0);
nResRow = m_pDoc->GetFirstEditTextRow(aRange);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There is an edit cell here.", SCROW(0), nResRow);
aRange.aStart.SetRow(1);
aRange.aEnd.SetRow(1);
nResRow = m_pDoc->GetFirstEditTextRow(aRange);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There shouldn't be an edit cell in specified range.", SCROW(-1), nResRow);
aRange.aStart.SetRow(2);
aRange.aEnd.SetRow(4);
nResRow = m_pDoc->GetFirstEditTextRow(aRange);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There shouldn't be an edit cell in specified range.", SCROW(-1), nResRow);
aRange.aStart.SetRow(0);
aRange.aEnd.SetRow(m_pDoc->MaxRow());
nResRow = m_pDoc->GetFirstEditTextRow(aRange);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be an edit cell in specified range.", SCROW(0), nResRow);
m_pDoc->SetString(ScAddress(0,0,0), u"Test"_ustr);
m_pDoc->SetValue(ScAddress(0,2,0), 1.0);
ScRefCellValue aCell;
aCell.assign(*m_pDoc, ScAddress(0,0,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be a string cell.", CELLTYPE_STRING, aCell.getType());
aCell.assign(*m_pDoc, ScAddress(0,1,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be an empty cell.", CELLTYPE_NONE, aCell.getType());
aCell.assign(*m_pDoc, ScAddress(0,2,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be a numeric cell.", CELLTYPE_VALUE, aCell.getType());
aCell.assign(*m_pDoc, ScAddress(0,3,0));
CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be an empty cell.", CELLTYPE_NONE, aCell.getType());
aRange.aStart.SetRow(1);
aRange.aEnd.SetRow(1);
nResRow = m_pDoc->GetFirstEditTextRow(aRange);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There shouldn't be an edit cell in specified range.", SCROW(-1), nResRow);
// Test with non-edit cell but with ambiguous script type.
m_pDoc->SetString(ScAddress(1,11,0), u"Some text"_ustr);
m_pDoc->SetString(ScAddress(1,12,0), u"Some text"_ustr);
m_pDoc->SetString(ScAddress(1,13,0), u"Other text"_ustr);
m_pDoc->SetScriptType(ScAddress(1,11,0), (SvtScriptType::LATIN | SvtScriptType::ASIAN));
m_pDoc->SetScriptType(ScAddress(1,12,0), (SvtScriptType::LATIN | SvtScriptType::ASIAN));
m_pDoc->SetScriptType(ScAddress(1,13,0), (SvtScriptType::LATIN | SvtScriptType::ASIAN));
nResRow = m_pDoc->GetFirstEditTextRow(ScRange(ScAddress(1,11,0)));
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(11), nResRow);
nResRow = m_pDoc->GetFirstEditTextRow(ScRange(ScAddress(1,12,0)));
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(12), nResRow);
for (SCROW i = 0; i <= 5; ++i)
m_pDoc->SetString(ScAddress(2,i,0), u"Text"_ustr);
m_pDoc->SetScriptType(ScAddress(2,5,0), (SvtScriptType::LATIN | SvtScriptType::ASIAN));
nResRow = m_pDoc->GetFirstEditTextRow(ScRange(ScAddress(2,1,0)));
CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(-1), nResRow);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testSetFormula)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
static struct aInputs
{
SCROW nRow;
SCCOL nCol;
const char* aFormula1; // Represents the formula that is input to SetFormula function.
const char* aFormula2; // Represents the formula that is actually stored in the cell.
formula::FormulaGrammar::Grammar const eGram;
} const aTest[] = {
{ 5 , 4 , "=SUM($D$2:$F$3)" ,"=SUM($D$2:$F$3)" , formula::FormulaGrammar::Grammar::GRAM_ENGLISH },
{ 5 , 5 , "=A1-$C2+B$3-$F$4" ,"=A1-$C2+B$3-$F$4", formula::FormulaGrammar::Grammar::GRAM_NATIVE },
{ 6 , 6 , "=A1-$C2+B$3-$F$4" ,"=A1-$C2+B$3-$F$4", formula::FormulaGrammar::Grammar::GRAM_NATIVE_XL_A1},
{ 7 , 8 , "=[.A1]-[.$C2]+[.G$3]-[.$F$4]","=A1-$C2+G$3-$F$4", formula::FormulaGrammar::Grammar::GRAM_ODFF }
};
for(const auto& rTest : aTest)
{
m_pDoc->SetFormula(ScAddress(rTest.nCol, rTest.nRow, 0), OUString::createFromAscii(rTest.aFormula1), rTest.eGram);
OUString aBuffer = m_pDoc->GetFormula(rTest.nCol, rTest.nRow, 0);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to set formula", OUString::createFromAscii(rTest.aFormula2), aBuffer);
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testMultipleDataCellsInRange)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
ScRange aRange(1,2,0); // B3
sc::MultiDataCellState aState = m_pDoc->HasMultipleDataCells(aRange);
CPPUNIT_ASSERT_EQUAL(sc::MultiDataCellState::Empty, aState.meState);
// Set a numeric value to B3.
m_pDoc->SetValue(ScAddress(1,2,0), 1.0);
aState = m_pDoc->HasMultipleDataCells(aRange);
CPPUNIT_ASSERT_EQUAL(sc::MultiDataCellState::HasOneCell, aState.meState);
CPPUNIT_ASSERT_EQUAL(SCCOL(1), aState.mnCol1);
CPPUNIT_ASSERT_EQUAL(SCROW(2), aState.mnRow1);
// Set another numeric value to B4.
m_pDoc->SetValue(ScAddress(1,3,0), 2.0);
aRange.aEnd.SetRow(3); // B3:B4
aState = m_pDoc->HasMultipleDataCells(aRange);
CPPUNIT_ASSERT_EQUAL(sc::MultiDataCellState::HasMultipleCells, aState.meState);
CPPUNIT_ASSERT_EQUAL(SCCOL(1), aState.mnCol1);
CPPUNIT_ASSERT_EQUAL(SCROW(2), aState.mnRow1);
// Set the query range to B4:B5. Now it should only report one cell, with
// B4 being the first non-empty cell.
aRange.aStart.SetRow(3);
aRange.aEnd.SetRow(4);
aState = m_pDoc->HasMultipleDataCells(aRange);
CPPUNIT_ASSERT_EQUAL(sc::MultiDataCellState::HasOneCell, aState.meState);
CPPUNIT_ASSERT_EQUAL(SCCOL(1), aState.mnCol1);
CPPUNIT_ASSERT_EQUAL(SCROW(3), aState.mnRow1);
// Set the query range to A1:C3. The first non-empty cell should be B3.
aRange = ScRange(0,0,0,2,2,0);
aState = m_pDoc->HasMultipleDataCells(aRange);
CPPUNIT_ASSERT_EQUAL(sc::MultiDataCellState::HasOneCell, aState.meState);
CPPUNIT_ASSERT_EQUAL(SCCOL(1), aState.mnCol1);
CPPUNIT_ASSERT_EQUAL(SCROW(2), aState.mnRow1);
// Set string cells to D4 and F5, and query D3:F5. D4 should be the first
// non-empty cell.
m_pDoc->SetString(ScAddress(3,3,0), u"foo"_ustr);
m_pDoc->SetString(ScAddress(5,4,0), u"bar"_ustr);
aRange = ScRange(3,2,0,5,4,0);
aState = m_pDoc->HasMultipleDataCells(aRange);
CPPUNIT_ASSERT_EQUAL(sc::MultiDataCellState::HasMultipleCells, aState.meState);
CPPUNIT_ASSERT_EQUAL(SCCOL(3), aState.mnCol1);
CPPUNIT_ASSERT_EQUAL(SCROW(3), aState.mnRow1);
// TODO : add more test cases as needed.
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testFormulaWizardSubformula)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetString(ScAddress(1,0,0), u"=1"_ustr); // B1
m_pDoc->SetString(ScAddress(1,1,0), u"=1/0"_ustr); // B2
m_pDoc->SetString(ScAddress(1,2,0), u"=gibberish"_ustr); // B3
ScSimpleFormulaCalculator aFCell1( *m_pDoc, ScAddress(0,0,0), u"=B1:B3"_ustr, true );
FormulaError nErrCode = aFCell1.GetErrCode();
CPPUNIT_ASSERT( nErrCode == FormulaError::NONE || aFCell1.IsMatrix() );
CPPUNIT_ASSERT_EQUAL( u"{1|#DIV/0!|#NAME?}"_ustr, aFCell1.GetString().getString() );
m_pDoc->SetString(ScAddress(1,0,0), u"=NA()"_ustr); // B1
m_pDoc->SetString(ScAddress(1,1,0), u"2"_ustr); // B2
m_pDoc->SetString(ScAddress(1,2,0), u"=1+2"_ustr); // B3
ScSimpleFormulaCalculator aFCell2( *m_pDoc, ScAddress(0,0,0), u"=B1:B3"_ustr, true );
nErrCode = aFCell2.GetErrCode();
CPPUNIT_ASSERT( nErrCode == FormulaError::NONE || aFCell2.IsMatrix() );
CPPUNIT_ASSERT_EQUAL( u"{#N/A|2|3}"_ustr, aFCell2.GetString().getString() );
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testDiagonalBorders)
{
m_pDoc->InsertTab(0, u"Diagonal"_ustr);
ScAddress aPos;
const editeng::SvxBorderLine* pLine;
const ScPatternAttr* pPat;
// diagonal down border
::editeng::SvxBorderLine dDownBorderLine(nullptr, 1);
SvxLineItem dDownLineItem(ATTR_BORDER_TLBR);
dDownLineItem.SetLine(&dDownBorderLine);
// set diagonal down border to cell(A1)
m_pDoc->ApplyAttr(0, 0, 0, dDownLineItem);
aPos = { 0, 0, 0 };
pPat = m_pDoc->GetPattern(aPos);
CPPUNIT_ASSERT(pPat);
pLine = pPat->GetItem(ATTR_BORDER_TLBR).GetLine();
CPPUNIT_ASSERT_MESSAGE("Diagonal down border was expected, but not found!", pLine);
// diagonal up border
::editeng::SvxBorderLine dUpBorderLine(nullptr, 1);
SvxLineItem dUpLineItem(ATTR_BORDER_BLTR);
dUpLineItem.SetLine(&dUpBorderLine);
// set diagonal up border to cell(A2)
m_pDoc->ApplyAttr(0, 1, 0, dUpLineItem);
aPos = { 0, 1, 0 };
pPat = m_pDoc->GetPattern(aPos);
CPPUNIT_ASSERT(pPat);
pLine = pPat->GetItem(ATTR_BORDER_BLTR).GetLine();
CPPUNIT_ASSERT_MESSAGE("Diagonal up border was expected, but not found!", pLine);
// diagonal down and up border in the same cell (A5)
m_pDoc->ApplyAttr(0, 4, 0, dDownLineItem);
m_pDoc->ApplyAttr(0, 4, 0, dUpLineItem);
// test if both borders are applied successfully in the same cell (A5)
aPos = { 0, 4, 0 };
pPat = m_pDoc->GetPattern(aPos);
CPPUNIT_ASSERT(pPat);
pLine = pPat->GetItem(ATTR_BORDER_TLBR).GetLine();
CPPUNIT_ASSERT_MESSAGE("Diagonal down border was expected, but not found!", pLine);
pLine = pPat->GetItem(ATTR_BORDER_BLTR).GetLine();
CPPUNIT_ASSERT_MESSAGE("Diagonal up border was expected, but not found!", pLine);
// test if both borders are removed successfully
dDownLineItem.SetLine(nullptr);
dUpLineItem.SetLine(nullptr);
// SetLine(nullptr) should remove the lines from (A5)
m_pDoc->ApplyAttr(0, 4, 0, dDownLineItem);
m_pDoc->ApplyAttr(0, 4, 0, dUpLineItem);
pPat = m_pDoc->GetPattern(aPos);
CPPUNIT_ASSERT(pPat);
pLine = pPat->GetItem(ATTR_BORDER_TLBR).GetLine();
CPPUNIT_ASSERT_MESSAGE("Diagonal down border was not expected, but is found!", !pLine);
pLine = pPat->GetItem(ATTR_BORDER_BLTR).GetLine();
CPPUNIT_ASSERT_MESSAGE("Diagonal up border was not expected, but is found!", !pLine);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testWholeDocBorders)
{
m_pDoc->InsertTab(0, u"Borders"_ustr);
// Set outside border to be on all sides, and inside borders to be only vertical.
// This should result in edge borders of the spreadsheets being set, but internal
// borders between cells should be only vertical, not horizontal.
::editeng::SvxBorderLine line(nullptr, 50, SvxBorderLineStyle::SOLID);
SvxBoxItem borderItem(ATTR_BORDER);
borderItem.SetLine(&line, SvxBoxItemLine::LEFT);
borderItem.SetLine(&line, SvxBoxItemLine::RIGHT);
borderItem.SetLine(&line, SvxBoxItemLine::TOP);
borderItem.SetLine(&line, SvxBoxItemLine::BOTTOM);
SvxBoxInfoItem boxInfoItem(ATTR_BORDER);
boxInfoItem.SetLine(&line, SvxBoxInfoItemLine::VERT);
ScMarkData mark( m_pDoc->GetSheetLimits(), ScRange( 0, 0, 0, m_pDoc->MaxCol(), m_pDoc->MaxRow(), 0 ));
m_pDoc->ApplySelectionFrame( mark, borderItem, &boxInfoItem );
const ScPatternAttr* attr;
attr = m_pDoc->GetPattern( 0, 0, 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetBottom());
attr = m_pDoc->GetPattern( 1, 0, 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetBottom());
attr = m_pDoc->GetPattern( 0, 1, 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetBottom());
attr = m_pDoc->GetPattern( 1, 1, 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetBottom());
attr = m_pDoc->GetPattern( m_pDoc->MaxCol(), 0, 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetBottom());
attr = m_pDoc->GetPattern( 0, m_pDoc->MaxRow(), 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetBottom());
attr = m_pDoc->GetPattern( m_pDoc->MaxCol(), m_pDoc->MaxRow(), 0 );
CPPUNIT_ASSERT(attr);
CPPUNIT_ASSERT(!attr->GetItem(ATTR_BORDER).GetTop());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetLeft());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetRight());
CPPUNIT_ASSERT(attr->GetItem(ATTR_BORDER).GetBottom());
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testSetStringAndNote)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// We need a drawing layer in order to create caption objects.
m_pDoc->InitDrawLayer(m_xDocShell.get());
//note on A1
ScAddress aAdrA1 (0, 0, 0);
ScPostIt* pNote = m_pDoc->GetOrCreateNote(aAdrA1);
pNote->SetText(aAdrA1, u"Hello world in A1"_ustr);
m_pDoc->SetString(0, 0, 0, u""_ustr);
pNote = m_pDoc->GetNote(aAdrA1);
CPPUNIT_ASSERT(pNote);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testUndoDataAnchor)
{
m_pDoc->InsertTab(0, u"Tab1"_ustr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be only 1 sheets to begin with",
static_cast<SCTAB>(1), m_pDoc->GetTableCount());
m_pDoc->InitDrawLayer();
ScDrawLayer* pDrawLayer = m_pDoc->GetDrawLayer();
CPPUNIT_ASSERT_MESSAGE("No drawing layer.", pDrawLayer);
SdrPage* pPage = pDrawLayer->GetPage(0);
CPPUNIT_ASSERT_MESSAGE("No page instance for the 1st sheet.", pPage);
// Insert an object.
tools::Rectangle aObjRect(2,1000,100,1100);
rtl::Reference<SdrObject> pObj = new SdrRectObj(*pDrawLayer, aObjRect);
pPage->InsertObject(pObj.get());
ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, false);
// Get anchor data
ScDrawObjData* pData = ScDrawLayer::GetObjData(pObj.get());
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData);
ScAddress aOldStart = pData->maStart;
ScAddress aOldEnd = pData->maEnd;
// Get non rotated anchor data
ScDrawObjData* pNData = ScDrawLayer::GetNonRotatedObjData( pObj.get() );
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData);
ScAddress aNOldStart = pNData->maStart;
ScAddress aNOldEnd = pNData->maEnd;
CPPUNIT_ASSERT_EQUAL(aOldStart, aNOldStart);
CPPUNIT_ASSERT_EQUAL(aOldEnd, aNOldEnd);
//pDrawLayer->BeginCalcUndo(false);
// Insert a new row at row 3.
ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
rFunc.InsertCells(ScRange( 0, aOldStart.Row() - 1, 0, m_pDoc->MaxCol(), aOldStart.Row(), 0 ), &aMark, INS_INSROWS_BEFORE, true, true);
pData = ScDrawLayer::GetObjData(pObj.get());
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData);
ScAddress aNewStart = pData->maStart;
ScAddress aNewEnd = pData->maEnd;
// Get non rotated anchor data
pNData = ScDrawLayer::GetNonRotatedObjData( pObj.get() );
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData);
ScAddress aNNewStart = pNData->maStart;
ScAddress aNNewEnd = pNData->maEnd;
CPPUNIT_ASSERT_EQUAL(aNewStart, aNNewStart);
CPPUNIT_ASSERT_EQUAL(aNewEnd, aNNewEnd);
CPPUNIT_ASSERT_MESSAGE("Failed to compare Address.", aNewStart != aOldStart );
CPPUNIT_ASSERT_MESSAGE("Failed to compare Address.", aNewEnd != aOldEnd );
CPPUNIT_ASSERT_MESSAGE("Failed to compare Address.", aNNewStart != aNOldStart );
CPPUNIT_ASSERT_MESSAGE("Failed to compare Address.", aNNewEnd != aNOldEnd );
SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
CPPUNIT_ASSERT(pUndoMgr);
pUndoMgr->Undo();
// Check state
ScAnchorType oldType = ScDrawLayer::GetAnchorType(*pObj);
CPPUNIT_ASSERT_EQUAL_MESSAGE( "Failed to check state SCA_CELL.", SCA_CELL, oldType);
// Get anchor data
pData = ScDrawLayer::GetObjData(pObj.get());
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData);
// Get non rotated anchor data
pNData = ScDrawLayer::GetNonRotatedObjData( pObj.get() );
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData);
// Check if data has moved to new rows
CPPUNIT_ASSERT_EQUAL(pData->maStart, aOldStart);
CPPUNIT_ASSERT_EQUAL(pData->maEnd, aOldEnd);
CPPUNIT_ASSERT_EQUAL(pNData->maStart, aNOldStart);
CPPUNIT_ASSERT_EQUAL(pNData->maEnd, aNOldEnd);
pUndoMgr->Redo();
// Get anchor data
pData = ScDrawLayer::GetObjData(pObj.get());
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData);
// Get non rotated anchor data
pNData = ScDrawLayer::GetNonRotatedObjData( pObj.get() );
CPPUNIT_ASSERT_MESSAGE("Failed to retrieve non rotated user data for this object.", pNData);
// Check if data has moved to new rows
CPPUNIT_ASSERT_EQUAL(pData->maStart, aNewStart);
CPPUNIT_ASSERT_EQUAL(pData->maEnd, aNewEnd);
CPPUNIT_ASSERT_EQUAL(pNData->maStart, aNNewStart);
CPPUNIT_ASSERT_EQUAL(pNData->maEnd, aNNewEnd);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testEmptyCalcDocDefaults)
{
CPPUNIT_ASSERT_EQUAL( sal_uInt64(0), m_pDoc->GetCellCount() );
CPPUNIT_ASSERT_EQUAL( sal_uInt64(0), m_pDoc->GetFormulaGroupCount() );
CPPUNIT_ASSERT_EQUAL( sal_uInt64(0), m_pDoc->GetCodeCount() );
CPPUNIT_ASSERT_EQUAL( int(CharCompressType::NONE), static_cast<int>(m_pDoc->GetAsianCompression()) );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasPrintRange() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsInVBAMode() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasNotes() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsCutMode() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsEmbedFonts() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsEmbedUsedFontsOnly() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsEmbedFontScriptLatin() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsEmbedFontScriptAsian() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsEmbedFontScriptComplex() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsEmbedded() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsDocEditable() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsDocProtected() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsDocVisible() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsUserInteractionEnabled() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasAnyCalcNotification() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsAutoCalcShellDisabled() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsForcedFormulaPending() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsCalculatingFormulaTree() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsClipOrUndo() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsClipboard() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsUndo() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsUndoEnabled() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsCutMode() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsClipboardSource() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsInsertingFromOtherDoc() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->PastingDrawFromOtherDoc() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsAdjustHeightLocked() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsExecuteLinkEnabled() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsChangeReadOnlyEnabled() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IdleCalcTextWidth() );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsIdleEnabled() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsDetectiveDirty() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasLinkFormulaNeedingCheck() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsChartListenerCollectionNeedsUpdate() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasRangeOverflow() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsImportingXML() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsCalcingAfterLoad() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->GetNoListening() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsValidAsianCompression() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->GetAsianKerning() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsValidAsianKerning() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsInInterpreter() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsInInterpreterTableOp() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsInDtorClear() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsExpandRefs() );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsInLinkUpdate() );
SCTAB tab = m_pDoc->GetVisibleTab();
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsVisible(tab) );
CPPUNIT_ASSERT_EQUAL( true, m_pDoc->IsDefaultTabBgColor(tab) );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasTable(tab) );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->IsActiveScenario(tab) );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasCalcNotification(tab) );
CPPUNIT_ASSERT_EQUAL( false, m_pDoc->HasManualBreaks(tab) );
}
void Test::checkPrecisionAsShown( OUString& rCode, double fValue, double fExpectedRoundVal )
{
SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
sal_uInt32 nFormat = pFormatter->GetEntryKey( rCode );
if ( nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
{
sal_Int32 nCheckPos = 0;
SvNumFormatType nType;
pFormatter->PutEntry( rCode, nCheckPos, nType, nFormat );
CPPUNIT_ASSERT_EQUAL( sal_Int32(0), nCheckPos );
}
double fRoundValue = m_pDoc->RoundValueAsShown( fValue, nFormat );
OString aMessage = "Format \"" +
OUStringToOString( rCode, RTL_TEXTENCODING_ASCII_US ) +
"\" is not correctly rounded";
CPPUNIT_ASSERT_EQUAL_MESSAGE( aMessage.getStr(), fExpectedRoundVal, fRoundValue );
}
CPPUNIT_TEST_FIXTURE(Test, testPrecisionAsShown)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
// Turn on "precision as shown" option.
setCalcAsShown( m_pDoc, true);
OUString aCode;
double fValue, fExpectedRoundVal;
{ // decimal rounding
aCode = "0.00";
fValue = 1.0/3.0;
fExpectedRoundVal = 0.33;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 10.001;
fExpectedRoundVal = 10.0;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
}
{ // thousand rounding tdf#106253
aCode = "0,,";
fValue = 4.0e9 / 7.0;
fExpectedRoundVal = 571e6;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
aCode = "\"k\"[$$-409]* #,;[RED]-\"k\"[$$-409]* #,";
fValue = 4.0e8 / 7.0;
fExpectedRoundVal = 57.143e6;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
}
{ // percent rounding
aCode = "0.00%";
fValue = 4.0 / 7.0;
fExpectedRoundVal = 0.5714;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 40.0 / 7.0;
fExpectedRoundVal = 5.7143;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
}
{ // scientific rounding
aCode = "0.00E0";
fValue = 400000.0 / 7.0;
fExpectedRoundVal = 57100.0;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 70000.0;
fExpectedRoundVal = 5.71e-5;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
// engineering rounding tdf#106252
aCode = "##0.000E0";
fValue = 400000.0 / 7.0;
fExpectedRoundVal = 57.143e3;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4000000.0 / 7.0;
fExpectedRoundVal = 571.429e3;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 40000000.0 / 7.0;
fExpectedRoundVal = 5.714e6;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 70000.0;
fExpectedRoundVal = 57.143e-6;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 7000.0;
fExpectedRoundVal = 571.429e-6;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 700.0;
fExpectedRoundVal = 5.714e-3;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
aCode = "##?0.0#E0";
fValue = 400000.0 / 7.0;
fExpectedRoundVal = 5.71e4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4000000.0 / 7.0;
fExpectedRoundVal = 57.14e4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 40000000.0 / 7.0;
fExpectedRoundVal = 571.43e4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 400000000.0 / 7.0;
fExpectedRoundVal = 5714.29e4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 70000.0;
fExpectedRoundVal = 5714.29e-8;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 7000.0;
fExpectedRoundVal = 5.71e-4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 700.0;
fExpectedRoundVal = 57.14e-4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
fValue = 4.0 / 70.0;
fExpectedRoundVal = 571.43e-4;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
}
{ // fraction rounding tdf#105657
aCode = "# ?/?";
fValue = 0.35;
fExpectedRoundVal = 1.0/3.0;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
}
{ // exact fraction
aCode = "# ?/??";
fValue = 0.35;
fExpectedRoundVal = 0.35;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
checkPrecisionAsShown( aCode, -fValue, -fExpectedRoundVal );
}
{ // several sub-formats tdf#106052
aCode = "0.00;-0.000";
fValue = 1.0/3.0;
fExpectedRoundVal = 0.33;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
fValue = -1.0/3.0;
fExpectedRoundVal = -0.333;
checkPrecisionAsShown( aCode, fValue, fExpectedRoundVal );
}
setCalcAsShown( m_pDoc, false);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testProtectedSheetEditByRow)
{
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
m_pDoc->InsertTab(0, u"Protected"_ustr);
{
// Remove protected flags from rows 2-5.
ScPatternAttr aAttr(m_pDoc->getCellAttributeHelper());
aAttr.GetItemSet().Put(ScProtectionAttr(false));
m_pDoc->ApplyPatternAreaTab(0, 1, m_pDoc->MaxCol(), 4, 0, aAttr);
// Protect the sheet without any options.
ScTableProtection aProtect;
aProtect.setProtected(true);
m_pDoc->SetTabProtection(0, &aProtect);
// Try to delete row 3. It should fail.
ScRange aRow3(0,2,0,m_pDoc->MaxCol(),2,0);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
bool bDeleted = rDocFunc.DeleteCells(aRow3, &aMark, DelCellCmd::Rows, true);
CPPUNIT_ASSERT_MESSAGE("deletion of row 3 should fail.", !bDeleted);
// Protect the sheet but allow row deletion.
aProtect.setOption(ScTableProtection::DELETE_ROWS, true);
m_pDoc->SetTabProtection(0, &aProtect);
// Now we should be able to delete row 3.
bDeleted = rDocFunc.DeleteCells(aRow3, &aMark, DelCellCmd::Rows, true);
CPPUNIT_ASSERT_MESSAGE("deletion of row 3 should succeed.", bDeleted);
// But, row deletion should still fail on a protected row.
ScRange aRow10(0,9,0,m_pDoc->MaxCol(),9,0);
bDeleted = rDocFunc.DeleteCells(aRow10, &aMark, DelCellCmd::Rows, true);
CPPUNIT_ASSERT_MESSAGE("deletion of row 10 should not be allowed.", !bDeleted);
// Try inserting a new row. It should fail.
bool bInserted = rDocFunc.InsertCells(aRow3, &aMark, INS_INSROWS_AFTER, true, true);
CPPUNIT_ASSERT_MESSAGE("row insertion at row 3 should fail.", !bInserted);
// Allow row insertions.
aProtect.setOption(ScTableProtection::INSERT_ROWS, true);
m_pDoc->SetTabProtection(0, &aProtect);
bInserted = rDocFunc.InsertCells(aRow3, &aMark, INS_INSROWS_AFTER, true, true);
CPPUNIT_ASSERT_MESSAGE("row insertion at row 3 should succeed.", bInserted);
// Row insertion is allowed even when the rows above and below have protected flags set.
bInserted = rDocFunc.InsertCells(aRow10, &aMark, INS_INSROWS_AFTER, true, true);
CPPUNIT_ASSERT_MESSAGE("row insertion at row 10 should succeed.", bInserted);
}
m_pDoc->InsertTab(1, u"Matrix"_ustr); // This sheet is unprotected.
{
// Insert matrix into B2:C3.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(1);
m_pDoc->InsertMatrixFormula(1, 1, 2, 2, aMark, u"={1;2|3;4}"_ustr);
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,1,1)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,1,1)));
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,1)));
CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(2,2,1)));
// Try to insert a row at row 3. It should fail because of matrix's presence.
ScRange aRow3(0,2,1,m_pDoc->MaxCol(),2,1);
bool bInserted = rDocFunc.InsertCells(aRow3, &aMark, INS_INSROWS_BEFORE, true, true);
CPPUNIT_ASSERT_MESSAGE("row insertion at row 3 should fail.", !bInserted);
}
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testProtectedSheetEditByColumn)
{
ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
m_pDoc->InsertTab(0, u"Protected"_ustr);
{
// Remove protected flags from columns B to E.
ScPatternAttr aAttr(m_pDoc->getCellAttributeHelper());
aAttr.GetItemSet().Put(ScProtectionAttr(false));
m_pDoc->ApplyPatternAreaTab(1, 0, 4, m_pDoc->MaxRow(), 0, aAttr);
// Protect the sheet without any options.
ScTableProtection aProtect;
aProtect.setProtected(true);
m_pDoc->SetTabProtection(0, &aProtect);
// Try to delete column C. It should fail.
ScRange aCol3(2,0,0,2,m_pDoc->MaxRow(),0);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
bool bDeleted = rDocFunc.DeleteCells(aCol3, &aMark, DelCellCmd::Cols, true);
CPPUNIT_ASSERT_MESSAGE("deletion of column 3 should fail.", !bDeleted);
// Protect the sheet but allow column deletion.
aProtect.setOption(ScTableProtection::DELETE_COLUMNS, true);
m_pDoc->SetTabProtection(0, &aProtect);
// Now we should be able to delete column C.
bDeleted = rDocFunc.DeleteCells(aCol3, &aMark, DelCellCmd::Cols, true);
CPPUNIT_ASSERT_MESSAGE("deletion of column 3 should succeed.", bDeleted);
// But, column deletion should still fail on a protected column.
ScRange aCol10(9,0,0,9,m_pDoc->MaxRow(),0);
bDeleted = rDocFunc.DeleteCells(aCol10, &aMark, DelCellCmd::Cols, true);
CPPUNIT_ASSERT_MESSAGE("deletion of column 10 should not be allowed.", !bDeleted);
// Try inserting a new column. It should fail.
bool bInserted = rDocFunc.InsertCells(aCol3, &aMark, INS_INSCOLS_AFTER, true, true);
CPPUNIT_ASSERT_MESSAGE("column insertion at column 3 should fail.", !bInserted);
// Allow column insertions.
aProtect.setOption(ScTableProtection::INSERT_COLUMNS, true);
m_pDoc->SetTabProtection(0, &aProtect);
bInserted = rDocFunc.InsertCells(aCol3, &aMark, INS_INSCOLS_AFTER, true, true);
CPPUNIT_ASSERT_MESSAGE("column insertion at column 3 should succeed.", bInserted);
// Column insertion is allowed even when the columns above and below have protected flags set.
bInserted = rDocFunc.InsertCells(aCol10, &aMark, INS_INSCOLS_AFTER, true, true);
CPPUNIT_ASSERT_MESSAGE("column insertion at column 10 should succeed.", bInserted);
}
m_pDoc->InsertTab(1, u"Matrix"_ustr); // This sheet is unprotected.
{
// Insert matrix into B2:C3.
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(1);
m_pDoc->InsertMatrixFormula(1, 1, 2, 2, aMark, u"={1;2|3;4}"_ustr);
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,1,1)));
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,1,1)));
CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,1)));
CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(2,2,1)));
// Try to insert a column at column C. It should fail because of matrix's presence.
ScRange aCol3(2,0,1,2,m_pDoc->MaxRow(),1);
bool bInserted = rDocFunc.InsertCells(aCol3, &aMark, INS_INSCOLS_BEFORE, true, true);
CPPUNIT_ASSERT_MESSAGE("column insertion at column C should fail.", !bInserted);
}
m_pDoc->DeleteTab(1);
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testInsertColumnsWithFormulaCells)
{
m_pDoc->InsertTab(0, u"Tab1"_ustr);
std::set<SCCOL> aCols = m_pDoc->QueryColumnsWithFormulaCells(0);
CPPUNIT_ASSERT_MESSAGE("empty sheet should contain no formula cells.", aCols.empty());
auto equals = [](const std::set<SCCOL>& left, const std::set<SCCOL>& right)
{
return left == right;
};
// insert formula cells in columns 2, 4 and 6.
m_pDoc->SetFormula(ScAddress(2, 2, 0), u"=1"_ustr, m_pDoc->GetGrammar());
m_pDoc->SetFormula(ScAddress(4, 2, 0), u"=1"_ustr, m_pDoc->GetGrammar());
m_pDoc->SetFormula(ScAddress(6, 2, 0), u"=1"_ustr, m_pDoc->GetGrammar());
aCols = m_pDoc->QueryColumnsWithFormulaCells(0);
std::set<SCCOL> aExpected = { 2, 4, 6 };
CPPUNIT_ASSERT_MESSAGE("Columns 2, 4 and 6 should contain formula cells.", equals(aExpected, aCols));
// Insert 2 columns at column A to shift everything to right by 2.
m_pDoc->InsertCol(0, 0, m_pDoc->MaxRow(), 0, 0, 2);
aExpected = { 4, 6, 8 };
aCols = m_pDoc->QueryColumnsWithFormulaCells(0);
CPPUNIT_ASSERT_MESSAGE("Columns 4, 6 and 8 should contain formula cells.", equals(aExpected, aCols));
try
{
m_pDoc->CheckIntegrity(0);
}
catch (const std::exception& e)
{
std::ostringstream os;
os << "document integrity check failed: " << e.what();
CPPUNIT_FAIL(os.str());
}
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(Test, testDocumentModelAccessor_getDocumentCurrencies)
{
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
// Check document currencies - expect 0
auto pAccessor = m_xDocShell->GetDocumentModelAccessor();
CPPUNIT_ASSERT(pAccessor);
CPPUNIT_ASSERT_EQUAL(size_t(0), pAccessor->getDocumentCurrencies().size());
// Set a currency to a cell
{
m_pDoc->SetValue(ScAddress(0, 0, 0), 2.0);
OUString aCode = u"#.##0,00[$€-424]"_ustr;
sal_Int32 nCheckPos;
SvNumFormatType eType;
sal_uInt32 nFormat;
m_pDoc->GetFormatTable()->PutEntry(aCode, nCheckPos, eType, nFormat, LANGUAGE_SLOVENIAN);
CPPUNIT_ASSERT_EQUAL(SvNumFormatType::CURRENCY, eType);
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs); // A1.
CPPUNIT_ASSERT_EQUAL(u"2,00€"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
}
// Check document currencies again
auto aCurrencyIDs = pAccessor->getDocumentCurrencies();
CPPUNIT_ASSERT_EQUAL(size_t(1), aCurrencyIDs.size());
CPPUNIT_ASSERT_EQUAL(LANGUAGE_SLOVENIAN, aCurrencyIDs[0].eLanguage);
CPPUNIT_ASSERT_EQUAL(u"-424"_ustr, aCurrencyIDs[0].aExtension);
CPPUNIT_ASSERT_EQUAL(u""_ustr, aCurrencyIDs[0].aSymbol);
// Set the same currency to 2 more cells
{
m_pDoc->SetValue(ScAddress(1, 1, 0), 5.0);
m_pDoc->SetValue(ScAddress(2, 2, 0), 7.0);
OUString aCode = u"#.##0,00[$€-424]"_ustr;
sal_Int32 nCheckPos;
SvNumFormatType eType;
sal_uInt32 nFormat;
m_pDoc->GetFormatTable()->PutEntry(aCode, nCheckPos, eType, nFormat, LANGUAGE_SLOVENIAN);
CPPUNIT_ASSERT_EQUAL(SvNumFormatType::CURRENCY, eType);
ScPatternAttr aNewAttrs(m_pDoc->getCellAttributeHelper());
SfxItemSet& rSet = aNewAttrs.GetItemSet();
rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
m_pDoc->ApplyPattern(1, 1, 0, aNewAttrs); // B2.
m_pDoc->ApplyPattern(2, 2, 0, aNewAttrs); // C3.
CPPUNIT_ASSERT_EQUAL(u"5,00€"_ustr, m_pDoc->GetString(ScAddress(1, 1, 0)));
CPPUNIT_ASSERT_EQUAL(u"7,00€"_ustr, m_pDoc->GetString(ScAddress(2, 2, 0)));
}
// Check document currencies again - should be 1 entry only
aCurrencyIDs = pAccessor->getDocumentCurrencies();
CPPUNIT_ASSERT_EQUAL(size_t(1), aCurrencyIDs.size());
CPPUNIT_ASSERT_EQUAL(LANGUAGE_SLOVENIAN, aCurrencyIDs[0].eLanguage);
CPPUNIT_ASSERT_EQUAL(u"-424"_ustr, aCurrencyIDs[0].aExtension);
CPPUNIT_ASSERT_EQUAL(u""_ustr, aCurrencyIDs[0].aSymbol);
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */