summaryrefslogtreecommitdiffstats
path: root/sc/qa/unit/ucalc.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/qa/unit/ucalc.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/qa/unit/ucalc.cxx')
-rw-r--r--sc/qa/unit/ucalc.cxx6948
1 files changed, 6948 insertions, 0 deletions
diff --git a/sc/qa/unit/ucalc.cxx b/sc/qa/unit/ucalc.cxx
new file mode 100644
index 000000000..df937c50f
--- /dev/null
+++ b/sc/qa/unit/ucalc.cxx
@@ -0,0 +1,6948 @@
+/* -*- 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 <scdll.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 <columniterator.hxx>
+#include <scopetools.hxx>
+#include <dociter.hxx>
+#include <edittextiterator.hxx>
+#include <editutil.hxx>
+#include <asciiopt.hxx>
+#include <impex.hxx>
+#include <docpool.hxx>
+#include <globalnames.hxx>
+#include <columnspanset.hxx>
+
+#include <editable.hxx>
+#include <tabprotection.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 <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/sfxsids.hrc>
+
+class ScUndoPaste;
+class ScUndoCut;
+
+class Test : public test::BootstrapFixture
+{
+public:
+ Test();
+
+ void checkPrecisionAsShown(OUString& rCode, double fValue, double fExpectedRoundVal);
+
+ /** Get a separate new ScDocShell with ScDocument that suits unit test needs. */
+ void getNewDocShell(ScDocShellRef& rDocShellRef);
+ /** Close such new ScDocShell. */
+ void closeDocShell(ScDocShellRef& rDocShellRef);
+
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ void testCollator();
+ void testSharedStringPool();
+ void testSharedStringPoolUndoDoc();
+ void testRangeList();
+ void testMarkData();
+ void testInput();
+ void testColumnIterator();
+ void testTdf90698();
+ void testTdf114406();
+ void testTdf93951();
+ void testTdf134490();
+ void testTdf135249();
+ void testDocStatistics();
+ void testRowForHeight();
+
+ /**
+ * The 'data entries' data is a list of strings used for suggestions as
+ * the user types in new cell value.
+ */
+ void testDataEntries();
+
+ /**
+ * Selection function is responsible for displaying quick calculation
+ * results in the status bar.
+ */
+ void testSelectionFunction();
+
+ void testMarkedCellIteration();
+
+ void testCopyToDocument();
+
+ void testHorizontalIterator();
+ void testValueIterator();
+ void testHorizontalAttrIterator();
+ void testIteratorsUnallocatedColumnsAttributes();
+ void testIteratorsDefPattern();
+ void testLastChangedColFlagsWidth();
+
+ /**
+ * More direct test for cell broadcaster management, used to track formula
+ * dependencies.
+ */
+ void testCellBroadcaster();
+
+ void testFuncParam();
+ void testNamedRange();
+ void testInsertNameList();
+ void testCSV();
+ void testMatrix();
+ void testMatrixComparisonWithErrors();
+ void testMatrixConditionalBooleanResult();
+ void testEnterMixedMatrix();
+ void testMatrixEditable();
+
+ void testCellCopy();
+ void testSheetCopy();
+ void testSheetMove();
+ void testDataArea();
+ void testAutofilter();
+ void testAutoFilterTimeValue();
+ void testAutofilterOptimizations();
+ void testTdf76836();
+ void testTdf76441();
+ void testTdf142186();
+ void testTdf137063();
+ void testTdf126342();
+ void testAdvancedFilter();
+ void testDateFilterContains();
+ void testTdf98642();
+ void testMergedCells();
+ void testUpdateReference();
+ void testSearchCells();
+ void testFormulaPosition();
+ void testFormulaWizardSubformula();
+ void testDiagonalBorders();
+ void testWholeDocBorders();
+
+ /**
+ * Make sure the sheet streams are invalidated properly.
+ */
+ void testStreamValid();
+
+ /**
+ * Test built-in cell functions to make sure their categories and order
+ * are correct.
+ */
+ void testFunctionLists();
+
+ void testGraphicsInGroup();
+ void testGraphicsOnSheetMove();
+
+ /**
+ * 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.
+ */
+ void testToggleRefFlag();
+
+ /**
+ * Test to make sure correct precedent / dependent cells are obtained when
+ * preparing to jump to them.
+ */
+ void testJumpToPrecedentsDependents();
+
+ void testSetBackgroundColor();
+ void testRenameTable();
+
+ void testTdf64001();
+ void testAutoFill();
+ void testAutoFillSimple();
+
+ void testFindAreaPosVertical();
+ void testFindAreaPosColRight();
+ void testShiftCells();
+
+ void testNoteBasic();
+ void testNoteDeleteRow();
+ void testNoteDeleteCol();
+ void testNoteLifeCycle();
+ void testNoteCopyPaste();
+ void testNoteContainsNotesInRange();
+ void testAreasWithNotes();
+ void testAnchoredRotatedShape();
+ void testCellTextWidth();
+ void testEditTextIterator();
+
+ void testImportStream();
+ void testDeleteContents();
+ void testTransliterateText();
+
+ void testFormulaToValue();
+ void testFormulaToValue2();
+
+ void testColumnFindEditCells();
+ void testSetStringAndNote();
+
+ void testUndoDataAnchor();
+ void testSetFormula();
+ void testMultipleDataCellsInRange();
+
+ void testEmptyCalcDocDefaults();
+
+ void testPrecisionAsShown();
+ void testProtectedSheetEditByRow();
+ void testProtectedSheetEditByColumn();
+
+ void testInsertColumnsWithFormulaCells();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testCollator);
+ CPPUNIT_TEST(testSharedStringPool);
+ CPPUNIT_TEST(testSharedStringPoolUndoDoc);
+ CPPUNIT_TEST(testRangeList);
+ CPPUNIT_TEST(testMarkData);
+ CPPUNIT_TEST(testInput);
+ CPPUNIT_TEST(testColumnIterator);
+ CPPUNIT_TEST(testTdf90698);
+ CPPUNIT_TEST(testTdf114406);
+ CPPUNIT_TEST(testTdf93951);
+ CPPUNIT_TEST(testTdf134490);
+ CPPUNIT_TEST(testTdf135249);
+ CPPUNIT_TEST(testDocStatistics);
+ CPPUNIT_TEST(testRowForHeight);
+ CPPUNIT_TEST(testDataEntries);
+ CPPUNIT_TEST(testSelectionFunction);
+ CPPUNIT_TEST(testMarkedCellIteration);
+ CPPUNIT_TEST(testCopyToDocument);
+ CPPUNIT_TEST(testHorizontalIterator);
+ CPPUNIT_TEST(testValueIterator);
+ CPPUNIT_TEST(testHorizontalAttrIterator);
+ CPPUNIT_TEST(testIteratorsUnallocatedColumnsAttributes);
+ CPPUNIT_TEST(testIteratorsDefPattern);
+ CPPUNIT_TEST(testLastChangedColFlagsWidth);
+ CPPUNIT_TEST(testCellBroadcaster);
+ CPPUNIT_TEST(testFuncParam);
+ CPPUNIT_TEST(testNamedRange);
+ CPPUNIT_TEST(testInsertNameList);
+ CPPUNIT_TEST(testCSV);
+ CPPUNIT_TEST(testMatrix);
+ CPPUNIT_TEST(testMatrixComparisonWithErrors);
+ CPPUNIT_TEST(testMatrixConditionalBooleanResult);
+ CPPUNIT_TEST(testEnterMixedMatrix);
+ CPPUNIT_TEST(testMatrixEditable);
+ CPPUNIT_TEST(testCellCopy);
+ CPPUNIT_TEST(testSheetCopy);
+ CPPUNIT_TEST(testSheetMove);
+ CPPUNIT_TEST(testDataArea);
+ CPPUNIT_TEST(testGraphicsInGroup);
+ CPPUNIT_TEST(testGraphicsOnSheetMove);
+ CPPUNIT_TEST(testStreamValid);
+ CPPUNIT_TEST(testFunctionLists);
+ CPPUNIT_TEST(testToggleRefFlag);
+ CPPUNIT_TEST(testAutofilter);
+ CPPUNIT_TEST(testAutoFilterTimeValue);
+ CPPUNIT_TEST(testAutofilterOptimizations);
+ CPPUNIT_TEST(testTdf76836);
+ CPPUNIT_TEST(testTdf76441);
+ CPPUNIT_TEST(testTdf142186);
+ CPPUNIT_TEST(testTdf137063);
+ CPPUNIT_TEST(testTdf126342);
+ CPPUNIT_TEST(testAdvancedFilter);
+ CPPUNIT_TEST(testDateFilterContains);
+ CPPUNIT_TEST(testTdf98642);
+ CPPUNIT_TEST(testMergedCells);
+ CPPUNIT_TEST(testUpdateReference);
+ CPPUNIT_TEST(testSearchCells);
+ CPPUNIT_TEST(testFormulaPosition);
+ CPPUNIT_TEST(testFormulaWizardSubformula);
+ CPPUNIT_TEST(testDiagonalBorders);
+ CPPUNIT_TEST(testWholeDocBorders);
+ CPPUNIT_TEST(testJumpToPrecedentsDependents);
+ CPPUNIT_TEST(testSetBackgroundColor);
+ CPPUNIT_TEST(testRenameTable);
+ CPPUNIT_TEST(testTdf64001);
+ CPPUNIT_TEST(testAutoFill);
+ CPPUNIT_TEST(testAutoFillSimple);
+ CPPUNIT_TEST(testFindAreaPosVertical);
+ CPPUNIT_TEST(testFindAreaPosColRight);
+ CPPUNIT_TEST(testShiftCells);
+ CPPUNIT_TEST(testNoteBasic);
+ CPPUNIT_TEST(testNoteDeleteRow);
+ CPPUNIT_TEST(testNoteDeleteCol);
+ CPPUNIT_TEST(testNoteLifeCycle);
+ CPPUNIT_TEST(testNoteCopyPaste);
+ CPPUNIT_TEST(testNoteContainsNotesInRange);
+ CPPUNIT_TEST(testAreasWithNotes);
+ CPPUNIT_TEST(testAnchoredRotatedShape);
+ CPPUNIT_TEST(testCellTextWidth);
+ CPPUNIT_TEST(testEditTextIterator);
+ CPPUNIT_TEST(testImportStream);
+ CPPUNIT_TEST(testDeleteContents);
+ CPPUNIT_TEST(testTransliterateText);
+ CPPUNIT_TEST(testFormulaToValue);
+ CPPUNIT_TEST(testFormulaToValue2);
+ CPPUNIT_TEST(testColumnFindEditCells);
+ CPPUNIT_TEST(testSetStringAndNote);
+ CPPUNIT_TEST(testUndoDataAnchor);
+ CPPUNIT_TEST(testSetFormula);
+ CPPUNIT_TEST(testMultipleDataCellsInRange);
+ CPPUNIT_TEST(testEmptyCalcDocDefaults);
+ CPPUNIT_TEST(testPrecisionAsShown);
+ CPPUNIT_TEST(testProtectedSheetEditByRow);
+ CPPUNIT_TEST(testProtectedSheetEditByColumn);
+ CPPUNIT_TEST(testInsertColumnsWithFormulaCells);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ ScDocShellRef m_xDocShell;
+ ScDocument* m_pDoc;
+};
+
+Test::Test()
+{
+}
+
+void Test::getNewDocShell( ScDocShellRef& rDocShellRef )
+{
+ rDocShellRef = new ScDocShell(
+ SfxModelFlags::EMBEDDED_OBJECT |
+ SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+ SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
+
+ rDocShellRef->SetIsInUcalc();
+ rDocShellRef->DoInitUnitTest();
+}
+
+void Test::closeDocShell( ScDocShellRef& rDocShellRef )
+{
+ rDocShellRef->DoClose();
+ rDocShellRef.clear();
+}
+
+void Test::setUp()
+{
+ BootstrapFixture::setUp();
+
+ ScDLL::Init();
+
+ getNewDocShell(m_xDocShell);
+ m_pDoc = &m_xDocShell->GetDocument();
+}
+
+void Test::tearDown()
+{
+ closeDocShell(m_xDocShell);
+ BootstrapFixture::tearDown();
+}
+
+void Test::testCollator()
+{
+ sal_Int32 nRes = ScGlobal::GetCollator().compareString("A", "B");
+ CPPUNIT_ASSERT_MESSAGE("these strings are supposed to be different!", nRes != 0);
+}
+
+void Test::testSharedStringPool()
+{
+ m_pDoc->InsertTab(0, "foo");
+
+ 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), "Andy"); // A1
+ m_pDoc->SetString(ScAddress(0,1,0), "Andy"); // A2
+ m_pDoc->SetString(ScAddress(0,2,0), "Bruce"); // A3
+ m_pDoc->SetString(ScAddress(0,3,0), "andy"); // A4
+ m_pDoc->SetString(ScAddress(0,4,0), "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 and purge again.
+ clearRange(m_pDoc, ScAddress(0,0,0));
+ rPool.purge();
+ CPPUNIT_ASSERT_EQUAL(5+extraCount, rPool.getCount());
+ CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, rPool.getCountIgnoreCase());
+
+ // Clear A2 and purge again.
+ clearRange(m_pDoc, ScAddress(0,1,0));
+ rPool.purge();
+ CPPUNIT_ASSERT_EQUAL(4+extraCount, rPool.getCount());
+ CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, rPool.getCountIgnoreCase());
+
+ // Clear A3 and purge again.
+ clearRange(m_pDoc, ScAddress(0,2,0));
+ rPool.purge();
+ CPPUNIT_ASSERT_EQUAL(3+extraCount, rPool.getCount());
+ CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, rPool.getCountIgnoreCase());
+
+ // Clear A4 and purge again.
+ clearRange(m_pDoc, ScAddress(0,3,0));
+ rPool.purge();
+ CPPUNIT_ASSERT_EQUAL(1+extraCount, rPool.getCount());
+ CPPUNIT_ASSERT_EQUAL(1+extraCountIgnoreCase, rPool.getCountIgnoreCase());
+
+ // Clear A5 and the pool should be completely empty.
+ clearRange(m_pDoc, 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
+ ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
+ rEE.SetTextCurrentDefaults("Andy and Bruce");
+
+ ESelection aSel;
+ aSel.nStartPara = aSel.nEndPara = 0;
+
+ {
+ // Set 'Andy' bold.
+ SfxItemSet aItemSet = rEE.GetEmptyItemSet();
+ aSel.nStartPos = 0;
+ aSel.nEndPos = 4;
+ SvxWeightItem aWeight(WEIGHT_BOLD, EE_CHAR_WEIGHT);
+ aItemSet.Put(aWeight);
+ rEE.QuickSetAttribs(aItemSet, aSel);
+ }
+
+ {
+ // Set 'Bruce' italic.
+ SfxItemSet aItemSet = rEE.GetEmptyItemSet();
+ SvxPostureItem aItalic(ITALIC_NORMAL, EE_CHAR_ITALIC);
+ aItemSet.Put(aItalic);
+ aSel.nStartPos = 9;
+ aSel.nEndPos = 14;
+ rEE.QuickSetAttribs(aItemSet, aSel);
+ }
+
+ 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("ANDY and BRUCE");
+ 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);
+}
+
+void 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, "Test");
+
+ m_pDoc->SetString(ScAddress(0,0,0), "Header");
+ m_pDoc->SetString(ScAddress(0,1,0), "A1");
+ m_pDoc->SetString(ScAddress(0,2,0), "A2");
+ m_pDoc->SetString(ScAddress(0,3,0), "A3");
+
+ 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);
+}
+
+void Test::testRangeList()
+{
+ m_pDoc->InsertTab(0, "foo");
+
+ 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);
+}
+
+void 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);
+}
+
+void Test::testInput()
+{
+
+ CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
+ m_pDoc->InsertTab (0, "foo"));
+ OUString test;
+
+ m_pDoc->SetString(0, 0, 0, "'10.5");
+ 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, "'apple'");
+ test = m_pDoc->GetString(0, 0, 0);
+ bTest = test == "'apple'";
+ CPPUNIT_ASSERT_MESSAGE("Text content should have retained the first apostrophe.", bTest);
+
+ // Customized string handling policy.
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ m_pDoc->SetString(0, 0, 0, "000123", &aParam);
+ test = m_pDoc->GetString(0, 0, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Text content should have been treated as string, not number.", OUString("000123"), test);
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testColumnIterator() // tdf#118620
+{
+ CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
+ m_pDoc->InsertTab (0, "foo"));
+
+ m_pDoc->SetString(0, 0, 0, "'10.5");
+ m_pDoc->SetString(0, m_pDoc->MaxRow()-5, 0, "42.0");
+ 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);
+}
+
+void Test::testTdf90698()
+{
+ CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test"));
+ m_pDoc->SetString(ScAddress(0,0,0), "=(1;2)");
+
+ // 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(OUString("=(1;2)"), aFormula);
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf114406()
+{
+ CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test"));
+ m_pDoc->SetString(ScAddress(0,0,0), "5");
+ m_pDoc->SetString(ScAddress(1,0,0), "=A1/100%");
+
+ // 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(OUString("=A1/100%"), aFormula);
+
+ CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,0,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf93951()
+{
+ CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test"));
+ m_pDoc->SetString(ScAddress(0,0,0), "=2*" + OUStringChar(u'\x00A7') + "*2");
+
+ 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(OUString("=2*" + OUStringChar(u'\x00A7') + "*2"), aFormula);
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf134490()
+{
+ CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test"));
+
+ m_pDoc->SetString(ScAddress(0,0,0), "--1");
+ m_pDoc->SetString(ScAddress(0,1,0), "---1");
+ m_pDoc->SetString(ScAddress(0,2,0), "+-1");
+ m_pDoc->SetString(ScAddress(0,3,0), "+--1");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: --1
+ // - Actual : -1
+ CPPUNIT_ASSERT_EQUAL(OUString("--1"), m_pDoc->GetString(ScAddress(0,0,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("---1"), m_pDoc->GetString(ScAddress(0,1,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("+-1"), m_pDoc->GetString(ScAddress(0,2,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("+--1"), m_pDoc->GetString(ScAddress(0,3,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf135249()
+{
+ CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test"));
+
+ m_pDoc->SetString(ScAddress(0,0,0), "1:60");
+ m_pDoc->SetString(ScAddress(0,1,0), "1:123");
+ m_pDoc->SetString(ScAddress(0,2,0), "1:1:123");
+ m_pDoc->SetString(ScAddress(0,3,0), "0:123");
+ m_pDoc->SetString(ScAddress(0,4,0), "0:0:123");
+ m_pDoc->SetString(ScAddress(0,5,0), "0:123:59");
+
+ // These are not valid duration inputs
+ CPPUNIT_ASSERT_EQUAL(OUString("1:60"), m_pDoc->GetString(ScAddress(0,0,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("1:123"), m_pDoc->GetString(ScAddress(0,1,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("1:1:123"), 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(OUString("02:03:00 AM"), m_pDoc->GetString(ScAddress(0,3,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("12:02:03 AM"), m_pDoc->GetString(ScAddress(0,4,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("02:03:59 AM"), m_pDoc->GetString(ScAddress(0,5,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testDocStatistics()
+{
+ SCTAB nStartTabs = m_pDoc->GetTableCount();
+ m_pDoc->InsertTab(0, "Sheet1");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to increment sheet count.",
+ static_cast<SCTAB>(nStartTabs+1), m_pDoc->GetTableCount());
+ m_pDoc->InsertTab(1, "Sheet2");
+ 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), "Test");
+ 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), "=A1");
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetFormulaGroupCount());
+ m_pDoc->SetString(ScAddress(3,1,1), "=A2");
+ m_pDoc->SetString(ScAddress(3,2,1), "=A3");
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetFormulaGroupCount());
+ m_pDoc->SetString(ScAddress(3,3,1), "=A5");
+ m_pDoc->SetString(ScAddress(3,4,1), "=A6");
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(2), m_pDoc->GetFormulaGroupCount());
+ m_pDoc->SetString(ScAddress(3,1,1), "=A3");
+ 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.
+}
+
+void Test::testRowForHeight()
+{
+ m_pDoc->InsertTab(0, "Sheet1");
+ 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 = {
+ { 1, 1 },
+ { 99, 1 },
+ { 120, 2 },
+ { 330, 7 },
+ { 420, 13 },
+ { 780, 15 },
+ { 1860, 20 },
+ { 4020, 28 },
+ };
+
+ for (const Check& rCheck : aChecks)
+ {
+ SCROW nRow = m_pDoc->GetRowForHeight(0, rCheck.nHeight);
+ CPPUNIT_ASSERT_EQUAL(rCheck.nRow, nRow);
+ }
+}
+
+void Test::testDataEntries()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ m_pDoc->SetString(ScAddress(0,5,0), "Andy");
+ m_pDoc->SetString(ScAddress(0,6,0), "Bruce");
+ m_pDoc->SetString(ScAddress(0,7,0), "Charlie");
+ m_pDoc->SetString(ScAddress(0,10,0), "Andy");
+
+ 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(OUString("Andy"), it->GetString());
+ ++it;
+ CPPUNIT_ASSERT_EQUAL(OUString("Bruce"), it->GetString());
+ ++it;
+ CPPUNIT_ASSERT_EQUAL(OUString("Charlie"), 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(OUString("Andy"), it->GetString());
+ ++it;
+ CPPUNIT_ASSERT_EQUAL(OUString("Bruce"), it->GetString());
+ ++it;
+ CPPUNIT_ASSERT_EQUAL(OUString("Charlie"), it->GetString());
+ ++it;
+ CPPUNIT_ASSERT_MESSAGE("The entries should have ended here.", bool(it == aEntries.end()));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testSelectionFunction()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // Insert values into B2:B4.
+ m_pDoc->SetString(ScAddress(1,1,0), "=1"); // 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), "A");
+ m_pDoc->SetString(ScAddress(1,5,0), "B");
+ m_pDoc->SetString(ScAddress(1,6,0), "=\"C\""); // formula
+ m_pDoc->SetString(ScAddress(1,7,0), "D");
+
+ // 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("Rich Text");
+ m_pDoc->SetEditText(ScAddress(3,4,0), rEE.CreateTextObject());
+
+ // Insert Another string into D6.
+ m_pDoc->SetString(ScAddress(3,5,0), "E");
+
+ // 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 (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i)
+ {
+ double fRes = 0.0;
+ bool bRes = m_pDoc->GetSelectionFunction(aChecks[i].meFunc, ScAddress(), aMark, fRes);
+ CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
+ CPPUNIT_ASSERT_EQUAL(aChecks[i].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 (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i)
+ {
+ double fRes = 0.0;
+ bool bRes = m_pDoc->GetSelectionFunction(aChecks[i].meFunc, ScAddress(), aMark, fRes);
+ CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
+ CPPUNIT_ASSERT_EQUAL(aChecks[i].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 (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i)
+ {
+ double fRes = 0.0;
+ bool bRes = m_pDoc->GetSelectionFunction(aChecks[i].meFunc, aPos, aEmpty, fRes);
+ CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
+ CPPUNIT_ASSERT_EQUAL(aChecks[i].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 (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i)
+ {
+ double fRes = 0.0;
+ bool bRes = m_pDoc->GetSelectionFunction(aChecks[i].meFunc, aPos, aEmpty, fRes);
+ CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
+ CPPUNIT_ASSERT_EQUAL(aChecks[i].mfExpected, fRes);
+ }
+ }
+
+ // Calculate function across selected sheets.
+ clearSheet(m_pDoc, 0);
+ m_pDoc->InsertTab(1, "Test2");
+ m_pDoc->InsertTab(2, "Test3");
+
+ // 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);
+}
+
+void Test::testMarkedCellIteration()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // Insert cells to A1, A5, B2 and C3.
+ m_pDoc->SetString(ScAddress(0,0,0), "California");
+ m_pDoc->SetValue(ScAddress(0,4,0), 1.2);
+ m_pDoc->SetEditText(ScAddress(1,1,0), "Boston");
+ m_pDoc->SetFormula(ScAddress(2,2,0), "=SUM(1,2,3)", 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);
+}
+
+void Test::testCopyToDocument()
+{
+ CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, "src"));
+
+ // We need a drawing layer in order to create caption objects.
+ m_pDoc->InitDrawLayer(m_xDocShell.get());
+
+ m_pDoc->SetString(0, 0, 0, "Header");
+ m_pDoc->SetString(0, 1, 0, "1");
+ m_pDoc->SetString(0, 2, 0, "2");
+ m_pDoc->SetString(0, 3, 0, "3");
+ m_pDoc->SetString(0, 4, 0, "=4/2");
+ m_pDoc->CalcAll();
+
+ //note on A1
+ ScAddress aAdrA1 (0, 0, 0); // numerical cell content
+ ScPostIt* pNote = m_pDoc->GetOrCreateNote(aAdrA1);
+ pNote->SetText(aAdrA1, "Hello world in A1");
+
+ // Copy statically to another document.
+
+ ScDocShellRef xDocSh2;
+ getNewDocShell(xDocSh2);
+ ScDocument* pDestDoc = &xDocSh2->GetDocument();
+ pDestDoc->InsertTab(0, "src");
+ 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(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);
+ closeDocShell(xDocSh2);
+
+ m_pDoc->DeleteTab(0);
+}
+
+namespace {
+
+struct HoriIterCheck
+{
+ SCCOL nCol;
+ SCROW nRow;
+ const char* pVal;
+};
+
+bool 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;
+}
+
+}
+
+void Test::testHorizontalIterator()
+{
+ m_pDoc->InsertTab(0, "test");
+
+ {
+ // 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, SAL_N_ELEMENTS(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, SAL_N_ELEMENTS(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, SAL_N_ELEMENTS(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, SAL_N_ELEMENTS(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, SAL_N_ELEMENTS(aChecks));
+
+ if (!bRes)
+ CPPUNIT_FAIL("Failed on test in middle.");
+ }
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testValueIterator()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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 = SAL_N_ELEMENTS(aChecks);
+ ScValueIterator aIter(aContext, *m_pDoc, 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);
+}
+
+void Test::testHorizontalAttrIterator()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // Set the background color of B2:C3,D2,E3,C4:D4,B5:D5 to blue
+ ScPatternAttr aCellBackColor(m_pDoc->GetPool());
+ 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 = SAL_N_ELEMENTS(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 == m_pDoc->GetDefPattern())
+ 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);
+}
+
+void Test::testIteratorsUnallocatedColumnsAttributes()
+{
+ m_pDoc->InsertTab(0, "Tab1");
+
+ // 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->GetPool());
+ 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->GetFont(aFont, SC_AUTOCOL_RAW);
+ 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);
+}
+
+void Test::testIteratorsDefPattern()
+{
+ m_pDoc->InsertTab(0, "Tab1");
+
+ // 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->GetPool());
+ 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->GetDefPattern();
+ CPPUNIT_ASSERT(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);
+}
+
+void Test::testLastChangedColFlagsWidth()
+{
+ m_pDoc->InsertTab(0, "Tab1");
+
+ 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;
+}
+
+}
+
+void Test::testCellBroadcaster()
+{
+ CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, "foo"));
+
+ sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation.
+ m_pDoc->SetString(ScAddress(1,0,0), "=A1"); // 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, 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), "=A1"); // 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), "=A1");
+ m_pDoc->SetString(ScAddress(1,1,0), "=A2");
+ m_pDoc->SetString(ScAddress(1,2,0), "=A3");
+ 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), "=RC[-1]");
+ }
+ }
+
+ // 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), "=A1");
+ m_pDoc->SetString(ScAddress(2,0,0), "=B1");
+ 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);
+}
+
+void Test::testFuncParam()
+{
+
+ CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
+ m_pDoc->InsertTab (0, "foo"));
+
+ // First, the normal case, with no missing parameters.
+ m_pDoc->SetString(0, 0, 0, "=AVERAGE(1;2;3)");
+ 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, "=AVERAGE(1;;;)");
+ 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, "=\"\"+3"); // empty string
+ m_pDoc->SetString(0, 1, 0, "=\" \"+3"); // only blank
+ m_pDoc->SetString(0, 2, 0, "=\" 4 \"+3"); // number in blanks
+ m_pDoc->SetString(0, 3, 0, "=\" x \"+3"); // non-numeric
+ m_pDoc->SetString(0, 4, 0, "=\"4.4\"+3"); // 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", OUString("#VALUE!"), 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", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 1, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), 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", OUString("#VALUE!"), 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", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 4, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), 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", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 1, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), 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", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 4, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), 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", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 1, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 2, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 3, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), aVal);
+ aVal = m_pDoc->GetString( 0, 4, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("incorrect result", OUString("#VALUE!"), aVal);
+
+ m_pDoc->DeleteTab(0);
+}
+
+void 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, "Sheet1"));
+
+ m_pDoc->SetValue (0, 0, 0, 101);
+
+ std::unique_ptr<ScRangeName> pNames(new ScRangeName);
+ bool bSuccess = insertRangeNames(m_pDoc, pNames.get(), aNames, aNames + SAL_N_ELEMENTS(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 (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i)
+ {
+ const ScRangeData* p = pNewRanges->findByIndex(aNames[i].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(aNames[i].mpName));
+ }
+
+ // Test usage in formula expression.
+ m_pDoc->SetString (1, 0, 0, "=A1/Divisor");
+ 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 (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i)
+ {
+ const ScRangeData* p = m_pDoc->GetRangeName()->findByIndex(aNames[i].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(aNames[i].mpName));
+ }
+
+ // Test using another-sheet-local name, scope Sheet1.
+ ScRangeData* pLocal1 = new ScRangeData( *m_pDoc, "local1", ScAddress(0,0,0));
+ ScRangeData* pLocal2 = new ScRangeData( *m_pDoc, "local2", "$Sheet1.$A$1");
+ ScRangeData* pLocal3 = new ScRangeData( *m_pDoc, "local3", "Sheet1.$A$1");
+ ScRangeData* pLocal4 = new ScRangeData( *m_pDoc, "local4", "$A$1"); // 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, "Sheet2"));
+
+ // Use other-sheet-local name of Sheet1 on Sheet2.
+ ScAddress aPos(1,0,1);
+ OUString aFormula("=Sheet1.local1+Sheet1.local2+Sheet1.local3+Sheet1.local4");
+ 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);
+}
+
+void Test::testInsertNameList()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ 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 + SAL_N_ELEMENTS(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 (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i, aPos.IncRow())
+ {
+ OUString aName = m_pDoc->GetString(aPos);
+ CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(aNames[i].mpName), aName);
+ ScAddress aExprPos = aPos;
+ aExprPos.IncCol();
+ OUString aExpr = m_pDoc->GetString(aExprPos);
+ OUString aExpected = "=" + OUString::createFromAscii(aNames[i].mpExpr);
+ CPPUNIT_ASSERT_EQUAL(aExpected, aExpr);
+ }
+
+ m_pDoc->DeleteTab(0);
+}
+
+void 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 (size_t i = 0; i < SAL_N_ELEMENTS(aTests); i++) {
+ OUString aStr(aTests[i].pStr, strlen (aTests[i].pStr), RTL_TEXTENCODING_UTF8);
+ double nValue = 0.0;
+ bool bResult = ScStringUtil::parseSimpleNumber
+ (aStr, aTests[i].eSep == English ? '.' : ',',
+ aTests[i].eSep == English ? ',' : '.',
+ 0,
+ nValue);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("CSV numeric detection failure", aTests[i].bResult, bResult);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE ("CSV numeric value failure", aTests[i].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", OUString("Test"), 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);
+ }
+ }
+};
+
+}
+
+void 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("Test"), 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("Test"), 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("A"), 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("B"), 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]);
+ }
+ }
+}
+
+void Test::testMatrixComparisonWithErrors()
+{
+ m_pDoc->InsertTab(0, "foo");
+
+ // Insert the source values in A1:A2.
+ m_pDoc->SetString(0, 0, 0, "=1/0");
+ 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, "=A1:A2>0");
+
+ CPPUNIT_ASSERT_EQUAL(OUString("#DIV/0!"), m_pDoc->GetString(0,0,0));
+ CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue( 0,1,0));
+ CPPUNIT_ASSERT_EQUAL(OUString("#DIV/0!"), m_pDoc->GetString(1,2,0));
+ CPPUNIT_ASSERT_EQUAL(OUString("TRUE"), m_pDoc->GetString(1,3,0));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testMatrixConditionalBooleanResult()
+{
+ m_pDoc->InsertTab(0, "foo");
+
+ // 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, "=IF({1;0};TRUE();42)"); // {TRUE,42}
+ m_pDoc->InsertMatrixFormula( 0,1, 1,1, aMark, "=IF({0;1};TRUE();42)"); // {42,1} aim for {42,TRUE}
+ m_pDoc->InsertMatrixFormula( 0,2, 1,2, aMark, "=IF({1;0};42;FALSE())"); // {42,0} aim for {42,FALSE}
+ m_pDoc->InsertMatrixFormula( 0,3, 1,3, aMark, "=IF({0;1};42;FALSE())"); // {FALSE,42}
+
+ CPPUNIT_ASSERT_EQUAL( OUString("TRUE"), m_pDoc->GetString(0,0,0));
+ CPPUNIT_ASSERT_EQUAL( OUString("42"), m_pDoc->GetString(1,0,0));
+ CPPUNIT_ASSERT_EQUAL( OUString("42"), m_pDoc->GetString(0,1,0));
+ //CPPUNIT_ASSERT_EQUAL( OUString("TRUE"), m_pDoc->GetString(1,1,0)); // not yet
+ CPPUNIT_ASSERT_EQUAL( OUString("42"), m_pDoc->GetString(0,2,0));
+ //CPPUNIT_ASSERT_EQUAL( OUString("FALSE"), m_pDoc->GetString(1,2,0)); // not yet
+ CPPUNIT_ASSERT_EQUAL( OUString("FALSE"), m_pDoc->GetString(0,3,0));
+ CPPUNIT_ASSERT_EQUAL( OUString("42"), m_pDoc->GetString(1,3,0));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testEnterMixedMatrix()
+{
+ m_pDoc->InsertTab(0, "foo");
+
+ // Insert the source values in A1:B2.
+ m_pDoc->SetString(0, 0, 0, "A");
+ m_pDoc->SetString(1, 0, 0, "B");
+ 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, "=A1:B2");
+
+ 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);
+}
+
+void Test::testMatrixEditable()
+{
+ sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on.
+
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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), "=5");
+
+ // 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, "=TRANSPOSE(A1:B1)");
+
+ // 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);
+}
+
+void Test::testCellCopy()
+{
+ m_pDoc->InsertTab(0, "TestTab");
+ ScAddress aSrc(0,0,0);
+ ScAddress aDest(0,1,0);
+ OUString aStr("please copy me");
+ m_pDoc->SetString(aSrc, "please copy me");
+ 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));
+}
+
+void Test::testSheetCopy()
+{
+ m_pDoc->InsertTab(0, "TestTab");
+ 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), "copy me");
+
+ // Insert edit cells in B1:B3.
+ ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
+ rEE.SetTextCurrentDefaults("Edit 1");
+ m_pDoc->SetEditText(ScAddress(1,0,0), rEE.CreateTextObject());
+ rEE.SetTextCurrentDefaults("Edit 2");
+ m_pDoc->SetEditText(ScAddress(1,1,0), rEE.CreateTextObject());
+ rEE.SetTextCurrentDefaults("Edit 3");
+ 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, "Hello world in A3");
+
+ // 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(OUString("copy me"), 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(OUString("Edit 1"), 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(OUString("Edit 2"), 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(OUString("Edit 3"), 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);
+}
+
+void Test::testSheetMove()
+{
+ m_pDoc->InsertTab(0, "TestTab1");
+ 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, "TestTab2");
+ 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", OUString("TestTab1"), 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", OUString("TestTab2"), aName);
+ m_pDoc->DeleteTab(1);
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testDataArea()
+{
+ m_pDoc->InsertTab(0, "Data");
+
+ // 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, "Some text");
+ 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);
+}
+
+void Test::testStreamValid()
+{
+ m_pDoc->InsertTab(0, "Sheet1");
+ m_pDoc->InsertTab(1, "Sheet2");
+ m_pDoc->InsertTab(2, "Sheet3");
+ m_pDoc->InsertTab(3, "Sheet4");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("We should have 4 sheet instances.", static_cast<SCTAB>(4), m_pDoc->GetTableCount());
+
+ OUString a1("A1");
+ OUString a2("A2");
+ 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, "=Sheet1.A1");
+ m_pDoc->SetString(0, 1, 1, "=Sheet1.A2");
+ m_pDoc->SetString(0, 0, 2, "=Sheet1.A1");
+ m_pDoc->SetString(0, 0, 3, "=Sheet1.A2");
+
+ 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);
+}
+
+void Test::testFunctionLists()
+{
+ 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",
+ "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",
+ "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",
+ "GETPIVOTDATA",
+ "HLOOKUP",
+ "HYPERLINK",
+ "INDEX",
+ "INDIRECT",
+ "LOOKUP",
+ "MATCH",
+ "OFFSET",
+ "ROW",
+ "ROWS",
+ "SHEET",
+ "SHEETS",
+ "STYLE",
+ "VLOOKUP",
+ 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());
+ }
+ }
+}
+
+void Test::testGraphicsInGroup()
+{
+ m_pDoc->InsertTab(0, "TestTab");
+ 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);
+ SdrRectObj *pObj = new SdrRectObj(*pDrawLayer, aOrigRect);
+ pPage->InsertObject(pObj);
+ 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
+ SdrCircObj* pObj = new SdrCircObj(*pDrawLayer, SdrCircKind::Full, aOrigRect);
+ pPage->InsertObject(pObj);
+ 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()));
+ SdrPathObj* pObj = new SdrPathObj(*pDrawLayer, SdrObjKind::Line, basegfx::B2DPolyPolygon(aTempPoly));
+ pObj->NbcSetLogicRect(aOrigRect);
+ pPage->InsertObject(pObj);
+ 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);
+}
+
+void Test::testGraphicsOnSheetMove()
+{
+ m_pDoc->InsertTab(0, "Tab1");
+ m_pDoc->InsertTab(1, "Tab2");
+ 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);
+ SdrObject* pObj = new SdrRectObj(*pDrawLayer, aObjRect);
+ pPage->InsertObject(pObj);
+ 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);
+ 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, "NewTab");
+ 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);
+ 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);
+}
+
+void Test::testToggleRefFlag()
+{
+ // 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, "Test");
+
+ {
+ // Calc A1: basic 2D reference
+
+ OUString aFormula("=B100");
+ 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.", OUString("=$B$100"), aFormula );
+
+ // column absolute / row absolute -> column relative / row absolute
+ aFinder.ToggleRel(0, aFormula.getLength());
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", OUString("=B$100"), aFormula );
+
+ // column relative / row absolute -> column absolute / row relative
+ aFinder.ToggleRel(0, aFormula.getLength());
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", OUString("=$B100"), aFormula );
+
+ // column absolute / row relative -> column relative / row relative
+ aFinder.ToggleRel(0, aFormula.getLength());
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong conversion.", OUString("=B100"), aFormula );
+ }
+
+ {
+ // Excel R1C1: basic 2D reference
+
+ OUString aFormula("=R2C1");
+ 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(OUString("=R2C[-3]"), aFormula);
+
+ // column relative / row absolute - > column absolute / row relative
+ aFinder.ToggleRel(0, aFormula.getLength());
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=R[-4]C1"), aFormula);
+
+ // column absolute / row relative -> column relative / row relative
+ aFinder.ToggleRel(0, aFormula.getLength());
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=R[-4]C[-3]"), aFormula);
+
+ // column relative / row relative -> column absolute / row absolute
+ aFinder.ToggleRel(0, aFormula.getLength());
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=R2C1"), 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("=R1C1");
+ 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(OUString("=R1C[-1]"), aFormula);
+
+ // Make the row relative.
+ n = aFormula.getLength();
+ aFinder.ToggleRel(n, n);
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=R[-1]C1"), aFormula);
+
+ // Make both relative.
+ n = aFormula.getLength();
+ aFinder.ToggleRel(n, n);
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=R[-1]C[-1]"), aFormula);
+
+ // Back to the original.
+ n = aFormula.getLength();
+ aFinder.ToggleRel(n, n);
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=R1C1"), aFormula);
+ }
+
+ {
+ // Calc A1:
+ OUString aFormula("=A1+4");
+ 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(OUString("=$A$1+4"), aFormula);
+
+ aFinder.ToggleRel(2, 2);
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=A$1+4"), aFormula);
+
+ aFinder.ToggleRel(2, 2);
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=$A1+4"), aFormula);
+
+ aFinder.ToggleRel(2, 2);
+ aFormula = aFinder.GetText();
+ CPPUNIT_ASSERT_EQUAL(OUString("=A1+4"), aFormula);
+ }
+
+ // TODO: Add more test cases esp. for 3D references, Excel A1 syntax, and
+ // partial selection within formula string.
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testAutofilter()
+{
+ m_pDoc->InsertTab( 0, "Test" );
+
+ // 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 = SAL_N_ELEMENTS(aData[0]);
+ SCROW nRows = SAL_N_ELEMENTS(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("NONAME", 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);
+}
+
+void Test::testAutoFilterTimeValue()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ m_pDoc->SetString(ScAddress(0,0,0), "Hours");
+ 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->GetPool());
+ 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(OUString("1736:39:00"), m_pDoc->GetString(ScAddress(0,1,0))); // A2
+ CPPUNIT_ASSERT_EQUAL(OUString("6360:00:00"), 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("1736:39:00");
+ 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);
+}
+
+void Test::testAutofilterOptimizations()
+{
+ m_pDoc->InsertTab( 0, "Test" );
+
+ constexpr SCCOL nCols = 4;
+ constexpr SCROW nRows = 200;
+ m_pDoc->SetString(0, 0, 0, "Column1");
+ m_pDoc->SetString(1, 0, 0, "Column2");
+ m_pDoc->SetString(2, 0, 0, "Column3");
+ m_pDoc->SetString(3, 0, 0, "Column4");
+
+ // 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("NONAME", 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);
+}
+
+void Test::testTdf76441()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // The result will be different depending on whether the format is set before
+ // or after inserting the string
+
+ OUString aCode = "MM:SS";
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ sal_uInt32 nFormat;
+ SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
+ pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
+
+ ScPatternAttr aNewAttrs(m_pDoc->GetPool());
+ 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), "01:20");
+
+ m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("20:00"), 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), "01:20");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 01:20
+ // - Actual : 20:00
+ CPPUNIT_ASSERT_EQUAL(OUString("01:20"), m_pDoc->GetString(ScAddress(0,1,0)));
+ }
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf76836()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ OUString aCode = "\"192.168.0.\"@";
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ sal_uInt32 nFormat;
+ SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
+ pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
+
+ ScPatternAttr aNewAttrs(m_pDoc->GetPool());
+ 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(OUString("10"), m_pDoc->GetString(ScAddress(0,0,0)));
+
+ m_pDoc->ApplyPattern(0, 1, 0, aNewAttrs);
+ m_pDoc->SetString(ScAddress(0,1,0), "10");
+ CPPUNIT_ASSERT_EQUAL(OUString("192.168.0.10"), m_pDoc->GetString(ScAddress(0,1,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf142186()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // The result will be different depending on whether the format is set before
+ // or after inserting the string
+
+ OUString aCode = "0\".\"0";
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ sal_uInt32 nFormat;
+ SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
+ pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
+
+ ScPatternAttr aNewAttrs(m_pDoc->GetPool());
+ 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), "123.45");
+
+ m_pDoc->ApplyPattern(0, 0, 0, aNewAttrs);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("12.3"), 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), "123.45");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 12.3
+ // - Actual : 1234.5
+ CPPUNIT_ASSERT_EQUAL(OUString("12.3"), m_pDoc->GetString(ScAddress(0,1,0)));
+ }
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf137063()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ 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(OUString("0.000000006"), m_pDoc->GetString(ScAddress(0,0,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("6E-10"), m_pDoc->GetString(ScAddress(0,1,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testTdf126342()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ OUString aCode = "YYYY-MM-DD";
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ sal_uInt32 nFormat;
+ SvNumberFormatter* pFormatter = m_pDoc->GetFormatTable();
+ pFormatter->PutEntry( aCode, nCheckPos, nType, nFormat );
+
+ ScPatternAttr aNewAttrs(m_pDoc->GetPool());
+ 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), "11/7/19");
+
+ CPPUNIT_ASSERT_EQUAL(OUString("2019-11-07"), m_pDoc->GetString(ScAddress(0,0,0)));
+
+ // Overwrite the existing date with the exact same input
+ m_pDoc->SetString(ScAddress(0,0,0), "11/7/19");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 2019-11-07
+ // - Actual : 2011-07-19
+ CPPUNIT_ASSERT_EQUAL(OUString("2019-11-07"), m_pDoc->GetString(ScAddress(0,0,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testAdvancedFilter()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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(OUString("R"), 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);
+}
+
+void Test::testDateFilterContains()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ constexpr SCCOL nCols = 1;
+ constexpr SCROW nRows = 5;
+ m_pDoc->SetString(0, 0, 0, "Date");
+ m_pDoc->SetString(0, 1, 0, "1/2/2021");
+ m_pDoc->SetString(0, 2, 0, "2/1/1999");
+ m_pDoc->SetString(0, 3, 0, "2/1/1997");
+ m_pDoc->SetString(0, 4, 0, "3/3/2001");
+ m_pDoc->SetString(0, 5, 0, "3/3/1996");
+
+ // 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->GetPool());
+ 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("NONAME", 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("2");
+ 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);
+}
+
+void Test::testTdf98642()
+{
+ m_pDoc->InsertTab(0, "Sheet1");
+ m_pDoc->SetString(0, 0, 0, "test");
+
+ ScRangeData* pName1 = new ScRangeData( *m_pDoc, "name1", "$Sheet1.$A$1");
+ ScRangeData* pName2 = new ScRangeData( *m_pDoc, "name2", "$Sheet1.$A$1");
+
+ 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, "=name1");
+ m_pDoc->SetString(1, 1, 0, "=name2");
+
+ CPPUNIT_ASSERT_EQUAL(OUString("test"), m_pDoc->GetString(1, 0, 0));
+ CPPUNIT_ASSERT_EQUAL(OUString("test"), m_pDoc->GetString(1, 1, 0));
+
+ OUString aFormula = m_pDoc->GetFormula(1,0,0);
+ CPPUNIT_ASSERT_EQUAL(OUString("=name1"), 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(OUString("=name2"), aFormula);
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testMergedCells()
+{
+ //test merge and unmerge
+ //TODO: an undo/redo test for this would be a good idea
+ m_pDoc->InsertTab(0, "Sheet1");
+ 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);
+}
+
+void 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, "Sheet1");
+ m_pDoc->InsertTab(1, "Sheet2");
+
+ //test case 1 , rename table2 to sheet 1, it should return error
+ OUString nameToSet = "Sheet1";
+ 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);
+}
+
+void 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, "Sheet1");
+ 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);
+}
+
+void 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, "Sheet1");
+ m_pDoc->InsertTab(1, "Sheet2");
+ m_pDoc->InsertTab(2, "Sheet3");
+ m_pDoc->InsertTab(3, "Sheet4");
+
+ 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, "=A1+B1");
+ m_pDoc->SetString(2,1,2, "=Sheet4.B2+A1");
+
+ double aValue;
+ aValue = m_pDoc->GetValue(2,0,2);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("formula does not return correct result", aValue, 3);
+ aValue = m_pDoc->GetValue(2,1,2);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("formula does not return correct result", aValue, 5);
+
+ //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", aValue, 3);
+ aValue = m_pDoc->GetValue(2,1,1);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting first sheet formula does not return correct result", aValue, 5);
+
+ 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", aValue, 3);
+ aValue = m_pDoc->GetValue(2,1,0);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting second sheet formula does not return correct result", aValue, 5);
+
+ //test adding two sheets
+ m_pDoc->InsertTab(0, "Sheet2");
+ aValue = m_pDoc->GetValue(2,0,1);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting first sheet formula does not return correct result", aValue, 3);
+ aValue = m_pDoc->GetValue(2,1,1);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting first sheet formula does not return correct result", aValue, 5);
+
+ m_pDoc->InsertTab(0, "Sheet1");
+ aValue = m_pDoc->GetValue(2,0,2);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting second sheet formula does not return correct result", aValue, 3);
+ aValue = m_pDoc->GetValue(2,1,2);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting second sheet formula does not return correct result", aValue, 5);
+
+ //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", aValue, 3);
+ aValue = m_pDoc->GetValue(2, 1, 0);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after deleting sheets formula does not return correct result", aValue, 5);
+
+ 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", aValue, 3);
+ aValue = m_pDoc->GetValue(2, 1, 2);
+ ASSERT_DOUBLES_EQUAL_MESSAGE("after inserting sheets formula does not return correct result", aValue, 5);
+
+ 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, "Sheet1");
+ m_pDoc->InsertTab(1, "Sheet2");
+ m_pDoc->SetString(0,1,0, "s1");
+ m_pDoc->SetString(0,0,1, "=MATCH(\"s1\";Sheet1.A:A;0)");
+ 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);
+}
+
+void Test::testSearchCells()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ m_pDoc->SetString(ScAddress(0,0,0), "A");
+ m_pDoc->SetString(ScAddress(0,1,0), "B");
+ m_pDoc->SetString(ScAddress(0,2,0), "A");
+ // Leave A4 blank.
+ m_pDoc->SetString(ScAddress(0,4,0), "A");
+ m_pDoc->SetString(ScAddress(0,5,0), "B");
+ m_pDoc->SetString(ScAddress(0,6,0), "C");
+
+ SvxSearchItem aItem(SID_SEARCH_ITEM);
+ aItem.SetSearchString("A");
+ 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 bSuccess = m_pDoc->SearchAndReplace(aItem, nCol, nRow, nTab, aMarkData, aMatchedRanges, aUndoStr);
+
+ 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(aHit));
+ aHit.SetRow(2);
+ CPPUNIT_ASSERT_MESSAGE("A3 should be inside the matched range.", aMatchedRanges.Contains(aHit));
+ aHit.SetRow(4);
+ CPPUNIT_ASSERT_MESSAGE("A5 should be inside the matched range.", aMatchedRanges.Contains(aHit));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testFormulaPosition()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ ScAddress aPos(0,0,0); // A1
+ m_pDoc->SetString(aPos, "=ROW()");
+ aPos.IncRow(); // A2
+ m_pDoc->SetString(aPos, "=ROW()");
+ aPos.SetRow(3); // A4;
+ m_pDoc->SetString(aPos, "=ROW()");
+
+ {
+ SCROW aRows[] = { 0, 1, 3 };
+ bool bRes = checkFormulaPositions(*m_pDoc, aPos.Tab(), aPos.Col(), aRows, SAL_N_ELEMENTS(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, SAL_N_ELEMENTS(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;
+}
+
+}
+
+void Test::testJumpToPrecedentsDependents()
+{
+ // Precedent is another cell that the cell references, while dependent is
+ // another cell that references it.
+ m_pDoc->InsertTab(0, "Test");
+
+ m_pDoc->SetString(2, 0, 0, "=A1+A2+B3"); // C1
+ m_pDoc->SetString(2, 1, 0, "=A1"); // 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(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(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(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);
+}
+
+void Test::testTdf64001()
+{
+ m_pDoc->InsertTab(0, "test");
+
+ ScMarkData aMarkData(m_pDoc->GetSheetLimits());
+ aMarkData.SelectTable(0, true);
+
+ m_pDoc->SetString( 0, 0, 0, "TRUE" );
+ 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( OUString("TRUE"), m_pDoc->GetString( 0, i, 0 ) );
+ }
+
+ m_pDoc->SetString( 0, 10, 0, "FALSE" );
+
+ m_pDoc->SetString( 1, 0, 0, "=COUNTIF(A1:A11;TRUE)" );
+
+ // 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);
+}
+
+void Test::testAutoFill()
+{
+ m_pDoc->InsertTab(0, "test");
+
+ 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, "January" );
+ 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( OUString("February"), aTestValue );
+ aTestValue = m_pDoc->GetString( 0, 102, 0 );
+ CPPUNIT_ASSERT_EQUAL( OUString("March"), aTestValue );
+
+ // test that two same user data list entries will not result in incremental fill
+ m_pDoc->SetString( 0, 101, 0, "January" );
+ 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( OUString("January"), 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, "=A111");
+
+ 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, "2012-10-31" );
+ m_pDoc->SetString( 0, 101, 0, "2012-10-31" );
+ 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( OUString("2012-10-31"), m_pDoc->GetString( 0, 102, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("2012-10-31"), m_pDoc->GetString( 0, 103, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("2012-10-31"), 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, "2019-10-31");
+ m_pDoc->SetString(0, 101, 0, "2019-11-30");
+ m_pDoc->SetString(0, 102, 0, "2019-12-31");
+ 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(OUString("2020-01-31"), m_pDoc->GetString(0, 103, 0));
+ CPPUNIT_ASSERT_EQUAL(OUString("2020-02-29"), m_pDoc->GetString(0, 104, 0));
+ CPPUNIT_ASSERT_EQUAL(OUString("2020-03-31"), 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, "1.0" );
+ m_pDoc->SetString( 0, 51, 0, "1.1" );
+ m_pDoc->SetString( 0, 52, 0, "1.2" );
+ m_pDoc->SetString( 0, 53, 0, "1.3" );
+ m_pDoc->Fill( 0, 50, 0, 53, nullptr, aMarkData, 3, FILL_TO_BOTTOM, FILL_AUTO );
+
+ CPPUNIT_ASSERT_EQUAL( OUString("1.4"), m_pDoc->GetString( 0, 54, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("1.5"), m_pDoc->GetString( 0, 55, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("1.6"), m_pDoc->GetString( 0, 56, 0 ) );
+
+ m_pDoc->SetString( 0, 60, 0, "4.0" );
+ m_pDoc->SetString( 0, 61, 0, "4.1" );
+ m_pDoc->SetString( 0, 62, 0, "4.2" );
+ m_pDoc->SetString( 0, 63, 0, "4.3" );
+ 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( OUString("4.4"), m_pDoc->GetString( 0, 64, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("4.5"), m_pDoc->GetString( 0, 65, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("4.6"), 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, "001-001-001" );
+ 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( OUString("001-001-002"), m_pDoc->GetString( 0, 71, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("001-001-003"), m_pDoc->GetString( 0, 72, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("001-001-004"), 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, "1%" );
+ 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( OUString("2.00%"), m_pDoc->GetString( 0, 81, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("3.00%"), m_pDoc->GetString( 0, 82, 0 ) );
+ CPPUNIT_ASSERT_EQUAL( OUString("4.00%"), 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, "1" );
+ m_pDoc->SetString( 0, 1, 0, "1.1" );
+ 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( OUString("6"), m_pDoc->GetString( 0, 50, 0 ) );
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testAutoFillSimple()
+{
+ m_pDoc->InsertTab(0, "test");
+
+ m_pDoc->SetValue(0, 0, 0, 1);
+ m_pDoc->SetString(0, 1, 0, "=10");
+
+ 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);
+}
+
+void 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, "Test1");
+ 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);
+}
+
+void Test::testFindAreaPosColRight()
+{
+ std::vector<std::vector<const char*>> aData = {
+ { "", "1", "1", "", "1", "1", "1" },
+ { "", "", "1", "1", "1", "", "1" },
+ };
+
+ m_pDoc->InsertTab(0, "test1");
+ 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);
+}
+
+void Test::testShiftCells()
+{
+ m_pDoc->InsertTab(0, "foo");
+
+ // We need a drawing layer in order to create caption objects.
+ m_pDoc->InitDrawLayer(m_xDocShell.get());
+
+ OUString aTestVal("Some Text");
+
+ // 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, "Hello");
+
+ 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);
+}
+
+void Test::testNoteBasic()
+{
+ m_pDoc->InsertTab(0, "PostIts");
+
+ // 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, "Hello world");
+ pNote->SetAuthor("Jim Bob");
+
+ 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, "Table2");
+ 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, "Note is here.");
+
+ // 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);
+}
+
+void Test::testNoteDeleteRow()
+{
+ m_pDoc->InsertTab(0, "Sheet1");
+
+ // 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, "Hello");
+ pNote->SetAuthor("Jim Bob");
+
+ 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, "First");
+ ScNoteUtil::CreateNoteFromString(*m_pDoc, aPos, "First Note", false, false);
+
+ aPos = ScAddress(1,3,0); // B4
+ m_pDoc->SetString(aPos, "Second");
+ ScNoteUtil::CreateNoteFromString(*m_pDoc, aPos, "Second Note", 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(OUString("First Note"), pNote->GetText());
+ pNote = m_pDoc->GetNote(ScAddress(1,2,0));
+ CPPUNIT_ASSERT_MESSAGE("B3 should have a note.", pNote);
+ CPPUNIT_ASSERT_EQUAL(OUString("Second Note"), 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(OUString("First Note"), pNote->GetText());
+ pNote = m_pDoc->GetNote(ScAddress(1,3,0));
+ CPPUNIT_ASSERT_MESSAGE("B4 should have a note.", pNote);
+ CPPUNIT_ASSERT_EQUAL(OUString("Second Note"), 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(OUString("Second Note"), 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(OUString("First Note"), pNote->GetText());
+ pNote = m_pDoc->GetNote(ScAddress(1,3,0));
+ CPPUNIT_ASSERT_MESSAGE("B4 should have a note.", pNote);
+ CPPUNIT_ASSERT_EQUAL(OUString("Second Note"), pNote->GetText());
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testNoteDeleteCol()
+{
+ m_pDoc->InsertTab(0, "Sheet1");
+
+ // 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, "Hello");
+ pNote->SetAuthor("Jim Bob");
+
+ 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);
+}
+
+void Test::testNoteLifeCycle()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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, "New note");
+ 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, "New content");
+
+ // 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(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, "OtherSheet1");
+ 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, 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);
+ closeDocShell(xDocSh2);
+
+ pasteFromClip( m_pDoc, 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);
+}
+
+void Test::testNoteCopyPaste()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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, "Text");
+ ScPostIt* pNote = m_pDoc->GetOrCreateNote(aPos);
+ CPPUNIT_ASSERT(pNote);
+ pNote->SetText(aPos, "Note1");
+
+ // 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, "Note2");
+
+ // 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(OUString("Note1"), pNote->GetText());
+
+ pNote = aClipDoc.GetNote(ScAddress(1,3,0));
+ CPPUNIT_ASSERT(pNote);
+ CPPUNIT_ASSERT_EQUAL(OUString("Note2"), 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(OUString("Note1"), pNote->GetText());
+
+ pNote = m_pDoc->GetNote(ScAddress(1,7,0));
+ CPPUNIT_ASSERT(pNote);
+ CPPUNIT_ASSERT_EQUAL(OUString("Note2"), 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
+void Test::testNoteContainsNotesInRange() {
+ m_pDoc->InsertTab(0, "PostIts");
+
+ // 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)))));
+}
+
+void Test::testAreasWithNotes()
+{
+ m_pDoc->InsertTab(0, "Sheet1");
+
+ // 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, "Hello");
+ pNote->SetAuthor("Jim Bob");
+ ScAddress rAddrMin(2, 2, 0);
+ ScPostIt* pNoteMin = m_pDoc->GetOrCreateNote(rAddrMin);
+ pNoteMin->SetText(rAddrMin, "Hello");
+
+ 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);
+
+ 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, "Some Text");
+ m_pDoc->SetString(3, 3, 0, "Some Text");
+
+ 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);
+}
+
+void Test::testAnchoredRotatedShape()
+{
+ m_pDoc->InsertTab(0, "TestTab");
+ 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 );
+ SdrRectObj *pObj = new SdrRectObj(*pDrawLayer, aRect);
+ pPage->InsertObject(pObj);
+ 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_EQUAL( true, testEqualsWithTolerance( aRotRect.GetHeight(), aSnap.GetHeight(), TOLERANCE ) );
+ CPPUNIT_ASSERT_EQUAL( true, testEqualsWithTolerance( aRotRect.GetWidth(), aSnap.GetWidth(), TOLERANCE ) );
+ CPPUNIT_ASSERT_EQUAL( true, testEqualsWithTolerance( aRotRect.Left(), aSnap.Left(), TOLERANCE ) );
+ CPPUNIT_ASSERT_EQUAL( true, testEqualsWithTolerance( aRotRect.Top(), aSnap.Top(), TOLERANCE ) );
+
+ ScDrawObjData aAnchor;
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj );
+ 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_EQUAL( true, testEqualsWithTolerance( aRotRect.GetHeight(), aSnap.GetHeight(), TOLERANCE ) );
+ CPPUNIT_ASSERT_EQUAL( true, testEqualsWithTolerance( 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);
+}
+
+void Test::testCellTextWidth()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ 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, "Only one cell");
+ 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, "foo");
+
+ // Set values to row 10 through 18.
+ for (SCROW i = 10; i <= 18; ++i)
+ m_pDoc->SetString(0, i, 0, "foo");
+
+ {
+ // 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 (size_t i = 0; i < SAL_N_ELEMENTS(aRows); ++i, pIter->next())
+ {
+ CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
+ CPPUNIT_ASSERT_EQUAL(aRows[i], pIter->getPos());
+ }
+ 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 (size_t i = 0; i < SAL_N_ELEMENTS(aRows); ++i, pIter->next())
+ {
+ CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
+ CPPUNIT_ASSERT_EQUAL(aRows[i], pIter->getPos());
+ }
+ 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 (size_t i = 0; i < SAL_N_ELEMENTS(aRows); ++i, pIter->next())
+ {
+ CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
+ CPPUNIT_ASSERT_EQUAL(aRows[i], pIter->getPos());
+ }
+ 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 (size_t i = 0; i < SAL_N_ELEMENTS(aRows); ++i, pIter->next())
+ {
+ CPPUNIT_ASSERT_MESSAGE("Cell expected, but not there.", pIter->hasCell());
+ CPPUNIT_ASSERT_EQUAL(aRows[i], pIter->getPos());
+ }
+ 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;
+}
+
+void Test::testEditTextIterator()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ {
+ // 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("A2");
+ 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("A5");
+ m_pDoc->SetEditText(ScAddress(0,4,0), rEditEngine.CreateTextObject());
+ rEditEngine.SetTextCurrentDefaults("A6");
+ m_pDoc->SetEditText(ScAddress(0,5,0), rEditEngine.CreateTextObject());
+ rEditEngine.SetTextCurrentDefaults("A7");
+ 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("C1");
+ m_pDoc->SetEditText(ScAddress(2,0,0), rEditEngine.CreateTextObject());
+ rEditEngine.SetTextCurrentDefaults("C3");
+ m_pDoc->SetEditText(ScAddress(2,2,0), rEditEngine.CreateTextObject());
+ rEditEngine.SetTextCurrentDefaults("C4");
+ 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), "=ROW()");
+ m_pDoc->SetValue(ScAddress(1,3,0), 1.2);
+ m_pDoc->SetString(ScAddress(2,4,0), "Simple string");
+ 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);
+}
+
+void Test::testImportStream()
+{
+ sc::AutoCalcSwitch aAC(*m_pDoc, true); // turn on auto calc.
+ sc::UndoSwitch aUndo(*m_pDoc, true); // enable undo.
+
+ m_pDoc->InsertTab(0, "Test");
+
+ m_pDoc->SetString(ScAddress(0,1,0), "=SUM(A1:C1)"); // A2
+
+ CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0,1,0)));
+
+ // CSV import options.
+ ScAsciiOptions aOpt;
+ aOpt.SetFieldSeps(",");
+
+ // Import values to A1:C1.
+ ScImportExport aObj(*m_pDoc, ScAddress(0,0,0));
+ aObj.SetImportBroadcast(true);
+ aObj.SetExtOptions(aOpt);
+ aObj.ImportString("1,2,3", 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);
+}
+
+void Test::testDeleteContents()
+{
+ sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
+ sc::UndoSwitch aUndoSwitch(*m_pDoc, true); // enable undo.
+
+ m_pDoc->InsertTab(0, "Test");
+
+ 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), "=SUM(D2:D15)");
+
+ 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);
+}
+
+void Test::testTransliterateText()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // Set texts to A1:A3.
+ m_pDoc->SetString(ScAddress(0,0,0), "Mike");
+ m_pDoc->SetString(ScAddress(0,1,0), "Noah");
+ m_pDoc->SetString(ScAddress(0,2,0), "Oscar");
+
+ // 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(OUString("MIKE"), m_pDoc->GetString(ScAddress(0,0,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("NOAH"), m_pDoc->GetString(ScAddress(0,1,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("OSCAR"), 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(OUString("Mike"), m_pDoc->GetString(ScAddress(0,0,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("Noah"), m_pDoc->GetString(ScAddress(0,1,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("Oscar"), m_pDoc->GetString(ScAddress(0,2,0)));
+
+ pUndoMgr->Redo();
+ CPPUNIT_ASSERT_EQUAL(OUString("MIKE"), m_pDoc->GetString(ScAddress(0,0,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("NOAH"), m_pDoc->GetString(ScAddress(0,1,0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("OSCAR"), m_pDoc->GetString(ScAddress(0,2,0)));
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testFormulaToValue()
+{
+ sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
+ FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
+
+ m_pDoc->InsertTab(0, "Test");
+
+ 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);
+}
+
+void Test::testFormulaToValue2()
+{
+ sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
+ FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
+
+ m_pDoc->InsertTab(0, "Test");
+
+ 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);
+}
+
+void Test::testColumnFindEditCells()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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("Test");
+ 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), "Test");
+ 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.meType);
+ aCell.assign(*m_pDoc, ScAddress(0,1,0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be an empty cell.", CELLTYPE_NONE, aCell.meType);
+ aCell.assign(*m_pDoc, ScAddress(0,2,0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be a numeric cell.", CELLTYPE_VALUE, aCell.meType);
+ aCell.assign(*m_pDoc, ScAddress(0,3,0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be an empty cell.", CELLTYPE_NONE, aCell.meType);
+
+ 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), "Some text");
+ m_pDoc->SetString(ScAddress(1,12,0), "Some text");
+ m_pDoc->SetString(ScAddress(1,13,0), "Other text");
+
+ 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(ScAddress(1,11,0));
+ CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(11), nResRow);
+ nResRow = m_pDoc->GetFirstEditTextRow(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), "Text");
+
+ m_pDoc->SetScriptType(ScAddress(2,5,0), (SvtScriptType::LATIN | SvtScriptType::ASIAN));
+
+ nResRow = m_pDoc->GetFirstEditTextRow(ScAddress(2,1,0));
+ CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(-1), nResRow);
+
+ m_pDoc->DeleteTab(0);
+}
+
+
+void Test::testSetFormula()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ 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(size_t i = 0; i < SAL_N_ELEMENTS(aTest); ++i)
+ {
+ m_pDoc->SetFormula(ScAddress(aTest[i].nCol, aTest[i].nRow, 0), OUString::createFromAscii(aTest[i].aFormula1), aTest[i].eGram);
+ OUString aBuffer = m_pDoc->GetFormula(aTest[i].nCol, aTest[i].nRow, 0);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to set formula", OUString::createFromAscii(aTest[i].aFormula2), aBuffer);
+ }
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testMultipleDataCellsInRange()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ 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), "foo");
+ m_pDoc->SetString(ScAddress(5,4,0), "bar");
+ 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);
+}
+
+void Test::testFormulaWizardSubformula()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ m_pDoc->SetString(ScAddress(1,0,0), "=1"); // B1
+ m_pDoc->SetString(ScAddress(1,1,0), "=1/0"); // B2
+ m_pDoc->SetString(ScAddress(1,2,0), "=gibberish"); // B3
+
+ ScSimpleFormulaCalculator aFCell1( *m_pDoc, ScAddress(0,0,0), "=B1:B3", true );
+ FormulaError nErrCode = aFCell1.GetErrCode();
+ CPPUNIT_ASSERT( nErrCode == FormulaError::NONE || aFCell1.IsMatrix() );
+ CPPUNIT_ASSERT_EQUAL( OUString("{1|#DIV/0!|#NAME?}"), aFCell1.GetString().getString() );
+
+ m_pDoc->SetString(ScAddress(1,0,0), "=NA()"); // B1
+ m_pDoc->SetString(ScAddress(1,1,0), "2"); // B2
+ m_pDoc->SetString(ScAddress(1,2,0), "=1+2"); // B3
+ ScSimpleFormulaCalculator aFCell2( *m_pDoc, ScAddress(0,0,0), "=B1:B3", true );
+ nErrCode = aFCell2.GetErrCode();
+ CPPUNIT_ASSERT( nErrCode == FormulaError::NONE || aFCell2.IsMatrix() );
+ CPPUNIT_ASSERT_EQUAL( OUString("{#N/A|2|3}"), aFCell2.GetString().getString() );
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testDiagonalBorders()
+{
+ m_pDoc->InsertTab(0, "Diagonal");
+
+ 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);
+}
+
+void Test::testWholeDocBorders()
+{
+ m_pDoc->InsertTab(0, "Borders");
+
+ // 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);
+}
+
+void Test::testSetStringAndNote()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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, "Hello world in A1");
+
+ m_pDoc->SetString(0, 0, 0, "");
+
+ pNote = m_pDoc->GetNote(aAdrA1);
+ CPPUNIT_ASSERT(pNote);
+
+ m_pDoc->DeleteTab(0);
+}
+
+void Test::testUndoDataAnchor()
+{
+ m_pDoc->InsertTab(0, "Tab1");
+ 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);
+ SdrObject* pObj = new SdrRectObj(*pDrawLayer, aObjRect);
+ pPage->InsertObject(pObj);
+ ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *m_pDoc, 0, false);
+
+ // Get anchor data
+ ScDrawObjData* pData = ScDrawLayer::GetObjData(pObj);
+ 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 );
+ 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);
+ 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 );
+ 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);
+ CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData);
+
+ // Get non rotated anchor data
+ pNData = ScDrawLayer::GetNonRotatedObjData( pObj );
+ 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);
+ CPPUNIT_ASSERT_MESSAGE("Failed to retrieve user data for this object.", pData);
+
+ // Get non rotated anchor data
+ pNData = ScDrawLayer::GetNonRotatedObjData( pObj );
+ 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);
+}
+
+
+void 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 );
+}
+
+void Test::testPrecisionAsShown()
+{
+ m_pDoc->InsertTab(0, "Test");
+
+ // 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);
+}
+
+void Test::testProtectedSheetEditByRow()
+{
+ ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
+ m_pDoc->InsertTab(0, "Protected");
+
+ {
+ // Remove protected flags from rows 2-5.
+ ScPatternAttr aAttr(m_pDoc->GetPool());
+ 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, "Matrix"); // 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, "={1;2|3;4}");
+
+ 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);
+}
+
+void Test::testProtectedSheetEditByColumn()
+{
+ ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
+ m_pDoc->InsertTab(0, "Protected");
+
+ {
+ // Remove protected flags from columns B to E.
+ ScPatternAttr aAttr(m_pDoc->GetPool());
+ 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, "Matrix"); // 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, "={1;2|3;4}");
+
+ 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);
+}
+
+void Test::testInsertColumnsWithFormulaCells()
+{
+ m_pDoc->InsertTab(0, "Tab1");
+
+ 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), "=1", m_pDoc->GetGrammar());
+ m_pDoc->SetFormula(ScAddress(4, 2, 0), "=1", m_pDoc->GetGrammar());
+ m_pDoc->SetFormula(ScAddress(6, 2, 0), "=1", 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_SUITE_REGISTRATION(Test);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */