From dee27ddfb6aa780f022098126d216e91faa569fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Fri, 12 Jun 2026 14:58:42 +0900 Subject: [PATCH] Add tests for callables in Calc formulas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These cover the formula behaviour that first-class callables add, and guard the regressions found while bringing the feature in. The tests check that: - an unknown name reports a name error and keeps its text in the stored formula expression - a LET whose bound parameters use the ODFF _xlpm. prefix resolves those parameters and evaluates to the right value - a built-in used as a value and then called returns the same result as calling it directly, with no arguments, with one argument, and with several - a built-in chosen by IF and then called returns the chosen function's value - a call used as an argument to another call evaluates the inner call first and feeds its result to the outer one - a callable used where a number is expected reports an illegal parameter error instead of crashing Signed-off-by: Tomaž Vajngerl Change-Id: Ic7150f32b7db613811669fbc1f1bbe83520f9ae8 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5585 Reviewed-by: Mike Kaganski Tested-by: Tomaž Vajngerl (cherry picked from commit 941f444ac8267c266ab6ce3d63212ce62b09ff39) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207820 Tested-by: Xisco Fauli Reviewed-by: Xisco Fauli Tested-by: Jenkins --- sc/qa/unit/ucalc_formula2.cxx | 142 ++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/sc/qa/unit/ucalc_formula2.cxx b/sc/qa/unit/ucalc_formula2.cxx index a0e39d981698..7ccad2b9dead 100644 --- a/sc/qa/unit/ucalc_formula2.cxx +++ b/sc/qa/unit/ucalc_formula2.cxx @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -6183,6 +6184,28 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testEnterMatrixUndoRedoKeepsDynamicFlag) m_pDoc->DeleteTab(0); } +CPPUNIT_TEST_FIXTURE(TestFormula2, testUnknownNameRoundTrip) +{ + // An unknown name evaluates to a name error and its text survives in the + // stored formula expression. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + ScAddress aPosition(0, 0, 0); + m_pDoc->SetString(aPosition, u"=ZZUNKNOWNZZ"_ustr); + + CPPUNIT_ASSERT_EQUAL(u"#NAME?"_ustr, m_pDoc->GetString(aPosition)); + + // Casing of the round-tripped name is not guaranteed, so match without + // regard to case. + OUString aFormula = m_pDoc->GetFormula(0, 0, 0); + CPPUNIT_ASSERT(!aFormula.isEmpty()); + CPPUNIT_ASSERT_MESSAGE(aFormula.toUtf8().getStr(), + aFormula.equalsIgnoreAsciiCase(u"=ZZUNKNOWNZZ")); + + m_pDoc->DeleteTab(0); +} + CPPUNIT_TEST_FIXTURE(TestFormula2, testCopyCellDropsAutoDynamicEligibility) { // A clone (paste, fill, transpose) drops the auto-promotion @@ -6218,6 +6241,125 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testCopyCellDropsAutoDynamicEligibility) m_pDoc->DeleteTab(0); } +CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncLetWithOdffParameters) +{ + // A LET whose bound parameters use the ODFF _xlpm. prefix evaluates to the + // right value. The prefix starts with an underscore, which keeps the name + // from looking like an ordinary name to the compiler, so the bound + // parameters have to be resolved even in that case. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + ScAddress aPosition(0, 0, 0); + m_pDoc->SetFormula(aPosition, + u"=COM.MICROSOFT.LET(" + "_xlpm.first;5;" + "_xlpm.second;SUM(_xlpm.first;5);" + "_xlpm.third;SUM(_xlpm.second;5);" + "SUM(_xlpm.second;_xlpm.third))"_ustr, + formula::FormulaGrammar::GRAM_ODFF); + + CPPUNIT_ASSERT_EQUAL(25.0, m_pDoc->GetValue(aPosition)); + CPPUNIT_ASSERT_EQUAL(u"25"_ustr, m_pDoc->GetString(aPosition)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testCallBuiltInThroughCallable) +{ + // A built-in function used as a value and then called gets routed through + // the call operator. It returns the same result as calling the built-in + // directly. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + // Parenthesizing SUM turns it into a callable, and the following argument + // list calls it. + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, u"=(SUM)(1;2;3)"_ustr); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(aPos)); + + // Binding a built-in to a LET parameter and calling that parameter takes + // the same call path. + aPos.IncRow(); + m_pDoc->SetString(aPos, u"=LET(f;SUM;f(1;2;3))"_ustr); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testCallBuiltInZeroAndOneArg) +{ + // A built-in called through the call operator returns the same value with + // no arguments and with one argument as the built-in does on its own. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + // PI takes no arguments, so the empty list calls it with nothing. + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, u"=(PI)()"_ustr); + CPPUNIT_ASSERT_EQUAL(M_PI, m_pDoc->GetValue(aPos)); + + // ABS takes one argument. + aPos.IncRow(); + m_pDoc->SetString(aPos, u"=(ABS)(-5)"_ustr); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testCallBuiltInChosenByFunction) +{ + // A built-in picked by IF and then called through an open paren that + // follows the closing paren of IF returns the chosen built-in's value. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + // A true condition makes IF yield SUM. + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, u"=IF(TRUE();SUM;MAX)(1;2;3)"_ustr); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(aPos)); + + // A false condition makes IF yield MAX. + aPos.IncRow(); + m_pDoc->SetString(aPos, u"=IF(FALSE();SUM;MAX)(1;2;3)"_ustr); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testCallableCallAsCallableArgument) +{ + // A call through the call operator used as an argument to another such call + // evaluates inner first and feeds its value to the outer call. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, u"=(SUM)((MAX)(1;2);3)"_ustr); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(aPos)); + + m_pDoc->DeleteTab(0); +} + +CPPUNIT_TEST_FIXTURE(TestFormula2, testCallableWhereNumberExpected) +{ + // A callable handed to an operator that expects a number resolves to an + // illegal parameter error. + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); + m_pDoc->InsertTab(0, u"Sheet1"_ustr); + + ScAddress aPos(0, 0, 0); + m_pDoc->SetString(aPos, u"=(SUM)+1"_ustr); + CPPUNIT_ASSERT_EQUAL(u"Err:504"_ustr, m_pDoc->GetString(aPos)); + + aPos.IncRow(); + m_pDoc->SetString(aPos, u"=SUM+1"_ustr); + CPPUNIT_ASSERT_EQUAL(u"Err:504"_ustr, m_pDoc->GetString(aPos)); + + m_pDoc->DeleteTab(0); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */