diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sc/qa/unit/helper | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/qa/unit/helper')
-rw-r--r-- | sc/qa/unit/helper/csv_handler.hxx | 198 | ||||
-rw-r--r-- | sc/qa/unit/helper/debughelper.hxx | 32 | ||||
-rw-r--r-- | sc/qa/unit/helper/qahelper.cxx | 999 | ||||
-rw-r--r-- | sc/qa/unit/helper/qahelper.hxx | 215 | ||||
-rw-r--r-- | sc/qa/unit/helper/scfiltertestbase.cxx | 67 | ||||
-rw-r--r-- | sc/qa/unit/helper/scfiltertestbase.hxx | 68 | ||||
-rw-r--r-- | sc/qa/unit/helper/scqahelperdllapi.h | 20 | ||||
-rw-r--r-- | sc/qa/unit/helper/shared_test_impl.hxx | 298 | ||||
-rw-r--r-- | sc/qa/unit/helper/sorthelper.hxx | 53 |
9 files changed, 1950 insertions, 0 deletions
diff --git a/sc/qa/unit/helper/csv_handler.hxx b/sc/qa/unit/helper/csv_handler.hxx new file mode 100644 index 0000000000..4c59144c3b --- /dev/null +++ b/sc/qa/unit/helper/csv_handler.hxx @@ -0,0 +1,198 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include "qahelper.hxx" + +#include <patattr.hxx> +#include <document.hxx> +#include <cellform.hxx> +#include <cellvalue.hxx> + +#include <orcus/csv_parser.hpp> + +#define DEBUG_CSV_HANDLER 0 + +inline OUString getConditionalFormatString(ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab) +{ + OUString aString; + const Color* pColor; + ScRefCellValue aCell(*pDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.isEmpty()) + return aString; + + const SfxItemSet* pCondSet = pDoc->GetCondResult( nCol, nRow, nTab ); + const ScPatternAttr* pPattern = pDoc->GetPattern(nCol, nRow, nTab); + SvNumberFormatter* pFormatter = pDoc->GetFormatTable(); + sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet ); + aString = ScCellFormat::GetString(*pDoc, ScAddress(nCol, nRow, nTab), nFormat, &pColor, *pFormatter); + return aString; +} + +inline OString createErrorMessage(SCCOL nCol, SCROW nRow, SCTAB nTab) +{ + return "Error in Table: " + + OString::number(static_cast<sal_Int32>(nTab)) + + " Column: " + + OString::number(static_cast<sal_Int32>(nCol)) + + " Row: " + + OString::number(nRow); +} + +inline OString createErrorMessage(SCCOL nCol, SCROW nRow, SCTAB nTab, std::u16string_view rExpectedString, std::u16string_view rString) +{ + return createErrorMessage(nCol, nRow, nTab) + "; Expected: '" + + OUStringToOString(rExpectedString, RTL_TEXTENCODING_UTF8) + "' Found: '" + + OUStringToOString(rString, RTL_TEXTENCODING_UTF8) + "'"; +} + +inline OString createErrorMessage(SCCOL nCol, SCROW nRow, SCTAB nTab, double aExpected, double aValue) +{ + return createErrorMessage(nCol, nRow, nTab) + "; Expected: '" + OString::number(aExpected) + + "' Found: '" + OString::number(aValue) + "'"; + +} + +class csv_handler : public orcus::csv_handler +{ +public: + csv_handler(ScDocument* pDoc, SCTAB nTab, StringType eType): + mpDoc(pDoc), + mnCol(0), + mnRow(0), + mnTab(nTab), + meStringType(eType) {} + + void end_row() + { + ++mnRow; + mnCol = 0; + } + + void cell(std::string_view value, bool /*transient*/) + { + const char* p = value.data(); + std::size_t n = value.size(); + +#if DEBUG_CSV_HANDLER + std::cout << "Col: " << mnCol << " Row: " << mnRow << std::endl; +#endif //DEBUG_CSV_HANDLER + if (n == 0) + { + // Empty cell. + if (!mpDoc->GetString(mnCol, mnRow, mnTab).isEmpty()) + { + // cell in the document is not empty. + CPPUNIT_ASSERT_MESSAGE(createErrorMessage(mnCol, mnRow, mnTab).getStr(), false); + } + } + else if (meStringType == StringType::PureString) + { + OUString aCSVString(p, n, RTL_TEXTENCODING_UTF8); + OUString aString = mpDoc->GetString(mnCol, mnRow, mnTab); + +#if DEBUG_CSV_HANDLER + std::cout << "String: " << OUStringToOString(aString, RTL_TEXTENCODING_UTF8).getStr() << std::endl; + std::cout << "CSVString: " << OUStringToOString(aCSVString, RTL_TEXTENCODING_UTF8).getStr() << std::endl; + std::cout << "result: " << (int)(aCSVString == aString) << std::endl; +#endif //DEBUG_CSV_HANDLER + + CPPUNIT_ASSERT_EQUAL_MESSAGE(createErrorMessage(mnCol, mnRow, mnTab, aCSVString, aString).getStr(), aCSVString, aString); + } + else + { + char* pRemainingChars = nullptr; + std::string aStr(p, n); + double nValue = strtod(&aStr[0], &pRemainingChars); + if (*pRemainingChars) + { + OUString aString; + switch (meStringType) + { + case StringType::StringValue: + aString = mpDoc->GetString(mnCol, mnRow, mnTab); + break; + default: + break; + } + OUString aCSVString(p, n, RTL_TEXTENCODING_UTF8); +#if DEBUG_CSV_HANDLER + std::cout << "String: " << OUStringToOString(aString, RTL_TEXTENCODING_UTF8).getStr() << std::endl; + std::cout << "CSVString: " << OUStringToOString(aCSVString, RTL_TEXTENCODING_UTF8).getStr() << std::endl; + std::cout << "result: " << (int)(aCSVString == aString) << std::endl; +#endif //DEBUG_CSV_HANDLER + + CPPUNIT_ASSERT_EQUAL_MESSAGE(createErrorMessage(mnCol, mnRow, mnTab, aCSVString, aString).getStr(), aCSVString, aString); + } + else + { + double aValue = mpDoc->GetValue(mnCol, mnRow, mnTab); +#if DEBUG_CSV_HANDLER + std::cout << "Value: " << aValue << std::endl; + std::cout << "CSVValue: " << nValue << std::endl; + std::cout << "result: " << (int)(aValue == nValue) << std::endl; +#endif //DEBUG_CSV_HANDLER + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(createErrorMessage(mnCol, mnRow, mnTab, nValue, aValue).getStr(), nValue, aValue, 1e-10); + } + } + ++mnCol; + } + +private: + ScDocument* mpDoc; + SCCOL mnCol; + SCROW mnRow; + SCTAB mnTab; + StringType meStringType; +}; + +class conditional_format_handler : public orcus::csv_handler +{ +public: + conditional_format_handler(ScDocument* pDoc, SCTAB nTab): + mpDoc(pDoc), + mnCol(0), + mnRow(0), + mnTab(nTab) {} + + void end_row() + { + ++mnRow; + mnCol = 0; + } + + void cell(std::string_view value, bool /*transient*/) + { +#if DEBUG_CSV_HANDLER + std::cout << "Col: " << mnCol << " Row: " << mnRow << std::endl; +#endif //DEBUG_CSV_HANDLER + OUString aString = getConditionalFormatString(mpDoc, mnCol, mnRow, mnTab); + OUString aCSVString(value.data(), value.size(), RTL_TEXTENCODING_UTF8); +#if DEBUG_CSV_HANDLER + std::cout << "String: " << OUStringToOString(aString, RTL_TEXTENCODING_UTF8).getStr() << std::endl; + std::cout << "CSVString: " << OUStringToOString(aCSVString, RTL_TEXTENCODING_UTF8).getStr() << std::endl; + std::cout << "result: " << (int)(aCSVString == aString) << std::endl; +#endif //DEBUG_CSV_HANDLER + CPPUNIT_ASSERT_EQUAL_MESSAGE(createErrorMessage(mnCol, mnRow, mnTab, aCSVString, aString).getStr(), aCSVString, aString ); + ++mnCol; + } + +private: + ScDocument* mpDoc; + SCCOL mnCol; + SCROW mnRow; + SCTAB mnTab; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/debughelper.hxx b/sc/qa/unit/helper/debughelper.hxx new file mode 100644 index 0000000000..47d0e25e08 --- /dev/null +++ b/sc/qa/unit/helper/debughelper.hxx @@ -0,0 +1,32 @@ +/* -*- 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/. + */ + +#pragma once + +#ifdef _WIN32 +#if !defined NOMINMAX +#define NOMINMAX +#endif +#include <prewin.h> +#include <postwin.h> +#undef NOMINMAX +#undef RGB +#endif + +#include <iostream> +#include <vector> + +#define CALC_DEBUG_OUTPUT 0 +#define CALC_TEST_PERF 0 + +#include <sal/types.h> + +using namespace ::com::sun::star; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/qahelper.cxx b/sc/qa/unit/helper/qahelper.cxx new file mode 100644 index 0000000000..4dc09a65de --- /dev/null +++ b/sc/qa/unit/helper/qahelper.cxx @@ -0,0 +1,999 @@ +/* -*- 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 "qahelper.hxx" +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/propertysequence.hxx> +#include "csv_handler.hxx" +#include "debughelper.hxx" +#include <drwlayer.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <compiler.hxx> +#include <conditio.hxx> +#include <stlsheet.hxx> +#include <formulacell.hxx> +#include <formulagroup.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdoole2.hxx> +#include <tools/UnitConversion.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/justifyitem.hxx> +#include <clipcontext.hxx> +#include <clipparam.hxx> +#include <refundo.hxx> +#include <sal/log.hxx> +#include <svl/gridprinter.hxx> +#include <sfx2/docfile.hxx> +#include <undoblk.hxx> +#include <scdll.hxx> +#include <scitems.hxx> +#include <stringutil.hxx> +#include <tokenarray.hxx> +#include <vcl/keycodes.hxx> +#include <vcl/scheduler.hxx> +#include <o3tl/safeint.hxx> + +#include <orcus/csv_parser.hpp> + +#include <cstdlib> +#include <fstream> + +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/data/XDataReceiver.hpp> +#include <com/sun/star/document/MacroExecMode.hpp> + +using namespace com::sun::star; +using namespace ::com::sun::star::uno; +using ::std::cout; +using ::std::cerr; +using ::std::endl; + +FormulaGrammarSwitch::FormulaGrammarSwitch(ScDocument* pDoc, formula::FormulaGrammar::Grammar eGrammar) : + mpDoc(pDoc), meOldGrammar(pDoc->GetGrammar()) +{ + mpDoc->SetGrammar(eGrammar); +} + +FormulaGrammarSwitch::~FormulaGrammarSwitch() +{ + mpDoc->SetGrammar(meOldGrammar); +} + +// calc data structure pretty printer +std::ostream& operator<<(std::ostream& rStrm, const ScAddress& rAddr) +{ + rStrm << "Col: " << rAddr.Col() << " Row: " << rAddr.Row() << " Tab: " << rAddr.Tab() << "\n"; + return rStrm; +} + +std::ostream& operator<<(std::ostream& rStrm, const ScRange& rRange) +{ + rStrm << "ScRange: " << rRange.aStart << rRange.aEnd << "\n"; + return rStrm; +} + +std::ostream& operator<<(std::ostream& rStrm, const ScRangeList& rList) +{ + rStrm << "ScRangeList: \n"; + for(size_t i = 0; i < rList.size(); ++i) + rStrm << rList[i]; + return rStrm; +} + +std::ostream& operator<<(std::ostream& rStrm, const OpCode& rCode) +{ + rStrm << static_cast<sal_uInt16>(rCode); + return rStrm; +} + +void ScModelTestBase::loadFile(const OUString& aFileName, std::string& aContent) +{ + OString aOFileName = OUStringToOString(aFileName, RTL_TEXTENCODING_UTF8); + +#ifdef ANDROID + size_t size; + if (strncmp(aOFileName.getStr(), "/assets/", sizeof("/assets/")-1) == 0) { + const char *contents = (const char *) lo_apkentry(aOFileName.getStr(), &size); + if (contents != 0) { + aContent = std::string(contents, size); + return; + } + } +#endif + + std::ifstream aFile(aOFileName.getStr()); + + OString aErrorMsg = "Could not open csv file: " + aOFileName; + CPPUNIT_ASSERT_MESSAGE(aErrorMsg.getStr(), aFile); + std::ostringstream aOStream; + aOStream << aFile.rdbuf(); + aFile.close(); + aContent = aOStream.str(); +} + +void ScModelTestBase::testFile(const OUString& aFileName, ScDocument& rDoc, SCTAB nTab, StringType aStringFormat) +{ + csv_handler aHandler(&rDoc, nTab, aStringFormat); + orcus::csv::parser_config aConfig; + aConfig.delimiters.push_back(','); + aConfig.delimiters.push_back(';'); + aConfig.text_qualifier = '"'; + aConfig.trim_cell_value = false; + + std::string aContent; + loadFile(aFileName, aContent); + orcus::csv_parser<csv_handler> parser(aContent, aHandler, aConfig); + try + { + parser.parse(); + } + catch (const orcus::parse_error& e) + { + std::cout << "reading csv content file failed: " << e.what() << std::endl; + OString aErrorMsg = OString::Concat("csv parser error: ") + e.what(); + CPPUNIT_ASSERT_MESSAGE(aErrorMsg.getStr(), false); + } +} + +void ScModelTestBase::testCondFile( const OUString& aFileName, ScDocument* pDoc, SCTAB nTab, bool bCommaAsDelimiter ) +{ + conditional_format_handler aHandler(pDoc, nTab); + orcus::csv::parser_config aConfig; + if ( bCommaAsDelimiter ) + aConfig.delimiters.push_back(','); + aConfig.delimiters.push_back(';'); + aConfig.text_qualifier = '"'; + std::string aContent; + loadFile(aFileName, aContent); + orcus::csv_parser<conditional_format_handler> parser(aContent, aHandler, aConfig); + try + { + parser.parse(); + } + catch (const orcus::parse_error& e) + { + std::cout << "reading csv content file failed: " << e.what() << std::endl; + OString aErrorMsg = OString::Concat("csv parser error: ") + e.what(); + CPPUNIT_ASSERT_MESSAGE(aErrorMsg.getStr(), false); + } +} + +void ScModelTestBase::testFormats(ScDocument* pDoc,std::u16string_view sFormat) +{ + //test Sheet1 with csv file + OUString aCSVFileName = createFilePath(u"contentCSV/numberFormat.csv"); + testFile(aCSVFileName, *pDoc, 0, StringType::PureString); + //need to test the color of B3 + //it's not a font color! + //formatting for B5: # ??/100 gets lost during import + + //test Sheet2 + const ScPatternAttr* pPattern = pDoc->GetPattern(0, 0, 1); + vcl::Font aFont; + model::ComplexColor aComplexColor; + + pPattern->fillFontOnly(aFont); + pPattern->fillColor(aComplexColor, ScAutoFontColorMode::Raw); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font size should be 10", tools::Long(200), aFont.GetFontSize().getHeight()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font color should be black", COL_AUTO, aComplexColor.getFinalColor()); + pPattern = pDoc->GetPattern(0,1,1); + pPattern->fillFontOnly(aFont); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font size should be 12", tools::Long(240), aFont.GetFontSize().getHeight()); + pPattern = pDoc->GetPattern(0,2,1); + pPattern->fillFontOnly(aFont); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be italic", ITALIC_NORMAL, aFont.GetItalic()); + pPattern = pDoc->GetPattern(0,4,1); + pPattern->fillFontOnly(aFont); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be bold", WEIGHT_BOLD, aFont.GetWeight()); + pPattern = pDoc->GetPattern(1,0,1); + pPattern->fillFontOnly(aFont); + pPattern->fillColor(aComplexColor, ScAutoFontColorMode::Raw); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be blue", COL_BLUE, aComplexColor.getFinalColor()); + pPattern = pDoc->GetPattern(1,1,1); + pPattern->fillFontOnly(aFont); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be striked out with a single line", STRIKEOUT_SINGLE, aFont.GetStrikeout()); + //some tests on sheet2 only for ods + if (sFormat == u"calc8") + { + pPattern = pDoc->GetPattern(1,2,1); + pPattern->fillFontOnly(aFont); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be striked out with a double line", STRIKEOUT_DOUBLE, aFont.GetStrikeout()); + pPattern = pDoc->GetPattern(1,3,1); + pPattern->fillFontOnly(aFont); + CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be underlined with a dotted line", LINESTYLE_DOTTED, aFont.GetUnderline()); + //check row height import + //disable for now until we figure out cause of win tinderboxes test failures + //CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(256), pDoc->GetRowHeight(0,1) ); //0.178in + //CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(304), pDoc->GetRowHeight(1,1) ); //0.211in + //CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(477), pDoc->GetRowHeight(5,1) ); //0.3311in + //check column width import + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(555), pDoc->GetColWidth(4,1) ); //0.3854in + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(1280), pDoc->GetColWidth(5,1) ); //0.889in + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(4153), pDoc->GetColWidth(6,1) ); //2.8839in + //test case for i53253 where a cell has text with different styles and space between the text. + OUString aTestStr = pDoc->GetString(3,0,1); + OUString aKnownGoodStr("text14 space"); + CPPUNIT_ASSERT_EQUAL( aKnownGoodStr, aTestStr ); + //test case for cell text with line breaks. + aTestStr = pDoc->GetString(3,5,1); + aKnownGoodStr = "Hello,\nCalc!"; + CPPUNIT_ASSERT_EQUAL( aKnownGoodStr, aTestStr ); + } + pPattern = pDoc->GetPattern(1,4,1); + Color aColor = pPattern->GetItem(ATTR_BACKGROUND).GetColor(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("background color should be green", COL_LIGHTGREEN, aColor); + pPattern = pDoc->GetPattern(2,0,1); + SvxCellHorJustify eHorJustify = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("cell content should be aligned centre horizontally", SvxCellHorJustify::Center, eHorJustify); + //test alignment + pPattern = pDoc->GetPattern(2,1,1); + eHorJustify = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("cell content should be aligned right horizontally", SvxCellHorJustify::Right, eHorJustify); + pPattern = pDoc->GetPattern(2,2,1); + eHorJustify = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("cell content should be aligned block horizontally", SvxCellHorJustify::Block, eHorJustify); + + //test Sheet3 only for ods and xlsx + if ( sFormat == u"calc8" || sFormat == u"Calc Office Open XML" ) + { + aCSVFileName = createFilePath(u"contentCSV/conditionalFormatting.csv"); + testCondFile(aCSVFileName, pDoc, 2); + // test parent cell style import ( fdo#55198 ) + if ( sFormat == u"Calc Office Open XML" ) + { + pPattern = pDoc->GetPattern(1,1,3); + ScStyleSheet* pStyleSheet = const_cast<ScStyleSheet*>(pPattern->GetStyleSheet()); + // check parent style name + OUString sExpected("Excel Built-in Date"); + OUString sResult = pStyleSheet->GetName(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("parent style for Sheet4.B2 is 'Excel Built-in Date'", sExpected, sResult); + // check align of style + SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + eHorJustify = rItemSet.Get( ATTR_HOR_JUSTIFY ).GetValue(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("'Excel Built-in Date' style should be aligned centre horizontally", SvxCellHorJustify::Center, eHorJustify); + // check date format ( should be just month e.g. 29 ) + sResult =pDoc->GetString( 1,1,3 ); + sExpected = "29"; + CPPUNIT_ASSERT_EQUAL_MESSAGE("'Excel Built-in Date' style should just display month", sExpected, sResult ); + + // check actual align applied to cell, should be the same as + // the style + eHorJustify = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("cell with 'Excel Built-in Date' style should be aligned centre horizontally", SvxCellHorJustify::Center, eHorJustify); + } + } + + ScConditionalFormat* pCondFormat = pDoc->GetCondFormat(0,0,2); + const ScRangeList& rRange = pCondFormat->GetRange(); + CPPUNIT_ASSERT_EQUAL(ScRangeList(ScRange(0,0,2,3,0,2)), rRange); + + pCondFormat = pDoc->GetCondFormat(0,1,2); + const ScRangeList& rRange2 = pCondFormat->GetRange(); + CPPUNIT_ASSERT_EQUAL(ScRangeList(ScRange(0,1,2,0,1,2)), rRange2); + + pCondFormat = pDoc->GetCondFormat(1,1,2); + const ScRangeList& rRange3 = pCondFormat->GetRange(); + CPPUNIT_ASSERT_EQUAL(ScRangeList(ScRange(1,1,2,3,1,2)), rRange3); +} + +void ScModelTestBase::goToCell(const OUString& rCell) +{ + uno::Sequence<beans::PropertyValue> aArgs + = comphelper::InitPropertySequence({ { "ToPoint", uno::Any(rCell) } }); + dispatchCommand(mxComponent, ".uno:GoToCell", aArgs); +} + +void ScModelTestBase::typeString(const std::u16string_view& rStr) +{ + ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent); + for (const char16_t c : rStr) + { + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, c, 0); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, c, 0); + Scheduler::ProcessEventsToIdle(); + } +} + +void ScModelTestBase::insertStringToCell(const OUString& rCell, const std::u16string_view& rStr) +{ + goToCell(rCell); + + typeString(rStr); + + ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN); + Scheduler::ProcessEventsToIdle(); +} + +void ScModelTestBase::insertArrayToCell(const OUString& rCell, const std::u16string_view& rStr) +{ + goToCell(rCell); + + typeString(rStr); + + ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_MOD1 | KEY_SHIFT | awt::Key::RETURN); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_MOD1 | KEY_SHIFT | awt::Key::RETURN); + Scheduler::ProcessEventsToIdle(); +} + +void ScModelTestBase::insertNewSheet(ScDocument& rDoc) +{ + sal_Int32 nTabs = static_cast<sal_Int32>(rDoc.GetTableCount()); + + uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence( + { { "Name", uno::Any(OUString("NewTab")) }, { "Index", uno::Any(nTabs + 1) } })); + dispatchCommand(mxComponent, ".uno:Insert", aArgs); + + CPPUNIT_ASSERT_EQUAL(static_cast<SCTAB>(nTabs + 1), rDoc.GetTableCount()); +} + +void ScModelTestBase::executeAutoSum() +{ + dispatchCommand(mxComponent, ".uno:AutoSum", {}); + + // Use RETURN key to exit autosum edit view + ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN); + Scheduler::ProcessEventsToIdle(); +} + +const SdrOle2Obj* ScModelTestBase::getSingleOleObject(ScDocument& rDoc, sal_uInt16 nPage) +{ + // Retrieve the chart object instance from the 2nd page (for the 2nd sheet). + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + if (!pDrawLayer) + { + cout << "Failed to retrieve the drawing layer object." << endl; + return nullptr; + } + + const SdrPage* pPage = pDrawLayer->GetPage(nPage); + if (!pPage) + { + cout << "Failed to retrieve the page object." << endl; + return nullptr; + } + + if (pPage->GetObjCount() != 1) + { + cout << "This page should contain one drawing object." << endl; + return nullptr; + } + + const SdrObject* pObj = pPage->GetObj(0); + if (!pObj) + { + cout << "Failed to retrieve the drawing object." << endl; + return nullptr; + } + + if (pObj->GetObjIdentifier() != SdrObjKind::OLE2) + { + cout << "This is not an OLE2 object." << endl; + return nullptr; + } + + return static_cast<const SdrOle2Obj*>(pObj); +} + +const SdrOle2Obj* ScModelTestBase::getSingleChartObject(ScDocument& rDoc, sal_uInt16 nPage) +{ + const SdrOle2Obj* pObj = getSingleOleObject(rDoc, nPage); + + if (!pObj) + return pObj; + + if (!pObj->IsChart()) + { + cout << "This should be a chart object." << endl; + return nullptr; + } + + return pObj; +} + +static std::vector<OUString> getChartRangeRepresentations(const SdrOle2Obj& rChartObj) +{ + std::vector<OUString> aRangeReps; + + // Make sure the chart object has correct range references. + Reference<frame::XModel> xModel = rChartObj.getXModel(); + if (!xModel.is()) + { + cout << "Failed to get the embedded object interface." << endl; + return aRangeReps; + } + + Reference<chart2::XChartDocument> xChartDoc(xModel, UNO_QUERY); + if (!xChartDoc.is()) + { + cout << "Failed to get the chart document interface." << endl; + return aRangeReps; + } + + Reference<chart2::data::XDataSource> xDataSource(xChartDoc, UNO_QUERY); + if (!xDataSource.is()) + { + cout << "Failed to get the data source interface." << endl; + return aRangeReps; + } + + Sequence<Reference<chart2::data::XLabeledDataSequence> > xDataSeqs = xDataSource->getDataSequences(); + if (!xDataSeqs.hasElements()) + { + cout << "There should be at least one data sequences." << endl; + return aRangeReps; + } + + Reference<chart2::data::XDataReceiver> xDataRec(xChartDoc, UNO_QUERY); + if (!xDataRec.is()) + { + cout << "Failed to get the data receiver interface." << endl; + return aRangeReps; + } + + Sequence<OUString> aRangeRepSeqs = xDataRec->getUsedRangeRepresentations(); + comphelper::sequenceToContainer(aRangeReps, aRangeRepSeqs); + + return aRangeReps; +} + +ScRangeList ScModelTestBase::getChartRanges(ScDocument& rDoc, const SdrOle2Obj& rChartObj) +{ + std::vector<OUString> aRangeReps = getChartRangeRepresentations(rChartObj); + ScRangeList aRanges; + for (size_t i = 0, n = aRangeReps.size(); i < n; ++i) + { + ScRange aRange; + ScRefFlags nRes = aRange.Parse(aRangeReps[i], rDoc, rDoc.GetAddressConvention()); + if (nRes & ScRefFlags::VALID) + // This is a range address. + aRanges.push_back(aRange); + else + { + // Parse it as a single cell address. + ScAddress aAddr; + nRes = aAddr.Parse(aRangeReps[i], rDoc, rDoc.GetAddressConvention()); + CPPUNIT_ASSERT_MESSAGE("Failed to parse a range representation.", (nRes & ScRefFlags::VALID)); + aRanges.push_back(aAddr); + } + } + + return aRanges; +} + +bool checkOutput( + const ScDocument* pDoc, const ScRange& aOutRange, + const std::vector<std::vector<const char*>>& aCheck, const char* pCaption ) +{ + bool bResult = true; + const ScAddress& s = aOutRange.aStart; + const ScAddress& e = aOutRange.aEnd; + svl::GridPrinter printer(e.Row() - s.Row() + 1, e.Col() - s.Col() + 1, CALC_DEBUG_OUTPUT != 0); + SCROW nOutRowSize = e.Row() - s.Row() + 1; + SCCOL nOutColSize = e.Col() - s.Col() + 1; + + // Check if expected size iz smaller than actual size (and prevent a crash) + if (aCheck.size() < o3tl::make_unsigned(nOutRowSize) || aCheck[0].size() < o3tl::make_unsigned(nOutColSize)) + { + // Dump the arrays to console, so we can compare + std::cout << "Expected data:" << std::endl; + for (size_t nRow = 0; nRow < aCheck.size(); ++nRow) + { + for (size_t nCol = 0; nCol < aCheck[nRow].size(); ++nCol) + { + const char* p = aCheck[nRow][nCol]; + if (p) + { + OUString aCheckVal = OUString::createFromAscii(p); + std::cout << "'" << aCheckVal << "', "; + } + else + std::cout << "null, "; + } + std::cout << std::endl; + } + + std::cout << "Actual data:" << std::endl; + for (SCROW nRow = 0; nRow < nOutRowSize; ++nRow) + { + for (SCCOL nCol = 0; nCol < nOutColSize; ++nCol) + { + OUString aVal = pDoc->GetString(nCol + s.Col(), nRow + s.Row(), s.Tab()); + std::cout << "'" << aVal << "', "; + } + std::cout << std::endl; + } + std::cout << std::endl; + + return false; + } + + for (SCROW nRow = 0; nRow < nOutRowSize; ++nRow) + { + for (SCCOL nCol = 0; nCol < nOutColSize; ++nCol) + { + OUString aVal = pDoc->GetString(nCol + s.Col(), nRow + s.Row(), s.Tab()); + printer.set(nRow, nCol, aVal); + const char* p = aCheck[nRow][nCol]; + if (p) + { + OUString aCheckVal = OUString::createFromAscii(p); + bool bEqual = aCheckVal == aVal; + if (!bEqual) + { + std::cout << "Expected: " << aCheckVal << " Actual: " << aVal << std::endl; + bResult = false; + } + } + else if (!aVal.isEmpty()) + { + std::cout << "Empty cell expected" << std::endl; + bResult = false; + } + } + } + printer.print(pCaption); + return bResult; +} + +void ScUcalcTestBase::setUp() +{ + BootstrapFixture::setUp(); + + ScDLL::Init(); + + m_xDocShell + = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS + | SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + m_xDocShell->DoInitUnitTest(); + + m_pDoc = &m_xDocShell->GetDocument(); +} + +void ScUcalcTestBase::tearDown() +{ + m_xDocShell->DoClose(); + m_xDocShell.clear(); + + test::BootstrapFixture::tearDown(); +} + +void ScModelTestBase::createScDoc(const char* pName, const char* pPassword, bool bCheckWarningError) +{ + if (!pName) + load("private:factory/scalc"); + else + loadFromFile(OUString::createFromAscii(pName), pPassword); + + uno::Reference<lang::XServiceInfo> xServiceInfo(mxComponent, uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.sheet.SpreadsheetDocument")); + + if (bCheckWarningError) + CPPUNIT_ASSERT(!getScDocShell()->GetMedium()->GetWarningError()); +} + +ScDocument* ScModelTestBase::getScDoc() +{ + ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent); + CPPUNIT_ASSERT(pModelObj); + return pModelObj->GetDocument(); +} + +ScDocument* ScModelTestBase::getScDoc2() +{ + ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent2); + CPPUNIT_ASSERT(pModelObj); + return pModelObj->GetDocument(); +} + +ScDocShell* ScModelTestBase::getScDocShell() +{ + SfxObjectShell* pFoundShell = SfxObjectShell::GetShellFromComponent(mxComponent); + CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell); + ScDocShell* pDocSh = dynamic_cast<ScDocShell*>(pFoundShell); + CPPUNIT_ASSERT(pDocSh); + return pDocSh; +} + +ScTabViewShell* ScModelTestBase::getViewShell() +{ + ScDocShell* pDocSh = getScDocShell(); + ScTabViewShell* pTabViewShell = pDocSh->GetBestViewShell(false); + CPPUNIT_ASSERT_MESSAGE("No ScTabViewShell", pTabViewShell); + return pTabViewShell; +} + +void ScModelTestBase::miscRowHeightsTest( TestParam const * aTestValues, unsigned int numElems) +{ + for ( unsigned int index=0; index<numElems; ++index ) + { + const std::u16string_view sFileName = aTestValues[ index ].sTestDoc; + const OUString sExportType = aTestValues[ index ].sExportType; + loadFromFile(sFileName); + + if ( !sExportType.isEmpty() ) + saveAndReload(sExportType); + + ScDocument* pDoc = getScDoc(); + + for (int i=0; i<aTestValues[ index ].nRowData; ++i) + { + SCROW nRow = aTestValues[ index ].pData[ i].nStartRow; + SCROW nEndRow = aTestValues[ index ].pData[ i ].nEndRow; + SCTAB nTab = aTestValues[ index ].pData[ i ].nTab; + int nExpectedHeight = aTestValues[ index ].pData[ i ].nExpectedHeight; + if ( nExpectedHeight == -1 ) + nExpectedHeight = convertTwipToMm100(ScGlobal::GetStandardRowHeight()); + bool bCheckOpt = ( ( aTestValues[ index ].pData[ i ].nCheck & CHECK_OPTIMAL ) == CHECK_OPTIMAL ); + for ( ; nRow <= nEndRow; ++nRow ) + { + SAL_INFO( "sc.qa", " checking row " << nRow << " for height " << nExpectedHeight ); + int nHeight = convertTwipToMm100(pDoc->GetRowHeight(nRow, nTab, false)); + if ( bCheckOpt ) + { + bool bOpt = !(pDoc->GetRowFlags( nRow, nTab ) & CRFlags::ManualSize); + CPPUNIT_ASSERT_EQUAL(aTestValues[ index ].pData[ i ].bOptimal, bOpt); + } + CPPUNIT_ASSERT_EQUAL(nExpectedHeight, nHeight); + } + } + } +} + +void ScModelTestBase::enableOpenCL() +{ + /** + * Turn on OpenCL group interpreter. Call this after the document is + * loaded and before performing formula calculation. + */ + sc::FormulaGroupInterpreter::enableOpenCL_UnitTestsOnly(); +} + +void ScModelTestBase::disableOpenCL() +{ + sc::FormulaGroupInterpreter::disableOpenCL_UnitTestsOnly(); +} + +void ScModelTestBase::initTestEnv(std::u16string_view fileName) +{ + // Some documents contain macros, disable them, otherwise + // the "Error, BASIC runtime error." dialog is prompted + // and it crashes in tearDown + std::vector<beans::PropertyValue> args; + beans::PropertyValue aMacroValue; + aMacroValue.Name = "MacroExecutionMode"; + aMacroValue.Handle = -1; + aMacroValue.Value <<= document::MacroExecMode::NEVER_EXECUTE; + aMacroValue.State = beans::PropertyState_DIRECT_VALUE; + args.push_back(aMacroValue); + + disableOpenCL(); + CPPUNIT_ASSERT(!ScCalcConfig::isOpenCLEnabled()); + + // Open the document with OpenCL disabled + mxComponent = mxDesktop->loadComponentFromURL( + createFileURL(fileName), "_default", 0, comphelper::containerToSequence(args)); + + enableOpenCL(); + CPPUNIT_ASSERT(ScCalcConfig::isOpenCLEnabled()); + + // it's not possible to open the same document twice, thus, create a temp file + createTempCopy(fileName); + + // Open the document with OpenCL enabled + mxComponent2 = mxDesktop->loadComponentFromURL( + maTempFile.GetURL(), "_default", 0, comphelper::containerToSequence(args)); + + // Check there are 2 documents + uno::Reference<frame::XFrames> xFrames = mxDesktop->getFrames(); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xFrames->getCount()); +} + +ScRange ScUcalcTestBase::insertRangeData( + ScDocument* pDoc, const ScAddress& rPos, const std::vector<std::vector<const char*>>& rData ) +{ + if (rData.empty()) + return ScRange(ScAddress::INITIALIZE_INVALID); + + ScAddress aPos = rPos; + + SCCOL nColWidth = 1; + for (const std::vector<const char*>& rRow : rData) + nColWidth = std::max<SCCOL>(nColWidth, rRow.size()); + + ScRange aRange(aPos); + aRange.aEnd.IncCol(nColWidth-1); + aRange.aEnd.IncRow(rData.size()-1); + + clearRange(pDoc, aRange); + + for (const std::vector<const char*>& rRow : rData) + { + aPos.SetCol(rPos.Col()); + + for (const char* pStr : rRow) + { + if (!pStr) + { + aPos.IncCol(); + continue; + } + + OUString aStr(pStr, strlen(pStr), RTL_TEXTENCODING_UTF8); + + ScSetStringParam aParam; // Leave default. + aParam.meStartListening = sc::NoListening; + pDoc->SetString(aPos, aStr, &aParam); + + aPos.IncCol(); + } + + aPos.IncRow(); + } + + pDoc->StartAllListeners(aRange); + printRange(pDoc, aRange, "Range data content"); + return aRange; +} + +ScUndoCut* ScUcalcTestBase::cutToClip(ScDocShell& rDocSh, const ScRange& rRange, ScDocument* pClipDoc, bool bCreateUndo) +{ + ScDocument* pSrcDoc = &rDocSh.GetDocument(); + + ScClipParam aClipParam(rRange, true); + ScMarkData aMark(pSrcDoc->GetSheetLimits()); + aMark.SetMarkArea(rRange); + pSrcDoc->CopyToClip(aClipParam, pClipDoc, &aMark, false, false); + + // Taken from ScViewFunc::CutToClip() + ScDocumentUniquePtr pUndoDoc; + if (bCreateUndo) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndoSelected( *pSrcDoc, aMark ); + // all sheets - CopyToDocument skips those that don't exist in pUndoDoc + ScRange aCopyRange = rRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(pSrcDoc->GetTableCount()-1); + pSrcDoc->CopyToDocument( aCopyRange, + (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS, + false, *pUndoDoc ); + } + + aMark.MarkToMulti(); + pSrcDoc->DeleteSelection( InsertDeleteFlags::ALL, aMark ); + aMark.MarkToSimple(); + + if (pUndoDoc) + return new ScUndoCut( &rDocSh, rRange, rRange.aEnd, aMark, std::move(pUndoDoc) ); + + return nullptr; +} + +void ScUcalcTestBase::copyToClip(ScDocument* pSrcDoc, const ScRange& rRange, ScDocument* pClipDoc) +{ + ScClipParam aClipParam(rRange, false); + ScMarkData aMark(pSrcDoc->GetSheetLimits()); + aMark.SetMarkArea(rRange); + pSrcDoc->CopyToClip(aClipParam, pClipDoc, &aMark, false, false); +} + +void ScUcalcTestBase::pasteFromClip(ScDocument* pDestDoc, const ScRange& rDestRange, ScDocument* pClipDoc) +{ + ScMarkData aMark(pDestDoc->GetSheetLimits()); + aMark.SetMarkArea(rDestRange); + pDestDoc->CopyFromClip(rDestRange, aMark, InsertDeleteFlags::ALL, nullptr, pClipDoc); +} + +ScUndoPaste* ScUcalcTestBase::createUndoPaste(ScDocShell& rDocSh, const ScRange& rRange, ScDocumentUniquePtr pUndoDoc) +{ + ScDocument& rDoc = rDocSh.GetDocument(); + ScMarkData aMarkData(rDoc.GetSheetLimits()); + aMarkData.SetMarkArea(rRange); + std::unique_ptr<ScRefUndoData> pRefUndoData(new ScRefUndoData(&rDoc)); + + return new ScUndoPaste( + &rDocSh, rRange, aMarkData, std::move(pUndoDoc), nullptr, InsertDeleteFlags::ALL, std::move(pRefUndoData), false); +} + +void ScUcalcTestBase::pasteOneCellFromClip(ScDocument* pDestDoc, const ScRange& rDestRange, ScDocument* pClipDoc, InsertDeleteFlags eFlags) +{ + ScMarkData aMark(pDestDoc->GetSheetLimits()); + aMark.SetMarkArea(rDestRange); + sc::CopyFromClipContext aCxt(*pDestDoc, nullptr, pClipDoc, eFlags, false, false); + aCxt.setDestRange(rDestRange.aStart.Col(), rDestRange.aStart.Row(), + rDestRange.aEnd.Col(), rDestRange.aEnd.Row()); + aCxt.setTabRange(rDestRange.aStart.Tab(), rDestRange.aEnd.Tab()); + pDestDoc->CopyOneCellFromClip(aCxt, rDestRange.aStart.Col(), rDestRange.aStart.Row(), + rDestRange.aEnd.Col(), rDestRange.aEnd.Row()); +} + +void ScUcalcTestBase::setCalcAsShown(ScDocument* pDoc, bool bCalcAsShown) +{ + ScDocOptions aOpt = pDoc->GetDocOptions(); + aOpt.SetCalcAsShown(bCalcAsShown); + pDoc->SetDocOptions(aOpt); +} + +ScDocShell* ScUcalcTestBase::findLoadedDocShellByName(std::u16string_view rName) +{ + ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false)); + while (pShell) + { + SfxMedium* pMedium = pShell->GetMedium(); + if (pMedium) + { + OUString aName = pMedium->GetName(); + if (aName == rName) + return pShell; + } + pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false)); + } + return nullptr; +} + +bool ScUcalcTestBase::insertRangeNames( + ScDocument* pDoc, ScRangeName* pNames, const RangeNameDef* p, const RangeNameDef* pEnd) +{ + ScAddress aA1(0, 0, 0); + for (; p != pEnd; ++p) + { + ScRangeData* pNew = new ScRangeData( + *pDoc, + OUString::createFromAscii(p->mpName), + OUString::createFromAscii(p->mpExpr), + aA1, ScRangeData::Type::Name, + formula::FormulaGrammar::GRAM_ENGLISH); + pNew->SetIndex(p->mnIndex); + bool bSuccess = pNames->insert(pNew); + if (!bSuccess) + { + cerr << "Insertion failed." << endl; + return false; + } + } + + return true; +} + +OUString ScUcalcTestBase::getRangeByName(ScDocument* pDoc, const OUString& aRangeName) +{ + ScRangeData* pName = pDoc->GetRangeName()->findByUpperName(aRangeName.toAsciiUpperCase()); + CPPUNIT_ASSERT(pName); + return pName->GetSymbol(pDoc->GetGrammar()); +} + +#if CALC_DEBUG_OUTPUT != 0 +void ScUcalcTestBase::printFormula(ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, const char* pCaption) +{ + if (pCaption != nullptr) + cout << pCaption << ", "; + cout << nCol << "/" << nRow << ": " << pDoc->GetFormula(nCol, nRow, nTab); + cout << endl; +} +#else +// Avoid unused parameter warning +void ScUcalcTestBase::printFormula(ScDocument*, SCCOL, SCROW, SCTAB, const char*) {} +#endif + +void ScUcalcTestBase::printRange(ScDocument* pDoc, const ScRange& rRange, const char* pCaption, + const bool printFormula) +{ + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); + svl::GridPrinter printer(nRow2 - nRow1 + 1, nCol2 - nCol1 + 1, CALC_DEBUG_OUTPUT != 0); + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + ScAddress aPos(nCol, nRow, rRange.aStart.Tab()); + ScRefCellValue aCell(*pDoc, aPos); + OUString aVal = printFormula ? pDoc->GetFormula(nCol, nRow, rRange.aStart.Tab()) + : ScCellFormat::GetOutputString(*pDoc, aPos, aCell); + printer.set(nRow - nRow1, nCol - nCol1, aVal); + } + } + printer.print(pCaption); +} + +void ScUcalcTestBase::printRange(ScDocument* pDoc, const ScRange& rRange, const OString& rCaption, + const bool printFormula) +{ + printRange(pDoc, rRange, rCaption.getStr(), printFormula); +} + +void ScUcalcTestBase::clearRange(ScDocument* pDoc, const ScRange& rRange) +{ + ScMarkData aMarkData(pDoc->GetSheetLimits()); + aMarkData.SetMarkArea(rRange); + pDoc->DeleteArea( + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), aMarkData, InsertDeleteFlags::CONTENTS); +} + +void ScUcalcTestBase::clearSheet(ScDocument* pDoc, SCTAB nTab) +{ + ScRange aRange(0,0,nTab,pDoc->MaxCol(),pDoc->MaxRow(),nTab); + clearRange(pDoc, aRange); +} + +bool ScUcalcTestBase::checkFormulaPosition(ScDocument& rDoc, const ScAddress& rPos) +{ + OUString aStr(rPos.Format(ScRefFlags::VALID)); + const ScFormulaCell* pFC = rDoc.GetFormulaCell(rPos); + if (!pFC) + { + cerr << "Formula cell expected at " << aStr << " but not found." << endl; + return false; + } + + if (pFC->aPos != rPos) + { + OUString aStr2(pFC->aPos.Format(ScRefFlags::VALID)); + cerr << "Formula cell at " << aStr << " has incorrect position of " << aStr2 << endl; + return false; + } + + return true; +} + +bool ScUcalcTestBase::checkFormulaPositions( + ScDocument& rDoc, SCTAB nTab, SCCOL nCol, const SCROW* pRows, size_t nRowCount) +{ + ScAddress aPos(nCol, 0, nTab); + for (size_t i = 0; i < nRowCount; ++i) + { + SCROW nRow = pRows[i]; + aPos.SetRow(nRow); + + if (!checkFormulaPosition(rDoc, aPos)) + { + OUString aStr(aPos.Format(ScRefFlags::VALID)); + cerr << "Formula cell position failed at " << aStr << "." << endl; + return false; + } + } + return true; +} + +std::unique_ptr<ScTokenArray> ScUcalcTestBase::compileFormula( + ScDocument* pDoc, const OUString& rFormula, + formula::FormulaGrammar::Grammar eGram ) +{ + ScAddress aPos(0,0,0); + ScCompiler aComp(*pDoc, aPos, eGram); + return aComp.CompileString(rFormula); +} + +void ScUcalcTestBase::clearFormulaCellChangedFlag( ScDocument& rDoc, const ScRange& rRange ) +{ + const ScAddress& s = rRange.aStart; + const ScAddress& e = rRange.aEnd; + for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab) + { + for (SCCOL nCol = s.Col(); nCol <= e.Col(); ++nCol) + { + for (SCROW nRow = s.Row(); nRow <= e.Row(); ++nRow) + { + ScAddress aPos(nCol, nRow, nTab); + ScFormulaCell* pFC = rDoc.GetFormulaCell(aPos); + if (pFC) + pFC->SetChanged(false); + } + } + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/qahelper.hxx b/sc/qa/unit/helper/qahelper.hxx new file mode 100644 index 0000000000..4b95451c96 --- /dev/null +++ b/sc/qa/unit/helper/qahelper.hxx @@ -0,0 +1,215 @@ +/* -*- 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/. + */ + +#pragma once + +#include <docsh.hxx> +#include <address.hxx> + +#include <cppunit/SourceLine.h> + +#include <test/unoapixml_test.hxx> +#include <test/bootstrapfixture.hxx> +#include <comphelper/documentconstants.hxx> + +#include <comphelper/fileformat.h> +#include <formula/grammar.hxx> +#include "scqahelperdllapi.h" + +#include <string> +#include <string_view> +#include <sstream> +#include <undoblk.hxx> + +#include <sal/types.h> + +#include <memory> +#include <tuple> + +enum class StringType { PureString, StringValue }; + +#define CHECK_OPTIMAL 0x1 + +class SdrOle2Obj; +class ScRangeList; +class ScTokenArray; + +// data format for row height tests +struct TestParam +{ + struct RowData + { + SCROW nStartRow; + SCROW nEndRow; + SCTAB nTab; + int nExpectedHeight; // -1 for default height + int nCheck; // currently only CHECK_OPTIMAL ( we could add CHECK_MANUAL etc.) + bool bOptimal; + }; + const std::u16string_view sTestDoc; + const OUString sExportType; // empty for import test, otherwise this is an export test + int nRowData; + RowData const * pData; +}; + +struct RangeNameDef +{ + const char* mpName; + const char* mpExpr; + sal_uInt16 mnIndex; +}; + +// Printers for the calc data structures. Needed for the EQUAL assertion +// macros from CPPUNIT. + +SCQAHELPER_DLLPUBLIC std::ostream& operator<<(std::ostream& rStrm, const ScAddress& rAddr); + +SCQAHELPER_DLLPUBLIC std::ostream& operator<<(std::ostream& rStrm, const ScRange& rRange); + +SCQAHELPER_DLLPUBLIC std::ostream& operator<<(std::ostream& rStrm, const ScRangeList& rList); + +SCQAHELPER_DLLPUBLIC std::ostream& operator<<(std::ostream& rStrm, const OpCode& rCode); + +SCQAHELPER_DLLPUBLIC bool checkOutput( + const ScDocument* pDoc, const ScRange& aOutRange, + const std::vector<std::vector<const char*>>& aCheck, const char* pCaption ); + +inline std::string print(const ScAddress& rAddr) +{ + std::ostringstream str; + str << "Col: " << rAddr.Col(); + str << " Row: " << rAddr.Row(); + str << " Tab: " << rAddr.Tab(); + return str.str(); +} + +/** + * Temporarily set formula grammar. + */ +class SCQAHELPER_DLLPUBLIC FormulaGrammarSwitch +{ + ScDocument* mpDoc; + formula::FormulaGrammar::Grammar meOldGrammar; + +public: + FormulaGrammarSwitch(ScDocument* pDoc, formula::FormulaGrammar::Grammar eGrammar); + ~FormulaGrammarSwitch(); +}; + +class SCQAHELPER_DLLPUBLIC ScUcalcTestBase : public test::BootstrapFixture +{ +public: + virtual void setUp() override; + virtual void tearDown() override; + + ScRange insertRangeData(ScDocument* pDoc, const ScAddress& rPos, + const std::vector<std::vector<const char*>>& rData); + void copyToClip(ScDocument* pSrcDoc, const ScRange& rRange, ScDocument* pClipDoc); + void pasteFromClip(ScDocument* pDestDoc, const ScRange& rDestRange, + ScDocument* pClipDoc); + ScUndoPaste* createUndoPaste(ScDocShell& rDocSh, const ScRange& rRange, + ScDocumentUniquePtr pUndoDoc); + void pasteOneCellFromClip(ScDocument* pDestDoc, const ScRange& rDestRange, + ScDocument* pClipDoc, + InsertDeleteFlags eFlags = InsertDeleteFlags::ALL); + void setCalcAsShown(ScDocument* pDoc, bool bCalcAsShown); + ScDocShell* findLoadedDocShellByName(std::u16string_view rName); + ScUndoCut* cutToClip(ScDocShell& rDocSh, const ScRange& rRange, ScDocument* pClipDoc, + bool bCreateUndo); + bool insertRangeNames(ScDocument* pDoc, ScRangeName* pNames, const RangeNameDef* p, + const RangeNameDef* pEnd); + OUString getRangeByName(ScDocument* pDoc, const OUString& aRangeName); + void printFormula(ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, + const char* pCaption = nullptr); + void printRange(ScDocument* pDoc, const ScRange& rRange, const char* pCaption, + const bool printFormula = false); + void printRange(ScDocument* pDoc, const ScRange& rRange, + const OString& rCaption, const bool printFormula = false); + void clearRange(ScDocument* pDoc, const ScRange& rRange); + void clearSheet(ScDocument* pDoc, SCTAB nTab); + bool checkFormulaPosition(ScDocument& rDoc, const ScAddress& rPos); + bool checkFormulaPositions( + ScDocument& rDoc, SCTAB nTab, SCCOL nCol, const SCROW* pRows, size_t nRowCount); + std::unique_ptr<ScTokenArray> compileFormula( + ScDocument* pDoc, const OUString& rFormula, + formula::FormulaGrammar::Grammar eGram = formula::FormulaGrammar::GRAM_NATIVE ); + void clearFormulaCellChangedFlag( ScDocument& rDoc, const ScRange& rRange ); + +protected: + ScDocShellRef m_xDocShell; + ScDocument* m_pDoc; +}; + +class SCQAHELPER_DLLPUBLIC ScModelTestBase : public UnoApiXmlTest +{ +public: + ScModelTestBase(OUString path) + : UnoApiXmlTest(path) + { + } + + void createScDoc(const char* pName = nullptr, const char* pPassword = nullptr, bool bCheckErrorCode = true); + ScDocument* getScDoc(); + ScDocument* getScDoc2(); + ScDocShell* getScDocShell(); + ScTabViewShell* getViewShell(); + void miscRowHeightsTest( TestParam const * aTestValues, unsigned int numElems); + + void enableOpenCL(); + void disableOpenCL(); + void initTestEnv(std::u16string_view fileName); + + void testFile(const OUString& aFileName, ScDocument& rDoc, SCTAB nTab, StringType aStringFormat = StringType::StringValue); + + //need own handler because conditional formatting strings must be generated + void testCondFile(const OUString& aFileName, ScDocument* pDoc, SCTAB nTab, bool bCommaAsDelimiter = true); + + const SdrOle2Obj* getSingleOleObject(ScDocument& rDoc, sal_uInt16 nPage); + + const SdrOle2Obj* getSingleChartObject(ScDocument& rDoc, sal_uInt16 nPage); + + ScRangeList getChartRanges(ScDocument& rDoc, const SdrOle2Obj& rChartObj); + + void testFormats(ScDocument* pDoc,std::u16string_view sFormat); + + void goToCell(const OUString& rCell); + void typeString(const std::u16string_view& rStr); + void insertStringToCell(const OUString& rCell, const std::u16string_view& rStr); + void insertArrayToCell(const OUString& rCell, const std::u16string_view& rStr); + void insertNewSheet(ScDocument& rDoc); + void executeAutoSum(); + +private: + // Why is this here and not in osl, and using the already existing file + // handling APIs? Do we really want to add arbitrary new file handling + // wrappers here and there (and then having to handle the Android (and + // eventually perhaps iOS) special cases here, too)? Please move this to osl, + // it sure looks generally useful. Or am I missing something? + + void loadFile(const OUString& aFileName, std::string& aContent); +}; + +#define ASSERT_DOUBLES_EQUAL( expected, result ) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL( (expected), (result), 1e-14 ) + +#define ASSERT_DOUBLES_EQUAL_MESSAGE( message, expected, result ) \ + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( (message), (expected), (result), 1e-14 ) + +inline void assertPointEqual( + const Point& rExpected, const Point& rActual, const sal_Int32 nTolerance, + const CppUnit::SourceLine& rSourceLine ) +{ + CPPUNIT_NS::assertDoubleEquals( rExpected.X(), rActual.X(), nTolerance, rSourceLine, "different X" ); + CPPUNIT_NS::assertDoubleEquals( rExpected.Y(), rActual.Y(), nTolerance, rSourceLine, "different Y" ); +} + +#define CPPUNIT_ASSERT_POINT_EQUAL_WITH_TOLERANCE(aExpected, aActual, aTolerance) \ + assertPointEqual( aExpected, aActual, aTolerance, CPPUNIT_SOURCELINE() ) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/scfiltertestbase.cxx b/sc/qa/unit/helper/scfiltertestbase.cxx new file mode 100644 index 0000000000..0e8f4525ef --- /dev/null +++ b/sc/qa/unit/helper/scfiltertestbase.cxx @@ -0,0 +1,67 @@ +/* -*- 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 "scfiltertestbase.hxx" + +#include <sfx2/docfilt.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/intitem.hxx> + +#include <com/sun/star/document/MacroExecMode.hpp> + +using namespace com::sun::star; +using namespace ::com::sun::star::uno; + +ScDocShellRef ScFilterTestBase::loadDoc(const OUString& rURL, const OUString& rFilter, + const OUString& rUserData, const OUString& rTypeName, + SfxFilterFlags nFilterFlags, + SotClipboardFormatId nClipboardID, sal_Int32 nFilterVersion) +{ + auto pFilter + = std::make_shared<SfxFilter>(rFilter, OUString(), nFilterFlags, nClipboardID, rTypeName, + OUString(), rUserData, "private:factory/scalc"); + pFilter->SetVersion(nFilterVersion); + + ScDocShellRef xDocShRef = new ScDocShell; + xDocShRef->GetDocument().EnableUserInteraction(false); + SfxMedium* pSrcMed = new SfxMedium(rURL, StreamMode::STD_READ); + pSrcMed->SetFilter(pFilter); + pSrcMed->UseInteractionHandler(false); + pSrcMed->GetItemSet().Put( + SfxUInt16Item(SID_MACROEXECMODE, css::document::MacroExecMode::ALWAYS_EXECUTE_NO_WARN)); + SAL_INFO("sc.qa", "about to load " << rURL); + if (!xDocShRef->DoLoad(pSrcMed)) + { + xDocShRef->DoClose(); + // load failed. + xDocShRef.clear(); + } + + return xDocShRef; +} + +void ScFilterTestBase::setUp() +{ + test::BootstrapFixture::setUp(); + + // This is a bit of a fudge, we do this to ensure that ScGlobals::ensure, + // which is a private symbol to us, gets called + m_xCalcComponent + = getMultiServiceFactory()->createInstance("com.sun.star.comp.Calc.SpreadsheetDocument"); + CPPUNIT_ASSERT_MESSAGE("no calc component!", m_xCalcComponent.is()); +} + +void ScFilterTestBase::tearDown() +{ + uno::Reference<lang::XComponent>(m_xCalcComponent, UNO_QUERY_THROW)->dispose(); + test::BootstrapFixture::tearDown(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/scfiltertestbase.hxx b/sc/qa/unit/helper/scfiltertestbase.hxx new file mode 100644 index 0000000000..a0a12e47e2 --- /dev/null +++ b/sc/qa/unit/helper/scfiltertestbase.hxx @@ -0,0 +1,68 @@ +/* -*- 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/. + */ + +#pragma once + +#include <unotest/filters-test.hxx> +#include <test/bootstrapfixture.hxx> + +#include <docsh.hxx> +#include <address.hxx> + +#include <comphelper/fileformat.h> +#include "scqahelperdllapi.h" + +#define ODS_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::TEMPLATE \ + | SfxFilterFlags::OWN | SfxFilterFlags::DEFAULT | SfxFilterFlags::ENCRYPTION \ + | SfxFilterFlags::PASSWORDTOMODIFY) +#define XLS_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::ALIEN \ + | SfxFilterFlags::ENCRYPTION | SfxFilterFlags::PASSWORDTOMODIFY | SfxFilterFlags::PREFERED) +#define XLSX_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::ALIEN \ + | SfxFilterFlags::STARONEFILTER | SfxFilterFlags::PREFERED) +#define LOTUS123_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::ALIEN | SfxFilterFlags::PREFERED) +#define CSV_FORMAT_TYPE (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::ALIEN) +#define HTML_FORMAT_TYPE (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::ALIEN) +#define DIF_FORMAT_TYPE (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::ALIEN) +#define XLS_XML_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::ALIEN | SfxFilterFlags::PREFERED) +#define XLSB_XML_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::ALIEN | SfxFilterFlags::STARONEFILTER \ + | SfxFilterFlags::PREFERED) +#define FODS_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::OWN \ + | SfxFilterFlags::STARONEFILTER) +#define GNUMERIC_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::ALIEN | SfxFilterFlags::PREFERED) +#define XLTX_FORMAT_TYPE \ + (SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::TEMPLATE \ + | SfxFilterFlags::ALIEN | SfxFilterFlags::STARONEFILTER | SfxFilterFlags::PREFERED) + +class SCQAHELPER_DLLPUBLIC ScFilterTestBase : public test::BootstrapFixture, + public test::FiltersTest +{ +protected: + ScDocShellRef loadDoc(const OUString& rURL, const OUString& rFilter, const OUString& rUserData, + const OUString& rTypeName, SfxFilterFlags nFilterFlags, + SotClipboardFormatId nClipboardID, + sal_Int32 nFilterVersion = SOFFICE_FILEFORMAT_CURRENT); + +private: + // reference to document interface that we are testing + css::uno::Reference<css::uno::XInterface> m_xCalcComponent; + +public: + virtual void setUp() override; + virtual void tearDown() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/scqahelperdllapi.h b/sc/qa/unit/helper/scqahelperdllapi.h new file mode 100644 index 0000000000..3b72847214 --- /dev/null +++ b/sc/qa/unit/helper/scqahelperdllapi.h @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/types.h> + +#if defined(SCQAHELPER_DLLIMPLEMENTATION) +#define SCQAHELPER_DLLPUBLIC SAL_DLLPUBLIC_EXPORT +#else +#define SCQAHELPER_DLLPUBLIC SAL_DLLPUBLIC_IMPORT +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/shared_test_impl.hxx b/sc/qa/unit/helper/shared_test_impl.hxx new file mode 100644 index 0000000000..4d792cd378 --- /dev/null +++ b/sc/qa/unit/helper/shared_test_impl.hxx @@ -0,0 +1,298 @@ +/* -*- 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/. + */ + +#pragma once + +#include <memory> +#include <colorscale.hxx> +#include <conditio.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include "qahelper.hxx" +#include <formula/errorcodes.hxx> + +struct FindCondFormatByEnclosingRange +{ + explicit FindCondFormatByEnclosingRange(const ScRange& rRange): + mrRange(rRange) {} + + bool operator()(const std::unique_ptr<ScConditionalFormat>& pFormat) + { + if (pFormat->GetRange().Combine() == mrRange) + return true; + + return false; + } + +private: + const ScRange& mrRange; +}; + +struct DataBarData +{ + ScRange aRange; + ScColorScaleEntryType eLowerLimitType; + ScColorScaleEntryType eUpperLimitType; + databar::ScAxisPosition eAxisPosition; +}; + +DataBarData const aData[] = { + { ScRange(1,2,0,1,5,0), COLORSCALE_AUTO, COLORSCALE_AUTO, databar::AUTOMATIC }, + { ScRange(3,2,0,3,5,0), COLORSCALE_MIN, COLORSCALE_MAX, databar::AUTOMATIC }, + { ScRange(5,2,0,5,5,0), COLORSCALE_PERCENTILE, COLORSCALE_PERCENT, databar::AUTOMATIC }, + { ScRange(7,2,0,7,5,0), COLORSCALE_VALUE, COLORSCALE_FORMULA, databar::AUTOMATIC }, + { ScRange(1,9,0,1,12,0), COLORSCALE_AUTO, COLORSCALE_AUTO, databar::MIDDLE } +}; + +void testDataBar_Impl(const ScDocument& rDoc) +{ + ScConditionalFormatList* pList = rDoc.GetCondFormList(0); + CPPUNIT_ASSERT(pList); + + for(size_t i = 0; i < std::size(aData); ++i) + { + ScConditionalFormatList::const_iterator itr = std::find_if(pList->begin(), + pList->end(), FindCondFormatByEnclosingRange(aData[i].aRange)); + CPPUNIT_ASSERT(itr != pList->end()); + CPPUNIT_ASSERT_EQUAL(size_t(1), (*itr)->size()); + + const ScFormatEntry* pFormatEntry = (*itr)->GetEntry(0); + CPPUNIT_ASSERT_EQUAL(ScFormatEntry::Type::Databar, pFormatEntry->GetType()); + const ScDataBarFormat* pDataBar = static_cast<const ScDataBarFormat*>(pFormatEntry); + CPPUNIT_ASSERT(pDataBar); + const ScDataBarFormatData* pDataBarData = pDataBar->GetDataBarData(); + CPPUNIT_ASSERT_EQUAL(aData[i].eLowerLimitType, pDataBarData->mpLowerLimit->GetType()); + CPPUNIT_ASSERT_EQUAL(aData[i].eUpperLimitType, pDataBarData->mpUpperLimit->GetType()); + + CPPUNIT_ASSERT_EQUAL(aData[i].eAxisPosition, pDataBarData->meAxisPosition); + } +} + +struct ColorScale2EntryData +{ + ScRange aRange; + ScColorScaleEntryType eLowerType; + ScColorScaleEntryType eUpperType; +}; + +ColorScale2EntryData const aData2Entry[] = { + { ScRange(1,2,0,1,5,0), COLORSCALE_MIN, COLORSCALE_MAX }, + { ScRange(3,2,0,3,5,0), COLORSCALE_PERCENTILE, COLORSCALE_PERCENT }, + { ScRange(5,2,0,5,5,0), COLORSCALE_VALUE, COLORSCALE_FORMULA } +}; + +void testColorScale2Entry_Impl(const ScDocument& rDoc) +{ + const ScConditionalFormatList* pList = rDoc.GetCondFormList(0); + CPPUNIT_ASSERT(pList); + + for(size_t i = 0; i < std::size(aData2Entry); ++i) + { + ScConditionalFormatList::const_iterator itr = std::find_if(pList->begin(), + pList->end(), FindCondFormatByEnclosingRange(aData2Entry[i].aRange)); + CPPUNIT_ASSERT(itr != pList->end()); + CPPUNIT_ASSERT_EQUAL(size_t(1), (*itr)->size()); + + const ScFormatEntry* pFormatEntry = (*itr)->GetEntry(0); + CPPUNIT_ASSERT_EQUAL(ScFormatEntry::Type::Colorscale, pFormatEntry->GetType()); + const ScColorScaleFormat* pColFormat = static_cast<const ScColorScaleFormat*>(pFormatEntry); + CPPUNIT_ASSERT_EQUAL(size_t(2), pColFormat->size()); + + ScColorScaleEntries::const_iterator format_itr = pColFormat->begin(); + CPPUNIT_ASSERT_EQUAL(aData2Entry[i].eLowerType, (*format_itr)->GetType()); + ++format_itr; + CPPUNIT_ASSERT(format_itr != pColFormat->end()); + CPPUNIT_ASSERT_EQUAL(aData2Entry[i].eUpperType, (*format_itr)->GetType()); + } +} + +struct ColorScale3EntryData +{ + ScRange aRange; + ScColorScaleEntryType eLowerType; + ScColorScaleEntryType eMiddleType; + ScColorScaleEntryType eUpperType; +}; + +ColorScale3EntryData const aData3Entry[] = { + { ScRange(1,1,1,1,6,1), COLORSCALE_MIN, COLORSCALE_PERCENTILE, COLORSCALE_MAX }, + { ScRange(3,1,1,3,6,1), COLORSCALE_PERCENTILE, COLORSCALE_VALUE, COLORSCALE_PERCENT }, + { ScRange(5,1,1,5,6,1), COLORSCALE_VALUE, COLORSCALE_VALUE, COLORSCALE_FORMULA } +}; + +void testColorScale3Entry_Impl(const ScDocument& rDoc) +{ + ScConditionalFormatList* pList = rDoc.GetCondFormList(1); + CPPUNIT_ASSERT(pList); + + for(size_t i = 0; i < std::size(aData3Entry); ++i) + { + ScConditionalFormatList::const_iterator itr = std::find_if(pList->begin(), + pList->end(), FindCondFormatByEnclosingRange(aData3Entry[i].aRange)); + CPPUNIT_ASSERT(itr != pList->end()); + CPPUNIT_ASSERT_EQUAL(size_t(1), (*itr)->size()); + + const ScFormatEntry* pFormatEntry = (*itr)->GetEntry(0); + CPPUNIT_ASSERT_EQUAL(ScFormatEntry::Type::Colorscale, pFormatEntry->GetType()); + const ScColorScaleFormat* pColFormat = static_cast<const ScColorScaleFormat*>(pFormatEntry); + CPPUNIT_ASSERT_EQUAL(size_t(3), pColFormat->size()); + + ScColorScaleEntries::const_iterator format_itr = pColFormat->begin(); + CPPUNIT_ASSERT_EQUAL(aData3Entry[i].eLowerType, (*format_itr)->GetType()); + ++format_itr; + CPPUNIT_ASSERT(format_itr != pColFormat->end()); + CPPUNIT_ASSERT_EQUAL(aData3Entry[i].eMiddleType, (*format_itr)->GetType()); + ++format_itr; + CPPUNIT_ASSERT(format_itr != pColFormat->end()); + CPPUNIT_ASSERT_EQUAL(aData3Entry[i].eUpperType, (*format_itr)->GetType()); + } +} + +bool isFormulaWithoutError(ScDocument& rDoc, const ScAddress& rPos) +{ + ScFormulaCell* pFC = rDoc.GetFormulaCell(rPos); + if (!pFC) + return false; + + return pFC->GetErrCode() == FormulaError::NONE; +} + +void testFunctionsExcel2010_Impl( ScDocument& rDoc ) +{ + // Original test case document is functions-excel-2010.xlsx + // Which test rows to evaluate, 1-based as in UI to ease maintenance. + static struct + { + SCROW nRow; + bool bEvaluate; + } const aTests[] = { + { 2, false }, // name=[ AGGREGATE ], result=0, expected=1 + { 3, true }, + { 4, true }, + { 5, true }, + { 6, true }, + { 7, true }, + { 8, true }, + { 9, true }, + { 10, true }, + { 11, true }, + { 12, true }, + { 13, true }, + { 14, true }, + { 15, true }, + { 16, true }, + { 17, true }, + { 18, true }, + { 19, true }, + { 20, true }, + { 21, true }, + { 22, true }, + { 23, true }, + { 24, true }, + { 25, true }, + { 26, true }, + { 27, true }, + { 28, true }, + { 29, true }, + { 30, true }, + { 31, true }, + { 32, true }, + { 33, true }, + { 34, true }, + { 35, true }, + { 36, true }, + { 37, true }, + { 38, true }, + { 39, true }, + { 40, true }, + { 41, true }, + { 42, true }, + { 43, true }, + { 44, true }, + { 45, false }, // name=[ NETWORKDAYS.INTL ], result=18, expected=19 + { 46, true }, + { 47, true }, + { 48, true }, + { 49, true }, + { 50, true }, + { 51, true }, + { 52, true }, + { 53, true }, + { 54, true }, + { 55, true }, + { 56, true }, + { 57, true }, + { 58, true }, + { 59, true }, + { 60, true }, + { 61, true }, + { 62, true }, + { 63, true }, + { 64, true }, + { 65, true }, + { 66, true }, + { 67, true }, + { 68, true }, + { 69, true }, + { 70, true }, + { 71, true }, + { 72, true }, + { 73, true }, + { 74, true }, + { 75, true }, + { 76, true }, + { 77, true }, + { 78, true }, + { 79, false }, // name=[ WORKDAY.INTL ], result=41755 , expected=41754 + { 80, true } + }; + + for (size_t i=0; i < std::size(aTests); ++i) + { + if (aTests[i].bEvaluate) + { + // Column 0 is description, 1 is formula, 2 is Excel result, 3 is + // comparison. + SCROW nRow = aTests[i].nRow - 1; // 0-based + + OString aStr = OString::number( aTests[i].nRow) + + ", function name=[ " + + OUStringToOString( rDoc.GetString( ScAddress( 0, nRow, 0)), RTL_TEXTENCODING_UTF8 ) + + " ], result=" + + OString::number( rDoc.GetValue( ScAddress( 1, nRow, 0)) ) + + ", expected=" + + OString::number( rDoc.GetValue( ScAddress( 2, nRow, 0)) ); + + ScFormulaCell* pFC = rDoc.GetFormulaCell( ScAddress( 1, nRow, 0) ); + if ( pFC && pFC->GetErrCode() != FormulaError::NONE ) + aStr += ", error code =" + OString::number( static_cast<int>(pFC->GetErrCode()) ); + + CPPUNIT_ASSERT_MESSAGE( OString( "Expected a formula cell without error at row " + + aStr ).getStr(), isFormulaWithoutError( rDoc, ScAddress( 1, nRow, 0))); + CPPUNIT_ASSERT_MESSAGE( OString( "Expected a TRUE value at row " + + aStr ).getStr(), 0 != rDoc.GetValue( ScAddress( 3, nRow, 0))); + + } + } +} + +void testCeilingFloor_Impl( ScDocument& rDoc ) +{ + // Original test case document is ceiling-floor.xlsx + // Sheet1.K1 has =AND(K3:K81) to evaluate all results. + static constexpr OUString pORef = u"Sheet1.K1"_ustr; + ScAddress aPos; + aPos.Parse(pORef, rDoc); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong formula.", OUString("=AND(K3:K81)"), rDoc.GetFormula(aPos.Col(), aPos.Row(), aPos.Tab())); + CPPUNIT_ASSERT_MESSAGE( OUString( pORef + " result is error.").toUtf8().getStr(), + isFormulaWithoutError( rDoc, aPos)); + CPPUNIT_ASSERT_EQUAL(1.0, rDoc.GetValue(aPos)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/helper/sorthelper.hxx b/sc/qa/unit/helper/sorthelper.hxx new file mode 100644 index 0000000000..5a37c29ddf --- /dev/null +++ b/sc/qa/unit/helper/sorthelper.hxx @@ -0,0 +1,53 @@ +/* -*- 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/. + */ + +#pragma once + +// Unfortunately requires linkage to sc/ internals so +// can't live in qahelper itself. +#include <inputopt.hxx> +#include <scmod.hxx> + +/** + * Temporarily set the sorting type. + */ +class SortTypeSetter { + bool mbSortRefUpdate; +public: + explicit SortTypeSetter(bool bSortRefUpdate) + { + mbSortRefUpdate = changeTo(bSortRefUpdate); + } + static bool changeTo(bool bSortRefUpdate) + { + ScInputOptions aInputOptions = SC_MOD()->GetInputOptions(); + bool bRet = aInputOptions.GetSortRefUpdate(); + aInputOptions.SetSortRefUpdate(bSortRefUpdate); + SC_MOD()->SetInputOptions(aInputOptions); + return bRet; + } + virtual ~SortTypeSetter() COVERITY_NOEXCEPT_FALSE + { + changeTo(mbSortRefUpdate); + } +}; + +class SortRefNoUpdateSetter : private SortTypeSetter +{ +public: + SortRefNoUpdateSetter() : SortTypeSetter(false) {} +}; + +class SortRefUpdateSetter : private SortTypeSetter +{ +public: + SortRefUpdateSetter() : SortTypeSetter(true) {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |