Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ucalc.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 <svl/asiancfg.hxx>

#include <simpleformulacalc.hxx>
#include <stringutil.hxx>
#include <scmatrix.hxx>
#include <scitems.hxx>
#include <reffind.hxx>
#include <clipparam.hxx>
#include <undoblk.hxx>
#include <undotab.hxx>
#include <attrib.hxx>
#include <dbdata.hxx>
#include <reftokenhelper.hxx>
#include <userdat.hxx>
#include <refdata.hxx>

#include <docfunc.hxx>
#include <funcdesc.hxx>
#include <globstr.hrc>
#include <scresid.hxx>

#include <columniterator.hxx>
#include <scopetools.hxx>
#include <dociter.hxx>
#include <edittextiterator.hxx>
#include <editutil.hxx>
#include <asciiopt.hxx>
#include <impex.hxx>
#include <globalnames.hxx>
#include <columnspanset.hxx>

#include <editable.hxx>
#include <tabprotection.hxx>
#include <undomanager.hxx>

#include <formula/IFunctionDescription.hxx>

#include <editeng/borderline.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/lineitem.hxx>

#include <o3tl/nonstaticstring.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdocirc.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdocapt.hxx>
#include <svl/numformat.hxx>
#include <svl/srchitem.hxx>
#include <svl/sharedstringpool.hxx>
#include <unotools/collatorwrapper.hxx>
#include <sfx2/IDocumentModelAccessor.hxx>

#include <sfx2/sfxsids.hrc>

class ScUndoPaste;
class ScUndoCut;
using ::std::cerr;
using ::std::endl;

namespace {

struct HoriIterCheck
{
    SCCOL nCol;
    SCROW nRow;
    const char* pVal;
};

}

class Test : public ScUcalcTestBase
{
protected:
    void checkPrecisionAsShown(OUString& rCode, double fValue, double fExpectedRoundVal);

    /** Get a separate new ScDocShell with ScDocument that suits unit test needs. */
    void getNewDocShell(ScDocShellRef& rDocShellRef);

    bool checkHorizontalIterator(ScDocument& rDoc, const std::vector<std::vector<const char*>>& rData,
            const HoriIterCheck* pChecks, size_t nCheckCount);
};

void Test::getNewDocShell( ScDocShellRef& rDocShellRef )
{
    rDocShellRef = new ScDocShell(
        SfxModelFlags::EMBEDDED_OBJECT |
        SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
        SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);

    rDocShellRef->DoInitUnitTest();
}

CPPUNIT_TEST_FIXTURE(Test, testCollator)
{
    sal_Int32 nRes = ScGlobal::GetCollator().compareString(u"A"_ustr, u"B"_ustr);
    CPPUNIT_ASSERT_MESSAGE("these strings are supposed to be different!", nRes != 0);
}

CPPUNIT_TEST_FIXTURE(Test, testUndoBackgroundColorInsertRow)
{
    m_pDoc->InsertTab(0, u"Table1"_ustr);

    ScMarkData aMark(m_pDoc->GetSheetLimits());

    // Set Values to B1, C2, D5
    m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
    m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
    m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5

    // Add patterns
    ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
    aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
    m_pDoc->ApplyPatternAreaTab(0, 3, m_pDoc->MaxCol(), 3, 0, aCellBlueColor);

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

    // Check patterns
    const SfxPoolItem* pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(1000, 4, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    // Undo the new row
    m_pDoc->GetUndoManager()->Undo();

    // Check patterns
    // Failed if row 3 is not blue all the way through
    pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(1000, 3, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testUndoBackgroundColorInsertColumn)
{
    m_pDoc->InsertTab(0, u"Table1"_ustr);

    ScMarkData aMark(m_pDoc->GetSheetLimits());

    // Set Values to B1, C2, D5
    m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
    m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
    m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5

    // Add patterns
    ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
    aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
    m_pDoc->ApplyPatternAreaTab(3, 0, 3, m_pDoc->MaxRow(), 0, aCellBlueColor);

    // Insert a new column at column 3
    ScRange aColumnOne(2, 0, 0, 2, m_pDoc->MaxRow(), 0);
    aMark.SetMarkArea(aColumnOne);
    ScDocFunc& rFunc = m_xDocShell->GetDocFunc();
    rFunc.InsertCells(aColumnOne, &aMark, INS_INSCOLS_BEFORE, truetrue);

    // Check patterns
    const SfxPoolItem* pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(4, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    // Undo the new column
    m_pDoc->GetUndoManager()->Undo();

    // Check patterns
    // Failed if column 3 is not blue all the way through
    pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(3, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testMergedHyperlink)
{
    m_pDoc->InsertTab(0, u"Table1"_ustr);
    m_pDoc->InitDrawLayer(m_xDocShell.get());

    ScFieldEditEngine& pEE = m_pDoc->GetEditEngine();
    pEE.SetTextCurrentDefaults(u"https://libreoffice.org/"_ustr);
    m_pDoc->SetEditText(ScAddress(1, 0, 0), pEE.CreateTextObject()); // B1

    m_pDoc->DoMergeContents(0, 0, 1, 0, 0); // A1:B1

    CPPUNIT_ASSERT_EQUAL(CELLTYPE_EDIT, m_pDoc->GetCellType(ScAddress(0, 0, 0))); // A1
    const EditTextObject* pEditObj = m_pDoc->GetEditText(ScAddress(0, 0, 0)); // A1
    CPPUNIT_ASSERT(pEditObj);
    CPPUNIT_ASSERT_EQUAL(u"https://libreoffice.org/"_ustr, pEditObj->GetText(0));
}

CPPUNIT_TEST_FIXTURE(Test, testSharedStringPool)
{
    m_pDoc->InsertTab(0, u"foo"_ustr);

    svl::SharedStringPool& rPool = m_pDoc->GetSharedStringPool();
    size_t extraCount = rPool.getCount(); // internal items such as SharedString::getEmptyString()
    size_t extraCountIgnoreCase = rPool.getCountIgnoreCase();

    // Strings that are identical.
    m_pDoc->SetString(ScAddress(0,0,0), o3tl::nonStaticString(u"Andy"));  // A1
    m_pDoc->SetString(ScAddress(0,1,0), o3tl::nonStaticString(u"Andy"));  // A2
    m_pDoc->SetString(ScAddress(0,2,0), o3tl::nonStaticString(u"Bruce")); // A3
    m_pDoc->SetString(ScAddress(0,3,0), o3tl::nonStaticString(u"andy"));  // A4
    m_pDoc->SetString(ScAddress(0,4,0), o3tl::nonStaticString(u"BRUCE")); // A5

    {
        // These two shared string objects must go out of scope before the purge test.
        svl::SharedString aSS1 = m_pDoc->GetSharedString(ScAddress(0,0,0));
        svl::SharedString aSS2 = m_pDoc->GetSharedString(ScAddress(0,1,0));
        CPPUNIT_ASSERT_MESSAGE("Failed to get a valid shared string.", aSS1.isValid());
        CPPUNIT_ASSERT_MESSAGE("Failed to get a valid shared string.", aSS2.isValid());
        CPPUNIT_ASSERT_EQUAL(aSS1.getData(), aSS2.getData());

        aSS2 = m_pDoc->GetSharedString(ScAddress(0,2,0));
        CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());

        aSS2 = m_pDoc->GetSharedString(ScAddress(0,3,0));
        CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());

        aSS2 = m_pDoc->GetSharedString(ScAddress(0,4,0));
        CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());

        // A3 and A5 should differ but should be equal case-insensitively.
        aSS1 = m_pDoc->GetSharedString(ScAddress(0,2,0));
        aSS2 = m_pDoc->GetSharedString(ScAddress(0,4,0));
        CPPUNIT_ASSERT_MESSAGE("They must differ", aSS1.getData() != aSS2.getData());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("They must be equal when cases are ignored.", aSS1.getDataIgnoreCase(), aSS2.getDataIgnoreCase());

        // A2 and A4 should be equal when ignoring cases.
        aSS1 = m_pDoc->GetSharedString(ScAddress(0,1,0));
        aSS2 = m_pDoc->GetSharedString(ScAddress(0,3,0));
        CPPUNIT_ASSERT_EQUAL_MESSAGE("They must be equal when cases are ignored.", aSS1.getDataIgnoreCase(), aSS2.getDataIgnoreCase());
    }

    // Check the string counts after purging. Purging shouldn't remove any strings in this case.
    rPool.purge();
    CPPUNIT_ASSERT_EQUAL(5+extraCount, rPool.getCount());
    CPPUNIT_ASSERT_EQUAL(2+extraCountIgnoreCase, rPool.getCountIgnoreCase());

    // Clear A1
    clearRange(m_pDoc, ScRange(ScAddress(0,0,0)));
    // Clear A2
    clearRange(m_pDoc, ScRange(ScAddress(0,1,0)));
    // Clear A3
    clearRange(m_pDoc, ScRange(ScAddress(0,2,0)));
    // Clear A4
    clearRange(m_pDoc, ScRange(ScAddress(0,3,0)));
    // Clear A5 and the pool should be completely empty.
    clearRange(m_pDoc, ScRange(ScAddress(0,4,0)));
    rPool.purge();
    CPPUNIT_ASSERT_EQUAL(extraCount, rPool.getCount());
    CPPUNIT_ASSERT_EQUAL(extraCountIgnoreCase, rPool.getCountIgnoreCase());

    // Now, compare string and edit text cells.
    m_pDoc->SetString(ScAddress(0,0,0), "Andy and Bruce"); // A1 // [-loplugin:ostr]
    ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
    rEE.SetTextCurrentDefaults(u"Andy and Bruce"_ustr);

    {
        // Set 'Andy' bold.
        SfxItemSet aItemSet = rEE.GetEmptyItemSet();
        SvxWeightItem aWeight(WEIGHT_BOLD, EE_CHAR_WEIGHT);
        aItemSet.Put(aWeight);
        rEE.QuickSetAttribs(aItemSet, ESelection(0, 0, 0, 4));
    }

    {
        // Set 'Bruce' italic.
        SfxItemSet aItemSet = rEE.GetEmptyItemSet();
        SvxPostureItem aItalic(ITALIC_NORMAL, EE_CHAR_ITALIC);
        aItemSet.Put(aItalic);
        rEE.QuickSetAttribs(aItemSet, ESelection(0, 9, 0, 14));
    }

    m_pDoc->SetEditText(ScAddress(1,0,0), rEE.CreateTextObject()); // B1

    // These two should be equal.
    svl::SharedString aSS1 = m_pDoc->GetSharedString(ScAddress(0,0,0));
    svl::SharedString aSS2 = m_pDoc->GetSharedString(ScAddress(1,0,0));
    CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS1.isValid());
    CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS2.isValid());
    CPPUNIT_ASSERT_EQUAL(aSS1.getData(), aSS2.getData());

    rEE.SetTextCurrentDefaults(u"ANDY and BRUCE"_ustr);
    m_pDoc->SetEditText(ScAddress(2,0,0), rEE.CreateTextObject()); // C1
    aSS2 = m_pDoc->GetSharedString(ScAddress(2,0,0));
    CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS2.isValid());
    CPPUNIT_ASSERT_MESSAGE("These two should be different when cases are considered.", aSS1.getData() != aSS2.getData());

    // But they should be considered equal when cases are ignored.
    aSS1 = m_pDoc->GetSharedString(ScAddress(0,0,0));
    aSS2 = m_pDoc->GetSharedString(ScAddress(2,0,0));
    CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS1.isValid());
    CPPUNIT_ASSERT_MESSAGE("Failed to get a valid string ID.", aSS2.isValid());
    CPPUNIT_ASSERT_EQUAL(aSS1.getDataIgnoreCase(), aSS2.getDataIgnoreCase());

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testBackgroundColorDeleteColumn)
{
    m_pDoc->InsertTab(0, u"Table1"_ustr);

    ScMarkData aMark(m_pDoc->GetSheetLimits());

    // Set Values to B1, C2, D5
    m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
    m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
    m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5

    // Add patterns
    ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
    aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
    m_pDoc->ApplyPatternAreaTab(3, 0, 3, m_pDoc->MaxRow(), 0, aCellBlueColor);

    // Delete column 10
    m_pDoc->DeleteCol(ScRange(9,0,0,9,m_pDoc->MaxRow(),0));

    // Check patterns
    const SfxPoolItem* pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(3, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    // Delete column 2
    m_pDoc->DeleteCol(ScRange(1,0,0,1,m_pDoc->MaxRow(),0));

    // Check patterns
    pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(2, 1000, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testBackgroundColorDeleteRow)
{
    m_pDoc->InsertTab(0, u"Table1"_ustr);

    ScMarkData aMark(m_pDoc->GetSheetLimits());

    // Set Values to B1, C2, D5
    m_pDoc->SetValue(ScAddress(1, 0, 0), 1.0); // B1
    m_pDoc->SetValue(ScAddress(2, 1, 0), 2.0); // C2
    m_pDoc->SetValue(ScAddress(3, 4, 0), 3.0); // D5

    // Add patterns
    ScPatternAttr aCellBlueColor(m_pDoc->getCellAttributeHelper());
    aCellBlueColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
    m_pDoc->ApplyPatternAreaTab(0, 3, m_pDoc->MaxCol(), 3, 0, aCellBlueColor);

    // Delete row 10
    m_pDoc->DeleteRow(ScRange(0,9,0,m_pDoc->MaxCol(),9,0));

    // Check patterns
    const SfxPoolItem* pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(1000, 3, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

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

    // Check patterns
    pItem = nullptr;
    m_pDoc->GetPattern(ScAddress(1000, 2, 0))->GetItemSet().HasItem(ATTR_BACKGROUND, &pItem);
    CPPUNIT_ASSERT(pItem);
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<const SvxBrushItem*>(pItem)->GetColor());

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testSharedStringPoolUndoDoc)
{
    struct
    {
        bool check( const ScDocument& rSrcDoc, ScDocument& rCopyDoc )
        {
            // Copy A1:A4 to the undo document.
            for (SCROW i = 0; i <= 4; ++i)
            {
                ScAddress aPos(0,i,0);
                rCopyDoc.SetString(aPos, rSrcDoc.GetString(aPos));
            }

            // String values in A1:A4 should have identical hash.
            for (SCROW i = 0; i <= 4; ++i)
            {
                ScAddress aPos(0,i,0);
                svl::SharedString aSS1 = rSrcDoc.GetSharedString(aPos);
                svl::SharedString aSS2 = rCopyDoc.GetSharedString(aPos);
                if (aSS1.getDataIgnoreCase() != aSS2.getDataIgnoreCase())
                {
                    cerr << "String hash values are not equal at row " << (i+1)
                        << " for string '" << aSS1.getString() << "'" << endl;
                    return false;
                }
            }

            return true;
        }

    } aTest;

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

    m_pDoc->SetString(ScAddress(0,0,0), u"Header"_ustr);
    m_pDoc->SetString(ScAddress(0,1,0), u"A1"_ustr);
    m_pDoc->SetString(ScAddress(0,2,0), u"A2"_ustr);
    m_pDoc->SetString(ScAddress(0,3,0), u"A3"_ustr);

    ScDocument aUndoDoc(SCDOCMODE_UNDO);
    aUndoDoc.InitUndo(*m_pDoc, 0, 0);

    bool bSuccess = aTest.check(*m_pDoc, aUndoDoc);
    CPPUNIT_ASSERT_MESSAGE("Check failed with undo document.", bSuccess);

    // Test the clip document as well.
    ScDocument aClipDoc(SCDOCMODE_CLIP);
    aClipDoc.ResetClip(m_pDoc, static_cast<SCTAB>(0));

    bSuccess = aTest.check(*m_pDoc, aClipDoc);
    CPPUNIT_ASSERT_MESSAGE("Check failed with clip document.", bSuccess);

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testRangeList)
{
    m_pDoc->InsertTab(0, u"foo"_ustr);

    ScRangeList aRL;
    aRL.push_back(ScRange(1,1,0,3,10,0));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("List should have one range.", size_t(1), aRL.size());
    const ScRange* p = &aRL[0];
    CPPUNIT_ASSERT_MESSAGE("Failed to get the range object.", p);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong range.", ScAddress(1,1,0), p->aStart);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong range.", ScAddress(3,10,0), p->aEnd);

    // TODO: Add more tests here.

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testMarkData)
{
    ScMarkData aMarkData(m_pDoc->GetSheetLimits());

    // Empty mark. Nothing is selected.
    std::vector<sc::ColRowSpan> aSpans = aMarkData.GetMarkedRowSpans();
    CPPUNIT_ASSERT_MESSAGE("Span should be empty.", aSpans.empty());
    aSpans = aMarkData.GetMarkedColSpans();
    CPPUNIT_ASSERT_MESSAGE("Span should be empty.", aSpans.empty());

    // Select B3:F7.
    aMarkData.SetMarkArea(ScRange(1,2,0,5,6,0));
    aSpans = aMarkData.GetMarkedRowSpans();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected row span.", size_t(1), aSpans.size());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(2), aSpans[0].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(6), aSpans[0].mnEnd);

    aSpans = aMarkData.GetMarkedColSpans();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected column span.", size_t(1), aSpans.size());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(1), aSpans[0].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(5), aSpans[0].mnEnd);

    // Select A11:B13.
    aMarkData.SetMultiMarkArea(ScRange(0,10,0,1,12,0));
    aSpans = aMarkData.GetMarkedRowSpans();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be 2 selected row spans.", size_t(2), aSpans.size());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(2), aSpans[0].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(6), aSpans[0].mnEnd);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(10), aSpans[1].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(12), aSpans[1].mnEnd);

    aSpans = aMarkData.GetMarkedColSpans();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected column span.", size_t(1), aSpans.size());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(0), aSpans[0].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(5), aSpans[0].mnEnd);

    // Select C8:C10.
    aMarkData.SetMultiMarkArea(ScRange(2,7,0,2,9,0));
    aSpans = aMarkData.GetMarkedRowSpans();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected row span.", size_t(1), aSpans.size());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(2), aSpans[0].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(12), aSpans[0].mnEnd);

    aSpans = aMarkData.GetMarkedColSpans();
    CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be one selected column span.", size_t(1), aSpans.size());
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(0), aSpans[0].mnStart);
    CPPUNIT_ASSERT_EQUAL(static_cast<SCCOLROW>(5), aSpans[0].mnEnd);
}

CPPUNIT_TEST_FIXTURE(Test, testInput)
{

    CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
                            m_pDoc->InsertTab (0, u"foo"_ustr));
    OUString test;

    m_pDoc->SetString(0, 0, 0, u"'10.5"_ustr);
    test = m_pDoc->GetString(0, 0, 0);
    bool bTest = test == "10.5";
    CPPUNIT_ASSERT_MESSAGE("String number should have the first apostrophe stripped.", bTest);
    m_pDoc->SetString(0, 0, 0, u"'apple'"_ustr);
    test = m_pDoc->GetString(0, 0, 0);
    bTest = test == "apple'";
    CPPUNIT_ASSERT_MESSAGE("Text content should have the first apostrophe stripped.", bTest);

    // Customized string handling policy.
    ScSetStringParam aParam;
    aParam.setTextInput();
    m_pDoc->SetString(0, 0, 0, u"000123"_ustr, &aParam);
    test = m_pDoc->GetString(0, 0, 0);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Text content should have been treated as string, not number.", u"000123"_ustr, test);

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testColumnIterator) // tdf#118620
{
    CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet",
                            m_pDoc->InsertTab (0, u"foo"_ustr));

    m_pDoc->SetString(0, 0, 0, u"'10.5"_ustr);
    m_pDoc->SetString(0, m_pDoc->MaxRow()-5, 0, u"42.0"_ustr);
    std::optional<sc::ColumnIterator> it = m_pDoc->GetColumnIterator(0, 0, m_pDoc->MaxRow() - 10, m_pDoc->MaxRow());
    while (it->hasCell())
    {
        it->getCell();
        it->next();
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testTdf66613)
{
    // Create different print ranges and col/row repetitions for two tabs
    const SCTAB nFirstTab = 0;
    CPPUNIT_ASSERT(m_pDoc->InsertTab(nFirstTab, u"FirstPrintRange"_ustr));
    ScRange aFirstPrintRange(0, 0, nFirstTab, 2, 2, nFirstTab);
    m_pDoc->AddPrintRange(nFirstTab, aFirstPrintRange);
    ScRange aFirstRepeatColRange(0, 0, nFirstTab, 0, 0, nFirstTab);
    m_pDoc->SetRepeatColRange(nFirstTab, aFirstRepeatColRange);
    ScRange aFirstRepeatRowRange(1, 1, nFirstTab, 1, 1, nFirstTab);
    m_pDoc->SetRepeatRowRange(nFirstTab, aFirstRepeatRowRange);

    const SCTAB nSecondTab = 1;
    CPPUNIT_ASSERT(m_pDoc->InsertTab(nSecondTab, u"SecondPrintRange"_ustr));
    ScRange aSecondPrintRange(0, 0, nSecondTab, 3, 3, nSecondTab);
    m_pDoc->AddPrintRange(nSecondTab, aSecondPrintRange);
    ScRange aSecondRepeatColRange(1, 1, nSecondTab, 1, 1, nSecondTab);
    m_pDoc->SetRepeatColRange(nSecondTab, aSecondRepeatColRange);
    ScRange aSecondRepeatRowRange(2, 2, nSecondTab, 2, 2, nSecondTab);
    m_pDoc->SetRepeatRowRange(nSecondTab, aSecondRepeatRowRange);

    // Transfer generated tabs to a new document with different order
    ScDocument aScDocument;
    aScDocument.TransferTab(*m_pDoc, nSecondTab, nFirstTab);
    aScDocument.TransferTab(*m_pDoc, nFirstTab, nSecondTab);

    // Check the number of print ranges in both documents
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), m_pDoc->GetPrintRangeCount(nFirstTab));
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), m_pDoc->GetPrintRangeCount(nSecondTab));
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aScDocument.GetPrintRangeCount(nFirstTab));
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aScDocument.GetPrintRangeCount(nSecondTab));

    // Check the print ranges and col/row repetitions in both documents
    CPPUNIT_ASSERT_EQUAL(aFirstPrintRange, *m_pDoc->GetPrintRange(nFirstTab, 0));
    CPPUNIT_ASSERT_EQUAL(aFirstRepeatColRange, *m_pDoc->GetRepeatColRange(nFirstTab));
    CPPUNIT_ASSERT_EQUAL(aFirstRepeatRowRange, *m_pDoc->GetRepeatRowRange(nFirstTab));
    CPPUNIT_ASSERT_EQUAL(aSecondPrintRange, *m_pDoc->GetPrintRange(nSecondTab, 0));
    CPPUNIT_ASSERT_EQUAL(aSecondRepeatColRange, *m_pDoc->GetRepeatColRange(nSecondTab));
    CPPUNIT_ASSERT_EQUAL(aSecondRepeatRowRange, *m_pDoc->GetRepeatRowRange(nSecondTab));

    // Tabs have to be adjusted since the order of the tabs is inverted in the new document
    std::vector<ScRange*> aScRanges
        = { &aFirstPrintRange,  &aFirstRepeatColRange,  &aFirstRepeatRowRange,
            &aSecondPrintRange, &aSecondRepeatColRange, &aSecondRepeatRowRange };
    for (size_t i = 0; i < aScRanges.size(); i++)
    {
        const SCTAB nTab = i >= 3 ? nFirstTab : nSecondTab;
        aScRanges[i]->aStart.SetTab(nTab);
        aScRanges[i]->aEnd.SetTab(nTab);
    }

    // Without the fix in place, no print ranges and col/row repetitions would be present
    CPPUNIT_ASSERT_EQUAL(aFirstPrintRange, *aScDocument.GetPrintRange(nSecondTab, 0));
    CPPUNIT_ASSERT_EQUAL(aFirstRepeatColRange, *aScDocument.GetRepeatColRange(nSecondTab));
    CPPUNIT_ASSERT_EQUAL(aFirstRepeatRowRange, *aScDocument.GetRepeatRowRange(nSecondTab));
    CPPUNIT_ASSERT_EQUAL(aSecondPrintRange, *aScDocument.GetPrintRange(nFirstTab, 0));
    CPPUNIT_ASSERT_EQUAL(aSecondRepeatColRange, *aScDocument.GetRepeatColRange(nFirstTab));
    CPPUNIT_ASSERT_EQUAL(aSecondRepeatRowRange, *aScDocument.GetRepeatRowRange(nFirstTab));

    m_pDoc->DeleteTab(nFirstTab);
    m_pDoc->DeleteTab(nSecondTab);
}

CPPUNIT_TEST_FIXTURE(Test, testTdf113027)
{
    // Insert some sheets including a whitespace in their name and switch the grammar to R1C1
    CPPUNIT_ASSERT(m_pDoc->InsertTab(0, u"Sheet 1"_ustr));
    CPPUNIT_ASSERT(m_pDoc->InsertTab(1, u"Sheet 2"_ustr));
    FormulaGrammarSwitch aFGSwitch(m_pDoc, formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);

    // Add a formula containing a remote reference, i.e., to another sheet
    const ScAddress aScAddress(0, 0, 0);
    static constexpr OUString aFormula = u"='Sheet 2'!RC"_ustr;
    m_pDoc->SetString(aScAddress, aFormula);

    // Switch from relative to absolute cell reference
    ScRefFinder aFinder(aFormula, aScAddress, *m_pDoc, m_pDoc->GetAddressConvention());
    aFinder.ToggleRel(0, aFormula.getLength());

    // Without the fix in place, this test would have failed with
    // - Expected: ='Sheet 2'!R1C1
    // - Actual  : ='Sheet 2'!RC
    // i.e. the cell reference was not changed from relative to absolute
    CPPUNIT_ASSERT_EQUAL(u"='Sheet 2'!R1C1"_ustr, aFinder.GetText());

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

CPPUNIT_TEST_FIXTURE(Test, testTdf90698)
{
    CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
    m_pDoc->SetString(ScAddress(0,0,0), u"=(1;2)"_ustr);

    // Without the fix in place, this would have failed with
    // - Expected: =(1;2)
    // - Actual  : =(1~2)
    OUString aFormula = m_pDoc->GetFormula(0,0,0);
    CPPUNIT_ASSERT_EQUAL(u"=(1;2)"_ustr, aFormula);

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testTdf114406)
{
    CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
    m_pDoc->SetString(ScAddress(0,0,0), u"5"_ustr);
    m_pDoc->SetString(ScAddress(1,0,0), u"=A1/100%"_ustr);

    // Without the fix in place, this would have failed with
    // - Expected: =A1/100%
    // - Actual  : =A1/1
    OUString aFormula = m_pDoc->GetFormula(1,0,0);
    CPPUNIT_ASSERT_EQUAL(u"=A1/100%"_ustr, aFormula);

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

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testTdf93951)
{
    CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));
    m_pDoc->SetString(ScAddress(0,0,0), u"=2*§*2"_ustr);

    OUString aFormula = m_pDoc->GetFormula(0,0,0);

    // Without the fix in place, this test would have failed with
    // - Expected: =2*§*2
    // - Actual  : =2*
    CPPUNIT_ASSERT_EQUAL(u"=2*§*2"_ustr, aFormula);

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testTdf134490)
{
    CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));

    m_pDoc->SetString(ScAddress(0,0,0), u"--1"_ustr);
    m_pDoc->SetString(ScAddress(0,1,0), u"---1"_ustr);
    m_pDoc->SetString(ScAddress(0,2,0), u"+-1"_ustr);
    m_pDoc->SetString(ScAddress(0,3,0), u"+--1"_ustr);

    // Without the fix in place, this test would have failed with
    // - Expected: --1
    // - Actual  : -1
    CPPUNIT_ASSERT_EQUAL(u"--1"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL(u"---1"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
    CPPUNIT_ASSERT_EQUAL(u"+-1"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));
    CPPUNIT_ASSERT_EQUAL(u"+--1"_ustr, m_pDoc->GetString(ScAddress(0,3,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testTdf135249)
{
    CPPUNIT_ASSERT(m_pDoc->InsertTab (0, u"Test"_ustr));

    m_pDoc->SetString(ScAddress(0,0,0), u"1:60"_ustr);
    m_pDoc->SetString(ScAddress(0,1,0), u"1:123"_ustr);
    m_pDoc->SetString(ScAddress(0,2,0), u"1:1:123"_ustr);
    m_pDoc->SetString(ScAddress(0,3,0), u"0:123"_ustr);
    m_pDoc->SetString(ScAddress(0,4,0), u"0:0:123"_ustr);
    m_pDoc->SetString(ScAddress(0,5,0), u"0:123:59"_ustr);

    // These are not valid duration inputs
    CPPUNIT_ASSERT_EQUAL(u"1:60"_ustr, m_pDoc->GetString(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_EQUAL(u"1:123"_ustr, m_pDoc->GetString(ScAddress(0,1,0)));
    CPPUNIT_ASSERT_EQUAL(u"1:1:123"_ustr, m_pDoc->GetString(ScAddress(0,2,0)));

    // These are valid duration inputs
    // Without the fix in place, this test would have failed with
    // - Expected: 02:03:00 AM
    // - Actual  : 0:123
    CPPUNIT_ASSERT_EQUAL(u"02:03:00 AM"_ustr, m_pDoc->GetString(ScAddress(0,3,0)));
    CPPUNIT_ASSERT_EQUAL(u"12:02:03 AM"_ustr, m_pDoc->GetString(ScAddress(0,4,0)));
    CPPUNIT_ASSERT_EQUAL(u"02:03:59 AM"_ustr, m_pDoc->GetString(ScAddress(0,5,0)));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testDocStatistics)
{
    SCTAB nStartTabs = m_pDoc->GetTableCount();
    m_pDoc->InsertTab(0, u"Sheet1"_ustr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to increment sheet count.",
                               static_cast<SCTAB>(nStartTabs+1), m_pDoc->GetTableCount());
    m_pDoc->InsertTab(1, u"Sheet2"_ustr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to increment sheet count.",
                               static_cast<SCTAB>(nStartTabs+2), m_pDoc->GetTableCount());

    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(0), m_pDoc->GetCellCount());
    m_pDoc->SetValue(ScAddress(0,0,0), 2.0);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetCellCount());
    m_pDoc->SetValue(ScAddress(2,2,0), 2.5);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(2), m_pDoc->GetCellCount());
    m_pDoc->SetString(ScAddress(1,1,1), u"Test"_ustr);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(3), m_pDoc->GetCellCount());

    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(0), m_pDoc->GetFormulaGroupCount());
    m_pDoc->SetString(ScAddress(3,0,1), u"=A1"_ustr);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetFormulaGroupCount());
    m_pDoc->SetString(ScAddress(3,1,1), u"=A2"_ustr);
    m_pDoc->SetString(ScAddress(3,2,1), u"=A3"_ustr);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(1), m_pDoc->GetFormulaGroupCount());
    m_pDoc->SetString(ScAddress(3,3,1), u"=A5"_ustr);
    m_pDoc->SetString(ScAddress(3,4,1), u"=A6"_ustr);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(2), m_pDoc->GetFormulaGroupCount());
    m_pDoc->SetString(ScAddress(3,1,1), u"=A3"_ustr);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt64>(4), m_pDoc->GetFormulaGroupCount());

    m_pDoc->DeleteTab(1);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Failed to decrement sheet count.",
                               static_cast<SCTAB>(nStartTabs+1), m_pDoc->GetTableCount());
    m_pDoc->DeleteTab(0); // This may fail in case there is only one sheet in the document.
}

CPPUNIT_TEST_FIXTURE(Test, testRowForHeight)
{
    m_pDoc->InsertTab(0, u"Sheet1"_ustr);
    m_pDoc->SetRowHeightRange( 0,  9, 0, 100);
    m_pDoc->SetRowHeightRange(10, 19, 0, 200);
    m_pDoc->SetRowHeightRange(20, 29, 0, 300);

    // Hide some rows.
    m_pDoc->SetRowHidden(3,  5, 0, true);
    m_pDoc->SetRowHidden(8, 12, 0, true);

    struct Check
    {
        tools::Long nHeight;
        SCROW nRow;
    };

    std::vector<Check> aChecks = {
        { -2000,  0 },
        { -1000,  0 },
        {    -1,  0 },
        {     0,  0 }, // row 0 begins
        {     1,  0 },
        {    99,  0 }, // row 0 ends
        {   100,  1 }, // row 1 begins
        {   101,  1 },
        {   120,  1 },
        {   199,  1 }, // row 1 ends
        {   200,  2 }, // row 2 begins
        {   201,  2 },
        {   299,  2 }, // row 2 ends
        {   300,  6 }, // row 6 begins, because 3-5 are hidden
        {   330,  6 },
        {   399,  6 }, // row 6 ends
        {   400,  7 }, // row 7 begins
        {   401,  7 },
        {   420,  7 },
        {   499,  7 }, // row 7 ends
        {   500, 13 }, // row 13 begins, because 8-12 are hidden
        {   501, 13 },
        {   599, 13 },
        {   600, 13 },
        {   699, 13 }, // row 13 ends (row 13 is 200 pixels high)
        {   700, 14 }, // row 14 begins
        {   780, 14 },
        {   899, 14 }, // row 14 ends (row 14 is 200 pixels high)
        {   900, 15 }, // row 15 begins
        {  1860, 19 },
        {  4020, 27 },
    };

    for (const Check& rCheck : aChecks)
    {
        SCROW nRow = m_pDoc->GetRowForHeight(0, rCheck.nHeight);
        OString sMessage = "for height " + OString::number(rCheck.nHeight) + " we expect row " + OString::number(rCheck.nRow);
        CPPUNIT_ASSERT_EQUAL_MESSAGE(sMessage.getStr(), rCheck.nRow, nRow);
    }
}

CPPUNIT_TEST_FIXTURE(Test, testDataEntries)
{
    /**
     * The 'data entries' data is a list of strings used for suggestions as
     * the user types in new cell value.
     */

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

    m_pDoc->SetString(ScAddress(0,5,0), u"Andy"_ustr);
    m_pDoc->SetString(ScAddress(0,6,0), u"Bruce"_ustr);
    m_pDoc->SetString(ScAddress(0,7,0), u"Charlie"_ustr);
    m_pDoc->SetString(ScAddress(0,10,0), u"Andy"_ustr);

    std::vector<ScTypedStrData> aEntries;
    m_pDoc->GetDataEntries(0, 0, 0, aEntries); // Try at the very top.

    // Entries are supposed to be sorted in ascending order, and are all unique.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aEntries.size());
    std::vector<ScTypedStrData>::const_iterator it = aEntries.begin();
    CPPUNIT_ASSERT_EQUAL(u"Andy"_ustr, it->GetString());
    ++it;
    CPPUNIT_ASSERT_EQUAL(u"Bruce"_ustr, it->GetString());
    ++it;
    CPPUNIT_ASSERT_EQUAL(u"Charlie"_ustr, it->GetString());
    ++it;
    CPPUNIT_ASSERT_MESSAGE("The entries should have ended here."bool(it == aEntries.end()));

    aEntries.clear();
    m_pDoc->GetDataEntries(0, m_pDoc->MaxRow(), 0, aEntries); // Try at the very bottom.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aEntries.size());

    // Make sure we get the same set of suggestions.
    it = aEntries.begin();
    CPPUNIT_ASSERT_EQUAL(u"Andy"_ustr, it->GetString());
    ++it;
    CPPUNIT_ASSERT_EQUAL(u"Bruce"_ustr, it->GetString());
    ++it;
    CPPUNIT_ASSERT_EQUAL(u"Charlie"_ustr, it->GetString());
    ++it;
    CPPUNIT_ASSERT_MESSAGE("The entries should have ended here."bool(it == aEntries.end()));

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testSelectionFunction)
{
    /**
     * Selection function is responsible for displaying quick calculation
     * results in the status bar.
     */

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

    // Insert values into B2:B4.
    m_pDoc->SetString(ScAddress(1,1,0), u"=1"_ustr); // formula
    m_pDoc->SetValue(ScAddress(1,2,0), 2.0);
    m_pDoc->SetValue(ScAddress(1,3,0), 3.0);

    // Insert strings into B5:B8.
    m_pDoc->SetString(ScAddress(1,4,0), u"A"_ustr);
    m_pDoc->SetString(ScAddress(1,5,0), u"B"_ustr);
    m_pDoc->SetString(ScAddress(1,6,0), u"=\"C\""_ustr); // formula
    m_pDoc->SetString(ScAddress(1,7,0), u"D"_ustr);

    // Insert values into D2:D4.
    m_pDoc->SetValue(ScAddress(3,1,0), 4.0);
    m_pDoc->SetValue(ScAddress(3,2,0), 5.0);
    m_pDoc->SetValue(ScAddress(3,3,0), 6.0);

    // Insert edit text into D5.
    ScFieldEditEngine& rEE = m_pDoc->GetEditEngine();
    rEE.SetTextCurrentDefaults(u"Rich Text"_ustr);
    m_pDoc->SetEditText(ScAddress(3,4,0), rEE.CreateTextObject());

    // Insert Another string into D6.
    m_pDoc->SetString(ScAddress(3,5,0), u"E"_ustr);

    // Select B2:B8 & D2:D8 disjoint region.
    ScRangeList aRanges;
    aRanges.push_back(ScRange(1,1,0,1,7,0)); // B2:B8
    aRanges.push_back(ScRange(3,1,0,3,7,0)); // D2:D8
    ScMarkData aMark(m_pDoc->GetSheetLimits());
    aMark.MarkFromRangeList(aRanges, true);

    struct Check
    {
        ScSubTotalFunc meFunc;
        double mfExpected;
    };

    {
        static const Check aChecks[] =
        {
            { SUBTOTAL_FUNC_AVE,              3.5 },
            { SUBTOTAL_FUNC_CNT2,            12.0 },
            { SUBTOTAL_FUNC_CNT,              6.0 },
            { SUBTOTAL_FUNC_MAX,              6.0 },
            { SUBTOTAL_FUNC_MIN,              1.0 },
            { SUBTOTAL_FUNC_SUM,             21.0 },
            { SUBTOTAL_FUNC_SELECTION_COUNT, 14.0 }
        };

        for (const auto& rCheck : aChecks)
        {
            double fRes = 0.0;
            bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, ScAddress(), aMark, fRes);
            CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
            CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
        }
    }

    // Hide rows 4 and 6 and check the results again.

    m_pDoc->SetRowHidden(3, 3, 0, true);
    m_pDoc->SetRowHidden(5, 5, 0, true);
    CPPUNIT_ASSERT_MESSAGE("This row should be hidden.", m_pDoc->RowHidden(3, 0));
    CPPUNIT_ASSERT_MESSAGE("This row should be hidden.", m_pDoc->RowHidden(5, 0));

    {
        static const Check aChecks[] =
        {
            { SUBTOTAL_FUNC_AVE,              3.0 },
            { SUBTOTAL_FUNC_CNT2,             8.0 },
            { SUBTOTAL_FUNC_CNT,              4.0 },
            { SUBTOTAL_FUNC_MAX,              5.0 },
            { SUBTOTAL_FUNC_MIN,              1.0 },
            { SUBTOTAL_FUNC_SUM,             12.0 },
            { SUBTOTAL_FUNC_SELECTION_COUNT, 10.0 }
        };

        for (const auto& rCheck : aChecks)
        {
            double fRes = 0.0;
            bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, ScAddress(), aMark, fRes);
            CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
            CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
        }
    }

    // Make sure that when no selection is present, use the current cursor position.
    ScMarkData aEmpty(m_pDoc->GetSheetLimits());

    {
        // D3 (numeric cell containing 5.)
        ScAddress aPos(3, 2, 0);

        static const Check aChecks[] =
        {
            { SUBTOTAL_FUNC_AVE,             5.0 },
            { SUBTOTAL_FUNC_CNT2,            1.0 },
            { SUBTOTAL_FUNC_CNT,             1.0 },
            { SUBTOTAL_FUNC_MAX,             5.0 },
            { SUBTOTAL_FUNC_MIN,             5.0 },
            { SUBTOTAL_FUNC_SUM,             5.0 },
            { SUBTOTAL_FUNC_SELECTION_COUNT, 1.0 }
        };

        for (const auto& rCheck : aChecks)
        {
            double fRes = 0.0;
            bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, aPos, aEmpty, fRes);
            CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
            CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
        }
    }

    {
        // B7 (string formula cell containing ="C".)
        ScAddress aPos(1, 6, 0);

        static const Check aChecks[] =
        {
            { SUBTOTAL_FUNC_CNT2,            1.0 },
            { SUBTOTAL_FUNC_SELECTION_COUNT, 1.0 }
        };

        for (const auto& rCheck : aChecks)
        {
            double fRes = 0.0;
            bool bRes = m_pDoc->GetSelectionFunction(rCheck.meFunc, aPos, aEmpty, fRes);
            CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
            CPPUNIT_ASSERT_EQUAL(rCheck.mfExpected, fRes);
        }
    }

    // Calculate function across selected sheets.
    clearSheet(m_pDoc, 0);
    m_pDoc->InsertTab(1, u"Test2"_ustr);
    m_pDoc->InsertTab(2, u"Test3"_ustr);

    // Set values at B2 and C3 on each sheet.
    m_pDoc->SetValue(ScAddress(1,1,0), 1.0);
    m_pDoc->SetValue(ScAddress(2,2,0), 2.0);
    m_pDoc->SetValue(ScAddress(1,1,1), 4.0);
    m_pDoc->SetValue(ScAddress(2,2,1), 8.0);
    m_pDoc->SetValue(ScAddress(1,1,2), 16.0);
    m_pDoc->SetValue(ScAddress(2,2,2), 32.0);

    // Mark B2 and C3 on first sheet.
    aRanges.RemoveAll();
    aRanges.push_back(ScRange(1,1,0)); // B2
    aRanges.push_back(ScRange(2,2,0)); // C3
    aMark.MarkFromRangeList(aRanges, true);
    // Additionally select third sheet.
    aMark.SelectTable(2, true);

    {
        double fRes = 0.0;
        bool bRes = m_pDoc->GetSelectionFunction( SUBTOTAL_FUNC_SUM, ScAddress(), aMark, fRes);
        CPPUNIT_ASSERT_MESSAGE("Failed to fetch selection function result.", bRes);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("1+2+16+32=", 51.0, fRes);
    }

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

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

    // Insert cells to A1, A5, B2 and C3.
    m_pDoc->SetString(ScAddress(0,0,0), u"California"_ustr);
    m_pDoc->SetValue(ScAddress(0,4,0), 1.2);
    m_pDoc->SetEditText(ScAddress(1,1,0), u"Boston"_ustr);
    m_pDoc->SetFormula(ScAddress(2,2,0), u"=SUM(1,2,3)"_ustr, m_pDoc->GetGrammar());

    // Select A1:C5.
    ScMarkData aMarkData(m_pDoc->GetSheetLimits());
    aMarkData.SetMarkArea(ScRange(0,0,0,2,4,0));
    aMarkData.MarkToMulti(); // TODO : we shouldn't have to do this.

    struct Check
    {
        SCCOL mnCol;
        SCROW mnRow;
    };

    const std::vector<Check> aChecks = {
        { 0, 0 }, // A1
        { 0, 4 }, // A5
        { 1, 1 }, // B2
        { 2, 2 }, // C3
    };

    SCROW nRow = -1; // Start from the imaginary row before A1.
    SCCOL nCol = 0;

    for (const Check& rCheck : aChecks)
    {
        bool bFound = m_pDoc->GetNextMarkedCell(nCol, nRow, 0, aMarkData);
        if (!bFound)
        {
            std::ostringstream os;
            os << ScAddress(rCheck.mnCol, rCheck.mnRow, 0).GetColRowString() << " was expected, but not found.";
            CPPUNIT_FAIL(os.str());
        }

        CPPUNIT_ASSERT_EQUAL(rCheck.mnRow, nRow);
        CPPUNIT_ASSERT_EQUAL(rCheck.mnCol, nCol);
    }

    // No more marked cells on this sheet.
    bool bFound = m_pDoc->GetNextMarkedCell(nCol, nRow, 0, aMarkData);
    CPPUNIT_ASSERT(!bFound);

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testCopyToDocument)
{
    CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, u"src"_ustr));

    // We need a drawing layer in order to create caption objects.
    m_pDoc->InitDrawLayer(m_xDocShell.get());

    m_pDoc->SetString(0, 0, 0, u"Header"_ustr);
    m_pDoc->SetString(0, 1, 0, u"1"_ustr);
    m_pDoc->SetString(0, 2, 0, u"2"_ustr);
    m_pDoc->SetString(0, 3, 0, u"3"_ustr);
    m_pDoc->SetString(0, 4, 0, u"=4/2"_ustr);
    m_pDoc->CalcAll();

    //note on A1
    ScAddress aAdrA1 (0, 0, 0); // numerical cell content
    ScPostIt* pNote = m_pDoc->GetOrCreateNote(aAdrA1);
    pNote->SetText(aAdrA1, u"Hello world in A1"_ustr);

    // Copy statically to another document.

    ScDocShellRef xDocSh2;
    getNewDocShell(xDocSh2);
    ScDocument* pDestDoc = &xDocSh2->GetDocument();
    pDestDoc->InsertTab(0, u"src"_ustr);
    pDestDoc->InitDrawLayer(xDocSh2.get());     // for note caption objects

    m_pDoc->CopyStaticToDocument(ScRange(0,1,0,0,3,0), 0, *pDestDoc); // Copy A2:A4
    m_pDoc->CopyStaticToDocument(ScRange(ScAddress(0,0,0)), 0,     *pDestDoc); // Copy A1
    m_pDoc->CopyStaticToDocument(ScRange(0,4,0,0,7,0), 0, *pDestDoc); // Copy A5:A8

    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,0,0), pDestDoc->GetString(0,0,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,1,0), pDestDoc->GetString(0,1,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,2,0), pDestDoc->GetString(0,2,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,3,0), pDestDoc->GetString(0,3,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,4,0), pDestDoc->GetString(0,4,0));

    // verify note
    CPPUNIT_ASSERT_MESSAGE("There should be a note in A1 destDocument", pDestDoc->HasNote(ScAddress(0, 0, 0)));
    CPPUNIT_ASSERT_EQUAL_MESSAGE("The notes content should be the same on both documents",
            m_pDoc->GetNote(ScAddress(0, 0, 0))->GetText(), pDestDoc->GetNote(ScAddress(0, 0, 0))->GetText());

    pDestDoc->DeleteTab(0);
    xDocSh2->DoClose();
    xDocSh2.clear();

    m_pDoc->DeleteTab(0);
}

bool Test::checkHorizontalIterator(ScDocument& rDoc, const std::vector<std::vector<const char*>>& rData, const HoriIterCheck* pChecks, size_t nCheckCount)
{
    ScAddress aPos(0,0,0);
    insertRangeData(&rDoc, aPos, rData);
    ScHorizontalCellIterator aIter(rDoc, 0, 0, 0, 1, rData.size() - 1);

    SCCOL nCol;
    SCROW nRow;
    size_t i = 0;
    for (ScRefCellValue* pCell = aIter.GetNext(nCol, nRow); pCell; pCell = aIter.GetNext(nCol, nRow), ++i)
    {
        if (i >= nCheckCount)
        {
            cerr << "hit invalid check " << i << " of " << nCheckCount << endl;
            CPPUNIT_FAIL("Iterator claims there is more data than there should be.");
            return false;
        }

        if (pChecks[i].nCol != nCol)
        {
            cerr << "Column mismatch " << pChecks[i].nCol << " vs. " << nCol << endl;
            return false;
        }

        if (pChecks[i].nRow != nRow)
        {
            cerr << "Row mismatch " << pChecks[i].nRow << " vs. " << nRow << endl;
            return false;
        }

        if (OUString::createFromAscii(pChecks[i].pVal) != pCell->getString(rDoc))
        {
            cerr << "String mismatch " << pChecks[i].pVal << " vs. " <<
                pCell->getString(rDoc) << endl;
            return false;
        }
    }

    return true;
}

CPPUNIT_TEST_FIXTURE(Test, testHorizontalIterator)
{
    m_pDoc->InsertTab(0, u"test"_ustr);

    {
        // Raw data - mixed types
        std::vector<std::vector<const char*>> aData = {
            { "A""B" },
            { "C""1" },
            { "D""2" },
            { "E""3" }
        };

        static const HoriIterCheck aChecks[] = {
            { 0, 0, "A" },
            { 1, 0, "B" },
            { 0, 1, "C" },
            { 1, 1, "1" },
            { 0, 2, "D" },
            { 1, 2, "2" },
            { 0, 3, "E" },
            { 1, 3, "3" },
        };

        bool bRes = checkHorizontalIterator(
            *m_pDoc, aData, aChecks, std::size(aChecks));

        if (!bRes)
            CPPUNIT_FAIL("Failed on test mixed.");
    }

    {
        // Raw data - 'hole' data
        std::vector<std::vector<const char*>> aData = {
            { "A""B" },
            { "C",  nullptr  },
            { "D""E" },
        };

        static const HoriIterCheck aChecks[] = {
            { 0, 0, "A" },
            { 1, 0, "B" },
            { 0, 1, "C" },
            { 0, 2, "D" },
            { 1, 2, "E" },
        };

        bool bRes = checkHorizontalIterator(
            *m_pDoc, aData, aChecks, std::size(aChecks));

        if (!bRes)
            CPPUNIT_FAIL("Failed on test hole.");
    }

    {
        // Very holy data
        std::vector<std::vector<const char*>> aData = {
            {  nullptr,  "A" },
            {  nullptr,   nullptr  },
            {  nullptr,  "1" },
            { "B",  nullptr  },
            { "C""2" },
            { "D""3" },
            { "E",  nullptr  },
            {  nullptr,  "G" },
            {  nullptr,   nullptr  },
        };

        static const HoriIterCheck aChecks[] = {
            { 1, 0, "A" },
            { 1, 2, "1" },
            { 0, 3, "B" },
            { 0, 4, "C" },
            { 1, 4, "2" },
            { 0, 5, "D" },
            { 1, 5, "3" },
            { 0, 6, "E" },
            { 1, 7, "G" },
        };

        bool bRes = checkHorizontalIterator(
            *m_pDoc, aData, aChecks, std::size(aChecks));

        if (!bRes)
            CPPUNIT_FAIL("Failed on test holy.");
    }

    {
        // Degenerate case
        std::vector<std::vector<const char*>> aData = {
            {  nullptr,   nullptr },
            {  nullptr,   nullptr },
            {  nullptr,   nullptr },
        };

        bool bRes = checkHorizontalIterator(
            *m_pDoc, aData, nullptr, 0);

        if (!bRes)
            CPPUNIT_FAIL("Failed on test degenerate.");
    }

    {
        // Data at end
        std::vector<std::vector<const char*>> aData = {
            {  nullptr,   nullptr },
            {  nullptr,   nullptr },
            {  nullptr,  "A" },
        };

        static const HoriIterCheck aChecks[] = {
            { 1, 2, "A" },
        };

        bool bRes = checkHorizontalIterator(
            *m_pDoc, aData, aChecks, std::size(aChecks));

        if (!bRes)
            CPPUNIT_FAIL("Failed on test at end.");
    }

    {
        // Data in middle
        std::vector<std::vector<const char*>> aData = {
            {  nullptr,   nullptr  },
            {  nullptr,   nullptr  },
            {  nullptr,  "A" },
            {  nullptr,  "1" },
            {  nullptr,   nullptr  },
        };

        static const HoriIterCheck aChecks[] = {
            { 1, 2, "A" },
            { 1, 3, "1" },
        };

        bool bRes = checkHorizontalIterator(
            *m_pDoc, aData, aChecks, std::size(aChecks));

        if (!bRes)
            CPPUNIT_FAIL("Failed on test in middle.");
    }

    m_pDoc->DeleteTab(0);
}

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

    // Turn on "precision as shown" option.
    ScDocOptions aOpt = m_pDoc->GetDocOptions();
    aOpt.SetCalcAsShown(true);
    m_pDoc->SetDocOptions(aOpt);

    ScInterpreterContext aContext(*m_pDoc, m_pDoc->GetFormatTable());

    // Purely horizontal data layout with numeric data.
    for (SCCOL i = 1; i <= 3; ++i)
        m_pDoc->SetValue(ScAddress(i,2,0), i);

    {
        const double aChecks[] = { 1.0, 2.0, 3.0 };
        size_t const nCheckLen = std::size(aChecks);
        ScValueIterator aIter(aContext, ScRange(1,2,0,3,2,0));
        bool bHas = false;
        size_t nCheckPos = 0;
        double fVal;
        FormulaError nErr;
        for (bHas = aIter.GetFirst(fVal, nErr); bHas; bHas = aIter.GetNext(fVal, nErr), ++nCheckPos)
        {
            CPPUNIT_ASSERT_MESSAGE("Iteration longer than expected.", nCheckPos < nCheckLen);
            CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos], fVal);
            CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(nErr));
        }
    }

    m_pDoc->DeleteTab(0);
}

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

    // Set the background color of B2:C3,D2,E3,C4:D4,B5:D5 to blue
    ScPatternAttr aCellBackColor(m_pDoc->getCellAttributeHelper());
    aCellBackColor.GetItemSet().Put(SvxBrushItem(COL_BLUE, ATTR_BACKGROUND));
    m_pDoc->ApplyPatternAreaTab(1, 1, 2, 2, 0, aCellBackColor);
    m_pDoc->ApplyPatternAreaTab(3, 1, 3, 1, 0, aCellBackColor);
    m_pDoc->ApplyPatternAreaTab(4, 2, 4, 2, 0, aCellBackColor);
    m_pDoc->ApplyPatternAreaTab(2, 3, 3, 3, 0, aCellBackColor);
    m_pDoc->ApplyPatternAreaTab(1, 4, 4, 4, 0, aCellBackColor);

    // some numeric data
    for (SCCOL i = 1; i <= 4; ++i)
        for (SCROW j = 1; j <= 4; ++j)
            m_pDoc->SetValue(ScAddress(i,j,0), i*10+j);

    {
        const int aChecks[][3] = { {1, 3, 1}, {1, 2, 2}, {4, 4, 2}, {2, 3, 3}, {1, 4, 4} };
        const size_t nCheckLen = std::size(aChecks);

        ScHorizontalAttrIterator aIter(*m_pDoc, 0, 0, 0, 5, 5);
        SCCOL nCol1, nCol2;
        SCROW nRow;
        size_t nCheckPos = 0;
        for (const ScPatternAttr* pAttr = aIter.GetNext(nCol1, nCol2, nRow); pAttr; pAttr = aIter.GetNext(nCol1, nCol2, nRow))
        {
            if (pAttr->isDefault())
                continue;
            CPPUNIT_ASSERT_MESSAGE("Iteration longer than expected.", nCheckPos < nCheckLen);
            CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos][0], static_cast<int>(nCol1));
            CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos][1], static_cast<int>(nCol2));
            CPPUNIT_ASSERT_EQUAL(aChecks[nCheckPos][2], static_cast<int>(nRow));
            ++nCheckPos;
        }
    }

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testIteratorsUnallocatedColumnsAttributes)
{
    m_pDoc->InsertTab(0, u"Tab1"_ustr);

    // Set values in first two columns, to ensure allocation of those columns.
    m_pDoc->SetValue(ScAddress(0,1,0), 1);
    m_pDoc->SetValue(ScAddress(1,1,0), 2);
    constexpr SCCOL allocatedColsCount = 2;
    assert( allocatedColsCount >= INITIALCOLCOUNT );
    CPPUNIT_ASSERT_EQUAL(allocatedColsCount, m_pDoc->GetAllocatedColumnsCount(0));

    // Make entire second row and third row bold.
    ScPatternAttr boldAttr(m_pDoc->getCellAttributeHelper());
    boldAttr.GetItemSet().Put(SvxWeightItem(WEIGHT_BOLD, ATTR_FONT_WEIGHT));
    m_pDoc->ApplyPatternAreaTab(0, 1, m_pDoc->MaxCol(), 2, 0, boldAttr);

    // That shouldn't need allocating more columns, just changing the default attribute.
    CPPUNIT_ASSERT_EQUAL(allocatedColsCount, m_pDoc->GetAllocatedColumnsCount(0));
    vcl::Font aFont;
    const ScPatternAttr* pattern = m_pDoc->GetPattern(m_pDoc->MaxCol(), 1, 0);
    pattern->fillFontOnly(aFont);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be bold", WEIGHT_BOLD, aFont.GetWeightMaybeAskConfig());

    // Test iterators.
    ScDocAttrIterator docit( *m_pDoc, 0, allocatedColsCount - 1, 1, allocatedColsCount, 2 );
    SCCOL col1, col2;
    SCROW row1, row2;
    CPPUNIT_ASSERT_EQUAL( pattern, docit.GetNext( col1, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
    CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
    CPPUNIT_ASSERT_EQUAL( SCROW(2), row2 );
    CPPUNIT_ASSERT_EQUAL( pattern, docit.GetNext( col1, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col1 );
    CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
    CPPUNIT_ASSERT_EQUAL( SCROW(2), row2 );
    CPPUNIT_ASSERT( docit.GetNext( col1, row1, row2 ) == nullptr );

    ScAttrRectIterator rectit( *m_pDoc, 0, allocatedColsCount - 1, 1, allocatedColsCount, 2 );
    CPPUNIT_ASSERT_EQUAL( pattern, rectit.GetNext( col1, col2, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
    CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col2 );
    CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
    CPPUNIT_ASSERT_EQUAL( SCROW(2), row2 );
    CPPUNIT_ASSERT( rectit.GetNext( col1, col2, row1, row2 ) == nullptr );

    ScHorizontalAttrIterator horit( *m_pDoc, 0, allocatedColsCount - 1, 1, allocatedColsCount, 2 );
    CPPUNIT_ASSERT_EQUAL( pattern, horit.GetNext( col1, col2, row1 ));
    CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
    CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col2 );
    CPPUNIT_ASSERT_EQUAL( SCROW(1), row1 );
    CPPUNIT_ASSERT_EQUAL( pattern, horit.GetNext( col1, col2, row1 ));
    CPPUNIT_ASSERT_EQUAL( SCCOL(allocatedColsCount - 1), col1 );
    CPPUNIT_ASSERT_EQUAL( allocatedColsCount, col2 );
    CPPUNIT_ASSERT_EQUAL( SCROW(2), row1 );
    CPPUNIT_ASSERT( horit.GetNext( col1, col2, row1 ) == nullptr );

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testIteratorsDefPattern)
{
    m_pDoc->InsertTab(0, u"Tab1"_ustr);

    // The default pattern is the default style, which can be edited by the user.
    // As such iterators should not ignore it by default, because it might contain
    // some attributes set.

    // Set cells as bold, default allocated, bold, default unallocated.
    SCCOL firstCol = 100;
    SCCOL lastCol = 103;
    ScPatternAttr boldAttr(m_pDoc->getCellAttributeHelper());
    boldAttr.GetItemSet().Put(SvxWeightItem(WEIGHT_BOLD, ATTR_FONT_WEIGHT));
    m_pDoc->ApplyPattern(100, 0, 0, boldAttr);
    m_pDoc->ApplyPattern(102, 0, 0, boldAttr);

    CPPUNIT_ASSERT_EQUAL(SCCOL(102 + 1), m_pDoc->GetAllocatedColumnsCount(0));
    const ScPatternAttr* pattern = m_pDoc->GetPattern(100, 0, 0);
    const ScPatternAttr* defPattern(&m_pDoc->getCellAttributeHelper().getDefaultCellAttribute()); //GetDefPattern();
    CPPUNIT_ASSERT(!ScPatternAttr::areSame(pattern, defPattern));
    CPPUNIT_ASSERT_EQUAL(pattern, m_pDoc->GetPattern(102, 0, 0));
    CPPUNIT_ASSERT_EQUAL(defPattern, m_pDoc->GetPattern(101, 0, 0));
    CPPUNIT_ASSERT_EQUAL(defPattern, m_pDoc->GetPattern(103, 0, 0));

    // Test iterators.
    ScDocAttrIterator docit( *m_pDoc, 0, firstCol, 0, lastCol, 0 );
    SCCOL col1, col2;
    SCROW row1, row2;
    CPPUNIT_ASSERT_EQUAL(pattern, docit.GetNext( col1, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL(defPattern, docit.GetNext( col1, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL(pattern, docit.GetNext( col1, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL(defPattern, docit.GetNext( col1, row1, row2 ));
    CPPUNIT_ASSERT(docit.GetNext( col1, row1, row2 ) == nullptr );

    ScAttrRectIterator rectit( *m_pDoc, 0, firstCol, 0, lastCol, 0 );
    CPPUNIT_ASSERT_EQUAL(pattern, rectit.GetNext( col1, col2, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL(defPattern, rectit.GetNext( col1, col2, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL(pattern, rectit.GetNext( col1, col2, row1, row2 ));
    CPPUNIT_ASSERT_EQUAL(defPattern, rectit.GetNext( col1, col2, row1, row2 ));
    CPPUNIT_ASSERT(rectit.GetNext( col1, col2, row1, row2 ) == nullptr );

    ScHorizontalAttrIterator horit( *m_pDoc, 0, firstCol, 0, lastCol, 0 );
    CPPUNIT_ASSERT_EQUAL(pattern, horit.GetNext( col1, col2, row1 ));
    CPPUNIT_ASSERT_EQUAL(defPattern, horit.GetNext( col1, col2, row1 ));
    CPPUNIT_ASSERT_EQUAL(pattern, horit.GetNext( col1, col2, row1 ));
    CPPUNIT_ASSERT_EQUAL(defPattern, horit.GetNext( col1, col2, row1 ));
    CPPUNIT_ASSERT(horit.GetNext( col1, col2, row1 ) == nullptr );

    m_pDoc->DeleteTab(0);
}

CPPUNIT_TEST_FIXTURE(Test, testLastChangedColFlagsWidth)
{
    m_pDoc->InsertTab(0, u"Tab1"_ustr);

    constexpr SCCOL firstChangedCol = 100;
    assert( firstChangedCol > m_pDoc->GetAllocatedColumnsCount(0));
    CPPUNIT_ASSERT_EQUAL(INITIALCOLCOUNT, m_pDoc->GetAllocatedColumnsCount(0));
    for( SCCOL col = firstChangedCol; col <= m_pDoc->MaxCol(); ++col )
        m_pDoc->SetColWidth( col, 0, 10 );

    // That shouldn't need allocating more columns, just changing column flags.
    CPPUNIT_ASSERT_EQUAL(INITIALCOLCOUNT, m_pDoc->GetAllocatedColumnsCount(0));
    // But the flags are changed.
    CPPUNIT_ASSERT_EQUAL(m_pDoc->MaxCol(), m_pDoc->GetLastChangedColFlagsWidth(0));

    m_pDoc->DeleteTab(0);
}

namespace {

bool broadcasterShifted(const ScDocument& rDoc, const ScAddress& rFrom, const ScAddress& rTo)
{
    const SvtBroadcaster* pBC = rDoc.GetBroadcaster(rFrom);
    if (pBC)
    {
        cerr << "Broadcaster shouldn't be here." << endl;
        return false;
    }

    pBC = rDoc.GetBroadcaster(rTo);
    if (!pBC)
    {
        cerr << "Broadcaster should be here." << endl;
        return false;
    }
    return true;
}

formula::FormulaToken* getSingleRefToken(ScDocument& rDoc, const ScAddress& rPos)
{
    ScFormulaCell* pFC = rDoc.GetFormulaCell(rPos);
    if (!pFC)
    {
        cerr << "Formula cell expected, but not found." << endl;
        return nullptr;
    }

    ScTokenArray* pTokens = pFC->GetCode();
    if (!pTokens)
    {
        cerr << "Token array is not present." << endl;
        return nullptr;
    }

    formula::FormulaToken* pToken = pTokens->FirstToken();
    if (!pToken || pToken->GetType() != formula::svSingleRef)
    {
        cerr << "Not a single reference token." << endl;
        return nullptr;
    }

    return pToken;
}

bool checkRelativeRefToken(ScDocument& rDoc, const ScAddress& rPos, SCCOL nRelCol, SCROW nRelRow)
{
    formula::FormulaToken* pToken = getSingleRefToken(rDoc, rPos);
    if (!pToken)
        return false;

    ScSingleRefData& rRef = *pToken->GetSingleRef();
    if (!rRef.IsColRel() || rRef.Col() != nRelCol)
    {
        cerr << "Unexpected relative column address." << endl;
        return false;
    }

    if (!rRef.IsRowRel() || rRef.Row() != nRelRow)
    {
        cerr << "Unexpected relative row address." << endl;
        return false;
    }

    return true;
}

bool checkDeletedRefToken(ScDocument& rDoc, const ScAddress& rPos)
{
    formula::FormulaToken* pToken = getSingleRefToken(rDoc, rPos);
    if (!pToken)
        return false;

    ScSingleRefData& rRef = *pToken->GetSingleRef();
    if (!rRef.IsDeleted())
    {
        cerr << "Deleted reference is expected, but it's still a valid reference." << endl;
        return false;
    }

    return true;
}

}

CPPUNIT_TEST_FIXTURE(Test, testCellBroadcaster)
{
    /**
     * More direct test for cell broadcaster management, used to track formula
     * dependencies.
     */

    CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, u"foo"_ustr));

    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation.
    m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr); // B1 depends on A1.
    double val = m_pDoc->GetValue(ScAddress(1,0,0)); // A1 is empty, so the result should be 0.
    CPPUNIT_ASSERT_EQUAL(0.0, val);

    const SvtBroadcaster* pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
    CPPUNIT_ASSERT_MESSAGE("Cell A1 should have a broadcaster.", pBC);

    // Change the value of A1 and make sure that B1 follows.
    m_pDoc->SetValue(ScAddress(0,0,0), 1.23);
    val = m_pDoc->GetValue(ScAddress(1,0,0));
    CPPUNIT_ASSERT_EQUAL(1.23, val);

    // Move column A down 5 cells. Make sure B1 now references A6, not A1.
    m_pDoc->InsertRow(0, 0, 0, 0, 0, 5);
    CPPUNIT_ASSERT_MESSAGE("Relative reference check failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 5));

    // Make sure the broadcaster has also moved.
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,0,0), ScAddress(0,5,0)));

    // Set new value to A6 and make sure B1 gets updated.
    m_pDoc->SetValue(ScAddress(0,5,0), 45.6);
    val = m_pDoc->GetValue(ScAddress(1,0,0));
    CPPUNIT_ASSERT_EQUAL(45.6, val);

    // Move column A up 3 cells, and make sure B1 now references A3, not A6.
    m_pDoc->DeleteRow(0, 0, 0, 0, 0, 3);
    CPPUNIT_ASSERT_MESSAGE("Relative reference check failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 2));

    // The broadcaster should also have been relocated from A6 to A3.
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,5,0), ScAddress(0,2,0)));

    // Insert cells over A1:A10 and shift cells to right.
    m_pDoc->InsertCol(ScRange(0, 0, 0, 0, 10, 0));
    CPPUNIT_ASSERT_MESSAGE("Relative reference check failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(2,0,0), -1, 2));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,2,0), ScAddress(1,2,0)));

    // Delete formula in C2, which should remove the broadcaster in B3.
    pBC = m_pDoc->GetBroadcaster(ScAddress(1,2,0));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster in B3 should still exist.", pBC);
    clearRange(m_pDoc, ScRange(ScAddress(2,0,0)));
    CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(2,0,0))); // C2 should be empty.
    pBC = m_pDoc->GetBroadcaster(ScAddress(1,2,0));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster in B3 should have been removed.", !pBC);

    // Clear everything and start over.
    clearRange(m_pDoc, ScRange(0,0,0,10,100,0));

    m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr); // B1 depends on A1.
    pBC = m_pDoc->GetBroadcaster(ScAddress(0,0,0));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A1.", pBC);

    // While column A is still empty, move column A down 2 cells. This should
    // move the broadcaster from A1 to A3.
    m_pDoc->InsertRow(0, 0, 0, 0, 0, 2);
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,0,0), ScAddress(0,2,0)));

    // Move it back while column A is still empty.
    m_pDoc->DeleteRow(0, 0, 0, 0, 0, 2);
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,2,0), ScAddress(0,0,0)));

    // Clear everything again
    clearRange(m_pDoc, ScRange(0,0,0,10,100,0));

    // B1:B3 depends on A1:A3
    m_pDoc->SetString(ScAddress(1,0,0), u"=A1"_ustr);
    m_pDoc->SetString(ScAddress(1,1,0), u"=A2"_ustr);
    m_pDoc->SetString(ScAddress(1,2,0), u"=A3"_ustr);
    CPPUNIT_ASSERT_MESSAGE("Relative reference check in B1 failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 0));
    CPPUNIT_ASSERT_MESSAGE("Relative reference check in B2 failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(1,1,0), -1, 0));
    CPPUNIT_ASSERT_MESSAGE("Relative reference check in B3 failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(1,2,0), -1, 0));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A1.", m_pDoc->GetBroadcaster(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A2.", m_pDoc->GetBroadcaster(ScAddress(0,1,0)));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A3.", m_pDoc->GetBroadcaster(ScAddress(0,2,0)));

    // Insert Rows at row 2, down 5 rows.
    m_pDoc->InsertRow(0, 0, 0, 0, 1, 5);
    CPPUNIT_ASSERT_MESSAGE("Broadcaster should exist in A1.", m_pDoc->GetBroadcaster(ScAddress(0,0,0)));
    CPPUNIT_ASSERT_MESSAGE("Relative reference check in B1 failed.",
                           checkRelativeRefToken(*m_pDoc, ScAddress(1,0,0), -1, 0));

    // Broadcasters in A2 and A3 should shift down by 5 rows.
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,1,0), ScAddress(0,6,0)));
    CPPUNIT_ASSERT_MESSAGE("Broadcaster relocation failed.",
                           broadcasterShifted(*m_pDoc, ScAddress(0,2,0), ScAddress(0,7,0)));

    // B2 and B3 should reference shifted cells.
--> --------------------

--> maximum size reached

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

Messung V0.5
C=88 H=89 G=88

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

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






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge