Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sc/qa/unit/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 207 kB image not shown  

Quelle  ucalc_formula2.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */


#include "helper/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 <kahan.hxx>

#include <svl/broadcast.hxx>
#include <sfx2/docfile.hxx>

#include <memory>
#include <functional>
#include <set>
#include <algorithm>
#include <vector>

using namespace formula;
using ::std::vector;
using ::std::cerr;
using ::std::endl;

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(ScRange(aPos), false);
        aMark.SetMarkArea(ScRange(aPos));
        m_pDoc->CopyToClip(aClipParam, &aClipDoc, &aMark, falsefalse);

        // 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, u"Formula"_ustr);

    m_pDoc->SetString(ScAddress(0, 0, 0), u"=IF(B1=2;\"two\";\"not two\")"_ustr);
    CPPUNIT_ASSERT_EQUAL(u"not two"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
    m_pDoc->SetValue(ScAddress(1, 0, 0), 2.0);
    CPPUNIT_ASSERT_EQUAL(u"two"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
    m_pDoc->SetValue(ScAddress(1, 0, 0), 3.0);
    CPPUNIT_ASSERT_EQUAL(u"not two"_ustr, 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, u"=IF({1;0};IF(1;23);42)"_ustr);
    // 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, u"=SUM(IF(A5:A8;IF(B5:B8;C5:C8;0);0))"_ustr);
    // 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, u"=IF(IF({1;0};12);34;56)"_ustr);
    // 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, u"Formula"_ustr);

    m_pDoc->SetString(ScAddress(0, 0, 0), u"=CHOOSE(B1;\"one\";\"two\";\"three\")"_ustr);
    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(u"one"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
    m_pDoc->SetValue(ScAddress(1, 0, 0), 2.0);
    CPPUNIT_ASSERT_EQUAL(u"two"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
    m_pDoc->SetValue(ScAddress(1, 0, 0), 3.0);
    CPPUNIT_ASSERT_EQUAL(u"three"_ustr, 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, u"foo"_ustr));

    // 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,
                                u"=IFERROR(3*A11:A12;1998)"_ustr);

    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, u"=IFERROR(C1:E3;\"Error\")"_ustr);

    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, u"test1"_ustr));

    m_pDoc->SetString(0, 0, 0, u"=SHEETS()"_ustr);
    m_pDoc->CalcFormulaTree(falsefalse);
    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, u"test2"_ustr));

    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, u"foo"_ustr));

    double val = 1;
    m_pDoc->SetValue(0, 0, 0, val);
    m_pDoc->SetString(0, 1, 0, u"=IF(A1>0;NOW();0"_ustr);
    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(falsefalse);
    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(falsefalse);
    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, u"foo"_ustr));

    // 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, u"Formula"_ustr);

    // 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, u"=LEN(A1:A3)"_ustr);

    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, u"Test"_ustr);

    // 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, u"Test"_ustr);

    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, u"=LOOKUP(2;1/(C2:E2<>\"\");C3:E3)"_ustr); // A1

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for last column.", u"c"_ustr,
                                 m_pDoc->GetString(0, 0, 0));
    m_pDoc->SetString(4, 1, 0, u""_ustr); // E2
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for second last column.", u"b"_ustr,
                                 m_pDoc->GetString(0, 0, 0));

    m_pDoc->SetString(6, 1, 0, u"one"_ustr); // G2
    m_pDoc->SetString(6, 5, 0, u"two"_ustr); // 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, u"=LOOKUP(2;1/(NOT(ISBLANK(G2:G9)));G2:G9)"_ustr); // H9
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find match for last row.", u"two"_ustr,
                                 m_pDoc->GetString(7, 8, 0));

    // Lookup on empty range.
    m_pDoc->SetString(9, 8, 0, u"=LOOKUP(2;1/(NOT(ISBLANK(I2:I9)));I2:I9)"_ustr); // J9
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Should find no match.", u"#N/A"_ustr, 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, u"Test1"_ustr);
    m_pDoc->InsertTab(1, u"Test2"_ustr);

    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, u"k2"_ustr); // E1

    m_pDoc->SetString(4, 1, 1, u"=LOOKUP(1;1/(A$2:A$4=E$1);1)"_ustr);
    m_pDoc->SetString(4, 2, 1, u"=LOOKUP(E1;A$2:A$4;B2:B4)"_ustr);
    m_pDoc->SetString(4, 3, 1, u"=LOOKUP(1;1/(A$2:A$4=E$1);B2:B4)"_ustr);

    // Without the fix in place, this test would have failed with
    // - Expected: #N/A
    // - Actual  :
    CPPUNIT_ASSERT_EQUAL(u"#N/A"_ustr, m_pDoc->GetString(4, 1, 1));
    CPPUNIT_ASSERT_EQUAL(u"value2"_ustr, m_pDoc->GetString(4, 2, 1));
    CPPUNIT_ASSERT_EQUAL(u"value2"_ustr, 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, u"foo"_ustr));

    // 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), u"One"_ustr);
    m_pDoc->SetString(ScAddress(1, 4, 0), u"Two"_ustr);
    m_pDoc->SetString(ScAddress(1, 7, 0), u"Four"_ustr);

    // 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), u"=VLOOKUP(D1;$A$1:$B$8;2)"_ustr);
    m_pDoc->SetString(ScAddress(4, 1, 0), u"=VLOOKUP(D2;$A$1:$B$8;2)"_ustr);
    m_pDoc->SetString(ScAddress(4, 2, 0), u"=VLOOKUP(D3;$A$1:$B$8;2)"_ustr);
    m_pDoc->SetString(ScAddress(4, 3, 0), u"=VLOOKUP(D4;$A$1:$B$8;2)"_ustr);
    m_pDoc->SetString(ScAddress(4, 4, 0), u"=VLOOKUP(D5;$A$1:$B$8;2)"_ustr);
    m_pDoc->CalcAll();

    // Check the formula results in E1:E5.
    CPPUNIT_ASSERT_EQUAL(u"One"_ustr, m_pDoc->GetString(ScAddress(4, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"Two"_ustr, m_pDoc->GetString(ScAddress(4, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u"Two"_ustr, m_pDoc->GetString(ScAddress(4, 2, 0)));
    CPPUNIT_ASSERT_EQUAL(u"Four"_ustr, m_pDoc->GetString(ScAddress(4, 3, 0)));
    CPPUNIT_ASSERT_EQUAL(u"Four"_ustr, 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), u"A"_ustr);
    m_pDoc->SetString(ScAddress(0, 1, 0), u"B"_ustr);
    m_pDoc->SetString(ScAddress(0, 2, 0), u"C"_ustr);
    m_pDoc->SetString(ScAddress(0, 3, 0), u"D"_ustr);
    m_pDoc->SetString(ScAddress(0, 4, 0), u"E"_ustr);
    m_pDoc->SetString(ScAddress(0, 5, 0), u"F"_ustr);
    m_pDoc->SetString(ScAddress(0, 6, 0), u"G"_ustr);

    // Set the formula in C1.
    m_pDoc->SetString(ScAddress(2, 0, 0), u"=VLOOKUP(\"C\";A1:A16;1)"_ustr);
    CPPUNIT_ASSERT_EQUAL(u"C"_ustr, 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, u"foo"_ustr));

    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), u"=MATCH(2;A1:A20)"_ustr);
        m_pDoc->CalcAll();

        CPPUNIT_ASSERT_EQUAL(u"6"_ustr, 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), u"=MATCH(\"33\";B1:B5&B1:B5)"_ustr);
        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, u"foo"_ustr));

    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, u"foo"_ustr));

    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(u"foo"_ustr);
    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 = u"Test"_ustr, aRefErr = u"#REF!"_ustr;
    m_pDoc->SetString(0, 10, 0, aTest);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected cell value.", aTest, m_pDoc->GetString(0, 10, 0));

    OUString aPrefix = u"=INDIRECT(\""_ustr;

    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, u"foo"_ustr));
    CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(1, u"bar"_ustr));
    CPPUNIT_ASSERT_MESSAGE("failed to insert sheet", m_pDoc->InsertTab(2, u"baz"_ustr));

    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, u"=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$1),1)"_ustr);

    // Test range triplet (absolute, relative, relative) : (absolute, absolute, relative)
    m_pDoc->SetString(0, 1, 2, u"=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$2),1)"_ustr);

    // Test range triplet (absolute, relative, relative) : (absolute, absolute, absolute)
    m_pDoc->SetString(0, 2, 2, u"=COUNTIF(bar.$A1:INDIRECT(\"$A\"&foo.$A$3),1)"_ustr);

    // Test range triplet (absolute, absolute, relative) : (absolute, relative, relative)
    m_pDoc->SetString(0, 3, 2, u"=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$1),1)"_ustr);

    // Test range triplet (absolute, absolute, relative) : (absolute, absolute, relative)
    m_pDoc->SetString(0, 4, 2, u"=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$2),1)"_ustr);

    // Test range triplet (absolute, absolute, relative) : (absolute, absolute, relative)
    m_pDoc->SetString(0, 5, 2, u"=COUNTIF(bar.$A$1:INDIRECT(\"$A\"&foo.$A$3),1)"_ustr);

    // Test range triplet (absolute, absolute, absolute) : (absolute, relative, relative)
    m_pDoc->SetString(0, 6, 2, u"=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$1),1)"_ustr);

    // Test range triplet (absolute, absolute, absolute) : (absolute, absolute, relative)
    m_pDoc->SetString(0, 7, 2, u"=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$2),1)"_ustr);

    // Check indirect reference "bar.$A\"&foo.$A$1
    m_pDoc->SetString(0, 8, 2, u"=COUNTIF(bar.$A$1:INDIRECT(\"bar.$A\"&foo.$A$1),1)"_ustr);

    // 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, u"=COUNTIF($bar.$A$1:INDIRECT(\"$A\"&foo.$A$3),1)"_ustr);

    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, u"foo"_ustr));

    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation.

    ScRangeName* pGlobalNames = m_pDoc->GetRangeName();
    ScRangeData* pRangeData = new ScRangeData(*m_pDoc, u"RoleAssignment"_ustr, u"$D$4:$D$13"_ustr);
    pGlobalNames->insert(pRangeData);

    // D6: data to match, in 3rd row of named range.
    m_pDoc->SetString(3, 5, 0, u"Test1"_ustr);
    // 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,
                      u"=MATCH(\"Test1\";INDIRECT(ADDRESS(ROW(RoleAssignment)+1;COLUMN("
                      "RoleAssignment))&\":\"&ADDRESS(ROW(RoleAssignment)+ROWS(RoleAssignment)-1;"
                      "COLUMN(RoleAssignment)));0)"_ustr);

    // 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, u"foo"_ustr));

    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, u"=D2"_ustr);
    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, u"=SUM(D2:E6)"_ustr);
    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, u"=RC[-1]"_ustr);

        // Formula in column 3 that references cell to the left.
        m_pDoc->SetString(2, nRow, 0, u"=RC[-1]*2"_ustr);
    }

    // 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, u"=R2C3"_ustr);
    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, u"foo"_ustr));

    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, u"=A1/B1"_ustr);
    m_pDoc->SetString(1, 1, 0, u"=B1*C1"_ustr);

    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(1, 1, 0)); // B2 should equal 2.

    clearRange(m_pDoc, ScRange(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, u"Formula"_ustr);

    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, u"Test"_ustr);

    // 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), u"=SUM(A1:A3)"_ustr);

    // A6 to reference A5.
    m_pDoc->SetString(ScAddress(0, 5, 0), u"=A5*10"_ustr);
    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, u"Formula"_ustr);

    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, u"Test"_ustr);

    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, u"=A1"_ustr);
    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), u"ABC"_ustr);
    CPPUNIT_ASSERT_EQUAL(u"ABC"_ustr, 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(u"file:///extdata.fake"_ustr);
    OUString aExtSh1Name(u"Data1"_ustr);
    OUString aExtSh2Name(u"Data2"_ustr);
    OUString aExtSh3Name(u"Data3"_ustr);
    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);

    static OUString constexpr name(u"Name"_ustr);
    static OUString constexpr value(u"Value"_ustr);

    // Sheet 1
    rExtDoc.SetString(0, 0, 0, name);
    rExtDoc.SetString(0, 1, 0, u"Andy"_ustr);
    rExtDoc.SetString(0, 2, 0, u"Bruce"_ustr);
    rExtDoc.SetString(0, 3, 0, u"Charlie"_ustr);
    rExtDoc.SetString(0, 4, 0, u"David"_ustr);
    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, u"Edward"_ustr);
    rExtDoc.SetString(0, 2, 2, u"Frank"_ustr);
    rExtDoc.SetString(0, 3, 2, u"George"_ustr);
    rExtDoc.SetString(0, 4, 2, u"Henry"_ustr);
    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, u"Test Sheet"_ustr);
    m_pDoc->SetString(0, 0, 0, u"='file:///extdata.fake'#Data1.A1"_ustr);
    OUString test = m_pDoc->GetString(0, 0, 0);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Value is different from the original", name, test);

    // 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, u"='file:///extdata.fake'#Data1.B1"_ustr);
    test = m_pDoc->GetString(1, 0, 0);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Value is different from the original", value, test);

    m_pDoc->SetString(0, 1, 0, u"='file:///extdata.fake'#Data1.A2"_ustr);
    m_pDoc->SetString(0, 2, 0, u"='file:///extdata.fake'#Data1.A3"_ustr);
    m_pDoc->SetString(0, 3, 0, u"='file:///extdata.fake'#Data1.A4"_ustr);
    m_pDoc->SetString(0, 4, 0, u"='file:///extdata.fake'#Data1.A5"_ustr);
    m_pDoc->SetString(0, 5, 0, u"='file:///extdata.fake'#Data1.A6"_ustr);

    {
        // 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, u"='file:///extdata.fake'#Data1.B2"_ustr);
    m_pDoc->SetString(1, 2, 0, u"='file:///extdata.fake'#Data1.B3"_ustr);
    m_pDoc->SetString(1, 3, 0, u"='file:///extdata.fake'#Data1.B4"_ustr);
    m_pDoc->SetString(1, 4, 0, u"='file:///extdata.fake'#Data1.B5"_ustr);
    m_pDoc->SetString(1, 5, 0, u"='file:///extdata.fake'#Data1.B6"_ustr);
    {
        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, u"='file:///extdata.fake'#Data3.A1"_ustr);
    m_pDoc->SetString(2, 1, 0, u"='file:///extdata.fake'#Data3.A2"_ustr);
    m_pDoc->SetString(2, 2, 0, u"='file:///extdata.fake'#Data3.A3"_ustr);
    m_pDoc->SetString(2, 3, 0, u"='file:///extdata.fake'#Data3.A4"_ustr);
    {
        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);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=87 H=74 G=80

¤ Dauer der Verarbeitung: 0.29 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.