summaryrefslogtreecommitdiffstats
path: root/starmath/qa
diff options
context:
space:
mode:
Diffstat (limited to 'starmath/qa')
-rw-r--r--starmath/qa/cppunit/mock-visitor.hxx192
-rw-r--r--starmath/qa/cppunit/test_cursor.cxx172
-rw-r--r--starmath/qa/cppunit/test_node.cxx144
-rw-r--r--starmath/qa/cppunit/test_nodetotextvisitors.cxx665
-rw-r--r--starmath/qa/cppunit/test_parse.cxx128
-rw-r--r--starmath/qa/cppunit/test_starmath.cxx563
-rw-r--r--starmath/qa/extras/data/color.mml20
-rw-r--r--starmath/qa/extras/data/maction.mml10
-rw-r--r--starmath/qa/extras/data/mspace.mml12
-rw-r--r--starmath/qa/extras/data/ns-prefix-math.mml13
-rw-r--r--starmath/qa/extras/data/simple.mml13
-rw-r--r--starmath/qa/extras/data/tdf103430.mml15
-rw-r--r--starmath/qa/extras/data/tdf103500.mml41
-rw-r--r--starmath/qa/extras/data/tdf99556-1.mml3
-rw-r--r--starmath/qa/extras/mmlexport-test.cxx152
-rw-r--r--starmath/qa/extras/mmlimport-test.cxx173
-rw-r--r--starmath/qa/unit/data/starmath-dialogs-test.txt47
-rw-r--r--starmath/qa/unit/starmath-dialogs-test.cxx63
-rw-r--r--starmath/qa/unoapi/knownissues.xcl39
-rw-r--r--starmath/qa/unoapi/sm.sce26
20 files changed, 2491 insertions, 0 deletions
diff --git a/starmath/qa/cppunit/mock-visitor.hxx b/starmath/qa/cppunit/mock-visitor.hxx
new file mode 100644
index 000000000..8dab85336
--- /dev/null
+++ b/starmath/qa/cppunit/mock-visitor.hxx
@@ -0,0 +1,192 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_STARMATH_QA_CPPUNIT_MOCK_VISITOR_HXX
+#define INCLUDED_STARMATH_QA_CPPUNIT_MOCK_VISITOR_HXX
+
+#include <cppunit/TestAssert.h>
+#include <visitors.hxx>
+
+/** Simple visitor for testing SmVisitor */
+class MockVisitor : public SmVisitor
+{
+public:
+ virtual ~MockVisitor() {}
+
+ void Visit( SmTableNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmTableNode should have type SmNodeType::Table",
+ SmNodeType::Table, pNode->GetType());
+ auto eTT = pNode->GetToken().eType;
+ CPPUNIT_ASSERT_MESSAGE("The type of SmTableNode's token should be either TEND, TBINOM, or TSTACK",
+ eTT == TEND || eTT == TBINOM || eTT == TSTACK);
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmBraceNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmBraceNode should have type SmNodeType::Brace",
+ SmNodeType::Brace, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmBracebodyNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmBracebodyNode should have type SmNodeType::Bracebody",
+ SmNodeType::Bracebody, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmOperNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmOperNode should have type SmNodeType::Oper",
+ SmNodeType::Oper, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmAlignNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmAlignNode should have type SmNodeType::Align",
+ SmNodeType::Align, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmAttributNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmAttributNode should have type SmNodeType::Attribut",
+ SmNodeType::Attribut, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmFontNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmFontNode should have type SmNodeType::Font",
+ SmNodeType::Font, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmUnHorNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmUnHorNode should have type SmNodeType::UnHor",
+ SmNodeType::UnHor, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmBinHorNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmBinHorNode should have type SmNodeType::BinHor",
+ SmNodeType::BinHor, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmBinVerNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmBinVerNode should have type SmNodeType::BinVer",
+ SmNodeType::BinVer, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmBinDiagonalNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmBinDiagonalNode should have type SmNodeType::BinDiagonal",
+ SmNodeType::BinDiagonal, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmSubSupNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmSubSupNode should have type SmNodeType::SubSup",
+ SmNodeType::SubSup, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmMatrixNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmMatrixNode should have type SmNodeType::Matrix",
+ SmNodeType::Matrix, pNode->GetType());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmMatrixNode's token should be of type TMATRIX",
+ TMATRIX, pNode->GetToken().eType);
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmPlaceNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmPlaceNode should have type SmNodeType::Place",
+ SmNodeType::Place, pNode->GetType());
+ }
+
+ void Visit( SmTextNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmTextNode should have type SmNodeType::Text",
+ SmNodeType::Text, pNode->GetType());
+ }
+
+ void Visit( SmSpecialNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmSpecialNode should have type SmNodeType::Special",
+ SmNodeType::Special, pNode->GetType());
+ }
+
+ void Visit( SmGlyphSpecialNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmGlyphSpecialNode should have type SmNodeType::GlyphSpecial",
+ SmNodeType::GlyphSpecial, pNode->GetType());
+ }
+
+ void Visit( SmMathSymbolNode* pNode ) override {
+ CPPUNIT_ASSERT_MESSAGE("SmMathSymbolNode should have type SmNodeType::Math or SmNodeType::MathIdent",
+ pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::MathIdent);
+ }
+
+ void Visit( SmBlankNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmBlankNode should have type SmNodeType::Blank",
+ SmNodeType::Blank, pNode->GetType());
+ }
+
+ void Visit( SmErrorNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmErrorNode should have type SmNodeType::Error",
+ SmNodeType::Error, pNode->GetType());
+ }
+
+ void Visit( SmLineNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmLineNode should have type SmNodeType::Line",
+ SmNodeType::Line, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmExpressionNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmExpressionNode should have type SmNodeType::Expression",
+ SmNodeType::Expression, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmPolyLineNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmPolyLineNode should have type SmNodeType::PolyLine",
+ SmNodeType::PolyLine, pNode->GetType());
+ }
+
+ void Visit( SmRootNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmRootNode should have type SmNodeType::Root",
+ SmNodeType::Root, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+ void Visit( SmRootSymbolNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmRootSymbolNode should have type SmNodeType::RootSymbol",
+ SmNodeType::RootSymbol, pNode->GetType());
+ }
+
+ void Visit( SmRectangleNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmRectangleNode should have type SmNodeType::Rectangle",
+ SmNodeType::Rectangle, pNode->GetType());
+ }
+
+ void Visit( SmVerticalBraceNode* pNode ) override {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmVerticalBraceNode should have type SmNodeType::VerticalBrace",
+ SmNodeType::VerticalBrace, pNode->GetType());
+ VisitChildren( pNode );
+ }
+
+private:
+ /** Auxiliary method for visiting the children of a pNode */
+ void VisitChildren( SmStructureNode* pNode ) {
+ for (auto pChild : *pNode)
+ {
+ if (pChild)
+ pChild->Accept(this);
+ }
+ }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/cppunit/test_cursor.cxx b/starmath/qa/cppunit/test_cursor.cxx
new file mode 100644
index 000000000..080e7a274
--- /dev/null
+++ b/starmath/qa/cppunit/test_cursor.cxx
@@ -0,0 +1,172 @@
+/* -*- 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 <sal/config.h>
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/virdev.hxx>
+#include <sfx2/sfxmodelfactory.hxx>
+#include <smdll.hxx>
+
+#include <document.hxx>
+#include <node.hxx>
+#include <cursor.hxx>
+
+#include <memory>
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class Test : public test::BootstrapFixture {
+
+public:
+ // init
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ // tests
+ void testCopyPaste();
+ void testCopySelectPaste();
+ void testCutPaste();
+ void testCutSelectPaste();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testCopyPaste);
+ CPPUNIT_TEST(testCopySelectPaste);
+ CPPUNIT_TEST(testCutPaste);
+ CPPUNIT_TEST(testCutSelectPaste);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ SmDocShellRef xDocShRef;
+};
+
+void Test::setUp()
+{
+ BootstrapFixture::setUp();
+
+ SmGlobals::ensure();
+
+ xDocShRef = new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT);
+}
+
+void Test::tearDown()
+{
+ xDocShRef.clear();
+ BootstrapFixture::tearDown();
+}
+
+void Test::testCopyPaste()
+{
+ OUString const sInput("a * b + c");
+ auto xTree = SmParser().Parse(sInput);
+ xTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(xTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+
+ // go to the position at "*"
+ aCursor.Move(pOutputDevice, MoveRight);
+ // select "* b" and then copy
+ aCursor.Move(pOutputDevice, MoveRight, false);
+ aCursor.Move(pOutputDevice, MoveRight, false);
+ aCursor.Copy();
+ // go to the right end and then paste
+ aCursor.Move(pOutputDevice, MoveRight);
+ aCursor.Move(pOutputDevice, MoveRight);
+ aCursor.Paste();
+
+ CPPUNIT_ASSERT_EQUAL(OUString(" { a * b + c * b } "), xDocShRef->GetText());
+}
+
+void Test::testCopySelectPaste()
+{
+ OUString const sInput("a * b + c");
+ auto xTree = SmParser().Parse(sInput);
+ xTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(xTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+
+ // go to the right end
+ for (int i=0;i<5;i++)
+ aCursor.Move(pOutputDevice, MoveRight);
+ // select "b + c" and then copy
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Copy();
+ // go to the left end
+ aCursor.Move(pOutputDevice, MoveLeft);
+ aCursor.Move(pOutputDevice, MoveLeft);
+ // select "a" and then paste
+ aCursor.Move(pOutputDevice, MoveRight, false);
+ aCursor.Paste();
+
+ CPPUNIT_ASSERT_EQUAL(OUString(" { b + c * b + c } "), xDocShRef->GetText());
+}
+
+void Test::testCutPaste()
+{
+ OUString const sInput("a * b + c");
+ auto xTree = SmParser().Parse(sInput);
+ xTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(xTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+
+ // go to the position at "*"
+ aCursor.Move(pOutputDevice, MoveRight);
+ // select "* b" and then cut
+ aCursor.Move(pOutputDevice, MoveRight, false);
+ aCursor.Move(pOutputDevice, MoveRight, false);
+ aCursor.Cut();
+ // go to the left end and then paste
+ aCursor.Move(pOutputDevice, MoveRight);
+ aCursor.Move(pOutputDevice, MoveRight);
+ aCursor.Paste();
+
+ CPPUNIT_ASSERT_EQUAL(OUString(" { a + c * b } "), xDocShRef->GetText());
+}
+
+void Test::testCutSelectPaste()
+{
+ OUString const sInput("a * b + c");
+ auto xTree = SmParser().Parse(sInput);
+ xTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(xTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+
+ // go to the right end
+ for (int i=0;i<5;i++)
+ aCursor.Move(pOutputDevice, MoveRight);
+ // select "b + c" and then cut
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Cut();
+ // go to the left end
+ aCursor.Move(pOutputDevice, MoveLeft);
+ aCursor.Move(pOutputDevice, MoveLeft);
+ // select "a" and then paste
+ aCursor.Move(pOutputDevice, MoveRight, false);
+ aCursor.Paste();
+
+ CPPUNIT_ASSERT_EQUAL(OUString(" { b + c * } "), xDocShRef->GetText());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/cppunit/test_node.cxx b/starmath/qa/cppunit/test_node.cxx
new file mode 100644
index 000000000..dba0b9ff3
--- /dev/null
+++ b/starmath/qa/cppunit/test_node.cxx
@@ -0,0 +1,144 @@
+/* -*- 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 <sal/config.h>
+#include <test/bootstrapfixture.hxx>
+
+#include <o3tl/cppunittraitshelper.hxx>
+#include <sfx2/sfxmodelfactory.hxx>
+#include <vcl/virdev.hxx>
+
+#include <document.hxx>
+#include <smdll.hxx>
+#include <node.hxx>
+#include <parse.hxx>
+#include <utility.hxx>
+
+#include <memory>
+
+namespace {
+
+using namespace ::com::sun::star;
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+class NodeTest : public test::BootstrapFixture
+{
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+private:
+ void testTdf47813();
+ void testTdf52225();
+
+ CPPUNIT_TEST_SUITE(NodeTest);
+ CPPUNIT_TEST(testTdf47813);
+ CPPUNIT_TEST(testTdf52225);
+ CPPUNIT_TEST_SUITE_END();
+
+ SmDocShellRef mxDocShell;
+};
+
+void NodeTest::setUp()
+{
+ BootstrapFixture::setUp();
+ SmGlobals::ensure();
+ mxDocShell = new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT |
+ SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+ SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
+}
+
+void NodeTest::tearDown()
+{
+ if (mxDocShell.is())
+ mxDocShell->DoClose();
+ BootstrapFixture::tearDown();
+}
+
+void NodeTest::testTdf47813()
+{
+ SmParser aParser;
+#define MATRIX "matrix {-2#33##4#-5##6,0#7}"
+ auto pNodeA = aParser.Parse(MATRIX);
+ auto pNodeC = aParser.Parse("alignc " MATRIX);
+ auto pNodeL = aParser.Parse("alignl " MATRIX);
+ auto pNodeR = aParser.Parse("alignr " MATRIX);
+#undef MATRIX
+ ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
+ SmFormat aFmt;
+ pNodeA->Prepare(aFmt, *mxDocShell, 0);
+ pNodeA->Arrange(*pOutputDevice, aFmt);
+ pNodeC->Prepare(aFmt, *mxDocShell, 0);
+ pNodeC->Arrange(*pOutputDevice, aFmt);
+ pNodeL->Prepare(aFmt, *mxDocShell, 0);
+ pNodeL->Arrange(*pOutputDevice, aFmt);
+ pNodeR->Prepare(aFmt, *mxDocShell, 0);
+ pNodeR->Arrange(*pOutputDevice, aFmt);
+ long nWidthA = pNodeA->GetRect().GetWidth();
+ long nWidthC = pNodeC->GetRect().GetWidth();
+ long nWidthL = pNodeL->GetRect().GetWidth();
+ long nWidthR = pNodeR->GetRect().GetWidth();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, nWidthC/static_cast<double>(nWidthA), 0.01);
+ // these values appear to change slightly with display scaling
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, nWidthL/static_cast<double>(nWidthA), 0.03);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, nWidthR/static_cast<double>(nWidthA), 0.03);
+}
+
+void NodeTest::testTdf52225()
+{
+#define CHECK_GREEK_SYMBOL(text, code, bItalic) do { \
+ mxDocShell->SetText(text); \
+ const SmTableNode *pTree= mxDocShell->GetFormulaTree(); \
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pTree->GetNumSubNodes()); \
+ const SmNode *pLine = pTree->GetSubNode(0); \
+ CPPUNIT_ASSERT(pLine); \
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Line, pLine->GetType()); \
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pLine->GetNumSubNodes()); \
+ const SmNode *pNode = pLine->GetSubNode(0); \
+ CPPUNIT_ASSERT(pNode); \
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Special, pNode->GetType()); \
+ const SmSpecialNode *pSn = static_cast<const SmSpecialNode *>(pNode); \
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pSn->GetText().getLength()); \
+ CPPUNIT_ASSERT_EQUAL(code, pSn->GetText()[0]); \
+ CPPUNIT_ASSERT_EQUAL(OUString(text), pSn->GetToken().aText); \
+ CPPUNIT_ASSERT_EQUAL(bItalic, IsItalic(pSn->GetFont())); \
+ } while (false)
+
+ SmFormat aFormat = mxDocShell->GetFormat();
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(2), aFormat.GetGreekCharStyle()); // default format = 2
+ CHECK_GREEK_SYMBOL("%ALPHA", u'\x0391', false);
+ CHECK_GREEK_SYMBOL("%iALPHA", u'\x0391', true);
+ CHECK_GREEK_SYMBOL("%alpha", u'\x03b1', true);
+ CHECK_GREEK_SYMBOL("%ialpha", u'\x03b1', true);
+
+ // mode 1
+ aFormat.SetGreekCharStyle(1);
+ mxDocShell->SetFormat(aFormat);
+ CHECK_GREEK_SYMBOL("%BETA", u'\x0392', true);
+ CHECK_GREEK_SYMBOL("%iBETA", u'\x0392', true);
+ CHECK_GREEK_SYMBOL("%beta", u'\x03b2', true);
+ CHECK_GREEK_SYMBOL("%ibeta", u'\x03b2', true);
+
+ // mode 0
+ aFormat.SetGreekCharStyle(0);
+ mxDocShell->SetFormat(aFormat);
+ CHECK_GREEK_SYMBOL("%GAMMA", u'\x0393', false);
+ CHECK_GREEK_SYMBOL("%iGAMMA", u'\x0393', true);
+ CHECK_GREEK_SYMBOL("%gamma", u'\x03b3', false);
+ CHECK_GREEK_SYMBOL("%igamma", u'\x03b3', true);
+
+#undef CHECK_GREEK_SYMBOL
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(NodeTest);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/cppunit/test_nodetotextvisitors.cxx b/starmath/qa/cppunit/test_nodetotextvisitors.cxx
new file mode 100644
index 000000000..c3172ab60
--- /dev/null
+++ b/starmath/qa/cppunit/test_nodetotextvisitors.cxx
@@ -0,0 +1,665 @@
+/* -*- 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 <sal/config.h>
+#include <test/bootstrapfixture.hxx>
+
+#include <vcl/virdev.hxx>
+#include <sfx2/sfxmodelfactory.hxx>
+#include <smdll.hxx>
+
+#include <document.hxx>
+#include <node.hxx>
+#include <visitors.hxx>
+#include <cursor.hxx>
+
+#include "mock-visitor.hxx"
+#include <memory>
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class Test : public test::BootstrapFixture {
+
+public:
+ // init
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ // tests
+ void SimpleUnaryOp();
+ void SimpleBinaryOp();
+ void SimpleRelationalOp();
+ void SimpleSetOp();
+ void SimpleFunctions();
+ void SimpleOperators();
+ void SimpleAttributes();
+ void SimpleMisc();
+ void SimpleBrackets();
+ void SimpleFormats();
+ void SimpleGreekChars();
+ void SimpleSpecialChars();
+ void testBinomInBinHor();
+ void testBinVerInUnary();
+ void testBinHorInSubSup();
+ void testUnaryInMixedNumberAsNumerator();
+ void testMiscEquivalent();
+ void testParser();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(SimpleUnaryOp);
+ CPPUNIT_TEST(SimpleBinaryOp);
+ CPPUNIT_TEST(SimpleRelationalOp);
+ CPPUNIT_TEST(SimpleSetOp);
+ CPPUNIT_TEST(SimpleFunctions);
+ CPPUNIT_TEST(SimpleOperators);
+ CPPUNIT_TEST(SimpleAttributes);
+ CPPUNIT_TEST(SimpleMisc);
+ CPPUNIT_TEST(SimpleBrackets);
+ CPPUNIT_TEST(SimpleFormats);
+ CPPUNIT_TEST(SimpleGreekChars);
+ CPPUNIT_TEST(SimpleSpecialChars);
+ CPPUNIT_TEST(testBinomInBinHor);
+ CPPUNIT_TEST(testBinVerInUnary);
+ CPPUNIT_TEST(testBinHorInSubSup);
+ CPPUNIT_TEST(testUnaryInMixedNumberAsNumerator);
+ CPPUNIT_TEST(testMiscEquivalent);
+ CPPUNIT_TEST(testParser);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ SmDocShellRef xDocShRef;
+ void parseandparseagain(const char *input, const char *test_name);
+ void ParseAndCheck(const char *input, const char *expected, const char *test_name);
+ void ParseAndCompare(const char *formula1, const char *formula2, const char *test_name);
+};
+
+void Test::setUp()
+{
+ BootstrapFixture::setUp();
+
+ SmGlobals::ensure();
+
+ xDocShRef = new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT);
+}
+
+void Test::tearDown()
+{
+ xDocShRef.clear();
+ BootstrapFixture::tearDown();
+}
+
+/*
+ * Most of the formula commands in this file came from:
+ * http://wiki.openoffice.org/wiki/Template:Math_commands_reference
+ * which was licensed with a
+ * Creative Common Attribution 3.0 license and written by:
+ * Jeanweber, Weegreenblobbie, Jdpipe, TJFrazier, Ysangkok, B michaelsen, Spellbreaker
+ */
+
+void Test::SimpleUnaryOp()
+{
+ parseandparseagain("+1", "Positive (plus)");
+ parseandparseagain("-2", "Negative (minus)");
+ parseandparseagain("+-3", "Plus/minus");
+ parseandparseagain("-+4", "Minus/plus");
+ parseandparseagain("neg a", "Boolean 'not'");
+ parseandparseagain("fact a", "Factorial");
+ parseandparseagain(" - { 1 over 2 } ", "BinVer in Unary 1");
+ ParseAndCheck(" - { 1 over 2 } ", " - { 1 over 2 } ", "BinVer in Unary 1");
+ parseandparseagain(" { - { 1 over 2 } } ", "BinVer in Unary 2");
+ parseandparseagain(" - 1 over 2 ", "Unary in BinVer as numerator 1");
+ parseandparseagain(" { - 1 } over 2 ", "Unary in BinVer as numerator 2");
+ parseandparseagain(" 1 over - 2 ", "Unary in BinVer as denominator 1");
+ parseandparseagain(" 1 over { - 2 } ", "Unary in BinVer as denominator 2");
+ parseandparseagain(" 2 { - 1 over 2 } ", "Mixed number with Unary in denominator 1");
+ parseandparseagain(" 2 { - 1 } over 2 ", "Mixed number with Unary in denominator 2");
+ parseandparseagain(" - 1 + 2 ", "Unary in BinHor");
+}
+
+void Test::SimpleBinaryOp()
+{
+ parseandparseagain("a + b", "Addition");
+ parseandparseagain("a cdot b", "Dot product");
+ parseandparseagain("a times b", "Cross product");
+ parseandparseagain("a * b", "Multiplication (asterisk)");
+ parseandparseagain("a and b", "Boolean 'and'");
+ parseandparseagain("a - b", "Subtraction");
+ parseandparseagain("a over b", "Division (as a fraction)");
+ parseandparseagain("a div b", "Division (as an operator)");
+ parseandparseagain("a / b", "Division (with a slash)");
+ parseandparseagain("a or b", "Boolean 'or'");
+ parseandparseagain("a circ b", "Concatenation");
+}
+
+void Test::SimpleRelationalOp()
+{
+ parseandparseagain("a = b", "Is equal");
+ parseandparseagain("a <> b", "Is not equal");
+ parseandparseagain("a approx 2", "Approximately");
+ parseandparseagain("a divides b", "Divides");
+ parseandparseagain("a ndivides b", "Does not divide");
+ parseandparseagain("a < 2", "Less than");
+ parseandparseagain("a > 2", "Greater than");
+ parseandparseagain("a simeq b", "Similar to or equal");
+ parseandparseagain("a parallel b", "Parallel");
+ parseandparseagain("a ortho b", "Orthogonal to");
+ parseandparseagain("a leslant b", "Less than or equal to");
+ parseandparseagain("a geslant b", "Greater than or equal to");
+ parseandparseagain("a sim b", "Similar to");
+ parseandparseagain("a equiv b", "Congruent");
+ parseandparseagain("a <= b", "Less than or equal to");
+ parseandparseagain("a >= b", "Greater than or equal to");
+ parseandparseagain("a prop b", "Proportional");
+ parseandparseagain("a toward b", "Toward");
+ parseandparseagain("a dlarrow b", "Arrow left");
+ parseandparseagain("a dlrarrow b", "Double arrow left and right");
+ parseandparseagain("drarrow b", "Arrow right");
+}
+
+void Test::SimpleSetOp()
+{
+ parseandparseagain("a in B", "Is in");
+ parseandparseagain("a notin B", "Is not in");
+ parseandparseagain("A owns b", "Owns");
+ parseandparseagain("emptyset", "Empty set");
+ parseandparseagain("A intersection B", "Intersection");
+ parseandparseagain("A union B", "Union");
+ parseandparseagain("A setminus B", "Difference");
+ parseandparseagain("A slash B", "Quotient");
+ parseandparseagain("aleph", "Aleph");
+ parseandparseagain("A subset B", "Subset");
+ parseandparseagain("A subseteq B", "Subset or equal to");
+ parseandparseagain("A supset B", "Superset");
+ parseandparseagain("A supseteq B", "Superset or equal to");
+ parseandparseagain("A nsubset B", "Not subset");
+ parseandparseagain("A nsubseteq B", "Not subset or equal");
+ parseandparseagain("A nsupset B", "Not superset");
+ parseandparseagain("A nsupseteq B", "Not superset or equal");
+ parseandparseagain("setN", "Set of natural numbers");
+ parseandparseagain("setZ", "Set of integers");
+ parseandparseagain("setQ", "Set of rational numbers");
+ parseandparseagain("setR", "Set of real numbers");
+ parseandparseagain("setC", "Set of complex numbers");
+}
+
+void Test::SimpleFunctions()
+{
+ parseandparseagain("func e^{a}", "Exponential");
+ parseandparseagain("ln(a)", "Natural logarithm");
+ parseandparseagain("exp(a)", "Exponential function");
+ parseandparseagain("log(a)", "Logarithm");
+ parseandparseagain("a^{b}", "Power");
+ parseandparseagain("sin(a)", "Sine");
+ parseandparseagain("cos(a)", "Cosine");
+ parseandparseagain("tan(a)", "Tangent");
+ parseandparseagain("cot(a)", "Cotangent");
+ parseandparseagain("sqrt{a}", "Square root");
+ parseandparseagain("arcsin(a)", "Arcsine");
+ parseandparseagain("arccos(a)", "Arccosine");
+ parseandparseagain("arctan(a)", "Arctangent");
+ parseandparseagain("arccot(a)", "Arc cotangent");
+ parseandparseagain("nroot{a}{b}", "nth root");
+ parseandparseagain("sinh(a)", "Hyperbolic sine");
+ parseandparseagain("cosh(a)", "Hyperbolic cosine");
+ parseandparseagain("tanh(a)", "Hyperbolic tangent");
+ parseandparseagain("coth(a)", "Hyperbolic cotangent");
+ parseandparseagain("abs{a}", "Absolute value");
+ parseandparseagain("arsinh(a)", "Arc hyperbolic sine");
+ parseandparseagain("arcosh(a)", "Arc hyperbolic cosine");
+ parseandparseagain("artanh(a)", "Arc hyperbolic tangent");
+ parseandparseagain("arcoth(a)", "Arc hyperbolic cotangent");
+}
+
+void Test::SimpleOperators()
+{
+ parseandparseagain("lim{a}", "Limit");
+ parseandparseagain("sum{a}", "Sum");
+ parseandparseagain("prod{a}", "Product");
+ parseandparseagain("coprod{a}", "Coproduct");
+ parseandparseagain("int from {r_0} to {r_t} a", "Upper and lower bounds shown with integral (from & to)");
+ ParseAndCheck("int csup {r_0} csub {r_t} a", "int csup { r _ 0 } csub { r _ t } a ", "Upper and lower bounds shown with integral (csub & csup)");
+ ParseAndCheck("sum csup { size 8 { x - 1 } } csub { size 8 a } b ", "sum csup { size 8 { x - 1 } } csub { size 8 a } b ", "Sum with sized upper and lower bounds");
+ parseandparseagain("int{a}", "Integral");
+ parseandparseagain("intd_{1}^{2}{x dx}", "Dynamically-sized integral");
+ parseandparseagain("iint{a}", "Double integral");
+ parseandparseagain("iiint{a}", "Triple integral");
+ parseandparseagain("sum from{3}b", "Lower bound shown with summation symbol");
+ parseandparseagain("lint a", "Contour integral");
+ parseandparseagain("llint a", "Double curved integral");
+ parseandparseagain("lllint a", "Triple curved integral");
+ parseandparseagain("prod from {i=1} to {n} {(i+1)}", "Product with range");
+ ParseAndCheck("%Ux2135", "%Ux2135", "fdo#77831");
+}
+
+void Test::SimpleAttributes()
+{
+ parseandparseagain("acute a", "Acute accent");
+ parseandparseagain("grave a", "Grave accent");
+ parseandparseagain("check a", "Reverse circumflex");
+ parseandparseagain("breve a", "Breve");
+ parseandparseagain("circle a", "Circle");
+ parseandparseagain("vec a", "Vector arrow");
+ parseandparseagain("harpoon a", "Harpoon");
+ parseandparseagain("tilde a", "Tilde");
+ parseandparseagain("hat a", "Circumflex");
+ parseandparseagain("bar a", "Line above");
+ parseandparseagain("dot a", "Dot");
+ parseandparseagain("widevec abc", "Wide vector arrow");
+ parseandparseagain("wideharpoon abc", "Wide harpoon");
+ parseandparseagain("widetilde abc", "Wide tilde");
+ parseandparseagain("widehat abc", "Wide circumflex");
+ parseandparseagain("ddot a", "Double dot");
+ parseandparseagain("overline abc", "Line over");
+ parseandparseagain("underline abc", "Line under");
+ parseandparseagain("overstrike abc", "Line through");
+ parseandparseagain("dddot a", "Triple dot");
+ parseandparseagain("phantom a", "Transparent (useful to get a placeholder of a given size)");
+ parseandparseagain("bold a", "Bold font");
+ parseandparseagain("ital a", "Italic font");
+ parseandparseagain("nitalic a", "Roman (non-italic) font 1");
+ parseandparseagain("\"a\"", "Roman (non-italic) font 2");
+ parseandparseagain("size 16 qv", "Resize font");
+ parseandparseagain("font sans qv", "Sans serif font");
+ parseandparseagain("font serif qv", "Serif font");
+ parseandparseagain("font fixed qv", "Fixed font");
+ parseandparseagain("color cyan qv", "Cyan color");
+ parseandparseagain("color yellow qv", "Yellow color");
+ parseandparseagain("color white qv", "White color");
+ parseandparseagain("color green qv", "Green color");
+ parseandparseagain("color blue qv", "Blue color");
+ parseandparseagain("color red qv", "Red color");
+ parseandparseagain("color green X qv", "Green color changes back");
+ parseandparseagain("color green {X qv}", "Green color, more than one item");
+}
+
+void Test::SimpleMisc()
+{
+ parseandparseagain("infinity", "Infinity");
+ parseandparseagain("partial", "Partial");
+ parseandparseagain("nabla", "Nabla");
+ parseandparseagain("exists", "There exists");
+ parseandparseagain("notexists", "There not exists");
+ parseandparseagain("forall", "For all");
+ parseandparseagain("hbar", "H bar");
+ parseandparseagain("lambdabar", "Lambda bar");
+ parseandparseagain("re", "Real part");
+ parseandparseagain("im", "Imaginary part");
+ parseandparseagain("wp", "Weierstrass p");
+ parseandparseagain("leftarrow", "Left arrow");
+ parseandparseagain("rightarrow", "Right arrow");
+ parseandparseagain("uparrow", "Up arrow");
+ parseandparseagain("downarrow", "Down arrow");
+ parseandparseagain("dotslow", "Dots at bottom");
+ parseandparseagain("dotsaxis", "Dots at middle");
+ parseandparseagain("dotsvert", "Dots vertical");
+ parseandparseagain("dotsup", "Dots diagonal upward");
+ parseandparseagain("dotsdown", "Dots diagonal downward");
+}
+
+void Test::SimpleBrackets()
+{
+ parseandparseagain("(a)", "Round Brackets");
+ parseandparseagain("[b]", "Square Brackets");
+ parseandparseagain("ldbracket c rdbracket", "Double Square Brackets");
+ parseandparseagain("lline a rline", "Single line or absolute");
+ parseandparseagain("abs a", "Single line or absolute 2");
+ parseandparseagain("ldline a rdline", "Double line");
+ parseandparseagain("lbrace w rbrace", "Braces");
+ parseandparseagain("left lbrace stack{0, n <> 0 # 1, n = 1} right none", "Single left brace");
+ parseandparseagain("langle d rangle", "Angle Brackets");
+ parseandparseagain("langle a mline b rangle", "Operator Brackets");
+ parseandparseagain("{a}", "Group brackets (used for program control)");
+ parseandparseagain("left ( stack{a # b # z} right )", "Round brackets scalable");
+ parseandparseagain("left [ stack{x # y} right ]", "Square brackets scalable");
+ parseandparseagain("left ldbracket c right rdbracket", "Double square brackets scalable");
+ parseandparseagain("left lline a right rline", "Line scalable");
+ parseandparseagain("left ldline d right rdline", "Double line scalable");
+ parseandparseagain("left lbrace e right rbrace", "Brace scalable");
+ parseandparseagain("left langle f right rangle", "Angle bracket scalable");
+ parseandparseagain("left langle g mline h right rangle", "Operator brackets scalable");
+ parseandparseagain("{a} overbrace b", "Over brace scalable");
+ parseandparseagain("{b} underbrace a", "Under brace scalable");
+}
+
+void Test::SimpleFormats()
+{
+ parseandparseagain("a lsup{b}", "Left superscript");
+ parseandparseagain("a csup{b}", "Center superscript");
+ parseandparseagain("a^{b}", "Right superscript");
+ parseandparseagain("a lsub{b}", "Left subscript");
+ parseandparseagain("a csub{b}", "Center subscript");
+ parseandparseagain("a_{b}", "Right subscript");
+ parseandparseagain("stack { Hello world # alignl (a) }", "Align character to left");
+ parseandparseagain("stack{Hello world # alignc(a)}", "Align character to center");
+ parseandparseagain("stack { Hello world # alignr(a)}", "Align character to right");
+ parseandparseagain("binom{a}{b}", "Vertical stack of 2");
+ parseandparseagain("stack{a # b # z}", "Vertical stack, more than 2");
+ parseandparseagain("matrix{a # b ## c # d}", "Matrix");
+ parseandparseagain("matrix{a # \"=\" # alignl{b} ## {} # \"=\" # alignl{c+1}}", "Equations aligned at '=' (using 'matrix') ");
+ parseandparseagain("stack{alignl{a} = b # alignl{phantom{a} = c+1}}", "Equations aligned at '=' (using 'phantom') ");
+ parseandparseagain("asldkfjo newline sadkfj", "New line");
+ parseandparseagain("stuff `stuff", "Small gap (grave)");
+ parseandparseagain("stuff~stuff", "Large gap (tilde)");
+}
+
+void Test::SimpleGreekChars()
+{
+ parseandparseagain("%ALPHA", "Capital alpha");
+ parseandparseagain("%BETA", "Capital beta");
+ parseandparseagain("%CHI", "Capital chi");
+ parseandparseagain("%DELTA", "Capital delta");
+ parseandparseagain("%EPSILON", "Capital epsilon");
+ parseandparseagain("%ETA", "Capital eta");
+ parseandparseagain("%GAMMA", "Capital gamma");
+ parseandparseagain("%IOTA", "Capital iota");
+ parseandparseagain("%LAMBDA", "Capital lambda");
+ parseandparseagain("%MU", "Capital mu");
+ parseandparseagain("%NU", "Capital nu");
+ parseandparseagain("%OMEGA", "Capital omega");
+ parseandparseagain("%OMICRON", "Capital omicron");
+ parseandparseagain("%PHI", "Capital phi");
+ parseandparseagain("%PI", "Capital pi");
+ parseandparseagain("%PSI", "Capital psi");
+ parseandparseagain("%RHO", "Capital rho");
+ parseandparseagain("%SIGMA", "Capital sigma");
+ parseandparseagain("%TAU", "Capital tau");
+ parseandparseagain("%THETA", "Capital theta");
+ parseandparseagain("%UPSILON", "Capital upsilon");
+ parseandparseagain("%XI", "Capital xi");
+ parseandparseagain("%ZETA", "Capital zeta");
+ parseandparseagain("%alpha", "lowercase alpha");
+ parseandparseagain("%beta", "lowercase beta");
+ parseandparseagain("%chi", "lowercase chi");
+ parseandparseagain("%delta", "lowercase delta");
+ parseandparseagain("%epsilon", "lowercase epsilon");
+ parseandparseagain("%eta", "lowercase eta");
+ parseandparseagain("%gamma", "lowercase gamma");
+ parseandparseagain("%iota", "lowercase iota");
+ parseandparseagain("%kappa", "lowercase kappa");
+ parseandparseagain("%lambda", "lowercase lambda");
+ parseandparseagain("%mu", "lowercase mu");
+ parseandparseagain("%nu", "lowercase nu");
+ parseandparseagain("%omega", "lowercase omega");
+ parseandparseagain("%omicron", "lowercase omicron");
+ parseandparseagain("%phi", "lowercase phi");
+ parseandparseagain("%pi", "lowercase pi");
+ parseandparseagain("%psi", "lowercase psi");
+ parseandparseagain("%rho", "lowercase rho");
+ parseandparseagain("%sigma", "lowercase sigma");
+ parseandparseagain("%tau", "lowercase tau");
+ parseandparseagain("%theta", "lowercase theta");
+ parseandparseagain("%upsilon", "lowercase upsilon");
+ parseandparseagain("%varepsilon", "Varepsilon");
+ parseandparseagain("%varphi", "Varphi");
+ parseandparseagain("%varpi", "Varpi");
+ parseandparseagain("%varrho", "Varrho");
+ parseandparseagain("%varsigma", "Varsigma");
+ parseandparseagain("%vartheta", "Vartheta");
+ parseandparseagain("%xi", "lowercase xi");
+ parseandparseagain("%zeta", "lowercase zeta");
+}
+
+void Test::SimpleSpecialChars()
+{
+ parseandparseagain("%and", "And");
+ parseandparseagain("%angle", "Angle");
+ parseandparseagain("%element", "Element");
+ parseandparseagain("%identical", "Identical");
+ parseandparseagain("%infinite", "Infinite");
+ parseandparseagain("%noelement", "No element");
+ parseandparseagain("%notequal", "Not equal");
+ parseandparseagain("%or", "Or");
+ parseandparseagain("%perthousand", "Per thousand");
+ parseandparseagain("%strictlygreaterthan", "Strictly greater than");
+ parseandparseagain("%strictlylessthan", "Strictly less than");
+ parseandparseagain("%tendto", "Tend to");
+}
+
+/* This test takes a formula command, parses it, converts the node to text,
+ * parses it again, converts it to text again, and compares the values.
+ * Doing this doesn't prove that it is correct, but it should prove that the
+ * meaning of the original command is not being changed.
+ */
+void Test::parseandparseagain(const char *formula, const char *test_name)
+{
+ OUString output1, output2;
+
+ // parse 1
+ OUString input = OUString::createFromAscii(formula);
+ auto pNode1 = SmParser().ParseExpression(input);
+ pNode1->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+ SmNodeToTextVisitor(pNode1.get(), output1);
+
+ // parse 2
+ auto pNode2 = SmParser().ParseExpression(output1);
+ pNode2->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+ SmNodeToTextVisitor(pNode2.get(), output2);
+
+ // compare
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(test_name,
+ output1,
+ output2);
+
+ // auxiliary test for Accept()
+ std::unique_ptr<MockVisitor> mv(new MockVisitor);
+ pNode1->Accept(mv.get());
+ pNode2->Accept(mv.get());
+}
+
+void Test::ParseAndCheck(const char *formula, const char * expected, const char *test_name)
+{
+ OUString sOutput;
+
+ // parse
+ OUString sInput = OUString::createFromAscii(formula);
+ auto pNode = SmParser().ParseExpression(sInput);
+ pNode->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+ SmNodeToTextVisitor(pNode.get(), sOutput);
+
+ // compare
+ OUString sExpected = OUString::createFromAscii(expected);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(test_name,
+ sExpected,
+ sOutput);
+
+ // auxiliary test for Accept()
+ std::unique_ptr<MockVisitor> mv(new MockVisitor);
+ pNode->Accept(mv.get());
+}
+
+// Parse two formula commands and verify that they give the same output
+void Test::ParseAndCompare(const char *formula1, const char *formula2, const char *test_name)
+{
+ OUString sOutput1, sOutput2;
+
+ // parse formula1
+ OUString sInput1(formula1, strlen(formula1), RTL_TEXTENCODING_UTF8);
+ auto pNode1 = SmParser().ParseExpression(sInput1);
+ pNode1->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+ SmNodeToTextVisitor(pNode1.get(), sOutput1);
+
+ // parse formula2
+ OUString sInput2(formula2, strlen(formula2), RTL_TEXTENCODING_UTF8);
+ auto pNode2 = SmParser().ParseExpression(sInput2);
+ pNode2->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+ SmNodeToTextVisitor(pNode2.get(), sOutput2);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(test_name, sOutput1, sOutput2);
+
+ // auxiliary test for Accept()
+ std::unique_ptr<MockVisitor> mv(new MockVisitor);
+ pNode1->Accept(mv.get());
+ pNode2->Accept(mv.get());
+}
+
+void Test::testBinomInBinHor()
+{
+ OUString sInput, sExpected;
+
+ // set up a binom (table) node
+ sInput += "binom a b + c";
+ auto pTree = SmParser().Parse(sInput);
+ pTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(pTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance< VirtualDevice > pOutputDevice;
+
+ // move forward (more than) enough places to be at the end
+ int i;
+ for (i = 0; i < 8; ++i)
+ aCursor.Move(pOutputDevice, MoveRight);
+
+ // tack +d on the end, which will put the binom into an SmBinHorNode
+ aCursor.InsertElement(PlusElement);
+ aCursor.InsertText("d");
+
+ sExpected += " { { binom a b + c } + d } ";
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Binom Node in BinHor Node", sExpected, xDocShRef->GetText());
+}
+
+void Test::testBinVerInUnary()
+{
+ OUString sInput, sExpected;
+
+ // set up a unary operator with operand
+ sInput += "- 1";
+ auto pTree = SmParser().Parse(sInput);
+ pTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(pTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance< VirtualDevice > pOutputDevice;
+
+ // move forward (more than) enough places to be at the end
+ int i;
+ for (i = 0; i < 3; ++i)
+ aCursor.Move(pOutputDevice, MoveRight);
+
+ // select the operand
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ // set up a fraction
+ aCursor.InsertFraction();
+ aCursor.Move(pOutputDevice, MoveDown);
+ aCursor.InsertText("2");
+
+ sExpected += " - { 1 over 2 } ";
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Binary Vertical in Unary Operator", sExpected, xDocShRef->GetText());
+}
+
+void Test::testBinHorInSubSup()
+{
+ // set up a blank formula
+ auto pTree = SmParser().Parse(OUString());
+ pTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(pTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance< VirtualDevice > pOutputDevice;
+
+ // Insert an RSup expression with a BinHor for the exponent
+ aCursor.InsertText("a");
+ aCursor.InsertSubSup(RSUP);
+ aCursor.InsertText("b");
+ aCursor.InsertElement(PlusElement);
+ aCursor.InsertText("c");
+
+ // Move to the end and add d to the expression
+ aCursor.Move(pOutputDevice, MoveRight);
+ aCursor.InsertElement(PlusElement);
+ aCursor.InsertText("d");
+
+ OUString sExpected = " { a ^ { b + c } + d } ";
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("BinHor in SubSup", sExpected, xDocShRef->GetText());
+}
+
+void Test::testUnaryInMixedNumberAsNumerator()
+{
+ // set up a unary operator
+ OUString sInput = "- 1";
+ auto pTree = SmParser().Parse(sInput);
+ pTree->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+
+ SmCursor aCursor(pTree.get(), xDocShRef.get());
+ ScopedVclPtrInstance< VirtualDevice > pOutputDevice;
+
+ // move forward (more than) enough places to be at the end
+ for (size_t i = 0; i < 3; ++i)
+ aCursor.Move(pOutputDevice, MoveRight);
+
+ // Select the whole Unary Horizontal Node
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+ aCursor.Move(pOutputDevice, MoveLeft, false);
+
+ // Set up a fraction
+ aCursor.InsertFraction();
+ aCursor.Move(pOutputDevice, MoveDown);
+ aCursor.InsertText("2");
+
+ // Move left and turn this into a mixed number
+ // (bad form, but this could happen right?)
+ aCursor.Move(pOutputDevice, MoveLeft);
+ aCursor.Move(pOutputDevice, MoveLeft);
+ aCursor.InsertText("2");
+
+ // move forward (more than) enough places to be at the end
+ for (size_t i = 0; i < 8; ++i)
+ aCursor.Move(pOutputDevice, MoveRight);
+
+ // add 4 to the end
+ aCursor.InsertElement(PlusElement);
+ aCursor.InsertText("4");
+
+ OUString sExpected = " { 2 { - 1 over 2 } + 4 } ";
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Unary in mixed number as Numerator", sExpected, xDocShRef->GetText());
+}
+
+void Test::testMiscEquivalent()
+{
+ // fdo#55853
+ ParseAndCompare("2x", "2 x", "Number times variable");
+ ParseAndCompare("3x^2", "3 x^2", "Number times power");
+
+ // i#11752 and fdo#55853
+ ParseAndCompare("x_2n", "x_{2 n}", "Number times variable in subscript");
+ ParseAndCompare("x^2n", "x^{2 n}", "Number times variable in supscript");
+
+ // fdo#66081
+ ParseAndCompare("{x}", "x", "Variable in brace");
+ ParseAndCompare("{{x+{{y}}}}", "x+y", "Nested braces");
+
+ // check non-BMP Unicode char
+ ParseAndCompare("{\xf0\x9d\x91\x8e}", "\xf0\x9d\x91\x8e", "non-BMP variable in brace");
+ ParseAndCompare("{ \xf0\x9d\x91\x8e }", "\xf0\x9d\x91\x8e", "non-BMP variable in brace");
+
+ // tdf#88320
+ ParseAndCompare("A_1,B_2", "A_{1},B_2", "Comma between a digit and non-digit delimits subscript");
+
+ //tdf#97164
+ ParseAndCompare("100 %", "100\"%\"", "Percent symbol at the end");
+}
+
+void Test::testParser()
+{
+ OUString sOutput;
+ OUString sInput(u"{ \U0001D44E }"); // non-BMP Unicode
+ OUString sExpected(u"\U0001D44E");
+ auto pNode = SmParser().ParseExpression(sInput);
+ pNode->Prepare(xDocShRef->GetFormat(), *xDocShRef, 0);
+ SmNodeToTextVisitor(pNode.get(), sOutput);
+ CPPUNIT_ASSERT_EQUAL(sExpected, sOutput);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/cppunit/test_parse.cxx b/starmath/qa/cppunit/test_parse.cxx
new file mode 100644
index 000000000..ccc0f5478
--- /dev/null
+++ b/starmath/qa/cppunit/test_parse.cxx
@@ -0,0 +1,128 @@
+/* -*- 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 <sal/config.h>
+#include <test/bootstrapfixture.hxx>
+
+#include <sfx2/sfxmodelfactory.hxx>
+
+#include <document.hxx>
+#include <smdll.hxx>
+#include <node.hxx>
+#include <parse.hxx>
+
+#include <memory>
+
+namespace {
+
+using namespace ::com::sun::star;
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+class ParseTest : public test::BootstrapFixture
+{
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+private:
+ void testMinus();
+ void testNospace();
+
+ CPPUNIT_TEST_SUITE(ParseTest);
+ CPPUNIT_TEST(testMinus);
+ CPPUNIT_TEST(testNospace);
+ CPPUNIT_TEST_SUITE_END();
+
+ SmDocShellRef mxDocShell;
+};
+
+void ParseTest::setUp()
+{
+ BootstrapFixture::setUp();
+ SmGlobals::ensure();
+ mxDocShell = new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT |
+ SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+ SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
+}
+
+void ParseTest::tearDown()
+{
+ if (mxDocShell.is())
+ mxDocShell->DoClose();
+ BootstrapFixture::tearDown();
+}
+
+/*
+ * This shows that input "-" is recognized as a separate token even when
+ * it is immediately followed by a number.
+ */
+void ParseTest::testMinus()
+{
+ auto pNode = SmParser().Parse("-1.2");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pNode->GetNumSubNodes());
+ const SmNode *pNode0 = pNode->GetSubNode(0);
+ CPPUNIT_ASSERT(pNode0);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Line, pNode0->GetType());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pNode0->GetNumSubNodes());
+ const SmNode *pNode00 = pNode0->GetSubNode(0);
+ CPPUNIT_ASSERT(pNode00);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::UnHor, pNode00->GetType());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pNode00->GetNumSubNodes());
+ const SmNode *pNode000 = pNode00->GetSubNode(0);
+ CPPUNIT_ASSERT(pNode000);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Math, pNode000->GetType());
+ // GetText() vs GetToken().aText
+ CPPUNIT_ASSERT_EQUAL(OUString(MS_MINUS),
+ static_cast<const SmMathSymbolNode *>(pNode000)->GetText());
+ CPPUNIT_ASSERT_EQUAL(OUString("-"),
+ static_cast<const SmMathSymbolNode *>(pNode000)->GetToken().aText);
+ const SmNode *pNode001 = pNode00->GetSubNode(1);
+ CPPUNIT_ASSERT(pNode001);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Text, pNode001->GetType());
+ // GetText() vs GetToken().aText
+ CPPUNIT_ASSERT(static_cast<const SmTextNode *>(pNode001)->GetText().isEmpty());
+ CPPUNIT_ASSERT_EQUAL(OUString("1.2"),
+ static_cast<const SmTextNode *>(pNode001)->GetToken().aText);
+}
+
+/*
+ * This shows that "nospace" turns off the expression's IsUseExtraSpaces(),
+ * but leaves its descendants' flag on.
+ */
+void ParseTest::testNospace()
+{
+ auto pNode = SmParser().Parse("nospace{ nitalic d {F(x) G(x)} }");
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pNode->GetNumSubNodes());
+ const SmNode *pNode0 = pNode->GetSubNode(0);
+ CPPUNIT_ASSERT(pNode0);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Line, pNode0->GetType());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pNode0->GetNumSubNodes());
+ const SmNode *pNode00 = pNode0->GetSubNode(0);
+ CPPUNIT_ASSERT(pNode00);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Expression, pNode00->GetType());
+ CPPUNIT_ASSERT(!static_cast<const SmExpressionNode *>(pNode00)->IsUseExtraSpaces());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pNode00->GetNumSubNodes());
+ const SmNode *pNode000 = pNode00->GetSubNode(0);
+ CPPUNIT_ASSERT(pNode000);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Font, pNode000->GetType());
+ CPPUNIT_ASSERT_EQUAL(OUString("nitalic"),
+ static_cast<const SmFontNode *>(pNode000)->GetToken().aText);
+ const SmNode *pNode001 = pNode00->GetSubNode(1);
+ CPPUNIT_ASSERT(pNode001);
+ CPPUNIT_ASSERT_EQUAL(SmNodeType::Expression, pNode001->GetType());
+ CPPUNIT_ASSERT(static_cast<const SmExpressionNode *>(pNode001)->IsUseExtraSpaces());
+ CPPUNIT_ASSERT_EQUAL(size_t(2), pNode00->GetNumSubNodes());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ParseTest);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/cppunit/test_starmath.cxx b/starmath/qa/cppunit/test_starmath.cxx
new file mode 100644
index 000000000..e26bcd31b
--- /dev/null
+++ b/starmath/qa/cppunit/test_starmath.cxx
@@ -0,0 +1,563 @@
+/* -*- 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 <sal/config.h>
+
+#include <config_features.h>
+#include <vcl/print.hxx>
+
+#include <test/bootstrapfixture.hxx>
+
+#include <smdll.hxx>
+#include <document.hxx>
+#include <view.hxx>
+
+#include <tmpdevice.hxx>
+
+#include <sfx2/sfxmodelfactory.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/request.hxx>
+#include <sfx2/dispatch.hxx>
+
+#include <editeng/editeng.hxx>
+
+#include <sfx2/zoomitem.hxx>
+#include <starmath.hrc>
+#include <memory>
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class Test : public test::BootstrapFixture
+{
+public:
+ Test();
+
+ // init
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ // tests
+ void editUndoRedo();
+ void editMarker();
+ void editFailure();
+ void ParseErrorUnexpectedToken();
+ void ParseErrorPoundExpected();
+ void ParseErrorColorExpected();
+ void ParseErrorLgroupExpected();
+ void ParseErrorRgroupExpected();
+ void ParseErrorLbraceExpected();
+ void ParseErrorRbraceExpected();
+ void ParseErrorParentMismatch();
+ void ParseErrorRightExpected();
+ void ParseErrorFontExpected();
+ void ParseErrorSizeExpected();
+ void ParseErrorDoubleAlign();
+ void ParseErrorDoubleSubsupscript();
+
+ void replacePlaceholder();
+ void viewZoom();
+
+#if HAVE_MORE_FONTS
+ void testSmTmpDeviceRestoreFont();
+#endif
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(editUndoRedo);
+ CPPUNIT_TEST(editMarker);
+ CPPUNIT_TEST(editFailure);
+ CPPUNIT_TEST(ParseErrorUnexpectedToken);
+ CPPUNIT_TEST(ParseErrorPoundExpected);
+ CPPUNIT_TEST(ParseErrorColorExpected);
+ CPPUNIT_TEST(ParseErrorLgroupExpected);
+ CPPUNIT_TEST(ParseErrorRgroupExpected);
+ CPPUNIT_TEST(ParseErrorLbraceExpected);
+ CPPUNIT_TEST(ParseErrorRbraceExpected);
+ CPPUNIT_TEST(ParseErrorParentMismatch);
+ CPPUNIT_TEST(ParseErrorRightExpected);
+ CPPUNIT_TEST(ParseErrorFontExpected);
+ CPPUNIT_TEST(ParseErrorSizeExpected);
+ CPPUNIT_TEST(ParseErrorDoubleAlign);
+ CPPUNIT_TEST(ParseErrorDoubleSubsupscript);
+ CPPUNIT_TEST(replacePlaceholder);
+ CPPUNIT_TEST(viewZoom);
+#if HAVE_MORE_FONTS
+ CPPUNIT_TEST(testSmTmpDeviceRestoreFont);
+#endif
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ SfxBindings m_aBindings;
+ std::unique_ptr<SfxDispatcher> m_pDispatcher;
+ VclPtr<SmCmdBoxWindow> m_pSmCmdBoxWindow;
+ VclPtr<SmEditWindow> m_pEditWindow;
+ SmDocShellRef m_xDocShRef;
+ SmViewShell *m_pViewShell;
+};
+
+Test::Test()
+ : m_pViewShell(nullptr)
+{
+}
+
+void Test::setUp()
+{
+ BootstrapFixture::setUp();
+
+ SmGlobals::ensure();
+
+ m_xDocShRef = new SmDocShell(
+ SfxModelFlags::EMBEDDED_OBJECT |
+ SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+ SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
+ m_xDocShRef->DoInitNew();
+
+ SfxViewFrame *pViewFrame = SfxViewFrame::LoadHiddenDocument(*m_xDocShRef, SFX_INTERFACE_NONE);
+
+ CPPUNIT_ASSERT_MESSAGE("Should have a SfxViewFrame", pViewFrame);
+
+ m_pDispatcher.reset(new SfxDispatcher(pViewFrame));
+ m_aBindings.SetDispatcher(m_pDispatcher.get());
+ m_aBindings.EnterRegistrations();
+ m_pSmCmdBoxWindow.reset(VclPtr<SmCmdBoxWindow>::Create(&m_aBindings, nullptr, nullptr));
+ m_aBindings.LeaveRegistrations();
+ m_pEditWindow = VclPtr<SmEditWindow>::Create(*m_pSmCmdBoxWindow);
+ m_pViewShell = m_pEditWindow->GetView();
+ CPPUNIT_ASSERT_MESSAGE("Should have a SmViewShell", m_pViewShell);
+}
+
+void Test::tearDown()
+{
+ m_pEditWindow.disposeAndClear();
+ m_pSmCmdBoxWindow.disposeAndClear();
+ m_pDispatcher.reset();
+ m_xDocShRef->DoClose();
+ m_xDocShRef.clear();
+
+ BootstrapFixture::tearDown();
+}
+
+#if HAVE_MORE_FONTS
+void Test::testSmTmpDeviceRestoreFont()
+{
+ ScopedVclPtrInstance<Printer> pPrinter;
+ bool bUseMap100th_mm = true;
+
+ OUString aFontName("Linux Libertine G");
+ CPPUNIT_ASSERT(pPrinter->IsFontAvailable(aFontName));
+
+ vcl::Font aOriginalFont = pPrinter->GetFont();
+ aOriginalFont.SetColor(COL_RED);
+ pPrinter->SetTextColor(COL_RED);
+
+ vcl::Font aNewFont;
+
+ {
+ SmTmpDevice aTmpDev(*pPrinter, bUseMap100th_mm);
+
+ aNewFont = pPrinter->GetFont();
+ aNewFont.SetFamilyName(aFontName);
+ aTmpDev.SetFont(aNewFont);
+
+ CPPUNIT_ASSERT_EQUAL(aFontName, pPrinter->GetFont().GetFamilyName());
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPrinter->GetTextColor());
+ }
+
+ CPPUNIT_ASSERT(aNewFont != pPrinter->GetFont());
+ CPPUNIT_ASSERT_EQUAL(COL_RED, pPrinter->GetTextColor());
+}
+#endif
+
+void Test::editMarker()
+{
+ {
+ OUString sMarkedText("<?> under <?> under <?>");
+ m_pEditWindow->SetText(sMarkedText);
+ m_pEditWindow->Flush();
+ OUString sFinalText = m_pEditWindow->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be equal text", sMarkedText, sFinalText);
+ }
+
+ {
+ OUString const sTargetText("a under b under c");
+ ESelection aSelection;
+
+ m_pEditWindow->SelNextMark();
+ m_pEditWindow->Delete();
+ m_pEditWindow->InsertText("a");
+
+ m_pEditWindow->SelNextMark();
+ m_pEditWindow->SelNextMark();
+ m_pEditWindow->Delete();
+ m_pEditWindow->InsertText("c");
+
+ // should be safe i.e. do nothing
+ m_pEditWindow->SelNextMark();
+ aSelection = m_pEditWindow->GetSelection();
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aSelection.nStartPara);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(19), aSelection.nStartPos);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aSelection.nEndPara);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(19), aSelection.nEndPos);
+
+ m_pEditWindow->SelPrevMark();
+ m_pEditWindow->Delete();
+ m_pEditWindow->InsertText("b");
+
+ // tdf#106116: should be safe i.e. do nothing
+ m_pEditWindow->SelPrevMark();
+ aSelection = m_pEditWindow->GetSelection();
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aSelection.nStartPara);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(9), aSelection.nStartPos);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aSelection.nEndPara);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(9), aSelection.nEndPos);
+
+ m_pEditWindow->Flush();
+ OUString sFinalText = m_pEditWindow->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be a under b under c", sTargetText, sFinalText);
+ }
+
+ {
+ m_pEditWindow->SetText(OUString());
+ m_pEditWindow->Flush();
+ }
+}
+
+void Test::editFailure()
+{
+ m_xDocShRef->SetText("color a b over {a/}");
+
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+
+ CPPUNIT_ASSERT_MESSAGE("Should be a SmParseError::ColorExpected",
+ pErrorDesc && pErrorDesc->m_eType == SmParseError::ColorExpected);
+
+ pErrorDesc = m_xDocShRef->GetParser().PrevError();
+
+ CPPUNIT_ASSERT_MESSAGE("Should be a SmParseError::UnexpectedChar",
+ pErrorDesc && pErrorDesc->m_eType == SmParseError::UnexpectedChar);
+
+ pErrorDesc = m_xDocShRef->GetParser().PrevError();
+
+ CPPUNIT_ASSERT_MESSAGE("Should be a SmParseError::RgroupExpected",
+ pErrorDesc && pErrorDesc->m_eType == SmParseError::RgroupExpected);
+
+ const SmErrorDesc *pLastErrorDesc = m_xDocShRef->GetParser().PrevError();
+
+ CPPUNIT_ASSERT_MESSAGE("Should be three syntax errors",
+ pLastErrorDesc && pLastErrorDesc == pErrorDesc);
+}
+
+void Test::ParseErrorUnexpectedToken()
+{
+ m_xDocShRef->SetText("\\foo");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::UnexpectedToken expected",
+ SmParseError::UnexpectedToken, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorPoundExpected()
+{
+ m_xDocShRef->SetText("matrix {1#2##a##b#c}");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::PoundExpected expected",
+ SmParseError::PoundExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorColorExpected()
+{
+ m_xDocShRef->SetText("color 42 x");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::ColorExpected expected",
+ SmParseError::ColorExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorLgroupExpected()
+{
+ m_xDocShRef->SetText("stack 42");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::LgroupExpected expected",
+ SmParseError::LgroupExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorRgroupExpected()
+{
+ m_xDocShRef->SetText("stack {a#b#c)");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::RgroupExpected expected",
+ SmParseError::RgroupExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorLbraceExpected()
+{
+ m_xDocShRef->SetText("left 42");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::LbraceExpected expected",
+ SmParseError::LbraceExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorRbraceExpected()
+{
+ m_xDocShRef->SetText("left ( foo right x");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::RbraceExpected expected",
+ SmParseError::RbraceExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorParentMismatch()
+{
+ m_xDocShRef->SetText("lbrace foo rceil");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::ParentMismatch expected",
+ SmParseError::ParentMismatch, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorRightExpected()
+{
+ m_xDocShRef->SetText("left ( x mline y )");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::RightExpected expected",
+ SmParseError::RightExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorFontExpected()
+{
+ m_xDocShRef->SetText("font small bar");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::FontExpected expected",
+ SmParseError::FontExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorSizeExpected()
+{
+ m_xDocShRef->SetText("size small baz");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::SizeExpected expected",
+ SmParseError::SizeExpected, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorDoubleAlign()
+{
+ m_xDocShRef->SetText("alignl alignc x");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::DoubleAlign expected",
+ SmParseError::DoubleAlign, pErrorDesc->m_eType);
+}
+
+void Test::ParseErrorDoubleSubsupscript()
+{
+ m_xDocShRef->SetText("x_y_z");
+ const SmErrorDesc *pErrorDesc = m_xDocShRef->GetParser().NextError();
+ CPPUNIT_ASSERT(pErrorDesc);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("SmParseError::DoubleSubsupscript expected",
+ SmParseError::DoubleSubsupscript, pErrorDesc->m_eType);
+}
+
+void Test::editUndoRedo()
+{
+ EditEngine &rEditEngine = m_xDocShRef->GetEditEngine();
+
+ OUString sStringOne("a under b");
+ {
+ rEditEngine.SetText(0, sStringOne);
+ m_xDocShRef->UpdateText();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Strings must match", sFinalText, sStringOne);
+ }
+
+ OUString sStringTwo("a over b");
+ {
+ rEditEngine.SetText(0, sStringTwo);
+ m_xDocShRef->UpdateText();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Strings must match", sFinalText, sStringTwo);
+ }
+
+ SfxRequest aUndo(SID_UNDO, SfxCallMode::SYNCHRON, SmDocShell::GetPool());
+
+ {
+ m_xDocShRef->Execute(aUndo);
+ m_xDocShRef->UpdateText();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Strings much match", sFinalText, sStringOne);
+ }
+
+ {
+ m_xDocShRef->Execute(aUndo);
+ m_xDocShRef->UpdateText();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_MESSAGE("Must now be empty", sFinalText.isEmpty());
+ }
+
+ SfxRequest aRedo(SID_REDO, SfxCallMode::SYNCHRON, SmDocShell::GetPool());
+ {
+ m_xDocShRef->Execute(aRedo);
+ m_xDocShRef->UpdateText();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Strings much match", sFinalText, sStringOne);
+ }
+
+ {
+ rEditEngine.SetText(0, OUString());
+ m_xDocShRef->UpdateText();
+ rEditEngine.ClearModifyFlag();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_MESSAGE("Must be empty", sFinalText.isEmpty());
+ }
+
+}
+
+void Test::replacePlaceholder()
+{
+ // Test the placeholder replacement. In this case, testing 'a + b', it
+ // should return '+a + b' when selecting '+<?>' in ElementsDock
+ m_pEditWindow->SetText("a + b");
+ m_pEditWindow->SelectAll();
+ m_pEditWindow->InsertText("+<?>");
+ OUString sFinalText = m_pEditWindow->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be '+a + b'", OUString("+a + b"), sFinalText);
+}
+
+void Test::viewZoom()
+{
+ sal_uInt16 nOrigZoom, nNextZoom, nFinalZoom;
+
+ EditEngine &rEditEngine = m_xDocShRef->GetEditEngine();
+
+ OUString sStringOne("a under b");
+ {
+ rEditEngine.SetText(0, sStringOne);
+ m_xDocShRef->UpdateText();
+ OUString sFinalText = m_xDocShRef->GetText();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Strings must match", sFinalText, sStringOne);
+ }
+
+ SmGraphicWindow &rGraphicWindow = m_pViewShell->GetGraphicWindow();
+ rGraphicWindow.SetSizePixel(Size(1024, 800));
+ nOrigZoom = rGraphicWindow.GetZoom();
+
+ {
+ SfxRequest aZoomIn(SID_ZOOMIN, SfxCallMode::SYNCHRON, m_pViewShell->GetPool());
+ m_pViewShell->Execute(aZoomIn);
+ nNextZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should be bigger", nNextZoom > nOrigZoom);
+ }
+
+ {
+ SfxRequest aZoomOut(SID_ZOOMOUT, SfxCallMode::SYNCHRON, m_pViewShell->GetPool());
+ m_pViewShell->Execute(aZoomOut);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be equal", nOrigZoom, nFinalZoom);
+ }
+
+ sal_uInt16 nOptimalZoom=0;
+
+ {
+ SfxRequest aZoom(SID_ZOOM_OPTIMAL, SfxCallMode::SYNCHRON, m_pViewShell->GetPool());
+ m_pViewShell->Execute(aZoom);
+ nOptimalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should be about 800%", nOptimalZoom > nOrigZoom);
+ }
+
+ {
+ SfxItemSet aSet(SmDocShell::GetPool(), svl::Items<SID_ATTR_ZOOM, SID_ATTR_ZOOM>{});
+ aSet.Put(SvxZoomItem(SvxZoomType::OPTIMAL, 0));
+ SfxRequest aZoom(SID_ATTR_ZOOM, SfxCallMode::SYNCHRON, aSet);
+ m_pViewShell->Execute(aZoom);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be optimal zoom", nOptimalZoom, nFinalZoom);
+ }
+
+//To-Do: investigate GetPrinter logic of SvxZoomType::PAGEWIDTH/SvxZoomType::WHOLEPAGE to ensure
+//consistent value regardless of
+#if 0
+ {
+ SfxRequest aZoomOut(SID_ZOOMOUT, SfxCallMode::SYNCHRON, m_pViewShell->GetPool());
+ m_pViewShell->Execute(aZoomOut);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should not be optimal zoom", nFinalZoom != nOptimalZoom);
+
+ SfxItemSet aSet(m_xDocShRef->GetPool(), SID_ATTR_ZOOM, SID_ATTR_ZOOM);
+ aSet.Put(SvxZoomItem(SvxZoomType::PAGEWIDTH, 0));
+ SfxRequest aZoom(SID_ATTR_ZOOM, SfxCallMode::SYNCHRON, aSet);
+ m_pViewShell->Execute(aZoom);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should be same as optimal zoom", nFinalZoom == nOptimalZoom);
+ }
+
+ {
+ SfxRequest aZoomOut(SID_ZOOMOUT, SfxCallMode::SYNCHRON, m_pViewShell->GetPool());
+ m_pViewShell->Execute(aZoomOut);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should not be optimal zoom", nFinalZoom != nOptimalZoom);
+
+ SfxItemSet aSet(m_xDocShRef->GetPool(), SID_ATTR_ZOOM, SID_ATTR_ZOOM);
+ aSet.Put(SvxZoomItem(SvxZoomType::WHOLEPAGE, 0));
+ SfxRequest aZoom(SID_ATTR_ZOOM, SfxCallMode::SYNCHRON, aSet);
+ m_pViewShell->Execute(aZoom);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should be same as optimal zoom", nFinalZoom == nOptimalZoom);
+ }
+#endif
+
+ {
+ SfxRequest aZoomOut(SID_ZOOMOUT, SfxCallMode::SYNCHRON, m_pViewShell->GetPool());
+ m_pViewShell->Execute(aZoomOut);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_MESSAGE("Should not be optimal zoom", nFinalZoom != nOptimalZoom);
+
+ SfxItemSet aSet(SmDocShell::GetPool(), svl::Items<SID_ATTR_ZOOM, SID_ATTR_ZOOM>{});
+ aSet.Put(SvxZoomItem(SvxZoomType::PERCENT, 50));
+ SfxRequest aZoom(SID_ATTR_ZOOM, SfxCallMode::SYNCHRON, aSet);
+ m_pViewShell->Execute(aZoom);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be 50%", static_cast<sal_uInt16>(50), nFinalZoom);
+ }
+
+ {
+ SfxItemSet aSet(SmDocShell::GetPool(), svl::Items<SID_ATTR_ZOOM, SID_ATTR_ZOOM>{});
+ aSet.Put(SvxZoomItem(SvxZoomType::PERCENT, 5));
+ SfxRequest aZoom(SID_ATTR_ZOOM, SfxCallMode::SYNCHRON, aSet);
+ m_pViewShell->Execute(aZoom);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be Clipped to 25%", static_cast<sal_uInt16>(25), nFinalZoom);
+ }
+
+ {
+ SfxItemSet aSet(SmDocShell::GetPool(), svl::Items<SID_ATTR_ZOOM, SID_ATTR_ZOOM>{});
+ aSet.Put(SvxZoomItem(SvxZoomType::PERCENT, 1000));
+ SfxRequest aZoom(SID_ATTR_ZOOM, SfxCallMode::SYNCHRON, aSet);
+ m_pViewShell->Execute(aZoom);
+ nFinalZoom = rGraphicWindow.GetZoom();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Should be Clipped to 800%", static_cast<sal_uInt16>(800), nFinalZoom);
+ }
+
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/extras/data/color.mml b/starmath/qa/extras/data/color.mml
new file mode 100644
index 000000000..6aad87b21
--- /dev/null
+++ b/starmath/qa/extras/data/color.mml
@@ -0,0 +1,20 @@
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mi mathcolor="black">b</mi>
+ <mi mathcolor="white">w</mi>
+ <mi mathcolor="red">r</mi>
+ <mi mathcolor="green">g</mi>
+ <mi mathcolor="blue">b</mi>
+ <mi mathcolor="yellow">y</mi>
+ <mi mathcolor="silver">s</mi>
+ <mi mathcolor="gray">g</mi>
+ <mi mathcolor="maroon">m</mi>
+ <mi mathcolor="purple">p</mi>
+ <mi mathcolor="lime">l</mi>
+ <mi mathcolor="olive">o</mi>
+ <mi mathcolor="navy">n</mi>
+ <mi mathcolor="teal">t</mi>
+ <mi mathcolor="aqua">a</mi>
+ <mi mathcolor="fuchsia">f</mi>
+ </mrow>
+</math>
diff --git a/starmath/qa/extras/data/maction.mml b/starmath/qa/extras/data/maction.mml
new file mode 100644
index 000000000..365008799
--- /dev/null
+++ b/starmath/qa/extras/data/maction.mml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mtable>
+ <mtr><maction actiontype="toggle"><mn>1</mn><mn>0</mn><mn>0</mn></maction></mtr>
+ <mtr><maction actiontype="toggle" selection="2"><mn>0</mn><mn>2</mn><mn>0</mn></maction></mtr>
+ <mtr><maction actiontype="toggle" selection="3"><mn>0</mn><mn>0</mn><mn>3</mn></maction></mtr>
+ </mtable>
+ </mrow>
+</math>
diff --git a/starmath/qa/extras/data/mspace.mml b/starmath/qa/extras/data/mspace.mml
new file mode 100644
index 000000000..190651094
--- /dev/null
+++ b/starmath/qa/extras/data/mspace.mml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mi>a</mi>
+ <mspace />
+ <mi>b</mi>
+ <mspace width="2em" />
+ <mi>c</mi>
+ <mspace width="5.5em" />
+ <mi>d</mi>
+ </mrow>
+</math>
diff --git a/starmath/qa/extras/data/ns-prefix-math.mml b/starmath/qa/extras/data/ns-prefix-math.mml
new file mode 100644
index 000000000..c4c961223
--- /dev/null
+++ b/starmath/qa/extras/data/ns-prefix-math.mml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<math:math xmlns:math="http://www.w3.org/1998/Math/MathML">
+ <math:msup>
+ <math:mfenced>
+ <math:mrow>
+ <math:mi>a</math:mi>
+ <math:mo>+</math:mo>
+ <math:mi>b</math:mi>
+ </math:mrow>
+ </math:mfenced>
+ <math:mn>2</math:mn>
+ </math:msup>
+</math:math>
diff --git a/starmath/qa/extras/data/simple.mml b/starmath/qa/extras/data/simple.mml
new file mode 100644
index 000000000..822d1a709
--- /dev/null
+++ b/starmath/qa/extras/data/simple.mml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msup>
+ <mfenced>
+ <mrow>
+ <mi>a</mi>
+ <mo>+</mo>
+ <mi>b</mi>
+ </mrow>
+ </mfenced>
+ <mn>2</mn>
+ </msup>
+</math>
diff --git a/starmath/qa/extras/data/tdf103430.mml b/starmath/qa/extras/data/tdf103430.mml
new file mode 100644
index 000000000..92fba05dc
--- /dev/null
+++ b/starmath/qa/extras/data/tdf103430.mml
@@ -0,0 +1,15 @@
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mfrac>
+ <mrow>
+ <msup>
+ <mo form="prefix" rspace="0">d</mo>
+ <mn>2</mn>
+ </msup>
+ <mi mathvariant="normal" mathcolor="blue">y</mi>
+ </mrow>
+ <mrow>
+ <mo fontstyle="italic" fontweight="bold" mathvariant="normal" form="prefix" rspace="0">d</mo>
+ <mi fontfamily="serif" mathvariant="sans-serif-bold-italic" mathcolor="red">x</mi>
+ </mrow>
+ </mfrac>
+</math>
diff --git a/starmath/qa/extras/data/tdf103500.mml b/starmath/qa/extras/data/tdf103500.mml
new file mode 100644
index 000000000..7c4966985
--- /dev/null
+++ b/starmath/qa/extras/data/tdf103500.mml
@@ -0,0 +1,41 @@
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mrow>
+ <munderover>
+ <mo stretchy="false">∫</mo>
+ <mi>a</mi>
+ <mi>b</mi>
+ </munderover>
+ <mrow>
+ <mfrac>
+ <mn>1</mn>
+ <mi>x</mi>
+ </mfrac>
+ <mspace width="0.5em"/>
+ <mstyle mathvariant="normal">
+ <mi mathvariant="normal">d</mi>
+ </mstyle>
+ <mi>x</mi>
+ </mrow>
+ </mrow>
+ <mo stretchy="false">=</mo>
+ <mrow>
+ <munderover>
+ <mo stretchy="true">∫</mo>
+ <mi>a</mi>
+ <mi>b</mi>
+ </munderover>
+ <mrow>
+ <mfrac>
+ <mn>1</mn>
+ <mi>y</mi>
+ </mfrac>
+ <mspace width="0.5em"/>
+ <mstyle mathvariant="normal">
+ <mi mathvariant="normal">d</mi>
+ </mstyle>
+ <mi>y</mi>
+ </mrow>
+ </mrow>
+ </mrow>
+</math>
diff --git a/starmath/qa/extras/data/tdf99556-1.mml b/starmath/qa/extras/data/tdf99556-1.mml
new file mode 100644
index 000000000..0eae8b2df
--- /dev/null
+++ b/starmath/qa/extras/data/tdf99556-1.mml
@@ -0,0 +1,3 @@
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+<msqrt/>
+</math>
diff --git a/starmath/qa/extras/mmlexport-test.cxx b/starmath/qa/extras/mmlexport-test.cxx
new file mode 100644
index 000000000..18732e09a
--- /dev/null
+++ b/starmath/qa/extras/mmlexport-test.cxx
@@ -0,0 +1,152 @@
+/* -*- 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 <sal/config.h>
+
+#include <o3tl/cppunittraitshelper.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <test/xmltesttools.hxx>
+#include <unotools/tempfile.hxx>
+
+#include <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/sfxmodelfactory.hxx>
+
+#include <document.hxx>
+#include <smdll.hxx>
+
+#include <memory>
+
+namespace {
+
+using namespace ::com::sun::star;
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+class MathMLExportTest : public test::BootstrapFixture, public XmlTestTools
+{
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ void testBlank();
+ void testTdf97049();
+ void testTdf101022();
+
+ CPPUNIT_TEST_SUITE(MathMLExportTest);
+ CPPUNIT_TEST(testBlank);
+ CPPUNIT_TEST(testTdf97049);
+ CPPUNIT_TEST(testTdf101022);
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ virtual void registerNamespaces(xmlXPathContextPtr &pXmlXPathCtx) override;
+
+private:
+ xmlDocUniquePtr exportAndParse();
+
+ SmDocShellRef mxDocShell;
+};
+
+void MathMLExportTest::setUp()
+{
+ BootstrapFixture::setUp();
+ SmGlobals::ensure();
+ mxDocShell = new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT |
+ SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+ SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
+}
+
+void MathMLExportTest::tearDown()
+{
+ if (mxDocShell.is())
+ mxDocShell->DoClose();
+ BootstrapFixture::tearDown();
+}
+
+void MathMLExportTest::registerNamespaces(xmlXPathContextPtr &pXmlXPathCtx)
+{
+ xmlXPathRegisterNs(pXmlXPathCtx, BAD_CAST("m"), BAD_CAST("http://www.w3.org/1998/Math/MathML"));
+}
+
+xmlDocUniquePtr MathMLExportTest::exportAndParse()
+{
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ SfxMedium aStoreMedium(aTempFile.GetURL(), StreamMode::STD_WRITE);
+ std::shared_ptr<const SfxFilter> pExportFilter = SfxFilter::GetFilterByName(MATHML_XML);
+ aStoreMedium.SetFilter(pExportFilter);
+ CPPUNIT_ASSERT(mxDocShell->ConvertTo(aStoreMedium));
+ aStoreMedium.Commit();
+ xmlDocUniquePtr pDoc = parseXml(aTempFile);
+ CPPUNIT_ASSERT(pDoc);
+ return pDoc;
+}
+
+void MathMLExportTest::testBlank()
+{
+ mxDocShell->SetText("x`y~~z");
+ xmlDocUniquePtr pDoc = exportAndParse();
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mspace[1]", "width", "0.5em");
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mspace[2]", "width", "4em");
+}
+
+void MathMLExportTest::testTdf97049()
+{
+ mxDocShell->SetText("intd {{1 over x} dx}");
+ xmlDocUniquePtr pDoc = exportAndParse();
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mo[1]", "stretchy", "true");
+ auto aContent = getXPathContent(pDoc, "/m:math/m:semantics/m:mrow/m:mo[1]");
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aContent.getLength());
+ CPPUNIT_ASSERT_EQUAL(u'\x222B', aContent[0]);
+}
+
+void MathMLExportTest::testTdf101022()
+{
+#define CHECK_MATHVARIANT(capital, small) do \
+ { \
+ mxDocShell->SetText("%GAMMA %iGAMMA {ital %GAMMA} {nitalic %iGAMMA} " \
+ "%gamma %igamma {ital %gamma} {nitalic %igamma}"); \
+ xmlDocUniquePtr pDoc = exportAndParse(); \
+ if (capital) \
+ assertXPathNoAttribute(pDoc, "/m:math/m:semantics/m:mrow/m:mi[1]", "mathvariant"); \
+ else \
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mi[1]", "mathvariant", "normal"); \
+ assertXPathNoAttribute(pDoc, "/m:math/m:semantics/m:mrow/m:mstyle[1]/m:mi[1]", "mathvariant"); \
+ assertXPathNoAttribute(pDoc, "/m:math/m:semantics/m:mrow/m:mi[2]", "mathvariant"); \
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mstyle[2]/m:mi[1]", "mathvariant", "normal"); \
+ if (small) \
+ assertXPathNoAttribute(pDoc, "/m:math/m:semantics/m:mrow/m:mi[3]", "mathvariant"); \
+ else \
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mi[3]", "mathvariant", "normal"); \
+ assertXPathNoAttribute(pDoc, "/m:math/m:semantics/m:mrow/m:mstyle[3]/m:mi[1]", "mathvariant"); \
+ assertXPathNoAttribute(pDoc, "/m:math/m:semantics/m:mrow/m:mi[4]", "mathvariant"); \
+ assertXPath(pDoc, "/m:math/m:semantics/m:mrow/m:mstyle[4]/m:mi[1]", "mathvariant", "normal"); \
+ mxDocShell->SetText(""); \
+ } \
+ while (false)
+
+ CHECK_MATHVARIANT(false, true); // default mode 2
+
+ mxDocShell->SetGreekCharStyle(1); // mode 1
+ CHECK_MATHVARIANT(true, true);
+
+ mxDocShell->SetGreekCharStyle(0); // mode 0
+ CHECK_MATHVARIANT(false, false);
+
+#undef CHECK_MATHVARIANT
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(MathMLExportTest);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/extras/mmlimport-test.cxx b/starmath/qa/extras/mmlimport-test.cxx
new file mode 100644
index 000000000..e455e60d8
--- /dev/null
+++ b/starmath/qa/extras/mmlimport-test.cxx
@@ -0,0 +1,173 @@
+/* -*- 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 <sal/config.h>
+#include <test/bootstrapfixture.hxx>
+
+#include <comphelper/fileformat.h>
+
+#include <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/sfxmodelfactory.hxx>
+
+#include <document.hxx>
+#include <smdll.hxx>
+
+namespace {
+
+using namespace ::com::sun::star;
+
+typedef tools::SvRef<SmDocShell> SmDocShellRef;
+
+class Test : public test::BootstrapFixture
+{
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ void testColor();
+ void testSimple();
+ void testNsPrefixMath();
+ void testMaction();
+ void testMspace();
+ void testtdf99556();
+ void testTdf103430();
+ void testTdf103500();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testColor);
+ CPPUNIT_TEST(testSimple);
+ CPPUNIT_TEST(testNsPrefixMath);
+ CPPUNIT_TEST(testMaction);
+ CPPUNIT_TEST(testMspace);
+ CPPUNIT_TEST(testtdf99556);
+ CPPUNIT_TEST(testTdf103430);
+ CPPUNIT_TEST(testTdf103500);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ void loadURL(const OUString &rURL)
+ {
+ // Cf.
+ // filter/source/config/fragments/filters/MathML_XML__Math_.xcu
+ auto pFilter = std::make_shared<SfxFilter>(MATHML_XML,
+ OUString(),
+ SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::TEMPLATE,
+ SotClipboardFormatId::STARCALC_8,
+ "MathML 2.0",
+ OUString(),
+ OUString(),
+ "private:factory/smath*");
+ pFilter->SetVersion(SOFFICE_FILEFORMAT_60);
+
+ mxDocShell = new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT |
+ SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+ SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
+
+ SfxMedium* pSrcMed = new SfxMedium(rURL, StreamMode::STD_READ);
+ pSrcMed->SetFilter(pFilter);
+ pSrcMed->UseInteractionHandler(false);
+ bool bLoaded = mxDocShell->DoLoad(pSrcMed);
+ CPPUNIT_ASSERT_MESSAGE(OUStringToOString("failed to load " + rURL, RTL_TEXTENCODING_UTF8).getStr(),
+ bLoaded);
+ }
+
+ SmDocShellRef mxDocShell;
+};
+
+void Test::setUp()
+{
+ BootstrapFixture::setUp();
+ SmGlobals::ensure();
+}
+
+void Test::tearDown()
+{
+ if (mxDocShell.is()) mxDocShell->DoClose();
+ BootstrapFixture::tearDown();
+}
+
+void Test::testColor()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/color.mml"));
+ CPPUNIT_ASSERT_EQUAL(OUString("{{color black b}"
+ " {color white w}"
+ " {color red r}"
+ " {color green g}"
+ " {color blue b}"
+ " {color yellow y}"
+ " {color silver s}"
+ " {color gray g}"
+ " {color maroon m}"
+ " {color purple p}"
+ " {color lime l}"
+ " {color olive o}"
+ " {color navy n}"
+ " {color teal t}"
+ " {color aqua a}"
+ " {color fuchsia f}}"),
+ mxDocShell->GetText());
+}
+
+void Test::testSimple()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/simple.mml"));
+ OUString const sExpected("left ( {a + b} right )^2");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("loaded text", sExpected, mxDocShell->GetText());
+}
+
+void Test::testNsPrefixMath()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/ns-prefix-math.mml"));
+ OUString const sExpected("left ( {a + b} right )^2");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("loaded text", sExpected, mxDocShell->GetText());
+}
+
+void Test::testMaction()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/maction.mml"));
+ OUString const sExpected("matrix {1 ## 2 ## 3}");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("loaded text", sExpected, mxDocShell->GetText());
+}
+
+void Test::testMspace()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/mspace.mml"));
+ CPPUNIT_ASSERT_EQUAL(OUString("{a b ~ c ~~``` d}"), mxDocShell->GetText());
+}
+
+void Test::testtdf99556()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/tdf99556-1.mml"));
+ OUString const sExpected("sqrt { {} }");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("loaded text", sExpected, mxDocShell->GetText());
+}
+
+void Test::testTdf103430()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/tdf103430.mml"));
+ CPPUNIT_ASSERT_EQUAL(OUString("{{nitalic d}^2 {nitalic {color blue y}}} over {{nitalic d} {font sans {bold {italic {color red x}}}}}"),
+ mxDocShell->GetText());
+}
+
+void Test::testTdf103500()
+{
+ loadURL(m_directories.getURLFromSrc("starmath/qa/extras/data/tdf103500.mml"));
+ CPPUNIT_ASSERT_EQUAL(OUString("{{ int csub a csup b {1 over x ` {nitalic d} x}} = {intd csub a csup b {1 over y ` {nitalic d} y}}}"),
+ mxDocShell->GetText());
+}
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/unit/data/starmath-dialogs-test.txt b/starmath/qa/unit/data/starmath-dialogs-test.txt
new file mode 100644
index 000000000..7847bc71f
--- /dev/null
+++ b/starmath/qa/unit/data/starmath-dialogs-test.txt
@@ -0,0 +1,47 @@
+# -*- 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/.
+#
+
+# This file contains all dialogs that the unit tests in the module
+# will work on if it is in script mode. It will read one-by-one,
+# try to open it and create a screenshot that will be saved in
+# workdir/screenshots using the pattern of the ui-file name.
+#
+# Syntax:
+# - empty lines are allowed
+# - lines starting with '#' are treated as comment
+# - all other lines should contain a *.ui filename in the same
+# notation as in the dialog constructors (see code)
+
+#
+# The 'known' dialogs which have a hard-coded representation
+# in registerKnownDialogsByID/createDialogByID
+#
+
+# No known dialogs in starmath for now
+
+#
+# Dialogs without a hard-coded representation. These will
+# be visualized using a fallback based on VclBuilder
+#
+
+# currently deactivated, leads to problems and the test to not work
+# This is typically a hint that these should be hard-coded in the
+# test case since they need some document and model data to work
+
+modules/smath/ui/printeroptions.ui
+modules/smath/ui/smathsettings.ui
+modules/smath/ui/fontdialog.ui
+modules/smath/ui/fontsizedialog.ui
+modules/smath/ui/fonttypedialog.ui
+modules/smath/ui/spacingdialog.ui
+modules/smath/ui/alignmentdialog.ui
+modules/smath/ui/catalogdialog.ui
+modules/smath/ui/symdefinedialog.ui
+modules/smath/ui/savedefaultsdialog.ui
+modules/smath/ui/dockingelements.ui
diff --git a/starmath/qa/unit/starmath-dialogs-test.cxx b/starmath/qa/unit/starmath-dialogs-test.cxx
new file mode 100644
index 000000000..42e6ef45c
--- /dev/null
+++ b/starmath/qa/unit/starmath-dialogs-test.cxx
@@ -0,0 +1,63 @@
+/* -*- 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 <sal/config.h>
+#include <test/screenshot_test.hxx>
+#include <vcl/abstdlg.hxx>
+
+using namespace ::com::sun::star;
+
+/// Test opening a dialog in starmath
+class StarmathDialogsTest : public ScreenshotTest
+{
+private:
+ /// helper method to populate KnownDialogs, called in setUp(). Needs to be
+ /// written and has to add entries to KnownDialogs
+ virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override;
+
+ /// dialog creation for known dialogs by ID. Has to be implemented for
+ /// each registered known dialog
+ virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override;
+
+public:
+ StarmathDialogsTest();
+
+ // try to open a dialog
+ void openAnyDialog();
+
+ CPPUNIT_TEST_SUITE(StarmathDialogsTest);
+ CPPUNIT_TEST(openAnyDialog);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+StarmathDialogsTest::StarmathDialogsTest()
+{
+}
+
+void StarmathDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/)
+{
+ // fill map of known dialogs
+}
+
+VclPtr<VclAbstractDialog> StarmathDialogsTest::createDialogByID(sal_uInt32 /*nID*/)
+{
+ return nullptr;
+}
+
+void StarmathDialogsTest::openAnyDialog()
+{
+ /// process input file containing the UXMLDescriptions of the dialogs to dump
+ processDialogBatchFile("starmath/qa/unit/data/starmath-dialogs-test.txt");
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(StarmathDialogsTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/qa/unoapi/knownissues.xcl b/starmath/qa/unoapi/knownissues.xcl
new file mode 100644
index 000000000..2fedc6e55
--- /dev/null
+++ b/starmath/qa/unoapi/knownissues.xcl
@@ -0,0 +1,39 @@
+#
+# 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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+### i23394 ###
+sm.XMLImporter::com::sun::star::xml::sax::XDocumentHandler
+sm.XMLMetaImporter::com::sun::star::xml::sax::XDocumentHandler
+
+### i88645 ###
+sm.XMLExporter::com::sun::star::document::XFilter
+
+### i94322 ###
+sm.SmEditAccessible::com::sun::star::accessibility::XAccessibleEventBroadcaster
+
+### i94320 ###
+sm.SmGraphicAccessible::com::sun::star::accessibility::XAccessibleEventBroadcaster
+
+### i94275 ###
+sm.SmGraphicAccessible::com::sun::star::accessibility::XAccessibleText
+
+### i111220 ###
+sm.XMLMetaExporter::com::sun::star::document::XFilter
+
+### i112743 ###
+sm.XMLSettingsExporter::com::sun::star::document::XFilter
diff --git a/starmath/qa/unoapi/sm.sce b/starmath/qa/unoapi/sm.sce
new file mode 100644
index 000000000..4e7b278a7
--- /dev/null
+++ b/starmath/qa/unoapi/sm.sce
@@ -0,0 +1,26 @@
+#
+# 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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+-o sm.SmEditAccessible
+-o sm.SmGraphicAccessible
+-o sm.SmModel
+-o sm.XMLExporter
+-o sm.XMLImporter
+-o sm.XMLMetaExporter
+-o sm.XMLMetaImporter
+-o sm.XMLSettingsExporter
+-o sm.XMLSettingsImporter