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 115 kB image not shown  

Quelle  ucalc_sharedformula.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/debughelper.hxx"
#include "helper/qahelper.hxx"
#include <editutil.hxx>
#include <formulacell.hxx>
#include <cellvalue.hxx>
#include <scopetools.hxx>
#include <docfunc.hxx>
#include <dbdocfun.hxx>
#include <tokenstringcontext.hxx>
#include <globalnames.hxx>
#include <dbdata.hxx>
#include <bcaslot.hxx>
#include <undomanager.hxx>

#include <sfx2/docfile.hxx>

#include <memory>
#include <string_view>

using ::std::cerr;
using ::std::endl;

class TestSharedFormula : public ScUcalcTestBase
{
};

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulas)
{
    m_pDoc->InsertTab(0, u"Test"_ustr);

    ScAddress aPos(1, 9, 0); // B10
    m_pDoc->SetString(aPos, u"=A10*2"_ustr); // Insert into B10.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("Expected to be a non-shared cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("Expected to be a non-shared cell.", !pFC->IsShared());

    aPos.SetRow(10); // B11
    m_pDoc->SetString(aPos, u"=A11*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(9), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    aPos.SetRow(8); // B9
    m_pDoc->SetString(aPos, u"=A9*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    aPos.SetRow(12); // B13
    m_pDoc->SetString(aPos, u"=A13*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This formula cell shouldn't be shared yet.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This formula cell shouldn't be shared yet.", !pFC->IsShared());

    // Insert a formula to B12, and B9:B13 should be shared.
    aPos.SetRow(11); // B12
    m_pDoc->SetString(aPos, u"=A12*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(5), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Insert formulas to B15:B16.
    aPos.SetRow(14); // B15
    m_pDoc->SetString(aPos, u"=A15*2"_ustr);
    aPos.SetRow(15); // B16
    m_pDoc->SetString(aPos, u"=A16*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(14), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Insert a formula to B14, and B9:B16 should be shared.
    aPos.SetRow(13); // B14
    m_pDoc->SetString(aPos, u"=A14*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Insert an incompatible formula to B12, to split the shared range to B9:B11 and B13:B16.
    aPos.SetRow(11); // B12
    m_pDoc->SetString(aPos, u"=$A$1*4"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell shouldn't be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell shouldn't be shared.", !pFC->IsShared());

    aPos.SetRow(8); // B9
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    aPos.SetRow(12); // B13
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(12), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Extend B13:B16 to B13:B20.
    aPos.SetRow(16); // B17
    m_pDoc->SetString(aPos, u"=A17*2"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A18*2"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A19*2"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A20*2"_ustr);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC->IsShared());
    // B13:B20 should be shared.
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(12), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Empty B19. This should split it into B13:B18, and B20 non-shared.
    aPos.SetRow(18);
    m_pDoc->SetEmptyCell(aPos);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This cell should have been emptied.", CELLTYPE_NONE, m_pDoc->GetCellType(aPos));
    aPos.SetRow(12); // B13
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT(pFC);
    // B13:B18 should be shared.
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(12), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());
    // B20 should be non-shared.
    aPos.SetRow(19); // B20
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B20 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell should be non-shared.", !pFC->IsShared());

    // Empty B14, to make B13 non-shared and B15:B18 shared.
    aPos.SetRow(13); // B14
    m_pDoc->SetEmptyCell(aPos);
    aPos.SetRow(12); // B13
    pFC = m_pDoc->GetFormulaCell(aPos);
    // B13 should be non-shared.
    CPPUNIT_ASSERT_MESSAGE("B13 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This cell should be non-shared.", !pFC->IsShared());
    // B15:B18 should be shared.
    aPos.SetRow(14); // B15
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(14), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Set numeric value to B15, to make B16:B18 shared.
    aPos.SetRow(14);
    m_pDoc->SetValue(aPos, 1.2);
    aPos.SetRow(15);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT(pFC);
    // B16:B18 should be shared.
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(15), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Set string value to B16 to make B17:B18 shared.
    aPos.SetRow(15);
    ScCellValue aCell(svl::SharedString(u"Test"_ustr));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This should be a string value.", CELLTYPE_STRING, aCell.getType());
    aCell.commit(*m_pDoc, aPos);
    CPPUNIT_ASSERT_EQUAL(aCell.getSharedString()->getString(), m_pDoc->GetString(aPos));
    aPos.SetRow(16);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT(pFC);
    // B17:B18 should be shared.
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(16), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Set edit text to B17. Now B18 should be non-shared.
    ScFieldEditEngine& rEditEngine = m_pDoc->GetEditEngine();
    rEditEngine.SetTextCurrentDefaults(u"Edit Text"_ustr);
    aPos.SetRow(16);
    m_pDoc->SetEditText(aPos, rEditEngine.CreateTextObject());
    CPPUNIT_ASSERT_EQUAL(CELLTYPE_EDIT, m_pDoc->GetCellType(aPos));
    aPos.SetRow(17);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B18 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B18 should be non-shared.", !pFC->IsShared());

    // Set up a new group for shared formulas in B2:B10.
    clearRange(m_pDoc, ScRange(0,0,0,2,100,0));

    aPos.SetRow(1);
    m_pDoc->SetString(aPos, u"=A2*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A3*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A4*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A5*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A6*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A7*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A8*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A9*10"_ustr);
    aPos.IncRow();
    m_pDoc->SetString(aPos, u"=A10*10"_ustr);

    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B10 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(9), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Delete A4:B8. This should split the grouping to B2:B3 and B9:B10.
    clearRange(m_pDoc, ScRange(0,3,0,1,7,0));
    aPos.SetRow(1);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B2 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    aPos.SetRow(8);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B9 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Delete rows 4:8 and shift row 9 and below up to row 4.  This should
    // re-merge the two into a group of B2:B5.
    m_pDoc->DeleteRow(ScRange(0,3,0,m_pDoc->MaxCol(),7,0));
    aPos.SetRow(1);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B2 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Insert 2 rows at row 4, to split it into B2:B3 and B6:B7.
    m_pDoc->InsertRow(ScRange(0,3,0,m_pDoc->MaxCol(),4,0));
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B2 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    aPos.SetRow(5);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B6 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(5), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Test implicit intersection with shared formulas.
    aPos.Set(2,0,0);
    {
        // Insert data in C1:D2 and formulas in E1:E2
        const std::vector<std::vector<const char*>> aData = {
            { "5""1""=C:C/D:D" },
            { "4""2""=C:C/D:D" }
        };

        insertRangeData(m_pDoc, aPos, aData);
    }
    aPos.Set(4,1,0);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("E2 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    aPos.SetRow(0);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("5/1=5", 5.0, m_pDoc->GetValue(aPos));
    aPos.SetRow(1);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("4/2=2", 2.0, m_pDoc->GetValue(aPos));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdate)
{
    m_pDoc->InsertTab(0, u"Test"_ustr);

    sc::AutoCalcSwitch aACSwitch(*m_pDoc, false); // turn off auto calculation.

    // Set values to A10:A12.
    m_pDoc->SetValue(ScAddress(0,9,0), 1);
    m_pDoc->SetValue(ScAddress(0,10,0), 2);
    m_pDoc->SetValue(ScAddress(0,11,0), 3);

    {
        // Insert formulas that reference A10:A12 in B1:B3.
        const std::vector<std::vector<const char*>> aData = {
            { "=A10" },
            { "=A11" },
            { "=A12" }
        };

        insertRangeData(m_pDoc, ScAddress(1,0,0), aData);
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=A11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=A12"_ustr, m_pDoc->GetFormula(1,2,0));

    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    // Insert cells over A11:B11 to shift to right. This should split the B1:B3 grouping into 3.
    m_pDoc->InsertCol(ScRange(0,10,0,1,10,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=C11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=A12"_ustr, m_pDoc->GetFormula(1,2,0));

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", !pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,1,0));
    CPPUNIT_ASSERT_MESSAGE("B2 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B2 should be a non-shared formula cell.", !pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,2,0));
    CPPUNIT_ASSERT_MESSAGE("B3 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B3 should be a non-shared formula cell.", !pFC->IsShared());

    // Delete cells over A11:B11 to bring it back to the previous state.
    m_pDoc->DeleteCol(ScRange(0,10,0,1,10,0));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=A11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=A12"_ustr, m_pDoc->GetFormula(1,2,0));

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    // Insert cells over A11:A12 and shift down.
    m_pDoc->InsertRow(ScRange(0,10,0,0,11,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=A13"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=A14"_ustr, m_pDoc->GetFormula(1,2,0));

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", !pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,1,0));
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    // Delete A11:A12 to bring it back to the way it was.
    m_pDoc->DeleteRow(ScRange(0,10,0,0,11,0));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=A11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=A12"_ustr, m_pDoc->GetFormula(1,2,0));

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    // Insert cells over A11:B11 to shift to right again.
    m_pDoc->InsertCol(ScRange(0,10,0,1,10,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=C11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=A12"_ustr, m_pDoc->GetFormula(1,2,0));

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", !pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,1,0));
    CPPUNIT_ASSERT_MESSAGE("B2 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B2 should be a non-shared formula cell.", !pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,2,0));
    CPPUNIT_ASSERT_MESSAGE("B3 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B3 should be a non-shared formula cell.", !pFC->IsShared());

    // Insert cells over A12:B12 to shift to right.
    m_pDoc->InsertCol(ScRange(0,11,0,1,11,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=A10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=C11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=C12"_ustr, m_pDoc->GetFormula(1,2,0));

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B1 should be a non-shared formula cell.", !pFC->IsShared());
    // B2 and B3 should be grouped.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,1,0));
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    // Insert cells over A10:B10 to shift to right.
    m_pDoc->InsertCol(ScRange(0,9,0,1,9,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B1", u"=C10"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B2", u"=C11"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula in B3", u"=C12"_ustr, m_pDoc->GetFormula(1,2,0));

    // B1:B3 should be now grouped.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("This must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateMove)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Set values in B2:B4.
    for (SCROW i = 1; i <= 3; ++i)
        m_pDoc->SetValue(ScAddress(1,i,0), i);

    // Make sure the values are really there.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,3,0)));

    const std::vector<std::vector<const char*>> aData = {
        { "=RC[-1]" },
        { "=RC[-1]" },
        { "=RC[-1]" }
    };

    // Set formulas in C2:C4 that reference B2:B4 individually.
    insertRangeData(m_pDoc, ScAddress(2,1,0), aData);

    // Check the formula results.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    // Move B2:B4 to B1:B3.
    bool bMoved = m_xDocShell->GetDocFunc().MoveBlock(ScRange(1,1,0,1,3,0), ScAddress(1,0,0), truetruefalsetrue);
    CPPUNIT_ASSERT(bMoved);

    // Make sure the values have been moved for real.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    // The formulas should have been adjusted for the move.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=R[-1]C[-1]"_ustr, m_pDoc->GetFormula(2,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=R[-1]C[-1]"_ustr, m_pDoc->GetFormula(2,2,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=R[-1]C[-1]"_ustr, m_pDoc->GetFormula(2,3,0));

    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);
    pUndoMgr->Undo();

    // The values should have moved back.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,3,0)));

    // And the formulas should have been re-adjusted to their original references.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=RC[-1]"_ustr, m_pDoc->GetFormula(2,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=RC[-1]"_ustr, m_pDoc->GetFormula(2,2,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=RC[-1]"_ustr, m_pDoc->GetFormula(2,3,0));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateMove2)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, false); // turn auto calc off this time.
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Set values in B2:B3, and E2:E3.
    for (SCROW i = 1; i <= 2; ++i)
    {
        m_pDoc->SetValue(ScAddress(1,i,0), i);
        m_pDoc->SetValue(ScAddress(4,i,0), i);
    }

    // Make sure the values are really there.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(4,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(4,2,0)));

    {
        // Set formulas in C2:C3 that reference B2:B3 individually, and F2:F3 to E2:E3.
        const std::vector<std::vector<const char*>> aData = {
            { "=RC[-1]" },
            { "=RC[-1]" }
        };

        insertRangeData(m_pDoc, ScAddress(2,1,0), aData);
        insertRangeData(m_pDoc, ScAddress(5,1,0), aData);
    }

    m_pDoc->CalcFormulaTree(); // calculate manually.

    // Check the formula results.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(5,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(5,2,0)));

    // Move B2:C3 to C3:D4.
    bool bMoved = m_xDocShell->GetDocFunc().MoveBlock(
        ScRange(1,1,0,2,2,0), ScAddress(2,2,0), truetruefalsetrue);
    CPPUNIT_ASSERT(bMoved);

    // Make sure the range has been moved.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    // The formula cells should retain their results even with auto calc off
    // and without recalculation.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(3,2,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(3,3,0)));

    // And these formulas in F2:F3 are unaffected, therefore should not change.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(5,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(5,2,0)));

    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);

    // Undo the move.
    pUndoMgr->Undo();

    // Check the formula results.  The results should still be intact.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(5,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(5,2,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateRange)
{
    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Insert values to A3:A5.
    m_pDoc->SetValue(ScAddress(0,2,0), 1);
    m_pDoc->SetValue(ScAddress(0,3,0), 2);
    m_pDoc->SetValue(ScAddress(0,4,0), 3);

    {
        // Insert formulas to B3:B5.
        const std::vector<std::vector<const char*>> aData = {
            { "=SUM($A$3:$A$5)" },
            { "=SUM($A$3:$A$5)" },
            { "=SUM($A$3:$A$5)" }
        };

        insertRangeData(m_pDoc, ScAddress(1,2,0), aData);
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=SUM($A$3:$A$5)"_ustr, m_pDoc->GetFormula(1,2,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=SUM($A$3:$A$5)"_ustr, m_pDoc->GetFormula(1,3,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=SUM($A$3:$A$5)"_ustr, m_pDoc->GetFormula(1,4,0));

    // B3:B5 should be shared.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,2,0));
    CPPUNIT_ASSERT_MESSAGE("B3 should be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B3 should be shared.", pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,3,0));
    CPPUNIT_ASSERT_MESSAGE("B4 should be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B4 should be shared.", pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,4,0));
    CPPUNIT_ASSERT_MESSAGE("B3 should be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B3 should be shared.", pFC->IsShared());

    // Insert 2 rows at row 1.
    m_pDoc->InsertRow(ScRange(0,0,0,m_pDoc->MaxCol(),1,0));

    // B5:B7 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,4,0));
    CPPUNIT_ASSERT_MESSAGE("B5 should be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B5 should be shared.", pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,5,0));
    CPPUNIT_ASSERT_MESSAGE("B6 should be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B6 should be shared.", pFC->IsShared());
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,6,0));
    CPPUNIT_ASSERT_MESSAGE("B7 should be shared.", pFC);
    CPPUNIT_ASSERT_MESSAGE("B7 should be shared.", pFC->IsShared());

    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=SUM($A$5:$A$7)"_ustr, m_pDoc->GetFormula(1,4,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=SUM($A$5:$A$7)"_ustr, m_pDoc->GetFormula(1,5,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=SUM($A$5:$A$7)"_ustr, m_pDoc->GetFormula(1,6,0));

    m_pDoc->DeleteTab(0);
}

namespace {

struct SortByArea
{
    bool operator ()( const sc::AreaListener& rLeft, const sc::AreaListener& rRight ) const
    {
        if (rLeft.maArea.aStart.Tab() != rRight.maArea.aStart.Tab())
            return rLeft.maArea.aStart.Tab() < rRight.maArea.aStart.Tab();

        if (rLeft.maArea.aStart.Col() != rRight.maArea.aStart.Col())
            return rLeft.maArea.aStart.Col() < rRight.maArea.aStart.Col();

        if (rLeft.maArea.aStart.Row() != rRight.maArea.aStart.Row())
            return rLeft.maArea.aStart.Row() < rRight.maArea.aStart.Row();

        if (rLeft.maArea.aEnd.Tab() != rRight.maArea.aEnd.Tab())
            return rLeft.maArea.aEnd.Tab() < rRight.maArea.aEnd.Tab();

        if (rLeft.maArea.aEnd.Col() != rRight.maArea.aEnd.Col())
            return rLeft.maArea.aEnd.Col() < rRight.maArea.aEnd.Col();

        return rLeft.maArea.aEnd.Row() < rRight.maArea.aEnd.Row();
    }
};

}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateRangeDeleteRow)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
    m_pDoc->InsertTab(0, u"Formula"_ustr);

    ScRange aWholeArea(0, 0, 0, 100, 100, 0); // Large enough for all references used in the test.

    const std::vector<std::vector<const char*>> aData = {
        { "1""2""=SUM(A1:B1)" },
        { "3""4""=SUM(A2:B2)" },
        { nullptr, nullptr, nullptr },
        { "5""6""=SUM(A4:B4)" },
        { "7""8""=SUM(A5:B5)" }
    };

    insertRangeData(m_pDoc, ScAddress(0,0,0), aData);

    // Check initial formula values.
    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,3,0)));
    CPPUNIT_ASSERT_EQUAL(15.0, m_pDoc->GetValue(ScAddress(2,4,0)));

    // Check the area listener status.
    ScBroadcastAreaSlotMachine* pBASM = m_pDoc->GetBASM();
    CPPUNIT_ASSERT(pBASM);
    std::vector<sc::AreaListener> aListeners = pBASM->GetAllListeners(aWholeArea, sc::AreaOverlapType::Inside);
    std::sort(aListeners.begin(), aListeners.end(), SortByArea());

    // This check makes only sense if group listeners are activated.
#if !defined(USE_FORMULA_GROUP_LISTENER) || USE_FORMULA_GROUP_LISTENER
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should only be 2 area listeners.", size_t(2), aListeners.size());
    // First one should be group-listening on A1:B2.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This listener should be listening on A1:B2.", ScRange(0,0,0,1,1,0), aListeners[0].maArea);
    CPPUNIT_ASSERT_MESSAGE("This listener should be group-listening.", aListeners[0].mbGroupListening);
    // Second one should be group-listening on A4:B5.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This listener should be listening on A1:B2.", ScRange(0,0,0,1,1,0), aListeners[0].maArea);
    CPPUNIT_ASSERT_MESSAGE("This listener should be group-listening.", aListeners[0].mbGroupListening);
#endif

    // Make sure that C1:C2 and C4:C5 are formula groups.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(2,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    pFC = m_pDoc->GetFormulaCell(ScAddress(2,3,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    // Delete row 3.  This will merge the two formula groups.
    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);
    rFunc.DeleteCells(ScRange(0,2,0,m_pDoc->MaxCol(),2,0), &aMark, DelCellCmd::Rows, true);

    // Make sure C1:C4 belong to the same group.
    pFC = m_pDoc->GetFormulaCell(ScAddress(2,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());

    // This check makes only sense if group listeners are activated.
#if !defined(USE_FORMULA_GROUP_LISTENER) || USE_FORMULA_GROUP_LISTENER
    // We should only have one listener group-listening on A1:B4.
    aListeners = pBASM->GetAllListeners(aWholeArea, sc::AreaOverlapType::Inside);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should only be 1 area listener.", size_t(1), aListeners.size());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This listener should be listening on A1:B4.", ScRange(0,0,0,1,3,0), aListeners[0].maArea);
    CPPUNIT_ASSERT_MESSAGE("This listener should be group-listening.", aListeners[0].mbGroupListening);
#endif

    // Change the value of B4 and make sure the value of C4 changes.
    rFunc.SetValueCell(ScAddress(1,3,0), 100.0, false);
    CPPUNIT_ASSERT_EQUAL(107.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);

    // Undo the value change in B4, and make sure C4 follows.
    pUndoMgr->Undo();
    CPPUNIT_ASSERT_EQUAL(15.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    // Undo the deletion of row 3.
    pUndoMgr->Undo();

    // Make sure that C1:C2 and C4:C5 are formula groups again.
    pFC = m_pDoc->GetFormulaCell(ScAddress(2,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    pFC = m_pDoc->GetFormulaCell(ScAddress(2,3,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    // Check the values of formula cells again.
    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,3,0)));
    CPPUNIT_ASSERT_EQUAL(15.0, m_pDoc->GetValue(ScAddress(2,4,0)));

    aListeners = pBASM->GetAllListeners(aWholeArea, sc::AreaOverlapType::Inside);
    std::sort(aListeners.begin(), aListeners.end(), SortByArea());

    // This check makes only sense if group listeners are activated.
#if !defined(USE_FORMULA_GROUP_LISTENER) || USE_FORMULA_GROUP_LISTENER
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should only be 2 area listeners.", size_t(2), aListeners.size());
    // First one should be group-listening on A1:B2.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This listener should be listening on A1:B2.", ScRange(0,0,0,1,1,0), aListeners[0].maArea);
    CPPUNIT_ASSERT_MESSAGE("This listener should be group-listening.", aListeners[0].mbGroupListening);
    // Second one should be group-listening on A4:B5.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("This listener should be listening on A1:B2.", ScRange(0,0,0,1,1,0), aListeners[0].maArea);
    CPPUNIT_ASSERT_MESSAGE("This listener should be group-listening.", aListeners[0].mbGroupListening);
#endif

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateExternal)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
    m_pDoc->InsertTab(0, u"Formula"_ustr);

    // Launch an external document shell.
    ScDocShellRef xExtDocSh = new ScDocShell;

    SfxMedium* pMed = new SfxMedium(u"file:///extdata.fake"_ustr, StreamMode::STD_READWRITE);
    xExtDocSh->DoLoad(pMed);
    ScDocument& rExtDoc = xExtDocSh->GetDocument();

    // Populate A1:A3.
    rExtDoc.InsertTab(0, u"Data"_ustr);
    rExtDoc.SetString(ScAddress(0,0,0), u"A"_ustr);
    rExtDoc.SetString(ScAddress(0,1,0), u"B"_ustr);
    rExtDoc.SetString(ScAddress(0,2,0), u"C"_ustr);

    {
        // Insert formula cells in A7:A10 of the host document, referencing A1:A3
        // of the external document.
        const std::vector<std::vector<const char*>> aData = {
            { "='file:///extdata.fake'#$Data.A1" },
            { "='file:///extdata.fake'#$Data.A2" },
            { "='file:///extdata.fake'#$Data.A3" },
            { "=COUNTA('file:///extdata.fake'#$Data.A1:A3)" }
        };

        insertRangeData(m_pDoc, ScAddress(0,6,0), aData);
    }

    // Check the formula results.
    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(0,6,0)));
    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(0,7,0)));
    CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(0,8,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0,9,0)));

    // Check the formulas too.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A1"_ustr, m_pDoc->GetFormula(0,6,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A2"_ustr, m_pDoc->GetFormula(0,7,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A3"_ustr, m_pDoc->GetFormula(0,8,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=COUNTA('file:///extdata.fake'#$Data.A1:A3)"_ustr, m_pDoc->GetFormula(0,9,0));

    // Delete rows 1 and 2. This should not change the references in the formula cells below.
    ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);
    rDocFunc.DeleteCells(ScRange(0,0,0,m_pDoc->MaxCol(),1,0), &aMark, DelCellCmd::CellsUptrue);

    // Check the shifted formula cells now in A5:A8.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A1"_ustr, m_pDoc->GetFormula(0,4,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A2"_ustr, m_pDoc->GetFormula(0,5,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A3"_ustr, m_pDoc->GetFormula(0,6,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=COUNTA('file:///extdata.fake'#$Data.A1:A3)"_ustr, m_pDoc->GetFormula(0,7,0));

    // Undo and check the formulas again.
    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);
    pUndoMgr->Undo();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A1"_ustr, m_pDoc->GetFormula(0,6,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A2"_ustr, m_pDoc->GetFormula(0,7,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A3"_ustr, m_pDoc->GetFormula(0,8,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=COUNTA('file:///extdata.fake'#$Data.A1:A3)"_ustr, m_pDoc->GetFormula(0,9,0));

    // Redo the row deletion and check the formulas again.
    pUndoMgr->Redo();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A1"_ustr, m_pDoc->GetFormula(0,4,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A2"_ustr, m_pDoc->GetFormula(0,5,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"='file:///extdata.fake'#$Data.A3"_ustr, m_pDoc->GetFormula(0,6,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=COUNTA('file:///extdata.fake'#$Data.A1:A3)"_ustr, m_pDoc->GetFormula(0,7,0));

    xExtDocSh->DoClose();

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasInsertRow)
{
    struct
    {
        bool checkContent( ScDocument* pDoc )
        {
            // B1:B2 and B4:B5 should point to $A$5.
            SCROW pRows[] = { 0, 1, 3, 4 };
            for (size_t i = 0; i < SAL_N_ELEMENTS(pRows); ++i)
            {
                ScAddress aPos(1, pRows[i], 0);
                CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=$A$5"_ustr, pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab()));
            }

            // B1:B2 should be grouped.
            ScFormulaCell* pFC = pDoc->GetFormulaCell(ScAddress(1,0,0));
            if (!pFC || pFC->GetSharedTopRow() != 0 || pFC->GetSharedLength() != 2)
            {
                cerr << "B1:B2 should be grouped." << endl;
                return false;
            }

            // B4:B5 should be grouped.
            pFC = pDoc->GetFormulaCell(ScAddress(1,3,0));
            if (!pFC || pFC->GetSharedTopRow() != 3 || pFC->GetSharedLength() != 2)
            {
                cerr << "B4:B5 should be grouped." << endl;
                return false;
            }

            return true;
        }

        bool checkContentUndo( ScDocument* pDoc )
        {
            for (SCROW i = 0; i <= 3; ++i)
            {
                ScAddress aPos(1,i,0);
                CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=$A$4"_ustr, pDoc->GetFormula(aPos.Col(), aPos.Row(), aPos.Tab()));
            }

            // Ensure that B5 is empty.
            if (pDoc->GetCellType(ScAddress(1,4,0)) != CELLTYPE_NONE)
            {
                cerr << "B5 should be empty." << endl;
                return false;
            }

            // B1:B4 should be grouped.
            ScFormulaCell* pFC = pDoc->GetFormulaCell(ScAddress(1,0,0));
            if (!pFC || pFC->GetSharedTopRow() != 0 || pFC->GetSharedLength() != 4)
            {
                cerr << "B1:B4 should be grouped." << endl;
                return false;
            }

            return true;
        }

    } aCheck;

    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Scenario inspired by fdo#76470.

    // Set value to A4.
    m_pDoc->SetValue(ScAddress(0,3,0), 4.0);

    {
        // Set formula cells in B1:B4 all referencing A4 as absolute reference.
        const std::vector<std::vector<const char*>> aData = {
            { "=$A$4" },
            { "=$A$4" },
            { "=$A$4" },
            { "=$A$4" }
        };

        insertRangeData(m_pDoc, ScAddress(1,0,0), aData);
    }

    // Insert a new row at row 3.
    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);
    rFunc.InsertCells(ScRange(0,2,0,m_pDoc->MaxCol(),2,0), &aMark, INS_INSROWS_BEFORE, truetrue);

    bool bResult = aCheck.checkContent(m_pDoc);
    CPPUNIT_ASSERT_MESSAGE("Failed on the initial content check.", bResult);

    // Undo and check its result.
    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);
    pUndoMgr->Undo();

    bResult = aCheck.checkContentUndo(m_pDoc);
    CPPUNIT_ASSERT_MESSAGE("Failed on the content check after undo.", bResult);

    // Redo and check its result.
    pUndoMgr->Redo();
    bResult = aCheck.checkContent(m_pDoc);
    CPPUNIT_ASSERT_MESSAGE("Failed on the content check after redo.", bResult);

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasDeleteRows)
{
    m_pDoc->InsertTab(0, u"Test"_ustr);
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    {
        // Fill data cells A1:A20 and formula cells B1:B20.  Formulas in
        // B1:B10 and B11:B20 should be different.
        const std::vector<std::vector<const char*>> aData = {
            { "0""=RC[-1]+1" },
            { "1""=RC[-1]+1" },
            { "2""=RC[-1]+1" },
            { "3""=RC[-1]+1" },
            { "4""=RC[-1]+1" },
            { "5""=RC[-1]+1" },
            { "6""=RC[-1]+1" },
            { "7""=RC[-1]+1" },
            { "8""=RC[-1]+1" },
            { "9""=RC[-1]+1" },
            { "10""=RC[-1]+11" },
            { "11""=RC[-1]+11" },
            { "12""=RC[-1]+11" },
            { "13""=RC[-1]+11" },
            { "14""=RC[-1]+11" },
            { "15""=RC[-1]+11" },
            { "16""=RC[-1]+11" },
            { "17""=RC[-1]+11" },
            { "18""=RC[-1]+11" },
            { "19""=RC[-1]+11" }
        };

        insertRangeData(m_pDoc, ScAddress(0,0,0), aData);
    }

    // B1:B10 should be shared.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(10), pFC->GetSharedLength());
    // B11:B20 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,10,0));
    CPPUNIT_ASSERT_MESSAGE("1,10 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,10 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(10), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(10), pFC->GetSharedLength());

    // Delete rows 9:12
    m_pDoc->DeleteRow(ScRange(0,8,0,m_pDoc->MaxCol(),11,0));

    // B1:B8 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedLength());
    // B9:B16 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,8,0));
    CPPUNIT_ASSERT_MESSAGE("1,8 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,8 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedLength());

    // Delete row 3
    m_pDoc->DeleteRow(ScRange(0,2,0,m_pDoc->MaxCol(),2,0));

    // B1:B7 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(7), pFC->GetSharedLength());
    // B8:B15 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,7,0));
    CPPUNIT_ASSERT_MESSAGE("1,7 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,7 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(7), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedLength());

    // Delete row 5
    m_pDoc->DeleteRow(ScRange(0,4,0,m_pDoc->MaxCol(),4,0));

    // B1:B6 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,0 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedLength());
    // B7:B14 should be shared.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,6,0));
    CPPUNIT_ASSERT_MESSAGE("1,6 must be a shared formula cell.", pFC);
    CPPUNIT_ASSERT_MESSAGE("1,6 must be a shared formula cell.", pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(8), pFC->GetSharedLength());
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasDeleteColumns)
{
    using namespace formula;

    m_pDoc->InsertTab(0, u"Test"_ustr);

    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);

    // First, test a single cell case.  A value in B1 and formula in C1.
    m_pDoc->SetValue(ScAddress(1,0,0), 11.0);
    m_pDoc->SetString(ScAddress(2,0,0), u"=RC[-1]"_ustr);
    CPPUNIT_ASSERT_EQUAL(11.0, m_pDoc->GetValue(ScAddress(2,0,0)));

    // Delete column B.
    rFunc.DeleteCells(ScRange(1,0,0,1,m_pDoc->MaxRow(),0), &aMark, DelCellCmd::CellsLeft, true);
    CPPUNIT_ASSERT_EQUAL(u"#REF!"_ustr, m_pDoc->GetString(ScAddress(1,0,0)));

    // The reference should still point to row 1 but the column status should be set to 'deleted'.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT(pFC);
    const ScTokenArray* pCode = pFC->GetCode();
    CPPUNIT_ASSERT(pCode);
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), pCode->GetLen());
    const FormulaToken* pToken = pCode->GetArray()[0];
    CPPUNIT_ASSERT_EQUAL(svSingleRef, pToken->GetType());
    const ScSingleRefData* pSRef = pToken->GetSingleRef();
    CPPUNIT_ASSERT(pSRef->IsColDeleted());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pSRef->toAbs(*m_pDoc, ScAddress(1,0,0)).Row());

    // The formula string should show #REF! in lieu of the column position (only for Calc A1 syntax).
    sc::CompileFormulaContext aCFCxt(*m_pDoc, FormulaGrammar::GRAM_ENGLISH);
    CPPUNIT_ASSERT_EQUAL(u"=#REF!1"_ustr, pFC->GetFormula(aCFCxt));

    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);

    // Undo and make sure the deleted flag is gone.
    pUndoMgr->Undo();
    CPPUNIT_ASSERT_EQUAL(11.0, m_pDoc->GetValue(ScAddress(2,0,0)));
    pFC = m_pDoc->GetFormulaCell(ScAddress(2,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(u"=B1"_ustr, pFC->GetFormula(aCFCxt));

    // Clear row 1 and move over to a formula group case.
    clearRange(m_pDoc, ScRange(0,0,0,m_pDoc->MaxCol(),0,0));

    // Fill A1:B2 with numbers, and C1:C2 with formula that reference those numbers.
    for (SCROW i = 0; i <= 1; ++i)
    {
        m_pDoc->SetValue(ScAddress(0,i,0), (i+1));
        m_pDoc->SetValue(ScAddress(1,i,0), (i+11));
        m_pDoc->SetString(ScAddress(2,i,0), u"=RC[-2]+RC[-1]"_ustr);
        double fCheck = m_pDoc->GetValue(ScAddress(0,i,0));
        fCheck += m_pDoc->GetValue(ScAddress(1,i,0));
        CPPUNIT_ASSERT_EQUAL(fCheck, m_pDoc->GetValue(ScAddress(2,i,0)));
    }

    // Delete column B.
    rFunc.DeleteCells(ScRange(1,0,0,1,m_pDoc->MaxRow(),0), &aMark, DelCellCmd::CellsLeft, true);

    for (SCROW i = 0; i <= 1; ++i)
    {
        ScAddress aPos(1,i,0);
        CPPUNIT_ASSERT_EQUAL(u"#REF!"_ustr, m_pDoc->GetString(aPos));
    }

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0)); // B1
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(u"=A1+#REF!1"_ustr, pFC->GetFormula(aCFCxt));
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,1,0)); // B2
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(u"=A2+#REF!2"_ustr, pFC->GetFormula(aCFCxt));

    // Undo deletion of column B and check the results of C1:C2.
    pUndoMgr->Undo();
    for (SCROW i = 0; i <= 1; ++i)
    {
        double fCheck = m_pDoc->GetValue(ScAddress(0,i,0));
        fCheck += m_pDoc->GetValue(ScAddress(1,i,0));
        CPPUNIT_ASSERT_EQUAL(fCheck, m_pDoc->GetValue(ScAddress(2,i,0)));
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateMoveSheets)
{
    m_pDoc->InsertTab(0, u"Sheet1"_ustr);
    m_pDoc->InsertTab(1, u"Sheet2"_ustr);
    m_pDoc->InsertTab(2, u"Sheet3"_ustr);

    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // make sure auto calc is on.

    // Switch to R1C1 for ease of repeated formula insertions.
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    // Fill numbers in A1:A8 on Sheet2.
    for (SCROW i = 0; i <= 7; ++i)
        m_pDoc->SetValue(ScAddress(0,i,1), i+1);

    // Fill formula cells A1:A8 on Sheet1, to refer to the same cell address on Sheet2.
    for (SCROW i = 0; i <= 7; ++i)
        m_pDoc->SetString(ScAddress(0,i,0), u"=Sheet2!RC"_ustr);

    // Check the results.
    for (SCROW i = 0; i <= 7; ++i)
        CPPUNIT_ASSERT_EQUAL(static_cast<double>(i+1), m_pDoc->GetValue(ScAddress(0,i,0)));

    // Move Sheet3 to the leftmost position before Sheet1.
    m_pDoc->MoveTab(2, 0);

    // Check sheet names.
    std::vector<OUString> aTabNames = m_pDoc->GetAllTableNames();
    CPPUNIT_ASSERT_MESSAGE("There should be at least 3 sheets.", aTabNames.size() >= 3);
    CPPUNIT_ASSERT_EQUAL(u"Sheet3"_ustr, aTabNames[0]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet1"_ustr, aTabNames[1]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet2"_ustr, aTabNames[2]);

    // Check the results again on Sheet1.
    for (SCROW i = 0; i <= 7; ++i)
    {
        CPPUNIT_ASSERT_EQUAL(static_cast<double>(i+1), m_pDoc->GetValue(ScAddress(0,i,1)));
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula expression.", u"=Sheet2!RC"_ustr, m_pDoc->GetFormula(0,i,1));
    }

    // Insert a new sheet at the left end.
    m_pDoc->InsertTab(0, u"Sheet4"_ustr);

    // Check sheet names.
    aTabNames = m_pDoc->GetAllTableNames();
    CPPUNIT_ASSERT_MESSAGE("There should be at least 4 sheets.", aTabNames.size() >= 4);
    CPPUNIT_ASSERT_EQUAL(u"Sheet4"_ustr, aTabNames[0]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet3"_ustr, aTabNames[1]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet1"_ustr, aTabNames[2]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet2"_ustr, aTabNames[3]);

    // Check the results again on Sheet1.
    for (SCROW i = 0; i <= 7; ++i)
    {
        CPPUNIT_ASSERT_EQUAL(static_cast<double>(i+1), m_pDoc->GetValue(ScAddress(0,i,2)));
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula expression.", u"=Sheet2!RC"_ustr, m_pDoc->GetFormula(0,i,2));
    }

    // Delete Sheet4.
    m_pDoc->DeleteTab(0);

    // Check sheet names.
    aTabNames = m_pDoc->GetAllTableNames();
    CPPUNIT_ASSERT_MESSAGE("There should be at least 3 sheets.", aTabNames.size() >= 3);
    CPPUNIT_ASSERT_EQUAL(u"Sheet3"_ustr, aTabNames[0]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet1"_ustr, aTabNames[1]);
    CPPUNIT_ASSERT_EQUAL(u"Sheet2"_ustr, aTabNames[2]);

    // Check the results again on Sheet1.
    for (SCROW i = 0; i <= 7; ++i)
    {
        CPPUNIT_ASSERT_EQUAL(static_cast<double>(i+1), m_pDoc->GetValue(ScAddress(0,i,1)));
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula expression.", u"=Sheet2!RC"_ustr, m_pDoc->GetFormula(0,i,1));
    }

    m_pDoc->DeleteTab(2);
    m_pDoc->DeleteTab(1);
    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateCopySheets)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // make sure auto calc is on.

    m_pDoc->InsertTab(0, u"Sheet1"_ustr);
    m_pDoc->InsertTab(1, u"Sheet2"_ustr);

    m_pDoc->SetValue(ScAddress(0,0,1), 1.0); // A1 on Sheet2
    m_pDoc->SetValue(ScAddress(0,1,1), 2.0); // A2 on Sheet2

    // Reference values on Sheet2, but use absolute sheet references.
    m_pDoc->SetString(ScAddress(0,0,0), u"=$Sheet2.A1"_ustr);
    m_pDoc->SetString(ScAddress(0,1,0), u"=$Sheet2.A2"_ustr);

    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,1,0)));

    // Copy Sheet1 and insert the copied sheet before the current Sheet1 position.
    m_pDoc->CopyTab(0, 0);

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=$Sheet2.A1"_ustr, m_pDoc->GetFormula(0,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=$Sheet2.A2"_ustr, m_pDoc->GetFormula(0,1,0));

    // Check the values on the copied sheet.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,1,0)));

    // Check the values on the original sheet.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,1)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,1,1)));

    m_pDoc->DeleteTab(2);
    m_pDoc->DeleteTab(1);
    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasRefUpdateDeleteSheets)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // make sure auto calc is on.

    m_pDoc->InsertTab(0, u"Sheet1"_ustr);
    m_pDoc->InsertTab(1, u"Sheet2"_ustr);

    // Set values to B2:B4 on Sheet2.
    m_pDoc->SetValue(ScAddress(1,1,1), 1.0);
    m_pDoc->SetValue(ScAddress(1,2,1), 2.0);
    m_pDoc->SetValue(ScAddress(1,3,1), 3.0);

    // Set formulas in A1:A3 on Sheet1 that reference B2:B4 on Sheet2.
    m_pDoc->SetString(ScAddress(0,0,0), u"=Sheet2.B2"_ustr);
    m_pDoc->SetString(ScAddress(0,1,0), u"=Sheet2.B3"_ustr);
    m_pDoc->SetString(ScAddress(0,2,0), u"=Sheet2.B4"_ustr);

    // Check the formula results.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0,2,0)));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=Sheet2.B2"_ustr, m_pDoc->GetFormula(0,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=Sheet2.B3"_ustr, m_pDoc->GetFormula(0,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=Sheet2.B4"_ustr, m_pDoc->GetFormula(0,2,0));

    // Delete Sheet2.
    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
    rFunc.DeleteTable(1, true);

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=#REF!.B2"_ustr, m_pDoc->GetFormula(0,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=#REF!.B3"_ustr, m_pDoc->GetFormula(0,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=#REF!.B4"_ustr, m_pDoc->GetFormula(0,2,0));

    // Undo the deletion and make sure the formulas are back to the way they were.
    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);
    pUndoMgr->Undo();

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=Sheet2.B2"_ustr, m_pDoc->GetFormula(0,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=Sheet2.B3"_ustr, m_pDoc->GetFormula(0,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula", u"=Sheet2.B4"_ustr, m_pDoc->GetFormula(0,2,0));

    // TODO: We can't test redo yet as ScUndoDeleteTab::Redo() relies on
    // view shell to do its thing.

    m_pDoc->DeleteTab(1);
    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulasCopyPaste)
{
    m_pDoc->InsertTab(0, u"Test"_ustr);
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    // Fill formula cells B1:B10.
    for (SCROW i = 0; i <= 9; ++i)
        m_pDoc->SetString(1, i, 0, u"=RC[-1]"_ustr);

    ScAddress aPos(1, 8, 0); // B9
    ScFormulaCell* pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B9 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(10), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Copy formulas in B6:B9 to the clipboard doc.
    ScRange aSrcRange(1,5,0,1,8,0); // B6:B9
    ScDocument aClipDoc(SCDOCMODE_CLIP);
    copyToClip(m_pDoc, aSrcRange, &aClipDoc);
    pFC = aClipDoc.GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("B9 in the clip doc should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(5), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    // Paste them to C2:C10.
    ScRange aDestRange(2,1,0,2,9,0);
    pasteFromClip(m_pDoc, aDestRange, &aClipDoc);
    aPos.SetCol(2);
    aPos.SetRow(1);
    pFC = m_pDoc->GetFormulaCell(aPos);
    CPPUNIT_ASSERT_MESSAGE("C2 should be a formula cell.", pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(9), pFC->GetSharedLength());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());

    ScRange aRange(1,0,0,1,9,0); // B1:B10
    ScDocument* pUndoDoc = new ScDocument(SCDOCMODE_UNDO);
    pUndoDoc->InitUndo(*m_pDoc, 0, 0, truetrue);
    m_pDoc->CopyToDocument(aRange, InsertDeleteFlags::CONTENTS, false, *pUndoDoc);
    std::unique_ptr<ScUndoPaste> pUndo(createUndoPaste(*m_xDocShell, aRange, ScDocumentUniquePtr(pUndoDoc)));

    // First, make sure the formula cells are shared in the undo document.
    aPos.SetCol(1);
    for (SCROW i = 0; i <= 9; ++i)
    {
        aPos.SetRow(i);
        pFC = pUndoDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT_MESSAGE("Must be a formula cell.", pFC);
        CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(10), pFC->GetSharedLength());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());
    }

    // Overwrite B1:B10.
    for (SCROW i = 0; i <= 9; ++i)
        m_pDoc->SetValue(ScAddress(1,i,0), i*10);

    for (SCROW i = 0; i <= 9; ++i)
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Numeric cell was expected.", CELLTYPE_VALUE, m_pDoc->GetCellType(ScAddress(1,i,0)));

    // Undo the action to fill B1:B10 with formula cells again.
    pUndo->Undo();

    aPos.SetCol(1);
    for (SCROW i = 0; i <= 9; ++i)
    {
        aPos.SetRow(i);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT_MESSAGE("This should be a formula cell.", pFC);
        CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(10), pFC->GetSharedLength());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("The token is expected to be shared.", pFC->GetCode(), pFC->GetSharedCode());
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaInsertColumn)
{
    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Set shared formula group over H2:H3.
    m_pDoc->SetString(ScAddress(7,1,0), u"=G3*B3"_ustr);
    m_pDoc->SetString(ScAddress(7,2,0), u"=G4*B4"_ustr);

    // Insert a single column at Column F. This used to crash before fdo#74041.
    m_pDoc->InsertCol(ScRange(5,0,0,5,m_pDoc->MaxRow(),0));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=H3*B3"_ustr, m_pDoc->GetFormula(8,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=H4*B4"_ustr, m_pDoc->GetFormula(8,2,0));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaMoveBlock)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Set values to A1:A3.
    m_pDoc->SetValue(ScAddress(0,0,0), 1.0);
    m_pDoc->SetValue(ScAddress(0,1,0), 2.0);
    m_pDoc->SetValue(ScAddress(0,2,0), 3.0);

    // Set formulas in B1:B3 to reference A1:A3.
    m_pDoc->SetString(ScAddress(1,0,0), u"=RC[-1]"_ustr);
    m_pDoc->SetString(ScAddress(1,1,0), u"=RC[-1]"_ustr);
    m_pDoc->SetString(ScAddress(1,2,0), u"=RC[-1]"_ustr);

    ScRange aFormulaRange(1,0,0,1,2,0);

    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    clearFormulaCellChangedFlag(*m_pDoc, aFormulaRange);

    // Move A1:A3 to D1:D3.
    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
    bool bMoved = rFunc.MoveBlock(ScRange(0,0,0,0,2,0), ScAddress(3,0,0), truetruefalsetrue);
    CPPUNIT_ASSERT(bMoved);

    // The result should stay the same.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    clearFormulaCellChangedFlag(*m_pDoc, aFormulaRange);

    // Make sure these formula cells in B1:B3 have correct positions even after the move.
    std::vector<SCROW> aRows { 0, 1, 2 };
    bool bRes = checkFormulaPositions(*m_pDoc, 0, 1, aRows.data(), aRows.size());
    CPPUNIT_ASSERT(bRes);

    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);

    // Undo and check the result.
    pUndoMgr->Undo();
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    clearFormulaCellChangedFlag(*m_pDoc, aFormulaRange);

    // Redo and check the result.
    pUndoMgr->Redo();
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    // Clear the range and start over.
    clearRange(m_pDoc, ScRange(0,0,0,m_pDoc->MaxCol(),m_pDoc->MaxRow(),0));

    // Set values 1,2,3,4,5 to A1:A5.
    for (SCROW i = 0; i <= 4; ++i)
        m_pDoc->SetValue(ScAddress(0,i,0), (i+1));

    // Set formulas to B1:B5.
    for (SCROW i = 0; i <= 4; ++i)
        m_pDoc->SetString(ScAddress(1,i,0), u"=RC[-1]"_ustr);

    // Check the initial formula results.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(1,3,0)));
    CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,4,0)));

    // Move A1:A2 to D2:D3.
    bMoved = rFunc.MoveBlock(ScRange(0,0,0,0,1,0), ScAddress(3,1,0), truetruefalsetrue);
    CPPUNIT_ASSERT(bMoved);

    // Check the formula values again.  They should not change.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(1,3,0)));
    CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,4,0)));

    pUndoMgr->Undo();
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(1,3,0)));
    CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,4,0)));

    pUndoMgr->Redo();
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(1,3,0)));
    CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,4,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaUpdateOnNamedRangeChange)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    const charconst pName = "MyRange";
    const charconst pExpr1 = "$Test.$A$1:$A$3";
    const charconst pExpr2 = "$Test.$A$1:$A$4";

    RangeNameDef aName;
    aName.mpName = pName;
    aName.mpExpr = pExpr1;
    aName.mnIndex = 1;
    std::unique_ptr<ScRangeName> pNames(new ScRangeName);
    bool bSuccess = insertRangeNames(m_pDoc, pNames.get(), &aName, &aName + 1);
    CPPUNIT_ASSERT(bSuccess);
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pNames->size());
    m_pDoc->SetRangeName(std::move(pNames));

    // Set values to A1:A4.
    m_pDoc->SetValue(ScAddress(0,0,0), 1.0);
    m_pDoc->SetValue(ScAddress(0,1,0), 2.0);
    m_pDoc->SetValue(ScAddress(0,2,0), 3.0);
    m_pDoc->SetValue(ScAddress(0,3,0), 4.0);

    // Set formula to B1:B3.
    m_pDoc->SetString(ScAddress(1,0,0), u"=SUM(MyRange)"_ustr);
    m_pDoc->SetString(ScAddress(1,1,0), u"=SUM(MyRange)"_ustr);
    m_pDoc->SetString(ScAddress(1,2,0), u"=SUM(MyRange)"_ustr);

    // Set single formula with no named range to B5.
    m_pDoc->SetString(ScAddress(1,4,0), u"=ROW()"_ustr);

    // Set shared formula with no named range to B7:B8.
    m_pDoc->SetString(ScAddress(1,6,0), u"=ROW()"_ustr);
    m_pDoc->SetString(ScAddress(1,7,0), u"=ROW()"_ustr);

    // B1:B3 should be grouped.
    ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    // B7:B8 should be grouped.
    pFC = m_pDoc->GetFormulaCell(ScAddress(1,6,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(6), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,4,0)));
    CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(1,6,0)));
    CPPUNIT_ASSERT_EQUAL(8.0, m_pDoc->GetValue(ScAddress(1,7,0)));

    // Set a single formula to C1.
    m_pDoc->SetString(ScAddress(2,0,0), u"=AVERAGE(MyRange)"_ustr);
    pFC = m_pDoc->GetFormulaCell(ScAddress(2,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_MESSAGE("C1 should not be shared.", !pFC->IsShared());
    CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(2,0,0)));

    // Update the range of MyRange.
    pNames.reset(new ScRangeName);
    aName.mpExpr = pExpr2;
    bSuccess = insertRangeNames(m_pDoc, pNames.get(), &aName, &aName + 1);
    CPPUNIT_ASSERT(bSuccess);
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pNames->size());
    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();

    typedef std::map<OUString, ScRangeName> NameMapType;
    NameMapType aNewNames;
    OUString aScope(STR_GLOBAL_RANGE_NAME);
    aNewNames.insert(std::make_pair(aScope, std::move(*pNames)));
    rFunc.ModifyAllRangeNames(aNewNames);

    // Check to make sure all displayed formulas are still good.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=SUM(MyRange)"_ustr, m_pDoc->GetFormula(1,0,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=SUM(MyRange)"_ustr, m_pDoc->GetFormula(1,1,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=SUM(MyRange)"_ustr, m_pDoc->GetFormula(1,2,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=ROW()"_ustr, m_pDoc->GetFormula(1,4,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=ROW()"_ustr, m_pDoc->GetFormula(1,6,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=ROW()"_ustr, m_pDoc->GetFormula(1,7,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula!", u"=AVERAGE(MyRange)"_ustr, m_pDoc->GetFormula(2,0,0));

    // Check the calculation results as well.
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(1,2,0)));
    CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(1,4,0)));
    CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(1,6,0)));
    CPPUNIT_ASSERT_EQUAL(8.0, m_pDoc->GetValue(ScAddress(1,7,0)));
    CPPUNIT_ASSERT_EQUAL(2.5, m_pDoc->GetValue(ScAddress(2,0,0)));

    // Change the value of A4 and make sure the value change gets propagated.
    m_pDoc->SetValue(ScAddress(0,3,0), 0.0);
    CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaUpdateOnDBChange)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"RangeTest"_ustr);

    // Put 1, 2, 3, 4 in A1:A4.
    for (SCROW i = 0; i <= 3; ++i)
        m_pDoc->SetValue(ScAddress(0,i,0), (i+1));

    ScDBCollection* pDBs = m_pDoc->GetDBCollection();
    CPPUNIT_ASSERT_MESSAGE("Failed to fetch DB collection object.", pDBs);

    // Define database range 'MyRange' for A1:A2.
    std::unique_ptr<ScDBData> pData(new ScDBData(u"MyRange"_ustr, 0, 0, 0, 0, 1));
    bool bInserted = pDBs->getNamedDBs().insert(std::move(pData));
    CPPUNIT_ASSERT_MESSAGE("Failed to insert a new database range.", bInserted);

    // Insert in C2:C4 a group of formula cells that reference MyRange.
    for (SCROW i = 1; i <= 3; ++i)
        m_pDoc->SetString(ScAddress(2,i,0), u"=SUM(MyRange)"_ustr);

    // Make sure C2:C4 is a formula group.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(2,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT(pFC->IsSharedTop());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    // Check the initial formula results.
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    ScDBDocFunc aFunc(*m_xDocShell);

    // Change the range referenced by MyRange to A1:A4.
    ScDBCollection aNewDBs(*m_pDoc);
    std::unique_ptr<ScDBData> pNewData(new ScDBData(u"MyRange"_ustr, 0, 0, 0, 0, 3));
    bInserted = aNewDBs.getNamedDBs().insert(std::move(pNewData));
    CPPUNIT_ASSERT_MESSAGE("Failed to insert a new database range.", bInserted);

    std::vector<ScRange> aDeleted;
    aFunc.ModifyAllDBData(aNewDBs, aDeleted);

    // Check the updated formula results.
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
    CPPUNIT_ASSERT(pUndoMgr);

    // Undo and check the results.
    pUndoMgr->Undo();
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    // Redo and check the results.
    pUndoMgr->Redo();
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(2,1,0)));
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(2,2,0)));
    CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(2,3,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaAbsCellListener)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    m_pDoc->SetValue(ScAddress(0,0,0), 1.0);

    const std::vector<std::vector<const char*>> aData = {
        { "=$A$1" },
        { "=$A$1" },
        { "=$A$1" }
    };

    insertRangeData(m_pDoc, ScAddress(1,0,0), aData);

    // A1 should have 3 listeners listening into it.
    const SvtBroadcaster* pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
    CPPUNIT_ASSERT(pBC);
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pBC->GetAllListeners().size());

    // Check the formula results.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(1,2,0)));

    // Change the value of A1 and make sure B1:B3 follows.
    m_pDoc->SetValue(ScAddress(0,0,0), 2.5);

    CPPUNIT_ASSERT_EQUAL(2.5, m_pDoc->GetValue(ScAddress(1,0,0)));
    CPPUNIT_ASSERT_EQUAL(2.5, m_pDoc->GetValue(ScAddress(1,1,0)));
    CPPUNIT_ASSERT_EQUAL(2.5, m_pDoc->GetValue(ScAddress(1,2,0)));

    m_pDoc->DeleteTab(0);
}

static double checkNewValuesNotification( ScDocument* pDoc, const ScAddress& rOrgPos )
{
    ScAddress aPos(rOrgPos);
    aPos.IncCol();
    pDoc->SetValues( aPos, {1024.0, 2048.0, 4096.0, 8192.0, 16384.0});
    aPos = rOrgPos;
    double fVal = 0.0;
    for (SCROW i=0; i < 5; ++i)
    {
        fVal += pDoc->GetValue(aPos);
        aPos.IncRow();
    }
    return fVal;
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaUnshareAreaListeners)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    const std::vector<std::vector<const char*>> aData = {
        { "=SUM(B1:B2)""1" },
        { "=SUM(B2:B3)""2" },
        { "=SUM(B3:B4)""4" },
        {             nullptr, "8" }
    };

    insertRangeData(m_pDoc, ScAddress(0,0,0), aData);

    // Check that A1:A3 is a formula group.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(3), pFC->GetSharedLength());

    m_pDoc->SetValue(ScAddress(0,1,0), 23.0);   // unshare at A2
    m_pDoc->SetValue(ScAddress(1,1,0), 16.0);   // change value of B2
    m_pDoc->SetValue(ScAddress(1,2,0), 32.0);   // change value of B3
    // A1 and A3 should be recalculated.
    CPPUNIT_ASSERT_EQUAL(17.0, m_pDoc->GetValue(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL(40.0, m_pDoc->GetValue(ScAddress(0,2,0)));

    clearRange(m_pDoc, ScRange( 0,0,0, 1,3,0));

    for (int nRun = 0; nRun < 7; ++nRun)
    {
        // Data in A2:C6
        const ScAddress aOrgPos(0,1,0);
        const std::vector<std::vector<const char*>> aData2 = {
            { "=SUM(B2:C2)",   "1",   "2" },
            { "=SUM(B3:C3)",   "4",   "8" },
            { "=SUM(B4:C4)",  "16",  "32" },
            { "=SUM(B5:C5)",  "64""128" },
            { "=SUM(B6:C6)""256""512" },
        };
        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Check that A2:A6 is a formula group.
        pFC = m_pDoc->GetFormulaCell(aOrgPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A2", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aOrgPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(5), pFC->GetSharedLength());

        // Overwrite and thus unshare formula in A3.
        // Check different code paths with different methods.
        ScAddress aPos(aOrgPos);
        aPos.IncRow(2);
        switch (nRun)
        {
            case 0:
                // Directly set a different formula cell, which bypasses
                // ScDocument::SetString(), mimicking formula input in view.
                {
                    ScFormulaCell* pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B4"_ustr);
                    ScDocFunc& rDocFunc = m_xDocShell->GetDocFunc();
                    rDocFunc.SetFormulaCell( aPos, pCell, false);
                }
            break;
            case 1:
                m_pDoc->SetString( aPos, u"=B4"_ustr);    // set formula
            break;
            case 2:
                m_pDoc->SetString( aPos, u"x"_ustr);      // set string
            break;
            case 3:
                m_pDoc->SetString( aPos, u"4096"_ustr);   // set number/numeric
            break;
            case 4:
                m_pDoc->SetValue( aPos, 4096.0);    // set numeric
            break;
            case 5:
                m_pDoc->SetValues( aPos, {4096.0}); // set numeric vector
            break;
            case 6:
                // Set formula cell vector.
                {
                    ScFormulaCell* pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B4"_ustr);
                    std::vector<ScFormulaCell*> aCells { pCell };
                    m_pDoc->SetFormulaCells( aPos, aCells);
                }
            break;
        }

        // Check that A2:A3 and A5:A6 are two formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A2", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(3);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A5", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());

        // Check that listeners were set up and formulas are updated when B2:B6
        // get new values input (tdf#123736).
        aPos = aOrgPos;
        aPos.IncCol();
        m_pDoc->SetValues( aPos, {1024.0, 2048.0, 4096.0, 8192.0, 16384.0});

        aPos = aOrgPos;
        CPPUNIT_ASSERT_EQUAL(1026.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        CPPUNIT_ASSERT_EQUAL(2056.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        if (nRun != 2)  // if not string
            CPPUNIT_ASSERT_EQUAL(4096.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        CPPUNIT_ASSERT_EQUAL(8320.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        CPPUNIT_ASSERT_EQUAL(16896.0, m_pDoc->GetValue(aPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,5,0));
    }

    // Check detach/regroup combinations of overlapping when setting formula
    // cell vectors.
    {
        // Fixed data in A3:C7, modified formula range A1:A9
        const ScAddress aOrgPos(0,2,0);
        ScAddress aPos( ScAddress::UNINITIALIZED);
        ScFormulaCell* pCell;
        std::vector<ScFormulaCell*> aCells;
        const std::vector<std::vector<const char*>> aData2 = {
            { "=SUM(B3:C3)",   "1",   "2" },
            { "=SUM(B4:C4)",   "4",   "8" },
            { "=SUM(B5:C5)",  "16",  "32" },
            { "=SUM(B6:C6)",  "64""128" },
            { "=SUM(B7:C7)""256""512" },
        };

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Add grouping formulas in A1:A2, keep A3:A7
        aPos = ScAddress(0,0,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=SUM(B1:C1)"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=SUM(B2:C2)"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check it is one formula group.
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A1", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(7), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Add formulas in A1:A2, keep A3:A7
        aPos = ScAddress(0,0,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B1+C1"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B2+C2"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A1", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(2);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(5), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Add formula in A2, overwrite A3, keep A4:A7
        aPos = ScAddress(0,1,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B2+C2"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B3+C3"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A2", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(2);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A4", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(4), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Overwrite A3:A4, keep A5:A7
        aPos = ScAddress(0,2,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B3+C3"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B4+C4"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(2);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A5", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(3), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Keep A3, overwrite A4:A5, keep A6:A7
        aPos = ScAddress(0,3,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B4+C4"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B5+C5"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", !pFC->IsSharedTop());
        aPos.IncRow(1);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A4", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(2);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A6", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Keep A3:A4, overwrite A5:A6, keep A7
        aPos = ScAddress(0,4,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B5+C5"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B6+C6"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(2);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A5", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());
        aPos.IncRow(2);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A7", !pFC->IsSharedTop());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Keep A3:A5, overwrite A6:A7
        aPos = ScAddress(0,5,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B6+C6"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B7+C7"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(3), pFC->GetSharedLength());
        aPos.IncRow(3);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A6", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Keep A3:A6, overwrite A7, add A8
        aPos = ScAddress(0,6,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B7+C7"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B8+C8"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(4), pFC->GetSharedLength());
        aPos.IncRow(4);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A7", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Keep A3:A7, add A8:A9
        aPos = ScAddress(0,7,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B8+C8"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=B9+C9"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check formula groups.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A3", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(5), pFC->GetSharedLength());
        aPos.IncRow(5);
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A7", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(2), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Keep A3:A7, add grouping formulas in A8:A9
        aPos = ScAddress(0,7,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=SUM(B8:C8)"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=SUM(B9:C9)"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check it is one formula group.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A1", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(7), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));

        insertRangeData(m_pDoc, aOrgPos, aData2);

        // Overwrite grouping formulas in A4:A5
        aPos = ScAddress(0,3,0);
        std::vector<ScFormulaCell*>().swap( aCells);
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=SUM(B4:C4)"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow();
        pCell = new ScFormulaCell( *m_pDoc, aPos, u"=SUM(B5:C5)"_ustr);
        aCells.push_back(pCell);
        aPos.IncRow(-1);
        m_pDoc->SetFormulaCells( aPos, aCells);

        // Check it is one formula group.
        aPos = aOrgPos;
        pFC = m_pDoc->GetFormulaCell(aPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_MESSAGE("A1", pFC->IsSharedTop());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared top row.", aPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Shared length."static_cast<SCROW>(5), pFC->GetSharedLength());

        // Check notification of setting new values.
        CPPUNIT_ASSERT_EQUAL(32426.0, checkNewValuesNotification( m_pDoc, aOrgPos));

        clearRange(m_pDoc, ScRange( 0,0,0, 2,8,0));
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaListenerDeleteArea)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test0"_ustr);
    m_pDoc->InsertTab(1, u"Test1"_ustr);

    const std::vector<std::vector<const char*>> aData0 = {
        { """""=Test1.C1" },
        { """""=Test1.C2" }
    };
    const std::vector<std::vector<const char*>> aData1 = {
        { "=Test0.A1""=Test0.B1""=SUM(A1:B1)" },
        { "=Test0.A2""=Test0.B2""=SUM(A2:B2)" },
    };

    insertRangeData(m_pDoc, ScAddress(0,0,0), aData0);
    insertRangeData(m_pDoc, ScAddress(0,0,1), aData1);

    // Check that Test1.A1:A2 and Test1.B1:B2 are formula groups.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0,0,1));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    pFC = m_pDoc->GetFormulaCell(ScAddress(1,0,1));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    m_pDoc->SetValue(ScAddress(0,1,0), 1.0);   // change value of Test0.A2
    m_pDoc->SetValue(ScAddress(1,1,0), 2.0);   // change value of Test0.B2
    // Test0.C2 should be recalculated.
    CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(2,1,0)));

    // Delete Test0.B2
    clearRange(m_pDoc, ScRange(1,1,0));
    // Test0.C2 should be recalculated.
    CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(2,1,0)));

    m_pDoc->DeleteTab(1);
    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaUpdateOnReplacement)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    const std::vector<std::vector<const char*>> aData = {
        { "1"              },
        { "=SUM($A$1:$A1)" },
        { "=SUM($A$1:$A2)" },
        { "=SUM($A$1:$A3)" },
        { "=SUM($A$1:$A4)" },
        { "=SUM($A$1:$A5)" },
        { "=SUM($A$1:$A6)" },
        { "=SUM($A$1:$A7)" }
    };

    insertRangeData(m_pDoc, ScAddress(0,0,0), aData);

    // Check that A2:A8 is a formula group.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(ScAddress(0,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(7), pFC->GetSharedLength());

    {   // Check initial results.
        ScAddress aPos(0,0,0);
        const double fResult[] = { 1.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0 };
        for (SCROW nRow = 1; nRow < 8; ++nRow)
        {
            aPos.SetRow(nRow);
            CPPUNIT_ASSERT_EQUAL( fResult[nRow], m_pDoc->GetValue( aPos));
        }
    }

    // Set up an undo object for deleting A4.
    ScRange aUndoRange(0,3,0,0,3,0);
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);
    aMark.SetMultiMarkArea(aUndoRange);
    ScDocumentUniquePtr pUndoDoc(new ScDocument(SCDOCMODE_UNDO));
    pUndoDoc->InitUndo(*m_pDoc, 0, 0);
    m_pDoc->CopyToDocument(aUndoRange, InsertDeleteFlags::CONTENTS, false, *pUndoDoc, &aMark);
    ScUndoDeleteContents aUndo(*m_xDocShell, aMark, aUndoRange, std::move(pUndoDoc), false, InsertDeleteFlags::CONTENTS, true);

    // Delete A4.
    clearRange(m_pDoc, aUndoRange);

    // Check that A2:A3 and A5:A8 are formula groups.
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,4,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());

    {   // Check results of A4 deleted.
        ScAddress aPos(0,0,0);
        const double fResult[] = { 1.0, 1.0, 2.0, 0.0, 4.0, 8.0, 16.0, 32.0 };
        for (SCROW nRow = 1; nRow < 8; ++nRow)
        {
            aPos.SetRow(nRow);
            CPPUNIT_ASSERT_EQUAL( fResult[nRow], m_pDoc->GetValue( aPos));
        }
    }

    // Restore A4.
    aUndo.Undo();

    // Check that A2:A8 is a formula group.
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(7), pFC->GetSharedLength());

    {   // Check initial results.
        ScAddress aPos(0,0,0);
        const double fResult[] = { 1.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0 };
        for (SCROW nRow = 1; nRow < 8; ++nRow)
        {
            aPos.SetRow(nRow);
            CPPUNIT_ASSERT_EQUAL( fResult[nRow], m_pDoc->GetValue( aPos));
        }
    }

    // Delete A4 using selection.
    m_pDoc->DeleteSelection(InsertDeleteFlags::ALL, aMark);

    // Check that A2:A3 and A5:A8 are formula groups.
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,4,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());

    {   // Check results of A4 deleted.
        ScAddress aPos(0,0,0);
        const double fResult[] = { 1.0, 1.0, 2.0, 0.0, 4.0, 8.0, 16.0, 32.0 };
        for (SCROW nRow = 1; nRow < 8; ++nRow)
        {
            aPos.SetRow(nRow);
            CPPUNIT_ASSERT_EQUAL( fResult[nRow], m_pDoc->GetValue( aPos));
        }
    }

    // Restore A4.
    aUndo.Undo();

    // Check that A2:A8 is a formula group.
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(7), pFC->GetSharedLength());

    {   // Check initial results.
        ScAddress aPos(0,0,0);
        const double fResult[] = { 1.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0 };
        for (SCROW nRow = 1; nRow < 8; ++nRow)
        {
            aPos.SetRow(nRow);
            CPPUNIT_ASSERT_EQUAL( fResult[nRow], m_pDoc->GetValue( aPos));
        }
    }

    // Replace A4 with 0.
    m_pDoc->SetString( ScAddress(0,3,0), u"0"_ustr);

    // Check that A2:A3 and A5:A8 are formula groups.
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,1,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(1), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());
    pFC = m_pDoc->GetFormulaCell(ScAddress(0,4,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(4), pFC->GetSharedLength());

    {   // Check results of A4 set to zero.
        ScAddress aPos(0,0,0);
        const double fResult[] = { 1.0, 1.0, 2.0, 0.0, 4.0, 8.0, 16.0, 32.0 };
        for (SCROW nRow = 1; nRow < 8; ++nRow)
        {
            aPos.SetRow(nRow);
            CPPUNIT_ASSERT_EQUAL( fResult[nRow], m_pDoc->GetValue( aPos));
        }
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaDeleteTopCell)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    const std::vector<std::vector<const char*>> aData = {
        { "=SUM(B$1:B$2)""1" },
        { "=SUM(B$1:B$2)""2" }
    };

    insertRangeData( m_pDoc, ScAddress(0,0,0), aData);

    // Check that A1:A2 is a formula group.
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell( ScAddress(0,0,0));
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(0), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCROW>(2), pFC->GetSharedLength());

    // Check results A1:A2.
    CPPUNIT_ASSERT_EQUAL( 3.0, m_pDoc->GetValue( ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL( 3.0, m_pDoc->GetValue( ScAddress(0,1,0)));

    // Delete cell A1.
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);
    m_xDocShell->GetDocFunc().DeleteCell( ScAddress(0,0,0), aMark, InsertDeleteFlags::CONTENTS, false/*bApi=*/ true);
    // Check it's gone.
    CPPUNIT_ASSERT(!m_pDoc->GetFormulaCell( ScAddress(0,0,0)));

    // Check result A2.
    CPPUNIT_ASSERT_EQUAL( 3.0, m_pDoc->GetValue( ScAddress(0,1,0)));

    // Replace B1 with 4.
    m_pDoc->SetString( ScAddress(1,0,0), u"4"_ustr);

    // Check result A2.
    CPPUNIT_ASSERT_EQUAL( 6.0, m_pDoc->GetValue( ScAddress(0,1,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaCutCopyMoveIntoRef)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    // tdf#123714 case 1
    {
        m_pDoc->InsertTab(0, u"Test"_ustr);

        // Data in A1:C3
        std::vector<std::vector<const char*>> aData = {
            { "=B1""""1" },
            { "=B2""""1" },
            { "=B3"""""  }
        };
        const ScAddress aOrgPos(0,0,0);
        insertRangeData( m_pDoc, aOrgPos, aData);

        ScMarkData aMark(m_pDoc->GetSheetLimits());
        aMark.SelectOneTable(0);

        // Set up clip document.
        ScDocument aClipDoc(SCDOCMODE_CLIP);
        aClipDoc.ResetClip(m_pDoc, &aMark);
        // Cut C1:C2 to clipboard.
        cutToClip(*m_xDocShell, ScRange(2,0,0, 2,1,0), &aClipDoc, false);

        // Paste to B1:B2
        ScRange aPasteRange(1,0,0, 1,1,0);
        aMark.SetMarkArea(aPasteRange);
        m_pDoc->CopyFromClip( aPasteRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc);

        // Check data in A1:A2 after Paste.
        ScAddress aPos(aOrgPos);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("A1", 1.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        CPPUNIT_ASSERT_EQUAL_MESSAGE("A2", 1.0, m_pDoc->GetValue(aPos));

        m_pDoc->DeleteTab(0);
    }

    // tdf#123714 case 2
    {
        m_pDoc->InsertTab(0, u"Test"_ustr);

        // Data in A1:C3
        std::vector<std::vector<const char*>> aData = {
            {  "1",   "2""=SUM(A1:B1)" },
            {  "4",   "8""=SUM(A2:B2)" },
            { "16",  "32""=SUM(A3:B3)" },
            { "64""128""=SUM(A4:B4)" },
        };
        const ScAddress aOrgPos(0,0,0);
        insertRangeData( m_pDoc, aOrgPos, aData);

        ScAddress aPos;
        // Check results in C1:C4
        const double fVec0[] = { 3.0, 12.0, 48.0, 192.0 };
        aPos = ScAddress(2,0,0);
        for (SCROW i=0; i < 4; ++i)
        {
            CPPUNIT_ASSERT_EQUAL( fVec0[i], m_pDoc->GetValue(aPos));
            aPos.IncRow();
        }

        ScMarkData aMark(m_pDoc->GetSheetLimits());
        aMark.SelectOneTable(0);

        // Set up clip document.
        ScDocument aClipDoc(SCDOCMODE_CLIP);
        aClipDoc.ResetClip(m_pDoc, &aMark);
        // Cut B1:B2 to clipboard.
        cutToClip(*m_xDocShell, ScRange(1,0,0, 1,1,0), &aClipDoc, false);

        // Check results in C1:C4 after Cut.
        const double fVec1[] = { 1.0, 4.0, 48.0, 192.0 };
        aPos = ScAddress(2,0,0);
        for (SCROW i=0; i < 4; ++i)
        {
            CPPUNIT_ASSERT_EQUAL( fVec1[i], m_pDoc->GetValue(aPos));
            aPos.IncRow();
        }

        // Paste to B3:B4
        ScRange aPasteRange(1,2,0, 1,3,0);
        aMark.SetMarkArea(aPasteRange);
        m_pDoc->CopyFromClip( aPasteRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc);

        // Check results in C1:C4 after Paste.
        const double fVec2[] = { 1.0, 4.0, 18.0, 72.0 };
        aPos = ScAddress(2,0,0);
        for (SCROW i=0; i < 4; ++i)
        {
            CPPUNIT_ASSERT_EQUAL( fVec2[i], m_pDoc->GetValue(aPos));
            aPos.IncRow();
        }

        // Paste to B1:B2
        aPasteRange = ScRange(1,0,0, 1,1,0);
        aMark.SetMarkArea(aPasteRange);
        m_pDoc->CopyFromClip( aPasteRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc);

        // Check results in C1:C4 after Paste.
        const double fVec3[] = { 3.0, 12.0, 18.0, 72.0 };
        aPos = ScAddress(2,0,0);
        for (SCROW i=0; i < 4; ++i)
        {
            CPPUNIT_ASSERT_EQUAL( fVec3[i], m_pDoc->GetValue(aPos));
            aPos.IncRow();
        }

        m_pDoc->DeleteTab(0);
    }
}

// tdf#121002
CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaCutCopyMoveWithRef)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Data in A1:C4
    std::vector<std::vector<const char*>> aData = {
        {  "",  """=SUM(A1:B1)" },
        {  "",  """=SUM(A2:B2)" },
        { "1""2""=SUM(A3:B3)" },
        { "4""8""=SUM(A4:B4)" }
    };
    const ScAddress aOrgPos(0,0,0);
    insertRangeData( m_pDoc, aOrgPos, aData);

    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);

    ScAddress aPos( ScAddress::UNINITIALIZED);

    // Check results in C1:C4
    const double fVec0[] = { 0.0, 0.0, 3.0, 12.0 };
    aPos = ScAddress(2,0,0);
    for (SCROW i=0; i < 4; ++i)
    {
        CPPUNIT_ASSERT_EQUAL( fVec0[i], m_pDoc->GetValue(aPos));
        aPos.IncRow();
    }

    // Set up clip document.
    ScDocument aClipDoc(SCDOCMODE_CLIP);
    aClipDoc.ResetClip(m_pDoc, &aMark);
    // Cut A3:B3 to clipboard.
    cutToClip(*m_xDocShell, ScRange(0,2,0, 1,2,0), &aClipDoc, false);

    // Check results in C1:C4 after Cut.
    const double fVec1[] = { 0.0, 0.0, 0.0, 12.0 };
    aPos = ScAddress(2,0,0);
    for (SCROW i=0; i < 4; ++i)
    {
        CPPUNIT_ASSERT_EQUAL( fVec1[i], m_pDoc->GetValue(aPos));
        aPos.IncRow();
    }

    // Paste to A1:B1
    ScRange aPasteRange(0,0,0, 1,0,0);
    aMark.SetMarkArea(aPasteRange);
    m_pDoc->CopyFromClip( aPasteRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc);

    // Check results in C1:C4 after Paste.
    const double fVec2[] = { 3.0, 0.0, 3.0, 12.0 };
    aPos = ScAddress(2,0,0);
    for (SCROW i=0; i < 4; ++i)
    {
        CPPUNIT_ASSERT_EQUAL( fVec2[i], m_pDoc->GetValue(aPos));
        aPos.IncRow();
    }

    // Check formulas in C1:C4 after Paste.
    const std::u16string_view sForm[] = { u"=SUM(A1:B1)", u"=SUM(A2:B2)", u"=SUM(A1:B1)", u"=SUM(A4:B4)" };
    for (SCROW i=0; i < 4; ++i)
    {
        OUString aFormula = m_pDoc->GetFormula( 2,i,0 );
        CPPUNIT_ASSERT_EQUAL( OUString(sForm[i]), aFormula);
    }

    m_pDoc->DeleteTab(0);
}

// tdf#120013
CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaCutCopyMoveWithinRun)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Data in C3:E9
    const std::vector<std::vector<const char*>> aData = {
        { "2200",     """=SUM(C$3:C3)-SUM(D$3:D3)" },
        {     "",     """=SUM(C$3:C4)-SUM(D$3:D4)" },
        {     """1900""=SUM(C$3:C5)-SUM(D$3:D5)" },
        {     "",     """=SUM(C$3:C6)-SUM(D$3:D6)" },
        { "1600",     """=SUM(C$3:C7)-SUM(D$3:D7)" },
        {     """1000""=SUM(C$3:C8)-SUM(D$3:D8)" },
        {     "",     """=SUM(C$3:C9)-SUM(D$3:D9)" }
    };
    const ScAddress aOrgPos(2,2,0);
    insertRangeData( m_pDoc, aOrgPos, aData);

    // Check that E3:E9 is a formula group.
    const ScAddress aFormulaPos(4,2,0);
    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell( aFormulaPos);
    CPPUNIT_ASSERT(pFC);
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Shared formula top row.", aFormulaPos.Row(), pFC->GetSharedTopRow());
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Shared formula length."static_cast<SCROW>(7), pFC->GetSharedLength());

    ScAddress aPos( ScAddress::UNINITIALIZED);

    // Check results in E3:E9
    const double fVec0[] = { 2200.0, 2200.0, 300.0, 300.0, 1900.0, 900.0, 900.0 };
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Number of checks mismatch.", SAL_N_ELEMENTS(fVec0), aData.size());
    aPos = aFormulaPos;
    for (size_t i=0; i < SAL_N_ELEMENTS(fVec0); ++i)
    {
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "E3:E9", fVec0[i], m_pDoc->GetValue(aPos));
        aPos.IncRow();
    }

    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.SelectOneTable(0);

    // Set up clip document.
    ScDocument aClipDoc(SCDOCMODE_CLIP);
    aClipDoc.ResetClip(m_pDoc, &aMark);
    // Cut A8:D8 to clipboard.
    cutToClip(*m_xDocShell, ScRange(0,7,0, 3,7,0), &aClipDoc, false);

    // Check results in E3:E9 after Cut.
    const double fVec1[] = { 2200.0, 2200.0, 300.0, 300.0, 1900.0, 1900.0, 1900.0 };
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Number of checks mismatch.", SAL_N_ELEMENTS(fVec1), aData.size());
    aPos = aFormulaPos;
    for (size_t i=0; i < SAL_N_ELEMENTS(fVec1); ++i)
    {
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "E3:E9 after Cut.", fVec1[i], m_pDoc->GetValue(aPos));
        aPos.IncRow();
    }

    // Paste to A4:D4
    ScRange aPasteRange(0,3,0, 3,3,0);
    aMark.SetMarkArea(aPasteRange);
    m_pDoc->CopyFromClip( aPasteRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &aClipDoc);

    // Check results in E3:E9 after Paste.
    const double fVec2[] = { 2200.0, 1200.0, -700.0, -700.0, 900.0, 900.0, 900.0 };
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Number of checks mismatch.", SAL_N_ELEMENTS(fVec2), aData.size());
    aPos = aFormulaPos;
    for (size_t i=0; i < SAL_N_ELEMENTS(fVec2); ++i)
    {
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "E3:E9 after Paste.", fVec2[i], m_pDoc->GetValue(aPos));
        aPos.IncRow();
    }

    m_pDoc->DeleteTab(0);
}

// tdf#129396
CPPUNIT_TEST_FIXTURE(TestSharedFormula, testSharedFormulaInsertShift)
{
    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc.

    m_pDoc->InsertTab(0, u"Test"_ustr);

    // Data in A1:C2
    const std::vector<std::vector<const char*>> aData = {
        { "1""2""=SUM(A1:B1)" },
        { "4""8""=SUM(A2:B2)" }
    };
    const ScAddress aOrgPos(0,0,0);
    insertRangeData( m_pDoc, aOrgPos, aData);

    {
        // Check that C1:C2 is a formula group.
        const ScAddress aFormulaPos(2,0,0);
        const ScFormulaCell* pFC = m_pDoc->GetFormulaCell( aFormulaPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Shared formula top row.", aFormulaPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Shared formula length."static_cast<SCROW>(2), pFC->GetSharedLength());
    }

    {
        // Check results in C1:C2
        ScAddress aPos( aOrgPos);
        aPos.SetCol(2);
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "C1", 3.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "C2", 12.0, m_pDoc->GetValue(aPos));
    }

    const bool bSuccess = m_pDoc->InsertCol( 0, 0, 1, 0, 2, 1);
    CPPUNIT_ASSERT_MESSAGE( "InsertCol", bSuccess);

    {
        // Check that D1:D2 is a formula group.
        const ScAddress aFormulaPos(3,0,0);
        const ScFormulaCell* pFC = m_pDoc->GetFormulaCell( aFormulaPos);
        CPPUNIT_ASSERT(pFC);
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Shared formula top row.", aFormulaPos.Row(), pFC->GetSharedTopRow());
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Shared formula length."static_cast<SCROW>(2), pFC->GetSharedLength());
    }

    {
        // Modify values in B1:B2
        ScAddress aPos( aOrgPos);
        aPos.SetCol(1);
        m_pDoc->SetValue(aPos, 16.0);
        aPos.IncRow();
        m_pDoc->SetValue(aPos, 32.0);
    }

    {
        // Check results in D1:D2
        ScAddress aPos( aOrgPos);
        aPos.SetCol(3);
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "D1", 17.0, m_pDoc->GetValue(aPos));
        aPos.IncRow();
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "D2", 36.0, m_pDoc->GetValue(aPos));
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5 in Prozent
C=86 H=84 G=84

¤ Dauer der Verarbeitung: 0.47 Sekunden  (vorverarbeitet am  2026-05-04) ¤

*© 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.