diff options
author | Xisco Fauli <xiscofauli@libreoffice.org> | 2023-03-14 17:15:09 +0100 |
---|---|---|
committer | Xisco Fauli <xiscofauli@libreoffice.org> | 2023-03-15 08:09:05 +0000 |
commit | b7be39c5f45f8268acd9e19c3abd731fee6dcca3 (patch) | |
tree | fbfa87c3b4d01923eaa4f1aa3b45d56d1ead99cc /sc | |
parent | 633b9aebf8ac50f5e46e70f9f371fe7b936e0e1d (diff) |
CppunitTest_sc_ucalc_formula: split in two
it already has 118 tests
Change-Id: I13d6fe65aca2fa01cf115c5873b9ca853cf5e77b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148891
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Diffstat (limited to 'sc')
-rw-r--r-- | sc/CppunitTest_sc_ucalc_formula2.mk | 14 | ||||
-rw-r--r-- | sc/Module_sc.mk | 1 | ||||
-rw-r--r-- | sc/qa/unit/ucalc_formula.cxx | 4493 | ||||
-rw-r--r-- | sc/qa/unit/ucalc_formula2.cxx | 4613 |
4 files changed, 4628 insertions, 4493 deletions
diff --git a/sc/CppunitTest_sc_ucalc_formula2.mk b/sc/CppunitTest_sc_ucalc_formula2.mk new file mode 100644 index 000000000000..dc02436aa939 --- /dev/null +++ b/sc/CppunitTest_sc_ucalc_formula2.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call sc_ucalc_test,_formula2)) + +# vim: set noet sw=4 ts=4: diff --git a/sc/Module_sc.mk b/sc/Module_sc.mk index ba5f0710060a..b8761a24e78a 100644 --- a/sc/Module_sc.mk +++ b/sc/Module_sc.mk @@ -47,6 +47,7 @@ $(eval $(call gb_Module_add_check_targets,sc,\ CppunitTest_sc_ucalc_copypaste \ CppunitTest_sc_ucalc_datatransformation \ CppunitTest_sc_ucalc_formula \ + CppunitTest_sc_ucalc_formula2 \ CppunitTest_sc_ucalc_parallelism \ CppunitTest_sc_ucalc_pivottable \ CppunitTest_sc_ucalc_rangelst \ diff --git a/sc/qa/unit/ucalc_formula.cxx b/sc/qa/unit/ucalc_formula.cxx index d6453cf0815d..0c0c5d4481ad 100644 --- a/sc/qa/unit/ucalc_formula.cxx +++ b/sc/qa/unit/ucalc_formula.cxx @@ -9,7 +9,6 @@ #include "helper/debughelper.hxx" #include "helper/qahelper.hxx" -#include <clipparam.hxx> #include <scopetools.hxx> #include <formulacell.hxx> #include <docfunc.hxx> @@ -17,67 +16,22 @@ #include <tokenstringcontext.hxx> #include <refupdatecontext.hxx> #include <dbdata.hxx> -#include <scmatrix.hxx> #include <validat.hxx> #include <scitems.hxx> #include <docpool.hxx> -#include <docoptio.hxx> -#include <externalrefmgr.hxx> #include <scmod.hxx> #include <undomanager.hxx> -#include <broadcast.hxx> #include <formula/vectortoken.hxx> -#include <svl/broadcast.hxx> #include <svl/intitem.hxx> -#include <sfx2/docfile.hxx> #include <memory> -#include <functional> -#include <set> #include <algorithm> #include <vector> using namespace formula; namespace { - -ScRange getCachedRange(const ScExternalRefCache::TableTypeRef& pCacheTab) -{ - ScRange aRange; - - vector<SCROW> aRows; - pCacheTab->getAllRows(aRows); - bool bFirst = true; - for (const SCROW nRow : aRows) - { - vector<SCCOL> aCols; - pCacheTab->getAllCols(nRow, aCols); - for (const SCCOL nCol : aCols) - { - if (bFirst) - { - aRange.aStart = ScAddress(nCol, nRow, 0); - aRange.aEnd = aRange.aStart; - bFirst = false; - } - else - { - if (nCol < aRange.aStart.Col()) - aRange.aStart.SetCol(nCol); - else if (aRange.aEnd.Col() < nCol) - aRange.aEnd.SetCol(nCol); - - if (nRow < aRange.aStart.Row()) - aRange.aStart.SetRow(nRow); - else if (aRange.aEnd.Row() < nRow) - aRange.aEnd.SetRow(nRow); - } - } - } - return aRange; -} - void setExpandRefs(bool bExpand) { ScModule* pMod = SC_MOD(); @@ -130,85 +84,11 @@ void testFormulaRefUpdateNameCopySheetCheckTab( const ScDocument* pDoc, SCTAB nT CPPUNIT_ASSERT_EQUAL( 1100000.0 * nSheet, pDoc->GetValue(aPos)); } -class ColumnTest -{ - ScDocument * m_pDoc; - - const SCROW m_nTotalRows; - const SCROW m_nStart1; - const SCROW m_nEnd1; - const SCROW m_nStart2; - const SCROW m_nEnd2; - -public: - ColumnTest( ScDocument * pDoc, SCROW nTotalRows, - SCROW nStart1, SCROW nEnd1, SCROW nStart2, SCROW nEnd2 ) - : m_pDoc(pDoc), m_nTotalRows(nTotalRows) - , m_nStart1(nStart1), m_nEnd1(nEnd1) - , m_nStart2(nStart2), m_nEnd2(nEnd2) - {} - - void operator() ( SCCOL nColumn, const OUString& rFormula, - std::function<double(SCROW )> const & lExpected ) const - { - ScDocument aClipDoc(SCDOCMODE_CLIP); - ScMarkData aMark(m_pDoc->GetSheetLimits()); - - ScAddress aPos(nColumn, m_nStart1, 0); - m_pDoc->SetString(aPos, rFormula); - ASSERT_DOUBLES_EQUAL( lExpected(m_nStart1), m_pDoc->GetValue(aPos) ); - - // Copy formula cell to clipboard. - ScClipParam aClipParam(aPos, false); - aMark.SetMarkArea(aPos); - m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMark, false, false); - - // Paste it to first range. - InsertDeleteFlags nFlags = InsertDeleteFlags::CONTENTS; - ScRange aDestRange(nColumn, m_nStart1, 0, nColumn, m_nEnd1, 0); - aMark.SetMarkArea(aDestRange); - m_pDoc->CopyFromClip(aDestRange, aMark, nFlags, nullptr, &aClipDoc); - - // Paste it second range. - aDestRange = ScRange(nColumn, m_nStart2, 0, nColumn, m_nEnd2, 0); - aMark.SetMarkArea(aDestRange); - m_pDoc->CopyFromClip(aDestRange, aMark, nFlags, nullptr, &aClipDoc); - - // Check the formula results for passed column. - for( SCROW i = 0; i < m_nTotalRows; ++i ) - { - if( !((m_nStart1 <= i && i <= m_nEnd1) || (m_nStart2 <= i && i <= m_nEnd2)) ) - continue; - double fExpected = lExpected(i); - ASSERT_DOUBLES_EQUAL(fExpected, m_pDoc->GetValue(ScAddress(nColumn,i,0))); - } - } -}; - -} - -namespace { - -struct StrStrCheck { - const char* pVal; - const char* pRes; -}; } class TestFormula : public ScUcalcTestBase { -protected: - template<size_t DataSize, size_t FormulaSize, int Type> - void runTestMATCH(ScDocument* pDoc, const char* aData[DataSize], const StrStrCheck aChecks[FormulaSize]); - - template<size_t DataSize, size_t FormulaSize, int Type> - void runTestHorizontalMATCH(ScDocument* pDoc, const char* aData[DataSize], const StrStrCheck aChecks[FormulaSize]); - - void testExtRefFuncT(ScDocument* pDoc, ScDocument& rExtDoc); - void testExtRefFuncOFFSET(ScDocument* pDoc, ScDocument& rExtDoc); - void testExtRefFuncVLOOKUP(ScDocument* pDoc, ScDocument& rExtDoc); - void testExtRefConcat(ScDocument* pDoc, ScDocument& rExtDoc); }; CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaCreateStringFromTokens) @@ -5156,4379 +5036,6 @@ CPPUNIT_TEST_FIXTURE(TestFormula, testFuncCOUNTIF) m_pDoc->DeleteTab(0); } -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncIF) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "Formula"); - - m_pDoc->SetString(ScAddress(0,0,0), "=IF(B1=2;\"two\";\"not two\")"); - CPPUNIT_ASSERT_EQUAL(OUString("not two"), m_pDoc->GetString(ScAddress(0,0,0))); - m_pDoc->SetValue(ScAddress(1,0,0), 2.0); - CPPUNIT_ASSERT_EQUAL(OUString("two"), m_pDoc->GetString(ScAddress(0,0,0))); - m_pDoc->SetValue(ScAddress(1,0,0), 3.0); - CPPUNIT_ASSERT_EQUAL(OUString("not two"), m_pDoc->GetString(ScAddress(0,0,0))); - - // Test nested IF in array/matrix if the nested IF condition is a scalar. - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(0,2, 1,2, aMark, "=IF({1;0};IF(1;23);42)"); - // Results must be 23 and 42. - CPPUNIT_ASSERT_EQUAL(23.0, m_pDoc->GetValue(ScAddress(0,2,0))); - CPPUNIT_ASSERT_EQUAL(42.0, m_pDoc->GetValue(ScAddress(1,2,0))); - - // Test nested IF in array/matrix if nested IF conditions are range - // references, data in A5:C8, matrix formula in D4 so there is no - // implicit intersection between formula and ranges. - { - std::vector<std::vector<const char*>> aData = { - { "1", "1", "16" }, - { "0", "1", "32" }, - { "1", "0", "64" }, - { "0", "0", "128" } - }; - ScAddress aPos(0,4,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - } - m_pDoc->InsertMatrixFormula(3,3, 3,3, aMark, "=SUM(IF(A5:A8;IF(B5:B8;C5:C8;0);0))"); - // Result must be 16, only the first row matches all criteria. - CPPUNIT_ASSERT_EQUAL(16.0, m_pDoc->GetValue(ScAddress(3,3,0))); - - // A11:B11 - // Test nested IF in array/matrix if the nested IF has no Else path. - m_pDoc->InsertMatrixFormula(0,10, 1,10, aMark, "=IF(IF({1;0};12);34;56)"); - // Results must be 34 and 56. - CPPUNIT_ASSERT_EQUAL(34.0, m_pDoc->GetValue(ScAddress(0,10,0))); - CPPUNIT_ASSERT_EQUAL(56.0, m_pDoc->GetValue(ScAddress(1,10,0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncCHOOSE) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "Formula"); - - m_pDoc->SetString(ScAddress(0,0,0), "=CHOOSE(B1;\"one\";\"two\";\"three\")"); - FormulaError nError = m_pDoc->GetErrCode(ScAddress(0,0,0)); - CPPUNIT_ASSERT_MESSAGE("Formula result should be an error since B1 is still empty.", nError != FormulaError::NONE); - m_pDoc->SetValue(ScAddress(1,0,0), 1.0); - CPPUNIT_ASSERT_EQUAL(OUString("one"), m_pDoc->GetString(ScAddress(0,0,0))); - m_pDoc->SetValue(ScAddress(1,0,0), 2.0); - CPPUNIT_ASSERT_EQUAL(OUString("two"), m_pDoc->GetString(ScAddress(0,0,0))); - m_pDoc->SetValue(ScAddress(1,0,0), 3.0); - CPPUNIT_ASSERT_EQUAL(OUString("three"), m_pDoc->GetString(ScAddress(0,0,0))); - m_pDoc->SetValue(ScAddress(1,0,0), 4.0); - nError = m_pDoc->GetErrCode(ScAddress(0,0,0)); - CPPUNIT_ASSERT_MESSAGE("Formula result should be an error due to out-of-bound input..", nError != FormulaError::NONE); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncIFERROR) -{ - // IFERROR/IFNA (fdo#56124) - - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - // Empty A1:A39 first. - clearRange(m_pDoc, ScRange(0, 0, 0, 0, 40, 0)); - - // Raw data (rows 1 through 12) - const char* aData[] = { - "1", - "e", - "=SQRT(4)", - "=SQRT(-2)", - "=A4", - "=1/0", - "=NA()", - "bar", - "4", - "gee", - "=1/0", - "23" - }; - - SCROW nRows = SAL_N_ELEMENTS(aData); - for (SCROW i = 0; i < nRows; ++i) - m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i])); - - printRange(m_pDoc, ScRange(0, 0, 0, 0, nRows-1, 0), "data range for IFERROR/IFNA"); - - // formulas and results - static const struct { - const char* pFormula; const char* pResult; - } aChecks[] = { - { "=IFERROR(A1;9)", "1" }, - { "=IFERROR(A2;9)", "e" }, - { "=IFERROR(A3;9)", "2" }, - { "=IFERROR(A4;-7)", "-7" }, - { "=IFERROR(A5;-7)", "-7" }, - { "=IFERROR(A6;-7)", "-7" }, - { "=IFERROR(A7;-7)", "-7" }, - { "=IFNA(A6;9)", "#DIV/0!" }, - { "=IFNA(A7;-7)", "-7" }, - { "=IFNA(VLOOKUP(\"4\";A8:A10;1;0);-2)", "4" }, - { "=IFNA(VLOOKUP(\"fop\";A8:A10;1;0);-2)", "-2" }, - { "{=IFERROR(3*A11:A12;1998)}[0]", "1998" }, // um... this is not the correct way to insert a - { "{=IFERROR(3*A11:A12;1998)}[1]", "69" } // matrix formula, just a place holder, see below - }; - - nRows = SAL_N_ELEMENTS(aChecks); - for (SCROW i = 0; i < nRows-2; ++i) - { - SCROW nRow = 20 + i; - m_pDoc->SetString(0, nRow, 0, OUString::createFromAscii(aChecks[i].pFormula)); - } - - // Create a matrix range in last two rows of the range above, actual data - // of the placeholders. - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(0, 20 + nRows-2, 0, 20 + nRows-1, aMark, "=IFERROR(3*A11:A12;1998)"); - - m_pDoc->CalcAll(); - - for (SCROW i = 0; i < nRows; ++i) - { - SCROW nRow = 20 + i; - OUString aResult = m_pDoc->GetString(0, nRow, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE( - aChecks[i].pFormula, OUString::createFromAscii( aChecks[i].pResult), aResult); - } - - const SCCOL nCols = 3; - std::vector<std::vector<const char*>> aData2 = { - { "1", "2", "3" }, - { "4", "=1/0", "6" }, - { "7", "8", "9" } - }; - const char* aCheck2[][nCols] = { - { "1", "2", "3" }, - { "4", "Error","6" }, - { "7", "8", "9" } - }; - - // Data in C1:E3 - ScAddress aPos(2,0,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData2); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - - // Array formula in F4:H6 - const SCROW nElems2 = SAL_N_ELEMENTS(aCheck2); - const SCCOL nStartCol = aPos.Col() + nCols; - const SCROW nStartRow = aPos.Row() + nElems2; - m_pDoc->InsertMatrixFormula( nStartCol, nStartRow, nStartCol+nCols-1, nStartRow+nElems2-1, aMark, - "=IFERROR(C1:E3;\"Error\")"); - - m_pDoc->CalcAll(); - - for (SCCOL nCol = nStartCol; nCol < nStartCol + nCols; ++nCol) - { - for (SCROW nRow = nStartRow; nRow < nStartRow + nElems2; ++nRow) - { - OUString aResult = m_pDoc->GetString( nCol, nRow, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE( "IFERROR array result", - OUString::createFromAscii( aCheck2[nRow-nStartRow][nCol-nStartCol]), aResult); - } - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSHEET) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (SC_TAB_APPEND, "test1")); - - m_pDoc->SetString(0, 0, 0, "=SHEETS()"); - m_pDoc->CalcFormulaTree(false, false); - double original = m_pDoc->GetValue(0, 0, 0); - - CPPUNIT_ASSERT_EQUAL_MESSAGE("result of SHEETS() should equal the number of sheets, but doesn't.", - static_cast<SCTAB>(original), m_pDoc->GetTableCount()); - - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (SC_TAB_APPEND, "test2")); - - double modified = m_pDoc->GetValue(0, 0, 0); - ASSERT_DOUBLES_EQUAL_MESSAGE("result of SHEETS() did not get updated after sheet insertion.", - 1.0, modified - original); - - SCTAB nTabCount = m_pDoc->GetTableCount(); - m_pDoc->DeleteTab(--nTabCount); - - modified = m_pDoc->GetValue(0, 0, 0); - ASSERT_DOUBLES_EQUAL_MESSAGE("result of SHEETS() did not get updated after sheet removal.", - 0.0, modified - original); - - m_pDoc->DeleteTab(--nTabCount); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncNOW) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - double val = 1; - m_pDoc->SetValue(0, 0, 0, val); - m_pDoc->SetString(0, 1, 0, "=IF(A1>0;NOW();0"); - double now1 = m_pDoc->GetValue(0, 1, 0); - CPPUNIT_ASSERT_MESSAGE("Value of NOW() should be positive.", now1 > 0.0); - - val = 0; - m_pDoc->SetValue(0, 0, 0, val); - m_pDoc->CalcFormulaTree(false, false); - double zero = m_pDoc->GetValue(0, 1, 0); - ASSERT_DOUBLES_EQUAL_MESSAGE("Result should equal the 3rd parameter of IF, which is zero.", 0.0, zero); - - val = 1; - m_pDoc->SetValue(0, 0, 0, val); - m_pDoc->CalcFormulaTree(false, false); - double now2 = m_pDoc->GetValue(0, 1, 0); - CPPUNIT_ASSERT_MESSAGE("Result should be the value of NOW() again.", (now2 - now1) >= 0.0); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncNUMBERVALUE) -{ - // NUMBERVALUE fdo#57180 - - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - // Empty A1:A39 first. - clearRange(m_pDoc, ScRange(0, 0, 0, 0, 40, 0)); - - // Raw data (rows 1 through 6) - const char* aData[] = { - "1ag9a9b9", - "1ag34 5g g6 78b9%%", - "1 234d56E-2", - "d4", - "54.4", - "1a2b3e1%" - }; - - SCROW nRows = SAL_N_ELEMENTS(aData); - for (SCROW i = 0; i < nRows; ++i) - m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i])); - - printRange(m_pDoc, ScRange(0, 0, 0, 0, nRows - 1, 0), "data range for NUMBERVALUE"); - - // formulas and results - static const struct { - const char* pFormula; const char* pResult; - } aChecks[] = { - { "=NUMBERVALUE(A1;\"b\";\"ag\")", "199.9" }, - { "=NUMBERVALUE(A2;\"b\";\"ag\")", "134.56789" }, - { "=NUMBERVALUE(A2;\"b\";\"g\")", "#VALUE!" }, - { "=NUMBERVALUE(A3;\"d\")", "12.3456" }, - { "=NUMBERVALUE(A4;\"d\";\"foo\")", "0.4" }, - { "=NUMBERVALUE(A4;)", "Err:502" }, - { "=NUMBERVALUE(A5;)", "Err:502" }, - { "=NUMBERVALUE(A6;\"b\";\"a\")", "1.23" } - }; - - nRows = SAL_N_ELEMENTS(aChecks); - for (SCROW i = 0; i < nRows; ++i) - { - SCROW nRow = 20 + i; - m_pDoc->SetString(0, nRow, 0, OUString::createFromAscii(aChecks[i].pFormula)); - } - m_pDoc->CalcAll(); - - for (SCROW i = 0; i < nRows; ++i) - { - SCROW nRow = 20 + i; - OUString aResult = m_pDoc->GetString(0, nRow, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE( - aChecks[i].pFormula, OUString::createFromAscii( aChecks[i].pResult), aResult); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncLEN) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "Formula"); - - // Leave A1:A3 empty, and insert an array of LEN in B1:B3 that references - // these empty cells. - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(1, 0, 1, 2, aMark, "=LEN(A1:A3)"); - - ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0)); - CPPUNIT_ASSERT(pFC); - CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should be a matrix origin.", - ScMatrixMode::Formula, pFC->GetMatrixFlag()); - - // This should be a 1x3 matrix. - SCCOL nCols = -1; - SCROW nRows = -1; - pFC->GetMatColsRows(nCols, nRows); - CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCols); - CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), nRows); - - // LEN value should be 0 for an empty cell. - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1,0,0))); - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1,1,0))); - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1,2,0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncLOOKUP) -{ - FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1); - - m_pDoc->InsertTab(0, "Test"); - - // Raw data - const char* aData[][2] = { - { "=CONCATENATE(\"A\")", "1" }, - { "=CONCATENATE(\"B\")", "2" }, - { "=CONCATENATE(\"C\")", "3" }, - { nullptr, nullptr } // terminator - }; - - // Insert raw data into A1:B3. - for (SCROW i = 0; aData[i][0]; ++i) - { - m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i][0])); - m_pDoc->SetString(1, i, 0, OUString::createFromAscii(aData[i][1])); - } - - const char* aData2[][2] = { - { "A", "=LOOKUP(RC[-1];R1C1:R3C1;R1C2:R3C2)" }, - { "B", "=LOOKUP(RC[-1];R1C1:R3C1;R1C2:R3C2)" }, - { "C", "=LOOKUP(RC[-1];R1C1:R3C1;R1C2:R3C2)" }, - { nullptr, nullptr } // terminator - }; - - // Insert check formulas into A5:B7. - for (SCROW i = 0; aData2[i][0]; ++i) - { - m_pDoc->SetString(0, i+4, 0, OUString::createFromAscii(aData2[i][0])); - m_pDoc->SetString(1, i+4, 0, OUString::createFromAscii(aData2[i][1])); - } - - printRange(m_pDoc, ScRange(0,4,0,1,6,0), "Data range for LOOKUP."); - - // Values for B5:B7 should be 1, 2, and 3. - CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should not have an error code.", 0, static_cast<int>(m_pDoc->GetErrCode(ScAddress(1,4,0)))); - CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should not have an error code.", 0, static_cast<int>(m_pDoc->GetErrCode(ScAddress(1,5,0)))); - CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should not have an error code.", 0, static_cast<int>(m_pDoc->GetErrCode(ScAddress(1,6,0)))); - - ASSERT_DOUBLES_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,4,0))); - ASSERT_DOUBLES_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,5,0))); - ASSERT_DOUBLES_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,6,0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncLOOKUParrayWithError) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); - m_pDoc->InsertTab(0, "Test"); - - std::vector<std::vector<const char*>> aData = { - { "x", "y", "z" }, - { "a", "b", "c" } - }; - insertRangeData(m_pDoc, ScAddress(2,1,0), aData); // C2:E3 - m_pDoc->SetString(0,0,0, "=LOOKUP(2;1/(C2:E2<>\"\");C3:E3)"); // A1 - - CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for last column.", OUString("c"), m_pDoc->GetString(0,0,0)); - m_pDoc->SetString(4,1,0, ""); // E2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for second last column.", OUString("b"), m_pDoc->GetString(0,0,0)); - - m_pDoc->SetString(6,1,0, "one"); // G2 - m_pDoc->SetString(6,5,0, "two"); // G6 - // Creates an interim array {1,#DIV/0!,#DIV/0!,#DIV/0!,1,#DIV/0!,#DIV/0!,#DIV/0!} - m_pDoc->SetString(7,8,0, "=LOOKUP(2;1/(NOT(ISBLANK(G2:G9)));G2:G9)"); // H9 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for last row.", OUString("two"), m_pDoc->GetString(7,8,0)); - - // Lookup on empty range. - m_pDoc->SetString(9,8,0, "=LOOKUP(2;1/(NOT(ISBLANK(I2:I9)));I2:I9)"); // J9 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find no match.", OUString("#N/A"), m_pDoc->GetString(9,8,0)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf141146) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); - m_pDoc->InsertTab(0, "Test1"); - m_pDoc->InsertTab(1, "Test2"); - - std::vector<std::vector<const char*>> aData = { - { "k1", "value1"}, - { "k2", "value2"}, - { "k3", "value3"} - }; - - insertRangeData(m_pDoc, ScAddress(0,1,1), aData); // A2:B4 - m_pDoc->SetString(4,0,1, "k2"); // E1 - - m_pDoc->SetString(4,1,1, "=LOOKUP(1;1/(A$2:A$4=E$1);1)"); - m_pDoc->SetString(4,2,1, "=LOOKUP(E1;A$2:A$4;B2:B4)"); - m_pDoc->SetString(4,3,1, "=LOOKUP(1;1/(A$2:A$4=E$1);B2:B4)"); - - // Without the fix in place, this test would have failed with - // - Expected: #N/A - // - Actual : - CPPUNIT_ASSERT_EQUAL(OUString("#N/A"), m_pDoc->GetString(4,1,1)); - CPPUNIT_ASSERT_EQUAL(OUString("value2"), m_pDoc->GetString(4,2,1)); - CPPUNIT_ASSERT_EQUAL(OUString("value2"), m_pDoc->GetString(4,3,1)); - - m_pDoc->DeleteTab(1); - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncVLOOKUP) -{ - // VLOOKUP - - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - // Clear A1:F40. - clearRange(m_pDoc, ScRange(0, 0, 0, 5, 39, 0)); - - // Raw data - const char* aData[][2] = { - { "Key", "Val" }, - { "10", "3" }, - { "20", "4" }, - { "30", "5" }, - { "40", "6" }, - { "50", "7" }, - { "60", "8" }, - { "70", "9" }, - { "B", "10" }, - { "B", "11" }, - { "C", "12" }, - { "D", "13" }, - { "E", "14" }, - { "F", "15" }, - { nullptr, nullptr } // terminator - }; - - // Insert raw data into A1:B14. - for (SCROW i = 0; aData[i][0]; ++i) - { - m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i][0])); - m_pDoc->SetString(1, i, 0, OUString::createFromAscii(aData[i][1])); - } - - printRange(m_pDoc, ScRange(0, 0, 0, 1, 13, 0), "raw data for VLOOKUP"); - - // Formula data - static const struct { - const char* pLookup; const char* pFormula; const char* pRes; - } aChecks[] = { - { "Lookup", "Formula", nullptr }, - { "12", "=VLOOKUP(D2;A2:B14;2;1)", "3" }, - { "29", "=VLOOKUP(D3;A2:B14;2;1)", "4" }, - { "31", "=VLOOKUP(D4;A2:B14;2;1)", "5" }, - { "45", "=VLOOKUP(D5;A2:B14;2;1)", "6" }, - { "56", "=VLOOKUP(D6;A2:B14;2;1)", "7" }, - { "65", "=VLOOKUP(D7;A2:B14;2;1)", "8" }, - { "78", "=VLOOKUP(D8;A2:B14;2;1)", "9" }, - { "Andy", "=VLOOKUP(D9;A2:B14;2;1)", "#N/A" }, - { "Bruce", "=VLOOKUP(D10;A2:B14;2;1)", "11" }, - { "Charlie", "=VLOOKUP(D11;A2:B14;2;1)", "12" }, - { "David", "=VLOOKUP(D12;A2:B14;2;1)", "13" }, - { "Edward", "=VLOOKUP(D13;A2:B14;2;1)", "14" }, - { "Frank", "=VLOOKUP(D14;A2:B14;2;1)", "15" }, - { "Henry", "=VLOOKUP(D15;A2:B14;2;1)", "15" }, - { "100", "=VLOOKUP(D16;A2:B14;2;1)", "9" }, - { "1000", "=VLOOKUP(D17;A2:B14;2;1)", "9" }, - { "Zena", "=VLOOKUP(D18;A2:B14;2;1)", "15" } - }; - - // Insert formula data into D1:E18. - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - m_pDoc->SetString(3, i, 0, OUString::createFromAscii(aChecks[i].pLookup)); - m_pDoc->SetString(4, i, 0, OUString::createFromAscii(aChecks[i].pFormula)); - } - m_pDoc->CalcAll(); - printRange(m_pDoc, ScRange(3, 0, 0, 4, 17, 0), "formula data for VLOOKUP"); - - // Verify results. - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - if (i == 0) - // Skip the header row. - continue; - - OUString aRes = m_pDoc->GetString(4, i, 0); - bool bGood = aRes.equalsAscii(aChecks[i].pRes); - if (!bGood) - { - cerr << "row " << (i+1) << ": lookup value='" << aChecks[i].pLookup - << "' expected='" << aChecks[i].pRes << "' actual='" << aRes << "'" << endl; - CPPUNIT_ASSERT_MESSAGE("Unexpected result for VLOOKUP", false); - } - } - - // Clear the sheet and start over. - clearSheet(m_pDoc, 0); - - // Lookup on sorted data interspersed with empty cells. - - // A1:B8 is the search range. - m_pDoc->SetValue(ScAddress(0,2,0), 1.0); - m_pDoc->SetValue(ScAddress(0,4,0), 2.0); - m_pDoc->SetValue(ScAddress(0,7,0), 4.0); - m_pDoc->SetString(ScAddress(1,2,0), "One"); - m_pDoc->SetString(ScAddress(1,4,0), "Two"); - m_pDoc->SetString(ScAddress(1,7,0), "Four"); - - // D1:D5 contain match values. - m_pDoc->SetValue(ScAddress(3,0,0), 1.0); - m_pDoc->SetValue(ScAddress(3,1,0), 2.0); - m_pDoc->SetValue(ScAddress(3,2,0), 3.0); - m_pDoc->SetValue(ScAddress(3,3,0), 4.0); - m_pDoc->SetValue(ScAddress(3,4,0), 5.0); - - // E1:E5 contain formulas. - m_pDoc->SetString(ScAddress(4,0,0), "=VLOOKUP(D1;$A$1:$B$8;2)"); - m_pDoc->SetString(ScAddress(4,1,0), "=VLOOKUP(D2;$A$1:$B$8;2)"); - m_pDoc->SetString(ScAddress(4,2,0), "=VLOOKUP(D3;$A$1:$B$8;2)"); - m_pDoc->SetString(ScAddress(4,3,0), "=VLOOKUP(D4;$A$1:$B$8;2)"); - m_pDoc->SetString(ScAddress(4,4,0), "=VLOOKUP(D5;$A$1:$B$8;2)"); - m_pDoc->CalcAll(); - - // Check the formula results in E1:E5. - CPPUNIT_ASSERT_EQUAL(OUString("One"), m_pDoc->GetString(ScAddress(4,0,0))); - CPPUNIT_ASSERT_EQUAL(OUString("Two"), m_pDoc->GetString(ScAddress(4,1,0))); - CPPUNIT_ASSERT_EQUAL(OUString("Two"), m_pDoc->GetString(ScAddress(4,2,0))); - CPPUNIT_ASSERT_EQUAL(OUString("Four"), m_pDoc->GetString(ScAddress(4,3,0))); - CPPUNIT_ASSERT_EQUAL(OUString("Four"), m_pDoc->GetString(ScAddress(4,4,0))); - - // Start over again. - clearSheet(m_pDoc, 0); - - // Set A,B,...,G to A1:A7. - m_pDoc->SetString(ScAddress(0,0,0), "A"); - m_pDoc->SetString(ScAddress(0,1,0), "B"); - m_pDoc->SetString(ScAddress(0,2,0), "C"); - m_pDoc->SetString(ScAddress(0,3,0), "D"); - m_pDoc->SetString(ScAddress(0,4,0), "E"); - m_pDoc->SetString(ScAddress(0,5,0), "F"); - m_pDoc->SetString(ScAddress(0,6,0), "G"); - - // Set the formula in C1. - m_pDoc->SetString(ScAddress(2,0,0), "=VLOOKUP(\"C\";A1:A16;1)"); - CPPUNIT_ASSERT_EQUAL(OUString("C"), m_pDoc->GetString(ScAddress(2,0,0))); - - - // A21:E24, test position dependent implicit intersection as argument to a - // scalar value parameter in a function that has a ReferenceOrForceArray - // type parameter somewhere else and formula is not in array mode, - // VLOOKUP(Value;ReferenceOrForceArray;...) - std::vector<std::vector<const char*>> aData2 = { - { "1", "one", "3", "=VLOOKUP(C21:C24;A21:B24;2;0)", "three" }, - { "2", "two", "1", "=VLOOKUP(C21:C24;A21:B24;2;0)", "one" }, - { "3", "three", "4", "=VLOOKUP(C21:C24;A21:B24;2;0)", "four" }, - { "4", "four", "2", "=VLOOKUP(C21:C24;A21:B24;2;0)", "two" } - }; - - ScAddress aPos2(0,20,0); - ScRange aRange2 = insertRangeData(m_pDoc, aPos2, aData2); - CPPUNIT_ASSERT_EQUAL(aPos2, aRange2.aStart); - - aPos2.SetCol(3); // column D formula results - for (size_t i=0; i < aData2.size(); ++i) - { - CPPUNIT_ASSERT_EQUAL( OUString::createFromAscii( aData2[i][4]), m_pDoc->GetString(aPos2)); - aPos2.IncRow(); - } - - m_pDoc->DeleteTab(0); -} - -template<size_t DataSize, size_t FormulaSize, int Type> -void TestFormula::runTestMATCH(ScDocument* pDoc, const char* aData[DataSize], const StrStrCheck aChecks[FormulaSize]) -{ - size_t nDataSize = DataSize; - for (size_t i = 0; i < nDataSize; ++i) - pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i])); - - for (size_t i = 0; i < FormulaSize; ++i) - { - pDoc->SetString(1, i, 0, OUString::createFromAscii(aChecks[i].pVal)); - - OUString aFormula = "=MATCH(B" + OUString::number(i+1) + ";A1:A" - + OUString::number(nDataSize) + ";" + OUString::number(Type) + ")"; - pDoc->SetString(2, i, 0, aFormula); - } - - pDoc->CalcAll(); - printRange(pDoc, ScRange(0, 0, 0, 2, FormulaSize-1, 0), "MATCH"); - - // verify the results. - for (size_t i = 0; i < FormulaSize; ++i) - { - OUString aStr = pDoc->GetString(2, i, 0); - if (!aStr.equalsAscii(aChecks[i].pRes)) - { - cerr << "row " << (i+1) << ": expected='" << aChecks[i].pRes << "' actual='" << aStr << "'" - " criterion='" << aChecks[i].pVal << "'" << endl; - CPPUNIT_ASSERT_MESSAGE("Unexpected result for MATCH", false); - } - } -} - -template<size_t DataSize, size_t FormulaSize, int Type> -void TestFormula::runTestHorizontalMATCH(ScDocument* pDoc, const char* aData[DataSize], const StrStrCheck aChecks[FormulaSize]) -{ - size_t nDataSize = DataSize; - for (size_t i = 0; i < nDataSize; ++i) - pDoc->SetString(i, 0, 0, OUString::createFromAscii(aData[i])); - - for (size_t i = 0; i < FormulaSize; ++i) - { - pDoc->SetString(i, 1, 0, OUString::createFromAscii(aChecks[i].pVal)); - - // Assume we don't have more than 26 data columns... - OUString aFormula = "=MATCH(" + OUStringChar(static_cast<sal_Unicode>('A'+i)) + "2;A1:" - + OUStringChar(static_cast<sal_Unicode>('A'+nDataSize)) + "1;" + OUString::number(Type) - + ")"; - pDoc->SetString(i, 2, 0, aFormula); - } - - pDoc->CalcAll(); - printRange(pDoc, ScRange(0, 0, 0, FormulaSize-1, 2, 0), "MATCH"); - - // verify the results. - for (size_t i = 0; i < FormulaSize; ++i) - { - OUString aStr = pDoc->GetString(i, 2, 0); - if (!aStr.equalsAscii(aChecks[i].pRes)) - { - cerr << "column " << char('A'+i) << ": expected='" << aChecks[i].pRes << "' actual='" << aStr << "'" - " criterion='" << aChecks[i].pVal << "'" << endl; - CPPUNIT_ASSERT_MESSAGE("Unexpected result for horizontal MATCH", false); - } - } -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncMATCH) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - clearRange(m_pDoc, ScRange(0, 0, 0, 40, 40, 0)); - { - // Ascending in-exact match - - // data range (A1:A9) - const char* aData[] = { - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "B", - "B", - "C", - }; - - // formula (B1:C12) - static const StrStrCheck aChecks[] = { - { "0.8", "#N/A" }, - { "1.2", "1" }, - { "2.3", "2" }, - { "3.9", "3" }, - { "4.1", "4" }, - { "5.99", "5" }, - { "6.1", "6" }, - { "7.2", "7" }, - { "8.569", "8" }, - { "9.59", "9" }, - { "10", "9" }, - { "100", "9" }, - { "Andy", "#N/A" }, - { "Bruce", "11" }, - { "Charlie", "12" } - }; - - runTestMATCH<SAL_N_ELEMENTS(aData),SAL_N_ELEMENTS(aChecks),1>(m_pDoc, aData, aChecks); - clearRange(m_pDoc, ScRange(0, 0, 0, 4, 40, 0)); - runTestHorizontalMATCH<SAL_N_ELEMENTS(aData),SAL_N_ELEMENTS(aChecks),1>(m_pDoc, aData, aChecks); - clearRange(m_pDoc, ScRange(0, 0, 0, 40, 4, 0)); - } - - { - // Descending in-exact match - - // data range (A1:A9) - const char* aData[] = { - "D", - "C", - "B", - "9", - "8", - "7", - "6", - "5", - "4", - "3", - "2", - "1" - }; - - // formula (B1:C12) - static const StrStrCheck aChecks[] = { - { "10", "#N/A" }, - { "8.9", "4" }, - { "7.8", "5" }, - { "6.7", "6" }, - { "5.5", "7" }, - { "4.6", "8" }, - { "3.3", "9" }, - { "2.2", "10" }, - { "1.1", "11" }, - { "0.8", "12" }, - { "0", "12" }, - { "-2", "12" }, - { "Andy", "3" }, - { "Bruce", "2" }, - { "Charlie", "1" }, - { "David", "#N/A" } - }; - - runTestMATCH<SAL_N_ELEMENTS(aData),SAL_N_ELEMENTS(aChecks),-1>(m_pDoc, aData, aChecks); - clearRange(m_pDoc, ScRange(0, 0, 0, 4, 40, 0)); - runTestHorizontalMATCH<SAL_N_ELEMENTS(aData),SAL_N_ELEMENTS(aChecks),-1>(m_pDoc, aData, aChecks); - clearRange(m_pDoc, ScRange(0, 0, 0, 40, 4, 0)); - } - - { - // search range contains leading and trailing empty cell ranges. - - clearRange(m_pDoc, ScRange(0,0,0,2,100,0)); - - // A5:A8 contains sorted values. - m_pDoc->SetValue(ScAddress(0,4,0), 1.0); - m_pDoc->SetValue(ScAddress(0,5,0), 2.0); - m_pDoc->SetValue(ScAddress(0,6,0), 3.0); - m_pDoc->SetValue(ScAddress(0,7,0), 4.0); - - // Find value 2 which is in A6. - m_pDoc->SetString(ScAddress(1,0,0), "=MATCH(2;A1:A20)"); - m_pDoc->CalcAll(); - - CPPUNIT_ASSERT_EQUAL(OUString("6"), m_pDoc->GetString(ScAddress(1,0,0))); - } - - { - // Test the ReferenceOrForceArray parameter. - - clearRange(m_pDoc, ScRange(0,0,0,1,7,0)); - - // B1:B5 contain numeric values. - m_pDoc->SetValue(ScAddress(1,0,0), 1.0); - m_pDoc->SetValue(ScAddress(1,1,0), 2.0); - m_pDoc->SetValue(ScAddress(1,2,0), 3.0); - m_pDoc->SetValue(ScAddress(1,3,0), 4.0); - m_pDoc->SetValue(ScAddress(1,4,0), 5.0); - - // Find string value "33" in concatenated array, no implicit - // intersection is involved, array is forced. - m_pDoc->SetString(ScAddress(0,5,0), "=MATCH(\"33\";B1:B5&B1:B5)"); - m_pDoc->CalcAll(); - CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0,5,0))); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncCELL) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - clearRange(m_pDoc, ScRange(0, 0, 0, 2, 20, 0)); // Clear A1:C21. - - { - const char* pContent = "Some random text"; - m_pDoc->SetString(2, 9, 0, OUString::createFromAscii(pContent)); // Set this value to C10. - m_pDoc->SetValue(2, 0, 0, 1.2); // Set numeric value to C1; - - // We don't test: FILENAME, FORMAT, WIDTH, PROTECT, PREFIX - StrStrCheck aChecks[] = { - { "=CELL(\"COL\";C10)", "3" }, - { "=CELL(\"COL\";C5:C10)", "3" }, - { "=CELL(\"ROW\";C10)", "10" }, - { "=CELL(\"ROW\";C10:E10)", "10" }, - { "=CELL(\"SHEET\";C10)", "1" }, - { "=CELL(\"ADDRESS\";C10)", "$C$10" }, - { "=CELL(\"CONTENTS\";C10)", pContent }, - { "=CELL(\"COLOR\";C10)", "0" }, - { "=CELL(\"TYPE\";C9)", "b" }, - { "=CELL(\"TYPE\";C10)", "l" }, - { "=CELL(\"TYPE\";C1)", "v" }, - { "=CELL(\"PARENTHESES\";C10)", "0" } - }; - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aChecks[i].pVal)); - m_pDoc->CalcAll(); - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - OUString aVal = m_pDoc->GetString(0, i, 0); - CPPUNIT_ASSERT_MESSAGE("Unexpected result for CELL", aVal.equalsAscii(aChecks[i].pRes)); - } - } - - m_pDoc->DeleteTab(0); -} - -/** See also test case document fdo#44456 sheet cpearson */ -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncDATEDIF) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - - std::vector<std::vector<const char*>> aData = { - { "2007-01-01", "2007-01-10", "d", "9", "=DATEDIF(A1;B1;C1)" } , - { "2007-01-01", "2007-01-31", "m", "0", "=DATEDIF(A2;B2;C2)" } , - { "2007-01-01", "2007-02-01", "m", "1", "=DATEDIF(A3;B3;C3)" } , - { "2007-01-01", "2007-02-28", "m", "1", "=DATEDIF(A4;B4;C4)" } , - { "2007-01-01", "2007-12-31", "d", "364", "=DATEDIF(A5;B5;C5)" } , - { "2007-01-01", "2007-01-31", "y", "0", "=DATEDIF(A6;B6;C6)" } , - { "2007-01-01", "2008-07-01", "d", "547", "=DATEDIF(A7;B7;C7)" } , - { "2007-01-01", "2008-07-01", "m", "18", "=DATEDIF(A8;B8;C8)" } , - { "2007-01-01", "2008-07-01", "ym", "6", "=DATEDIF(A9;B9;C9)" } , - { "2007-01-01", "2008-07-01", "yd", "182", "=DATEDIF(A10;B10;C10)" } , - { "2008-01-01", "2009-07-01", "yd", "181", "=DATEDIF(A11;B11;C11)" } , - { "2007-01-01", "2007-01-31", "md", "30", "=DATEDIF(A12;B12;C12)" } , - { "2007-02-01", "2009-03-01", "md", "0", "=DATEDIF(A13;B13;C13)" } , - { "2008-02-01", "2009-03-01", "md", "0", "=DATEDIF(A14;B14;C14)" } , - { "2007-01-02", "2007-01-01", "md", "Err:502", "=DATEDIF(A15;B15;C15)" } // fail date1 > date2 - }; - - clearRange( m_pDoc, ScRange(0, 0, 0, 4, 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->CalcAll(); - - for (size_t i = 0; i < aData.size(); ++i) - { - OUString aVal = m_pDoc->GetString( 4, i, 0); - //std::cout << "row "<< i << ": " << OUStringToOString( aVal, RTL_TEXTENCODING_UTF8).getStr() << ", expected " << aData[i][3] << std::endl; - CPPUNIT_ASSERT_MESSAGE("Unexpected result for DATEDIF", aVal.equalsAscii( aData[i][3])); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncINDIRECT) -{ - OUString aTabName("foo"); - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, aTabName)); - clearRange(m_pDoc, ScRange(0, 0, 0, 0, 10, 0)); // Clear A1:A11 - - bool bGood = m_pDoc->GetName(0, aTabName); - CPPUNIT_ASSERT_MESSAGE("failed to get sheet name.", bGood); - - OUString aTest = "Test", aRefErr = "#REF!"; - m_pDoc->SetString(0, 10, 0, aTest); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cell value.", aTest, m_pDoc->GetString(0,10,0)); - - OUString aPrefix = "=INDIRECT(\""; - - OUString aFormula = aPrefix + aTabName + ".A11\")"; // Calc A1 - m_pDoc->SetString(0, 0, 0, aFormula); - aFormula = aPrefix + aTabName + "!A11\")"; // Excel A1 - m_pDoc->SetString(0, 1, 0, aFormula); - aFormula = aPrefix + aTabName + "!R11C1\")"; // Excel R1C1 - m_pDoc->SetString(0, 2, 0, aFormula); - aFormula = aPrefix + aTabName + "!R11C1\";0)"; // Excel R1C1 (forced) - m_pDoc->SetString(0, 3, 0, aFormula); - - m_pDoc->CalcAll(); - { - // Default (for new documents) is to use current formula syntax - // which is Calc A1 - const OUString* aChecks[] = { - &aTest, &aRefErr, &aRefErr, &aTest - }; - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - OUString aVal = m_pDoc->GetString(0, i, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); - } - } - - ScCalcConfig aConfig; - aConfig.SetStringRefSyntax( formula::FormulaGrammar::CONV_OOO ); - m_pDoc->SetCalcConfig(aConfig); - m_pDoc->CalcAll(); - { - // Explicit Calc A1 syntax - const OUString* aChecks[] = { - &aTest, &aRefErr, &aRefErr, &aTest - }; - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - OUString aVal = m_pDoc->GetString(0, i, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); - } - } - - aConfig.SetStringRefSyntax( formula::FormulaGrammar::CONV_XL_A1 ); - m_pDoc->SetCalcConfig(aConfig); - m_pDoc->CalcAll(); - { - // Excel A1 syntax - const OUString* aChecks[] = { - &aRefErr, &aTest, &aRefErr, &aTest - }; - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - OUString aVal = m_pDoc->GetString(0, i, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); - } - } - - aConfig.SetStringRefSyntax( formula::FormulaGrammar::CONV_XL_R1C1 ); - m_pDoc->SetCalcConfig(aConfig); - m_pDoc->CalcAll(); - { - // Excel R1C1 syntax - const OUString* aChecks[] = { - &aRefErr, &aRefErr, &aTest, &aTest - }; - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - OUString aVal = m_pDoc->GetString(0, i, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); - } - } - - m_pDoc->DeleteTab(0); -} - -// Test case for tdf#83365 - Access across spreadsheet returns Err:504 -// -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncINDIRECT2) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "foo")); - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (1, "bar")); - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (2, "baz")); - - m_pDoc->SetValue(0,0,0, 10.0); - m_pDoc->SetValue(0,1,0, 10.0); - m_pDoc->SetValue(0,2,0, 10.0); - - // Fill range bar.$A1:bar.$A10 with 1s - for (SCROW i = 0; i < 10; ++i) - m_pDoc->SetValue(0,i,1, 1.0); - - // Test range triplet (absolute, relative, relative) : (absolute, relative, relative) - m_pDoc->SetString(0,0,2, "=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$1),1)"); - - // Test range triplet (absolute, relative, relative) : (absolute, absolute, relative) - m_pDoc->SetString(0,1,2, "=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$2),1)"); - - // Test range triplet (absolute, relative, relative) : (absolute, absolute, absolute) - m_pDoc->SetString(0,2,2, "=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$3),1)"); - - // Test range triplet (absolute, absolute, relative) : (absolute, relative, relative) - m_pDoc->SetString(0,3,2, "=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$1),1)"); - - // Test range triplet (absolute, absolute, relative) : (absolute, absolute, relative) - m_pDoc->SetString(0,4,2, "=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$2),1)"); - - // Test range triplet (absolute, absolute, relative) : (absolute, absolute, relative) - m_pDoc->SetString(0,5,2, "=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$3),1)"); - - // Test range triplet (absolute, absolute, absolute) : (absolute, relative, relative) - m_pDoc->SetString(0,6,2, "=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$1),1)"); - - // Test range triplet (absolute, absolute, absolute) : (absolute, absolute, relative) - m_pDoc->SetString(0,7,2, "=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$2),1)"); - - // Check indirect reference "bar.$A\"&foo.$A$1 - m_pDoc->SetString(0,8,2, "=COUNTIF(bar.$A$1:INDIRECT(\"bar.$A\"&foo.$A$1),1)"); - - // This case should return illegal argument error because - // they reference 2 different absolute sheets - // Test range triplet (absolute, absolute, absolute) : (absolute, absolute, absolute) - m_pDoc->SetString(0,9,2, "=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$3),1)"); - - m_pDoc->CalcAll(); - - // Loop all formulas and check result = 10.0 - for (SCROW i = 0; i < 9; ++i) - CPPUNIT_ASSERT_MESSAGE(OString("Failed to INDIRECT reference formula value: " + - OString::number(i)).getStr(), m_pDoc->GetValue(0,i,2) != 10.0); - - // Check formula cell error - ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0,9,2)); - CPPUNIT_ASSERT_MESSAGE("This should be a formula cell.", pFC); - CPPUNIT_ASSERT_MESSAGE("This formula cell should be an error.", pFC->GetErrCode() != FormulaError::NONE); - - m_pDoc->DeleteTab(2); - m_pDoc->DeleteTab(1); - m_pDoc->DeleteTab(0); -} - -// Test for tdf#107724 do not propagate an array context from MATCH to INDIRECT -// as INDIRECT returns ParamClass::Reference -CPPUNIT_TEST_FIXTURE(TestFormula, testFunc_MATCH_INDIRECT) -{ - CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab( 0, "foo")); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); - ScRangeData* pRangeData = new ScRangeData( *m_pDoc, "RoleAssignment", "$D$4:$D$13"); - pGlobalNames->insert(pRangeData); - - // D6: data to match, in 3rd row of named range. - m_pDoc->SetString( 3,5,0, "Test1"); - // F15: Formula generating indirect reference of corner addresses taking - // row+offset and column from named range, which are not in array context - // thus don't create arrays of offsets. - m_pDoc->SetString( 5,14,0, "=MATCH(\"Test1\";INDIRECT(ADDRESS(ROW(RoleAssignment)+1;COLUMN(RoleAssignment))&\":\"&ADDRESS(ROW(RoleAssignment)+ROWS(RoleAssignment)-1;COLUMN(RoleAssignment)));0)"); - - // Match in 2nd row of range offset by 1 expected. - ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to not propagate array context from MATCH to INDIRECT", - 2.0, m_pDoc->GetValue(5,14,0)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaDepTracking) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, "foo")); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - const ScAddress aA5(0, 4, 0); - const ScAddress aB2(1, 1, 0); - const ScAddress aB5(1, 4, 0); - const ScAddress aC5(2, 4, 0); - const ScAddress aD2(3, 1, 0); - const ScAddress aD5(3, 4, 0); - const ScAddress aD6(3, 5, 0); - const ScAddress aE2(4, 1, 0); - const ScAddress aE3(4, 2, 0); - const ScAddress aE6(4, 5, 0); - - // B2 listens on D2. - m_pDoc->SetString(aB2, "=D2"); - double val = m_pDoc->GetValue(aB2); - ASSERT_DOUBLES_EQUAL_MESSAGE("Referencing an empty cell should yield zero.", 0.0, val); - - { - // Check the internal broadcaster state. - auto aState = m_pDoc->GetBroadcasterState(); - aState.dump(std::cout, m_pDoc); - CPPUNIT_ASSERT(aState.hasFormulaCellListener(aD2, aB2)); - } - - // Changing the value of D2 should trigger recalculation of B2. - m_pDoc->SetValue(aD2, 1.1); - val = m_pDoc->GetValue(aB2); - ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on value change.", 1.1, val); - - // And again. - m_pDoc->SetValue(aD2, 2.2); - val = m_pDoc->GetValue(aB2); - ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on value change.", 2.2, val); - - clearRange(m_pDoc, ScRange(0, 0, 0, 10, 10, 0)); - - { - // Make sure nobody is listening on anything. - auto aState = m_pDoc->GetBroadcasterState(); - aState.dump(std::cout, m_pDoc); - CPPUNIT_ASSERT(aState.aCellListenerStore.empty()); - } - - // Now, let's test the range dependency tracking. - - // B2 listens on D2:E6. - m_pDoc->SetString(aB2, "=SUM(D2:E6)"); - val = m_pDoc->GetValue(aB2); - ASSERT_DOUBLES_EQUAL_MESSAGE("Summing an empty range should yield zero.", 0.0, val); - - { - // Check the internal state to make sure it matches. - auto aState = m_pDoc->GetBroadcasterState(); - aState.dump(std::cout, m_pDoc); - CPPUNIT_ASSERT(aState.hasFormulaCellListener({aD2, aE6}, aB2)); - } - - // Set value to E3. This should trigger recalc on B2. - m_pDoc->SetValue(aE3, 2.4); - val = m_pDoc->GetValue(aB2); - ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on single value change.", 2.4, val); - - // Set value to D5 to trigger recalc again. Note that this causes an - // addition of 1.2 + 2.4 which is subject to binary floating point - // rounding error. We need to use approxEqual to assess its value. - - m_pDoc->SetValue(aD5, 1.2); - val = m_pDoc->GetValue(aB2); - CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", rtl::math::approxEqual(val, 3.6)); - - // Change the value of D2 (boundary case). - m_pDoc->SetValue(aD2, 1.0); - val = m_pDoc->GetValue(aB2); - CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", rtl::math::approxEqual(val, 4.6)); - - // Change the value of E6 (another boundary case). - m_pDoc->SetValue(aE6, 2.0); - val = m_pDoc->GetValue(aB2); - CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", rtl::math::approxEqual(val, 6.6)); - - // Change the value of D6 (another boundary case). - m_pDoc->SetValue(aD6, 3.0); - val = m_pDoc->GetValue(aB2); - CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", rtl::math::approxEqual(val, 9.6)); - - // Change the value of E2 (another boundary case). - m_pDoc->SetValue(aE2, 0.4); - val = m_pDoc->GetValue(aB2); - CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", rtl::math::approxEqual(val, 10.0)); - - // Change the existing non-empty value cell (E2). - m_pDoc->SetValue(aE2, 2.4); - val = m_pDoc->GetValue(aB2); - CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", rtl::math::approxEqual(val, 12.0)); - - clearRange(m_pDoc, ScRange(0, 0, 0, 10, 10, 0)); - - // Now, column-based dependency tracking. We now switch to the R1C1 - // syntax which is easier to use for repeated relative references. - - FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1); - - val = 0.0; - for (SCROW nRow = 1; nRow <= 9; ++nRow) - { - // Static value in column 1. - m_pDoc->SetValue(0, nRow, 0, ++val); - - // Formula in column 2 that references cell to the left. - m_pDoc->SetString(1, nRow, 0, "=RC[-1]"); - - // Formula in column 3 that references cell to the left. - m_pDoc->SetString(2, nRow, 0, "=RC[-1]*2"); - } - - // Check formula values. - val = 0.0; - for (SCROW nRow = 1; nRow <= 9; ++nRow) - { - ++val; - ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", val, m_pDoc->GetValue(1, nRow, 0)); - ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", val*2.0, m_pDoc->GetValue(2, nRow, 0)); - } - - // Intentionally insert a formula in column 1. This will break column 1's - // uniformity of consisting only of static value cells. - m_pDoc->SetString(aA5, "=R2C3"); - ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, m_pDoc->GetValue(aA5)); - ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, m_pDoc->GetValue(aB5)); - ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 4.0, m_pDoc->GetValue(aC5)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaDepTracking2) -{ - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, "foo")); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - double val = 2.0; - m_pDoc->SetValue(0, 0, 0, val); - val = 4.0; - m_pDoc->SetValue(1, 0, 0, val); - val = 5.0; - m_pDoc->SetValue(0, 1, 0, val); - m_pDoc->SetString(2, 0, 0, "=A1/B1"); - m_pDoc->SetString(1, 1, 0, "=B1*C1"); - - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(1, 1, 0)); // B2 should equal 2. - - clearRange(m_pDoc, ScAddress(2, 0, 0)); // Delete C1. - - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(1, 1, 0)); // B2 should now equal 0. - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaDepTracking3) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - m_pDoc->InsertTab(0, "Formula"); - - std::vector<std::vector<const char*>> aData = { - { "1", "2", "=SUM(A1:B1)", "=SUM(C1:C3)" }, - { "3", "4", "=SUM(A2:B2)", nullptr }, - { "5", "6", "=SUM(A3:B3)", nullptr }, - }; - - insertRangeData(m_pDoc, ScAddress(0,0,0), aData); - - // Check the initial formula results. - CPPUNIT_ASSERT_EQUAL( 3.0, m_pDoc->GetValue(ScAddress(2,0,0))); - CPPUNIT_ASSERT_EQUAL( 7.0, m_pDoc->GetValue(ScAddress(2,1,0))); - CPPUNIT_ASSERT_EQUAL(11.0, m_pDoc->GetValue(ScAddress(2,2,0))); - CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(3,0,0))); - - // Change B3 and make sure the change gets propagated to D1. - ScDocFunc& rFunc = m_xDocShell->GetDocFunc(); - rFunc.SetValueCell(ScAddress(1,2,0), 60.0, false); - CPPUNIT_ASSERT_EQUAL(65.0, m_pDoc->GetValue(ScAddress(2,2,0))); - CPPUNIT_ASSERT_EQUAL(75.0, m_pDoc->GetValue(ScAddress(3,0,0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaDepTrackingDeleteRow) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - m_pDoc->InsertTab(0, "Test"); - - // Values in A1:A3. - m_pDoc->SetValue(ScAddress(0,0,0), 1.0); - m_pDoc->SetValue(ScAddress(0,1,0), 3.0); - m_pDoc->SetValue(ScAddress(0,2,0), 5.0); - - // SUM(A1:A3) in A5. - m_pDoc->SetString(ScAddress(0,4,0), "=SUM(A1:A3)"); - - // A6 to reference A5. - m_pDoc->SetString(ScAddress(0,5,0), "=A5*10"); - const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0,5,0)); - CPPUNIT_ASSERT(pFC); - - // A4 should have a broadcaster with A5 listening to it. - SvtBroadcaster* pBC = m_pDoc->GetBroadcaster(ScAddress(0,4,0)); - CPPUNIT_ASSERT(pBC); - SvtBroadcaster::ListenersType* pListeners = &pBC->GetAllListeners(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A5 should have one listener.", size_t(1), pListeners->size()); - const SvtListener* pListener = pListeners->at(0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A6 should be listening to A5.", static_cast<const ScFormulaCell*>(pListener), pFC); - - // Check initial values. - CPPUNIT_ASSERT_EQUAL(9.0, m_pDoc->GetValue(ScAddress(0,4,0))); - CPPUNIT_ASSERT_EQUAL(90.0, m_pDoc->GetValue(ScAddress(0,5,0))); - - // Delete row 2. - ScDocFunc& rFunc = m_xDocShell->GetDocFunc(); - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - rFunc.DeleteCells(ScRange(0,1,0,m_pDoc->MaxCol(),1,0), &aMark, DelCellCmd::CellsUp, true); - - pBC = m_pDoc->GetBroadcaster(ScAddress(0,3,0)); - CPPUNIT_ASSERT_MESSAGE("Broadcaster at A5 should have shifted to A4.", pBC); - pListeners = &pBC->GetAllListeners(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A3 should have one listener.", size_t(1), pListeners->size()); - pFC = m_pDoc->GetFormulaCell(ScAddress(0,4,0)); - CPPUNIT_ASSERT(pFC); - pListener = pListeners->at(0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A5 should be listening to A4.", static_cast<const ScFormulaCell*>(pListener), pFC); - - // Check values after row deletion. - CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(0,3,0))); - CPPUNIT_ASSERT_EQUAL(60.0, m_pDoc->GetValue(ScAddress(0,4,0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaDepTrackingDeleteCol) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - m_pDoc->InsertTab(0, "Formula"); - - std::vector<std::vector<const char*>> aData = { - { "2", "=A1", "=B1" }, // not grouped - { nullptr, nullptr, nullptr }, // empty row to separate the formula groups. - { "3", "=A3", "=B3" }, // grouped - { "4", "=A4", "=B4" }, // grouped - }; - - ScAddress aPos(0,0,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - - // Check the initial values. - for (SCCOL i = 0; i <= 2; ++i) - { - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(i,0,0))); - CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(i,2,0))); - CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(i,3,0))); - } - - // Make sure B3:B4 and C3:C4 are grouped. - const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,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,2,0)); - CPPUNIT_ASSERT(pFC); - CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedTopRow()); - CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength()); - - // Delete column A. A1, B1, A3:A4 and B3:B4 should all show #REF!. - ScDocFunc& rFunc = m_xDocShell->GetDocFunc(); - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - rFunc.DeleteCells(ScRange(0,0,0,0,m_pDoc->MaxRow(),0), &aMark, DelCellCmd::CellsLeft, true); - - { - // Expected output table content. 0 = empty cell - std::vector<std::vector<const char*>> aOutputCheck = { - { "#REF!", "#REF!" }, - { nullptr, nullptr }, - { "#REF!", "#REF!" }, - { "#REF!", "#REF!" }, - }; - - ScRange aCheckRange(0,0,0,1,3,0); - bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after deleting column A"); - CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); - } - - // Undo and check the result. - SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager(); - CPPUNIT_ASSERT(pUndoMgr); - pUndoMgr->Undo(); - - { - // Expected output table content. 0 = empty cell - std::vector<std::vector<const char*>> aOutputCheck = { - { "2", "2", "2" }, - { nullptr, nullptr, nullptr }, - { "3", "3", "3" }, - { "4", "4", "4" }, - }; - - ScRange aCheckRange(0,0,0,2,3,0); - bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after undo"); - CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); - } - - // Redo and check. - pUndoMgr->Redo(); - { - // Expected output table content. 0 = empty cell - std::vector<std::vector<const char*>> aOutputCheck = { - { "#REF!", "#REF!" }, - { nullptr, nullptr }, - { "#REF!", "#REF!" }, - { "#REF!", "#REF!" }, - }; - - ScRange aCheckRange(0,0,0,1,3,0); - bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after redo"); - CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); - } - - // Undo and change the values in column A. - pUndoMgr->Undo(); - m_pDoc->SetValue(ScAddress(0,0,0), 22.0); - m_pDoc->SetValue(ScAddress(0,2,0), 23.0); - m_pDoc->SetValue(ScAddress(0,3,0), 24.0); - - { - // Expected output table content. 0 = empty cell - std::vector<std::vector<const char*>> aOutputCheck = { - { "22", "22", "22" }, - { nullptr, nullptr, nullptr }, - { "23", "23", "23" }, - { "24", "24", "24" }, - }; - - ScRange aCheckRange(0,0,0,2,3,0); - bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after undo & value change in column A"); - CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaMatrixResultUpdate) -{ - m_pDoc->InsertTab(0, "Test"); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. - - // Set a numeric value to A1. - m_pDoc->SetValue(ScAddress(0,0,0), 11.0); - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(1, 0, 1, 0, aMark, "=A1"); - CPPUNIT_ASSERT_EQUAL(11.0, m_pDoc->GetValue(ScAddress(1,0,0))); - ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0)); - CPPUNIT_ASSERT_MESSAGE("Failed to get formula cell.", pFC); - pFC->SetChanged(false); // Clear this flag to simulate displaying of formula cell value on screen. - - m_pDoc->SetString(ScAddress(0,0,0), "ABC"); - CPPUNIT_ASSERT_EQUAL(OUString("ABC"), m_pDoc->GetString(ScAddress(1,0,0))); - pFC->SetChanged(false); - - // Put a new value into A1. The formula should update. - m_pDoc->SetValue(ScAddress(0,0,0), 13.0); - CPPUNIT_ASSERT_EQUAL(13.0, m_pDoc->GetValue(ScAddress(1,0,0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testExternalRef) -{ - ScDocShellRef xExtDocSh = new ScDocShell; - OUString aExtDocName("file:///extdata.fake"); - OUString aExtSh1Name("Data1"); - OUString aExtSh2Name("Data2"); - OUString aExtSh3Name("Data3"); - SfxMedium* pMed = new SfxMedium(aExtDocName, StreamMode::STD_READWRITE); - xExtDocSh->DoLoad(pMed); - CPPUNIT_ASSERT_MESSAGE("external document instance not loaded.", - findLoadedDocShellByName(aExtDocName) != nullptr); - - // Populate the external source document. - ScDocument& rExtDoc = xExtDocSh->GetDocument(); - rExtDoc.InsertTab(0, aExtSh1Name); - rExtDoc.InsertTab(1, aExtSh2Name); - rExtDoc.InsertTab(2, aExtSh3Name); - - OUString const name("Name"); - OUString const value("Value"); - - // Sheet 1 - rExtDoc.SetString(0, 0, 0, name); - rExtDoc.SetString(0, 1, 0, "Andy"); - rExtDoc.SetString(0, 2, 0, "Bruce"); - rExtDoc.SetString(0, 3, 0, "Charlie"); - rExtDoc.SetString(0, 4, 0, "David"); - rExtDoc.SetString(1, 0, 0, value); - double val = 10; - rExtDoc.SetValue(1, 1, 0, val); - val = 11; - rExtDoc.SetValue(1, 2, 0, val); - val = 12; - rExtDoc.SetValue(1, 3, 0, val); - val = 13; - rExtDoc.SetValue(1, 4, 0, val); - - // Sheet 2 remains empty. - - // Sheet 3 - rExtDoc.SetString(0, 0, 2, name); - rExtDoc.SetString(0, 1, 2, "Edward"); - rExtDoc.SetString(0, 2, 2, "Frank"); - rExtDoc.SetString(0, 3, 2, "George"); - rExtDoc.SetString(0, 4, 2, "Henry"); - rExtDoc.SetString(1, 0, 2, value); - val = 99; - rExtDoc.SetValue(1, 1, 2, val); - val = 98; - rExtDoc.SetValue(1, 2, 2, val); - val = 97; - rExtDoc.SetValue(1, 3, 2, val); - val = 96; - rExtDoc.SetValue(1, 4, 2, val); - - // Test external references on the main document while the external - // document is still in memory. - m_pDoc->InsertTab(0, "Test Sheet"); - m_pDoc->SetString(0, 0, 0, "='file:///extdata.fake'#Data1.A1"); - OUString test = m_pDoc->GetString(0, 0, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Value is different from the original", test, name); - - // After the initial access to the external document, the external ref - // manager should create sheet cache entries for *all* sheets from that - // document. Note that the doc may have more than 3 sheets but ensure - // that the first 3 are what we expect. - ScExternalRefManager* pRefMgr = m_pDoc->GetExternalRefManager(); - sal_uInt16 nFileId = pRefMgr->getExternalFileId(aExtDocName); - vector<OUString> aTabNames; - pRefMgr->getAllCachedTableNames(nFileId, aTabNames); - CPPUNIT_ASSERT_MESSAGE("There should be at least 3 sheets.", aTabNames.size() >= 3); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected sheet name.", aTabNames[0], aExtSh1Name); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected sheet name.", aTabNames[1], aExtSh2Name); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected sheet name.", aTabNames[2], aExtSh3Name); - - m_pDoc->SetString(1, 0, 0, "='file:///extdata.fake'#Data1.B1"); - test = m_pDoc->GetString(1, 0, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Value is different from the original", test, value); - - m_pDoc->SetString(0, 1, 0, "='file:///extdata.fake'#Data1.A2"); - m_pDoc->SetString(0, 2, 0, "='file:///extdata.fake'#Data1.A3"); - m_pDoc->SetString(0, 3, 0, "='file:///extdata.fake'#Data1.A4"); - m_pDoc->SetString(0, 4, 0, "='file:///extdata.fake'#Data1.A5"); - m_pDoc->SetString(0, 5, 0, "='file:///extdata.fake'#Data1.A6"); - - { - // Referencing an empty cell should display '0'. - const char* pChecks[] = { "Andy", "Bruce", "Charlie", "David", "0" }; - for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) - { - test = m_pDoc->GetString(0, static_cast<SCROW>(i+1), 0); - CPPUNIT_ASSERT_MESSAGE("Unexpected cell value.", test.equalsAscii(pChecks[i])); - } - } - m_pDoc->SetString(1, 1, 0, "='file:///extdata.fake'#Data1.B2"); - m_pDoc->SetString(1, 2, 0, "='file:///extdata.fake'#Data1.B3"); - m_pDoc->SetString(1, 3, 0, "='file:///extdata.fake'#Data1.B4"); - m_pDoc->SetString(1, 4, 0, "='file:///extdata.fake'#Data1.B5"); - m_pDoc->SetString(1, 5, 0, "='file:///extdata.fake'#Data1.B6"); - { - double pChecks[] = { 10, 11, 12, 13, 0 }; - for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) - { - val = m_pDoc->GetValue(1, static_cast<SCROW>(i+1), 0); - ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected cell value.", pChecks[i], val); - } - } - - m_pDoc->SetString(2, 0, 0, "='file:///extdata.fake'#Data3.A1"); - m_pDoc->SetString(2, 1, 0, "='file:///extdata.fake'#Data3.A2"); - m_pDoc->SetString(2, 2, 0, "='file:///extdata.fake'#Data3.A3"); - m_pDoc->SetString(2, 3, 0, "='file:///extdata.fake'#Data3.A4"); - { - const char* pChecks[] = { "Name", "Edward", "Frank", "George" }; - for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) - { - test = m_pDoc->GetString(2, static_cast<SCROW>(i), 0); - CPPUNIT_ASSERT_MESSAGE("Unexpected cell value.", test.equalsAscii(pChecks[i])); - } - } - - m_pDoc->SetString(3, 0, 0, "='file:///extdata.fake'#Data3.B1"); - m_pDoc->SetString(3, 1, 0, "='file:///extdata.fake'#Data3.B2"); - m_pDoc->SetString(3, 2, 0, "='file:///extdata.fake'#Data3.B3"); - m_pDoc->SetString(3, 3, 0, "='file:///extdata.fake'#Data3.B4"); - { - const char* pChecks[] = { "Value", "99", "98", "97" }; - for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) - { - test = m_pDoc->GetString(3, static_cast<SCROW>(i), 0); - CPPUNIT_ASSERT_MESSAGE("Unexpected cell value.", test.equalsAscii(pChecks[i])); - } - } - - // At this point, all accessed cell data from the external document should - // have been cached. - ScExternalRefCache::TableTypeRef pCacheTab = pRefMgr->getCacheTable( - nFileId, aExtSh1Name, false); - CPPUNIT_ASSERT_MESSAGE("Cache table for sheet 1 should exist.", pCacheTab); - ScRange aCachedRange = getCachedRange(pCacheTab); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCCOL(0), aCachedRange.aStart.Col()); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCCOL(1), aCachedRange.aEnd.Col()); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCROW(0), aCachedRange.aStart.Row()); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCROW(4), aCachedRange.aEnd.Row()); - - // Sheet2 is not referenced at all; the cache table shouldn't even exist. - pCacheTab = pRefMgr->getCacheTable(nFileId, aExtSh2Name, false); - CPPUNIT_ASSERT_MESSAGE("Cache table for sheet 2 should *not* exist.", !pCacheTab); - - // Sheet3's row 5 is not referenced; it should not be cached. - pCacheTab = pRefMgr->getCacheTable(nFileId, aExtSh3Name, false); - CPPUNIT_ASSERT_MESSAGE("Cache table for sheet 3 should exist.", pCacheTab); - aCachedRange = getCachedRange(pCacheTab); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCCOL(0), aCachedRange.aStart.Col()); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCCOL(1), aCachedRange.aEnd.Col()); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCROW(0), aCachedRange.aStart.Row()); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", - SCROW(3), aCachedRange.aEnd.Row()); - - // Unload the external document shell. - xExtDocSh->DoClose(); - CPPUNIT_ASSERT_MESSAGE("external document instance should have been unloaded.", - !findLoadedDocShellByName(aExtDocName)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testExternalRangeName) -{ - ScDocShellRef xExtDocSh = new ScDocShell; - OUString const aExtDocName("file:///extdata.fake"); - SfxMedium* pMed = new SfxMedium(aExtDocName, StreamMode::STD_READWRITE); - xExtDocSh->DoLoad(pMed); - CPPUNIT_ASSERT_MESSAGE("external document instance not loaded.", - findLoadedDocShellByName(aExtDocName) != nullptr); - - ScDocument& rExtDoc = xExtDocSh->GetDocument(); - rExtDoc.InsertTab(0, "Data1"); - rExtDoc.SetValue(0, 0, 0, 123.456); - - ScRangeName* pRangeName = rExtDoc.GetRangeName(); - ScRangeData* pRangeData = new ScRangeData(rExtDoc, "ExternalName", - "$Data1.$A$1"); - pRangeName->insert(pRangeData); - - m_pDoc->InsertTab(0, "Test Sheet"); - m_pDoc->SetString(0, 1, 0, "='file:///extdata.fake'#ExternalName"); - - double nVal = m_pDoc->GetValue(0, 1, 0); - ASSERT_DOUBLES_EQUAL(123.456, nVal); - - xExtDocSh->DoClose(); - CPPUNIT_ASSERT_MESSAGE("external document instance should have been unloaded.", - !findLoadedDocShellByName(aExtDocName)); - m_pDoc->DeleteTab(0); -} - -void TestFormula::testExtRefFuncT(ScDocument* pDoc, ScDocument& rExtDoc) -{ - clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); - clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); - - rExtDoc.SetString(0, 0, 0, "'1.2"); - rExtDoc.SetString(0, 1, 0, "Foo"); - rExtDoc.SetValue(0, 2, 0, 12.3); - pDoc->SetString(0, 0, 0, "=T('file:///extdata.fake'#Data.A1)"); - pDoc->SetString(0, 1, 0, "=T('file:///extdata.fake'#Data.A2)"); - pDoc->SetString(0, 2, 0, "=T('file:///extdata.fake'#Data.A3)"); - pDoc->CalcAll(); - - OUString aRes = pDoc->GetString(0, 0, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Unexpected result with T.", OUString("1.2"), aRes); - aRes = pDoc->GetString(0, 1, 0); - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Unexpected result with T.", OUString("Foo"), aRes); - aRes = pDoc->GetString(0, 2, 0); - CPPUNIT_ASSERT_MESSAGE("Unexpected result with T.", aRes.isEmpty()); -} - -void TestFormula::testExtRefFuncOFFSET(ScDocument* pDoc, ScDocument& rExtDoc) -{ - clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); - clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); - - sc::AutoCalcSwitch aACSwitch(*pDoc, true); - - // External document has sheet named 'Data', and the internal doc has sheet named 'Test'. - rExtDoc.SetValue(ScAddress(0,1,0), 1.2); // Set 1.2 to A2. - pDoc->SetString(ScAddress(0,0,0), "=OFFSET('file:///extdata.fake'#Data.$A$1;1;0;1;1)"); - CPPUNIT_ASSERT_EQUAL(1.2, pDoc->GetValue(ScAddress(0,0,0))); -} - -void TestFormula::testExtRefFuncVLOOKUP(ScDocument* pDoc, ScDocument& rExtDoc) -{ - clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); - clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); - - // Populate the external document. - rExtDoc.SetString(ScAddress(0,0,0), "A1"); - rExtDoc.SetString(ScAddress(0,1,0), "A2"); - rExtDoc.SetString(ScAddress(0,2,0), "A3"); - rExtDoc.SetString(ScAddress(0,3,0), "A4"); - rExtDoc.SetString(ScAddress(0,4,0), "A5"); - - rExtDoc.SetString(ScAddress(1,0,0), "B1"); - rExtDoc.SetString(ScAddress(1,1,0), "B2"); - rExtDoc.SetString(ScAddress(1,2,0), "B3"); - rExtDoc.SetString(ScAddress(1,3,0), "B4"); - rExtDoc.SetString(ScAddress(1,4,0), "B5"); - - // Put formula in the source document. - - pDoc->SetString(ScAddress(0,0,0), "A2"); - - // Sort order TRUE - pDoc->SetString(ScAddress(1,0,0), "=VLOOKUP(A1;'file:///extdata.fake'#Data.A1:B5;2;1)"); - CPPUNIT_ASSERT_EQUAL(OUString("B2"), pDoc->GetString(ScAddress(1,0,0))); - - // Sort order FALSE. It should return the same result. - pDoc->SetString(ScAddress(1,0,0), "=VLOOKUP(A1;'file:///extdata.fake'#Data.A1:B5;2;0)"); - CPPUNIT_ASSERT_EQUAL(OUString("B2"), pDoc->GetString(ScAddress(1,0,0))); -} - -void TestFormula::testExtRefConcat(ScDocument* pDoc, ScDocument& rExtDoc) -{ - clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); - clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); - - sc::AutoCalcSwitch aACSwitch(*pDoc, true); - - // String and number - rExtDoc.SetString(ScAddress(0,0,0), "Answer: "); - rExtDoc.SetValue(ScAddress(0,1,0), 42); - - // Concat operation should combine string and number converted to string - pDoc->SetString(ScAddress(0,0,0), "='file:///extdata.fake'#Data.A1 & 'file:///extdata.fake'#Data.A2"); - CPPUNIT_ASSERT_EQUAL(OUString("Answer: 42"), pDoc->GetString(ScAddress(0,0,0))); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testExternalRefFunctions) -{ - ScDocShellRef xExtDocSh = new ScDocShell; - OUString aExtDocName("file:///extdata.fake"); - SfxMedium* pMed = new SfxMedium(aExtDocName, StreamMode::STD_READWRITE); - xExtDocSh->DoLoad(pMed); - CPPUNIT_ASSERT_MESSAGE("external document instance not loaded.", - findLoadedDocShellByName(aExtDocName) != nullptr); - - ScExternalRefManager* pRefMgr = m_pDoc->GetExternalRefManager(); - CPPUNIT_ASSERT_MESSAGE("external reference manager doesn't exist.", pRefMgr); - sal_uInt16 nFileId = pRefMgr->getExternalFileId(aExtDocName); - const OUString* pFileName = pRefMgr->getExternalFileName(nFileId); - CPPUNIT_ASSERT_MESSAGE("file name registration has somehow failed.", - pFileName); - CPPUNIT_ASSERT_EQUAL_MESSAGE("file name registration has somehow failed.", - aExtDocName, *pFileName); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - - // Populate the external source document. - ScDocument& rExtDoc = xExtDocSh->GetDocument(); - rExtDoc.InsertTab(0, "Data"); - double val = 1; - rExtDoc.SetValue(0, 0, 0, val); - // leave cell B1 empty. - val = 2; - rExtDoc.SetValue(0, 1, 0, val); - rExtDoc.SetValue(1, 1, 0, val); - val = 3; - rExtDoc.SetValue(0, 2, 0, val); - rExtDoc.SetValue(1, 2, 0, val); - val = 4; - rExtDoc.SetValue(0, 3, 0, val); - rExtDoc.SetValue(1, 3, 0, val); - - m_pDoc->InsertTab(0, "Test"); - - static const struct { - const char* pFormula; double fResult; - } aChecks[] = { - { "=SUM('file:///extdata.fake'#Data.A1:A4)", 10 }, - { "=SUM('file:///extdata.fake'#Data.B1:B4)", 9 }, - { "=AVERAGE('file:///extdata.fake'#Data.A1:A4)", 2.5 }, - { "=AVERAGE('file:///extdata.fake'#Data.B1:B4)", 3 }, - { "=COUNT('file:///extdata.fake'#Data.A1:A4)", 4 }, - { "=COUNT('file:///extdata.fake'#Data.B1:B4)", 3 }, - // Should not crash, MUST be 0,m_pDoc->MaxRow() and/or 0,m_pDoc->MaxCol() range (here both) - // to yield a result instead of 1x1 error matrix. - { "=SUM('file:///extdata.fake'#Data.1:1048576)", 19 } - }; - - for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - m_pDoc->SetString(0, 0, 0, OUString::createFromAscii(aChecks[i].pFormula)); - val = m_pDoc->GetValue(0, 0, 0); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("unexpected result involving external ranges.", aChecks[i].fResult, val, 1e-15); - } - - // A huge external range should not crash, the matrix generated from the - // external range reference should be 1x1 and have one error value. - // XXX NOTE: in case we supported sparse matrix that can hold this large - // areas these tests may be adapted. - m_pDoc->SetString(0, 0, 0, "=SUM('file:///extdata.fake'#Data.B1:AMJ1048575)"); - ScFormulaCell* pFC = m_pDoc->GetFormulaCell( ScAddress(0,0,0)); - FormulaError nErr = pFC->GetErrCode(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("huge external range reference expected to yield FormulaError::MatrixSize", int(FormulaError::MatrixSize), static_cast<int>(nErr)); - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(0,0,0,0, aMark, "'file:///extdata.fake'#Data.B1:AMJ1048575"); - pFC = m_pDoc->GetFormulaCell( ScAddress(0,0,0)); - nErr = pFC->GetErrCode(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("huge external range reference expected to yield FormulaError::MatrixSize", int(FormulaError::MatrixSize), static_cast<int>(nErr)); - SCSIZE nMatCols, nMatRows; - const ScMatrix* pMat = pFC->GetMatrix(); - CPPUNIT_ASSERT_MESSAGE("matrix expected", pMat != nullptr); - pMat->GetDimensions( nMatCols, nMatRows); - CPPUNIT_ASSERT_EQUAL_MESSAGE("1x1 matrix expected", SCSIZE(1), nMatCols); - CPPUNIT_ASSERT_EQUAL_MESSAGE("1x1 matrix expected", SCSIZE(1), nMatRows); - - pRefMgr->clearCache(nFileId); - testExtRefFuncT(m_pDoc, rExtDoc); - testExtRefFuncOFFSET(m_pDoc, rExtDoc); - testExtRefFuncVLOOKUP(m_pDoc, rExtDoc); - testExtRefConcat(m_pDoc, rExtDoc); - - // Unload the external document shell. - xExtDocSh->DoClose(); - CPPUNIT_ASSERT_MESSAGE("external document instance should have been unloaded.", - !findLoadedDocShellByName(aExtDocName)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testExternalRefUnresolved) -{ -#if !defined(_WIN32) //FIXME - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - m_pDoc->InsertTab(0, "Test"); - - // Test error propagation of unresolved (not existing document) external - // references. Well, let's hope no build machine has such file with sheet... - - std::vector<std::vector<const char*>> aData = { - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1+23" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1&\"W\"" }, - { "=ISREF('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, - { "=ISERROR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, - { "=ISERR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, - { "=ISBLANK('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, - { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, - { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, - { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1+23)" }, - { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1&\"W\")" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1=0" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1=\"\"" }, - { "=INDIRECT(\"'file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1\")" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2+23" }, - { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2&\"W\"" }, - { "=ISREF('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, - { "=ISERROR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, - { "=ISERR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, - { "=ISBLANK('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, - { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, - { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, - { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2+23)" }, - { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2&\"W\")" }, - // TODO: gives Err:504 FIXME { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2=0" }, - // TODO: gives Err:504 FIXME { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2=\"\"" }, - { "=INDIRECT(\"'file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2\")" }, - }; - - ScAddress aPos(0,0,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - - std::vector<std::vector<const char*>> aOutputCheck = { - { "#REF!" }, // plain single ref - { "#REF!" }, // +23 - { "#REF!" }, // &"W" - { "FALSE" }, // ISREF - { "TRUE" }, // ISERROR - { "TRUE" }, // ISERR - { "FALSE" }, // ISBLANK - { "FALSE" }, // ISNUMBER - { "FALSE" }, // ISTEXT - { "FALSE" }, // ISNUMBER - { "FALSE" }, // ISTEXT - { "#REF!" }, // =0 - { "#REF!" }, // ="" - { "#REF!" }, // INDIRECT - { "#REF!" }, // A1:A2 range - { "#REF!" }, // +23 - { "#REF!" }, // &"W" - { "FALSE" }, // ISREF - { "TRUE" }, // ISERROR - { "TRUE" }, // ISERR - { "FALSE" }, // ISBLANK - { "FALSE" }, // ISNUMBER - { "FALSE" }, // ISTEXT - { "FALSE" }, // ISNUMBER - { "FALSE" }, // ISTEXT - // TODO: gives Err:504 FIXME { "#REF!" }, // =0 - // TODO: gives Err:504 FIXME { "#REF!" }, // ="" - { "#REF!" }, // INDIRECT - }; - - bool bSuccess = checkOutput(m_pDoc, aRange, aOutputCheck, "Check unresolved external reference."); - CPPUNIT_ASSERT_MESSAGE("Unresolved reference check failed", bSuccess); - - m_pDoc->DeleteTab(0); -#endif -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testMatrixOp) -{ - m_pDoc->InsertTab(0, "Test"); - - for (SCROW nRow = 0; nRow < 4; ++nRow) - { - m_pDoc->SetValue(0, nRow, 0, nRow); - } - m_pDoc->SetValue(1, 0, 0, 2.0); - m_pDoc->SetValue(3, 0, 0, 1.0); - m_pDoc->SetValue(3, 1, 0, 2.0); - m_pDoc->SetString(2, 0, 0, "=SUMPRODUCT((A1:A4)*B1+D1)"); - m_pDoc->SetString(2, 1, 0, "=SUMPRODUCT((A1:A4)*B1-D2)"); - - double nVal = m_pDoc->GetValue(2, 0, 0); - CPPUNIT_ASSERT_EQUAL(16.0, nVal); - - nVal = m_pDoc->GetValue(2, 1, 0); - CPPUNIT_ASSERT_EQUAL(4.0, nVal); - - m_pDoc->SetString(4, 0, 0, "=SUMPRODUCT({1;2;4}+8)"); - m_pDoc->SetString(4, 1, 0, "=SUMPRODUCT(8+{1;2;4})"); - m_pDoc->SetString(4, 2, 0, "=SUMPRODUCT({1;2;4}-8)"); - m_pDoc->SetString(4, 3, 0, "=SUMPRODUCT(8-{1;2;4})"); - m_pDoc->SetString(4, 4, 0, "=SUMPRODUCT({1;2;4}+{8;16;32})"); - m_pDoc->SetString(4, 5, 0, "=SUMPRODUCT({8;16;32}+{1;2;4})"); - m_pDoc->SetString(4, 6, 0, "=SUMPRODUCT({1;2;4}-{8;16;32})"); - m_pDoc->SetString(4, 7, 0, "=SUMPRODUCT({8;16;32}-{1;2;4})"); - double fResult[8] = { 31.0, 31.0, -17.0, 17.0, 63.0, 63.0, -49.0, 49.0 }; - for (size_t i = 0; i < SAL_N_ELEMENTS(fResult); ++i) - { - CPPUNIT_ASSERT_EQUAL( fResult[i], m_pDoc->GetValue(4, i, 0)); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncRangeOp) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - - m_pDoc->InsertTab(0, "Sheet1"); - m_pDoc->InsertTab(1, "Sheet2"); - m_pDoc->InsertTab(2, "Sheet3"); - - // Sheet1.B1:B3 - m_pDoc->SetValue(1,0,0, 1.0); - m_pDoc->SetValue(1,1,0, 2.0); - m_pDoc->SetValue(1,2,0, 4.0); - // Sheet2.B1:B3 - m_pDoc->SetValue(1,0,1, 8.0); - m_pDoc->SetValue(1,1,1, 16.0); - m_pDoc->SetValue(1,2,1, 32.0); - // Sheet3.B1:B3 - m_pDoc->SetValue(1,0,2, 64.0); - m_pDoc->SetValue(1,1,2, 128.0); - m_pDoc->SetValue(1,2,2, 256.0); - - // Range operator should extend concatenated literal references during - // parse time already, so with this we can test ScComplexRefData::Extend() - - // Current sheet is Sheet1, so B1:B2 implies relative Sheet1.B1:B2 - - ScAddress aPos(0,0,0); - m_pDoc->SetString( aPos, "=SUM(B1:B2:B3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B1:B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 7.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(B1:B3:B2)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B1:B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 7.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(B2:B3:B1)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B1:B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 7.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(Sheet2.B1:B2:B3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet2.B1:B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 56.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(B2:B2:Sheet1.B2)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B2:B2)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 2.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(B2:B3:Sheet2.B1)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B1:Sheet2.B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 63.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(Sheet1.B1:Sheet2.B2:Sheet3.B3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B1:Sheet3.B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 511.0, m_pDoc->GetValue(aPos)); - - // B1:Sheet2.B2 would be ambiguous, Sheet1.B1:Sheet2.B2 or Sheet2.B1:B2 - // The actual representation of the error case may change, so this test may - // have to be adapted. - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(B1:Sheet2.B2:Sheet3.B3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(b1:sheet2.b2:Sheet3.B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( OUString("#NAME?"), m_pDoc->GetString(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(Sheet1.B1:Sheet3.B2:Sheet2.B3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B1:Sheet3.B3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 511.0, m_pDoc->GetValue(aPos)); - - aPos.IncRow(); - m_pDoc->SetString( aPos, "=SUM(B$2:B$2:B2)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B$2:B2)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - CPPUNIT_ASSERT_EQUAL( 2.0, m_pDoc->GetValue(aPos)); - - m_pDoc->DeleteTab(2); - m_pDoc->DeleteTab(1); - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncFORMULA) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - - m_pDoc->InsertTab(0, "Sheet1"); - - // Data in B1:D3 - std::vector<std::vector<const char*>> aData = { - { "=A1", "=FORMULA(B1)", "=FORMULA(B1:B3)" }, - { nullptr, "=FORMULA(B2)", "=FORMULA(B1:B3)" }, - { "=A3", "=FORMULA(B3)", "=FORMULA(B1:B3)" }, - }; - - ScAddress aPos(1,0,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - - // Checks of C1:D3, where Cy==Dy, and D4:D6 - const char* aChecks[] = { - "=A1", - "#N/A", - "=A3", - }; - for (size_t i=0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - CPPUNIT_ASSERT_EQUAL( OUString::createFromAscii( aChecks[i]), m_pDoc->GetString(2,i,0)); - CPPUNIT_ASSERT_EQUAL( OUString::createFromAscii( aChecks[i]), m_pDoc->GetString(3,i,0)); - } - - // Matrix in D4:D6, no intersection with B1:B3 - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(3, 3, 3, 5, aMark, "=FORMULA(B1:B3)"); - for (size_t i=0; i < SAL_N_ELEMENTS(aChecks); ++i) - { - CPPUNIT_ASSERT_EQUAL( OUString::createFromAscii( aChecks[i]), m_pDoc->GetString(3,i+3,0)); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncTableRef) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - - m_pDoc->InsertTab(0, "Sheet1"); - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc(); - - { - ScDBCollection* pDBs = m_pDoc->GetDBCollection(); - CPPUNIT_ASSERT_MESSAGE("Failed to fetch DB collection object.", pDBs); - - // Insert "table" database range definition for A1:B4, with default - // HasHeader=true and HasTotals=false. - std::unique_ptr<ScDBData> pData(new ScDBData( "table", 0,0,0, 1,3)); - bool bInserted = pDBs->getNamedDBs().insert(std::move(pData)); - CPPUNIT_ASSERT_MESSAGE( "Failed to insert \"table\" database range.", bInserted); - } - - { - // Populate "table" database range with headers and data in A1:B4 - std::vector<std::vector<const char*>> aData = { - { "Header1", "Header2" }, - { "1", "2" }, - { "4", "8" }, - { "16", "32" } - }; - ScAddress aPos(0,0,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - } - - // Named expressions that use Table structured references. - /* TODO: should the item/header separator really be equal to the parameter - * separator, thus be locale dependent and ';' semicolon here, or should it - * be a fixed ',' comma instead? */ - static const struct { - const char* pName; - const char* pExpr; - const char* pCounta; // expected result when used in row 2 (first data row) as argument to COUNTA() - const char* pSum3; // expected result when used in row 3 (second data row) as argument to SUM(). - const char* pSum4; // expected result when used in row 4 (third data row) as argument to SUM(). - const char* pSumX; // expected result when used in row 5 (non-intersecting) as argument to SUM(). - } aNames[] = { - { "all", "table[[#All]]", "8", "63", "63", "63" }, - { "data_implicit", "table[]", "6", "63", "63", "63" }, - { "data", "table[[#Data]]", "6", "63", "63", "63" }, - { "headers", "table[[#Headers]]", "2", "0", "0", "0" }, - { "header1", "table[[Header1]]", "3", "21", "21", "21" }, - { "header2", "table[[Header2]]", "3", "42", "42", "42" }, - { "data_header1", "table[[#Data];[Header1]]", "3", "21", "21", "21" }, - { "data_header2", "table[[#Data];[Header2]]", "3", "42", "42", "42" }, - { "this_row", "table[[#This Row]]", "2", "12", "48", "#VALUE!" }, - { "this_row_header1", "table[[#This Row];[Header1]]", "1", "4", "16", "#VALUE!" }, - { "this_row_header2", "table[[#This Row];[Header2]]", "1", "8", "32", "#VALUE!" }, - { "this_row_range_header_1_to_2", "table[[#This Row];[Header1]:[Header2]]", "2", "12", "48", "#VALUE!" } - }; - - { - // Insert named expressions. - ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); - CPPUNIT_ASSERT_MESSAGE("Failed to obtain global named expression object.", pGlobalNames); - - for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) - { - // Choose base position that does not intersect with the database - // range definition to test later use of [#This Row] results in - // proper rows. - ScRangeData* pName = new ScRangeData( - *m_pDoc, OUString::createFromAscii(aNames[i].pName), OUString::createFromAscii(aNames[i].pExpr), - ScAddress(2,4,0), ScRangeData::Type::Name, formula::FormulaGrammar::GRAM_NATIVE); - bool bInserted = pGlobalNames->insert(pName); - CPPUNIT_ASSERT_MESSAGE( - OString(OString::Concat("Failed to insert named expression ") + aNames[i].pName +".").getStr(), bInserted); - } - } - - // Use the named expressions in COUNTA() formulas, on row 2 that intersects. - for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) - { - OUString aFormula( "=COUNTA(" + OUString::createFromAscii( aNames[i].pName) + ")"); - ScAddress aPos(3+i,1,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aNames[i].pCounta)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Use the named expressions in SUM() formulas, on row 3 that intersects. - for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aNames[i].pName) + ")"); - ScAddress aPos(3+i,2,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aNames[i].pSum3)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Use the named expressions in SUM() formulas, on row 4 that intersects. - for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aNames[i].pName) + ")"); - ScAddress aPos(3+i,3,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aNames[i].pSum4)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Use the named expressions in SUM() formulas, on row 5 that does not intersect. - for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aNames[i].pName) + ")"); - ScAddress aPos(3+i,4,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aNames[i].pSumX)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Insert a column at column B to extend database range from column A,B to - // A,B,C. Use ScDocFunc so RefreshDirtyTableColumnNames() is called. - rDocFunc.InsertCells(ScRange(1,0,0,1,m_pDoc->MaxRow(),0), &aMark, INS_INSCOLS_BEFORE, false, true); - - // Re-verify the named expression in SUM() formula, on row 4 that - // intersects, now starting at column E, still works. - m_pDoc->CalcAll(); - for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aNames[i].pName) + ")"); - ScAddress aPos(4+i,3,0); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aNames[i].pSum4)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - const char* pColumn2Formula = "=SUM(table[[#Data];[Column2]])"; - { - // Populate "table" database range with empty header and data in newly - // inserted column, B1:B4 plus a table formula in B6. The empty header - // should result in the internal table column name "Column2" that is - // used in the formula. - std::vector<std::vector<const char*>> aData = { - { "" }, - { "64" }, - { "128" }, - { "256" }, - { "" }, - { pColumn2Formula } - }; - ScAddress aPos(1,0,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - } - - // Verify the formula result in B6 (64+128+256=448). - { - OUString aFormula( OUString::createFromAscii( pColumn2Formula)); - ScAddress aPos(1,5,0); - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + "448"), OUString(aPrefix + m_pDoc->GetString(aPos))); - } - - // Set header in column B. Use ScDocFunc to have table column names refreshed. - rDocFunc.SetStringCell(ScAddress(1,0,0), "NewHeader",true); - // Verify that formula adapted using the updated table column names. - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", OUString("=SUM(table[[#Data];[NewHeader]])"), m_pDoc->GetFormula(1,5,0)); - - // Set header in column A to identical string. Internal table column name - // for B should get a "2" appended. - rDocFunc.SetStringCell(ScAddress(0,0,0), "NewHeader",true); - // Verify that formula adapted using the updated table column names. - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", OUString("=SUM(table[[#Data];[NewHeader2]])"), m_pDoc->GetFormula(1,5,0)); - - // Set header in column B to empty string, effectively clearing the cell. - rDocFunc.SetStringCell(ScAddress(1,0,0), "",true); - // Verify that formula is still using the previous table column name. - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", OUString("=SUM(table[[#Data];[NewHeader2]])"), m_pDoc->GetFormula(1,5,0)); - - // === header-less === - - { - ScDBCollection* pDBs = m_pDoc->GetDBCollection(); - CPPUNIT_ASSERT_MESSAGE("Failed to fetch DB collection object.", pDBs); - - // Insert "headerless" database range definition for E10:F12, without headers. - std::unique_ptr<ScDBData> pData(new ScDBData( "hltable", 0, 4,9, 5,11, true, false)); - bool bInserted = pDBs->getNamedDBs().insert(std::move(pData)); - CPPUNIT_ASSERT_MESSAGE( "Failed to insert \"hltable\" database range.", bInserted); - } - - { - // Populate "hltable" database range with data in E10:F12 - std::vector<std::vector<const char*>> aData = { - { "1", "2" }, - { "4", "8" }, - { "16", "32" } - }; - ScAddress aPos(4,9,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - } - - // Named expressions that use header-less Table structured references. - static const struct { - const char* pName; - const char* pExpr; - const char* pCounta; // expected result when used in row 10 (first data row) as argument to COUNTA() - const char* pSum3; // expected result when used in row 11 (second data row) as argument to SUM(). - const char* pSum4; // expected result when used in row 12 (third data row) as argument to SUM(). - const char* pSumX; // expected result when used in row 13 (non-intersecting) as argument to SUM(). - } aHlNames[] = { - { "hl_all", "hltable[[#All]]", "6", "63", "63", "63" }, - { "hl_data_implicit", "hltable[]", "6", "63", "63", "63" }, - { "hl_data", "hltable[[#Data]]", "6", "63", "63", "63" }, - { "hl_headers", "hltable[[#Headers]]", "1", "#REF!", "#REF!", "#REF!" }, - { "hl_column1", "hltable[[Column1]]", "3", "21", "21", "21" }, - { "hl_column2", "hltable[[Column2]]", "3", "42", "42", "42" }, - { "hl_data_column1", "hltable[[#Data];[Column1]]", "3", "21", "21", "21" }, - { "hl_data_column2", "hltable[[#Data];[Column2]]", "3", "42", "42", "42" }, - { "hl_this_row", "hltable[[#This Row]]", "2", "12", "48", "#VALUE!" }, - { "hl_this_row_column1", "hltable[[#This Row];[Column1]]", "1", "4", "16", "#VALUE!" }, - { "hl_this_row_column2", "hltable[[#This Row];[Column2]]", "1", "8", "32", "#VALUE!" }, - { "hl_this_row_range_column_1_to_2", "hltable[[#This Row];[Column1]:[Column2]]", "2", "12", "48", "#VALUE!" } - }; - - { - // Insert named expressions. - ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); - CPPUNIT_ASSERT_MESSAGE("Failed to obtain global named expression object.", pGlobalNames); - - for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) - { - // Choose base position that does not intersect with the database - // range definition to test later use of [#This Row] results in - // proper rows. - ScRangeData* pName = new ScRangeData( - *m_pDoc, OUString::createFromAscii(aHlNames[i].pName), OUString::createFromAscii(aHlNames[i].pExpr), - ScAddress(6,12,0), ScRangeData::Type::Name, formula::FormulaGrammar::GRAM_NATIVE); - bool bInserted = pGlobalNames->insert(pName); - CPPUNIT_ASSERT_MESSAGE( - OString(OString::Concat("Failed to insert named expression ") + aHlNames[i].pName +".").getStr(), bInserted); - } - } - - // Use the named expressions in COUNTA() formulas, on row 10 that intersects. - for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) - { - OUString aFormula( "=COUNTA(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); - ScAddress aPos(7+i,9,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aHlNames[i].pCounta)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Use the named expressions in SUM() formulas, on row 11 that intersects. - for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); - ScAddress aPos(7+i,10,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aHlNames[i].pSum3)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Use the named expressions in SUM() formulas, on row 12 that intersects. - for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); - ScAddress aPos(7+i,11,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aHlNames[i].pSum4)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Use the named expressions in SUM() formulas, on row 13 that does not intersect. - for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); - ScAddress aPos(7+i,12,0); - m_pDoc->SetString( aPos, aFormula); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aHlNames[i].pSumX)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - // Insert a column at column F to extend database range from column E,F to - // E,F,G. Use ScDocFunc so RefreshDirtyTableColumnNames() is called. - rDocFunc.InsertCells(ScRange(5,0,0,5,m_pDoc->MaxRow(),0), &aMark, INS_INSCOLS_BEFORE, false, true); - - // Re-verify the named expression in SUM() formula, on row 12 that - // intersects, now starting at column I, still works. - m_pDoc->CalcAll(); - for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) - { - OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); - ScAddress aPos(8+i,11,0); - // For easier "debugability" have position and formula in assertion. - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + OUString::createFromAscii( aHlNames[i].pSum4)), - OUString(aPrefix + m_pDoc->GetString( aPos))); - } - - const char* pColumn3Formula = "=SUM(hltable[[#Data];[Column3]])"; - { - // Populate "hltable" database range with data in newly inserted - // column, F10:F12 plus a table formula in F14. The new header should - // result in the internal table column name "Column3" that is used in - // the formula. - std::vector<std::vector<const char*>> aData = { - { "64" }, - { "128" }, - { "256" }, - { "" }, - { pColumn3Formula } - }; - ScAddress aPos(5,9,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - } - - // Verify the formula result in F14 (64+128+256=448). - { - OUString aFormula( OUString::createFromAscii( pColumn3Formula)); - ScAddress aPos(5,13,0); - OUString aPrefix( aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); - CPPUNIT_ASSERT_EQUAL( OUString(aPrefix + "448"), OUString(aPrefix + m_pDoc->GetString(aPos))); - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncFTEST) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "FTest"); - - ScAddress aPos(6,0,0); - m_pDoc->SetString(aPos, "=FTEST(A1:C3;D1:F3)"); - m_pDoc->SetValue(0, 0, 0, 9.0); // A1 - OUString aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("FTEST should return #VALUE! for less than 2 values", - OUString("#VALUE!"), aVal); - m_pDoc->SetValue(0, 1, 0, 8.0); // A2 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("FTEST should return #VALUE! for less than 2 values", - OUString("#VALUE!"), aVal); - m_pDoc->SetValue(3, 0, 0, 5.0); // D1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("FTEST should return #VALUE! for less than 2 values", - OUString("#VALUE!"), aVal); - m_pDoc->SetValue(3, 1, 0, 6.0); // D2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 1.0000, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(1, 0, 0, 6.0); // B1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.6222, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(1, 1, 0, 8.0); // B2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.7732, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(4, 0, 0, 7.0); // E1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.8194, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(4, 1, 0, 4.0); // E2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.9674, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(2, 0, 0, 3.0); // C1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.3402, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(5, 0, 0, 28.0); // F1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0161, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(2, 1, 0, 9.0); // C2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0063, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(5, 1, 0, 4.0); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0081, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(0, 2, 0, 2.0); // A3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0122, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(3, 2, 0, 8.0); // D3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0178, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(1, 2, 0, 4.0); // B3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0093, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(4, 2, 0, 7.0); // E3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0132, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(5, 2, 0, 5.0); // F3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0168, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(2, 2, 0, 13.0); // C3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0422, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->SetString(0, 2, 0, "a"); // A3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0334, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetString(2, 0, 0, "b"); // C1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0261, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetString(5, 1, 0, "c"); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0219, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetString(4, 2, 0, "d"); // E3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0161, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetString(3, 2, 0, "e"); // D3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0110, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->DeleteTab(0); - m_pDoc->InsertTab(0, "FTest2"); - - /* Summary of the following test - A1:A5 = SQRT(C1*9/10)*{ 1.0, 1.0, 1.0, 1.0, 1.0 }; - A6:A10 = -SQRT(C1*9/10)*{ 1.0, 1.0, 1.0, 1.0, 1.0 }; - B1:B10 = SQRT(C2*19/20)*{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; - B11:B20 = -SQRT(C2*19/20)*{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; - C1 = POWER(1.5, D1) ; This is going to be the sample variance of the vector A1:A10 - C2 = POWER(1.5, D2) ; This is going to be the sample variance of the vector B1:B20 - D1 and D2 are varied over { -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 } - - Result of FTEST(A1:A10;B1:B20) in Calc is compared with that from Octave's var_test() function for each value of D1 and D2. - - The minimum variance ratio obtained in this way is 0.017342 and the maximum variance ratio is 57.665039 - */ - - const size_t nNumParams = 11; - const double fParameter[nNumParams] = { -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }; - - // Results of var_test() from Octave - const double fResults[nNumParams][nNumParams] = { - { 0.9451191535603041,0.5429768686792684,0.213130093422756,0.06607644828558357,0.0169804365506927,0.003790723514148109, - 0.0007645345628801703,0.0001435746909905777,2.566562398786942e-05,4.436218417280813e-06,7.495090956766148e-07 }, - { 0.4360331979746912,0.9451191535603054,0.5429768686792684,0.2131300934227565,0.06607644828558357,0.0169804365506927, - 0.003790723514148109,0.0007645345628801703,0.0001435746909905777,2.566562398786942e-05,4.436218417280813e-06 }, - { 0.1309752286653509,0.4360331979746914,0.9451191535603058,0.5429768686792684,0.2131300934227565,0.06607644828558357, - 0.0169804365506927,0.003790723514148109,0.0007645345628801703,0.0001435746909905777,2.566562398786942e-05 }, - { 0.02453502500565108,0.1309752286653514,0.4360331979746914,0.9451191535603058,0.5429768686792689,0.2131300934227565, - 0.06607644828558357,0.0169804365506927,0.003790723514148109,0.0007645345628801703,0.0001435746909905777 }, - { 0.002886791075972228,0.02453502500565108,0.1309752286653514,0.4360331979746914,0.9451191535603041,0.5429768686792689, - 0.2131300934227565,0.06607644828558357,0.0169804365506927,0.003790723514148109,0.0007645345628801703 }, - { 0.0002237196492846927,0.002886791075972228,0.02453502500565108,0.1309752286653509,0.4360331979746912,0.9451191535603036, - 0.5429768686792689,0.2131300934227565,0.06607644828558357,0.0169804365506927,0.003790723514148109 }, - { 1.224926820153627e-05,0.0002237196492846927,0.002886791075972228,0.02453502500565108,0.1309752286653509,0.4360331979746914, - 0.9451191535603054,0.5429768686792684,0.2131300934227565,0.06607644828558357,0.0169804365506927 }, - { 5.109390206481379e-07,1.224926820153627e-05,0.0002237196492846927,0.002886791075972228,0.02453502500565108, - 0.1309752286653509,0.4360331979746914,0.9451191535603058,0.5429768686792684,0.213130093422756,0.06607644828558357 }, - { 1.739106880727093e-08,5.109390206481379e-07,1.224926820153627e-05,0.0002237196492846927,0.002886791075972228, - 0.02453502500565086,0.1309752286653509,0.4360331979746914,0.9451191535603041,0.5429768686792684,0.2131300934227565 }, - { 5.111255862999542e-10,1.739106880727093e-08,5.109390206481379e-07,1.224926820153627e-05,0.0002237196492846927, - 0.002886791075972228,0.02453502500565108,0.1309752286653516,0.4360331979746914,0.9451191535603058,0.5429768686792684 }, - { 1.354649725726631e-11,5.111255862999542e-10,1.739106880727093e-08,5.109390206481379e-07,1.224926820153627e-05, - 0.0002237196492846927,0.002886791075972228,0.02453502500565108,0.1309752286653509,0.4360331979746914,0.9451191535603054 } - }; - - m_pDoc->SetValue(3, 0, 0, fParameter[0]); // D1 - m_pDoc->SetValue(3, 1, 0, fParameter[0]); // D2 - aPos.Set(2,0,0); // C1 - m_pDoc->SetString(aPos, "=POWER(1.5;D1)" ); // C1 - aPos.Set(2, 1, 0); // C2 - m_pDoc->SetString(aPos, "=POWER(1.5;D2)" ); // C2 - for ( SCROW nRow = 0; nRow < 5; ++nRow ) // Set A1:A5 = SQRT(C1*9/10), and A6:A10 = -SQRT(C1*9/10) - { - aPos.Set(0, nRow, 0); - m_pDoc->SetString(aPos, "=SQRT(C1*9/10)"); - aPos.Set(0, nRow + 5, 0); - m_pDoc->SetString(aPos, "=-SQRT(C1*9/10)"); - } - - for ( SCROW nRow = 0; nRow < 10; ++nRow ) // Set B1:B10 = SQRT(C2*19/20), and B11:B20 = -SQRT(C2*19/20) - { - aPos.Set(1, nRow, 0); - m_pDoc->SetString(aPos, "=SQRT(C2*19/20)"); - aPos.Set(1, nRow + 10, 0); - m_pDoc->SetString(aPos, "=-SQRT(C2*19/20)"); - } - - aPos.Set(4, 0, 0); // E1 - m_pDoc->SetString(aPos, "=FTEST(A1:A10;B1:B20)"); - aPos.Set(4, 1, 0); // E2 - m_pDoc->SetString(aPos, "=FTEST(B1:B20;A1:A10)"); - - ScAddress aPosRev(4, 1, 0); // E2 - aPos.Set(4, 0, 0); // E1 - - for ( size_t nFirstIdx = 0; nFirstIdx < nNumParams; ++nFirstIdx ) - { - m_pDoc->SetValue(3, 0, 0, fParameter[nFirstIdx]); // Set D1 - for ( size_t nSecondIdx = 0; nSecondIdx < nNumParams; ++nSecondIdx ) - { - m_pDoc->SetValue(3, 1, 0, fParameter[nSecondIdx]); // Set D2 - double fExpected = fResults[nFirstIdx][nSecondIdx]; - // Here a dynamic error limit is used. This is to handle correctly when the expected value is lower than the fixed error limit of 10e-5 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", fExpected, m_pDoc->GetValue(aPos), std::min(10e-5, fExpected*0.0001) ); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", fExpected, m_pDoc->GetValue(aPosRev), std::min(10e-5, fExpected*0.0001) ); - } - } - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncFTESTBug) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "FTest"); - - ScAddress aPos(9,0,0); - m_pDoc->SetString(aPos, "=FTEST(H1:H3;I1:I3)"); - - m_pDoc->SetValue(7, 0, 0, 9.0); // H1 - m_pDoc->SetValue(7, 1, 0, 8.0); // H2 - m_pDoc->SetValue(7, 2, 0, 6.0); // H3 - m_pDoc->SetValue(8, 0, 0, 5.0); // I1 - m_pDoc->SetValue(8, 1, 0, 7.0); // I2 - // tdf#93329 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.9046, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncCHITEST) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "ChiTest"); - - ScAddress aPos(6,0,0); - // 2x2 matrices test - m_pDoc->SetString(aPos, "=CHITEST(A1:B2;D1:E2)"); - OUString aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with empty cells", - OUString("Err:502"), aVal); - - m_pDoc->SetValue(0, 0, 0, 1.0); // A1 - m_pDoc->SetValue(0, 1, 0, 2.0); // A2 - m_pDoc->SetValue(1, 0, 0, 2.0); // B1 - m_pDoc->SetValue(1, 1, 0, 1.0); // B2 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrix with empty cells", - OUString("Err:502"), aVal); - - m_pDoc->SetValue(3, 0, 0, 2.0); // D1 - m_pDoc->SetValue(3, 1, 0, 3.0); // D2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.3613, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->SetValue(4, 1, 0, 1.0); // E2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.3613, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(4, 0, 0, 3.0); // E1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.2801, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(4, 0, 0, 0.0); // E1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return #DIV/0 for expected values of 0", OUString("#DIV/0!"), aVal); - m_pDoc->SetValue(4, 0, 0, 3.0); // E1 - m_pDoc->SetValue(1, 1, 0, 0.0); // B2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1410, m_pDoc->GetValue(aPos), 10e-4); - - // 3x3 matrices test - m_pDoc->SetString(aPos, "=CHITEST(A1:C3;D1:F3)"); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.7051, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->SetValue(2, 0, 0, 3.0); // C1 - m_pDoc->SetValue(2, 1, 0, 2.0); // C2 - m_pDoc->SetValue(2, 2, 0, 3.0); // C3 - m_pDoc->SetValue(0, 2, 0, 4.0); // A3 - m_pDoc->SetValue(1, 2, 0, 2.0); // B3 - m_pDoc->SetValue(5, 0, 0, 1.0); // F1 - m_pDoc->SetValue(5, 1, 0, 2.0); // F2 - m_pDoc->SetValue(5, 2, 0, 3.0); // F3 - m_pDoc->SetValue(3, 2, 0, 3.0); // D3 - m_pDoc->SetValue(4, 2, 0, 1.0); // E3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1117, m_pDoc->GetValue(aPos), 10e-4); - - // test with strings - m_pDoc->SetString(4, 2, 0, "a"); // E3 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with strings", - OUString("Err:502"), aVal); - m_pDoc->SetString(1, 2, 0, "a"); // B3 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with strings", - OUString("Err:502"), aVal); - m_pDoc->SetValue(4, 2, 0, 1.0); // E3 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with strings", - OUString("Err:502"), aVal); - m_pDoc->SetValue(1, 2, 0, 2.0); // B3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1117, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->SetValue(4, 1, 0, 5.0); // E2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0215, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(1, 2, 0, 1.0); // B3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0328, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(5, 0, 0, 3.0); // F1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1648, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(0, 1, 0, 3.0); // A2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1870, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(3, 1, 0, 5.0); // D2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1377, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(3, 2, 0, 4.0); // D3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1566, m_pDoc->GetValue(aPos), 10e-4); - - m_pDoc->SetValue(0, 0, 0, 0.0); // A1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0868, m_pDoc->GetValue(aPos), 10e-4); - - // no convergence error - m_pDoc->SetValue(4, 0, 0, 1.0E308); // E1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL(OUString("Err:523"), aVal); - m_pDoc->SetValue(4, 0, 0, 3.0); // E1 - - // zero in all cells - m_pDoc->SetValue(0, 1, 0, 0.0); // A2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0150, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(0, 2, 0, 0.0); // A3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0026, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(1, 0, 0, 0.0); // B1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.00079, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(1, 2, 0, 0.0); // B3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0005, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(2, 0, 0, 0.0); // C1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0001, m_pDoc->GetValue(aPos), 10e-4); - m_pDoc->SetValue(2, 1, 0, 0.0); // C2 - m_pDoc->SetValue(2, 2, 0, 0.0); // C3 - m_pDoc->SetValue(3, 0, 0, 0.0); // D1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return #DIV/0! for matrices with empty", - OUString("#DIV/0!"), aVal); - m_pDoc->SetValue(3, 1, 0, 0.0); // D2 - m_pDoc->SetValue(3, 2, 0, 0.0); // D3 - m_pDoc->SetValue(4, 0, 0, 0.0); // E1 - m_pDoc->SetValue(4, 1, 0, 0.0); // E2 - m_pDoc->SetValue(4, 2, 0, 0.0); // E3 - m_pDoc->SetValue(5, 0, 0, 0.0); // F1 - m_pDoc->SetValue(5, 1, 0, 0.0); // F2 - m_pDoc->SetValue(5, 2, 0, 0.0); // F3 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return #DIV/0! for matrices with empty", - OUString("#DIV/0!"), aVal); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncTTEST) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "TTest"); - - ScAddress aPos(6,0,0); - // type 1, mode/tails 1 - m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;1;1)"); - OUString aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("TTEST should return #VALUE! for empty matrices", - OUString("#VALUE!"), aVal); - - m_pDoc->SetValue(0, 0, 0, 8.0); // A1 - m_pDoc->SetValue(1, 0, 0, 2.0); // B1 - m_pDoc->SetValue(3, 0, 0, 3.0); // D1 - m_pDoc->SetValue(4, 0, 0, 1.0); // E1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.18717, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(2, 0, 0, 1.0); // C1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.18717, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 0, 0, 6.0); // F1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.45958, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(0, 1, 0, -4.0); // A2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.45958, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(3, 1, 0, 1.0); // D2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.35524, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(1, 1, 0, 5.0); // B2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.35524, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(4, 1, 0, -2.0); // E2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.41043, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(2, 1, 0, -1.0); // C2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.41043, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 1, 0, -3.0); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34990, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(0, 2, 0, 10.0); // A3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34990, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(3, 2, 0, 10.0); // D3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34686, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(1, 2, 0, 3.0); // B3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34686, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(4, 2, 0, 9.0); // E3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.47198, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(2, 2, 0, -5.0); // C3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.47198, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 2, 0, 6.0); // F3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.25529, m_pDoc->GetValue(aPos), 10e-5); - - m_pDoc->SetString(1, 1, 0, "a"); // B2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.12016, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetString(4, 1, 0, "b"); // E2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.12016, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetString(2, 2, 0, "c"); // C3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.25030, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetString(5, 1, 0, "d"); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.19637, m_pDoc->GetValue(aPos), 10e-5); - - // type 1, mode/tails 2 - m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;2;1)"); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.39273, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(1, 1, 0, 4.0); // B2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.39273, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(4, 1, 0, 3.0); // E2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.43970, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(2, 2, 0, -2.0); // C3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.22217, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 1, 0, -10.0); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.64668, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(0, 1, 0, 3.0); // A2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.95266, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(3, 2, 0, -1.0); // D3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.62636, m_pDoc->GetValue(aPos), 10e-5); - - // type 2, mode/tails 2 - m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;2;2)"); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.62549, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 1, 0, -1.0); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.94952, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(2, 2, 0, 5.0); // C3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.58876, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(2, 1, 0, 2.0); // C2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.43205, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(3, 2, 0, -4.0); // D3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.36165, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(0, 1, 0, 1.0); // A2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.44207, m_pDoc->GetValue(aPos), 10e-5); - - // type 3, mode/tails 1 - m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;1;3)"); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.22132, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(0, 0, 0, 1.0); // A1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.36977, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(0, 2, 0, -30.0); // A3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.16871, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(3, 1, 0, 5.0); // D2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.14396, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 1, 0, 2.0); // F2 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.12590, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(4, 2, 0, 2.0); // E3 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.16424, m_pDoc->GetValue(aPos), 10e-5); - m_pDoc->SetValue(5, 0, 0, -1.0); // F1 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.21472, m_pDoc->GetValue(aPos), 10e-5); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUMX2PY2) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "SumX2PY2 Test"); - - OUString aVal; - ScAddress aPos(6,0,0); - m_pDoc->SetString(aPos, "=SUMX2PY2(A1:C3;D1:F3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 0.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetValue(0, 0, 0, 1.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(3, 0, 0, 2.0); // D1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 2.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(4, 0, 0, 0.0); // E1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 9.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 0, 0, 3.0); // C1 - m_pDoc->SetValue(5, 0, 0, 3.0); // F1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 27.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 10.0); // A2 - m_pDoc->SetValue(3, 1, 0, -10.0); // D2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 227.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, -5.0); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 227.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(4, 1, 0, -5.0); // E2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 277.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 1, 0, 0.0); // C2 - m_pDoc->SetValue(5, 1, 0, 0.0); // F2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 277.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 2, 0, -8.0); // A3 - m_pDoc->SetValue(3, 2, 0, 8.0); // D3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 405.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 2, 0, 0.0); // B3 - m_pDoc->SetValue(4, 2, 0, 0.0); // E3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 405.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 2, 0, 1.0); // C3 - m_pDoc->SetValue(5, 2, 0, 1.0); // F3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 407.0, m_pDoc->GetValue(aPos)); - - // add some strings - m_pDoc->SetString(4, 1, 0, "a"); // E2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 357.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(1, 1, 0, "a"); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 357.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(0, 0, 0, "a"); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 352.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(3, 0, 0, "a"); // D1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 352.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=SUMX2PY2({1;2;3};{2;3;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 43.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMX2PY2({1;2;3};{2;3})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2PY2 should return #VALUE! for matrices with different sizes", - OUString("#VALUE!"), aVal); - m_pDoc->SetString(aPos, "=SUMX2PY2({1;2;3})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2PY2 needs two parameters", - OUString("Err:511"), aVal); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUMX2MY2) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "SumX2MY2 Test"); - - OUString aVal; - ScAddress aPos(6,0,0); - m_pDoc->SetString(aPos, "=SUMX2MY2(A1:C3;D1:F3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 0.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetValue(0, 0, 0, 10.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(3, 0, 0, -9.0); // D1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 19.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 2.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 19.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(4, 0, 0, 1.0); // E1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 0, 0, 3.0); // C1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(5, 0, 0, 3.0); // F1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 10.0); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(3, 1, 0, -10.0); // D2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, -5.0); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(4, 1, 0, -5.0); // E2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 1, 0, -3.0); // C2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(5, 1, 0, 3.0); // F2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 2, 0, -8.0); // A3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(3, 2, 0, 3.0); // D3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 77.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 2, 0, 2.0); // B3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 77.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(4, 2, 0, -6.0); // E3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 45.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 2, 0, -4.0); // C3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 45.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(5, 2, 0, 6.0); // F3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 25.0, m_pDoc->GetValue(aPos)); - - // add some strings - m_pDoc->SetString(5, 2, 0, "a"); // F3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 45.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(0, 2, 0, "a"); // A3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", -10.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(1, 0, 0, "a"); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", -13.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(3, 0, 0, "a"); // D1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", -32.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=SUMX2MY2({1;3;5};{0;4;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 3.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMX2MY2({1;-3;-5};{0;-4;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 3.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMX2MY2({9;5;1};{3;-3;3})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 80.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMX2MY2({1;2;3};{2;3})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2MY2 should return #VALUE! for matrices with different sizes", - OUString("#VALUE!"), aVal); - m_pDoc->SetString(aPos, "=SUMX2MY2({1;2;3})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2MY2 needs two parameters", - OUString("Err:511"), aVal); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncGCD) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "GCDTest"); - - OUString aVal; - ScAddress aPos(4,0,0); - - m_pDoc->SetString(aPos, "=GCD(A1)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 10.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 10.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, -2.0); // A1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetString(0, 0, 0, "a"); // A1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return #VALUE! for a single string", - OUString("#VALUE!"), aVal); - - m_pDoc->SetString(aPos, "=GCD(A1:B2)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, -12.0); // B1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for a matrix with values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetValue(0, 0, 0, 15.0); // A1 - m_pDoc->SetValue(0, 1, 0, 0.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 15.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 5.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 10.0); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 30.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 20.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 10.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, 120.0); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 10.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 80.0); // A2 - m_pDoc->SetValue(1, 0, 0, 40.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 20.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 45.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - - // with floor - m_pDoc->SetValue(1, 0, 0, 45.381); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, 120.895); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 20.97); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 10.15); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); - - // inline array - m_pDoc->SetString(aPos, "=GCD({3;6;9})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 3.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=GCD({150;0})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 150.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=GCD({-3;6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetString(aPos, "=GCD({\"a\";6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with strings", - OUString("Err:502"), aVal); - - //many inline array - m_pDoc->SetString(aPos, "=GCD({6;6;6};{3;6;9})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 3.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=GCD({300;300;300};{150;0})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 150.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos,"=GCD({3;6;9};{3;-6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetString(aPos, "=GCD({3;6;9};{\"a\";6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with strings", - OUString("Err:502"), aVal); - - // inline list of values - m_pDoc->SetString(aPos, "=GCD(12;24;36;48;60)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 12.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=GCD(0;12;24;36;48;60)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 12.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=GCD(\"a\";1)"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return #VALUE! for an array with strings", - OUString("#VALUE!"), aVal); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncLCM) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "LCMTest"); - - OUString aVal; - ScAddress aPos(4,0,0); - - m_pDoc->SetString(aPos, "=LCM(A1)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 10.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 10.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, -2.0); // A1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetString(0, 0, 0, "a"); // A1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return #VALUE! for a single string", - OUString("#VALUE!"), aVal); - - m_pDoc->SetString(aPos, "=LCM(A1:B2)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 1.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, -12.0); // B1 - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for a matrix with values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetValue(0, 0, 0, 15.0); // A1 - m_pDoc->SetValue(0, 1, 0, 0.0); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 5.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 10.0); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 30.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 30.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 30.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 20.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 60.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, 125.0); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 1500.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 99.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 49500.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 37.0); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 1831500.0, m_pDoc->GetValue(aPos)); - - // with floor - m_pDoc->SetValue(1, 0, 0, 99.89); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 1831500.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, 11.32); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 73260.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 22.58); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 7326.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, 3.99); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 198.0, m_pDoc->GetValue(aPos)); - - // inline array - m_pDoc->SetString(aPos, "=LCM({3;6;9})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 18.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=LCM({150;0})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=LCM({-3;6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetString(aPos, "=LCM({\"a\";6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with strings", - OUString("Err:502"), aVal); - - //many inline array - m_pDoc->SetString(aPos, "=LCM({6;6;6};{3;6;9})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 18.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=LCM({300;300;300};{150;0})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos,"=LCM({3;6;9};{3;-6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with values less than 0", - OUString("Err:502"), aVal); - m_pDoc->SetString(aPos, "=LCM({3;6;9};{\"a\";6;9})"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with strings", - OUString("Err:502"), aVal); - - m_pDoc->SetString(aPos, "=LCM(12;24;36;48;60)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 720.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=LCM(0;12;24;36;48;60)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=LCM(\"a\";1)"); - aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return #VALUE! for an array with strings", - OUString("#VALUE!"), aVal); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUMSQ) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "SUMSQTest"); - - ScAddress aPos(4,0,0); - - m_pDoc->SetString(aPos, "=SUMSQ(A1)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 0.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, 1.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 1.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 0, 0, -1.0); // A1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 1.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 1, 0, -2.0); // A2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 1.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=SUMSQ(A1:A3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 0, 0, 3.0); // B1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 5.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ(A1:C3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 14.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 1, 0, -4.0); // B2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 30.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(1, 2, 0, "a"); // B3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 30.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(1, 2, 0, 0.0); // B3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 30.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(0, 2, 0, 6.0); // A3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 66.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 0, 0, -5.0); // C1 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 91.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 1, 0, 3.0); // C2 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 100.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue(2, 2, 0, 2.0); // C3 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 104.0, m_pDoc->GetValue(aPos)); - - // inline array - m_pDoc->SetString(aPos, "=SUMSQ({1;2;3})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 14.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({3;6;9})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 126.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({15;0})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 225.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({-3;3;1})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 19.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({\"a\";-4;-5})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 41.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=SUMSQ({2;3};{4;5})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 54.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({-3;3;1};{-1})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 20.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({-4};{1;4;2};{-5;7};{9})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 192.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ({-2;2};{1};{-1};{0;0;0;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 26.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=SUMSQ(4;1;-3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 26.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ(0;5;13;-7;-4)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 259.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ(0;12;24;36;48;60)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 7920.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ(0;-12;-24;36;-48;60)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 7920.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetString(aPos, "=SUMSQ(\"a\";1;\"d\";-4;2)"); - OUString aVal = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMSQ should return #VALUE! for an array with strings", - OUString("#VALUE!"), aVal); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncMDETERM) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "MDETERM_test"); - ScAddress aPos(8,0,0); - OUString const aColCodes("ABCDEFGH"); - OUStringBuffer aFormulaBuffer("=MDETERM(A1:B2)"); - for( SCSIZE nSize = 3; nSize <= 8; nSize++ ) - { - double fVal = 1.0; - // Generate a singular integer matrix - for( SCROW nRow = 0; nRow < static_cast<SCROW>(nSize); nRow++ ) - { - for( SCCOL nCol = 0; nCol < static_cast<SCCOL>(nSize); nCol++ ) - { - m_pDoc->SetValue(nCol, nRow, 0, fVal); - fVal += 1.0; - } - } - aFormulaBuffer[12] = aColCodes[nSize-1]; - aFormulaBuffer[13] = static_cast<sal_Unicode>( '0' + nSize ); - m_pDoc->SetString(aPos, aFormulaBuffer.toString()); - -#if SAL_TYPES_SIZEOFPOINTER == 4 - // On crappy 32-bit targets, presumably without extended precision on - // interim results or optimization not catching it, this test fails - // when comparing to 0.0, so have a narrow error margin. See also - // commit message of 8140309d636d4a870875f2dd75ed3dfff2c0fbaf - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of MDETERM incorrect for singular integer matrix", - 0.0, m_pDoc->GetValue(aPos), 1e-12); -#else - // Even on one (and only one) x86_64 target the result was - // 6.34413156928661e-17 instead of 0.0 (tdf#99730) so lower the bar to - // 10e-14. - // Then again on aarch64, ppc64* and s390x it also fails. - // Sigh... why do we even test this? The original complaint in tdf#32834 - // was about -9.51712667007776E-016 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of MDETERM incorrect for singular integer matrix", - 0.0, m_pDoc->GetValue(aPos), 1e-14); -#endif - } - - int const aVals[] = {23, 31, 13, 12, 34, 64, 34, 31, 98, 32, 33, 63, 45, 54, 65, 76}; - int nIdx = 0; - for( SCROW nRow = 0; nRow < 4; nRow++ ) - for( SCCOL nCol = 0; nCol < 4; nCol++ ) - m_pDoc->SetValue(nCol, nRow, 0, static_cast<double>(aVals[nIdx++])); - m_pDoc->SetString(aPos, "=MDETERM(A1:D4)"); - // Following test is conservative in the sense that on Linux x86_64 the error is less that 1.0E-9 - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of MDETERM incorrect for non-singular integer matrix", - -180655.0, m_pDoc->GetValue(aPos), 1.0E-6); - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaErrorPropagation) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - - m_pDoc->InsertTab(0, "Sheet1"); - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - ScAddress aPos, aPos2; - const OUString aTRUE("TRUE"); - const OUString aFALSE("FALSE"); - - aPos.Set(0,0,0);// A1 - m_pDoc->SetValue( aPos, 1.0); - aPos.IncCol(); // B1 - m_pDoc->SetValue( aPos, 2.0); - aPos.IncCol(); - - aPos.IncRow(); // C2 - m_pDoc->SetString( aPos, "=ISERROR(A1:B1+3)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos)); - - aPos.IncRow(); // C3 - m_pDoc->SetString( aPos, "=ISERROR(A1:B1+{3})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos)); - aPos.IncRow(); // C4 - aPos2 = aPos; - aPos2.IncCol(); // D4 - m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, "=ISERROR(A1:B1+{3})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos)); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos2)); - - aPos.IncRow(); // C5 - m_pDoc->SetString( aPos, "=ISERROR({1;\"x\"}+{3;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos)); - aPos.IncRow(); // C6 - aPos2 = aPos; - aPos2.IncCol(); // D6 - m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, "=ISERROR({1;\"x\"}+{3;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos)); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos2)); - - aPos.IncRow(); // C7 - m_pDoc->SetString( aPos, "=ISERROR({\"x\";2}+{3;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos)); - aPos.IncRow(); // C8 - aPos2 = aPos; - aPos2.IncCol(); // D8 - m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, "=ISERROR({\"x\";2}+{3;4})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos)); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos2)); - - aPos.IncRow(); // C9 - m_pDoc->SetString( aPos, "=ISERROR(({1;\"x\"}+{3;4})-{5;6})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos)); - aPos.IncRow(); // C10 - aPos2 = aPos; - aPos2.IncCol(); // D10 - m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, "=ISERROR(({1;\"x\"}+{3;4})-{5;6})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos)); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos2)); - - aPos.IncRow(); // C11 - m_pDoc->SetString( aPos, "=ISERROR(({\"x\";2}+{3;4})-{5;6})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos)); - aPos.IncRow(); // C12 - aPos2 = aPos; - aPos2.IncCol(); // D12 - m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, "=ISERROR(({\"x\";2}+{3;4})-{5;6})"); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, m_pDoc->GetString(aPos)); - CPPUNIT_ASSERT_EQUAL_MESSAGE( aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, m_pDoc->GetString(aPos2)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf97369) -{ - const SCROW TOTAL_ROWS = 330; - const SCROW ROW_RANGE = 10; - const SCROW START1 = 9; - const SCROW END1 = 159; - const SCROW START2 = 169; - const SCROW END2 = 319; - - const double SHIFT1 = 200; - const double SHIFT2 = 400; - - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "tdf97369")); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - - // set up columns A, B, C - for( SCROW i = 0; i < TOTAL_ROWS; ++i ) - { - m_pDoc->SetValue(ScAddress(0, i, 0), i); // A - m_pDoc->SetValue(ScAddress(1, i, 0), i + SHIFT1); // B - m_pDoc->SetValue(ScAddress(2, i, 0), i + SHIFT2); // C - } - - const ColumnTest columnTest( m_pDoc, TOTAL_ROWS, START1, END1, START2, END2 ); - - auto lExpectedinD = [=] (SCROW n) { - return 3.0 * (n-START1) + SHIFT1 + SHIFT2; - }; - columnTest(3, "=SUM(A1:C1)", lExpectedinD); - - auto lExpectedinE = [=] (SCROW ) { - return SHIFT1 + SHIFT2; - }; - columnTest(4, "=SUM(A$1:C$1)", lExpectedinE); - - auto lExpectedinF = [ -#if defined _MSC_VER && !defined __clang__ && __cplusplus <= 201703L - // see <https://developercommunity2.visualstudio.com/t/ - // Lambdas-require-capturing-constant-value/907628> "Lambdas require capturing constant - // values" - ROW_RANGE -#endif - ] (SCROW n) { - return ((2*n + 1 - ROW_RANGE) * ROW_RANGE) / 2.0; - }; - columnTest(5, "=SUM(A1:A10)", lExpectedinF); - - auto lExpectedinG = [] (SCROW n) { - return ((n + 1) * n) / 2.0; - }; - columnTest(6, "=SUM(A$1:A10)", lExpectedinG); - - auto lExpectedinH = [=] (SCROW n) { - return 3.0 * (((2*n + 1 - ROW_RANGE) * ROW_RANGE) / 2) + ROW_RANGE * (SHIFT1 + SHIFT2); - }; - columnTest(7, "=SUM(A1:C10)", lExpectedinH); - - auto lExpectedinI = [=] (SCROW ) { - return 3.0 * (((2*START1 + 1 - ROW_RANGE) * ROW_RANGE) / 2) + ROW_RANGE * (SHIFT1 + SHIFT2); - }; - columnTest(8, "=SUM(A$1:C$10)", lExpectedinI); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf97587) -{ - const SCROW TOTAL_ROWS = 150; - const SCROW ROW_RANGE = 10; - - CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", - m_pDoc->InsertTab (0, "tdf97587")); - - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. - - std::set<SCROW> emptyCells = {0, 100}; - for( SCROW i = 0; i < ROW_RANGE; ++i ) - { - emptyCells.insert(i + TOTAL_ROWS / 3); - emptyCells.insert(i + TOTAL_ROWS); - } - - // set up columns A - for( SCROW i = 0; i < TOTAL_ROWS; ++i ) - { - if( emptyCells.find(i) != emptyCells.end() ) - continue; - m_pDoc->SetValue(ScAddress(0, i, 0), 1.0); - } - - ScDocument aClipDoc(SCDOCMODE_CLIP); - ScMarkData aMark(m_pDoc->GetSheetLimits()); - - ScAddress aPos(1, 0, 0); - m_pDoc->SetString(aPos, "=SUM(A1:A10)"); - - // Copy formula cell to clipboard. - ScClipParam aClipParam(aPos, false); - aMark.SetMarkArea(aPos); - m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMark, false, false); - - // Paste it to first range. - ScRange aDestRange(1, 1, 0, 1, TOTAL_ROWS + ROW_RANGE, 0); - aMark.SetMarkArea(aDestRange); - m_pDoc->CopyFromClip(aDestRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc); - - // Check the formula results in column B. - for( SCROW i = 0; i < TOTAL_ROWS + 1; ++i ) - { - int k = std::count_if( emptyCells.begin(), emptyCells.end(), - [=](SCROW n) { return (i <= n && n < i + ROW_RANGE); } ); - double fExpected = ROW_RANGE - k; - ASSERT_DOUBLES_EQUAL(fExpected, m_pDoc->GetValue(ScAddress(1,i,0))); - } - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf93415) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Sheet1")); - - ScCalcConfig aConfig; - aConfig.SetStringRefSyntax( formula::FormulaGrammar::CONV_XL_R1C1 ); - m_pDoc->SetCalcConfig(aConfig); - m_pDoc->CalcAll(); - - ScAddress aPos(0,0,0); - m_pDoc->SetString(aPos, "=ADDRESS(1;1;;;\"Sheet1\")"); - - // Without the fix in place, this would have failed with - // - Expected: Sheet1!$A$1 - // - Actual : Sheet1.$A$1 - CPPUNIT_ASSERT_EQUAL(OUString("Sheet1!$A$1"), m_pDoc->GetString(aPos)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf132519) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Sheet1")); - - ScCalcConfig aConfig; - aConfig.SetStringRefSyntax( formula::FormulaGrammar::CONV_XL_R1C1 ); - m_pDoc->SetCalcConfig(aConfig); - m_pDoc->CalcAll(); - - m_pDoc->SetString(2, 0, 0, "X"); - m_pDoc->SetString(1, 0, 0, "=CELL(\"ADDRESS\"; C1)"); - m_pDoc->SetString(0, 0, 0, "=INDIRECT(B1)"); - - // Without the fix in place, this test would have failed with - // - Expected: X - // - Actual : #REF! - CPPUNIT_ASSERT_EQUAL(OUString("X"), m_pDoc->GetString(0,0,0)); - - CPPUNIT_ASSERT_EQUAL(OUString("R1C3"), m_pDoc->GetString(1,0,0)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf100818) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Sheet1")); - - //Insert local range name - ScRangeData* pLocal = new ScRangeData( *m_pDoc, "local", "$Sheet1.$A$1"); - std::unique_ptr<ScRangeName> pLocalRangeName(new ScRangeName); - pLocalRangeName->insert(pLocal); - m_pDoc->SetRangeName(0, std::move(pLocalRangeName)); - - m_pDoc->SetValue(0, 0, 0, 1.0); - - CPPUNIT_ASSERT(m_pDoc->InsertTab (1, "Sheet2")); - - m_pDoc->SetString(0, 0, 1, "=INDIRECT(\"Sheet1.local\")"); - - // Without the fix in place, this test would have failed with - // - Expected: 1 - // - Actual : #REF! - CPPUNIT_ASSERT_EQUAL(OUString("1"), m_pDoc->GetString(0,0,1)); - - m_pDoc->DeleteTab(1); - m_pDoc->SetRangeName(0,nullptr); // Delete the names. - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testMatConcat) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test")); - - for (SCCOL nCol = 0; nCol < 10; ++nCol) - { - for (SCROW nRow = 0; nRow < 10; ++nRow) - { - m_pDoc->SetValue(ScAddress(nCol, nRow, 0), nCol*nRow); - } - } - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(0, 12, 9, 21, aMark, "=A1:J10&A1:J10"); - - for (SCCOL nCol = 0; nCol < 10; ++nCol) - { - for (SCROW nRow = 12; nRow < 22; ++nRow) - { - OUString aStr = m_pDoc->GetString(ScAddress(nCol, nRow, 0)); - CPPUNIT_ASSERT_EQUAL(OUString(OUString::number(nCol * (nRow - 12)) + OUString::number(nCol * (nRow - 12))), aStr); - } - } - - { // Data in A12:B16 - std::vector<std::vector<const char*>> aData = { - { "q", "w" }, - { "a", "" }, - { "", "x" }, - { "", "" }, - { "e", "r" }, - }; - - ScAddress aPos(0,11,0); - ScRange aRange = insertRangeData(m_pDoc, aPos, aData); - CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); - } - // Matrix formula in C17:C21 - m_pDoc->InsertMatrixFormula(2, 16, 2, 20, aMark, "=A12:A16&B12:B16"); - // Check proper concatenation including empty cells. - OUString aStr; - ScAddress aPos(2,16,0); - aStr = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL(OUString("qw"),aStr); - aPos.IncRow(); - aStr = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL(OUString("a"),aStr); - aPos.IncRow(); - aStr = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL(OUString("x"),aStr); - aPos.IncRow(); - aStr = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL(OUString(),aStr); - aPos.IncRow(); - aStr = m_pDoc->GetString(aPos); - CPPUNIT_ASSERT_EQUAL(OUString("er"),aStr); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testMatConcatReplication) -{ - // if one of the matrices is a one column or row matrix - // the matrix is replicated across the larger matrix - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test")); - - for (SCCOL nCol = 0; nCol < 10; ++nCol) - { - for (SCROW nRow = 0; nRow < 10; ++nRow) - { - m_pDoc->SetValue(ScAddress(nCol, nRow, 0), nCol*nRow); - } - } - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(0, 12, 9, 21, aMark, "=A1:J10&A1:J1"); - - for (SCCOL nCol = 0; nCol < 10; ++nCol) - { - for (SCROW nRow = 12; nRow < 22; ++nRow) - { - OUString aStr = m_pDoc->GetString(ScAddress(nCol, nRow, 0)); - CPPUNIT_ASSERT_EQUAL(OUString(OUString::number(nCol * (nRow - 12)) + "0"), aStr); - } - } - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testRefR1C1WholeCol) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test")); - - ScAddress aPos(1, 1, 1); - ScCompiler aComp(*m_pDoc, aPos, FormulaGrammar::GRAM_ENGLISH_XL_R1C1); - std::unique_ptr<ScTokenArray> pTokens(aComp.CompileString("=C[10]")); - sc::TokenStringContext aCxt(*m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH); - OUString aFormula = pTokens->CreateString(aCxt, aPos); - - CPPUNIT_ASSERT_EQUAL(OUString("L:L"), aFormula); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testRefR1C1WholeRow) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test")); - - ScAddress aPos(1, 1, 1); - ScCompiler aComp(*m_pDoc, aPos, FormulaGrammar::GRAM_ENGLISH_XL_R1C1); - std::unique_ptr<ScTokenArray> pTokens(aComp.CompileString("=R[3]")); - sc::TokenStringContext aCxt(*m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH); - OUString aFormula = pTokens->CreateString(aCxt, aPos); - - CPPUNIT_ASSERT_EQUAL(OUString("5:5"), aFormula); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testSingleCellCopyColumnLabel) -{ - ScDocOptions aOptions = m_pDoc->GetDocOptions(); - aOptions.SetLookUpColRowNames(true); - m_pDoc->SetDocOptions(aOptions); - m_pDoc->InsertTab(0, "Test"); - - m_pDoc->SetString(0, 0, 0, "a"); - m_pDoc->SetValue(0, 1, 0, 1.0); - m_pDoc->SetValue(0, 2, 0, 2.0); - m_pDoc->SetValue(0, 3, 0, 3.0); - m_pDoc->SetString(1, 1, 0, "='a'"); - - double nVal = m_pDoc->GetValue(1, 1, 0); - ASSERT_DOUBLES_EQUAL(1.0, nVal); - - ScDocument aClipDoc(SCDOCMODE_CLIP); - copyToClip(m_pDoc, ScRange(1, 1, 0), &aClipDoc); - pasteOneCellFromClip(m_pDoc, ScRange(1, 2, 0), &aClipDoc); - nVal = m_pDoc->GetValue(1, 2, 0); - ASSERT_DOUBLES_EQUAL(2.0, nVal); - - m_pDoc->DeleteTab(0); -} - -// Significant whitespace operator intersection in Excel syntax, tdf#96426 -CPPUNIT_TEST_FIXTURE(TestFormula, testIntersectionOpExcel) -{ - CPPUNIT_ASSERT(m_pDoc->InsertTab (0, "Test")); - - ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); - // Horizontal cell range covering C2. - pGlobalNames->insert( new ScRangeData( *m_pDoc, "horz", "$B$2:$D$2")); - // Vertical cell range covering C2. - pGlobalNames->insert( new ScRangeData( *m_pDoc, "vert", "$C$1:$C$3")); - // Data in C2. - m_pDoc->SetValue(2,1,0, 1.0); - - FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1); - - // Choose formula positions that don't intersect with those data ranges. - ScAddress aPos(0,3,0); - m_pDoc->SetString(aPos,"=B2:D2 C1:C3"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A4 intersecting references failed", 1.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - m_pDoc->SetString(aPos,"=horz vert"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A5 intersecting named expressions failed", 1.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - m_pDoc->SetString(aPos,"=(horz vert)*2"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A6 calculating with intersecting named expressions failed", 2.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - m_pDoc->SetString(aPos,"=2*(horz vert)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("A7 calculating with intersecting named expressions failed", 2.0, m_pDoc->GetValue(aPos)); - - m_pDoc->DeleteTab(0); -} - -//Test Subtotal and Aggregate during hide rows #tdf93171 -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncRowsHidden) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - m_pDoc->SetValue(0, 0, 0, 1); //A1 - m_pDoc->SetValue(0, 1, 0, 2); //A2 - m_pDoc->SetValue(0, 2, 0, 4); //A3 - m_pDoc->SetValue(0, 3, 0, 8); //A4 - m_pDoc->SetValue(0, 4, 0, 16); //A5 - m_pDoc->SetValue(0, 5, 0, 32); //A6 - - ScAddress aPos(0,6,0); - m_pDoc->SetString(aPos, "=SUBTOTAL(109; A1:A6)"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 63.0, m_pDoc->GetValue(aPos)); - //Hide row 1 - m_pDoc->SetRowHidden(0, 0, 0, true); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 62.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetRowHidden(0, 0, 0, false); - //Hide row 2 and 3 - m_pDoc->SetRowHidden(1, 2, 0, true); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 57.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetRowHidden(1, 2, 0, false); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 63.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=AGGREGATE(9; 5; A1:A6)"); //9=SUM 5=Ignore only hidden rows - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 63.0, m_pDoc->GetValue(aPos)); - //Hide row 1 - m_pDoc->SetRowHidden(0, 0, 0, true); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 62.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetRowHidden(0, 0, 0, false); - //Hide rows 3 to 5 - m_pDoc->SetRowHidden(2, 4, 0, true); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 35.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetRowHidden(2, 4, 0, false); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 63.0, m_pDoc->GetValue(aPos)); - - m_pDoc->SetString(aPos, "=SUM(A1:A6)"); - m_pDoc->SetRowHidden(2, 4, 0, true); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUM failed", 63.0, m_pDoc->GetValue(aPos)); - - m_pDoc->DeleteTab(0); -} - -// Test COUNTIFS, SUMIFS, AVERAGEIFS in array context. -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUMIFS) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - // Data in A1:B7, query in A9:A11 - std::vector<std::vector<const char*>> aData = { - { "a", "1" }, - { "b", "2" }, - { "c", "4" }, - { "d", "8" }, - { "a", "16" }, - { "b", "32" }, - { "c", "64" }, - { "" }, // {} doesn't work with some compilers - { "a" }, - { "b" }, - { "c" }, - }; - - insertRangeData(m_pDoc, ScAddress(0,0,0), aData); - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - // Matrix formula in C8:C10 with SUMIFS - m_pDoc->InsertMatrixFormula(2, 7, 2, 9, aMark, "=SUMIFS(B1:B7;A1:A7;A9:A11)"); - // Matrix formula in D8:D10 with COUNTIFS - m_pDoc->InsertMatrixFormula(3, 7, 3, 9, aMark, "=COUNTIFS(A1:A7;A9:A11)"); - // Matrix formula in E8:E10 with AVERAGEIFS - m_pDoc->InsertMatrixFormula(4, 7, 4, 9, aMark, "=AVERAGEIFS(B1:B7;A1:A7;A9:A11)"); - - { - // Result B1+B5, B2+B6, B3+B7 and counts and averages. - std::vector<std::vector<const char*>> aCheck = { - { "17", "2", "8.5" }, - { "34", "2", "17" }, - { "68", "2", "34" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(2,7,0, 4,9,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS in array context"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS in array context failed", bGood); - } - - // Matrix formula in G8:G10 with SUMIFS and reference list arrays. - m_pDoc->InsertMatrixFormula(6, 7, 6, 9, aMark, "=SUMIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\")"); - // Matrix formula in H8:H10 with COUNTIFS and reference list arrays. - m_pDoc->InsertMatrixFormula(7, 7, 7, 9, aMark, "=COUNTIFS(OFFSET(B1;ROW(1:3);0;2);\">4\")"); - // Matrix formula in I8:I10 with AVERAGEIFS and reference list arrays. - m_pDoc->InsertMatrixFormula(8, 7, 8, 9, aMark, "=AVERAGEIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\")"); - - { - // Result sums, counts and averages. - std::vector<std::vector<const char*>> aCheck = { - { "0", "0", "#DIV/0!" }, - { "8", "1", "8" }, - { "24", "2", "12" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(6,7,0, 8,9,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS with reference list arrays"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list arrays failed", bGood); - } - - // Matrix formula in K8:K10 with SUMIFS and reference list array condition - // and "normal" data range. - m_pDoc->InsertMatrixFormula(10, 7, 10, 9, aMark, "=SUMIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\")"); - // Matrix formula in L8:L10 with AVERAGEIFS and reference list array - // condition and "normal" data range. - m_pDoc->InsertMatrixFormula(11, 7, 11, 9, aMark, "=AVERAGEIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\")"); - - { - // Result sums and averages. - std::vector<std::vector<const char*>> aCheck = { - { "0", "#DIV/0!" }, - { "2", "2" }, - { "3", "1.5" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(10,7,0, 11,9,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS with reference list array and normal range"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list array and normal range failed", bGood); - } - - // Matrix formula in G18:G20 with SUMIFS and reference list arrays and a - // "normal" criteria range. - m_pDoc->InsertMatrixFormula(6, 17, 6, 19, aMark, "=SUMIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); - // Matrix formula in H18:H20 with COUNTIFS and reference list arrays and a - // "normal" criteria range. - m_pDoc->InsertMatrixFormula(7, 17, 7, 19, aMark, "=COUNTIFS(OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); - // Matrix formula in I18:I20 with AVERAGEIFS and reference list arrays and - // a "normal" criteria range. - m_pDoc->InsertMatrixFormula(8, 17, 8, 19, aMark, "=AVERAGEIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); - - { - // Result sums, counts and averages. - std::vector<std::vector<const char*>> aCheck = { - { "0", "0", "#DIV/0!" }, - { "8", "1", "8" }, - { "16", "1", "16" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(6,17,0, 8,19,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS with reference list arrays and a normal criteria range"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list arrays and a normal criteria range failed", bGood); - } - - // Matrix formula in K18:K20 with SUMIFS and reference list array condition - // and "normal" data range and a "normal" criteria range. - m_pDoc->InsertMatrixFormula(10, 17, 10, 19, aMark, "=SUMIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); - // Matrix formula in L18:L20 with AVERAGEIFS and reference list array - // condition and "normal" data range and a "normal" criteria range. - m_pDoc->InsertMatrixFormula(11, 17, 11, 19, aMark, "=AVERAGEIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); - - { - // Result sums and averages. - std::vector<std::vector<const char*>> aCheck = { - { "0", "#DIV/0!" }, - { "2", "2" }, - { "2", "2" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(10,17,0, 11,19,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS with reference list array and normal data and criteria range"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list array and normal data and criteria range failed", bGood); - } - - // Same, but swapped normal and array criteria. - - // Matrix formula in G28:G30 with SUMIFS and reference list arrays and a - // "normal" criteria range, swapped. - m_pDoc->InsertMatrixFormula(6, 27, 6, 29, aMark, "=SUMIFS(OFFSET(B1;ROW(1:3);0;2);B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); - // Matrix formula in H28:H30 with COUNTIFS and reference list arrays and a - // "normal" criteria range, swapped. - m_pDoc->InsertMatrixFormula(7, 27, 7, 29, aMark, "=COUNTIFS(B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); - // Matrix formula in I28:I30 with AVERAGEIFS and reference list arrays and - // a "normal" criteria range, swapped. - m_pDoc->InsertMatrixFormula(8, 27, 8, 29, aMark, "=AVERAGEIFS(OFFSET(B1;ROW(1:3);0;2);B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); - - { - // Result sums, counts and averages. - std::vector<std::vector<const char*>> aCheck = { - { "0", "0", "#DIV/0!" }, - { "8", "1", "8" }, - { "16", "1", "16" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(6,27,0, 8,29,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS with reference list arrays and a normal criteria range, swapped"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list arrays and a normal criteria range failed, swapped", bGood); - } - - // Matrix formula in K28:K30 with SUMIFS and reference list array condition - // and "normal" data range and a "normal" criteria range, swapped. - m_pDoc->InsertMatrixFormula(10, 27, 10, 29, aMark, "=SUMIFS(B1:B2;B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); - // Matrix formula in L28:L30 with AVERAGEIFS and reference list array - // condition and "normal" data range and a "normal" criteria range, - // swapped. - m_pDoc->InsertMatrixFormula(11, 27, 11, 29, aMark, "=AVERAGEIFS(B1:B2;B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); - - { - // Result sums and averages. - std::vector<std::vector<const char*>> aCheck = { - { "0", "#DIV/0!" }, - { "2", "2" }, - { "2", "2" } - }; - bool bGood = checkOutput(m_pDoc, ScRange(10,27,0, 11,29,0), aCheck, - "SUMIFS, COUNTIFS and AVERAGEIFS with reference list array and normal data and criteria range, swapped"); - CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list array and normal data and criteria range failed, swapped", bGood); - } - - m_pDoc->DeleteTab(0); -} - -// Test that COUNTIF counts properly empty cells if asked to. -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncCOUNTIFEmpty) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - // Data in A1:A9. - std::vector<std::vector<const char*>> aData = { - { "" }, - { "a" }, - { "b" }, - { "c" }, - { "d" }, - { "a" }, - { "" }, - { "b" }, - { "c" } - }; - - insertRangeData(m_pDoc, ScAddress(0,0,0), aData); - - constexpr SCROW maxRow = 20; // so that the unittest is not slow in dbgutil builds - SCROW startRow = 0; - SCROW endRow = maxRow; - SCCOL startCol = 0; - SCCOL endCol = 0; - // ScSortedRangeCache would normally shrink data range to this. - CPPUNIT_ASSERT(m_pDoc->ShrinkToDataArea(0, startCol, startRow, endCol, endRow)); - CPPUNIT_ASSERT_EQUAL(SCROW(8), endRow); - - // But not if matching empty cells. - m_pDoc->SetFormula( ScAddress(10, 0, 0), "=COUNTIFS($A1:$A" + OUString::number(maxRow + 1) + "; \"\")", - formula::FormulaGrammar::GRAM_NATIVE_UI); - CPPUNIT_ASSERT_EQUAL( double(maxRow + 1 - 7), m_pDoc->GetValue(ScAddress(10, 0, 0))); - - m_pDoc->DeleteTab(0); -} - -// Test that COUNTIFS counts properly empty cells if asked to. -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncCOUNTIFSRangeReduce) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - // Data in A1:C9. - std::vector<std::vector<const char*>> aData = { - { "" }, - { "a", "1", "1" }, - { "b", "2", "2" }, - { "c", "4", "3" }, - { "d", "8", "4" }, - { "a", "16", "5" }, - { "" }, - { "b", "", "6" }, - { "c", "64", "7" } - }; - - insertRangeData(m_pDoc, ScAddress(0,0,0), aData); - - constexpr SCROW maxRow = 20; // so that the unittest is not slow in dbgutil builds - ScRange aSubRange( ScAddress( 0, 0, 0 ), ScAddress( 2, maxRow, 0 )); - m_pDoc->GetDataAreaSubrange(aSubRange); - // This is the range the data should be reduced to in ScInterpreter::IterateParametersIfs(). - CPPUNIT_ASSERT_EQUAL( SCROW(1), aSubRange.aStart.Row()); - CPPUNIT_ASSERT_EQUAL( SCROW(8), aSubRange.aEnd.Row()); - - m_pDoc->SetFormula( ScAddress(10, 0, 0), "=COUNTIFS($A1:$A" + OUString::number(maxRow+1) - + "; \"\"; $B1:$B" + OUString::number(maxRow+1) - + "; \"\"; $C1:$C" + OUString::number(maxRow+1) +"; \"\")", - formula::FormulaGrammar::GRAM_NATIVE_UI); - // But it should find out that it can't range reduce and must count all the empty rows. - CPPUNIT_ASSERT_EQUAL( double(maxRow + 1 - 7), m_pDoc->GetValue(ScAddress(10, 0, 0))); - - // Check also with criteria set as cell references, the middle one resulting in matching - // empty cells (which should cause ScInterpreter::IterateParametersIfs() to undo - // the range reduction). This should only match the A8-C8 row, but it also shouldn't crash. - // Matching empty cells using a cell reference needs a formula to set the cell to - // an empty string, plain empty cell wouldn't do, so use K2 for that. - m_pDoc->SetFormula( ScAddress(10, 1, 0), "=\"\"", formula::FormulaGrammar::GRAM_NATIVE_UI ); - m_pDoc->SetFormula( ScAddress(10, 0, 0), "=COUNTIFS($A1:$A" + OUString::number(maxRow+1) - + "; A8; $B1:$B" + OUString::number(maxRow+1) - + "; K2; $C1:$C" + OUString::number(maxRow+1) + "; C8)", - formula::FormulaGrammar::GRAM_NATIVE_UI); - CPPUNIT_ASSERT_EQUAL( double(1), m_pDoc->GetValue(ScAddress(10, 0, 0))); - - m_pDoc->DeleteTab(0); -} - -// Test SUBTOTAL with reference lists in array context. -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncRefListArraySUBTOTAL) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - m_pDoc->SetValue(0,0,0, 1.0); // A1 - m_pDoc->SetValue(0,1,0, 2.0); // A2 - m_pDoc->SetValue(0,2,0, 4.0); // A3 - m_pDoc->SetValue(0,3,0, 8.0); // A4 - m_pDoc->SetValue(0,4,0, 16.0); // A5 - m_pDoc->SetValue(0,5,0, 32.0); // A6 - - // Matrix in B7:B9, individual SUM of A2:A3, A3:A4 and A4:A5 - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - m_pDoc->InsertMatrixFormula(1, 6, 1, 8, aMark, "=SUBTOTAL(9;OFFSET(A1;ROW(1:3);0;2))"); - ScAddress aPos(1,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL SUM for A2:A3 failed", 6.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL SUM for A3:A4 failed", 12.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL SUM for A4:A5 failed", 24.0, m_pDoc->GetValue(aPos)); - - // Matrix in C7:C9, individual AVERAGE of A2:A3, A3:A4 and A4:A5 - m_pDoc->InsertMatrixFormula(2, 6, 2, 8, aMark, "=SUBTOTAL(1;OFFSET(A1;ROW(1:3);0;2))"); - aPos.Set(2,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A2:A3 failed", 3.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A3:A4 failed", 6.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A4:A5 failed", 12.0, m_pDoc->GetValue(aPos)); - - // Matrix in D7:D9, individual MIN of A2:A3, A3:A4 and A4:A5 - m_pDoc->InsertMatrixFormula(3, 6, 3, 8, aMark, "=SUBTOTAL(5;OFFSET(A1;ROW(1:3);0;2))"); - aPos.Set(3,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MIN for A2:A3 failed", 2.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MIN for A3:A4 failed", 4.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MIN for A4:A5 failed", 8.0, m_pDoc->GetValue(aPos)); - - // Matrix in E7:E9, individual MAX of A2:A3, A3:A4 and A4:A5 - m_pDoc->InsertMatrixFormula(4, 6, 4, 8, aMark, "=SUBTOTAL(4;OFFSET(A1;ROW(1:3);0;2))"); - aPos.Set(4,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A2:A3 failed", 4.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A3:A4 failed", 8.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A4:A5 failed", 16.0, m_pDoc->GetValue(aPos)); - - // Matrix in F7:F9, individual STDEV of A2:A3, A3:A4 and A4:A5 - m_pDoc->InsertMatrixFormula(5, 6, 5, 8, aMark, "=SUBTOTAL(7;OFFSET(A1;ROW(1:3);0;2))"); - aPos.Set(5,6,0); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A2:A3 failed", 1.414214, m_pDoc->GetValue(aPos), 1e-6); - aPos.IncRow(); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A3:A4 failed", 2.828427, m_pDoc->GetValue(aPos), 1e-6); - aPos.IncRow(); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A4:A5 failed", 5.656854, m_pDoc->GetValue(aPos), 1e-6); - - // Matrix in G7:G9, individual AVERAGE of A2:A3, A3:A4 and A4:A5 - // Plus two "ordinary" ranges, one before and one after. - m_pDoc->InsertMatrixFormula(6, 6, 6, 8, aMark, "=SUBTOTAL(1;A1:A2;OFFSET(A1;ROW(1:3);0;2);A5:A6)"); - aPos.Set(6,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A1:A2,A2:A3,A5:A6 failed", 9.5, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A1:A2,A3:A4,A5:A6 failed", 10.5, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A1:A2,A4:A5,A5:A6 failed", 12.5, m_pDoc->GetValue(aPos)); - - // Matrix in H7:H9, individual MAX of A2:A3, A3:A4 and A4:A5 - // Plus two "ordinary" ranges, one before and one after. - m_pDoc->InsertMatrixFormula(7, 6, 7, 8, aMark, "=SUBTOTAL(4;A1:A2;OFFSET(A1;ROW(1:3);0;2);A5:A6)"); - aPos.Set(7,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A1:A2,A2:A3,A5:A6 failed", 32.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A1:A2,A3:A4,A5:A6 failed", 32.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A1:A2,A4:A5,A5:A6 failed", 32.0, m_pDoc->GetValue(aPos)); - - // Matrix in I7:I9, individual STDEV of A2:A3, A3:A4 and A4:A5 - // Plus two "ordinary" ranges, one before and one after. - m_pDoc->InsertMatrixFormula(8, 6, 8, 8, aMark, "=SUBTOTAL(7;A1:A2;OFFSET(A1;ROW(1:3);0;2);A5:A6)"); - aPos.Set(8,6,0); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A1:A2,A2:A3,A5:A6 failed", 12.35718, m_pDoc->GetValue(aPos), 1e-5); - aPos.IncRow(); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A1:A2,A3:A4,A5:A6 failed", 11.86170, m_pDoc->GetValue(aPos), 1e-5); - aPos.IncRow(); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A1:A2,A4:A5,A5:A6 failed", 11.55422, m_pDoc->GetValue(aPos), 1e-5); - - // Empty two cells such that they affect two ranges. - m_pDoc->SetString(0,1,0, ""); // A2 - m_pDoc->SetString(0,2,0, ""); // A3 - // Matrix in J7:J9, individual COUNTBLANK of A2:A3, A3:A4 and A4:A5 - m_pDoc->InsertMatrixFormula(9, 6, 9, 8, aMark, "=COUNTBLANK(OFFSET(A1;ROW(1:3);0;2))"); - aPos.Set(9,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("COUNTBLANK for A1:A2,A2:A3,A5:A6 failed", 2.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("COUNTBLANK for A1:A2,A3:A4,A5:A6 failed", 1.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - CPPUNIT_ASSERT_EQUAL_MESSAGE("COUNTBLANK for A1:A2,A4:A5,A5:A6 failed", 0.0, m_pDoc->GetValue(aPos)); - - // Restore these two cell values so we'd catch failures below. - m_pDoc->SetValue(0,1,0, 2.0); // A2 - m_pDoc->SetValue(0,2,0, 4.0); // A3 - // Hide rows 2 to 4. - m_pDoc->SetRowHidden(1,3,0, true); - // Matrix in K7, array of references as OFFSET result. - m_pDoc->InsertMatrixFormula(10, 6, 10, 6, aMark, "=SUM(SUBTOTAL(109;OFFSET(A1;ROW(A1:A7)-ROW(A1);;1)))"); - aPos.Set(10,6,0); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUM SUBTOTAL failed", 49.0, m_pDoc->GetValue(aPos)); - aPos.IncRow(); - // ForceArray in K8, array of references as OFFSET result. - m_pDoc->SetString( aPos, "=SUMPRODUCT(SUBTOTAL(109;OFFSET(A1;ROW(A1:A7)-ROW(A1);;1)))"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMPRODUCT SUBTOTAL failed", 49.0, m_pDoc->GetValue(aPos)); - - m_pDoc->DeleteTab(0); -} - -// tdf#115493 jump commands return the matrix result instead of the reference -// list array. -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncJumpMatrixArrayIF) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - m_pDoc->SetString(0,0,0, "a"); // A1 - std::vector<std::vector<const char*>> aData = { - { "a", "1" }, - { "b", "2" }, - { "a", "4" } - }; // A7:B9 - insertRangeData(m_pDoc, ScAddress(0,6,0), aData); - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - - // Matrix in C10, summing B7,B9 - m_pDoc->InsertMatrixFormula( 2,9, 2,9, aMark, "=SUM(IF(EXACT(A7:A9;A$1);B7:B9;0))"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C10 failed", 5.0, m_pDoc->GetValue(ScAddress(2,9,0))); - - // Matrix in C11, summing B7,B9 - m_pDoc->InsertMatrixFormula( 2,10, 2,10, aMark, - "=SUM(IF(EXACT(OFFSET(A7;0;0):OFFSET(A7;2;0);A$1);OFFSET(A7;0;1):OFFSET(A7;2;1);0))"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C11 failed", 5.0, m_pDoc->GetValue(ScAddress(2,10,0))); - - m_pDoc->DeleteTab(0); -} - -// tdf#123477 OFFSET() returns the matrix result instead of the reference list -// array if result is not used as ReferenceOrRefArray. -CPPUNIT_TEST_FIXTURE(TestFormula, testFuncJumpMatrixArrayOFFSET) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - std::vector<std::vector<const char*>> aData = { - { "abc" }, - { "bcd" }, - { "cde" } - }; - insertRangeData(m_pDoc, ScAddress(0,0,0), aData); // A1:A3 - - ScMarkData aMark(m_pDoc->GetSheetLimits()); - aMark.SelectOneTable(0); - - // Matrix in C5:C7, COLUMN()-3 here offsets by 0 but the entire expression - // is in array/matrix context. - m_pDoc->InsertMatrixFormula( 2,4, 2,6, aMark, "=FIND(\"c\";OFFSET(A1:A3;0;COLUMN()-3))"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C5 failed", 3.0, m_pDoc->GetValue(ScAddress(2,4,0))); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C6 failed", 2.0, m_pDoc->GetValue(ScAddress(2,5,0))); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C7 failed", 1.0, m_pDoc->GetValue(ScAddress(2,6,0))); - - m_pDoc->DeleteTab(0); -} - -// Test iterations with circular chain of references. -CPPUNIT_TEST_FIXTURE(TestFormula, testIterations) -{ - ScDocOptions aDocOpts = m_pDoc->GetDocOptions(); - aDocOpts.SetIter( true ); - m_pDoc->SetDocOptions( aDocOpts ); - - m_pDoc->InsertTab(0, "Test"); - - m_pDoc->SetValue( 0, 0, 0, 0.01 ); // A1 - m_pDoc->SetString( 0, 1, 0, "=A1" ); // A2 - m_pDoc->SetString( 0, 2, 0, "=COS(A2)" ); // A3 - m_pDoc->CalcAll(); - - // Establish reference cycle for the computation of the fixed point of COS() function - m_pDoc->SetString( 0, 0, 0, "=A3" ); // A1 - m_pDoc->CalcAll(); - - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Cell A3 should not have any formula error", FormulaError::NONE, m_pDoc->GetErrCode( ScAddress( 0, 2, 0) ) ); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Iterations to calculate fixed point of cos() failed", 0.7387, m_pDoc->GetValue(0, 2, 0), 1e-4 ); - - // Modify the formula - m_pDoc->SetString( 0, 2, 0, "=COS(A2)+0.001" ); // A3 - m_pDoc->CalcAll(); - - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Cell A3 should not have any formula error after perturbation", FormulaError::NONE, m_pDoc->GetErrCode( ScAddress( 0, 2, 0) ) ); - CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Iterations to calculate perturbed fixed point of cos() failed", 0.7399, m_pDoc->GetValue(0, 2, 0), 1e-4 ); - - m_pDoc->DeleteTab(0); - - aDocOpts.SetIter( false ); - m_pDoc->SetDocOptions( aDocOpts ); -} - -// tdf#111428 CellStoreEvent and its counter used for quick "has a column -// formula cells" must point to the correct column. -CPPUNIT_TEST_FIXTURE(TestFormula, testInsertColCellStoreEventSwap) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - m_pDoc->SetValue( 0,0,0, 1.0 ); // A1 - m_pDoc->SetString( 1,0,0, "=A1" ); // B1 - // Insert column left of B - m_pDoc->InsertCol( ScRange(1,0,0, 1,m_pDoc->MaxRow(),0)); - ScAddress aPos(2,0,0); // C1, new formula position - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Should be formula cell having value", 1.0, m_pDoc->GetValue(aPos)); - // After having swapped in an empty column, editing or adding a formula - // cell has to use the correct store context. To test this, - // ScDocument::SetString() can't be used as it doesn't expose the behavior - // in question, use ScDocFunc::SetFormulaCell() instead which actually is - // also called when editing a cell and creating a formula cell. - ScFormulaCell* pCell = new ScFormulaCell(*m_pDoc, aPos, "=A1+1"); - ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc(); - rDocFunc.SetFormulaCell( aPos, pCell, false); // C1, change formula - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Initial calculation failed", 2.0, m_pDoc->GetValue(aPos)); - m_pDoc->SetValue( 0,0,0, 2.0 ); // A1, change value - CPPUNIT_ASSERT_EQUAL_MESSAGE( "Recalculation failed", 3.0, m_pDoc->GetValue(aPos)); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testTdf147398) -{ - m_pDoc->InsertTab(0, "Test"); - - m_pDoc->SetString(0, 0, 0, "=SUM(A3:A5)"); - m_pDoc->SetString(0, 1, 0, "=COUNT(A3:A5)"); - m_pDoc->SetString(1, 0, 0, "=SUM(B3:B5)"); - m_pDoc->SetString(1, 1, 0, "=COUNT(B3:B5)"); - m_pDoc->SetString(2, 0, 0, "=SUM(C3:C5)"); - m_pDoc->SetString(2, 1, 0, "=COUNT(C3:C5)"); - m_pDoc->SetString(3, 0, 0, "=SUM(D3:D5)"); - m_pDoc->SetString(3, 1, 0, "=COUNT(D3:D5)"); - m_pDoc->SetString(4, 0, 0, "=SUM(E3:E5)"); - m_pDoc->SetString(4, 1, 0, "=COUNT(E3:E5)"); - - m_pDoc->SetString(5, 0, 0, "=SUM(A1:E1)/SUM(A2:E2)"); - - m_pDoc->SetValue(ScAddress(0, 2, 0), 50.0); - m_pDoc->SetValue(ScAddress(0, 3, 0), 100.0); - - CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(75.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); - - m_pDoc->SetValue(ScAddress(1, 2, 0), 150.0); - m_pDoc->SetValue(ScAddress(1, 3, 0), 200.0); - - CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); - CPPUNIT_ASSERT_EQUAL(125.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); - - m_pDoc->SetValue(ScAddress(2, 2, 0), 250.0); - m_pDoc->SetValue(ScAddress(2, 3, 0), 300.0); - - CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); - CPPUNIT_ASSERT_EQUAL(550.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); - CPPUNIT_ASSERT_EQUAL(175.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); - - m_pDoc->SetValue(ScAddress(3, 2, 0), 350.0); - m_pDoc->SetValue(ScAddress(3, 3, 0), 400.0); - - CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); - CPPUNIT_ASSERT_EQUAL(550.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); - CPPUNIT_ASSERT_EQUAL(750.0, m_pDoc->GetValue(ScAddress(3, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(3, 1, 0))); - CPPUNIT_ASSERT_EQUAL(225.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); - - m_pDoc->SetValue(ScAddress(4, 2, 0), 450.0); - m_pDoc->SetValue(ScAddress(4, 3, 0), 500.0); - - CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); - CPPUNIT_ASSERT_EQUAL(550.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); - CPPUNIT_ASSERT_EQUAL(750.0, m_pDoc->GetValue(ScAddress(3, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(3, 1, 0))); - CPPUNIT_ASSERT_EQUAL(950.0, m_pDoc->GetValue(ScAddress(4, 0, 0))); - CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(4, 1, 0))); - CPPUNIT_ASSERT_EQUAL(275.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); - - m_pDoc->DeleteTab(0); -} - -CPPUNIT_TEST_FIXTURE(TestFormula, testFormulaAfterDeleteRows) -{ - sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. - m_pDoc->InsertTab(0, "Test"); - - // Fill A1:A70000 with 1.0 - std::vector<double> aVals(70000, 1.0); - m_pDoc->SetValues(ScAddress(0, 0, 0), aVals); - // Set A70001 with formula "=SUM(A1:A70000)" - m_pDoc->SetString(0, 70000, 0, "=SUM(A1:A70000)"); - - // Delete rows 2:69998 - m_pDoc->DeleteRow(ScRange(0, 1, 0, m_pDoc->MaxCol(), 69997, 0)); - - const ScAddress aPos(0, 3, 0); // A4 - CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in A4.", OUString("=SUM(A1:A3)"), m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); - - ASSERT_DOUBLES_EQUAL_MESSAGE("Wrong value at A4", 3.0, m_pDoc->GetValue(aPos)); -} - CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/ucalc_formula2.cxx b/sc/qa/unit/ucalc_formula2.cxx new file mode 100644 index 000000000000..e943fdc157c7 --- /dev/null +++ b/sc/qa/unit/ucalc_formula2.cxx @@ -0,0 +1,4613 @@ +/* -*- 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 <clipparam.hxx> +#include <scopetools.hxx> +#include <formulacell.hxx> +#include <docfunc.hxx> +#include <tokenstringcontext.hxx> +#include <dbdata.hxx> +#include <scmatrix.hxx> +#include <docoptio.hxx> +#include <externalrefmgr.hxx> +#include <undomanager.hxx> +#include <broadcast.hxx> + +#include <svl/broadcast.hxx> +#include <sfx2/docfile.hxx> + +#include <memory> +#include <functional> +#include <set> +#include <algorithm> +#include <vector> + +using namespace formula; + +namespace +{ +ScRange getCachedRange(const ScExternalRefCache::TableTypeRef& pCacheTab) +{ + ScRange aRange; + + vector<SCROW> aRows; + pCacheTab->getAllRows(aRows); + bool bFirst = true; + for (const SCROW nRow : aRows) + { + vector<SCCOL> aCols; + pCacheTab->getAllCols(nRow, aCols); + for (const SCCOL nCol : aCols) + { + if (bFirst) + { + aRange.aStart = ScAddress(nCol, nRow, 0); + aRange.aEnd = aRange.aStart; + bFirst = false; + } + else + { + if (nCol < aRange.aStart.Col()) + aRange.aStart.SetCol(nCol); + else if (aRange.aEnd.Col() < nCol) + aRange.aEnd.SetCol(nCol); + + if (nRow < aRange.aStart.Row()) + aRange.aStart.SetRow(nRow); + else if (aRange.aEnd.Row() < nRow) + aRange.aEnd.SetRow(nRow); + } + } + } + return aRange; +} + +struct StrStrCheck +{ + const char* pVal; + const char* pRes; +}; + +class ColumnTest +{ + ScDocument* m_pDoc; + + const SCROW m_nTotalRows; + const SCROW m_nStart1; + const SCROW m_nEnd1; + const SCROW m_nStart2; + const SCROW m_nEnd2; + +public: + ColumnTest(ScDocument* pDoc, SCROW nTotalRows, SCROW nStart1, SCROW nEnd1, SCROW nStart2, + SCROW nEnd2) + : m_pDoc(pDoc) + , m_nTotalRows(nTotalRows) + , m_nStart1(nStart1) + , m_nEnd1(nEnd1) + , m_nStart2(nStart2) + , m_nEnd2(nEnd2) + { + } + + void operator()(SCCOL nColumn, const OUString& rFormula, + std::function<double(SCROW)> const& lExpected) const + { + ScDocument aClipDoc(SCDOCMODE_CLIP); + ScMarkData aMark(m_pDoc->GetSheetLimits()); + + ScAddress aPos(nColumn, m_nStart1, 0); + m_pDoc->SetString(aPos, rFormula); + ASSERT_DOUBLES_EQUAL(lExpected(m_nStart1), m_pDoc->GetValue(aPos)); + + // Copy formula cell to clipboard. + ScClipParam aClipParam(aPos, false); + aMark.SetMarkArea(aPos); + m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMark, false, false); + + // Paste it to first range. + InsertDeleteFlags nFlags = InsertDeleteFlags::CONTENTS; + ScRange aDestRange(nColumn, m_nStart1, 0, nColumn, m_nEnd1, 0); + aMark.SetMarkArea(aDestRange); + m_pDoc->CopyFromClip(aDestRange, aMark, nFlags, nullptr, &aClipDoc); + + // Paste it second range. + aDestRange = ScRange(nColumn, m_nStart2, 0, nColumn, m_nEnd2, 0); + aMark.SetMarkArea(aDestRange); + m_pDoc->CopyFromClip(aDestRange, aMark, nFlags, nullptr, &aClipDoc); + + // Check the formula results for passed column. + for (SCROW i = 0; i < m_nTotalRows; ++i) + { + if (!((m_nStart1 <= i && i <= m_nEnd1) || (m_nStart2 <= i && i <= m_nEnd2))) + continue; + double fExpected = lExpected(i); + ASSERT_DOUBLES_EQUAL(fExpected, m_pDoc->GetValue(ScAddress(nColumn, i, 0))); + } + } +}; + +} //namespace + +class TestFormula2 : public ScUcalcTestBase +{ +protected: + template <size_t DataSize, size_t FormulaSize, int Type> + void runTestMATCH(ScDocument* pDoc, const char* aData[DataSize], + const StrStrCheck aChecks[FormulaSize]); + template <size_t DataSize, size_t FormulaSize, int Type> + void runTestHorizontalMATCH(ScDocument* pDoc, const char* aData[DataSize], + const StrStrCheck aChecks[FormulaSize]); + + void testExtRefFuncT(ScDocument* pDoc, ScDocument& rExtDoc); + void testExtRefFuncOFFSET(ScDocument* pDoc, ScDocument& rExtDoc); + void testExtRefFuncVLOOKUP(ScDocument* pDoc, ScDocument& rExtDoc); + void testExtRefConcat(ScDocument* pDoc, ScDocument& rExtDoc); +}; + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncIF) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "Formula"); + + m_pDoc->SetString(ScAddress(0, 0, 0), "=IF(B1=2;\"two\";\"not two\")"); + CPPUNIT_ASSERT_EQUAL(OUString("not two"), m_pDoc->GetString(ScAddress(0, 0, 0))); + m_pDoc->SetValue(ScAddress(1, 0, 0), 2.0); + CPPUNIT_ASSERT_EQUAL(OUString("two"), m_pDoc->GetString(ScAddress(0, 0, 0))); + m_pDoc->SetValue(ScAddress(1, 0, 0), 3.0); + CPPUNIT_ASSERT_EQUAL(OUString("not two"), m_pDoc->GetString(ScAddress(0, 0, 0))); + + // Test nested IF in array/matrix if the nested IF condition is a scalar. + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(0, 2, 1, 2, aMark, "=IF({1;0};IF(1;23);42)"); + // Results must be 23 and 42. + CPPUNIT_ASSERT_EQUAL(23.0, m_pDoc->GetValue(ScAddress(0, 2, 0))); + CPPUNIT_ASSERT_EQUAL(42.0, m_pDoc->GetValue(ScAddress(1, 2, 0))); + + // Test nested IF in array/matrix if nested IF conditions are range + // references, data in A5:C8, matrix formula in D4 so there is no + // implicit intersection between formula and ranges. + { + std::vector<std::vector<const char*>> aData + = { { "1", "1", "16" }, { "0", "1", "32" }, { "1", "0", "64" }, { "0", "0", "128" } }; + ScAddress aPos(0, 4, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + } + m_pDoc->InsertMatrixFormula(3, 3, 3, 3, aMark, "=SUM(IF(A5:A8;IF(B5:B8;C5:C8;0);0))"); + // Result must be 16, only the first row matches all criteria. + CPPUNIT_ASSERT_EQUAL(16.0, m_pDoc->GetValue(ScAddress(3, 3, 0))); + + // A11:B11 + // Test nested IF in array/matrix if the nested IF has no Else path. + m_pDoc->InsertMatrixFormula(0, 10, 1, 10, aMark, "=IF(IF({1;0};12);34;56)"); + // Results must be 34 and 56. + CPPUNIT_ASSERT_EQUAL(34.0, m_pDoc->GetValue(ScAddress(0, 10, 0))); + CPPUNIT_ASSERT_EQUAL(56.0, m_pDoc->GetValue(ScAddress(1, 10, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncCHOOSE) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "Formula"); + + m_pDoc->SetString(ScAddress(0, 0, 0), "=CHOOSE(B1;\"one\";\"two\";\"three\")"); + FormulaError nError = m_pDoc->GetErrCode(ScAddress(0, 0, 0)); + CPPUNIT_ASSERT_MESSAGE("Formula result should be an error since B1 is still empty.", + nError != FormulaError::NONE); + m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); + CPPUNIT_ASSERT_EQUAL(OUString("one"), m_pDoc->GetString(ScAddress(0, 0, 0))); + m_pDoc->SetValue(ScAddress(1, 0, 0), 2.0); + CPPUNIT_ASSERT_EQUAL(OUString("two"), m_pDoc->GetString(ScAddress(0, 0, 0))); + m_pDoc->SetValue(ScAddress(1, 0, 0), 3.0); + CPPUNIT_ASSERT_EQUAL(OUString("three"), m_pDoc->GetString(ScAddress(0, 0, 0))); + m_pDoc->SetValue(ScAddress(1, 0, 0), 4.0); + nError = m_pDoc->GetErrCode(ScAddress(0, 0, 0)); + CPPUNIT_ASSERT_MESSAGE("Formula result should be an error due to out-of-bound input..", + nError != FormulaError::NONE); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncIFERROR) +{ + // IFERROR/IFNA (fdo#56124) + + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + // Empty A1:A39 first. + clearRange(m_pDoc, ScRange(0, 0, 0, 0, 40, 0)); + + // Raw data (rows 1 through 12) + const char* aData[] = { "1", "e", "=SQRT(4)", "=SQRT(-2)", "=A4", "=1/0", + "=NA()", "bar", "4", "gee", "=1/0", "23" }; + + SCROW nRows = SAL_N_ELEMENTS(aData); + for (SCROW i = 0; i < nRows; ++i) + m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i])); + + printRange(m_pDoc, ScRange(0, 0, 0, 0, nRows - 1, 0), "data range for IFERROR/IFNA"); + + // formulas and results + static const struct + { + const char* pFormula; + const char* pResult; + } aChecks[] = { + { "=IFERROR(A1;9)", "1" }, + { "=IFERROR(A2;9)", "e" }, + { "=IFERROR(A3;9)", "2" }, + { "=IFERROR(A4;-7)", "-7" }, + { "=IFERROR(A5;-7)", "-7" }, + { "=IFERROR(A6;-7)", "-7" }, + { "=IFERROR(A7;-7)", "-7" }, + { "=IFNA(A6;9)", "#DIV/0!" }, + { "=IFNA(A7;-7)", "-7" }, + { "=IFNA(VLOOKUP(\"4\";A8:A10;1;0);-2)", "4" }, + { "=IFNA(VLOOKUP(\"fop\";A8:A10;1;0);-2)", "-2" }, + { "{=IFERROR(3*A11:A12;1998)}[0]", + "1998" }, // um... this is not the correct way to insert a + { "{=IFERROR(3*A11:A12;1998)}[1]", "69" } // matrix formula, just a place holder, see below + }; + + nRows = SAL_N_ELEMENTS(aChecks); + for (SCROW i = 0; i < nRows - 2; ++i) + { + SCROW nRow = 20 + i; + m_pDoc->SetString(0, nRow, 0, OUString::createFromAscii(aChecks[i].pFormula)); + } + + // Create a matrix range in last two rows of the range above, actual data + // of the placeholders. + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(0, 20 + nRows - 2, 0, 20 + nRows - 1, aMark, + "=IFERROR(3*A11:A12;1998)"); + + m_pDoc->CalcAll(); + + for (SCROW i = 0; i < nRows; ++i) + { + SCROW nRow = 20 + i; + OUString aResult = m_pDoc->GetString(0, nRow, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aChecks[i].pFormula, + OUString::createFromAscii(aChecks[i].pResult), aResult); + } + + const SCCOL nCols = 3; + std::vector<std::vector<const char*>> aData2 + = { { "1", "2", "3" }, { "4", "=1/0", "6" }, { "7", "8", "9" } }; + const char* aCheck2[][nCols] = { { "1", "2", "3" }, { "4", "Error", "6" }, { "7", "8", "9" } }; + + // Data in C1:E3 + ScAddress aPos(2, 0, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData2); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + + // Array formula in F4:H6 + const SCROW nElems2 = SAL_N_ELEMENTS(aCheck2); + const SCCOL nStartCol = aPos.Col() + nCols; + const SCROW nStartRow = aPos.Row() + nElems2; + m_pDoc->InsertMatrixFormula(nStartCol, nStartRow, nStartCol + nCols - 1, + nStartRow + nElems2 - 1, aMark, "=IFERROR(C1:E3;\"Error\")"); + + m_pDoc->CalcAll(); + + for (SCCOL nCol = nStartCol; nCol < nStartCol + nCols; ++nCol) + { + for (SCROW nRow = nStartRow; nRow < nStartRow + nElems2; ++nRow) + { + OUString aResult = m_pDoc->GetString(nCol, nRow, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "IFERROR array result", + OUString::createFromAscii(aCheck2[nRow - nStartRow][nCol - nStartCol]), aResult); + } + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncSHEET) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(SC_TAB_APPEND, "test1")); + + m_pDoc->SetString(0, 0, 0, "=SHEETS()"); + m_pDoc->CalcFormulaTree(false, false); + double original = m_pDoc->GetValue(0, 0, 0); + + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "result of SHEETS() should equal the number of sheets, but doesn't.", + static_cast<SCTAB>(original), m_pDoc->GetTableCount()); + + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(SC_TAB_APPEND, "test2")); + + double modified = m_pDoc->GetValue(0, 0, 0); + ASSERT_DOUBLES_EQUAL_MESSAGE("result of SHEETS() did not get updated after sheet insertion.", + 1.0, modified - original); + + SCTAB nTabCount = m_pDoc->GetTableCount(); + m_pDoc->DeleteTab(--nTabCount); + + modified = m_pDoc->GetValue(0, 0, 0); + ASSERT_DOUBLES_EQUAL_MESSAGE("result of SHEETS() did not get updated after sheet removal.", 0.0, + modified - original); + + m_pDoc->DeleteTab(--nTabCount); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncNOW) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + double val = 1; + m_pDoc->SetValue(0, 0, 0, val); + m_pDoc->SetString(0, 1, 0, "=IF(A1>0;NOW();0"); + double now1 = m_pDoc->GetValue(0, 1, 0); + CPPUNIT_ASSERT_MESSAGE("Value of NOW() should be positive.", now1 > 0.0); + + val = 0; + m_pDoc->SetValue(0, 0, 0, val); + m_pDoc->CalcFormulaTree(false, false); + double zero = m_pDoc->GetValue(0, 1, 0); + ASSERT_DOUBLES_EQUAL_MESSAGE("Result should equal the 3rd parameter of IF, which is zero.", 0.0, + zero); + + val = 1; + m_pDoc->SetValue(0, 0, 0, val); + m_pDoc->CalcFormulaTree(false, false); + double now2 = m_pDoc->GetValue(0, 1, 0); + CPPUNIT_ASSERT_MESSAGE("Result should be the value of NOW() again.", (now2 - now1) >= 0.0); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncNUMBERVALUE) +{ + // NUMBERVALUE fdo#57180 + + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + // Empty A1:A39 first. + clearRange(m_pDoc, ScRange(0, 0, 0, 0, 40, 0)); + + // Raw data (rows 1 through 6) + const char* aData[] + = { "1ag9a9b9", "1ag34 5g g6 78b9%%", "1 234d56E-2", "d4", "54.4", "1a2b3e1%" }; + + SCROW nRows = SAL_N_ELEMENTS(aData); + for (SCROW i = 0; i < nRows; ++i) + m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i])); + + printRange(m_pDoc, ScRange(0, 0, 0, 0, nRows - 1, 0), "data range for NUMBERVALUE"); + + // formulas and results + static const struct + { + const char* pFormula; + const char* pResult; + } aChecks[] = { { "=NUMBERVALUE(A1;\"b\";\"ag\")", "199.9" }, + { "=NUMBERVALUE(A2;\"b\";\"ag\")", "134.56789" }, + { "=NUMBERVALUE(A2;\"b\";\"g\")", "#VALUE!" }, + { "=NUMBERVALUE(A3;\"d\")", "12.3456" }, + { "=NUMBERVALUE(A4;\"d\";\"foo\")", "0.4" }, + { "=NUMBERVALUE(A4;)", "Err:502" }, + { "=NUMBERVALUE(A5;)", "Err:502" }, + { "=NUMBERVALUE(A6;\"b\";\"a\")", "1.23" } }; + + nRows = SAL_N_ELEMENTS(aChecks); + for (SCROW i = 0; i < nRows; ++i) + { + SCROW nRow = 20 + i; + m_pDoc->SetString(0, nRow, 0, OUString::createFromAscii(aChecks[i].pFormula)); + } + m_pDoc->CalcAll(); + + for (SCROW i = 0; i < nRows; ++i) + { + SCROW nRow = 20 + i; + OUString aResult = m_pDoc->GetString(0, nRow, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aChecks[i].pFormula, + OUString::createFromAscii(aChecks[i].pResult), aResult); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncLEN) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "Formula"); + + // Leave A1:A3 empty, and insert an array of LEN in B1:B3 that references + // these empty cells. + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(1, 0, 1, 2, aMark, "=LEN(A1:A3)"); + + ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1, 0, 0)); + CPPUNIT_ASSERT(pFC); + CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should be a matrix origin.", ScMatrixMode::Formula, + pFC->GetMatrixFlag()); + + // This should be a 1x3 matrix. + SCCOL nCols = -1; + SCROW nRows = -1; + pFC->GetMatColsRows(nCols, nRows); + CPPUNIT_ASSERT_EQUAL(static_cast<SCCOL>(1), nCols); + CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), nRows); + + // LEN value should be 0 for an empty cell. + CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); + CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1, 2, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncLOOKUP) +{ + FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1); + + m_pDoc->InsertTab(0, "Test"); + + // Raw data + const char* aData[][2] = { + { "=CONCATENATE(\"A\")", "1" }, + { "=CONCATENATE(\"B\")", "2" }, + { "=CONCATENATE(\"C\")", "3" }, + { nullptr, nullptr } // terminator + }; + + // Insert raw data into A1:B3. + for (SCROW i = 0; aData[i][0]; ++i) + { + m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i][0])); + m_pDoc->SetString(1, i, 0, OUString::createFromAscii(aData[i][1])); + } + + const char* aData2[][2] = { + { "A", "=LOOKUP(RC[-1];R1C1:R3C1;R1C2:R3C2)" }, + { "B", "=LOOKUP(RC[-1];R1C1:R3C1;R1C2:R3C2)" }, + { "C", "=LOOKUP(RC[-1];R1C1:R3C1;R1C2:R3C2)" }, + { nullptr, nullptr } // terminator + }; + + // Insert check formulas into A5:B7. + for (SCROW i = 0; aData2[i][0]; ++i) + { + m_pDoc->SetString(0, i + 4, 0, OUString::createFromAscii(aData2[i][0])); + m_pDoc->SetString(1, i + 4, 0, OUString::createFromAscii(aData2[i][1])); + } + + printRange(m_pDoc, ScRange(0, 4, 0, 1, 6, 0), "Data range for LOOKUP."); + + // Values for B5:B7 should be 1, 2, and 3. + CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should not have an error code.", 0, + static_cast<int>(m_pDoc->GetErrCode(ScAddress(1, 4, 0)))); + CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should not have an error code.", 0, + static_cast<int>(m_pDoc->GetErrCode(ScAddress(1, 5, 0)))); + CPPUNIT_ASSERT_EQUAL_MESSAGE("This formula should not have an error code.", 0, + static_cast<int>(m_pDoc->GetErrCode(ScAddress(1, 6, 0)))); + + ASSERT_DOUBLES_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1, 4, 0))); + ASSERT_DOUBLES_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 5, 0))); + ASSERT_DOUBLES_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1, 6, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncLOOKUParrayWithError) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, "Test"); + + std::vector<std::vector<const char*>> aData = { { "x", "y", "z" }, { "a", "b", "c" } }; + insertRangeData(m_pDoc, ScAddress(2, 1, 0), aData); // C2:E3 + m_pDoc->SetString(0, 0, 0, "=LOOKUP(2;1/(C2:E2<>\"\");C3:E3)"); // A1 + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for last column.", OUString("c"), + m_pDoc->GetString(0, 0, 0)); + m_pDoc->SetString(4, 1, 0, ""); // E2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for second last column.", OUString("b"), + m_pDoc->GetString(0, 0, 0)); + + m_pDoc->SetString(6, 1, 0, "one"); // G2 + m_pDoc->SetString(6, 5, 0, "two"); // G6 + // Creates an interim array {1,#DIV/0!,#DIV/0!,#DIV/0!,1,#DIV/0!,#DIV/0!,#DIV/0!} + m_pDoc->SetString(7, 8, 0, "=LOOKUP(2;1/(NOT(ISBLANK(G2:G9)));G2:G9)"); // H9 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for last row.", OUString("two"), + m_pDoc->GetString(7, 8, 0)); + + // Lookup on empty range. + m_pDoc->SetString(9, 8, 0, "=LOOKUP(2;1/(NOT(ISBLANK(I2:I9)));I2:I9)"); // J9 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find no match.", OUString("#N/A"), + m_pDoc->GetString(9, 8, 0)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf141146) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, "Test1"); + m_pDoc->InsertTab(1, "Test2"); + + std::vector<std::vector<const char*>> aData + = { { "k1", "value1" }, { "k2", "value2" }, { "k3", "value3" } }; + + insertRangeData(m_pDoc, ScAddress(0, 1, 1), aData); // A2:B4 + m_pDoc->SetString(4, 0, 1, "k2"); // E1 + + m_pDoc->SetString(4, 1, 1, "=LOOKUP(1;1/(A$2:A$4=E$1);1)"); + m_pDoc->SetString(4, 2, 1, "=LOOKUP(E1;A$2:A$4;B2:B4)"); + m_pDoc->SetString(4, 3, 1, "=LOOKUP(1;1/(A$2:A$4=E$1);B2:B4)"); + + // Without the fix in place, this test would have failed with + // - Expected: #N/A + // - Actual : + CPPUNIT_ASSERT_EQUAL(OUString("#N/A"), m_pDoc->GetString(4, 1, 1)); + CPPUNIT_ASSERT_EQUAL(OUString("value2"), m_pDoc->GetString(4, 2, 1)); + CPPUNIT_ASSERT_EQUAL(OUString("value2"), m_pDoc->GetString(4, 3, 1)); + + m_pDoc->DeleteTab(1); + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncVLOOKUP) +{ + // VLOOKUP + + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + // Clear A1:F40. + clearRange(m_pDoc, ScRange(0, 0, 0, 5, 39, 0)); + + // Raw data + const char* aData[][2] = { + { "Key", "Val" }, { "10", "3" }, { "20", "4" }, { "30", "5" }, + { "40", "6" }, { "50", "7" }, { "60", "8" }, { "70", "9" }, + { "B", "10" }, { "B", "11" }, { "C", "12" }, { "D", "13" }, + { "E", "14" }, { "F", "15" }, { nullptr, nullptr } // terminator + }; + + // Insert raw data into A1:B14. + for (SCROW i = 0; aData[i][0]; ++i) + { + m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i][0])); + m_pDoc->SetString(1, i, 0, OUString::createFromAscii(aData[i][1])); + } + + printRange(m_pDoc, ScRange(0, 0, 0, 1, 13, 0), "raw data for VLOOKUP"); + + // Formula data + static const struct + { + const char* pLookup; + const char* pFormula; + const char* pRes; + } aChecks[] = { { "Lookup", "Formula", nullptr }, + { "12", "=VLOOKUP(D2;A2:B14;2;1)", "3" }, + { "29", "=VLOOKUP(D3;A2:B14;2;1)", "4" }, + { "31", "=VLOOKUP(D4;A2:B14;2;1)", "5" }, + { "45", "=VLOOKUP(D5;A2:B14;2;1)", "6" }, + { "56", "=VLOOKUP(D6;A2:B14;2;1)", "7" }, + { "65", "=VLOOKUP(D7;A2:B14;2;1)", "8" }, + { "78", "=VLOOKUP(D8;A2:B14;2;1)", "9" }, + { "Andy", "=VLOOKUP(D9;A2:B14;2;1)", "#N/A" }, + { "Bruce", "=VLOOKUP(D10;A2:B14;2;1)", "11" }, + { "Charlie", "=VLOOKUP(D11;A2:B14;2;1)", "12" }, + { "David", "=VLOOKUP(D12;A2:B14;2;1)", "13" }, + { "Edward", "=VLOOKUP(D13;A2:B14;2;1)", "14" }, + { "Frank", "=VLOOKUP(D14;A2:B14;2;1)", "15" }, + { "Henry", "=VLOOKUP(D15;A2:B14;2;1)", "15" }, + { "100", "=VLOOKUP(D16;A2:B14;2;1)", "9" }, + { "1000", "=VLOOKUP(D17;A2:B14;2;1)", "9" }, + { "Zena", "=VLOOKUP(D18;A2:B14;2;1)", "15" } }; + + // Insert formula data into D1:E18. + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + m_pDoc->SetString(3, i, 0, OUString::createFromAscii(aChecks[i].pLookup)); + m_pDoc->SetString(4, i, 0, OUString::createFromAscii(aChecks[i].pFormula)); + } + m_pDoc->CalcAll(); + printRange(m_pDoc, ScRange(3, 0, 0, 4, 17, 0), "formula data for VLOOKUP"); + + // Verify results. + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + if (i == 0) + // Skip the header row. + continue; + + OUString aRes = m_pDoc->GetString(4, i, 0); + bool bGood = aRes.equalsAscii(aChecks[i].pRes); + if (!bGood) + { + cerr << "row " << (i + 1) << ": lookup value='" << aChecks[i].pLookup << "' expected='" + << aChecks[i].pRes << "' actual='" << aRes << "'" << endl; + CPPUNIT_ASSERT_MESSAGE("Unexpected result for VLOOKUP", false); + } + } + + // Clear the sheet and start over. + clearSheet(m_pDoc, 0); + + // Lookup on sorted data interspersed with empty cells. + + // A1:B8 is the search range. + m_pDoc->SetValue(ScAddress(0, 2, 0), 1.0); + m_pDoc->SetValue(ScAddress(0, 4, 0), 2.0); + m_pDoc->SetValue(ScAddress(0, 7, 0), 4.0); + m_pDoc->SetString(ScAddress(1, 2, 0), "One"); + m_pDoc->SetString(ScAddress(1, 4, 0), "Two"); + m_pDoc->SetString(ScAddress(1, 7, 0), "Four"); + + // D1:D5 contain match values. + m_pDoc->SetValue(ScAddress(3, 0, 0), 1.0); + m_pDoc->SetValue(ScAddress(3, 1, 0), 2.0); + m_pDoc->SetValue(ScAddress(3, 2, 0), 3.0); + m_pDoc->SetValue(ScAddress(3, 3, 0), 4.0); + m_pDoc->SetValue(ScAddress(3, 4, 0), 5.0); + + // E1:E5 contain formulas. + m_pDoc->SetString(ScAddress(4, 0, 0), "=VLOOKUP(D1;$A$1:$B$8;2)"); + m_pDoc->SetString(ScAddress(4, 1, 0), "=VLOOKUP(D2;$A$1:$B$8;2)"); + m_pDoc->SetString(ScAddress(4, 2, 0), "=VLOOKUP(D3;$A$1:$B$8;2)"); + m_pDoc->SetString(ScAddress(4, 3, 0), "=VLOOKUP(D4;$A$1:$B$8;2)"); + m_pDoc->SetString(ScAddress(4, 4, 0), "=VLOOKUP(D5;$A$1:$B$8;2)"); + m_pDoc->CalcAll(); + + // Check the formula results in E1:E5. + CPPUNIT_ASSERT_EQUAL(OUString("One"), m_pDoc->GetString(ScAddress(4, 0, 0))); + CPPUNIT_ASSERT_EQUAL(OUString("Two"), m_pDoc->GetString(ScAddress(4, 1, 0))); + CPPUNIT_ASSERT_EQUAL(OUString("Two"), m_pDoc->GetString(ScAddress(4, 2, 0))); + CPPUNIT_ASSERT_EQUAL(OUString("Four"), m_pDoc->GetString(ScAddress(4, 3, 0))); + CPPUNIT_ASSERT_EQUAL(OUString("Four"), m_pDoc->GetString(ScAddress(4, 4, 0))); + + // Start over again. + clearSheet(m_pDoc, 0); + + // Set A,B,...,G to A1:A7. + m_pDoc->SetString(ScAddress(0, 0, 0), "A"); + m_pDoc->SetString(ScAddress(0, 1, 0), "B"); + m_pDoc->SetString(ScAddress(0, 2, 0), "C"); + m_pDoc->SetString(ScAddress(0, 3, 0), "D"); + m_pDoc->SetString(ScAddress(0, 4, 0), "E"); + m_pDoc->SetString(ScAddress(0, 5, 0), "F"); + m_pDoc->SetString(ScAddress(0, 6, 0), "G"); + + // Set the formula in C1. + m_pDoc->SetString(ScAddress(2, 0, 0), "=VLOOKUP(\"C\";A1:A16;1)"); + CPPUNIT_ASSERT_EQUAL(OUString("C"), m_pDoc->GetString(ScAddress(2, 0, 0))); + + // A21:E24, test position dependent implicit intersection as argument to a + // scalar value parameter in a function that has a ReferenceOrForceArray + // type parameter somewhere else and formula is not in array mode, + // VLOOKUP(Value;ReferenceOrForceArray;...) + std::vector<std::vector<const char*>> aData2 + = { { "1", "one", "3", "=VLOOKUP(C21:C24;A21:B24;2;0)", "three" }, + { "2", "two", "1", "=VLOOKUP(C21:C24;A21:B24;2;0)", "one" }, + { "3", "three", "4", "=VLOOKUP(C21:C24;A21:B24;2;0)", "four" }, + { "4", "four", "2", "=VLOOKUP(C21:C24;A21:B24;2;0)", "two" } }; + + ScAddress aPos2(0, 20, 0); + ScRange aRange2 = insertRangeData(m_pDoc, aPos2, aData2); + CPPUNIT_ASSERT_EQUAL(aPos2, aRange2.aStart); + + aPos2.SetCol(3); // column D formula results + for (size_t i = 0; i < aData2.size(); ++i) + { + CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(aData2[i][4]), m_pDoc->GetString(aPos2)); + aPos2.IncRow(); + } + + m_pDoc->DeleteTab(0); +} + +template <size_t DataSize, size_t FormulaSize, int Type> +void TestFormula2::runTestMATCH(ScDocument* pDoc, const char* aData[DataSize], + const StrStrCheck aChecks[FormulaSize]) +{ + size_t nDataSize = DataSize; + for (size_t i = 0; i < nDataSize; ++i) + pDoc->SetString(0, i, 0, OUString::createFromAscii(aData[i])); + + for (size_t i = 0; i < FormulaSize; ++i) + { + pDoc->SetString(1, i, 0, OUString::createFromAscii(aChecks[i].pVal)); + + OUString aFormula = "=MATCH(B" + OUString::number(i + 1) + ";A1:A" + + OUString::number(nDataSize) + ";" + OUString::number(Type) + ")"; + pDoc->SetString(2, i, 0, aFormula); + } + + pDoc->CalcAll(); + printRange(pDoc, ScRange(0, 0, 0, 2, FormulaSize - 1, 0), "MATCH"); + + // verify the results. + for (size_t i = 0; i < FormulaSize; ++i) + { + OUString aStr = pDoc->GetString(2, i, 0); + if (!aStr.equalsAscii(aChecks[i].pRes)) + { + cerr << "row " << (i + 1) << ": expected='" << aChecks[i].pRes << "' actual='" << aStr + << "'" + " criterion='" + << aChecks[i].pVal << "'" << endl; + CPPUNIT_ASSERT_MESSAGE("Unexpected result for MATCH", false); + } + } +} + +template <size_t DataSize, size_t FormulaSize, int Type> +void TestFormula2::runTestHorizontalMATCH(ScDocument* pDoc, const char* aData[DataSize], + const StrStrCheck aChecks[FormulaSize]) +{ + size_t nDataSize = DataSize; + for (size_t i = 0; i < nDataSize; ++i) + pDoc->SetString(i, 0, 0, OUString::createFromAscii(aData[i])); + + for (size_t i = 0; i < FormulaSize; ++i) + { + pDoc->SetString(i, 1, 0, OUString::createFromAscii(aChecks[i].pVal)); + + // Assume we don't have more than 26 data columns... + OUString aFormula = "=MATCH(" + OUStringChar(static_cast<sal_Unicode>('A' + i)) + + "2;A1:" + OUStringChar(static_cast<sal_Unicode>('A' + nDataSize)) + + "1;" + OUString::number(Type) + ")"; + pDoc->SetString(i, 2, 0, aFormula); + } + + pDoc->CalcAll(); + printRange(pDoc, ScRange(0, 0, 0, FormulaSize - 1, 2, 0), "MATCH"); + + // verify the results. + for (size_t i = 0; i < FormulaSize; ++i) + { + OUString aStr = pDoc->GetString(i, 2, 0); + if (!aStr.equalsAscii(aChecks[i].pRes)) + { + cerr << "column " << char('A' + i) << ": expected='" << aChecks[i].pRes << "' actual='" + << aStr + << "'" + " criterion='" + << aChecks[i].pVal << "'" << endl; + CPPUNIT_ASSERT_MESSAGE("Unexpected result for horizontal MATCH", false); + } + } +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncMATCH) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + clearRange(m_pDoc, ScRange(0, 0, 0, 40, 40, 0)); + { + // Ascending in-exact match + + // data range (A1:A9) + const char* aData[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9", "B", "B", "C", + }; + + // formula (B1:C12) + static const StrStrCheck aChecks[] + = { { "0.8", "#N/A" }, { "1.2", "1" }, { "2.3", "2" }, { "3.9", "3" }, + { "4.1", "4" }, { "5.99", "5" }, { "6.1", "6" }, { "7.2", "7" }, + { "8.569", "8" }, { "9.59", "9" }, { "10", "9" }, { "100", "9" }, + { "Andy", "#N/A" }, { "Bruce", "11" }, { "Charlie", "12" } }; + + runTestMATCH<SAL_N_ELEMENTS(aData), SAL_N_ELEMENTS(aChecks), 1>(m_pDoc, aData, aChecks); + clearRange(m_pDoc, ScRange(0, 0, 0, 4, 40, 0)); + runTestHorizontalMATCH<SAL_N_ELEMENTS(aData), SAL_N_ELEMENTS(aChecks), 1>(m_pDoc, aData, + aChecks); + clearRange(m_pDoc, ScRange(0, 0, 0, 40, 4, 0)); + } + + { + // Descending in-exact match + + // data range (A1:A9) + const char* aData[] = { "D", "C", "B", "9", "8", "7", "6", "5", "4", "3", "2", "1" }; + + // formula (B1:C12) + static const StrStrCheck aChecks[] + = { { "10", "#N/A" }, { "8.9", "4" }, { "7.8", "5" }, { "6.7", "6" }, + { "5.5", "7" }, { "4.6", "8" }, { "3.3", "9" }, { "2.2", "10" }, + { "1.1", "11" }, { "0.8", "12" }, { "0", "12" }, { "-2", "12" }, + { "Andy", "3" }, { "Bruce", "2" }, { "Charlie", "1" }, { "David", "#N/A" } }; + + runTestMATCH<SAL_N_ELEMENTS(aData), SAL_N_ELEMENTS(aChecks), -1>(m_pDoc, aData, aChecks); + clearRange(m_pDoc, ScRange(0, 0, 0, 4, 40, 0)); + runTestHorizontalMATCH<SAL_N_ELEMENTS(aData), SAL_N_ELEMENTS(aChecks), -1>(m_pDoc, aData, + aChecks); + clearRange(m_pDoc, ScRange(0, 0, 0, 40, 4, 0)); + } + + { + // search range contains leading and trailing empty cell ranges. + + clearRange(m_pDoc, ScRange(0, 0, 0, 2, 100, 0)); + + // A5:A8 contains sorted values. + m_pDoc->SetValue(ScAddress(0, 4, 0), 1.0); + m_pDoc->SetValue(ScAddress(0, 5, 0), 2.0); + m_pDoc->SetValue(ScAddress(0, 6, 0), 3.0); + m_pDoc->SetValue(ScAddress(0, 7, 0), 4.0); + + // Find value 2 which is in A6. + m_pDoc->SetString(ScAddress(1, 0, 0), "=MATCH(2;A1:A20)"); + m_pDoc->CalcAll(); + + CPPUNIT_ASSERT_EQUAL(OUString("6"), m_pDoc->GetString(ScAddress(1, 0, 0))); + } + + { + // Test the ReferenceOrForceArray parameter. + + clearRange(m_pDoc, ScRange(0, 0, 0, 1, 7, 0)); + + // B1:B5 contain numeric values. + m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); + m_pDoc->SetValue(ScAddress(1, 1, 0), 2.0); + m_pDoc->SetValue(ScAddress(1, 2, 0), 3.0); + m_pDoc->SetValue(ScAddress(1, 3, 0), 4.0); + m_pDoc->SetValue(ScAddress(1, 4, 0), 5.0); + + // Find string value "33" in concatenated array, no implicit + // intersection is involved, array is forced. + m_pDoc->SetString(ScAddress(0, 5, 0), "=MATCH(\"33\";B1:B5&B1:B5)"); + m_pDoc->CalcAll(); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 5, 0))); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncCELL) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + clearRange(m_pDoc, ScRange(0, 0, 0, 2, 20, 0)); // Clear A1:C21. + + { + const char* pContent = "Some random text"; + m_pDoc->SetString(2, 9, 0, OUString::createFromAscii(pContent)); // Set this value to C10. + m_pDoc->SetValue(2, 0, 0, 1.2); // Set numeric value to C1; + + // We don't test: FILENAME, FORMAT, WIDTH, PROTECT, PREFIX + StrStrCheck aChecks[] + = { { "=CELL(\"COL\";C10)", "3" }, { "=CELL(\"COL\";C5:C10)", "3" }, + { "=CELL(\"ROW\";C10)", "10" }, { "=CELL(\"ROW\";C10:E10)", "10" }, + { "=CELL(\"SHEET\";C10)", "1" }, { "=CELL(\"ADDRESS\";C10)", "$C$10" }, + { "=CELL(\"CONTENTS\";C10)", pContent }, { "=CELL(\"COLOR\";C10)", "0" }, + { "=CELL(\"TYPE\";C9)", "b" }, { "=CELL(\"TYPE\";C10)", "l" }, + { "=CELL(\"TYPE\";C1)", "v" }, { "=CELL(\"PARENTHESES\";C10)", "0" } }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + m_pDoc->SetString(0, i, 0, OUString::createFromAscii(aChecks[i].pVal)); + m_pDoc->CalcAll(); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + OUString aVal = m_pDoc->GetString(0, i, 0); + CPPUNIT_ASSERT_MESSAGE("Unexpected result for CELL", aVal.equalsAscii(aChecks[i].pRes)); + } + } + + m_pDoc->DeleteTab(0); +} + +/** See also test case document fdo#44456 sheet cpearson */ +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncDATEDIF) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + std::vector<std::vector<const char*>> aData = { + { "2007-01-01", "2007-01-10", "d", "9", "=DATEDIF(A1;B1;C1)" }, + { "2007-01-01", "2007-01-31", "m", "0", "=DATEDIF(A2;B2;C2)" }, + { "2007-01-01", "2007-02-01", "m", "1", "=DATEDIF(A3;B3;C3)" }, + { "2007-01-01", "2007-02-28", "m", "1", "=DATEDIF(A4;B4;C4)" }, + { "2007-01-01", "2007-12-31", "d", "364", "=DATEDIF(A5;B5;C5)" }, + { "2007-01-01", "2007-01-31", "y", "0", "=DATEDIF(A6;B6;C6)" }, + { "2007-01-01", "2008-07-01", "d", "547", "=DATEDIF(A7;B7;C7)" }, + { "2007-01-01", "2008-07-01", "m", "18", "=DATEDIF(A8;B8;C8)" }, + { "2007-01-01", "2008-07-01", "ym", "6", "=DATEDIF(A9;B9;C9)" }, + { "2007-01-01", "2008-07-01", "yd", "182", "=DATEDIF(A10;B10;C10)" }, + { "2008-01-01", "2009-07-01", "yd", "181", "=DATEDIF(A11;B11;C11)" }, + { "2007-01-01", "2007-01-31", "md", "30", "=DATEDIF(A12;B12;C12)" }, + { "2007-02-01", "2009-03-01", "md", "0", "=DATEDIF(A13;B13;C13)" }, + { "2008-02-01", "2009-03-01", "md", "0", "=DATEDIF(A14;B14;C14)" }, + { "2007-01-02", "2007-01-01", "md", "Err:502", + "=DATEDIF(A15;B15;C15)" } // fail date1 > date2 + }; + + clearRange(m_pDoc, ScRange(0, 0, 0, 4, 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->CalcAll(); + + for (size_t i = 0; i < aData.size(); ++i) + { + OUString aVal = m_pDoc->GetString(4, i, 0); + //std::cout << "row "<< i << ": " << OUStringToOString( aVal, RTL_TEXTENCODING_UTF8).getStr() << ", expected " << aData[i][3] << std::endl; + CPPUNIT_ASSERT_MESSAGE("Unexpected result for DATEDIF", aVal.equalsAscii(aData[i][3])); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncINDIRECT) +{ + OUString aTabName("foo"); + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, aTabName)); + clearRange(m_pDoc, ScRange(0, 0, 0, 0, 10, 0)); // Clear A1:A11 + + bool bGood = m_pDoc->GetName(0, aTabName); + CPPUNIT_ASSERT_MESSAGE("failed to get sheet name.", bGood); + + OUString aTest = "Test", aRefErr = "#REF!"; + m_pDoc->SetString(0, 10, 0, aTest); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cell value.", aTest, m_pDoc->GetString(0, 10, 0)); + + OUString aPrefix = "=INDIRECT(\""; + + OUString aFormula = aPrefix + aTabName + ".A11\")"; // Calc A1 + m_pDoc->SetString(0, 0, 0, aFormula); + aFormula = aPrefix + aTabName + "!A11\")"; // Excel A1 + m_pDoc->SetString(0, 1, 0, aFormula); + aFormula = aPrefix + aTabName + "!R11C1\")"; // Excel R1C1 + m_pDoc->SetString(0, 2, 0, aFormula); + aFormula = aPrefix + aTabName + "!R11C1\";0)"; // Excel R1C1 (forced) + m_pDoc->SetString(0, 3, 0, aFormula); + + m_pDoc->CalcAll(); + { + // Default (for new documents) is to use current formula syntax + // which is Calc A1 + const OUString* aChecks[] = { &aTest, &aRefErr, &aRefErr, &aTest }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + OUString aVal = m_pDoc->GetString(0, i, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); + } + } + + ScCalcConfig aConfig; + aConfig.SetStringRefSyntax(formula::FormulaGrammar::CONV_OOO); + m_pDoc->SetCalcConfig(aConfig); + m_pDoc->CalcAll(); + { + // Explicit Calc A1 syntax + const OUString* aChecks[] = { &aTest, &aRefErr, &aRefErr, &aTest }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + OUString aVal = m_pDoc->GetString(0, i, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); + } + } + + aConfig.SetStringRefSyntax(formula::FormulaGrammar::CONV_XL_A1); + m_pDoc->SetCalcConfig(aConfig); + m_pDoc->CalcAll(); + { + // Excel A1 syntax + const OUString* aChecks[] = { &aRefErr, &aTest, &aRefErr, &aTest }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + OUString aVal = m_pDoc->GetString(0, i, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); + } + } + + aConfig.SetStringRefSyntax(formula::FormulaGrammar::CONV_XL_R1C1); + m_pDoc->SetCalcConfig(aConfig); + m_pDoc->CalcAll(); + { + // Excel R1C1 syntax + const OUString* aChecks[] = { &aRefErr, &aRefErr, &aTest, &aTest }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + OUString aVal = m_pDoc->GetString(0, i, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong value!", *aChecks[i], aVal); + } + } + + m_pDoc->DeleteTab(0); +} + +// Test case for tdf#83365 - Access across spreadsheet returns Err:504 +// +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncINDIRECT2) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(1, "bar")); + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(2, "baz")); + + m_pDoc->SetValue(0, 0, 0, 10.0); + m_pDoc->SetValue(0, 1, 0, 10.0); + m_pDoc->SetValue(0, 2, 0, 10.0); + + // Fill range bar.$A1:bar.$A10 with 1s + for (SCROW i = 0; i < 10; ++i) + m_pDoc->SetValue(0, i, 1, 1.0); + + // Test range triplet (absolute, relative, relative) : (absolute, relative, relative) + m_pDoc->SetString(0, 0, 2, "=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$1),1)"); + + // Test range triplet (absolute, relative, relative) : (absolute, absolute, relative) + m_pDoc->SetString(0, 1, 2, "=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$2),1)"); + + // Test range triplet (absolute, relative, relative) : (absolute, absolute, absolute) + m_pDoc->SetString(0, 2, 2, "=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$3),1)"); + + // Test range triplet (absolute, absolute, relative) : (absolute, relative, relative) + m_pDoc->SetString(0, 3, 2, "=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$1),1)"); + + // Test range triplet (absolute, absolute, relative) : (absolute, absolute, relative) + m_pDoc->SetString(0, 4, 2, "=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$2),1)"); + + // Test range triplet (absolute, absolute, relative) : (absolute, absolute, relative) + m_pDoc->SetString(0, 5, 2, "=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$3),1)"); + + // Test range triplet (absolute, absolute, absolute) : (absolute, relative, relative) + m_pDoc->SetString(0, 6, 2, "=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$1),1)"); + + // Test range triplet (absolute, absolute, absolute) : (absolute, absolute, relative) + m_pDoc->SetString(0, 7, 2, "=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$2),1)"); + + // Check indirect reference "bar.$A\"&foo.$A$1 + m_pDoc->SetString(0, 8, 2, "=COUNTIF(bar.$A$1:INDIRECT(\"bar.$A\"&foo.$A$1),1)"); + + // This case should return illegal argument error because + // they reference 2 different absolute sheets + // Test range triplet (absolute, absolute, absolute) : (absolute, absolute, absolute) + m_pDoc->SetString(0, 9, 2, "=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$3),1)"); + + m_pDoc->CalcAll(); + + // Loop all formulas and check result = 10.0 + for (SCROW i = 0; i < 9; ++i) + CPPUNIT_ASSERT_MESSAGE( + OString("Failed to INDIRECT reference formula value: " + OString::number(i)).getStr(), + m_pDoc->GetValue(0, i, 2) != 10.0); + + // Check formula cell error + ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0, 9, 2)); + CPPUNIT_ASSERT_MESSAGE("This should be a formula cell.", pFC); + CPPUNIT_ASSERT_MESSAGE("This formula cell should be an error.", + pFC->GetErrCode() != FormulaError::NONE); + + m_pDoc->DeleteTab(2); + m_pDoc->DeleteTab(1); + m_pDoc->DeleteTab(0); +} + +// Test for tdf#107724 do not propagate an array context from MATCH to INDIRECT +// as INDIRECT returns ParamClass::Reference +CPPUNIT_TEST_FIXTURE(TestFormula2, testFunc_MATCH_INDIRECT) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); + ScRangeData* pRangeData = new ScRangeData(*m_pDoc, "RoleAssignment", "$D$4:$D$13"); + pGlobalNames->insert(pRangeData); + + // D6: data to match, in 3rd row of named range. + m_pDoc->SetString(3, 5, 0, "Test1"); + // F15: Formula generating indirect reference of corner addresses taking + // row+offset and column from named range, which are not in array context + // thus don't create arrays of offsets. + m_pDoc->SetString(5, 14, 0, + "=MATCH(\"Test1\";INDIRECT(ADDRESS(ROW(RoleAssignment)+1;COLUMN(" + "RoleAssignment))&\":\"&ADDRESS(ROW(RoleAssignment)+ROWS(RoleAssignment)-1;" + "COLUMN(RoleAssignment)));0)"); + + // Match in 2nd row of range offset by 1 expected. + ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to not propagate array context from MATCH to INDIRECT", + 2.0, m_pDoc->GetValue(5, 14, 0)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaDepTracking) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + const ScAddress aA5(0, 4, 0); + const ScAddress aB2(1, 1, 0); + const ScAddress aB5(1, 4, 0); + const ScAddress aC5(2, 4, 0); + const ScAddress aD2(3, 1, 0); + const ScAddress aD5(3, 4, 0); + const ScAddress aD6(3, 5, 0); + const ScAddress aE2(4, 1, 0); + const ScAddress aE3(4, 2, 0); + const ScAddress aE6(4, 5, 0); + + // B2 listens on D2. + m_pDoc->SetString(aB2, "=D2"); + double val = m_pDoc->GetValue(aB2); + ASSERT_DOUBLES_EQUAL_MESSAGE("Referencing an empty cell should yield zero.", 0.0, val); + + { + // Check the internal broadcaster state. + auto aState = m_pDoc->GetBroadcasterState(); + aState.dump(std::cout, m_pDoc); + CPPUNIT_ASSERT(aState.hasFormulaCellListener(aD2, aB2)); + } + + // Changing the value of D2 should trigger recalculation of B2. + m_pDoc->SetValue(aD2, 1.1); + val = m_pDoc->GetValue(aB2); + ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on value change.", 1.1, val); + + // And again. + m_pDoc->SetValue(aD2, 2.2); + val = m_pDoc->GetValue(aB2); + ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on value change.", 2.2, val); + + clearRange(m_pDoc, ScRange(0, 0, 0, 10, 10, 0)); + + { + // Make sure nobody is listening on anything. + auto aState = m_pDoc->GetBroadcasterState(); + aState.dump(std::cout, m_pDoc); + CPPUNIT_ASSERT(aState.aCellListenerStore.empty()); + } + + // Now, let's test the range dependency tracking. + + // B2 listens on D2:E6. + m_pDoc->SetString(aB2, "=SUM(D2:E6)"); + val = m_pDoc->GetValue(aB2); + ASSERT_DOUBLES_EQUAL_MESSAGE("Summing an empty range should yield zero.", 0.0, val); + + { + // Check the internal state to make sure it matches. + auto aState = m_pDoc->GetBroadcasterState(); + aState.dump(std::cout, m_pDoc); + CPPUNIT_ASSERT(aState.hasFormulaCellListener({ aD2, aE6 }, aB2)); + } + + // Set value to E3. This should trigger recalc on B2. + m_pDoc->SetValue(aE3, 2.4); + val = m_pDoc->GetValue(aB2); + ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on single value change.", 2.4, val); + + // Set value to D5 to trigger recalc again. Note that this causes an + // addition of 1.2 + 2.4 which is subject to binary floating point + // rounding error. We need to use approxEqual to assess its value. + + m_pDoc->SetValue(aD5, 1.2); + val = m_pDoc->GetValue(aB2); + CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", + rtl::math::approxEqual(val, 3.6)); + + // Change the value of D2 (boundary case). + m_pDoc->SetValue(aD2, 1.0); + val = m_pDoc->GetValue(aB2); + CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", + rtl::math::approxEqual(val, 4.6)); + + // Change the value of E6 (another boundary case). + m_pDoc->SetValue(aE6, 2.0); + val = m_pDoc->GetValue(aB2); + CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", + rtl::math::approxEqual(val, 6.6)); + + // Change the value of D6 (another boundary case). + m_pDoc->SetValue(aD6, 3.0); + val = m_pDoc->GetValue(aB2); + CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", + rtl::math::approxEqual(val, 9.6)); + + // Change the value of E2 (another boundary case). + m_pDoc->SetValue(aE2, 0.4); + val = m_pDoc->GetValue(aB2); + CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", + rtl::math::approxEqual(val, 10.0)); + + // Change the existing non-empty value cell (E2). + m_pDoc->SetValue(aE2, 2.4); + val = m_pDoc->GetValue(aB2); + CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", + rtl::math::approxEqual(val, 12.0)); + + clearRange(m_pDoc, ScRange(0, 0, 0, 10, 10, 0)); + + // Now, column-based dependency tracking. We now switch to the R1C1 + // syntax which is easier to use for repeated relative references. + + FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1); + + val = 0.0; + for (SCROW nRow = 1; nRow <= 9; ++nRow) + { + // Static value in column 1. + m_pDoc->SetValue(0, nRow, 0, ++val); + + // Formula in column 2 that references cell to the left. + m_pDoc->SetString(1, nRow, 0, "=RC[-1]"); + + // Formula in column 3 that references cell to the left. + m_pDoc->SetString(2, nRow, 0, "=RC[-1]*2"); + } + + // Check formula values. + val = 0.0; + for (SCROW nRow = 1; nRow <= 9; ++nRow) + { + ++val; + ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", val, + m_pDoc->GetValue(1, nRow, 0)); + ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", val * 2.0, + m_pDoc->GetValue(2, nRow, 0)); + } + + // Intentionally insert a formula in column 1. This will break column 1's + // uniformity of consisting only of static value cells. + m_pDoc->SetString(aA5, "=R2C3"); + ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, m_pDoc->GetValue(aA5)); + ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, m_pDoc->GetValue(aB5)); + ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 4.0, m_pDoc->GetValue(aC5)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaDepTracking2) +{ + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "foo")); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + double val = 2.0; + m_pDoc->SetValue(0, 0, 0, val); + val = 4.0; + m_pDoc->SetValue(1, 0, 0, val); + val = 5.0; + m_pDoc->SetValue(0, 1, 0, val); + m_pDoc->SetString(2, 0, 0, "=A1/B1"); + m_pDoc->SetString(1, 1, 0, "=B1*C1"); + + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(1, 1, 0)); // B2 should equal 2. + + clearRange(m_pDoc, ScAddress(2, 0, 0)); // Delete C1. + + CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(1, 1, 0)); // B2 should now equal 0. + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaDepTracking3) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + m_pDoc->InsertTab(0, "Formula"); + + std::vector<std::vector<const char*>> aData = { + { "1", "2", "=SUM(A1:B1)", "=SUM(C1:C3)" }, + { "3", "4", "=SUM(A2:B2)", nullptr }, + { "5", "6", "=SUM(A3:B3)", nullptr }, + }; + + insertRangeData(m_pDoc, ScAddress(0, 0, 0), aData); + + // Check the initial formula results. + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); + CPPUNIT_ASSERT_EQUAL(11.0, m_pDoc->GetValue(ScAddress(2, 2, 0))); + CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(3, 0, 0))); + + // Change B3 and make sure the change gets propagated to D1. + ScDocFunc& rFunc = m_xDocShell->GetDocFunc(); + rFunc.SetValueCell(ScAddress(1, 2, 0), 60.0, false); + CPPUNIT_ASSERT_EQUAL(65.0, m_pDoc->GetValue(ScAddress(2, 2, 0))); + CPPUNIT_ASSERT_EQUAL(75.0, m_pDoc->GetValue(ScAddress(3, 0, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaDepTrackingDeleteRow) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + m_pDoc->InsertTab(0, "Test"); + + // Values in A1:A3. + m_pDoc->SetValue(ScAddress(0, 0, 0), 1.0); + m_pDoc->SetValue(ScAddress(0, 1, 0), 3.0); + m_pDoc->SetValue(ScAddress(0, 2, 0), 5.0); + + // SUM(A1:A3) in A5. + m_pDoc->SetString(ScAddress(0, 4, 0), "=SUM(A1:A3)"); + + // A6 to reference A5. + m_pDoc->SetString(ScAddress(0, 5, 0), "=A5*10"); + const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0, 5, 0)); + CPPUNIT_ASSERT(pFC); + + // A4 should have a broadcaster with A5 listening to it. + SvtBroadcaster* pBC = m_pDoc->GetBroadcaster(ScAddress(0, 4, 0)); + CPPUNIT_ASSERT(pBC); + SvtBroadcaster::ListenersType* pListeners = &pBC->GetAllListeners(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A5 should have one listener.", size_t(1), pListeners->size()); + const SvtListener* pListener = pListeners->at(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A6 should be listening to A5.", + static_cast<const ScFormulaCell*>(pListener), pFC); + + // Check initial values. + CPPUNIT_ASSERT_EQUAL(9.0, m_pDoc->GetValue(ScAddress(0, 4, 0))); + CPPUNIT_ASSERT_EQUAL(90.0, m_pDoc->GetValue(ScAddress(0, 5, 0))); + + // Delete row 2. + ScDocFunc& rFunc = m_xDocShell->GetDocFunc(); + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + rFunc.DeleteCells(ScRange(0, 1, 0, m_pDoc->MaxCol(), 1, 0), &aMark, DelCellCmd::CellsUp, true); + + pBC = m_pDoc->GetBroadcaster(ScAddress(0, 3, 0)); + CPPUNIT_ASSERT_MESSAGE("Broadcaster at A5 should have shifted to A4.", pBC); + pListeners = &pBC->GetAllListeners(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A3 should have one listener.", size_t(1), pListeners->size()); + pFC = m_pDoc->GetFormulaCell(ScAddress(0, 4, 0)); + CPPUNIT_ASSERT(pFC); + pListener = pListeners->at(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A5 should be listening to A4.", + static_cast<const ScFormulaCell*>(pListener), pFC); + + // Check values after row deletion. + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(0, 3, 0))); + CPPUNIT_ASSERT_EQUAL(60.0, m_pDoc->GetValue(ScAddress(0, 4, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaDepTrackingDeleteCol) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + m_pDoc->InsertTab(0, "Formula"); + + std::vector<std::vector<const char*>> aData = { + { "2", "=A1", "=B1" }, // not grouped + { nullptr, nullptr, nullptr }, // empty row to separate the formula groups. + { "3", "=A3", "=B3" }, // grouped + { "4", "=A4", "=B4" }, // grouped + }; + + ScAddress aPos(0, 0, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + + // Check the initial values. + for (SCCOL i = 0; i <= 2; ++i) + { + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(i, 0, 0))); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(i, 2, 0))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(i, 3, 0))); + } + + // Make sure B3:B4 and C3:C4 are grouped. + const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1, 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, 2, 0)); + CPPUNIT_ASSERT(pFC); + CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedTopRow()); + CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength()); + + // Delete column A. A1, B1, A3:A4 and B3:B4 should all show #REF!. + ScDocFunc& rFunc = m_xDocShell->GetDocFunc(); + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + rFunc.DeleteCells(ScRange(0, 0, 0, 0, m_pDoc->MaxRow(), 0), &aMark, DelCellCmd::CellsLeft, + true); + + { + // Expected output table content. 0 = empty cell + std::vector<std::vector<const char*>> aOutputCheck = { + { "#REF!", "#REF!" }, + { nullptr, nullptr }, + { "#REF!", "#REF!" }, + { "#REF!", "#REF!" }, + }; + + ScRange aCheckRange(0, 0, 0, 1, 3, 0); + bool bSuccess + = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after deleting column A"); + CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); + } + + // Undo and check the result. + SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager(); + CPPUNIT_ASSERT(pUndoMgr); + pUndoMgr->Undo(); + + { + // Expected output table content. 0 = empty cell + std::vector<std::vector<const char*>> aOutputCheck = { + { "2", "2", "2" }, + { nullptr, nullptr, nullptr }, + { "3", "3", "3" }, + { "4", "4", "4" }, + }; + + ScRange aCheckRange(0, 0, 0, 2, 3, 0); + bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after undo"); + CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); + } + + // Redo and check. + pUndoMgr->Redo(); + { + // Expected output table content. 0 = empty cell + std::vector<std::vector<const char*>> aOutputCheck = { + { "#REF!", "#REF!" }, + { nullptr, nullptr }, + { "#REF!", "#REF!" }, + { "#REF!", "#REF!" }, + }; + + ScRange aCheckRange(0, 0, 0, 1, 3, 0); + bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, "Check after redo"); + CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); + } + + // Undo and change the values in column A. + pUndoMgr->Undo(); + m_pDoc->SetValue(ScAddress(0, 0, 0), 22.0); + m_pDoc->SetValue(ScAddress(0, 2, 0), 23.0); + m_pDoc->SetValue(ScAddress(0, 3, 0), 24.0); + + { + // Expected output table content. 0 = empty cell + std::vector<std::vector<const char*>> aOutputCheck = { + { "22", "22", "22" }, + { nullptr, nullptr, nullptr }, + { "23", "23", "23" }, + { "24", "24", "24" }, + }; + + ScRange aCheckRange(0, 0, 0, 2, 3, 0); + bool bSuccess = checkOutput(m_pDoc, aCheckRange, aOutputCheck, + "Check after undo & value change in column A"); + CPPUNIT_ASSERT_MESSAGE("Table output check failed", bSuccess); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaMatrixResultUpdate) +{ + m_pDoc->InsertTab(0, "Test"); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation. + + // Set a numeric value to A1. + m_pDoc->SetValue(ScAddress(0, 0, 0), 11.0); + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(1, 0, 1, 0, aMark, "=A1"); + CPPUNIT_ASSERT_EQUAL(11.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1, 0, 0)); + CPPUNIT_ASSERT_MESSAGE("Failed to get formula cell.", pFC); + pFC->SetChanged( + false); // Clear this flag to simulate displaying of formula cell value on screen. + + m_pDoc->SetString(ScAddress(0, 0, 0), "ABC"); + CPPUNIT_ASSERT_EQUAL(OUString("ABC"), m_pDoc->GetString(ScAddress(1, 0, 0))); + pFC->SetChanged(false); + + // Put a new value into A1. The formula should update. + m_pDoc->SetValue(ScAddress(0, 0, 0), 13.0); + CPPUNIT_ASSERT_EQUAL(13.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testExternalRef) +{ + ScDocShellRef xExtDocSh = new ScDocShell; + OUString aExtDocName("file:///extdata.fake"); + OUString aExtSh1Name("Data1"); + OUString aExtSh2Name("Data2"); + OUString aExtSh3Name("Data3"); + SfxMedium* pMed = new SfxMedium(aExtDocName, StreamMode::STD_READWRITE); + xExtDocSh->DoLoad(pMed); + CPPUNIT_ASSERT_MESSAGE("external document instance not loaded.", + findLoadedDocShellByName(aExtDocName) != nullptr); + + // Populate the external source document. + ScDocument& rExtDoc = xExtDocSh->GetDocument(); + rExtDoc.InsertTab(0, aExtSh1Name); + rExtDoc.InsertTab(1, aExtSh2Name); + rExtDoc.InsertTab(2, aExtSh3Name); + + OUString const name("Name"); + OUString const value("Value"); + + // Sheet 1 + rExtDoc.SetString(0, 0, 0, name); + rExtDoc.SetString(0, 1, 0, "Andy"); + rExtDoc.SetString(0, 2, 0, "Bruce"); + rExtDoc.SetString(0, 3, 0, "Charlie"); + rExtDoc.SetString(0, 4, 0, "David"); + rExtDoc.SetString(1, 0, 0, value); + double val = 10; + rExtDoc.SetValue(1, 1, 0, val); + val = 11; + rExtDoc.SetValue(1, 2, 0, val); + val = 12; + rExtDoc.SetValue(1, 3, 0, val); + val = 13; + rExtDoc.SetValue(1, 4, 0, val); + + // Sheet 2 remains empty. + + // Sheet 3 + rExtDoc.SetString(0, 0, 2, name); + rExtDoc.SetString(0, 1, 2, "Edward"); + rExtDoc.SetString(0, 2, 2, "Frank"); + rExtDoc.SetString(0, 3, 2, "George"); + rExtDoc.SetString(0, 4, 2, "Henry"); + rExtDoc.SetString(1, 0, 2, value); + val = 99; + rExtDoc.SetValue(1, 1, 2, val); + val = 98; + rExtDoc.SetValue(1, 2, 2, val); + val = 97; + rExtDoc.SetValue(1, 3, 2, val); + val = 96; + rExtDoc.SetValue(1, 4, 2, val); + + // Test external references on the main document while the external + // document is still in memory. + m_pDoc->InsertTab(0, "Test Sheet"); + m_pDoc->SetString(0, 0, 0, "='file:///extdata.fake'#Data1.A1"); + OUString test = m_pDoc->GetString(0, 0, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Value is different from the original", test, name); + + // After the initial access to the external document, the external ref + // manager should create sheet cache entries for *all* sheets from that + // document. Note that the doc may have more than 3 sheets but ensure + // that the first 3 are what we expect. + ScExternalRefManager* pRefMgr = m_pDoc->GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aExtDocName); + vector<OUString> aTabNames; + pRefMgr->getAllCachedTableNames(nFileId, aTabNames); + CPPUNIT_ASSERT_MESSAGE("There should be at least 3 sheets.", aTabNames.size() >= 3); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected sheet name.", aTabNames[0], aExtSh1Name); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected sheet name.", aTabNames[1], aExtSh2Name); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected sheet name.", aTabNames[2], aExtSh3Name); + + m_pDoc->SetString(1, 0, 0, "='file:///extdata.fake'#Data1.B1"); + test = m_pDoc->GetString(1, 0, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Value is different from the original", test, value); + + m_pDoc->SetString(0, 1, 0, "='file:///extdata.fake'#Data1.A2"); + m_pDoc->SetString(0, 2, 0, "='file:///extdata.fake'#Data1.A3"); + m_pDoc->SetString(0, 3, 0, "='file:///extdata.fake'#Data1.A4"); + m_pDoc->SetString(0, 4, 0, "='file:///extdata.fake'#Data1.A5"); + m_pDoc->SetString(0, 5, 0, "='file:///extdata.fake'#Data1.A6"); + + { + // Referencing an empty cell should display '0'. + const char* pChecks[] = { "Andy", "Bruce", "Charlie", "David", "0" }; + for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) + { + test = m_pDoc->GetString(0, static_cast<SCROW>(i + 1), 0); + CPPUNIT_ASSERT_MESSAGE("Unexpected cell value.", test.equalsAscii(pChecks[i])); + } + } + m_pDoc->SetString(1, 1, 0, "='file:///extdata.fake'#Data1.B2"); + m_pDoc->SetString(1, 2, 0, "='file:///extdata.fake'#Data1.B3"); + m_pDoc->SetString(1, 3, 0, "='file:///extdata.fake'#Data1.B4"); + m_pDoc->SetString(1, 4, 0, "='file:///extdata.fake'#Data1.B5"); + m_pDoc->SetString(1, 5, 0, "='file:///extdata.fake'#Data1.B6"); + { + double pChecks[] = { 10, 11, 12, 13, 0 }; + for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) + { + val = m_pDoc->GetValue(1, static_cast<SCROW>(i + 1), 0); + ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected cell value.", pChecks[i], val); + } + } + + m_pDoc->SetString(2, 0, 0, "='file:///extdata.fake'#Data3.A1"); + m_pDoc->SetString(2, 1, 0, "='file:///extdata.fake'#Data3.A2"); + m_pDoc->SetString(2, 2, 0, "='file:///extdata.fake'#Data3.A3"); + m_pDoc->SetString(2, 3, 0, "='file:///extdata.fake'#Data3.A4"); + { + const char* pChecks[] = { "Name", "Edward", "Frank", "George" }; + for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) + { + test = m_pDoc->GetString(2, static_cast<SCROW>(i), 0); + CPPUNIT_ASSERT_MESSAGE("Unexpected cell value.", test.equalsAscii(pChecks[i])); + } + } + + m_pDoc->SetString(3, 0, 0, "='file:///extdata.fake'#Data3.B1"); + m_pDoc->SetString(3, 1, 0, "='file:///extdata.fake'#Data3.B2"); + m_pDoc->SetString(3, 2, 0, "='file:///extdata.fake'#Data3.B3"); + m_pDoc->SetString(3, 3, 0, "='file:///extdata.fake'#Data3.B4"); + { + const char* pChecks[] = { "Value", "99", "98", "97" }; + for (size_t i = 0; i < SAL_N_ELEMENTS(pChecks); ++i) + { + test = m_pDoc->GetString(3, static_cast<SCROW>(i), 0); + CPPUNIT_ASSERT_MESSAGE("Unexpected cell value.", test.equalsAscii(pChecks[i])); + } + } + + // At this point, all accessed cell data from the external document should + // have been cached. + ScExternalRefCache::TableTypeRef pCacheTab + = pRefMgr->getCacheTable(nFileId, aExtSh1Name, false); + CPPUNIT_ASSERT_MESSAGE("Cache table for sheet 1 should exist.", pCacheTab); + ScRange aCachedRange = getCachedRange(pCacheTab); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCCOL(0), + aCachedRange.aStart.Col()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCCOL(1), + aCachedRange.aEnd.Col()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCROW(0), + aCachedRange.aStart.Row()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCROW(4), + aCachedRange.aEnd.Row()); + + // Sheet2 is not referenced at all; the cache table shouldn't even exist. + pCacheTab = pRefMgr->getCacheTable(nFileId, aExtSh2Name, false); + CPPUNIT_ASSERT_MESSAGE("Cache table for sheet 2 should *not* exist.", !pCacheTab); + + // Sheet3's row 5 is not referenced; it should not be cached. + pCacheTab = pRefMgr->getCacheTable(nFileId, aExtSh3Name, false); + CPPUNIT_ASSERT_MESSAGE("Cache table for sheet 3 should exist.", pCacheTab); + aCachedRange = getCachedRange(pCacheTab); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCCOL(0), + aCachedRange.aStart.Col()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCCOL(1), + aCachedRange.aEnd.Col()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCROW(0), + aCachedRange.aStart.Row()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cached data range.", SCROW(3), + aCachedRange.aEnd.Row()); + + // Unload the external document shell. + xExtDocSh->DoClose(); + CPPUNIT_ASSERT_MESSAGE("external document instance should have been unloaded.", + !findLoadedDocShellByName(aExtDocName)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testExternalRangeName) +{ + ScDocShellRef xExtDocSh = new ScDocShell; + OUString const aExtDocName("file:///extdata.fake"); + SfxMedium* pMed = new SfxMedium(aExtDocName, StreamMode::STD_READWRITE); + xExtDocSh->DoLoad(pMed); + CPPUNIT_ASSERT_MESSAGE("external document instance not loaded.", + findLoadedDocShellByName(aExtDocName) != nullptr); + + ScDocument& rExtDoc = xExtDocSh->GetDocument(); + rExtDoc.InsertTab(0, "Data1"); + rExtDoc.SetValue(0, 0, 0, 123.456); + + ScRangeName* pRangeName = rExtDoc.GetRangeName(); + ScRangeData* pRangeData = new ScRangeData(rExtDoc, "ExternalName", "$Data1.$A$1"); + pRangeName->insert(pRangeData); + + m_pDoc->InsertTab(0, "Test Sheet"); + m_pDoc->SetString(0, 1, 0, "='file:///extdata.fake'#ExternalName"); + + double nVal = m_pDoc->GetValue(0, 1, 0); + ASSERT_DOUBLES_EQUAL(123.456, nVal); + + xExtDocSh->DoClose(); + CPPUNIT_ASSERT_MESSAGE("external document instance should have been unloaded.", + !findLoadedDocShellByName(aExtDocName)); + m_pDoc->DeleteTab(0); +} + +void TestFormula2::testExtRefFuncT(ScDocument* pDoc, ScDocument& rExtDoc) +{ + clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); + clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); + + rExtDoc.SetString(0, 0, 0, "'1.2"); + rExtDoc.SetString(0, 1, 0, "Foo"); + rExtDoc.SetValue(0, 2, 0, 12.3); + pDoc->SetString(0, 0, 0, "=T('file:///extdata.fake'#Data.A1)"); + pDoc->SetString(0, 1, 0, "=T('file:///extdata.fake'#Data.A2)"); + pDoc->SetString(0, 2, 0, "=T('file:///extdata.fake'#Data.A3)"); + pDoc->CalcAll(); + + OUString aRes = pDoc->GetString(0, 0, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected result with T.", OUString("1.2"), aRes); + aRes = pDoc->GetString(0, 1, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected result with T.", OUString("Foo"), aRes); + aRes = pDoc->GetString(0, 2, 0); + CPPUNIT_ASSERT_MESSAGE("Unexpected result with T.", aRes.isEmpty()); +} + +void TestFormula2::testExtRefFuncOFFSET(ScDocument* pDoc, ScDocument& rExtDoc) +{ + clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); + clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); + + sc::AutoCalcSwitch aACSwitch(*pDoc, true); + + // External document has sheet named 'Data', and the internal doc has sheet named 'Test'. + rExtDoc.SetValue(ScAddress(0, 1, 0), 1.2); // Set 1.2 to A2. + pDoc->SetString(ScAddress(0, 0, 0), "=OFFSET('file:///extdata.fake'#Data.$A$1;1;0;1;1)"); + CPPUNIT_ASSERT_EQUAL(1.2, pDoc->GetValue(ScAddress(0, 0, 0))); +} + +void TestFormula2::testExtRefFuncVLOOKUP(ScDocument* pDoc, ScDocument& rExtDoc) +{ + clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); + clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); + + // Populate the external document. + rExtDoc.SetString(ScAddress(0, 0, 0), "A1"); + rExtDoc.SetString(ScAddress(0, 1, 0), "A2"); + rExtDoc.SetString(ScAddress(0, 2, 0), "A3"); + rExtDoc.SetString(ScAddress(0, 3, 0), "A4"); + rExtDoc.SetString(ScAddress(0, 4, 0), "A5"); + + rExtDoc.SetString(ScAddress(1, 0, 0), "B1"); + rExtDoc.SetString(ScAddress(1, 1, 0), "B2"); + rExtDoc.SetString(ScAddress(1, 2, 0), "B3"); + rExtDoc.SetString(ScAddress(1, 3, 0), "B4"); + rExtDoc.SetString(ScAddress(1, 4, 0), "B5"); + + // Put formula in the source document. + + pDoc->SetString(ScAddress(0, 0, 0), "A2"); + + // Sort order TRUE + pDoc->SetString(ScAddress(1, 0, 0), "=VLOOKUP(A1;'file:///extdata.fake'#Data.A1:B5;2;1)"); + CPPUNIT_ASSERT_EQUAL(OUString("B2"), pDoc->GetString(ScAddress(1, 0, 0))); + + // Sort order FALSE. It should return the same result. + pDoc->SetString(ScAddress(1, 0, 0), "=VLOOKUP(A1;'file:///extdata.fake'#Data.A1:B5;2;0)"); + CPPUNIT_ASSERT_EQUAL(OUString("B2"), pDoc->GetString(ScAddress(1, 0, 0))); +} + +void TestFormula2::testExtRefConcat(ScDocument* pDoc, ScDocument& rExtDoc) +{ + clearRange(pDoc, ScRange(0, 0, 0, 1, 9, 0)); + clearRange(&rExtDoc, ScRange(0, 0, 0, 1, 9, 0)); + + sc::AutoCalcSwitch aACSwitch(*pDoc, true); + + // String and number + rExtDoc.SetString(ScAddress(0, 0, 0), "Answer: "); + rExtDoc.SetValue(ScAddress(0, 1, 0), 42); + + // Concat operation should combine string and number converted to string + pDoc->SetString(ScAddress(0, 0, 0), + "='file:///extdata.fake'#Data.A1 & 'file:///extdata.fake'#Data.A2"); + CPPUNIT_ASSERT_EQUAL(OUString("Answer: 42"), pDoc->GetString(ScAddress(0, 0, 0))); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testExternalRefFunctions) +{ + ScDocShellRef xExtDocSh = new ScDocShell; + OUString aExtDocName("file:///extdata.fake"); + SfxMedium* pMed = new SfxMedium(aExtDocName, StreamMode::STD_READWRITE); + xExtDocSh->DoLoad(pMed); + CPPUNIT_ASSERT_MESSAGE("external document instance not loaded.", + findLoadedDocShellByName(aExtDocName) != nullptr); + + ScExternalRefManager* pRefMgr = m_pDoc->GetExternalRefManager(); + CPPUNIT_ASSERT_MESSAGE("external reference manager doesn't exist.", pRefMgr); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aExtDocName); + const OUString* pFileName = pRefMgr->getExternalFileName(nFileId); + CPPUNIT_ASSERT_MESSAGE("file name registration has somehow failed.", pFileName); + CPPUNIT_ASSERT_EQUAL_MESSAGE("file name registration has somehow failed.", aExtDocName, + *pFileName); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + + // Populate the external source document. + ScDocument& rExtDoc = xExtDocSh->GetDocument(); + rExtDoc.InsertTab(0, "Data"); + double val = 1; + rExtDoc.SetValue(0, 0, 0, val); + // leave cell B1 empty. + val = 2; + rExtDoc.SetValue(0, 1, 0, val); + rExtDoc.SetValue(1, 1, 0, val); + val = 3; + rExtDoc.SetValue(0, 2, 0, val); + rExtDoc.SetValue(1, 2, 0, val); + val = 4; + rExtDoc.SetValue(0, 3, 0, val); + rExtDoc.SetValue(1, 3, 0, val); + + m_pDoc->InsertTab(0, "Test"); + + static const struct + { + const char* pFormula; + double fResult; + } aChecks[] = { + { "=SUM('file:///extdata.fake'#Data.A1:A4)", 10 }, + { "=SUM('file:///extdata.fake'#Data.B1:B4)", 9 }, + { "=AVERAGE('file:///extdata.fake'#Data.A1:A4)", 2.5 }, + { "=AVERAGE('file:///extdata.fake'#Data.B1:B4)", 3 }, + { "=COUNT('file:///extdata.fake'#Data.A1:A4)", 4 }, + { "=COUNT('file:///extdata.fake'#Data.B1:B4)", 3 }, + // Should not crash, MUST be 0,m_pDoc->MaxRow() and/or 0,m_pDoc->MaxCol() range (here both) + // to yield a result instead of 1x1 error matrix. + { "=SUM('file:///extdata.fake'#Data.1:1048576)", 19 } + }; + + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + m_pDoc->SetString(0, 0, 0, OUString::createFromAscii(aChecks[i].pFormula)); + val = m_pDoc->GetValue(0, 0, 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("unexpected result involving external ranges.", + aChecks[i].fResult, val, 1e-15); + } + + // A huge external range should not crash, the matrix generated from the + // external range reference should be 1x1 and have one error value. + // XXX NOTE: in case we supported sparse matrix that can hold this large + // areas these tests may be adapted. + m_pDoc->SetString(0, 0, 0, "=SUM('file:///extdata.fake'#Data.B1:AMJ1048575)"); + ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0, 0, 0)); + FormulaError nErr = pFC->GetErrCode(); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "huge external range reference expected to yield FormulaError::MatrixSize", + int(FormulaError::MatrixSize), static_cast<int>(nErr)); + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(0, 0, 0, 0, aMark, "'file:///extdata.fake'#Data.B1:AMJ1048575"); + pFC = m_pDoc->GetFormulaCell(ScAddress(0, 0, 0)); + nErr = pFC->GetErrCode(); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "huge external range reference expected to yield FormulaError::MatrixSize", + int(FormulaError::MatrixSize), static_cast<int>(nErr)); + SCSIZE nMatCols, nMatRows; + const ScMatrix* pMat = pFC->GetMatrix(); + CPPUNIT_ASSERT_MESSAGE("matrix expected", pMat != nullptr); + pMat->GetDimensions(nMatCols, nMatRows); + CPPUNIT_ASSERT_EQUAL_MESSAGE("1x1 matrix expected", SCSIZE(1), nMatCols); + CPPUNIT_ASSERT_EQUAL_MESSAGE("1x1 matrix expected", SCSIZE(1), nMatRows); + + pRefMgr->clearCache(nFileId); + testExtRefFuncT(m_pDoc, rExtDoc); + testExtRefFuncOFFSET(m_pDoc, rExtDoc); + testExtRefFuncVLOOKUP(m_pDoc, rExtDoc); + testExtRefConcat(m_pDoc, rExtDoc); + + // Unload the external document shell. + xExtDocSh->DoClose(); + CPPUNIT_ASSERT_MESSAGE("external document instance should have been unloaded.", + !findLoadedDocShellByName(aExtDocName)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testExternalRefUnresolved) +{ +#if !defined(_WIN32) //FIXME + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + m_pDoc->InsertTab(0, "Test"); + + // Test error propagation of unresolved (not existing document) external + // references. Well, let's hope no build machine has such file with sheet... + + std::vector<std::vector<const char*>> aData = { + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1+23" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1&\"W\"" }, + { "=ISREF('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, + { "=ISERROR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, + { "=ISERR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, + { "=ISBLANK('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, + { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, + { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1)" }, + { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1+23)" }, + { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1&\"W\")" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1=0" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1=\"\"" }, + { "=INDIRECT(\"'file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1\")" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2+23" }, + { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2&\"W\"" }, + { "=ISREF('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, + { "=ISERROR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, + { "=ISERR('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, + { "=ISBLANK('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, + { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, + { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2)" }, + { "=ISNUMBER('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2+23)" }, + { "=ISTEXT('file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2&\"W\")" }, + // TODO: gives Err:504 FIXME { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2=0" }, + // TODO: gives Err:504 FIXME { "='file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2=\"\"" }, + { "=INDIRECT(\"'file:///NonExistingFilePath/AnyName.ods'#$NoSuchSheet.A1:A2\")" }, + }; + + ScAddress aPos(0, 0, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + + std::vector<std::vector<const char*>> aOutputCheck = { + { "#REF!" }, // plain single ref + { "#REF!" }, // +23 + { "#REF!" }, // &"W" + { "FALSE" }, // ISREF + { "TRUE" }, // ISERROR + { "TRUE" }, // ISERR + { "FALSE" }, // ISBLANK + { "FALSE" }, // ISNUMBER + { "FALSE" }, // ISTEXT + { "FALSE" }, // ISNUMBER + { "FALSE" }, // ISTEXT + { "#REF!" }, // =0 + { "#REF!" }, // ="" + { "#REF!" }, // INDIRECT + { "#REF!" }, // A1:A2 range + { "#REF!" }, // +23 + { "#REF!" }, // &"W" + { "FALSE" }, // ISREF + { "TRUE" }, // ISERROR + { "TRUE" }, // ISERR + { "FALSE" }, // ISBLANK + { "FALSE" }, // ISNUMBER + { "FALSE" }, // ISTEXT + { "FALSE" }, // ISNUMBER + { "FALSE" }, // ISTEXT + // TODO: gives Err:504 FIXME { "#REF!" }, // =0 + // TODO: gives Err:504 FIXME { "#REF!" }, // ="" + { "#REF!" }, // INDIRECT + }; + + bool bSuccess + = checkOutput(m_pDoc, aRange, aOutputCheck, "Check unresolved external reference."); + CPPUNIT_ASSERT_MESSAGE("Unresolved reference check failed", bSuccess); + + m_pDoc->DeleteTab(0); +#endif +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testMatrixOp) +{ + m_pDoc->InsertTab(0, "Test"); + + for (SCROW nRow = 0; nRow < 4; ++nRow) + { + m_pDoc->SetValue(0, nRow, 0, nRow); + } + m_pDoc->SetValue(1, 0, 0, 2.0); + m_pDoc->SetValue(3, 0, 0, 1.0); + m_pDoc->SetValue(3, 1, 0, 2.0); + m_pDoc->SetString(2, 0, 0, "=SUMPRODUCT((A1:A4)*B1+D1)"); + m_pDoc->SetString(2, 1, 0, "=SUMPRODUCT((A1:A4)*B1-D2)"); + + double nVal = m_pDoc->GetValue(2, 0, 0); + CPPUNIT_ASSERT_EQUAL(16.0, nVal); + + nVal = m_pDoc->GetValue(2, 1, 0); + CPPUNIT_ASSERT_EQUAL(4.0, nVal); + + m_pDoc->SetString(4, 0, 0, "=SUMPRODUCT({1;2;4}+8)"); + m_pDoc->SetString(4, 1, 0, "=SUMPRODUCT(8+{1;2;4})"); + m_pDoc->SetString(4, 2, 0, "=SUMPRODUCT({1;2;4}-8)"); + m_pDoc->SetString(4, 3, 0, "=SUMPRODUCT(8-{1;2;4})"); + m_pDoc->SetString(4, 4, 0, "=SUMPRODUCT({1;2;4}+{8;16;32})"); + m_pDoc->SetString(4, 5, 0, "=SUMPRODUCT({8;16;32}+{1;2;4})"); + m_pDoc->SetString(4, 6, 0, "=SUMPRODUCT({1;2;4}-{8;16;32})"); + m_pDoc->SetString(4, 7, 0, "=SUMPRODUCT({8;16;32}-{1;2;4})"); + double fResult[8] = { 31.0, 31.0, -17.0, 17.0, 63.0, 63.0, -49.0, 49.0 }; + for (size_t i = 0; i < SAL_N_ELEMENTS(fResult); ++i) + { + CPPUNIT_ASSERT_EQUAL(fResult[i], m_pDoc->GetValue(4, i, 0)); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncRangeOp) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + + m_pDoc->InsertTab(0, "Sheet1"); + m_pDoc->InsertTab(1, "Sheet2"); + m_pDoc->InsertTab(2, "Sheet3"); + + // Sheet1.B1:B3 + m_pDoc->SetValue(1, 0, 0, 1.0); + m_pDoc->SetValue(1, 1, 0, 2.0); + m_pDoc->SetValue(1, 2, 0, 4.0); + // Sheet2.B1:B3 + m_pDoc->SetValue(1, 0, 1, 8.0); + m_pDoc->SetValue(1, 1, 1, 16.0); + m_pDoc->SetValue(1, 2, 1, 32.0); + // Sheet3.B1:B3 + m_pDoc->SetValue(1, 0, 2, 64.0); + m_pDoc->SetValue(1, 1, 2, 128.0); + m_pDoc->SetValue(1, 2, 2, 256.0); + + // Range operator should extend concatenated literal references during + // parse time already, so with this we can test ScComplexRefData::Extend() + + // Current sheet is Sheet1, so B1:B2 implies relative Sheet1.B1:B2 + + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, "=SUM(B1:B2:B3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B1:B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(B1:B3:B2)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B1:B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(B2:B3:B1)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B1:B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(Sheet2.B1:B2:B3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet2.B1:B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(56.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(B2:B2:Sheet1.B2)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B2:B2)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(B2:B3:Sheet2.B1)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B1:Sheet2.B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(63.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(Sheet1.B1:Sheet2.B2:Sheet3.B3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B1:Sheet3.B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(511.0, m_pDoc->GetValue(aPos)); + + // B1:Sheet2.B2 would be ambiguous, Sheet1.B1:Sheet2.B2 or Sheet2.B1:B2 + // The actual representation of the error case may change, so this test may + // have to be adapted. + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(B1:Sheet2.B2:Sheet3.B3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(b1:sheet2.b2:Sheet3.B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(OUString("#NAME?"), m_pDoc->GetString(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(Sheet1.B1:Sheet3.B2:Sheet2.B3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(Sheet1.B1:Sheet3.B3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(511.0, m_pDoc->GetValue(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, "=SUM(B$2:B$2:B2)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=SUM(B$2:B2)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(2); + m_pDoc->DeleteTab(1); + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncFORMULA) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + + m_pDoc->InsertTab(0, "Sheet1"); + + // Data in B1:D3 + std::vector<std::vector<const char*>> aData = { + { "=A1", "=FORMULA(B1)", "=FORMULA(B1:B3)" }, + { nullptr, "=FORMULA(B2)", "=FORMULA(B1:B3)" }, + { "=A3", "=FORMULA(B3)", "=FORMULA(B1:B3)" }, + }; + + ScAddress aPos(1, 0, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + + // Checks of C1:D3, where Cy==Dy, and D4:D6 + const char* aChecks[] = { + "=A1", + "#N/A", + "=A3", + }; + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(aChecks[i]), m_pDoc->GetString(2, i, 0)); + CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(aChecks[i]), m_pDoc->GetString(3, i, 0)); + } + + // Matrix in D4:D6, no intersection with B1:B3 + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(3, 3, 3, 5, aMark, "=FORMULA(B1:B3)"); + for (size_t i = 0; i < SAL_N_ELEMENTS(aChecks); ++i) + { + CPPUNIT_ASSERT_EQUAL(OUString::createFromAscii(aChecks[i]), m_pDoc->GetString(3, i + 3, 0)); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncTableRef) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + + m_pDoc->InsertTab(0, "Sheet1"); + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc(); + + { + ScDBCollection* pDBs = m_pDoc->GetDBCollection(); + CPPUNIT_ASSERT_MESSAGE("Failed to fetch DB collection object.", pDBs); + + // Insert "table" database range definition for A1:B4, with default + // HasHeader=true and HasTotals=false. + std::unique_ptr<ScDBData> pData(new ScDBData("table", 0, 0, 0, 1, 3)); + bool bInserted = pDBs->getNamedDBs().insert(std::move(pData)); + CPPUNIT_ASSERT_MESSAGE("Failed to insert \"table\" database range.", bInserted); + } + + { + // Populate "table" database range with headers and data in A1:B4 + std::vector<std::vector<const char*>> aData + = { { "Header1", "Header2" }, { "1", "2" }, { "4", "8" }, { "16", "32" } }; + ScAddress aPos(0, 0, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + } + + // Named expressions that use Table structured references. + /* TODO: should the item/header separator really be equal to the parameter + * separator, thus be locale dependent and ';' semicolon here, or should it + * be a fixed ',' comma instead? */ + static const struct + { + const char* pName; + const char* pExpr; + const char* + pCounta; // expected result when used in row 2 (first data row) as argument to COUNTA() + const char* + pSum3; // expected result when used in row 3 (second data row) as argument to SUM(). + const char* + pSum4; // expected result when used in row 4 (third data row) as argument to SUM(). + const char* + pSumX; // expected result when used in row 5 (non-intersecting) as argument to SUM(). + } aNames[] + = { { "all", "table[[#All]]", "8", "63", "63", "63" }, + { "data_implicit", "table[]", "6", "63", "63", "63" }, + { "data", "table[[#Data]]", "6", "63", "63", "63" }, + { "headers", "table[[#Headers]]", "2", "0", "0", "0" }, + { "header1", "table[[Header1]]", "3", "21", "21", "21" }, + { "header2", "table[[Header2]]", "3", "42", "42", "42" }, + { "data_header1", "table[[#Data];[Header1]]", "3", "21", "21", "21" }, + { "data_header2", "table[[#Data];[Header2]]", "3", "42", "42", "42" }, + { "this_row", "table[[#This Row]]", "2", "12", "48", "#VALUE!" }, + { "this_row_header1", "table[[#This Row];[Header1]]", "1", "4", "16", "#VALUE!" }, + { "this_row_header2", "table[[#This Row];[Header2]]", "1", "8", "32", "#VALUE!" }, + { "this_row_range_header_1_to_2", "table[[#This Row];[Header1]:[Header2]]", "2", "12", + "48", "#VALUE!" } }; + + { + // Insert named expressions. + ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); + CPPUNIT_ASSERT_MESSAGE("Failed to obtain global named expression object.", pGlobalNames); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) + { + // Choose base position that does not intersect with the database + // range definition to test later use of [#This Row] results in + // proper rows. + ScRangeData* pName + = new ScRangeData(*m_pDoc, OUString::createFromAscii(aNames[i].pName), + OUString::createFromAscii(aNames[i].pExpr), ScAddress(2, 4, 0), + ScRangeData::Type::Name, formula::FormulaGrammar::GRAM_NATIVE); + bool bInserted = pGlobalNames->insert(pName); + CPPUNIT_ASSERT_MESSAGE(OString(OString::Concat("Failed to insert named expression ") + + aNames[i].pName + ".") + .getStr(), + bInserted); + } + } + + // Use the named expressions in COUNTA() formulas, on row 2 that intersects. + for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) + { + OUString aFormula("=COUNTA(" + OUString::createFromAscii(aNames[i].pName) + ")"); + ScAddress aPos(3 + i, 1, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aNames[i].pCounta)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Use the named expressions in SUM() formulas, on row 3 that intersects. + for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aNames[i].pName) + ")"); + ScAddress aPos(3 + i, 2, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aNames[i].pSum3)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Use the named expressions in SUM() formulas, on row 4 that intersects. + for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aNames[i].pName) + ")"); + ScAddress aPos(3 + i, 3, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aNames[i].pSum4)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Use the named expressions in SUM() formulas, on row 5 that does not intersect. + for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aNames[i].pName) + ")"); + ScAddress aPos(3 + i, 4, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aNames[i].pSumX)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Insert a column at column B to extend database range from column A,B to + // A,B,C. Use ScDocFunc so RefreshDirtyTableColumnNames() is called. + rDocFunc.InsertCells(ScRange(1, 0, 0, 1, m_pDoc->MaxRow(), 0), &aMark, INS_INSCOLS_BEFORE, + false, true); + + // Re-verify the named expression in SUM() formula, on row 4 that + // intersects, now starting at column E, still works. + m_pDoc->CalcAll(); + for (size_t i = 0; i < SAL_N_ELEMENTS(aNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aNames[i].pName) + ")"); + ScAddress aPos(4 + i, 3, 0); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aNames[i].pSum4)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + const char* pColumn2Formula = "=SUM(table[[#Data];[Column2]])"; + { + // Populate "table" database range with empty header and data in newly + // inserted column, B1:B4 plus a table formula in B6. The empty header + // should result in the internal table column name "Column2" that is + // used in the formula. + std::vector<std::vector<const char*>> aData + = { { "" }, { "64" }, { "128" }, { "256" }, { "" }, { pColumn2Formula } }; + ScAddress aPos(1, 0, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + } + + // Verify the formula result in B6 (64+128+256=448). + { + OUString aFormula(OUString::createFromAscii(pColumn2Formula)); + ScAddress aPos(1, 5, 0); + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + "448"), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Set header in column B. Use ScDocFunc to have table column names refreshed. + rDocFunc.SetStringCell(ScAddress(1, 0, 0), "NewHeader", true); + // Verify that formula adapted using the updated table column names. + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", OUString("=SUM(table[[#Data];[NewHeader]])"), + m_pDoc->GetFormula(1, 5, 0)); + + // Set header in column A to identical string. Internal table column name + // for B should get a "2" appended. + rDocFunc.SetStringCell(ScAddress(0, 0, 0), "NewHeader", true); + // Verify that formula adapted using the updated table column names. + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", OUString("=SUM(table[[#Data];[NewHeader2]])"), + m_pDoc->GetFormula(1, 5, 0)); + + // Set header in column B to empty string, effectively clearing the cell. + rDocFunc.SetStringCell(ScAddress(1, 0, 0), "", true); + // Verify that formula is still using the previous table column name. + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", OUString("=SUM(table[[#Data];[NewHeader2]])"), + m_pDoc->GetFormula(1, 5, 0)); + + // === header-less === + + { + ScDBCollection* pDBs = m_pDoc->GetDBCollection(); + CPPUNIT_ASSERT_MESSAGE("Failed to fetch DB collection object.", pDBs); + + // Insert "headerless" database range definition for E10:F12, without headers. + std::unique_ptr<ScDBData> pData(new ScDBData("hltable", 0, 4, 9, 5, 11, true, false)); + bool bInserted = pDBs->getNamedDBs().insert(std::move(pData)); + CPPUNIT_ASSERT_MESSAGE("Failed to insert \"hltable\" database range.", bInserted); + } + + { + // Populate "hltable" database range with data in E10:F12 + std::vector<std::vector<const char*>> aData + = { { "1", "2" }, { "4", "8" }, { "16", "32" } }; + ScAddress aPos(4, 9, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + } + + // Named expressions that use header-less Table structured references. + static const struct + { + const char* pName; + const char* pExpr; + const char* + pCounta; // expected result when used in row 10 (first data row) as argument to COUNTA() + const char* + pSum3; // expected result when used in row 11 (second data row) as argument to SUM(). + const char* + pSum4; // expected result when used in row 12 (third data row) as argument to SUM(). + const char* + pSumX; // expected result when used in row 13 (non-intersecting) as argument to SUM(). + } aHlNames[] + = { { "hl_all", "hltable[[#All]]", "6", "63", "63", "63" }, + { "hl_data_implicit", "hltable[]", "6", "63", "63", "63" }, + { "hl_data", "hltable[[#Data]]", "6", "63", "63", "63" }, + { "hl_headers", "hltable[[#Headers]]", "1", "#REF!", "#REF!", "#REF!" }, + { "hl_column1", "hltable[[Column1]]", "3", "21", "21", "21" }, + { "hl_column2", "hltable[[Column2]]", "3", "42", "42", "42" }, + { "hl_data_column1", "hltable[[#Data];[Column1]]", "3", "21", "21", "21" }, + { "hl_data_column2", "hltable[[#Data];[Column2]]", "3", "42", "42", "42" }, + { "hl_this_row", "hltable[[#This Row]]", "2", "12", "48", "#VALUE!" }, + { "hl_this_row_column1", "hltable[[#This Row];[Column1]]", "1", "4", "16", "#VALUE!" }, + { "hl_this_row_column2", "hltable[[#This Row];[Column2]]", "1", "8", "32", "#VALUE!" }, + { "hl_this_row_range_column_1_to_2", "hltable[[#This Row];[Column1]:[Column2]]", "2", + "12", "48", "#VALUE!" } }; + + { + // Insert named expressions. + ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); + CPPUNIT_ASSERT_MESSAGE("Failed to obtain global named expression object.", pGlobalNames); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) + { + // Choose base position that does not intersect with the database + // range definition to test later use of [#This Row] results in + // proper rows. + ScRangeData* pName + = new ScRangeData(*m_pDoc, OUString::createFromAscii(aHlNames[i].pName), + OUString::createFromAscii(aHlNames[i].pExpr), ScAddress(6, 12, 0), + ScRangeData::Type::Name, formula::FormulaGrammar::GRAM_NATIVE); + bool bInserted = pGlobalNames->insert(pName); + CPPUNIT_ASSERT_MESSAGE(OString(OString::Concat("Failed to insert named expression ") + + aHlNames[i].pName + ".") + .getStr(), + bInserted); + } + } + + // Use the named expressions in COUNTA() formulas, on row 10 that intersects. + for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) + { + OUString aFormula("=COUNTA(" + OUString::createFromAscii(aHlNames[i].pName) + ")"); + ScAddress aPos(7 + i, 9, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aHlNames[i].pCounta)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Use the named expressions in SUM() formulas, on row 11 that intersects. + for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aHlNames[i].pName) + ")"); + ScAddress aPos(7 + i, 10, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aHlNames[i].pSum3)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Use the named expressions in SUM() formulas, on row 12 that intersects. + for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aHlNames[i].pName) + ")"); + ScAddress aPos(7 + i, 11, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aHlNames[i].pSum4)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Use the named expressions in SUM() formulas, on row 13 that does not intersect. + for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aHlNames[i].pName) + ")"); + ScAddress aPos(7 + i, 12, 0); + m_pDoc->SetString(aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aHlNames[i].pSumX)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + // Insert a column at column F to extend database range from column E,F to + // E,F,G. Use ScDocFunc so RefreshDirtyTableColumnNames() is called. + rDocFunc.InsertCells(ScRange(5, 0, 0, 5, m_pDoc->MaxRow(), 0), &aMark, INS_INSCOLS_BEFORE, + false, true); + + // Re-verify the named expression in SUM() formula, on row 12 that + // intersects, now starting at column I, still works. + m_pDoc->CalcAll(); + for (size_t i = 0; i < SAL_N_ELEMENTS(aHlNames); ++i) + { + OUString aFormula("=SUM(" + OUString::createFromAscii(aHlNames[i].pName) + ")"); + ScAddress aPos(8 + i, 11, 0); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + OUString::createFromAscii(aHlNames[i].pSum4)), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + const char* pColumn3Formula = "=SUM(hltable[[#Data];[Column3]])"; + { + // Populate "hltable" database range with data in newly inserted + // column, F10:F12 plus a table formula in F14. The new header should + // result in the internal table column name "Column3" that is used in + // the formula. + std::vector<std::vector<const char*>> aData + = { { "64" }, { "128" }, { "256" }, { "" }, { pColumn3Formula } }; + ScAddress aPos(5, 9, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + } + + // Verify the formula result in F14 (64+128+256=448). + { + OUString aFormula(OUString::createFromAscii(pColumn3Formula)); + ScAddress aPos(5, 13, 0); + OUString aPrefix(aPos.Format(ScRefFlags::VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL(OUString(aPrefix + "448"), + OUString(aPrefix + m_pDoc->GetString(aPos))); + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncFTEST) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "FTest"); + + ScAddress aPos(6, 0, 0); + m_pDoc->SetString(aPos, "=FTEST(A1:C3;D1:F3)"); + m_pDoc->SetValue(0, 0, 0, 9.0); // A1 + OUString aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("FTEST should return #VALUE! for less than 2 values", + OUString("#VALUE!"), aVal); + m_pDoc->SetValue(0, 1, 0, 8.0); // A2 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("FTEST should return #VALUE! for less than 2 values", + OUString("#VALUE!"), aVal); + m_pDoc->SetValue(3, 0, 0, 5.0); // D1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("FTEST should return #VALUE! for less than 2 values", + OUString("#VALUE!"), aVal); + m_pDoc->SetValue(3, 1, 0, 6.0); // D2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 1.0000, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(1, 0, 0, 6.0); // B1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.6222, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(1, 1, 0, 8.0); // B2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.7732, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(4, 0, 0, 7.0); // E1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.8194, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(4, 1, 0, 4.0); // E2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.9674, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(2, 0, 0, 3.0); // C1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.3402, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(5, 0, 0, 28.0); // F1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0161, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(2, 1, 0, 9.0); // C2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0063, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(5, 1, 0, 4.0); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0081, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(0, 2, 0, 2.0); // A3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0122, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(3, 2, 0, 8.0); // D3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0178, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(1, 2, 0, 4.0); // B3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0093, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(4, 2, 0, 7.0); // E3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0132, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(5, 2, 0, 5.0); // F3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0168, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(2, 2, 0, 13.0); // C3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0422, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->SetString(0, 2, 0, "a"); // A3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0334, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetString(2, 0, 0, "b"); // C1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0261, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetString(5, 1, 0, "c"); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0219, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetString(4, 2, 0, "d"); // E3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0161, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetString(3, 2, 0, "e"); // D3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.0110, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->DeleteTab(0); + m_pDoc->InsertTab(0, "FTest2"); + + /* Summary of the following test + A1:A5 = SQRT(C1*9/10)*{ 1.0, 1.0, 1.0, 1.0, 1.0 }; + A6:A10 = -SQRT(C1*9/10)*{ 1.0, 1.0, 1.0, 1.0, 1.0 }; + B1:B10 = SQRT(C2*19/20)*{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; + B11:B20 = -SQRT(C2*19/20)*{ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }; + C1 = POWER(1.5, D1) ; This is going to be the sample variance of the vector A1:A10 + C2 = POWER(1.5, D2) ; This is going to be the sample variance of the vector B1:B20 + D1 and D2 are varied over { -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 } + + Result of FTEST(A1:A10;B1:B20) in Calc is compared with that from Octave's var_test() function for each value of D1 and D2. + + The minimum variance ratio obtained in this way is 0.017342 and the maximum variance ratio is 57.665039 + */ + + const size_t nNumParams = 11; + const double fParameter[nNumParams] + = { -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 }; + + // Results of var_test() from Octave + const double fResults[nNumParams][nNumParams] = { + { 0.9451191535603041, 0.5429768686792684, 0.213130093422756, 0.06607644828558357, + 0.0169804365506927, 0.003790723514148109, 0.0007645345628801703, 0.0001435746909905777, + 2.566562398786942e-05, 4.436218417280813e-06, 7.495090956766148e-07 }, + { 0.4360331979746912, 0.9451191535603054, 0.5429768686792684, 0.2131300934227565, + 0.06607644828558357, 0.0169804365506927, 0.003790723514148109, 0.0007645345628801703, + 0.0001435746909905777, 2.566562398786942e-05, 4.436218417280813e-06 }, + { 0.1309752286653509, 0.4360331979746914, 0.9451191535603058, 0.5429768686792684, + 0.2131300934227565, 0.06607644828558357, 0.0169804365506927, 0.003790723514148109, + 0.0007645345628801703, 0.0001435746909905777, 2.566562398786942e-05 }, + { 0.02453502500565108, 0.1309752286653514, 0.4360331979746914, 0.9451191535603058, + 0.5429768686792689, 0.2131300934227565, 0.06607644828558357, 0.0169804365506927, + 0.003790723514148109, 0.0007645345628801703, 0.0001435746909905777 }, + { 0.002886791075972228, 0.02453502500565108, 0.1309752286653514, 0.4360331979746914, + 0.9451191535603041, 0.5429768686792689, 0.2131300934227565, 0.06607644828558357, + 0.0169804365506927, 0.003790723514148109, 0.0007645345628801703 }, + { 0.0002237196492846927, 0.002886791075972228, 0.02453502500565108, 0.1309752286653509, + 0.4360331979746912, 0.9451191535603036, 0.5429768686792689, 0.2131300934227565, + 0.06607644828558357, 0.0169804365506927, 0.003790723514148109 }, + { 1.224926820153627e-05, 0.0002237196492846927, 0.002886791075972228, 0.02453502500565108, + 0.1309752286653509, 0.4360331979746914, 0.9451191535603054, 0.5429768686792684, + 0.2131300934227565, 0.06607644828558357, 0.0169804365506927 }, + { 5.109390206481379e-07, 1.224926820153627e-05, 0.0002237196492846927, 0.002886791075972228, + 0.02453502500565108, 0.1309752286653509, 0.4360331979746914, 0.9451191535603058, + 0.5429768686792684, 0.213130093422756, 0.06607644828558357 }, + { 1.739106880727093e-08, 5.109390206481379e-07, 1.224926820153627e-05, + 0.0002237196492846927, 0.002886791075972228, 0.02453502500565086, 0.1309752286653509, + 0.4360331979746914, 0.9451191535603041, 0.5429768686792684, 0.2131300934227565 }, + { 5.111255862999542e-10, 1.739106880727093e-08, 5.109390206481379e-07, + 1.224926820153627e-05, 0.0002237196492846927, 0.002886791075972228, 0.02453502500565108, + 0.1309752286653516, 0.4360331979746914, 0.9451191535603058, 0.5429768686792684 }, + { 1.354649725726631e-11, 5.111255862999542e-10, 1.739106880727093e-08, + 5.109390206481379e-07, 1.224926820153627e-05, 0.0002237196492846927, 0.002886791075972228, + 0.02453502500565108, 0.1309752286653509, 0.4360331979746914, 0.9451191535603054 } + }; + + m_pDoc->SetValue(3, 0, 0, fParameter[0]); // D1 + m_pDoc->SetValue(3, 1, 0, fParameter[0]); // D2 + aPos.Set(2, 0, 0); // C1 + m_pDoc->SetString(aPos, "=POWER(1.5;D1)"); // C1 + aPos.Set(2, 1, 0); // C2 + m_pDoc->SetString(aPos, "=POWER(1.5;D2)"); // C2 + for (SCROW nRow = 0; nRow < 5; + ++nRow) // Set A1:A5 = SQRT(C1*9/10), and A6:A10 = -SQRT(C1*9/10) + { + aPos.Set(0, nRow, 0); + m_pDoc->SetString(aPos, "=SQRT(C1*9/10)"); + aPos.Set(0, nRow + 5, 0); + m_pDoc->SetString(aPos, "=-SQRT(C1*9/10)"); + } + + for (SCROW nRow = 0; nRow < 10; + ++nRow) // Set B1:B10 = SQRT(C2*19/20), and B11:B20 = -SQRT(C2*19/20) + { + aPos.Set(1, nRow, 0); + m_pDoc->SetString(aPos, "=SQRT(C2*19/20)"); + aPos.Set(1, nRow + 10, 0); + m_pDoc->SetString(aPos, "=-SQRT(C2*19/20)"); + } + + aPos.Set(4, 0, 0); // E1 + m_pDoc->SetString(aPos, "=FTEST(A1:A10;B1:B20)"); + aPos.Set(4, 1, 0); // E2 + m_pDoc->SetString(aPos, "=FTEST(B1:B20;A1:A10)"); + + ScAddress aPosRev(4, 1, 0); // E2 + aPos.Set(4, 0, 0); // E1 + + for (size_t nFirstIdx = 0; nFirstIdx < nNumParams; ++nFirstIdx) + { + m_pDoc->SetValue(3, 0, 0, fParameter[nFirstIdx]); // Set D1 + for (size_t nSecondIdx = 0; nSecondIdx < nNumParams; ++nSecondIdx) + { + m_pDoc->SetValue(3, 1, 0, fParameter[nSecondIdx]); // Set D2 + double fExpected = fResults[nFirstIdx][nSecondIdx]; + // Here a dynamic error limit is used. This is to handle correctly when the expected value is lower than the fixed error limit of 10e-5 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", fExpected, + m_pDoc->GetValue(aPos), + std::min(10e-5, fExpected * 0.0001)); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", fExpected, + m_pDoc->GetValue(aPosRev), + std::min(10e-5, fExpected * 0.0001)); + } + } + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncFTESTBug) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "FTest"); + + ScAddress aPos(9, 0, 0); + m_pDoc->SetString(aPos, "=FTEST(H1:H3;I1:I3)"); + + m_pDoc->SetValue(7, 0, 0, 9.0); // H1 + m_pDoc->SetValue(7, 1, 0, 8.0); // H2 + m_pDoc->SetValue(7, 2, 0, 6.0); // H3 + m_pDoc->SetValue(8, 0, 0, 5.0); // I1 + m_pDoc->SetValue(8, 1, 0, 7.0); // I2 + // tdf#93329 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of FTEST failed", 0.9046, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncCHITEST) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "ChiTest"); + + ScAddress aPos(6, 0, 0); + // 2x2 matrices test + m_pDoc->SetString(aPos, "=CHITEST(A1:B2;D1:E2)"); + OUString aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with empty cells", + OUString("Err:502"), aVal); + + m_pDoc->SetValue(0, 0, 0, 1.0); // A1 + m_pDoc->SetValue(0, 1, 0, 2.0); // A2 + m_pDoc->SetValue(1, 0, 0, 2.0); // B1 + m_pDoc->SetValue(1, 1, 0, 1.0); // B2 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrix with empty cells", + OUString("Err:502"), aVal); + + m_pDoc->SetValue(3, 0, 0, 2.0); // D1 + m_pDoc->SetValue(3, 1, 0, 3.0); // D2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.3613, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->SetValue(4, 1, 0, 1.0); // E2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.3613, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(4, 0, 0, 3.0); // E1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.2801, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(4, 0, 0, 0.0); // E1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return #DIV/0 for expected values of 0", + OUString("#DIV/0!"), aVal); + m_pDoc->SetValue(4, 0, 0, 3.0); // E1 + m_pDoc->SetValue(1, 1, 0, 0.0); // B2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1410, + m_pDoc->GetValue(aPos), 10e-4); + + // 3x3 matrices test + m_pDoc->SetString(aPos, "=CHITEST(A1:C3;D1:F3)"); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.7051, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->SetValue(2, 0, 0, 3.0); // C1 + m_pDoc->SetValue(2, 1, 0, 2.0); // C2 + m_pDoc->SetValue(2, 2, 0, 3.0); // C3 + m_pDoc->SetValue(0, 2, 0, 4.0); // A3 + m_pDoc->SetValue(1, 2, 0, 2.0); // B3 + m_pDoc->SetValue(5, 0, 0, 1.0); // F1 + m_pDoc->SetValue(5, 1, 0, 2.0); // F2 + m_pDoc->SetValue(5, 2, 0, 3.0); // F3 + m_pDoc->SetValue(3, 2, 0, 3.0); // D3 + m_pDoc->SetValue(4, 2, 0, 1.0); // E3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1117, + m_pDoc->GetValue(aPos), 10e-4); + + // test with strings + m_pDoc->SetString(4, 2, 0, "a"); // E3 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with strings", + OUString("Err:502"), aVal); + m_pDoc->SetString(1, 2, 0, "a"); // B3 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with strings", + OUString("Err:502"), aVal); + m_pDoc->SetValue(4, 2, 0, 1.0); // E3 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return Err:502 for matrices with strings", + OUString("Err:502"), aVal); + m_pDoc->SetValue(1, 2, 0, 2.0); // B3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1117, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->SetValue(4, 1, 0, 5.0); // E2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0215, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(1, 2, 0, 1.0); // B3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0328, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(5, 0, 0, 3.0); // F1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1648, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(0, 1, 0, 3.0); // A2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1870, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(3, 1, 0, 5.0); // D2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1377, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(3, 2, 0, 4.0); // D3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.1566, + m_pDoc->GetValue(aPos), 10e-4); + + m_pDoc->SetValue(0, 0, 0, 0.0); // A1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0868, + m_pDoc->GetValue(aPos), 10e-4); + + // no convergence error + m_pDoc->SetValue(4, 0, 0, 1.0E308); // E1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL(OUString("Err:523"), aVal); + m_pDoc->SetValue(4, 0, 0, 3.0); // E1 + + // zero in all cells + m_pDoc->SetValue(0, 1, 0, 0.0); // A2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0150, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(0, 2, 0, 0.0); // A3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0026, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(1, 0, 0, 0.0); // B1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.00079, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(1, 2, 0, 0.0); // B3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0005, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(2, 0, 0, 0.0); // C1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of CHITEST failed", 0.0001, + m_pDoc->GetValue(aPos), 10e-4); + m_pDoc->SetValue(2, 1, 0, 0.0); // C2 + m_pDoc->SetValue(2, 2, 0, 0.0); // C3 + m_pDoc->SetValue(3, 0, 0, 0.0); // D1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return #DIV/0! for matrices with empty", + OUString("#DIV/0!"), aVal); + m_pDoc->SetValue(3, 1, 0, 0.0); // D2 + m_pDoc->SetValue(3, 2, 0, 0.0); // D3 + m_pDoc->SetValue(4, 0, 0, 0.0); // E1 + m_pDoc->SetValue(4, 1, 0, 0.0); // E2 + m_pDoc->SetValue(4, 2, 0, 0.0); // E3 + m_pDoc->SetValue(5, 0, 0, 0.0); // F1 + m_pDoc->SetValue(5, 1, 0, 0.0); // F2 + m_pDoc->SetValue(5, 2, 0, 0.0); // F3 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("CHITEST should return #DIV/0! for matrices with empty", + OUString("#DIV/0!"), aVal); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncTTEST) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "TTest"); + + ScAddress aPos(6, 0, 0); + // type 1, mode/tails 1 + m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;1;1)"); + OUString aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("TTEST should return #VALUE! for empty matrices", + OUString("#VALUE!"), aVal); + + m_pDoc->SetValue(0, 0, 0, 8.0); // A1 + m_pDoc->SetValue(1, 0, 0, 2.0); // B1 + m_pDoc->SetValue(3, 0, 0, 3.0); // D1 + m_pDoc->SetValue(4, 0, 0, 1.0); // E1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.18717, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(2, 0, 0, 1.0); // C1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.18717, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 0, 0, 6.0); // F1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.45958, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(0, 1, 0, -4.0); // A2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.45958, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(3, 1, 0, 1.0); // D2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.35524, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(1, 1, 0, 5.0); // B2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.35524, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(4, 1, 0, -2.0); // E2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.41043, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(2, 1, 0, -1.0); // C2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.41043, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 1, 0, -3.0); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34990, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(0, 2, 0, 10.0); // A3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34990, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(3, 2, 0, 10.0); // D3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34686, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(1, 2, 0, 3.0); // B3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.34686, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(4, 2, 0, 9.0); // E3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.47198, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(2, 2, 0, -5.0); // C3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.47198, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 2, 0, 6.0); // F3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.25529, + m_pDoc->GetValue(aPos), 10e-5); + + m_pDoc->SetString(1, 1, 0, "a"); // B2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.12016, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetString(4, 1, 0, "b"); // E2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.12016, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetString(2, 2, 0, "c"); // C3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.25030, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetString(5, 1, 0, "d"); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.19637, + m_pDoc->GetValue(aPos), 10e-5); + + // type 1, mode/tails 2 + m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;2;1)"); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.39273, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(1, 1, 0, 4.0); // B2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.39273, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(4, 1, 0, 3.0); // E2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.43970, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(2, 2, 0, -2.0); // C3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.22217, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 1, 0, -10.0); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.64668, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(0, 1, 0, 3.0); // A2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.95266, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(3, 2, 0, -1.0); // D3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.62636, + m_pDoc->GetValue(aPos), 10e-5); + + // type 2, mode/tails 2 + m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;2;2)"); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.62549, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 1, 0, -1.0); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.94952, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(2, 2, 0, 5.0); // C3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.58876, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(2, 1, 0, 2.0); // C2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.43205, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(3, 2, 0, -4.0); // D3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.36165, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(0, 1, 0, 1.0); // A2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.44207, + m_pDoc->GetValue(aPos), 10e-5); + + // type 3, mode/tails 1 + m_pDoc->SetString(aPos, "=TTEST(A1:C3;D1:F3;1;3)"); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.22132, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(0, 0, 0, 1.0); // A1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.36977, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(0, 2, 0, -30.0); // A3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.16871, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(3, 1, 0, 5.0); // D2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.14396, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 1, 0, 2.0); // F2 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.12590, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(4, 2, 0, 2.0); // E3 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.16424, + m_pDoc->GetValue(aPos), 10e-5); + m_pDoc->SetValue(5, 0, 0, -1.0); // F1 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Calculation of TTEST failed", 0.21472, + m_pDoc->GetValue(aPos), 10e-5); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncSUMX2PY2) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "SumX2PY2 Test"); + + OUString aVal; + ScAddress aPos(6, 0, 0); + m_pDoc->SetString(aPos, "=SUMX2PY2(A1:C3;D1:F3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 0.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetValue(0, 0, 0, 1.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(3, 0, 0, 2.0); // D1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 2.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(4, 0, 0, 0.0); // E1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 9.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 0, 0, 3.0); // C1 + m_pDoc->SetValue(5, 0, 0, 3.0); // F1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 27.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 10.0); // A2 + m_pDoc->SetValue(3, 1, 0, -10.0); // D2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 227.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, -5.0); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 227.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(4, 1, 0, -5.0); // E2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 277.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 1, 0, 0.0); // C2 + m_pDoc->SetValue(5, 1, 0, 0.0); // F2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 277.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 2, 0, -8.0); // A3 + m_pDoc->SetValue(3, 2, 0, 8.0); // D3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 405.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 2, 0, 0.0); // B3 + m_pDoc->SetValue(4, 2, 0, 0.0); // E3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 405.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 2, 0, 1.0); // C3 + m_pDoc->SetValue(5, 2, 0, 1.0); // F3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 407.0, m_pDoc->GetValue(aPos)); + + // add some strings + m_pDoc->SetString(4, 1, 0, "a"); // E2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 357.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(1, 1, 0, "a"); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 357.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(0, 0, 0, "a"); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 352.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(3, 0, 0, "a"); // D1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 352.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=SUMX2PY2({1;2;3};{2;3;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2PY2 failed", 43.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMX2PY2({1;2;3};{2;3})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2PY2 should return #VALUE! for matrices with different sizes", + OUString("#VALUE!"), aVal); + m_pDoc->SetString(aPos, "=SUMX2PY2({1;2;3})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2PY2 needs two parameters", OUString("Err:511"), aVal); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncSUMX2MY2) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "SumX2MY2 Test"); + + OUString aVal; + ScAddress aPos(6, 0, 0); + m_pDoc->SetString(aPos, "=SUMX2MY2(A1:C3;D1:F3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 0.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetValue(0, 0, 0, 10.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(3, 0, 0, -9.0); // D1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 19.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 2.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 19.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(4, 0, 0, 1.0); // E1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 0, 0, 3.0); // C1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(5, 0, 0, 3.0); // F1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 10.0); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(3, 1, 0, -10.0); // D2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, -5.0); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(4, 1, 0, -5.0); // E2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 1, 0, -3.0); // C2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(5, 1, 0, 3.0); // F2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 2, 0, -8.0); // A3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 22.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(3, 2, 0, 3.0); // D3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 77.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 2, 0, 2.0); // B3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 77.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(4, 2, 0, -6.0); // E3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 45.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 2, 0, -4.0); // C3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 45.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(5, 2, 0, 6.0); // F3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 25.0, m_pDoc->GetValue(aPos)); + + // add some strings + m_pDoc->SetString(5, 2, 0, "a"); // F3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 45.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(0, 2, 0, "a"); // A3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", -10.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(1, 0, 0, "a"); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", -13.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(3, 0, 0, "a"); // D1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", -32.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=SUMX2MY2({1;3;5};{0;4;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 3.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMX2MY2({1;-3;-5};{0;-4;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 3.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMX2MY2({9;5;1};{3;-3;3})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMX2MY2 failed", 80.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMX2MY2({1;2;3};{2;3})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2MY2 should return #VALUE! for matrices with different sizes", + OUString("#VALUE!"), aVal); + m_pDoc->SetString(aPos, "=SUMX2MY2({1;2;3})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMX2MY2 needs two parameters", OUString("Err:511"), aVal); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncGCD) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "GCDTest"); + + OUString aVal; + ScAddress aPos(4, 0, 0); + + m_pDoc->SetString(aPos, "=GCD(A1)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 10.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 10.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, -2.0); // A1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetString(0, 0, 0, "a"); // A1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return #VALUE! for a single string", + OUString("#VALUE!"), aVal); + + m_pDoc->SetString(aPos, "=GCD(A1:B2)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, -12.0); // B1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for a matrix with values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetValue(0, 0, 0, 15.0); // A1 + m_pDoc->SetValue(0, 1, 0, 0.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 15.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 5.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 10.0); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 30.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 20.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 10.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, 120.0); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 10.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 80.0); // A2 + m_pDoc->SetValue(1, 0, 0, 40.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 20.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 45.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + + // with floor + m_pDoc->SetValue(1, 0, 0, 45.381); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, 120.895); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 20.97); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 10.15); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 5.0, m_pDoc->GetValue(aPos)); + + // inline array + m_pDoc->SetString(aPos, "=GCD({3;6;9})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 3.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=GCD({150;0})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 150.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=GCD({-3;6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetString(aPos, "=GCD({\"a\";6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with strings", + OUString("Err:502"), aVal); + + //many inline array + m_pDoc->SetString(aPos, "=GCD({6;6;6};{3;6;9})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 3.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=GCD({300;300;300};{150;0})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 150.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=GCD({3;6;9};{3;-6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetString(aPos, "=GCD({3;6;9};{\"a\";6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return Err:502 for an array with strings", + OUString("Err:502"), aVal); + + // inline list of values + m_pDoc->SetString(aPos, "=GCD(12;24;36;48;60)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 12.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=GCD(0;12;24;36;48;60)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 12.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=GCD(\"a\";1)"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("GCD should return #VALUE! for an array with strings", + OUString("#VALUE!"), aVal); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncLCM) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "LCMTest"); + + OUString aVal; + ScAddress aPos(4, 0, 0); + + m_pDoc->SetString(aPos, "=LCM(A1)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 10.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 10.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, -2.0); // A1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetString(0, 0, 0, "a"); // A1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return #VALUE! for a single string", + OUString("#VALUE!"), aVal); + + m_pDoc->SetString(aPos, "=LCM(A1:B2)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of GCD for failed", 1.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, -12.0); // B1 + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for a matrix with values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetValue(0, 0, 0, 15.0); // A1 + m_pDoc->SetValue(0, 1, 0, 0.0); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 5.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 10.0); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 30.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 30.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 30.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 20.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 60.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, 125.0); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 1500.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 99.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 49500.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 37.0); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 1831500.0, + m_pDoc->GetValue(aPos)); + + // with floor + m_pDoc->SetValue(1, 0, 0, 99.89); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 1831500.0, + m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, 11.32); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 73260.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 22.58); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 7326.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, 3.99); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 198.0, m_pDoc->GetValue(aPos)); + + // inline array + m_pDoc->SetString(aPos, "=LCM({3;6;9})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 18.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=LCM({150;0})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=LCM({-3;6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetString(aPos, "=LCM({\"a\";6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with strings", + OUString("Err:502"), aVal); + + //many inline array + m_pDoc->SetString(aPos, "=LCM({6;6;6};{3;6;9})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 18.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=LCM({300;300;300};{150;0})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=LCM({3;6;9};{3;-6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with values less than 0", + OUString("Err:502"), aVal); + m_pDoc->SetString(aPos, "=LCM({3;6;9};{\"a\";6;9})"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return Err:502 for an array with strings", + OUString("Err:502"), aVal); + + m_pDoc->SetString(aPos, "=LCM(12;24;36;48;60)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 720.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=LCM(0;12;24;36;48;60)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of LCM for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=LCM(\"a\";1)"); + aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("LCM should return #VALUE! for an array with strings", + OUString("#VALUE!"), aVal); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncSUMSQ) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "SUMSQTest"); + + ScAddress aPos(4, 0, 0); + + m_pDoc->SetString(aPos, "=SUMSQ(A1)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 0.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 1.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 1.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, -1.0); // A1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 1.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 1, 0, -2.0); // A2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 1.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=SUMSQ(A1:A3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 0, 0, 3.0); // B1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 5.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ(A1:C3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 14.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 1, 0, -4.0); // B2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 30.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(1, 2, 0, "a"); // B3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 30.0, + m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(1, 2, 0, 0.0); // B3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 30.0, + m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 2, 0, 6.0); // A3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 66.0, + m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 0, 0, -5.0); // C1 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 91.0, + m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 1, 0, 3.0); // C2 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 100.0, + m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(2, 2, 0, 2.0); // C3 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ with a string for failed", 104.0, + m_pDoc->GetValue(aPos)); + + // inline array + m_pDoc->SetString(aPos, "=SUMSQ({1;2;3})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 14.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({3;6;9})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 126.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({15;0})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 225.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({-3;3;1})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 19.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({\"a\";-4;-5})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 41.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=SUMSQ({2;3};{4;5})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 54.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({-3;3;1};{-1})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 20.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({-4};{1;4;2};{-5;7};{9})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 192.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ({-2;2};{1};{-1};{0;0;0;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 26.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=SUMSQ(4;1;-3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 26.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ(0;5;13;-7;-4)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 259.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ(0;12;24;36;48;60)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 7920.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ(0;-12;-24;36;-48;60)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUMSQ for failed", 7920.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetString(aPos, "=SUMSQ(\"a\";1;\"d\";-4;2)"); + OUString aVal = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMSQ should return #VALUE! for an array with strings", + OUString("#VALUE!"), aVal); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncMDETERM) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "MDETERM_test"); + ScAddress aPos(8, 0, 0); + OUString const aColCodes("ABCDEFGH"); + OUStringBuffer aFormulaBuffer("=MDETERM(A1:B2)"); + for (SCSIZE nSize = 3; nSize <= 8; nSize++) + { + double fVal = 1.0; + // Generate a singular integer matrix + for (SCROW nRow = 0; nRow < static_cast<SCROW>(nSize); nRow++) + { + for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(nSize); nCol++) + { + m_pDoc->SetValue(nCol, nRow, 0, fVal); + fVal += 1.0; + } + } + aFormulaBuffer[12] = aColCodes[nSize - 1]; + aFormulaBuffer[13] = static_cast<sal_Unicode>('0' + nSize); + m_pDoc->SetString(aPos, aFormulaBuffer.toString()); + +#if SAL_TYPES_SIZEOFPOINTER == 4 + // On crappy 32-bit targets, presumably without extended precision on + // interim results or optimization not catching it, this test fails + // when comparing to 0.0, so have a narrow error margin. See also + // commit message of 8140309d636d4a870875f2dd75ed3dfff2c0fbaf + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "Calculation of MDETERM incorrect for singular integer matrix", 0.0, + m_pDoc->GetValue(aPos), 1e-12); +#else + // Even on one (and only one) x86_64 target the result was + // 6.34413156928661e-17 instead of 0.0 (tdf#99730) so lower the bar to + // 10e-14. + // Then again on aarch64, ppc64* and s390x it also fails. + // Sigh... why do we even test this? The original complaint in tdf#32834 + // was about -9.51712667007776E-016 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "Calculation of MDETERM incorrect for singular integer matrix", 0.0, + m_pDoc->GetValue(aPos), 1e-14); +#endif + } + + int const aVals[] = { 23, 31, 13, 12, 34, 64, 34, 31, 98, 32, 33, 63, 45, 54, 65, 76 }; + int nIdx = 0; + for (SCROW nRow = 0; nRow < 4; nRow++) + for (SCCOL nCol = 0; nCol < 4; nCol++) + m_pDoc->SetValue(nCol, nRow, 0, static_cast<double>(aVals[nIdx++])); + m_pDoc->SetString(aPos, "=MDETERM(A1:D4)"); + // Following test is conservative in the sense that on Linux x86_64 the error is less that 1.0E-9 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "Calculation of MDETERM incorrect for non-singular integer matrix", -180655.0, + m_pDoc->GetValue(aPos), 1.0E-6); + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaErrorPropagation) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->InsertTab(0, "Sheet1"); + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + ScAddress aPos, aPos2; + const OUString aTRUE("TRUE"); + const OUString aFALSE("FALSE"); + + aPos.Set(0, 0, 0); // A1 + m_pDoc->SetValue(aPos, 1.0); + aPos.IncCol(); // B1 + m_pDoc->SetValue(aPos, 2.0); + aPos.IncCol(); + + aPos.IncRow(); // C2 + m_pDoc->SetString(aPos, "=ISERROR(A1:B1+3)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos)); + + aPos.IncRow(); // C3 + m_pDoc->SetString(aPos, "=ISERROR(A1:B1+{3})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos)); + aPos.IncRow(); // C4 + aPos2 = aPos; + aPos2.IncCol(); // D4 + m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, + "=ISERROR(A1:B1+{3})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos2)); + + aPos.IncRow(); // C5 + m_pDoc->SetString(aPos, "=ISERROR({1;\"x\"}+{3;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos)); + aPos.IncRow(); // C6 + aPos2 = aPos; + aPos2.IncCol(); // D6 + m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, + "=ISERROR({1;\"x\"}+{3;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos2)); + + aPos.IncRow(); // C7 + m_pDoc->SetString(aPos, "=ISERROR({\"x\";2}+{3;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos)); + aPos.IncRow(); // C8 + aPos2 = aPos; + aPos2.IncCol(); // D8 + m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, + "=ISERROR({\"x\";2}+{3;4})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos2)); + + aPos.IncRow(); // C9 + m_pDoc->SetString(aPos, "=ISERROR(({1;\"x\"}+{3;4})-{5;6})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos)); + aPos.IncRow(); // C10 + aPos2 = aPos; + aPos2.IncCol(); // D10 + m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, + "=ISERROR(({1;\"x\"}+{3;4})-{5;6})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos2)); + + aPos.IncRow(); // C11 + m_pDoc->SetString(aPos, "=ISERROR(({\"x\";2}+{3;4})-{5;6})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos)); + aPos.IncRow(); // C12 + aPos2 = aPos; + aPos2.IncCol(); // D12 + m_pDoc->InsertMatrixFormula(aPos.Col(), aPos.Row(), aPos2.Col(), aPos2.Row(), aMark, + "=ISERROR(({\"x\";2}+{3;4})-{5;6})"); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos.Format(ScRefFlags::VALID).toUtf8().getStr(), aTRUE, + m_pDoc->GetString(aPos)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(aPos2.Format(ScRefFlags::VALID).toUtf8().getStr(), aFALSE, + m_pDoc->GetString(aPos2)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf97369) +{ + const SCROW TOTAL_ROWS = 330; + const SCROW ROW_RANGE = 10; + const SCROW START1 = 9; + const SCROW END1 = 159; + const SCROW START2 = 169; + const SCROW END2 = 319; + + const double SHIFT1 = 200; + const double SHIFT2 = 400; + + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "tdf97369")); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + + // set up columns A, B, C + for (SCROW i = 0; i < TOTAL_ROWS; ++i) + { + m_pDoc->SetValue(ScAddress(0, i, 0), i); // A + m_pDoc->SetValue(ScAddress(1, i, 0), i + SHIFT1); // B + m_pDoc->SetValue(ScAddress(2, i, 0), i + SHIFT2); // C + } + + const ColumnTest columnTest(m_pDoc, TOTAL_ROWS, START1, END1, START2, END2); + + auto lExpectedinD = [=](SCROW n) { return 3.0 * (n - START1) + SHIFT1 + SHIFT2; }; + columnTest(3, "=SUM(A1:C1)", lExpectedinD); + + auto lExpectedinE = [=](SCROW) { return SHIFT1 + SHIFT2; }; + columnTest(4, "=SUM(A$1:C$1)", lExpectedinE); + + auto lExpectedinF = + [ +#if defined _MSC_VER && !defined __clang__ && __cplusplus <= 201703L + // see <https://developercommunity2.visualstudio.com/t/ + // Lambdas-require-capturing-constant-value/907628> "Lambdas require capturing constant + // values" + ROW_RANGE +#endif + ](SCROW n) { return ((2 * n + 1 - ROW_RANGE) * ROW_RANGE) / 2.0; }; + columnTest(5, "=SUM(A1:A10)", lExpectedinF); + + auto lExpectedinG = [](SCROW n) { return ((n + 1) * n) / 2.0; }; + columnTest(6, "=SUM(A$1:A10)", lExpectedinG); + + auto lExpectedinH = [=](SCROW n) { + return 3.0 * (((2 * n + 1 - ROW_RANGE) * ROW_RANGE) / 2) + ROW_RANGE * (SHIFT1 + SHIFT2); + }; + columnTest(7, "=SUM(A1:C10)", lExpectedinH); + + auto lExpectedinI = [=](SCROW) { + return 3.0 * (((2 * START1 + 1 - ROW_RANGE) * ROW_RANGE) / 2) + + ROW_RANGE * (SHIFT1 + SHIFT2); + }; + columnTest(8, "=SUM(A$1:C$10)", lExpectedinI); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf97587) +{ + const SCROW TOTAL_ROWS = 150; + const SCROW ROW_RANGE = 10; + + CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(0, "tdf97587")); + + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. + + std::set<SCROW> emptyCells = { 0, 100 }; + for (SCROW i = 0; i < ROW_RANGE; ++i) + { + emptyCells.insert(i + TOTAL_ROWS / 3); + emptyCells.insert(i + TOTAL_ROWS); + } + + // set up columns A + for (SCROW i = 0; i < TOTAL_ROWS; ++i) + { + if (emptyCells.find(i) != emptyCells.end()) + continue; + m_pDoc->SetValue(ScAddress(0, i, 0), 1.0); + } + + ScDocument aClipDoc(SCDOCMODE_CLIP); + ScMarkData aMark(m_pDoc->GetSheetLimits()); + + ScAddress aPos(1, 0, 0); + m_pDoc->SetString(aPos, "=SUM(A1:A10)"); + + // Copy formula cell to clipboard. + ScClipParam aClipParam(aPos, false); + aMark.SetMarkArea(aPos); + m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMark, false, false); + + // Paste it to first range. + ScRange aDestRange(1, 1, 0, 1, TOTAL_ROWS + ROW_RANGE, 0); + aMark.SetMarkArea(aDestRange); + m_pDoc->CopyFromClip(aDestRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc); + + // Check the formula results in column B. + for (SCROW i = 0; i < TOTAL_ROWS + 1; ++i) + { + int k = std::count_if(emptyCells.begin(), emptyCells.end(), + [=](SCROW n) { return (i <= n && n < i + ROW_RANGE); }); + double fExpected = ROW_RANGE - k; + ASSERT_DOUBLES_EQUAL(fExpected, m_pDoc->GetValue(ScAddress(1, i, 0))); + } + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf93415) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Sheet1")); + + ScCalcConfig aConfig; + aConfig.SetStringRefSyntax(formula::FormulaGrammar::CONV_XL_R1C1); + m_pDoc->SetCalcConfig(aConfig); + m_pDoc->CalcAll(); + + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, "=ADDRESS(1;1;;;\"Sheet1\")"); + + // Without the fix in place, this would have failed with + // - Expected: Sheet1!$A$1 + // - Actual : Sheet1.$A$1 + CPPUNIT_ASSERT_EQUAL(OUString("Sheet1!$A$1"), m_pDoc->GetString(aPos)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf132519) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Sheet1")); + + ScCalcConfig aConfig; + aConfig.SetStringRefSyntax(formula::FormulaGrammar::CONV_XL_R1C1); + m_pDoc->SetCalcConfig(aConfig); + m_pDoc->CalcAll(); + + m_pDoc->SetString(2, 0, 0, "X"); + m_pDoc->SetString(1, 0, 0, "=CELL(\"ADDRESS\"; C1)"); + m_pDoc->SetString(0, 0, 0, "=INDIRECT(B1)"); + + // Without the fix in place, this test would have failed with + // - Expected: X + // - Actual : #REF! + CPPUNIT_ASSERT_EQUAL(OUString("X"), m_pDoc->GetString(0, 0, 0)); + + CPPUNIT_ASSERT_EQUAL(OUString("R1C3"), m_pDoc->GetString(1, 0, 0)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf100818) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Sheet1")); + + //Insert local range name + ScRangeData* pLocal = new ScRangeData(*m_pDoc, "local", "$Sheet1.$A$1"); + std::unique_ptr<ScRangeName> pLocalRangeName(new ScRangeName); + pLocalRangeName->insert(pLocal); + m_pDoc->SetRangeName(0, std::move(pLocalRangeName)); + + m_pDoc->SetValue(0, 0, 0, 1.0); + + CPPUNIT_ASSERT(m_pDoc->InsertTab(1, "Sheet2")); + + m_pDoc->SetString(0, 0, 1, "=INDIRECT(\"Sheet1.local\")"); + + // Without the fix in place, this test would have failed with + // - Expected: 1 + // - Actual : #REF! + CPPUNIT_ASSERT_EQUAL(OUString("1"), m_pDoc->GetString(0, 0, 1)); + + m_pDoc->DeleteTab(1); + m_pDoc->SetRangeName(0, nullptr); // Delete the names. + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testMatConcat) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Test")); + + for (SCCOL nCol = 0; nCol < 10; ++nCol) + { + for (SCROW nRow = 0; nRow < 10; ++nRow) + { + m_pDoc->SetValue(ScAddress(nCol, nRow, 0), nCol * nRow); + } + } + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(0, 12, 9, 21, aMark, "=A1:J10&A1:J10"); + + for (SCCOL nCol = 0; nCol < 10; ++nCol) + { + for (SCROW nRow = 12; nRow < 22; ++nRow) + { + OUString aStr = m_pDoc->GetString(ScAddress(nCol, nRow, 0)); + CPPUNIT_ASSERT_EQUAL(OUString(OUString::number(nCol * (nRow - 12)) + + OUString::number(nCol * (nRow - 12))), + aStr); + } + } + + { // Data in A12:B16 + std::vector<std::vector<const char*>> aData = { + { "q", "w" }, { "a", "" }, { "", "x" }, { "", "" }, { "e", "r" }, + }; + + ScAddress aPos(0, 11, 0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData); + CPPUNIT_ASSERT_EQUAL(aPos, aRange.aStart); + } + // Matrix formula in C17:C21 + m_pDoc->InsertMatrixFormula(2, 16, 2, 20, aMark, "=A12:A16&B12:B16"); + // Check proper concatenation including empty cells. + OUString aStr; + ScAddress aPos(2, 16, 0); + aStr = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL(OUString("qw"), aStr); + aPos.IncRow(); + aStr = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL(OUString("a"), aStr); + aPos.IncRow(); + aStr = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL(OUString("x"), aStr); + aPos.IncRow(); + aStr = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL(OUString(), aStr); + aPos.IncRow(); + aStr = m_pDoc->GetString(aPos); + CPPUNIT_ASSERT_EQUAL(OUString("er"), aStr); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testMatConcatReplication) +{ + // if one of the matrices is a one column or row matrix + // the matrix is replicated across the larger matrix + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Test")); + + for (SCCOL nCol = 0; nCol < 10; ++nCol) + { + for (SCROW nRow = 0; nRow < 10; ++nRow) + { + m_pDoc->SetValue(ScAddress(nCol, nRow, 0), nCol * nRow); + } + } + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(0, 12, 9, 21, aMark, "=A1:J10&A1:J1"); + + for (SCCOL nCol = 0; nCol < 10; ++nCol) + { + for (SCROW nRow = 12; nRow < 22; ++nRow) + { + OUString aStr = m_pDoc->GetString(ScAddress(nCol, nRow, 0)); + CPPUNIT_ASSERT_EQUAL(OUString(OUString::number(nCol * (nRow - 12)) + "0"), aStr); + } + } + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testRefR1C1WholeCol) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Test")); + + ScAddress aPos(1, 1, 1); + ScCompiler aComp(*m_pDoc, aPos, FormulaGrammar::GRAM_ENGLISH_XL_R1C1); + std::unique_ptr<ScTokenArray> pTokens(aComp.CompileString("=C[10]")); + sc::TokenStringContext aCxt(*m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH); + OUString aFormula = pTokens->CreateString(aCxt, aPos); + + CPPUNIT_ASSERT_EQUAL(OUString("L:L"), aFormula); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testRefR1C1WholeRow) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Test")); + + ScAddress aPos(1, 1, 1); + ScCompiler aComp(*m_pDoc, aPos, FormulaGrammar::GRAM_ENGLISH_XL_R1C1); + std::unique_ptr<ScTokenArray> pTokens(aComp.CompileString("=R[3]")); + sc::TokenStringContext aCxt(*m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH); + OUString aFormula = pTokens->CreateString(aCxt, aPos); + + CPPUNIT_ASSERT_EQUAL(OUString("5:5"), aFormula); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testSingleCellCopyColumnLabel) +{ + ScDocOptions aOptions = m_pDoc->GetDocOptions(); + aOptions.SetLookUpColRowNames(true); + m_pDoc->SetDocOptions(aOptions); + m_pDoc->InsertTab(0, "Test"); + + m_pDoc->SetString(0, 0, 0, "a"); + m_pDoc->SetValue(0, 1, 0, 1.0); + m_pDoc->SetValue(0, 2, 0, 2.0); + m_pDoc->SetValue(0, 3, 0, 3.0); + m_pDoc->SetString(1, 1, 0, "='a'"); + + double nVal = m_pDoc->GetValue(1, 1, 0); + ASSERT_DOUBLES_EQUAL(1.0, nVal); + + ScDocument aClipDoc(SCDOCMODE_CLIP); + copyToClip(m_pDoc, ScRange(1, 1, 0), &aClipDoc); + pasteOneCellFromClip(m_pDoc, ScRange(1, 2, 0), &aClipDoc); + nVal = m_pDoc->GetValue(1, 2, 0); + ASSERT_DOUBLES_EQUAL(2.0, nVal); + + m_pDoc->DeleteTab(0); +} + +// Significant whitespace operator intersection in Excel syntax, tdf#96426 +CPPUNIT_TEST_FIXTURE(TestFormula2, testIntersectionOpExcel) +{ + CPPUNIT_ASSERT(m_pDoc->InsertTab(0, "Test")); + + ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); + // Horizontal cell range covering C2. + pGlobalNames->insert(new ScRangeData(*m_pDoc, "horz", "$B$2:$D$2")); + // Vertical cell range covering C2. + pGlobalNames->insert(new ScRangeData(*m_pDoc, "vert", "$C$1:$C$3")); + // Data in C2. + m_pDoc->SetValue(2, 1, 0, 1.0); + + FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1); + + // Choose formula positions that don't intersect with those data ranges. + ScAddress aPos(0, 3, 0); + m_pDoc->SetString(aPos, "=B2:D2 C1:C3"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A4 intersecting references failed", 1.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + m_pDoc->SetString(aPos, "=horz vert"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A5 intersecting named expressions failed", 1.0, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + m_pDoc->SetString(aPos, "=(horz vert)*2"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A6 calculating with intersecting named expressions failed", 2.0, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + m_pDoc->SetString(aPos, "=2*(horz vert)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("A7 calculating with intersecting named expressions failed", 2.0, + m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +//Test Subtotal and Aggregate during hide rows #tdf93171 +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncRowsHidden) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + m_pDoc->SetValue(0, 0, 0, 1); //A1 + m_pDoc->SetValue(0, 1, 0, 2); //A2 + m_pDoc->SetValue(0, 2, 0, 4); //A3 + m_pDoc->SetValue(0, 3, 0, 8); //A4 + m_pDoc->SetValue(0, 4, 0, 16); //A5 + m_pDoc->SetValue(0, 5, 0, 32); //A6 + + ScAddress aPos(0, 6, 0); + m_pDoc->SetString(aPos, "=SUBTOTAL(109; A1:A6)"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 63.0, m_pDoc->GetValue(aPos)); + //Hide row 1 + m_pDoc->SetRowHidden(0, 0, 0, true); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 62.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetRowHidden(0, 0, 0, false); + //Hide row 2 and 3 + m_pDoc->SetRowHidden(1, 2, 0, true); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 57.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetRowHidden(1, 2, 0, false); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUBTOTAL failed", 63.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=AGGREGATE(9; 5; A1:A6)"); //9=SUM 5=Ignore only hidden rows + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 63.0, m_pDoc->GetValue(aPos)); + //Hide row 1 + m_pDoc->SetRowHidden(0, 0, 0, true); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 62.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetRowHidden(0, 0, 0, false); + //Hide rows 3 to 5 + m_pDoc->SetRowHidden(2, 4, 0, true); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 35.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetRowHidden(2, 4, 0, false); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of AGGREGATE failed", 63.0, m_pDoc->GetValue(aPos)); + + m_pDoc->SetString(aPos, "=SUM(A1:A6)"); + m_pDoc->SetRowHidden(2, 4, 0, true); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Calculation of SUM failed", 63.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +// Test COUNTIFS, SUMIFS, AVERAGEIFS in array context. +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncSUMIFS) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + // Data in A1:B7, query in A9:A11 + std::vector<std::vector<const char*>> aData = { + { "a", "1" }, { "b", "2" }, { "c", "4" }, { "d", "8" }, + { "a", "16" }, { "b", "32" }, { "c", "64" }, { "" }, // {} doesn't work with some compilers + { "a" }, { "b" }, { "c" }, + }; + + insertRangeData(m_pDoc, ScAddress(0, 0, 0), aData); + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + // Matrix formula in C8:C10 with SUMIFS + m_pDoc->InsertMatrixFormula(2, 7, 2, 9, aMark, "=SUMIFS(B1:B7;A1:A7;A9:A11)"); + // Matrix formula in D8:D10 with COUNTIFS + m_pDoc->InsertMatrixFormula(3, 7, 3, 9, aMark, "=COUNTIFS(A1:A7;A9:A11)"); + // Matrix formula in E8:E10 with AVERAGEIFS + m_pDoc->InsertMatrixFormula(4, 7, 4, 9, aMark, "=AVERAGEIFS(B1:B7;A1:A7;A9:A11)"); + + { + // Result B1+B5, B2+B6, B3+B7 and counts and averages. + std::vector<std::vector<const char*>> aCheck + = { { "17", "2", "8.5" }, { "34", "2", "17" }, { "68", "2", "34" } }; + bool bGood = checkOutput(m_pDoc, ScRange(2, 7, 0, 4, 9, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS in array context"); + CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS in array context failed", bGood); + } + + // Matrix formula in G8:G10 with SUMIFS and reference list arrays. + m_pDoc->InsertMatrixFormula(6, 7, 6, 9, aMark, + "=SUMIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\")"); + // Matrix formula in H8:H10 with COUNTIFS and reference list arrays. + m_pDoc->InsertMatrixFormula(7, 7, 7, 9, aMark, "=COUNTIFS(OFFSET(B1;ROW(1:3);0;2);\">4\")"); + // Matrix formula in I8:I10 with AVERAGEIFS and reference list arrays. + m_pDoc->InsertMatrixFormula( + 8, 7, 8, 9, aMark, "=AVERAGEIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\")"); + + { + // Result sums, counts and averages. + std::vector<std::vector<const char*>> aCheck + = { { "0", "0", "#DIV/0!" }, { "8", "1", "8" }, { "24", "2", "12" } }; + bool bGood = checkOutput(m_pDoc, ScRange(6, 7, 0, 8, 9, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS with reference list arrays"); + CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list arrays failed", + bGood); + } + + // Matrix formula in K8:K10 with SUMIFS and reference list array condition + // and "normal" data range. + m_pDoc->InsertMatrixFormula(10, 7, 10, 9, aMark, + "=SUMIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\")"); + // Matrix formula in L8:L10 with AVERAGEIFS and reference list array + // condition and "normal" data range. + m_pDoc->InsertMatrixFormula(11, 7, 11, 9, aMark, + "=AVERAGEIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\")"); + + { + // Result sums and averages. + std::vector<std::vector<const char*>> aCheck + = { { "0", "#DIV/0!" }, { "2", "2" }, { "3", "1.5" } }; + bool bGood = checkOutput( + m_pDoc, ScRange(10, 7, 0, 11, 9, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS with reference list array and normal range"); + CPPUNIT_ASSERT_MESSAGE( + "SUMIFS, COUNTIFS or AVERAGEIFS with reference list array and normal range failed", + bGood); + } + + // Matrix formula in G18:G20 with SUMIFS and reference list arrays and a + // "normal" criteria range. + m_pDoc->InsertMatrixFormula( + 6, 17, 6, 19, aMark, + "=SUMIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); + // Matrix formula in H18:H20 with COUNTIFS and reference list arrays and a + // "normal" criteria range. + m_pDoc->InsertMatrixFormula(7, 17, 7, 19, aMark, + "=COUNTIFS(OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); + // Matrix formula in I18:I20 with AVERAGEIFS and reference list arrays and + // a "normal" criteria range. + m_pDoc->InsertMatrixFormula( + 8, 17, 8, 19, aMark, + "=AVERAGEIFS(OFFSET(B1;ROW(1:3);0;2);OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); + + { + // Result sums, counts and averages. + std::vector<std::vector<const char*>> aCheck + = { { "0", "0", "#DIV/0!" }, { "8", "1", "8" }, { "16", "1", "16" } }; + bool bGood = checkOutput(m_pDoc, ScRange(6, 17, 0, 8, 19, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS with reference list arrays and a " + "normal criteria range"); + CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list arrays and a " + "normal criteria range failed", + bGood); + } + + // Matrix formula in K18:K20 with SUMIFS and reference list array condition + // and "normal" data range and a "normal" criteria range. + m_pDoc->InsertMatrixFormula(10, 17, 10, 19, aMark, + "=SUMIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); + // Matrix formula in L18:L20 with AVERAGEIFS and reference list array + // condition and "normal" data range and a "normal" criteria range. + m_pDoc->InsertMatrixFormula(11, 17, 11, 19, aMark, + "=AVERAGEIFS(B1:B2;OFFSET(B1;ROW(1:3);0;2);\">4\";B1:B2;\">1\")"); + + { + // Result sums and averages. + std::vector<std::vector<const char*>> aCheck + = { { "0", "#DIV/0!" }, { "2", "2" }, { "2", "2" } }; + bool bGood = checkOutput(m_pDoc, ScRange(10, 17, 0, 11, 19, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS with reference list array and " + "normal data and criteria range"); + CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list array and " + "normal data and criteria range failed", + bGood); + } + + // Same, but swapped normal and array criteria. + + // Matrix formula in G28:G30 with SUMIFS and reference list arrays and a + // "normal" criteria range, swapped. + m_pDoc->InsertMatrixFormula( + 6, 27, 6, 29, aMark, + "=SUMIFS(OFFSET(B1;ROW(1:3);0;2);B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); + // Matrix formula in H28:H30 with COUNTIFS and reference list arrays and a + // "normal" criteria range, swapped. + m_pDoc->InsertMatrixFormula(7, 27, 7, 29, aMark, + "=COUNTIFS(B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); + // Matrix formula in I28:I30 with AVERAGEIFS and reference list arrays and + // a "normal" criteria range, swapped. + m_pDoc->InsertMatrixFormula( + 8, 27, 8, 29, aMark, + "=AVERAGEIFS(OFFSET(B1;ROW(1:3);0;2);B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); + + { + // Result sums, counts and averages. + std::vector<std::vector<const char*>> aCheck + = { { "0", "0", "#DIV/0!" }, { "8", "1", "8" }, { "16", "1", "16" } }; + bool bGood = checkOutput(m_pDoc, ScRange(6, 27, 0, 8, 29, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS with reference list arrays and a " + "normal criteria range, swapped"); + CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list arrays and a " + "normal criteria range failed, swapped", + bGood); + } + + // Matrix formula in K28:K30 with SUMIFS and reference list array condition + // and "normal" data range and a "normal" criteria range, swapped. + m_pDoc->InsertMatrixFormula(10, 27, 10, 29, aMark, + "=SUMIFS(B1:B2;B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); + // Matrix formula in L28:L30 with AVERAGEIFS and reference list array + // condition and "normal" data range and a "normal" criteria range, + // swapped. + m_pDoc->InsertMatrixFormula(11, 27, 11, 29, aMark, + "=AVERAGEIFS(B1:B2;B1:B2;\">1\";OFFSET(B1;ROW(1:3);0;2);\">4\")"); + + { + // Result sums and averages. + std::vector<std::vector<const char*>> aCheck + = { { "0", "#DIV/0!" }, { "2", "2" }, { "2", "2" } }; + bool bGood = checkOutput(m_pDoc, ScRange(10, 27, 0, 11, 29, 0), aCheck, + "SUMIFS, COUNTIFS and AVERAGEIFS with reference list array and " + "normal data and criteria range, swapped"); + CPPUNIT_ASSERT_MESSAGE("SUMIFS, COUNTIFS or AVERAGEIFS with reference list array and " + "normal data and criteria range failed, swapped", + bGood); + } + + m_pDoc->DeleteTab(0); +} + +// Test that COUNTIF counts properly empty cells if asked to. +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncCOUNTIFEmpty) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + // Data in A1:A9. + std::vector<std::vector<const char*>> aData + = { { "" }, { "a" }, { "b" }, { "c" }, { "d" }, { "a" }, { "" }, { "b" }, { "c" } }; + + insertRangeData(m_pDoc, ScAddress(0, 0, 0), aData); + + constexpr SCROW maxRow = 20; // so that the unittest is not slow in dbgutil builds + SCROW startRow = 0; + SCROW endRow = maxRow; + SCCOL startCol = 0; + SCCOL endCol = 0; + // ScSortedRangeCache would normally shrink data range to this. + CPPUNIT_ASSERT(m_pDoc->ShrinkToDataArea(0, startCol, startRow, endCol, endRow)); + CPPUNIT_ASSERT_EQUAL(SCROW(8), endRow); + + // But not if matching empty cells. + m_pDoc->SetFormula(ScAddress(10, 0, 0), + "=COUNTIFS($A1:$A" + OUString::number(maxRow + 1) + "; \"\")", + formula::FormulaGrammar::GRAM_NATIVE_UI); + CPPUNIT_ASSERT_EQUAL(double(maxRow + 1 - 7), m_pDoc->GetValue(ScAddress(10, 0, 0))); + + m_pDoc->DeleteTab(0); +} + +// Test that COUNTIFS counts properly empty cells if asked to. +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncCOUNTIFSRangeReduce) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + // Data in A1:C9. + std::vector<std::vector<const char*>> aData = { { "" }, + { "a", "1", "1" }, + { "b", "2", "2" }, + { "c", "4", "3" }, + { "d", "8", "4" }, + { "a", "16", "5" }, + { "" }, + { "b", "", "6" }, + { "c", "64", "7" } }; + + insertRangeData(m_pDoc, ScAddress(0, 0, 0), aData); + + constexpr SCROW maxRow = 20; // so that the unittest is not slow in dbgutil builds + ScRange aSubRange(ScAddress(0, 0, 0), ScAddress(2, maxRow, 0)); + m_pDoc->GetDataAreaSubrange(aSubRange); + // This is the range the data should be reduced to in ScInterpreter::IterateParametersIfs(). + CPPUNIT_ASSERT_EQUAL(SCROW(1), aSubRange.aStart.Row()); + CPPUNIT_ASSERT_EQUAL(SCROW(8), aSubRange.aEnd.Row()); + + m_pDoc->SetFormula(ScAddress(10, 0, 0), + "=COUNTIFS($A1:$A" + OUString::number(maxRow + 1) + "; \"\"; $B1:$B" + + OUString::number(maxRow + 1) + "; \"\"; $C1:$C" + + OUString::number(maxRow + 1) + "; \"\")", + formula::FormulaGrammar::GRAM_NATIVE_UI); + // But it should find out that it can't range reduce and must count all the empty rows. + CPPUNIT_ASSERT_EQUAL(double(maxRow + 1 - 7), m_pDoc->GetValue(ScAddress(10, 0, 0))); + + // Check also with criteria set as cell references, the middle one resulting in matching + // empty cells (which should cause ScInterpreter::IterateParametersIfs() to undo + // the range reduction). This should only match the A8-C8 row, but it also shouldn't crash. + // Matching empty cells using a cell reference needs a formula to set the cell to + // an empty string, plain empty cell wouldn't do, so use K2 for that. + m_pDoc->SetFormula(ScAddress(10, 1, 0), "=\"\"", formula::FormulaGrammar::GRAM_NATIVE_UI); + m_pDoc->SetFormula(ScAddress(10, 0, 0), + "=COUNTIFS($A1:$A" + OUString::number(maxRow + 1) + "; A8; $B1:$B" + + OUString::number(maxRow + 1) + "; K2; $C1:$C" + + OUString::number(maxRow + 1) + "; C8)", + formula::FormulaGrammar::GRAM_NATIVE_UI); + CPPUNIT_ASSERT_EQUAL(double(1), m_pDoc->GetValue(ScAddress(10, 0, 0))); + + m_pDoc->DeleteTab(0); +} + +// Test SUBTOTAL with reference lists in array context. +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncRefListArraySUBTOTAL) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + m_pDoc->SetValue(0, 0, 0, 1.0); // A1 + m_pDoc->SetValue(0, 1, 0, 2.0); // A2 + m_pDoc->SetValue(0, 2, 0, 4.0); // A3 + m_pDoc->SetValue(0, 3, 0, 8.0); // A4 + m_pDoc->SetValue(0, 4, 0, 16.0); // A5 + m_pDoc->SetValue(0, 5, 0, 32.0); // A6 + + // Matrix in B7:B9, individual SUM of A2:A3, A3:A4 and A4:A5 + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + m_pDoc->InsertMatrixFormula(1, 6, 1, 8, aMark, "=SUBTOTAL(9;OFFSET(A1;ROW(1:3);0;2))"); + ScAddress aPos(1, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL SUM for A2:A3 failed", 6.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL SUM for A3:A4 failed", 12.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL SUM for A4:A5 failed", 24.0, m_pDoc->GetValue(aPos)); + + // Matrix in C7:C9, individual AVERAGE of A2:A3, A3:A4 and A4:A5 + m_pDoc->InsertMatrixFormula(2, 6, 2, 8, aMark, "=SUBTOTAL(1;OFFSET(A1;ROW(1:3);0;2))"); + aPos.Set(2, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A2:A3 failed", 3.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A3:A4 failed", 6.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A4:A5 failed", 12.0, m_pDoc->GetValue(aPos)); + + // Matrix in D7:D9, individual MIN of A2:A3, A3:A4 and A4:A5 + m_pDoc->InsertMatrixFormula(3, 6, 3, 8, aMark, "=SUBTOTAL(5;OFFSET(A1;ROW(1:3);0;2))"); + aPos.Set(3, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MIN for A2:A3 failed", 2.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MIN for A3:A4 failed", 4.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MIN for A4:A5 failed", 8.0, m_pDoc->GetValue(aPos)); + + // Matrix in E7:E9, individual MAX of A2:A3, A3:A4 and A4:A5 + m_pDoc->InsertMatrixFormula(4, 6, 4, 8, aMark, "=SUBTOTAL(4;OFFSET(A1;ROW(1:3);0;2))"); + aPos.Set(4, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A2:A3 failed", 4.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A3:A4 failed", 8.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A4:A5 failed", 16.0, m_pDoc->GetValue(aPos)); + + // Matrix in F7:F9, individual STDEV of A2:A3, A3:A4 and A4:A5 + m_pDoc->InsertMatrixFormula(5, 6, 5, 8, aMark, "=SUBTOTAL(7;OFFSET(A1;ROW(1:3);0;2))"); + aPos.Set(5, 6, 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A2:A3 failed", 1.414214, + m_pDoc->GetValue(aPos), 1e-6); + aPos.IncRow(); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A3:A4 failed", 2.828427, + m_pDoc->GetValue(aPos), 1e-6); + aPos.IncRow(); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A4:A5 failed", 5.656854, + m_pDoc->GetValue(aPos), 1e-6); + + // Matrix in G7:G9, individual AVERAGE of A2:A3, A3:A4 and A4:A5 + // Plus two "ordinary" ranges, one before and one after. + m_pDoc->InsertMatrixFormula(6, 6, 6, 8, aMark, + "=SUBTOTAL(1;A1:A2;OFFSET(A1;ROW(1:3);0;2);A5:A6)"); + aPos.Set(6, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A1:A2,A2:A3,A5:A6 failed", 9.5, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A1:A2,A3:A4,A5:A6 failed", 10.5, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL AVERAGE for A1:A2,A4:A5,A5:A6 failed", 12.5, + m_pDoc->GetValue(aPos)); + + // Matrix in H7:H9, individual MAX of A2:A3, A3:A4 and A4:A5 + // Plus two "ordinary" ranges, one before and one after. + m_pDoc->InsertMatrixFormula(7, 6, 7, 8, aMark, + "=SUBTOTAL(4;A1:A2;OFFSET(A1;ROW(1:3);0;2);A5:A6)"); + aPos.Set(7, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A1:A2,A2:A3,A5:A6 failed", 32.0, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A1:A2,A3:A4,A5:A6 failed", 32.0, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUBTOTAL MAX for A1:A2,A4:A5,A5:A6 failed", 32.0, + m_pDoc->GetValue(aPos)); + + // Matrix in I7:I9, individual STDEV of A2:A3, A3:A4 and A4:A5 + // Plus two "ordinary" ranges, one before and one after. + m_pDoc->InsertMatrixFormula(8, 6, 8, 8, aMark, + "=SUBTOTAL(7;A1:A2;OFFSET(A1;ROW(1:3);0;2);A5:A6)"); + aPos.Set(8, 6, 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A1:A2,A2:A3,A5:A6 failed", 12.35718, + m_pDoc->GetValue(aPos), 1e-5); + aPos.IncRow(); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A1:A2,A3:A4,A5:A6 failed", 11.86170, + m_pDoc->GetValue(aPos), 1e-5); + aPos.IncRow(); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("SUBTOTAL STDEV for A1:A2,A4:A5,A5:A6 failed", 11.55422, + m_pDoc->GetValue(aPos), 1e-5); + + // Empty two cells such that they affect two ranges. + m_pDoc->SetString(0, 1, 0, ""); // A2 + m_pDoc->SetString(0, 2, 0, ""); // A3 + // Matrix in J7:J9, individual COUNTBLANK of A2:A3, A3:A4 and A4:A5 + m_pDoc->InsertMatrixFormula(9, 6, 9, 8, aMark, "=COUNTBLANK(OFFSET(A1;ROW(1:3);0;2))"); + aPos.Set(9, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("COUNTBLANK for A1:A2,A2:A3,A5:A6 failed", 2.0, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("COUNTBLANK for A1:A2,A3:A4,A5:A6 failed", 1.0, + m_pDoc->GetValue(aPos)); + aPos.IncRow(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("COUNTBLANK for A1:A2,A4:A5,A5:A6 failed", 0.0, + m_pDoc->GetValue(aPos)); + + // Restore these two cell values so we'd catch failures below. + m_pDoc->SetValue(0, 1, 0, 2.0); // A2 + m_pDoc->SetValue(0, 2, 0, 4.0); // A3 + // Hide rows 2 to 4. + m_pDoc->SetRowHidden(1, 3, 0, true); + // Matrix in K7, array of references as OFFSET result. + m_pDoc->InsertMatrixFormula(10, 6, 10, 6, aMark, + "=SUM(SUBTOTAL(109;OFFSET(A1;ROW(A1:A7)-ROW(A1);;1)))"); + aPos.Set(10, 6, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUM SUBTOTAL failed", 49.0, m_pDoc->GetValue(aPos)); + aPos.IncRow(); + // ForceArray in K8, array of references as OFFSET result. + m_pDoc->SetString(aPos, "=SUMPRODUCT(SUBTOTAL(109;OFFSET(A1;ROW(A1:A7)-ROW(A1);;1)))"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("SUMPRODUCT SUBTOTAL failed", 49.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +// tdf#115493 jump commands return the matrix result instead of the reference +// list array. +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncJumpMatrixArrayIF) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + m_pDoc->SetString(0, 0, 0, "a"); // A1 + std::vector<std::vector<const char*>> aData + = { { "a", "1" }, { "b", "2" }, { "a", "4" } }; // A7:B9 + insertRangeData(m_pDoc, ScAddress(0, 6, 0), aData); + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + + // Matrix in C10, summing B7,B9 + m_pDoc->InsertMatrixFormula(2, 9, 2, 9, aMark, "=SUM(IF(EXACT(A7:A9;A$1);B7:B9;0))"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C10 failed", 5.0, m_pDoc->GetValue(ScAddress(2, 9, 0))); + + // Matrix in C11, summing B7,B9 + m_pDoc->InsertMatrixFormula( + 2, 10, 2, 10, aMark, + "=SUM(IF(EXACT(OFFSET(A7;0;0):OFFSET(A7;2;0);A$1);OFFSET(A7;0;1):OFFSET(A7;2;1);0))"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C11 failed", 5.0, m_pDoc->GetValue(ScAddress(2, 10, 0))); + + m_pDoc->DeleteTab(0); +} + +// tdf#123477 OFFSET() returns the matrix result instead of the reference list +// array if result is not used as ReferenceOrRefArray. +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncJumpMatrixArrayOFFSET) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + std::vector<std::vector<const char*>> aData = { { "abc" }, { "bcd" }, { "cde" } }; + insertRangeData(m_pDoc, ScAddress(0, 0, 0), aData); // A1:A3 + + ScMarkData aMark(m_pDoc->GetSheetLimits()); + aMark.SelectOneTable(0); + + // Matrix in C5:C7, COLUMN()-3 here offsets by 0 but the entire expression + // is in array/matrix context. + m_pDoc->InsertMatrixFormula(2, 4, 2, 6, aMark, "=FIND(\"c\";OFFSET(A1:A3;0;COLUMN()-3))"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C5 failed", 3.0, m_pDoc->GetValue(ScAddress(2, 4, 0))); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C6 failed", 2.0, m_pDoc->GetValue(ScAddress(2, 5, 0))); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Formula C7 failed", 1.0, m_pDoc->GetValue(ScAddress(2, 6, 0))); + + m_pDoc->DeleteTab(0); +} + +// Test iterations with circular chain of references. +CPPUNIT_TEST_FIXTURE(TestFormula2, testIterations) +{ + ScDocOptions aDocOpts = m_pDoc->GetDocOptions(); + aDocOpts.SetIter(true); + m_pDoc->SetDocOptions(aDocOpts); + + m_pDoc->InsertTab(0, "Test"); + + m_pDoc->SetValue(0, 0, 0, 0.01); // A1 + m_pDoc->SetString(0, 1, 0, "=A1"); // A2 + m_pDoc->SetString(0, 2, 0, "=COS(A2)"); // A3 + m_pDoc->CalcAll(); + + // Establish reference cycle for the computation of the fixed point of COS() function + m_pDoc->SetString(0, 0, 0, "=A3"); // A1 + m_pDoc->CalcAll(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cell A3 should not have any formula error", FormulaError::NONE, + m_pDoc->GetErrCode(ScAddress(0, 2, 0))); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Iterations to calculate fixed point of cos() failed", + 0.7387, m_pDoc->GetValue(0, 2, 0), 1e-4); + + // Modify the formula + m_pDoc->SetString(0, 2, 0, "=COS(A2)+0.001"); // A3 + m_pDoc->CalcAll(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cell A3 should not have any formula error after perturbation", + FormulaError::NONE, m_pDoc->GetErrCode(ScAddress(0, 2, 0))); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "Iterations to calculate perturbed fixed point of cos() failed", 0.7399, + m_pDoc->GetValue(0, 2, 0), 1e-4); + + m_pDoc->DeleteTab(0); + + aDocOpts.SetIter(false); + m_pDoc->SetDocOptions(aDocOpts); +} + +// tdf#111428 CellStoreEvent and its counter used for quick "has a column +// formula cells" must point to the correct column. +CPPUNIT_TEST_FIXTURE(TestFormula2, testInsertColCellStoreEventSwap) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + m_pDoc->SetValue(0, 0, 0, 1.0); // A1 + m_pDoc->SetString(1, 0, 0, "=A1"); // B1 + // Insert column left of B + m_pDoc->InsertCol(ScRange(1, 0, 0, 1, m_pDoc->MaxRow(), 0)); + ScAddress aPos(2, 0, 0); // C1, new formula position + CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be formula cell having value", 1.0, + m_pDoc->GetValue(aPos)); + // After having swapped in an empty column, editing or adding a formula + // cell has to use the correct store context. To test this, + // ScDocument::SetString() can't be used as it doesn't expose the behavior + // in question, use ScDocFunc::SetFormulaCell() instead which actually is + // also called when editing a cell and creating a formula cell. + ScFormulaCell* pCell = new ScFormulaCell(*m_pDoc, aPos, "=A1+1"); + ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc(); + rDocFunc.SetFormulaCell(aPos, pCell, false); // C1, change formula + CPPUNIT_ASSERT_EQUAL_MESSAGE("Initial calculation failed", 2.0, m_pDoc->GetValue(aPos)); + m_pDoc->SetValue(0, 0, 0, 2.0); // A1, change value + CPPUNIT_ASSERT_EQUAL_MESSAGE("Recalculation failed", 3.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testTdf147398) +{ + m_pDoc->InsertTab(0, "Test"); + + m_pDoc->SetString(0, 0, 0, "=SUM(A3:A5)"); + m_pDoc->SetString(0, 1, 0, "=COUNT(A3:A5)"); + m_pDoc->SetString(1, 0, 0, "=SUM(B3:B5)"); + m_pDoc->SetString(1, 1, 0, "=COUNT(B3:B5)"); + m_pDoc->SetString(2, 0, 0, "=SUM(C3:C5)"); + m_pDoc->SetString(2, 1, 0, "=COUNT(C3:C5)"); + m_pDoc->SetString(3, 0, 0, "=SUM(D3:D5)"); + m_pDoc->SetString(3, 1, 0, "=COUNT(D3:D5)"); + m_pDoc->SetString(4, 0, 0, "=SUM(E3:E5)"); + m_pDoc->SetString(4, 1, 0, "=COUNT(E3:E5)"); + + m_pDoc->SetString(5, 0, 0, "=SUM(A1:E1)/SUM(A2:E2)"); + + m_pDoc->SetValue(ScAddress(0, 2, 0), 50.0); + m_pDoc->SetValue(ScAddress(0, 3, 0), 100.0); + + CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); + CPPUNIT_ASSERT_EQUAL(75.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); + + m_pDoc->SetValue(ScAddress(1, 2, 0), 150.0); + m_pDoc->SetValue(ScAddress(1, 3, 0), 200.0); + + CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); + CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); + CPPUNIT_ASSERT_EQUAL(125.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); + + m_pDoc->SetValue(ScAddress(2, 2, 0), 250.0); + m_pDoc->SetValue(ScAddress(2, 3, 0), 300.0); + + CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); + CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); + CPPUNIT_ASSERT_EQUAL(550.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); + CPPUNIT_ASSERT_EQUAL(175.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); + + m_pDoc->SetValue(ScAddress(3, 2, 0), 350.0); + m_pDoc->SetValue(ScAddress(3, 3, 0), 400.0); + + CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); + CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); + CPPUNIT_ASSERT_EQUAL(550.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); + CPPUNIT_ASSERT_EQUAL(750.0, m_pDoc->GetValue(ScAddress(3, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(3, 1, 0))); + CPPUNIT_ASSERT_EQUAL(225.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); + + m_pDoc->SetValue(ScAddress(4, 2, 0), 450.0); + m_pDoc->SetValue(ScAddress(4, 3, 0), 500.0); + + CPPUNIT_ASSERT_EQUAL(150.0, m_pDoc->GetValue(ScAddress(0, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); + CPPUNIT_ASSERT_EQUAL(350.0, m_pDoc->GetValue(ScAddress(1, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 1, 0))); + CPPUNIT_ASSERT_EQUAL(550.0, m_pDoc->GetValue(ScAddress(2, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2, 1, 0))); + CPPUNIT_ASSERT_EQUAL(750.0, m_pDoc->GetValue(ScAddress(3, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(3, 1, 0))); + CPPUNIT_ASSERT_EQUAL(950.0, m_pDoc->GetValue(ScAddress(4, 0, 0))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(4, 1, 0))); + CPPUNIT_ASSERT_EQUAL(275.0, m_pDoc->GetValue(ScAddress(5, 0, 0))); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testFormulaAfterDeleteRows) +{ + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + m_pDoc->InsertTab(0, "Test"); + + // Fill A1:A70000 with 1.0 + std::vector<double> aVals(70000, 1.0); + m_pDoc->SetValues(ScAddress(0, 0, 0), aVals); + // Set A70001 with formula "=SUM(A1:A70000)" + m_pDoc->SetString(0, 70000, 0, "=SUM(A1:A70000)"); + + // Delete rows 2:69998 + m_pDoc->DeleteRow(ScRange(0, 1, 0, m_pDoc->MaxCol(), 69997, 0)); + + const ScAddress aPos(0, 3, 0); // A4 + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in A4.", OUString("=SUM(A1:A3)"), + m_pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + + ASSERT_DOUBLES_EQUAL_MESSAGE("Wrong value at A4", 3.0, m_pDoc->GetValue(aPos)); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |