Table Styles: Add unit tests for Calc table styles

generation, rendering, and total row

Tests cover: style generation (60 ECMA defaults), fill colors,
border widths/styles, font properties, theme change regeneration,
undo/redo, multiple style categories (Light/Medium/Dark), style
switching, no header/no total row handling, banded columns,
first/last column, tiny table edge cases, and total row toggle
with custom COUNTIF and SUBTOTAL formula preservation.

Also add ooxml export tests for TableStyles and a few test for
custom table styles. (dxf)

Change-Id: I60b78766e35f80f54747e3ef904e8c59c4c1e8ff
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/202926
Reviewed-by: Balazs Varga <balazs.varga@collabora.com>
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/core/+/173
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit aead0fc8bf5484ea36cfcea0b13f3cca2d1a8408)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/204506
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
This commit is contained in:
Balazs Varga
2026-04-29 12:28:11 +02:00
committed by Xisco Fauli
parent 431835ce23
commit e2fcf200b5
9 changed files with 1327 additions and 0 deletions
@@ -0,0 +1,12 @@
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
$(eval $(call sc_subsequent_test,export_test6))
# vim: set noet sw=4 ts=4:
+62
View File
@@ -0,0 +1,62 @@
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
$(eval $(call gb_CppunitTest_CppunitTest,sc_tablestyle_totalrow))
$(eval $(call gb_CppunitTest_add_exception_objects,sc_tablestyle_totalrow, \
sc/qa/unit/ucalc_tablestyle_totalrow \
))
$(eval $(call gb_CppunitTest_use_externals,sc_tablestyle_totalrow, \
boost_headers \
mdds_headers \
libxml2 \
))
$(eval $(call gb_CppunitTest_use_libraries,sc_tablestyle_totalrow, \
comphelper \
cppu \
cppuhelper \
editeng \
i18nlangtag \
sal \
sc \
scqahelper \
sfx \
subsequenttest \
svl \
svx \
svxcore \
test \
tl \
unotest \
utl \
vcl \
))
$(eval $(call gb_CppunitTest_set_include,sc_tablestyle_totalrow,\
-I$(SRCDIR)/sc/source/ui/inc \
-I$(SRCDIR)/sc/inc \
$$(INCLUDE) \
))
$(eval $(call gb_CppunitTest_use_sdk_api,sc_tablestyle_totalrow))
$(eval $(call gb_CppunitTest_use_ure,sc_tablestyle_totalrow))
$(eval $(call gb_CppunitTest_use_vcl,sc_tablestyle_totalrow))
$(eval $(call gb_CppunitTest_use_rdb,sc_tablestyle_totalrow,services))
$(eval $(call gb_CppunitTest_use_configuration,sc_tablestyle_totalrow))
$(eval $(call gb_CppunitTest_add_arguments,sc_tablestyle_totalrow, \
-env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}" \
))
# vim: set noet sw=4 ts=4:
+70
View File
@@ -0,0 +1,70 @@
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
#*************************************************************************
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
#*************************************************************************
$(eval $(call gb_CppunitTest_CppunitTest,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_use_common_precompiled_header,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_add_exception_objects,sc_ucalc_tablestyles, \
sc/qa/unit/ucalc_tablestyles \
))
$(eval $(call gb_CppunitTest_use_externals,sc_ucalc_tablestyles, \
boost_headers \
mdds_headers \
libxml2 \
))
$(eval $(call gb_CppunitTest_use_libraries,sc_ucalc_tablestyles, \
basegfx \
comphelper \
cppu \
cppuhelper \
docmodel \
editeng \
sal \
salhelper \
sax \
sc \
svl \
scqahelper \
sfx \
svxcore \
subsequenttest \
test \
tl \
unotest \
utl \
vcl \
))
$(eval $(call gb_CppunitTest_set_include,sc_ucalc_tablestyles,\
-I$(SRCDIR)/sc/source/ui/inc \
-I$(SRCDIR)/sc/inc \
$$(INCLUDE) \
))
$(eval $(call gb_CppunitTest_use_api,sc_ucalc_tablestyles,\
offapi \
udkapi \
))
$(eval $(call gb_CppunitTest_use_sdk_api,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_use_ure,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_use_vcl,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_use_rdb,sc_ucalc_tablestyles,services))
$(eval $(call gb_CppunitTest_use_components,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_use_configuration,sc_ucalc_tablestyles))
$(eval $(call gb_CppunitTest_add_arguments,sc_ucalc_tablestyles, \
-env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}" \
))
# vim: set noet sw=4 ts=4:
+3
View File
@@ -48,6 +48,8 @@ $(eval $(call gb_Module_add_check_targets,sc,\
CppunitTest_sc_ucalc_copypaste \
CppunitTest_sc_ucalc_datatransformation \
CppunitTest_sc_ucalc_document_themes \
CppunitTest_sc_ucalc_tablestyles \
CppunitTest_sc_tablestyle_totalrow \
CppunitTest_sc_ucalc_formula \
CppunitTest_sc_ucalc_formula2 \
CppunitTest_sc_ucalc_parallelism \
@@ -102,6 +104,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sc, \
CppunitTest_sc_subsequent_export_test3 \
CppunitTest_sc_subsequent_export_test4 \
CppunitTest_sc_subsequent_export_test5 \
CppunitTest_sc_subsequent_export_test6 \
CppunitTest_sc_uicalc \
CppunitTest_sc_uicalc2 \
CppunitTest_sc_vba_macro_test \
Binary file not shown.
Binary file not shown.
+189
View File
@@ -0,0 +1,189 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "helper/qahelper.hxx"
#include <dbdata.hxx>
#include <docsh.hxx>
#include <formulacell.hxx>
#include <tablestyle.hxx>
#include <svtools/sfxecode.hxx>
#include <svl/intitem.hxx>
#include <sfx2/docfile.hxx>
#include <comphelper/sequence.hxx>
using namespace ::com::sun::star;
class ScExportTest6 : public ScModelTestBase
{
public:
ScExportTest6()
: ScModelTestBase(u"sc/qa/unit/data"_ustr)
{
}
};
// --- Table Style OOXML Export Tests ---
CPPUNIT_TEST_FIXTURE(ScExportTest6, testTableStyleDefaultExportXLSX)
{
// Open xlsx with default table style (TableStyleMedium2), save as xlsx, verify roundtrip
createScDoc("xlsx/TableStyleTest.xlsx");
save(TestFilter::XLSX);
xmlDocUniquePtr pTable = parseExport(u"xl/tables/table1.xml"_ustr);
CPPUNIT_ASSERT(pTable);
// Table style name should survive roundtrip
assertXPath(pTable, "/x:table/x:tableStyleInfo", "name", u"TableStyleMedium2");
assertXPath(pTable, "/x:table/x:tableStyleInfo", "showRowStripes", u"1");
assertXPath(pTable, "/x:table/x:tableStyleInfo", "showColumnStripes", u"0");
assertXPath(pTable, "/x:table/x:tableStyleInfo", "showFirstColumn", u"0");
assertXPath(pTable, "/x:table/x:tableStyleInfo", "showLastColumn", u"0");
// Verify table range includes total row
assertXPath(pTable, "/x:table", "ref", u"A1:C10");
assertXPath(pTable, "/x:table", "totalsRowCount", u"1");
// totalsRowShown is unnecessary and should not be exported
assertXPathNoAttribute(pTable, "/x:table", "totalsRowShown");
// Verify column definitions
assertXPath(pTable, "/x:table/x:tableColumns/x:tableColumn[1]", "name", u"A");
assertXPath(pTable, "/x:table/x:tableColumns/x:tableColumn[3]", "name", u"C");
// Total row: Column A should have "Total" label
assertXPath(pTable, "/x:table/x:tableColumns/x:tableColumn[1]", "totalsRowLabel", u"Total");
// Total row: Column B should have custom function (COUNTIF)
assertXPath(pTable, "/x:table/x:tableColumns/x:tableColumn[2]", "totalsRowFunction", u"custom");
assertXPathContent(pTable, "/x:table/x:tableColumns/x:tableColumn[2]/x:totalsRowFormula",
u"COUNTIF(Table2[B],\">5\")");
// Total row: Column C should have average function
assertXPath(pTable, "/x:table/x:tableColumns/x:tableColumn[3]", "totalsRowFunction",
u"average");
}
CPPUNIT_TEST_FIXTURE(ScExportTest6, testTableStyleCustomExportXLSX)
{
// Open xlsx with custom table style, save as xlsx, verify roundtrip
createScDoc("xlsx/Book1_custom.xlsx");
save(TestFilter::XLSX);
// Verify table style info
xmlDocUniquePtr pTable = parseExport(u"xl/tables/table1.xml"_ustr);
CPPUNIT_ASSERT(pTable);
assertXPath(pTable, "/x:table/x:tableStyleInfo", "name", u"Custom_Styles1");
// Verify custom style is exported in styles.xml
xmlDocUniquePtr pStyles = parseExport(u"xl/styles.xml"_ustr);
CPPUNIT_ASSERT(pStyles);
// Custom table style should exist with correct name and 7 elements
assertXPath(pStyles, "/x:styleSheet/x:tableStyles/x:tableStyle", "name", u"Custom_Styles1");
assertXPath(pStyles, "/x:styleSheet/x:tableStyles/x:tableStyle", "count", u"7");
// Verify all 7 table style elements are exported
const char* const sStylePath = "/x:styleSheet/x:tableStyles/x:tableStyle";
assertXPath(pStyles, OString::Concat(sStylePath) + "/x:tableStyleElement[@type='wholeTable']");
assertXPath(pStyles, OString::Concat(sStylePath) + "/x:tableStyleElement[@type='headerRow']");
assertXPath(pStyles, OString::Concat(sStylePath) + "/x:tableStyleElement[@type='totalRow']");
assertXPath(pStyles, OString::Concat(sStylePath) + "/x:tableStyleElement[@type='firstColumn']");
assertXPath(pStyles, OString::Concat(sStylePath) + "/x:tableStyleElement[@type='lastColumn']");
assertXPath(pStyles,
OString::Concat(sStylePath) + "/x:tableStyleElement[@type='firstRowStripe']");
assertXPath(pStyles,
OString::Concat(sStylePath) + "/x:tableStyleElement[@type='firstColumnStripe']");
// Verify DXFs exist for the custom style elements
OUString sDxfCount = getXPath(pStyles, "/x:styleSheet/x:dxfs", "count");
CPPUNIT_ASSERT(sDxfCount.toInt32() >= 7);
// Verify each table style element has a valid dxfId attribute
// (DXF order may change during roundtrip, so we check by element type)
OString sWholeTable = OString::Concat(sStylePath) + "/x:tableStyleElement[@type='wholeTable']";
OString sHeaderRow = OString::Concat(sStylePath) + "/x:tableStyleElement[@type='headerRow']";
OString sTotalRow = OString::Concat(sStylePath) + "/x:tableStyleElement[@type='totalRow']";
// Each element should have a dxfId pointing to a valid DXF
sal_Int32 nWholeTableDxfId = getXPath(pStyles, sWholeTable, "dxfId").toInt32();
sal_Int32 nHeaderRowDxfId = getXPath(pStyles, sHeaderRow, "dxfId").toInt32();
sal_Int32 nTotalRowDxfId = getXPath(pStyles, sTotalRow, "dxfId").toInt32();
CPPUNIT_ASSERT(nWholeTableDxfId >= 0);
CPPUNIT_ASSERT(nHeaderRowDxfId >= 0);
CPPUNIT_ASSERT(nTotalRowDxfId >= 0);
// All dxfIds should be different (each element has its own DXF)
CPPUNIT_ASSERT(nWholeTableDxfId != nHeaderRowDxfId);
CPPUNIT_ASSERT(nWholeTableDxfId != nTotalRowDxfId);
CPPUNIT_ASSERT(nHeaderRowDxfId != nTotalRowDxfId);
// Verify totalRow DXF has double top border (use 1-based index: dxfId + 1)
OString sTotalRowDxf
= "/x:styleSheet/x:dxfs/x:dxf[" + OString::number(nTotalRowDxfId + 1) + "]";
assertXPath(pStyles, sTotalRowDxf + "/x:border/x:top", "style", u"double");
// Verify wholeTable DXF has thin borders
OString sWholeTableDxf
= "/x:styleSheet/x:dxfs/x:dxf[" + OString::number(nWholeTableDxfId + 1) + "]";
assertXPath(pStyles, sWholeTableDxf + "/x:border/x:left", "style", u"thin");
assertXPath(pStyles, sWholeTableDxf + "/x:border/x:top", "style", u"thin");
// Verify firstRowStripe DXF has a fill with theme color
OString sFirstRowStripe
= OString::Concat(sStylePath) + "/x:tableStyleElement[@type='firstRowStripe']";
sal_Int32 nFirstRowStripeDxfId = getXPath(pStyles, sFirstRowStripe, "dxfId").toInt32();
CPPUNIT_ASSERT(nFirstRowStripeDxfId >= 0);
OString sFirstRowStripeDxf
= "/x:styleSheet/x:dxfs/x:dxf[" + OString::number(nFirstRowStripeDxfId + 1) + "]";
assertXPath(pStyles, sFirstRowStripeDxf + "/x:fill/x:patternFill/x:bgColor", "theme", u"9");
}
CPPUNIT_TEST_FIXTURE(ScExportTest6, testTableStyleCustomRoundtripXLSX)
{
// Open xlsx with custom table style, save and reload, verify in-memory state
createScDoc("xlsx/Book1_custom.xlsx");
saveAndReload(TestFilter::XLSX);
ScDocument* pDoc = getScDoc();
CPPUNIT_ASSERT(pDoc);
// Verify custom table style exists in the document
ScTableStyles* pStyles = pDoc->GetTableStyles();
CPPUNIT_ASSERT(pStyles);
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"Custom_Styles1"_ustr) != nullptr);
// Verify DB range has the custom style applied
ScDBData* pDBData = pDoc->GetDBAtArea(0, 0, 0, 3, 10);
CPPUNIT_ASSERT(pDBData);
const ScTableStyleParam* pParam = pDBData->GetTableStyleInfo();
CPPUNIT_ASSERT(pParam);
CPPUNIT_ASSERT_EQUAL(u"Custom_Styles1"_ustr, pParam->maStyleID);
// Verify table style options survived roundtrip
CPPUNIT_ASSERT(pParam->mbRowStripes);
CPPUNIT_ASSERT(!pParam->mbColumnStripes);
CPPUNIT_ASSERT(!pParam->mbFirstColumn);
CPPUNIT_ASSERT(!pParam->mbLastColumn);
// Verify header and total row flags
CPPUNIT_ASSERT(pDBData->HasHeader());
CPPUNIT_ASSERT(pDBData->HasTotals());
// Verify data survived roundtrip
CPPUNIT_ASSERT_EQUAL(u"Names"_ustr, pDoc->GetString(ScAddress(0, 0, 0))); // header
CPPUNIT_ASSERT_EQUAL(u"Summary"_ustr, pDoc->GetString(ScAddress(0, 10, 0))); // total label
CPPUNIT_ASSERT_EQUAL(382.0, pDoc->GetValue(ScAddress(3, 10, 0))); // SUM of Age column
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+161
View File
@@ -0,0 +1,161 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "helper/qahelper.hxx"
#include <dbdata.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <sc.hrc>
#include <tabvwsh.hxx>
#include <undomanager.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>
class ScTableStyleTotalRowTest : public ScModelTestBase
{
public:
ScTableStyleTotalRowTest()
: ScModelTestBase(u"sc/qa/unit/data"_ustr)
{
}
};
namespace
{
ScDBData* findDBData(ScDocument* pDoc, const OUString& rName)
{
ScDBCollection* pColl = pDoc->GetDBCollection();
if (!pColl)
return nullptr;
ScDBData* pData
= pColl->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rName));
return pData;
}
void dispatchDatabaseSettings(ScTabViewShell* pViewShell, const ScDBData* pDBData, bool bTotalRow)
{
const ScTableStyleParam* pStyleInfo = pDBData->GetTableStyleInfo();
CPPUNIT_ASSERT(pStyleInfo);
SfxDispatcher* pDispatcher = pViewShell->GetViewFrame().GetBindings().GetDispatcher();
CPPUNIT_ASSERT(pDispatcher);
ScDatabaseSettingItem aItem(pDBData->HasHeader(), bTotalRow, pStyleInfo->mbFirstColumn,
pStyleInfo->mbLastColumn, pStyleInfo->mbRowStripes,
pStyleInfo->mbColumnStripes, pDBData->HasAutoFilter(),
pStyleInfo->maStyleID);
pDispatcher->ExecuteList(SID_DATABASE_SETTINGS, SfxCallMode::SYNCHRON, { &aItem });
}
} // anonymous namespace
CPPUNIT_TEST_FIXTURE(ScTableStyleTotalRowTest, testTotalRowToggle)
{
// Load test file: Table2 with A1:C10, TableStyleMedium2, totalsRowCount=1
// Headers: A, B, C (row 1)
// Data rows 2-9
// Total row 10: "Total" in A10, SUBTOTAL(101,Table2[C]) in C10
createScDoc("xlsx/TableStyleTest.xlsx");
ScDocument* pDoc = getScDoc();
CPPUNIT_ASSERT(pDoc);
ScTabViewShell* pViewShell = getViewShell();
CPPUNIT_ASSERT(pViewShell);
// Move cursor into the table area
goToCell(u"A2"_ustr);
// --- Verify initial state: total row exists ---
CPPUNIT_ASSERT_EQUAL(u"Total"_ustr, pDoc->GetString(ScAddress(0, 9, 0)));
// B10 should have custom COUNTIF formula result = 3
CPPUNIT_ASSERT_EQUAL(3.0, pDoc->GetValue(ScAddress(1, 9, 0)));
// C10 should have SUBTOTAL formula result (average of C2:C9) with number format
double fAvgValue = pDoc->GetValue(ScAddress(2, 9, 0));
CPPUNIT_ASSERT(fAvgValue != 0.0);
// Verify the number format (Hungarian Forint "Ft") is preserved after import
OUString aFormattedValue = pDoc->GetString(ScAddress(2, 9, 0));
CPPUNIT_ASSERT_EQUAL(u" 2.75 Ft "_ustr, aFormattedValue);
ScDBData* pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
CPPUNIT_ASSERT(pDBData->HasTotals());
// --- Toggle total row OFF ---
dispatchDatabaseSettings(pViewShell, pDBData, false);
// Refresh DB data pointer (may have been replaced)
pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
CPPUNIT_ASSERT(!pDBData->HasTotals());
// Last data row should still have data
CPPUNIT_ASSERT(pDoc->GetValue(ScAddress(0, 8, 0)) != 0.0);
// --- Toggle total row ON ---
dispatchDatabaseSettings(pViewShell, pDBData, true);
pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
CPPUNIT_ASSERT(pDBData->HasTotals());
// Total row should be restored
CPPUNIT_ASSERT_EQUAL(u"Total"_ustr, pDoc->GetString(ScAddress(0, 9, 0)));
// B10 custom COUNTIF formula should be restored
CPPUNIT_ASSERT_EQUAL(3.0, pDoc->GetValue(ScAddress(1, 9, 0)));
// SUBTOTAL formula in C10 should also be restored with same value
double fRestoredValue = pDoc->GetValue(ScAddress(2, 9, 0));
CPPUNIT_ASSERT_DOUBLES_EQUAL(fAvgValue, fRestoredValue, 0.0);
// TODO: number format (e.g. "Ft" currency) on total row cells is lost
// after toggle off/on cycle — this is a known limitation to fix later.
// CPPUNIT_ASSERT_EQUAL(u" 2.75 Ft "_ustr, pDoc->GetString(ScAddress(2, 9, 0)));
}
CPPUNIT_TEST_FIXTURE(ScTableStyleTotalRowTest, testTotalRowUndoRedo)
{
createScDoc("xlsx/TableStyleTest.xlsx");
ScDocument* pDoc = getScDoc();
ScTabViewShell* pViewShell = getViewShell();
goToCell(u"A2"_ustr);
// Initial: total row exists
CPPUNIT_ASSERT_EQUAL(u"Total"_ustr, pDoc->GetString(ScAddress(0, 9, 0)));
double fOrigValue = pDoc->GetValue(ScAddress(2, 9, 0));
ScDBData* pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
// Toggle total row OFF
dispatchDatabaseSettings(pViewShell, pDBData, false);
pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
CPPUNIT_ASSERT(!pDBData->HasTotals());
// --- Undo: should restore total row ---
pDoc->GetUndoManager()->Undo();
pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
CPPUNIT_ASSERT(pDBData->HasTotals());
CPPUNIT_ASSERT_EQUAL(u"Total"_ustr, pDoc->GetString(ScAddress(0, 9, 0)));
CPPUNIT_ASSERT_DOUBLES_EQUAL(fOrigValue, pDoc->GetValue(ScAddress(2, 9, 0)), 0.0);
// --- Redo: should remove total row again ---
pDoc->GetUndoManager()->Redo();
pDBData = findDBData(pDoc, u"Table2"_ustr);
CPPUNIT_ASSERT(pDBData);
CPPUNIT_ASSERT(!pDBData->HasTotals());
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+830
View File
@@ -0,0 +1,830 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "helper/qahelper.hxx"
#include <TableStyleGenerator.hxx>
#include <tablestyle.hxx>
#include <dbdata.hxx>
#include <docsh.hxx>
#include <document.hxx>
#include <scitems.hxx>
#include <patattr.hxx>
#include <undomanager.hxx>
#include <docmodel/theme/Theme.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/borderline.hxx>
#include <editeng/wghtitem.hxx>
#include <svx/svdpage.hxx>
#include <ThemeColorChanger.hxx>
using namespace css;
class TableStylesTest : public ScUcalcTestBase
{
};
namespace
{
// Theme A: distinct primary accent colors for easy verification
std::shared_ptr<model::ColorSet> createTestThemeA()
{
auto pColorSet = std::make_shared<model::ColorSet>("ThemeA");
pColorSet->add(model::ThemeColorType::Dark1, 0x000000);
pColorSet->add(model::ThemeColorType::Light1, 0xFFFFFF);
pColorSet->add(model::ThemeColorType::Dark2, 0x444444);
pColorSet->add(model::ThemeColorType::Light2, 0xEEEEEE);
pColorSet->add(model::ThemeColorType::Accent1, 0xFF0000); // red
pColorSet->add(model::ThemeColorType::Accent2, 0x00FF00); // green
pColorSet->add(model::ThemeColorType::Accent3, 0x0000FF); // blue
pColorSet->add(model::ThemeColorType::Accent4, 0xFFFF00); // yellow
pColorSet->add(model::ThemeColorType::Accent5, 0xFF00FF); // magenta
pColorSet->add(model::ThemeColorType::Accent6, 0x00FFFF); // cyan
pColorSet->add(model::ThemeColorType::Hyperlink, 0x0563C1);
pColorSet->add(model::ThemeColorType::FollowedHyperlink, 0x954F72);
return pColorSet;
}
// Theme B: swapped accents for theme change verification
std::shared_ptr<model::ColorSet> createTestThemeB()
{
auto pColorSet = std::make_shared<model::ColorSet>("ThemeB");
pColorSet->add(model::ThemeColorType::Dark1, 0x000000);
pColorSet->add(model::ThemeColorType::Light1, 0xFFFFFF);
pColorSet->add(model::ThemeColorType::Dark2, 0x333333);
pColorSet->add(model::ThemeColorType::Light2, 0xDDDDDD);
pColorSet->add(model::ThemeColorType::Accent1, 0x0000FF); // blue (was red)
pColorSet->add(model::ThemeColorType::Accent2, 0xFF0000); // red (was green)
pColorSet->add(model::ThemeColorType::Accent3, 0x00FF00); // green (was blue)
pColorSet->add(model::ThemeColorType::Accent4, 0x00FFFF); // cyan (was yellow)
pColorSet->add(model::ThemeColorType::Accent5, 0xFFFF00); // yellow (was magenta)
pColorSet->add(model::ThemeColorType::Accent6, 0xFF00FF); // magenta (was cyan)
pColorSet->add(model::ThemeColorType::Hyperlink, 0x0563C1);
pColorSet->add(model::ThemeColorType::FollowedHyperlink, 0x954F72);
return pColorSet;
}
// Set up the document theme with the given ColorSet on the draw layer
void applyThemeToDocument(ScDocument* pDoc, std::shared_ptr<model::ColorSet> pColorSet)
{
ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer();
CPPUNIT_ASSERT(pDrawLayer);
auto const& pTheme = pDrawLayer->getTheme();
CPPUNIT_ASSERT(pTheme);
pTheme->setColorSet(pColorSet);
}
// Create a ScDBData with table style assigned, range A1:D10 with header and total rows
ScDBData* createTestDBData(ScDocument* pDoc, const OUString& rStyleName)
{
// range: A1:D11 (row 0 = header, rows 1-9 = data, row 10 = total)
ScDBData* pDBData = new ScDBData(u"TestTable"_ustr, 0, 0, 0, 3, 10, true, true, true);
ScTableStyleParam aStyleParam;
aStyleParam.maStyleID = rStyleName;
aStyleParam.mbRowStripes = true;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbFirstColumn = false;
aStyleParam.mbLastColumn = false;
pDBData->SetTableStyleInfo(aStyleParam);
bool bInserted
= pDoc->GetDBCollection()->getNamedDBs().insert(std::unique_ptr<ScDBData>(pDBData));
CPPUNIT_ASSERT(bInserted);
return pDBData;
}
} // anonymous namespace
// Test 1: Verify 60 default styles are generated with correct names
CPPUNIT_TEST_FIXTURE(TableStylesTest, testGenerateDefaultStyles)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
ScTableStyles* pStyles = m_pDoc->GetTableStyles();
CPPUNIT_ASSERT(pStyles);
// Verify some key style names from each category
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleLight1"_ustr));
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleLight21"_ustr));
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleMedium1"_ustr));
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleMedium2"_ustr));
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleMedium28"_ustr));
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleDark1"_ustr));
CPPUNIT_ASSERT(pStyles->GetTableStyle(u"TableStyleDark11"_ustr));
// Verify non-existent style returns null
CPPUNIT_ASSERT(!pStyles->GetTableStyle(u"NonExistentStyle"_ustr));
// All styles should be marked as OOXML defaults
const ScTableStyle* pStyle = pStyles->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
CPPUNIT_ASSERT(pStyle->IsOOXMLDefault());
m_pDoc->DeleteTab(0);
}
// Test 2: Verify fill colors are correctly resolved from theme
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleFillColors)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
ScDBData* pDBData = createTestDBData(m_pDoc, u"TableStyleMedium2"_ustr);
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Header row (row 0) should have a solid fill based on Accent1 (red = 0xFF0000)
const SvxBrushItem* pHeaderFill = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pHeaderFill);
Color aHeaderColor = pHeaderFill->GetColor();
// Header fill for Medium2 uses Accent1 (theme index 4) — should be red-based
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), aHeaderColor.GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), aHeaderColor.GetGreen());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), aHeaderColor.GetBlue());
// First row stripe (row 1, rowIndex 0) should have a tinted fill
const SvxBrushItem* pStripeFill = pStyle->GetFillItem(*pDBData, 0, 1, 0);
if (pStripeFill)
{
Color aStripeColor = pStripeFill->GetColor();
// Tinted Accent1 — should still be reddish but lighter
CPPUNIT_ASSERT(aStripeColor.GetRed() > aStripeColor.GetGreen());
CPPUNIT_ASSERT(aStripeColor.GetRed() > aStripeColor.GetBlue());
}
// Second row stripe (row 2, rowIndex 1) — Medium2 has no fill for second stripe
const SvxBrushItem* pStripe2Fill = pStyle->GetFillItem(*pDBData, 0, 2, 1);
// Either null or transparent/white
if (pStripe2Fill)
{
Color aStripe2Color = pStripe2Fill->GetColor();
CPPUNIT_ASSERT(aStripe2Color == COL_WHITE || aStripe2Color == COL_TRANSPARENT);
}
m_pDoc->DeleteTab(0);
}
// Test 3: Verify border widths and styles
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleBorders)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
ScDBData* pDBData = createTestDBData(m_pDoc, u"TableStyleMedium2"_ustr);
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Data cell (row 5, middle of table) — should have thin borders from WholeTable
std::unique_ptr<SvxBoxItem> pBoxItem = pStyle->GetBoxItem(*pDBData, 1, 5, 4);
CPPUNIT_ASSERT(pBoxItem);
// Top and bottom borders should be thin (SvxBorderLineWidth::Thin = 15)
const editeng::SvxBorderLine* pTopLine = pBoxItem->GetLine(SvxBoxItemLine::TOP);
CPPUNIT_ASSERT(pTopLine);
CPPUNIT_ASSERT_EQUAL(tools::Long(15), pTopLine->GetWidth());
const editeng::SvxBorderLine* pBotLine = pBoxItem->GetLine(SvxBoxItemLine::BOTTOM);
CPPUNIT_ASSERT(pBotLine);
CPPUNIT_ASSERT_EQUAL(tools::Long(15), pBotLine->GetWidth());
// Border color should be Accent1-tinted (reddish)
CPPUNIT_ASSERT(pTopLine->GetColor().GetRed() > pTopLine->GetColor().GetGreen());
// Total row (row 10, rowIndex 9) — should have a DOUBLE top border
std::unique_ptr<SvxBoxItem> pTotalBox = pStyle->GetBoxItem(*pDBData, 1, 10, 9);
CPPUNIT_ASSERT(pTotalBox);
const editeng::SvxBorderLine* pTotalTop = pTotalBox->GetLine(SvxBoxItemLine::TOP);
CPPUNIT_ASSERT(pTotalTop);
CPPUNIT_ASSERT_EQUAL(SvxBorderLineStyle::DOUBLE_THIN, pTotalTop->GetBorderLineStyle());
m_pDoc->DeleteTab(0);
}
// Test 4: Verify font properties (bold, color)
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleFonts)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
ScDBData* pDBData = createTestDBData(m_pDoc, u"TableStyleMedium2"_ustr);
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Header row should have bold white font
const SfxItemSet* pHeaderFont = pStyle->GetFontItemSet(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pHeaderFont);
// Check bold
const SvxWeightItem* pWeight = pHeaderFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
CPPUNIT_ASSERT(pWeight);
CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, pWeight->GetWeight());
// Check white font color
const SvxColorItem* pColorItem = pHeaderFont->GetItemIfSet(ATTR_FONT_COLOR, false);
CPPUNIT_ASSERT(pColorItem);
CPPUNIT_ASSERT_EQUAL(COL_WHITE, pColorItem->GetValue());
// Total row should have bold font
const SfxItemSet* pTotalFont = pStyle->GetFontItemSet(*pDBData, 0, 10, 9);
CPPUNIT_ASSERT(pTotalFont);
const SvxWeightItem* pTotalWeight = pTotalFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
CPPUNIT_ASSERT(pTotalWeight);
CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, pTotalWeight->GetWeight());
// Data row should have no font override
const SfxItemSet* pDataFont = pStyle->GetFontItemSet(*pDBData, 0, 5, 4);
// Either null or empty — data rows typically have no font styling
if (pDataFont)
{
const SvxWeightItem* pDataWeight = pDataFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
// If present, should not be bold
if (pDataWeight)
CPPUNIT_ASSERT(pDataWeight->GetWeight() != WEIGHT_BOLD);
}
m_pDoc->DeleteTab(0);
}
// Test 5: Verify styles regenerate with new theme colors
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleThemeChange)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
// Start with Theme A (Accent1 = red)
auto pColorSetA = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSetA);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSetA);
ScDBData* pDBData = createTestDBData(m_pDoc, u"TableStyleMedium2"_ustr);
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Verify header fill is red-based (Accent1 = 0xFF0000)
const SvxBrushItem* pFillA = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFillA);
Color aColorA = pFillA->GetColor();
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), aColorA.GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), aColorA.GetBlue());
// Apply Theme B (Accent1 = blue) via ThemeColorChanger
auto pColorSetB = createTestThemeB();
sc::ThemeColorChanger aChanger(*m_xDocShell);
aChanger.apply(pColorSetB);
// Re-fetch style pointer (regenerated)
pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Verify header fill is now blue-based (Accent1 = 0x0000FF)
const SvxBrushItem* pFillB = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFillB);
Color aColorB = pFillB->GetColor();
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), aColorB.GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), aColorB.GetBlue());
// Border colors should also have changed
std::unique_ptr<SvxBoxItem> pBoxB = pStyle->GetBoxItem(*pDBData, 1, 5, 4);
CPPUNIT_ASSERT(pBoxB);
const editeng::SvxBorderLine* pLine = pBoxB->GetLine(SvxBoxItemLine::TOP);
CPPUNIT_ASSERT(pLine);
// Should be blue-based now
CPPUNIT_ASSERT(pLine->GetColor().GetBlue() > pLine->GetColor().GetRed());
m_pDoc->DeleteTab(0);
}
// Test 6: Verify undo/redo restores correct table style colors
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleThemeChangeUndoRedo)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
// Start with Theme A (Accent1 = red)
auto pColorSetA = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSetA);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSetA);
ScDBData* pDBData = createTestDBData(m_pDoc, u"TableStyleMedium2"_ustr);
// Verify initial state: red
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
const SvxBrushItem* pFill = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFill);
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), pFill->GetColor().GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pFill->GetColor().GetBlue());
// Apply Theme B (Accent1 = blue)
auto pColorSetB = createTestThemeB();
sc::ThemeColorChanger aChanger(*m_xDocShell);
aChanger.apply(pColorSetB);
// Verify: blue
pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
pFill = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFill);
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pFill->GetColor().GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), pFill->GetColor().GetBlue());
// Undo → should be red again
m_pDoc->GetUndoManager()->Undo();
pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
pFill = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFill);
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), pFill->GetColor().GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pFill->GetColor().GetBlue());
// Redo → should be blue again
m_pDoc->GetUndoManager()->Redo();
pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
pFill = pStyle->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFill);
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x00), pFill->GetColor().GetRed());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), pFill->GetColor().GetBlue());
m_pDoc->DeleteTab(0);
}
// Test 7: Verify multiple style categories have correct structure
CPPUNIT_TEST_FIXTURE(TableStylesTest, testMultipleTableStyleCategories)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
// range: A1:D11 (row 0 = header, rows 1-9 = data, row 10 = total)
ScDBData* pDBData = new ScDBData(u"TestTable"_ustr, 0, 0, 0, 3, 10, true, true, true);
ScTableStyleParam aStyleParam;
aStyleParam.mbRowStripes = true;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbFirstColumn = false;
aStyleParam.mbLastColumn = false;
ScTableStyles* pStyles = m_pDoc->GetTableStyles();
CPPUNIT_ASSERT(pStyles);
// Test Light style: typically no header fill, thin borders
{
const ScTableStyle* pLight = pStyles->GetTableStyle(u"TableStyleLight1"_ustr);
CPPUNIT_ASSERT(pLight);
aStyleParam.maStyleID = u"TableStyleLight1"_ustr;
pDBData->SetTableStyleInfo(aStyleParam);
// Light1 header row — may or may not have a fill, but should have a font
const SfxItemSet* pHeaderFont = pLight->GetFontItemSet(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pHeaderFont);
const SvxWeightItem* pWeight = pHeaderFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
CPPUNIT_ASSERT(pWeight);
CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, pWeight->GetWeight());
}
// Test Medium styles: various accent-based styles
{
const OUString aMediumStyles[] = { u"TableStyleMedium1"_ustr, u"TableStyleMedium2"_ustr,
u"TableStyleMedium15"_ustr, u"TableStyleMedium28"_ustr };
for (const auto& rName : aMediumStyles)
{
const ScTableStyle* pMedium = pStyles->GetTableStyle(rName);
CPPUNIT_ASSERT_MESSAGE(OString("Style not found: " + rName.toUtf8()).getStr(),
pMedium != nullptr);
aStyleParam.maStyleID = rName;
pDBData->SetTableStyleInfo(aStyleParam);
// Medium styles should have header fill
const SvxBrushItem* pFill = pMedium->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT_MESSAGE(OString("No header fill for: " + rName.toUtf8()).getStr(),
pFill != nullptr);
// Medium styles should have borders on data cells
std::unique_ptr<SvxBoxItem> pBox = pMedium->GetBoxItem(*pDBData, 1, 5, 4);
CPPUNIT_ASSERT_MESSAGE(OString("No borders for: " + rName.toUtf8()).getStr(),
pBox != nullptr);
}
}
// Test Dark styles: dark fill, white font
{
const ScTableStyle* pDark = pStyles->GetTableStyle(u"TableStyleDark1"_ustr);
CPPUNIT_ASSERT(pDark);
aStyleParam.maStyleID = u"TableStyleDark1"_ustr;
pDBData->SetTableStyleInfo(aStyleParam);
// Dark1 header should have a fill
const SvxBrushItem* pFill = pDark->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFill);
// Dark1 header font should be bold and white
const SfxItemSet* pFont = pDark->GetFontItemSet(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFont);
const SvxWeightItem* pWeight = pFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
CPPUNIT_ASSERT(pWeight);
CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, pWeight->GetWeight());
const SvxColorItem* pColor = pFont->GetItemIfSet(ATTR_FONT_COLOR, false);
CPPUNIT_ASSERT(pColor);
CPPUNIT_ASSERT_EQUAL(COL_WHITE, pColor->GetValue());
}
delete pDBData;
m_pDoc->DeleteTab(0);
}
// Test 8: Verify changing table style on a database range
CPPUNIT_TEST_FIXTURE(TableStylesTest, testChangeTableStyle)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
// Create DB range with initial style
ScDBData* pDBData = new ScDBData(u"TestTable"_ustr, 0, 0, 0, 3, 10, true, true, true);
ScTableStyleParam aStyleParam;
aStyleParam.mbRowStripes = true;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbFirstColumn = false;
aStyleParam.mbLastColumn = false;
aStyleParam.maStyleID = u"TableStyleLight1"_ustr;
pDBData->SetTableStyleInfo(aStyleParam);
bool bInserted
= m_pDoc->GetDBCollection()->getNamedDBs().insert(std::unique_ptr<ScDBData>(pDBData));
CPPUNIT_ASSERT(bInserted);
ScTableStyles* pStyles = m_pDoc->GetTableStyles();
CPPUNIT_ASSERT(pStyles);
// --- Style 1: TableStyleLight1 ---
{
const ScTableStyle* pStyle = pStyles->GetTableStyle(u"TableStyleLight1"_ustr);
CPPUNIT_ASSERT(pStyle);
// Light1 header — should have bold font
const SfxItemSet* pFont = pStyle->GetFontItemSet(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFont);
// Remember header fill for comparison
const SvxBrushItem* pFillLight = pStyle->GetFillItem(*pDBData, 0, 0, 0);
// Light styles may or may not have header fill — just record for later comparison
Color aLightHeaderColor = pFillLight ? pFillLight->GetColor() : COL_TRANSPARENT;
// --- Switch to Style 2: TableStyleMedium2 ---
aStyleParam.maStyleID = u"TableStyleMedium2"_ustr;
pDBData->SetTableStyleInfo(aStyleParam);
const ScTableStyle* pMedium = pStyles->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pMedium);
// Medium2 header should have Accent1-based fill (red)
const SvxBrushItem* pFillMedium = pMedium->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFillMedium);
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), pFillMedium->GetColor().GetRed());
// The fill should differ from Light1
CPPUNIT_ASSERT(pFillMedium->GetColor() != aLightHeaderColor);
// --- Switch to Style 3: TableStyleDark1 ---
aStyleParam.maStyleID = u"TableStyleDark1"_ustr;
pDBData->SetTableStyleInfo(aStyleParam);
const ScTableStyle* pDark = pStyles->GetTableStyle(u"TableStyleDark1"_ustr);
CPPUNIT_ASSERT(pDark);
// Dark1 should have white font on header
const SfxItemSet* pDarkFont = pDark->GetFontItemSet(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pDarkFont);
const SvxColorItem* pDarkColor = pDarkFont->GetItemIfSet(ATTR_FONT_COLOR, false);
CPPUNIT_ASSERT(pDarkColor);
CPPUNIT_ASSERT_EQUAL(COL_WHITE, pDarkColor->GetValue());
// Dark1 header fill should differ from Medium2
const SvxBrushItem* pFillDark = pDark->GetFillItem(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pFillDark);
CPPUNIT_ASSERT(pFillDark->GetColor() != pFillMedium->GetColor());
// --- Switch to Style 4: TableStyleLight8 (with column stripes) ---
aStyleParam.maStyleID = u"TableStyleLight8"_ustr;
aStyleParam.mbColumnStripes = true;
aStyleParam.mbRowStripes = false;
pDBData->SetTableStyleInfo(aStyleParam);
const ScTableStyle* pLight8 = pStyles->GetTableStyle(u"TableStyleLight8"_ustr);
CPPUNIT_ASSERT(pLight8);
// Should have valid font on header
const SfxItemSet* pLight8Font = pLight8->GetFontItemSet(*pDBData, 0, 0, 0);
CPPUNIT_ASSERT(pLight8Font);
// --- Switch to no style (empty / "None") ---
aStyleParam.maStyleID = u""_ustr;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbRowStripes = true;
pDBData->SetTableStyleInfo(aStyleParam);
// With empty style ID, GetTableStyle returns null — no crash, no formatting
const ScTableStyle* pNone = pStyles->GetTableStyle(u""_ustr);
CPPUNIT_ASSERT(!pNone);
// Also test "None" explicitly
aStyleParam.maStyleID = u"None"_ustr;
pDBData->SetTableStyleInfo(aStyleParam);
const ScTableStyle* pNone2 = pStyles->GetTableStyle(u"None"_ustr);
// "None" is not a real style — should be null
CPPUNIT_ASSERT(!pNone2);
}
m_pDoc->DeleteTab(0);
}
// Test 9: Verify table style adapts to no header / no total row
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleNoHeaderNoTotal)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// No header, no total: range A1:D10, bHasH=false, bTotals=false
ScDBData aDBNoHeaderNoTotal(u"NoHeaderNoTotal"_ustr, 0, 0, 0, 3, 9, true, false, false);
ScTableStyleParam aStyleParam;
aStyleParam.maStyleID = u"TableStyleMedium2"_ustr;
aStyleParam.mbRowStripes = true;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbFirstColumn = false;
aStyleParam.mbLastColumn = false;
aDBNoHeaderNoTotal.SetTableStyleInfo(aStyleParam);
// Row 0 should be data (first row stripe), NOT header
const SvxBrushItem* pFillRow0 = pStyle->GetFillItem(aDBNoHeaderNoTotal, 0, 0, 0);
// With header, row 0 would be solid Accent1 (0xFF0000).
// Without header, row 0 is first data row — should have first row stripe pattern
if (pFillRow0)
{
// Should NOT be pure Accent1 red (that's the header color)
CPPUNIT_ASSERT(pFillRow0->GetColor() != Color(0xFF0000));
}
// Compare: in a table WITH header, row 1 (first data row) should have the same
// fill as row 0 in a table WITHOUT header — both are first row stripe
ScDBData aDBWithHeader(u"WithHeader"_ustr, 0, 0, 0, 3, 10, true, true, false);
aDBWithHeader.SetTableStyleInfo(aStyleParam);
const SvxBrushItem* pFirstStripeWithHeader = pStyle->GetFillItem(aDBWithHeader, 0, 1, 0);
if (pFillRow0 && pFirstStripeWithHeader)
{
// Both should be the same first row stripe color
CPPUNIT_ASSERT_EQUAL(pFirstStripeWithHeader->GetColor(), pFillRow0->GetColor());
}
// No total row — last row (row 9) is just a data row, not a total row
// No DOUBLE_THIN border
std::unique_ptr<SvxBoxItem> pBoxLast = pStyle->GetBoxItem(aDBNoHeaderNoTotal, 1, 9, 9);
if (pBoxLast)
{
const editeng::SvxBorderLine* pTopLine = pBoxLast->GetLine(SvxBoxItemLine::TOP);
if (pTopLine)
CPPUNIT_ASSERT(pTopLine->GetBorderLineStyle() != SvxBorderLineStyle::DOUBLE_THIN);
}
// No bold font on last row (total row would have bold)
const SfxItemSet* pLastRowFont = pStyle->GetFontItemSet(aDBNoHeaderNoTotal, 0, 9, 9);
if (pLastRowFont)
{
const SvxWeightItem* pWeight = pLastRowFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
if (pWeight)
CPPUNIT_ASSERT(pWeight->GetWeight() != WEIGHT_BOLD);
}
// Last row should have normal stripe fill, same as other data rows
// Row 9 (rowIndex 9) with no header: even index = first stripe, odd = second
const SvxBrushItem* pLastRowFill = pStyle->GetFillItem(aDBNoHeaderNoTotal, 0, 9, 9);
// Compare with another data row at the same stripe position
const SvxBrushItem* pDataRowFill = pStyle->GetFillItem(aDBNoHeaderNoTotal, 0, 1, 1);
if (pLastRowFill && pDataRowFill)
{
// Same stripe position → same color
CPPUNIT_ASSERT_EQUAL(pDataRowFill->GetColor(), pLastRowFill->GetColor());
}
// With header, with total
ScDBData aDBWithBoth(u"WithBoth"_ustr, 0, 0, 0, 3, 10, true, true, true);
aDBWithBoth.SetTableStyleInfo(aStyleParam);
// Row 0 should be header — solid Accent1
const SvxBrushItem* pHeaderFill = pStyle->GetFillItem(aDBWithBoth, 0, 0, 0);
CPPUNIT_ASSERT(pHeaderFill);
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xFF), pHeaderFill->GetColor().GetRed());
// Total row (row 10) should have DOUBLE_THIN top border
std::unique_ptr<SvxBoxItem> pTotalBox = pStyle->GetBoxItem(aDBWithBoth, 1, 10, 9);
CPPUNIT_ASSERT(pTotalBox);
const editeng::SvxBorderLine* pTotalTop = pTotalBox->GetLine(SvxBoxItemLine::TOP);
CPPUNIT_ASSERT(pTotalTop);
CPPUNIT_ASSERT_EQUAL(SvxBorderLineStyle::DOUBLE_THIN, pTotalTop->GetBorderLineStyle());
m_pDoc->DeleteTab(0);
}
// Test 10: Verify banded columns work correctly
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleBandedColumns)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
// Use a style that supports column stripes (Light8 has column stripe patterns)
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleLight8"_ustr);
CPPUNIT_ASSERT(pStyle);
ScDBData aDBData(u"BandedCols"_ustr, 0, 0, 0, 5, 10, true, true, false);
ScTableStyleParam aStyleParam;
aStyleParam.maStyleID = u"TableStyleLight8"_ustr;
aStyleParam.mbRowStripes = false;
aStyleParam.mbColumnStripes = true;
aStyleParam.mbFirstColumn = false;
aStyleParam.mbLastColumn = false;
aDBData.SetTableStyleInfo(aStyleParam);
// With column stripes enabled, alternating columns should have different fills
// Data row (row 1, rowIndex 0): col 0 vs col 1 should differ
const SvxBrushItem* pFillCol0 = pStyle->GetFillItem(aDBData, 0, 1, 0);
const SvxBrushItem* pFillCol1 = pStyle->GetFillItem(aDBData, 1, 1, 0);
// At least one should have a fill for column stripes to be visible
// The two columns should have different styling (one striped, one not)
if (pFillCol0 && pFillCol1)
{
// They should differ — that's what banded columns means
CPPUNIT_ASSERT(pFillCol0->GetColor() != pFillCol1->GetColor());
}
m_pDoc->DeleteTab(0);
}
// Test 11: Verify first/last column special formatting
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleFirstLastColumn)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
// Use a style that has first/last column patterns
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Range A1:D11 with first and last column enabled
ScDBData aDBData(u"FirstLastCol"_ustr, 0, 0, 0, 3, 10, true, true, true);
ScTableStyleParam aStyleParam;
aStyleParam.maStyleID = u"TableStyleMedium2"_ustr;
aStyleParam.mbRowStripes = true;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbFirstColumn = true;
aStyleParam.mbLastColumn = true;
aDBData.SetTableStyleInfo(aStyleParam);
// First column (col 0) data row should have bold font
const SfxItemSet* pFirstColFont = pStyle->GetFontItemSet(aDBData, 0, 5, 4);
if (pFirstColFont)
{
const SvxWeightItem* pWeight = pFirstColFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
if (pWeight)
CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, pWeight->GetWeight());
}
// Last column (col 3) data row should also have bold font
const SfxItemSet* pLastColFont = pStyle->GetFontItemSet(aDBData, 3, 5, 4);
if (pLastColFont)
{
const SvxWeightItem* pWeight = pLastColFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
if (pWeight)
CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, pWeight->GetWeight());
}
// Middle column (col 1) should NOT have bold font from first/last column
const SfxItemSet* pMidColFont = pStyle->GetFontItemSet(aDBData, 1, 5, 4);
if (pMidColFont)
{
const SvxWeightItem* pWeight = pMidColFont->GetItemIfSet(ATTR_FONT_WEIGHT, false);
if (pWeight)
CPPUNIT_ASSERT(pWeight->GetWeight() != WEIGHT_BOLD);
}
m_pDoc->DeleteTab(0);
}
// Test 12: Verify tiny table (single data row) doesn't crash
CPPUNIT_TEST_FIXTURE(TableStylesTest, testTableStyleTinyTable)
{
m_pDoc->InitDrawLayer();
m_pDoc->InsertTab(0, u"Test"_ustr);
auto pColorSet = createTestThemeA();
applyThemeToDocument(m_pDoc, pColorSet);
ScTableStyleGenerator::generateDefaultStyles(*m_pDoc, *pColorSet);
const ScTableStyle* pStyle = m_pDoc->GetTableStyles()->GetTableStyle(u"TableStyleMedium2"_ustr);
CPPUNIT_ASSERT(pStyle);
// Tiny table: A1:B2 — header row + 1 data row, no total
ScDBData aDB1(u"Tiny1"_ustr, 0, 0, 0, 1, 1, true, true, false);
ScTableStyleParam aStyleParam;
aStyleParam.maStyleID = u"TableStyleMedium2"_ustr;
aStyleParam.mbRowStripes = true;
aStyleParam.mbColumnStripes = false;
aStyleParam.mbFirstColumn = false;
aStyleParam.mbLastColumn = false;
aDB1.SetTableStyleInfo(aStyleParam);
// Should not crash — header row 0
const SvxBrushItem* pFill = pStyle->GetFillItem(aDB1, 0, 0, 0);
CPPUNIT_ASSERT(pFill); // header should still have fill
const SfxItemSet* pFont = pStyle->GetFontItemSet(aDB1, 0, 0, 0);
CPPUNIT_ASSERT(pFont);
std::unique_ptr<SvxBoxItem> pBox = pStyle->GetBoxItem(aDB1, 0, 0, 0);
// Border may or may not exist, just verify no crash
// Data row 1
pStyle->GetFillItem(aDB1, 0, 1, 0);
pStyle->GetFontItemSet(aDB1, 0, 1, 0);
pStyle->GetBoxItem(aDB1, 0, 1, 0);
// Single cell table: A1:A1 — just header, no data
ScDBData aDB2(u"Tiny2"_ustr, 0, 0, 0, 0, 0, true, true, false);
aDB2.SetTableStyleInfo(aStyleParam);
// Should not crash
pStyle->GetFillItem(aDB2, 0, 0, 0);
pStyle->GetFontItemSet(aDB2, 0, 0, 0);
pStyle->GetBoxItem(aDB2, 0, 0, 0);
// Header + total, no data rows: A1:B2 with total
ScDBData aDB3(u"Tiny3"_ustr, 0, 0, 0, 1, 1, true, true, true);
aDB3.SetTableStyleInfo(aStyleParam);
// Row 0 = header, row 1 = total — no data rows
pStyle->GetFillItem(aDB3, 0, 0, 0);
pStyle->GetFillItem(aDB3, 0, 1, 0);
pStyle->GetBoxItem(aDB3, 0, 0, 0);
pStyle->GetBoxItem(aDB3, 0, 1, 0);
m_pDoc->DeleteTab(0);
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */