summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColomban Wendling <cwendling@hypra.fr>2022-07-21 21:51:21 +0200
committerMichael Weghorn <m.weghorn@posteo.de>2022-08-01 17:03:40 +0200
commit0185ddd6d5f0324ba57b3fa36229103a6b27138e (patch)
tree81a2249571acbfa25391bb1fcfa05e8d8c5e602d
parent56d5a31d6ec2b45e43659be4b5fd4964de29e61f (diff)
Add infrastructure and basic tests including slight UI interaction
This introduces a couple helper base classes for implementing accessibility tests more easily, and includes a few tests as examples, including basic document layout check and minimal UI interaction through menus. Change-Id: I8961af8be1e7d52dc55fe27c758806d9b4c3c5d9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137337 Tested-by: Jenkins Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
-rw-r--r--include/test/a11y/AccessibilityTools.hxx12
-rw-r--r--include/test/a11y/accessibletestbase.hxx138
-rw-r--r--include/test/a11y/swaccessibletestbase.hxx58
-rw-r--r--sc/CppunitTest_sc_a11y.mk39
-rw-r--r--sc/Module_sc.mk1
-rw-r--r--sc/qa/extras/accessibility/basics.cxx90
-rw-r--r--sw/CppunitTest_sw_a11y.mk38
-rw-r--r--sw/Module_sw.mk1
-rw-r--r--sw/qa/extras/accessibility/basics.cxx96
-rw-r--r--test/Library_subsequenttest.mk2
-rw-r--r--test/source/a11y/AccessibilityTools.cxx50
-rw-r--r--test/source/a11y/accessibletestbase.cxx260
-rw-r--r--test/source/a11y/swaccessibletestbase.cxx135
13 files changed, 907 insertions, 13 deletions
diff --git a/include/test/a11y/AccessibilityTools.hxx b/include/test/a11y/AccessibilityTools.hxx
index e56c68c9bebb..1efd0b9f2960 100644
--- a/include/test/a11y/AccessibilityTools.hxx
+++ b/include/test/a11y/AccessibilityTools.hxx
@@ -38,6 +38,18 @@ public:
static const sal_Int32 MAX_CHILDREN = 500;
static css::uno::Reference<css::accessibility::XAccessibleContext>
+ getAccessibleObjectForPredicate(
+ const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx,
+ const std::function<
+ bool(const css::uno::Reference<css::accessibility::XAccessibleContext>&)>& cPredicate);
+ static css::uno::Reference<css::accessibility::XAccessibleContext>
+ getAccessibleObjectForPredicate(
+ const css::uno::Reference<css::accessibility::XAccessible>& xAcc,
+ const std::function<
+ bool(const css::uno::Reference<css::accessibility::XAccessibleContext>&)>& cPredicate);
+ static css::uno::Reference<css::accessibility::XAccessibleContext> getAccessibleObjectForRole(
+ const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx, sal_Int16 role);
+ static css::uno::Reference<css::accessibility::XAccessibleContext>
getAccessibleObjectForRole(const css::uno::Reference<css::accessibility::XAccessible>& xacc,
sal_Int16 role);
diff --git a/include/test/a11y/accessibletestbase.hxx b/include/test/a11y/accessibletestbase.hxx
new file mode 100644
index 000000000000..50a39f63a7dd
--- /dev/null
+++ b/include/test/a11y/accessibletestbase.hxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <test/testdllapi.hxx>
+
+#include <deque>
+#include <string>
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <vcl/ITiledRenderable.hxx>
+
+#include <rtl/ustring.hxx>
+#include <test/bootstrapfixture.hxx>
+
+#include "AccessibilityTools.hxx"
+
+namespace test
+{
+class OOO_DLLPUBLIC_TEST AccessibleTestBase : public test::BootstrapFixture
+{
+protected:
+ css::uno::Reference<css::frame::XDesktop2> mxDesktop;
+ css::uno::Reference<css::lang::XComponent> mxDocument;
+ css::uno::Reference<css::awt::XWindow> mxWindow;
+
+ static bool isDocumentRole(const sal_Int16 role);
+
+ virtual void load(const rtl::OUString& sURL);
+ virtual void loadFromSrc(const rtl::OUString& sSrcPath);
+ void close();
+ css::uno::Reference<css::accessibility::XAccessibleContext> getWindowAccessibleContext();
+ virtual css::uno::Reference<css::accessibility::XAccessibleContext>
+ getDocumentAccessibleContext();
+
+ void documentPostKeyEvent(int nType, int nCharCode, int nKeyCode)
+ {
+ vcl::ITiledRenderable* pTiledRenderable
+ = dynamic_cast<vcl::ITiledRenderable*>(mxDocument.get());
+ CPPUNIT_ASSERT(pTiledRenderable);
+ pTiledRenderable->postKeyEvent(nType, nCharCode, nKeyCode);
+ }
+
+ static css::uno::Reference<css::accessibility::XAccessibleContext> getFirstRelationTargetOfType(
+ const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext,
+ sal_Int16 relationType);
+
+ /**
+ * @brief Tries to list all children of an accessible
+ * @param xContext An XAccessibleContext object
+ * @returns The list of all children (but no more than @c AccessibilityTools::MAX_CHILDREN)
+ *
+ * This fetches children of @p xContext. This would ideally just be the same than iterating
+ * over children the regular way up to @c AccessibilityTools::MAX_CHILDREN, but unfortunately
+ * some components (Writer, Impress, ...) do not provide all their children the regular way and
+ * require specifics to include them.
+ *
+ * There is no guarantee on *which* children are returned if there are more than
+ * @c AccessibilityTools::MAX_CHILDREN -- yet they will always be the same in a given context.
+ */
+ virtual std::deque<css::uno::Reference<css::accessibility::XAccessibleContext>>
+ getAllChildren(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
+
+ void dumpA11YTree(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext,
+ const int depth = 0);
+
+ css::uno::Reference<css::accessibility::XAccessibleContext>
+ getItemFromName(const css::uno::Reference<css::accessibility::XAccessibleContext>& xMenuCtx,
+ std::u16string_view name);
+ bool
+ activateMenuItem(const css::uno::Reference<css::accessibility::XAccessibleAction>& xAction);
+ /* just convenience not to have to query accessibility::XAccessibleAction manually */
+ bool activateMenuItem(const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx)
+ {
+ return activateMenuItem(css::uno::Reference<css::accessibility::XAccessibleAction>(
+ xCtx, css::uno::UNO_QUERY_THROW));
+ }
+
+ /* convenience to get a menu item from a list of menu item names. Unlike
+ * getItemFromName(context, name), this requires subsequently found items to implement
+ * XAccessibleAction, as each but the last item will be activated before looking for
+ * the next one, to account for the fact menus might not be fully populated before being
+ * activated. */
+ template <typename... Ts>
+ css::uno::Reference<css::accessibility::XAccessibleContext>
+ getItemFromName(const css::uno::Reference<css::accessibility::XAccessibleContext>& xMenuCtx,
+ std::u16string_view name, Ts... names)
+ {
+ auto item = getItemFromName(xMenuCtx, name);
+ CPPUNIT_ASSERT(item.is());
+ activateMenuItem(item);
+ return getItemFromName(item, names...);
+ }
+
+ /* convenience to activate an item by its name and all its parent menus up to xMenuCtx.
+ * @see getItemFromName() */
+ template <typename... Ts>
+ bool
+ activateMenuItem(const css::uno::Reference<css::accessibility::XAccessibleContext>& xMenuCtx,
+ Ts... names)
+ {
+ auto item = getItemFromName(xMenuCtx, names...);
+ CPPUNIT_ASSERT(item.is());
+ return activateMenuItem(item);
+ }
+
+ /* convenience to activate an item by its name and all its parent menus up to the main window
+ * menu bar */
+ template <typename... Ts> bool activateMenuItem(Ts... names)
+ {
+ auto menuBar = AccessibilityTools::getAccessibleObjectForRole(
+ getWindowAccessibleContext(), css::accessibility::AccessibleRole::MENU_BAR);
+ CPPUNIT_ASSERT(menuBar.is());
+ return activateMenuItem(menuBar, names...);
+ }
+
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/include/test/a11y/swaccessibletestbase.hxx b/include/test/a11y/swaccessibletestbase.hxx
new file mode 100644
index 000000000000..a8ed42a4dcef
--- /dev/null
+++ b/include/test/a11y/swaccessibletestbase.hxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <test/testdllapi.hxx>
+
+#include <deque>
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+
+#include "accessibletestbase.hxx"
+
+namespace test
+{
+class OOO_DLLPUBLIC_TEST SwAccessibleTestBase : public AccessibleTestBase
+{
+private:
+ void collectText(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext,
+ rtl::OUStringBuffer& buffer, bool onlyChildren = false);
+
+protected:
+ static css::uno::Reference<css::accessibility::XAccessibleContext> getPreviousFlowingSibling(
+ const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
+ static css::uno::Reference<css::accessibility::XAccessibleContext> getNextFlowingSibling(
+ const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
+
+ /**
+ * This fetches regular children plus siblings linked with FLOWS_TO/FLOWS_FROM which are not
+ * already in the regular children set. This is required because most offscreen children of the
+ * document contents are not listed as part of their parent children, but as FLOWS_* reference
+ * from one to the next.
+ * There is currently no guarantee all children will be listed, and it is fairly likely
+ * offscreen frames and tables might be missing for example.
+ */
+ virtual std::deque<css::uno::Reference<css::accessibility::XAccessibleContext>> getAllChildren(
+ const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext) override;
+
+ /** Collects contents of @p xContext in a dummy markup form */
+ OUString
+ collectText(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
+
+ /** Collects contents of the current document */
+ OUString collectText() { return collectText(getDocumentAccessibleContext()); }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sc/CppunitTest_sc_a11y.mk b/sc/CppunitTest_sc_a11y.mk
new file mode 100644
index 000000000000..e013beb987cc
--- /dev/null
+++ b/sc/CppunitTest_sc_a11y.mk
@@ -0,0 +1,39 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,sc_a11y))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sc_a11y, \
+ sc/qa/extras/accessibility/basics \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sc_a11y, \
+ sal \
+ cppu \
+ subsequenttest \
+ test \
+ tl \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sc_a11y,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,sc_a11y))
+$(eval $(call gb_CppunitTest_use_rdb,sc_a11y,services))
+$(eval $(call gb_CppunitTest_use_ure,sc_a11y))
+$(eval $(call gb_CppunitTest_use_vcl,sc_a11y))
+
+$(eval $(call gb_CppunitTest_use_instdir_configuration,sc_a11y))
+$(eval $(call gb_CppunitTest_use_common_configuration,sc_a11y))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sc/Module_sc.mk b/sc/Module_sc.mk
index 5a0d0c13f40e..5323b6031d58 100644
--- a/sc/Module_sc.mk
+++ b/sc/Module_sc.mk
@@ -83,6 +83,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sc, \
CppunitTest_sc_subsequent_export_test2 \
CppunitTest_sc_uicalc \
CppunitTest_sc_vba_macro_test \
+ CppunitTest_sc_a11y \
))
ifneq ($(ENABLE_JUMBO_SHEETS),)
diff --git a/sc/qa/extras/accessibility/basics.cxx b/sc/qa/extras/accessibility/basics.cxx
new file mode 100644
index 000000000000..ec55ea04ed26
--- /dev/null
+++ b/sc/qa/extras/accessibility/basics.cxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <com/sun/star/util/Date.hpp>
+#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
+
+#include <com/sun/star/awt/Key.hpp>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <vcl/scheduler.hxx>
+
+#include <tools/date.hxx>
+#include <tools/time.hxx>
+
+#include <test/a11y/accessibletestbase.hxx>
+#include <test/a11y/AccessibilityTools.hxx>
+
+using namespace css;
+
+CPPUNIT_TEST_FIXTURE(test::AccessibleTestBase, TestCalcMenu)
+{
+ load(u"private:factory/scalc");
+
+ const Date beforeDate(Date::SYSTEM);
+ const double beforeTime = tools::Time(tools::Time::SYSTEM).GetTimeInDays();
+
+ // in cell A1, insert the date
+ CPPUNIT_ASSERT(activateMenuItem(u"Insert", u"Date"));
+ // move down to A2
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::DOWN);
+ // in cell A2, insert the time
+ CPPUNIT_ASSERT(activateMenuItem(u"Insert", u"Time"));
+
+ uno::Reference<accessibility::XAccessibleTable> sheet(
+ getDocumentAccessibleContext()->getAccessibleChild(0)->getAccessibleContext(), // sheet 1
+ uno::UNO_QUERY_THROW);
+
+ /* As it's very tricky to check the dates and times are correct in text format (imagine running
+ * on 1970-12-31 23:59:59.99, it's gonna shift *everything* in a 100th of a second) because
+ * clock can have changed between generating the two values to compare. So instead we just
+ * check the text is not empty, and the underlying value (representing the date or time) is
+ * between the time it was before and after the call. */
+
+ // cell A1 contains a date
+ auto xCell = sheet->getAccessibleCellAt(0, 0)->getAccessibleContext();
+ uno::Reference<accessibility::XAccessibleText> xText(xCell, uno::UNO_QUERY_THROW);
+ std::cout << "A1 (text): " << xText->getText() << std::endl;
+ CPPUNIT_ASSERT(!xText->getText().isEmpty());
+ uno::Reference<accessibility::XAccessibleValue> xValue(xCell, uno::UNO_QUERY_THROW);
+ double value;
+ CPPUNIT_ASSERT(xValue->getCurrentValue() >>= value);
+ std::cout << "A1 (value): " << value << std::endl;
+ uno::Reference<util::XNumberFormatsSupplier> xSupplier(mxDocument, uno::UNO_QUERY_THROW);
+ util::Date nullDate;
+ CPPUNIT_ASSERT(xSupplier->getNumberFormatSettings()->getPropertyValue("NullDate") >>= nullDate);
+ const Date afterDate(Date::SYSTEM);
+ CPPUNIT_ASSERT_GREATEREQUAL(double(beforeDate - nullDate), value);
+ CPPUNIT_ASSERT_LESSEQUAL(double(afterDate - nullDate), value);
+
+ // cell A2 contains time, no date, so we have to be careful passing midnight
+ xCell = sheet->getAccessibleCellAt(1, 0)->getAccessibleContext();
+ xText.set(xCell, uno::UNO_QUERY_THROW);
+ std::cout << "A2 (text): " << xText->getText() << std::endl;
+ CPPUNIT_ASSERT(!xText->getText().isEmpty());
+ xValue.set(xCell, uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT(xValue->getCurrentValue() >>= value);
+ std::cout << "A2 (value): " << value << std::endl;
+ double afterTime = tools::Time(tools::Time::SYSTEM).GetTimeInDays();
+ // in case day changed -- assuming no more than 24 hours passed
+ if (afterTime < beforeTime)
+ {
+ afterTime += 1;
+ if (value < beforeTime)
+ value += 1;
+ }
+ CPPUNIT_ASSERT_GREATEREQUAL(beforeTime, value);
+ CPPUNIT_ASSERT_LESSEQUAL(afterTime, value);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sw/CppunitTest_sw_a11y.mk b/sw/CppunitTest_sw_a11y.mk
new file mode 100644
index 000000000000..67c527d1179c
--- /dev/null
+++ b/sw/CppunitTest_sw_a11y.mk
@@ -0,0 +1,38 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,sw_a11y))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sw_a11y, \
+ sw/qa/extras/accessibility/basics \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sw_a11y, \
+ sal \
+ cppu \
+ subsequenttest \
+ test \
+ unotest \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sw_a11y,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,sw_a11y))
+$(eval $(call gb_CppunitTest_use_rdb,sw_a11y,services))
+$(eval $(call gb_CppunitTest_use_ure,sw_a11y))
+$(eval $(call gb_CppunitTest_use_vcl,sw_a11y))
+
+$(eval $(call gb_CppunitTest_use_instdir_configuration,sw_a11y))
+$(eval $(call gb_CppunitTest_use_common_configuration,sw_a11y))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk
index dc88c465553d..318f8581d7df 100644
--- a/sw/Module_sw.mk
+++ b/sw/Module_sw.mk
@@ -148,6 +148,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\
CppunitTest_sw_core_view \
CppunitTest_sw_core_attr \
CppunitTest_sw_filter_ww8 \
+ CppunitTest_sw_a11y \
))
ifneq ($(DISABLE_GUI),TRUE)
diff --git a/sw/qa/extras/accessibility/basics.cxx b/sw/qa/extras/accessibility/basics.cxx
new file mode 100644
index 000000000000..44e835ab533a
--- /dev/null
+++ b/sw/qa/extras/accessibility/basics.cxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <com/sun/star/awt/Key.hpp>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <vcl/scheduler.hxx>
+
+#include <test/a11y/swaccessibletestbase.hxx>
+#include <test/a11y/AccessibilityTools.hxx>
+
+using namespace css;
+
+CPPUNIT_TEST_FIXTURE(test::SwAccessibleTestBase, TestBasicStructure)
+{
+ loadFromSrc(u"/sw/qa/python/testdocuments/xtextcontent.odt");
+ auto xContext = getDocumentAccessibleContext();
+ CPPUNIT_ASSERT(xContext.is());
+
+ dumpA11YTree(xContext);
+ CPPUNIT_ASSERT_EQUAL(
+ rtl::OUString("<PARAGRAPH>String1</PARAGRAPH><PARAGRAPH/><PARAGRAPH/><PARAGRAPH/>"
+ "<TABLE name=\"Table1-1\" description=\"Table1 on page 1\">"
+ "<TABLE_CELL name=\"A1\" description=\"A1\">"
+ "<PARAGRAPH>String2</PARAGRAPH>"
+ "</TABLE_CELL>"
+ "</TABLE>"
+ "<PARAGRAPH/>"
+ "<TEXT_FRAME name=\"Frame1\"><PARAGRAPH>Frame1</PARAGRAPH></TEXT_FRAME>"
+ "<TEXT_FRAME name=\"Frame2\"><PARAGRAPH>Frame2</PARAGRAPH></TEXT_FRAME>"),
+ collectText(xContext));
+}
+
+CPPUNIT_TEST_FIXTURE(test::SwAccessibleTestBase, TestTypeSimple)
+{
+ load(u"private:factory/swriter");
+ auto xContext = getDocumentAccessibleContext();
+ CPPUNIT_ASSERT(xContext.is());
+
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'h', 0);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'e', 0);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'l', 0);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'l', 0);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'o', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT_EQUAL(rtl::OUString("<PARAGRAPH>hello</PARAGRAPH>"), collectText(xContext));
+}
+
+CPPUNIT_TEST_FIXTURE(test::SwAccessibleTestBase, TestTypeMultiPara)
+{
+ load(u"private:factory/swriter");
+ auto xContext = getDocumentAccessibleContext();
+ CPPUNIT_ASSERT(xContext.is());
+
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'A', 0);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'B', 0);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT_EQUAL(
+ rtl::OUString("<PARAGRAPH>A</PARAGRAPH><PARAGRAPH>B</PARAGRAPH><PARAGRAPH>C</PARAGRAPH>"),
+ collectText(xContext));
+}
+
+CPPUNIT_TEST_FIXTURE(test::SwAccessibleTestBase, TestMenuInsertPageNumber)
+{
+ load(u"private:factory/swriter");
+ CPPUNIT_ASSERT(activateMenuItem(u"Insert", u"Page Number"));
+ CPPUNIT_ASSERT_EQUAL(rtl::OUString("<PARAGRAPH>1</PARAGRAPH>"), collectText());
+}
+
+CPPUNIT_TEST_FIXTURE(test::SwAccessibleTestBase, TestMenuInsertPageBreak)
+{
+ load(u"private:factory/swriter");
+
+ CPPUNIT_ASSERT(activateMenuItem(u"Insert", u"Page Number"));
+ CPPUNIT_ASSERT(activateMenuItem(u"Insert", u"Page Break"));
+ // we need to move focus to the paragraph after the page break to insert the page number there
+ documentPostKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
+ CPPUNIT_ASSERT(activateMenuItem(u"Insert", u"Page Number"));
+
+ CPPUNIT_ASSERT_EQUAL(rtl::OUString("<PARAGRAPH>1</PARAGRAPH><PARAGRAPH>2</PARAGRAPH>"),
+ collectText());
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/test/Library_subsequenttest.mk b/test/Library_subsequenttest.mk
index 9b35eda003ba..813b61ec538f 100644
--- a/test/Library_subsequenttest.mk
+++ b/test/Library_subsequenttest.mk
@@ -43,6 +43,8 @@ $(eval $(call gb_Library_add_exception_objects,subsequenttest,\
test/source/unoapi_test \
test/source/calc_unoapi_test \
test/source/a11y/AccessibilityTools \
+ test/source/a11y/accessibletestbase \
+ test/source/a11y/swaccessibletestbase \
test/source/beans/xpropertyset \
test/source/chart/xchartdata \
test/source/container/xchild \
diff --git a/test/source/a11y/AccessibilityTools.cxx b/test/source/a11y/AccessibilityTools.cxx
index 44b168b54a94..991089dcb3e0 100644
--- a/test/source/a11y/AccessibilityTools.cxx
+++ b/test/source/a11y/AccessibilityTools.cxx
@@ -32,32 +32,56 @@
using namespace css;
-css::uno::Reference<css::accessibility::XAccessibleContext>
-AccessibilityTools::getAccessibleObjectForRole(
- const css::uno::Reference<css::accessibility::XAccessible>& xacc, sal_Int16 role)
+uno::Reference<accessibility::XAccessibleContext>
+AccessibilityTools::getAccessibleObjectForPredicate(
+ const uno::Reference<accessibility::XAccessibleContext>& xCtx,
+ const std::function<bool(const uno::Reference<accessibility::XAccessibleContext>&)>& cPredicate)
{
- css::uno::Reference<css::accessibility::XAccessibleContext> ac = xacc->getAccessibleContext();
- bool isShowing = ac->getAccessibleStateSet() & accessibility::AccessibleStateType::SHOWING;
-
- if ((ac->getAccessibleRole() == role) && isShowing)
+ if (cPredicate(xCtx))
{
- return ac;
+ return xCtx;
}
else
{
- int count = ac->getAccessibleChildCount();
+ int count = xCtx->getAccessibleChildCount();
for (int i = 0; i < count && i < AccessibilityTools::MAX_CHILDREN; i++)
{
- css::uno::Reference<css::accessibility::XAccessibleContext> ac2
- = AccessibilityTools::getAccessibleObjectForRole(ac->getAccessibleChild(i), role);
- if (ac2.is())
- return ac2;
+ uno::Reference<accessibility::XAccessibleContext> xCtx2
+ = getAccessibleObjectForPredicate(xCtx->getAccessibleChild(i), cPredicate);
+ if (xCtx2.is())
+ return xCtx2;
}
}
return nullptr;
}
+uno::Reference<accessibility::XAccessibleContext>
+AccessibilityTools::getAccessibleObjectForPredicate(
+ const uno::Reference<accessibility::XAccessible>& xAcc,
+ const std::function<bool(const uno::Reference<accessibility::XAccessibleContext>&)>& cPredicate)
+{
+ return getAccessibleObjectForPredicate(xAcc->getAccessibleContext(), cPredicate);
+}
+
+uno::Reference<accessibility::XAccessibleContext> AccessibilityTools::getAccessibleObjectForRole(
+ const uno::Reference<accessibility::XAccessibleContext>& xCtx, sal_Int16 role)
+{
+ return getAccessibleObjectForPredicate(
+ xCtx, [&role](const uno::Reference<accessibility::XAccessibleContext>& xObjCtx) {
+ return (xObjCtx->getAccessibleRole() == role
+ && xObjCtx->getAccessibleStateSet()
+ & accessibility::AccessibleStateType::SHOWING);
+ });
+}
+
+css::uno::Reference<css::accessibility::XAccessibleContext>
+AccessibilityTools::getAccessibleObjectForRole(
+ const css::uno::Reference<css::accessibility::XAccessible>& xacc, sal_Int16 role)
+{
+ return getAccessibleObjectForRole(xacc->getAccessibleContext(), role);
+}
+
bool AccessibilityTools::equals(const uno::Reference<accessibility::XAccessible>& xacc1,
const uno::Reference<accessibility::XAccessible>& xacc2)
{
diff --git a/test/source/a11y/accessibletestbase.cxx b/test/source/a11y/accessibletestbase.cxx
new file mode 100644
index 000000000000..3968c32fa08d
--- /dev/null
+++ b/test/source/a11y/accessibletestbase.cxx
@@ -0,0 +1,260 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <test/a11y/accessibletestbase.hxx>
+
+#include <string>
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/FrameSearchFlag.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XFrame2.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/util/XCloseable.hpp>
+
+#include <vcl/scheduler.hxx>
+
+#include <test/a11y/AccessibilityTools.hxx>
+
+using namespace css;
+
+void test::AccessibleTestBase::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop = frame::Desktop::create(mxComponentContext);
+}
+
+void test::AccessibleTestBase::close()
+{
+ if (mxDocument.is())
+ {
+ uno::Reference<util::XCloseable> xCloseable(mxDocument, uno::UNO_QUERY_THROW);
+ xCloseable->close(false);
+ mxDocument.clear();
+ }
+}
+
+void test::AccessibleTestBase::tearDown() { close(); }
+
+void test::AccessibleTestBase::load(const rtl::OUString& sURL)
+{
+ // make sure there is no open document in case it is called more than once
+ close();
+ mxDocument = mxDesktop->loadComponentFromURL(sURL, "_blank", frame::FrameSearchFlag::AUTO, {});
+
+ uno::Reference<frame::XModel> xModel(mxDocument, uno::UNO_QUERY_THROW);
+ mxWindow.set(xModel->getCurrentController()->getFrame()->getContainerWindow());
+
+ // bring window to front
+ uno::Reference<awt::XTopWindow> xTopWindow(mxWindow, uno::UNO_QUERY_THROW);
+ xTopWindow->toFront();
+}
+
+void test::AccessibleTestBase::loadFromSrc(const rtl::OUString& sSrcPath)
+{
+ load(m_directories.getURLFromSrc(sSrcPath));
+}
+
+uno::Reference<accessibility::XAccessibleContext>
+test::AccessibleTestBase::getWindowAccessibleContext()
+{
+ uno::Reference<accessibility::XAccessible> xAccessible(mxWindow, uno::UNO_QUERY_THROW);
+
+ return xAccessible->getAccessibleContext();
+}
+
+bool test::AccessibleTestBase::isDocumentRole(const sal_Int16 role)
+{
+ return (role == accessibility::AccessibleRole::DOCUMENT
+ || role == accessibility::AccessibleRole::DOCUMENT_PRESENTATION
+ || role == accessibility::AccessibleRole::DOCUMENT_SPREADSHEET
+ || role == accessibility::AccessibleRole::DOCUMENT_TEXT);
+}
+
+uno::Reference<accessibility::XAccessibleContext>
+test::AccessibleTestBase::getDocumentAccessibleContext()
+{
+ uno::Reference<frame::XModel> xModel(mxDocument, uno::UNO_QUERY_THROW);
+ uno::Reference<accessibility::XAccessible> xAccessible(
+ xModel->getCurrentController()->getFrame()->getComponentWindow(), uno::UNO_QUERY_THROW);
+
+ return AccessibilityTools::getAccessibleObjectForPredicate(
+ xAccessible->getAccessibleContext(),
+ [](const uno::Reference<accessibility::XAccessibleContext>& xCtx) {
+ return (isDocumentRole(xCtx->getAccessibleRole())
+ && xCtx->getAccessibleStateSet() & accessibility::AccessibleStateType::SHOWING);
+ });
+}
+
+uno::Reference<accessibility::XAccessibleContext>
+test::AccessibleTestBase::getFirstRelationTargetOfType(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext, sal_Int16 relationType)
+{
+ auto relset = xContext->getAccessibleRelationSet();
+
+ if (relset.is())
+ {
+ for (sal_Int32 i = 0; i < relset->getRelationCount(); ++i)
+ {
+ const auto& rel = relset->getRelation(i);
+ if (rel.RelationType == relationType)
+ {
+ for (auto& target : rel.TargetSet)
+ {
+ uno::Reference<accessibility::XAccessible> targetAccessible(target,
+ uno::UNO_QUERY);
+ if (targetAccessible.is())
+ return targetAccessible->getAccessibleContext();
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+std::deque<uno::Reference<accessibility::XAccessibleContext>>
+test::AccessibleTestBase::getAllChildren(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext)
+{
+ std::deque<uno::Reference<accessibility::XAccessibleContext>> children;
+ auto childCount = xContext->getAccessibleChildCount();
+
+ for (sal_Int32 i = 0; i < childCount && i < AccessibilityTools::MAX_CHILDREN; i++)
+ {
+ auto child = xContext->getAccessibleChild(i);
+ children.push_back(child->getAccessibleContext());
+ }
+
+ return children;
+}
+
+/** Prints the tree of accessible objects starting at @p xContext to stdout */
+void test::AccessibleTestBase::dumpA11YTree(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext, const int depth)
+{
+ Scheduler::ProcessEventsToIdle();
+ auto xRelSet = xContext->getAccessibleRelationSet();
+
+ std::cout << AccessibilityTools::debugString(xContext);
+ /* relation set is not included in AccessibilityTools::debugString(), but might be useful in
+ * this context, so we compute it here */
+ if (xRelSet.is())
+ {
+ auto relCount = xRelSet->getRelationCount();
+ if (relCount)
+ {
+ std::cout << " rels=[";
+ for (sal_Int32 i = 0; i < relCount; ++i)
+ {
+ if (i > 0)
+ std::cout << ", ";
+
+ const auto& rel = xRelSet->getRelation(i);
+ std::cout << "(type=" << AccessibilityTools::getRelationTypeName(rel.RelationType)
+ << " (" << rel.RelationType << ")";
+ std::cout << " targets=[";
+ int j = 0;
+ for (auto& target : rel.TargetSet)
+ {
+ if (j++ > 0)
+ std::cout << ", ";
+ uno::Reference<accessibility::XAccessible> ta(target, uno::UNO_QUERY_THROW);
+ std::cout << AccessibilityTools::debugString(ta);
+ }
+ std::cout << "])";
+ }
+ std::cout << "]";
+ }
+ }
+ std::cout << std::endl;
+
+ sal_Int32 i = 0;
+ for (auto& child : getAllChildren(xContext))
+ {
+ for (int j = 0; j < depth; j++)
+ std::cout << " ";
+ std::cout << " * child " << i++ << ": ";
+ dumpA11YTree(child, depth + 1);
+ }
+}
+
+/* see OAccessibleMenuItemComponent::GetAccessibleName() */
+static bool accessibleNameMatches(const uno::Reference<accessibility::XAccessibleContext>& xContext,
+ std::u16string_view name)
+{
+ const OUString actualName = xContext->getAccessibleName();
+
+ if (actualName == name)
+ return true;
+
+#if defined(_WIN32)
+ /* on Win32, ignore a \tSHORTCUT suffix on a menu item */
+ switch (xContext->getAccessibleRole())
+ {
+ case accessibility::AccessibleRole::MENU_ITEM:
+ case accessibility::AccessibleRole::RADIO_MENU_ITEM:
+ case accessibility::AccessibleRole::CHECK_MENU_ITEM:
+ return actualName.startsWith(name) && actualName[name.length()] == '\t';
+
+ default:
+ break;
+ }
+#endif
+
+ return false;
+}
+
+/** Gets a child by name (usually in a menu) */
+uno::Reference<accessibility::XAccessibleContext> test::AccessibleTestBase::getItemFromName(
+ const uno::Reference<accessibility::XAccessibleContext>& xMenuCtx, std::u16string_view name)
+{
+ auto childCount = xMenuCtx->getAccessibleChildCount();
+
+ std::cout << "looking up item " << OUString(name) << " in "
+ << AccessibilityTools::debugString(xMenuCtx) << std::endl;
+ for (sal_Int32 i = 0; i < childCount && i < AccessibilityTools::MAX_CHILDREN; i++)
+ {
+ auto item = xMenuCtx->getAccessibleChild(i)->getAccessibleContext();
+ if (accessibleNameMatches(item, name))
+ {
+ std::cout << "-> found " << AccessibilityTools::debugString(item) << std::endl;
+ return item;
+ }
+ }
+
+ std::cout << "-> NOT FOUND!" << std::endl;
+ std::cout << " Contents was: ";
+ dumpA11YTree(xMenuCtx, 1);
+
+ return uno::Reference<accessibility::XAccessibleContext>();
+}
+
+bool test::AccessibleTestBase::activateMenuItem(
+ const uno::Reference<accessibility::XAccessibleAction>& xAction)
+{
+ // assume first action is the right one, there's not description anyway
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xAction->getAccessibleActionCount());
+ if (xAction->doAccessibleAction(0))
+ {
+ Scheduler::ProcessEventsToIdle();
+ return true;
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/test/source/a11y/swaccessibletestbase.cxx b/test/source/a11y/swaccessibletestbase.cxx
new file mode 100644
index 000000000000..b43d65c0cf78
--- /dev/null
+++ b/test/source/a11y/swaccessibletestbase.cxx
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <test/a11y/swaccessibletestbase.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <rtl/ustrbuf.hxx>
+
+#include <test/a11y/AccessibilityTools.hxx>
+
+using namespace css;
+
+uno::Reference<accessibility::XAccessibleContext>
+test::SwAccessibleTestBase::getPreviousFlowingSibling(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext)
+{
+ return getFirstRelationTargetOfType(xContext,
+ accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM);
+}
+
+uno::Reference<accessibility::XAccessibleContext> test::SwAccessibleTestBase::getNextFlowingSibling(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext)
+{
+ return getFirstRelationTargetOfType(xContext,
+ accessibility::AccessibleRelationType::CONTENT_FLOWS_TO);
+}
+
+/* Care has to be taken not to walk sideways as the relation is also used
+ * with children of nested containers (possibly as the "natural"/"perceived" flow?). */
+std::deque<uno::Reference<accessibility::XAccessibleContext>>
+test::SwAccessibleTestBase::getAllChildren(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext)
+{
+ /* first, get all "natural" children */
+ auto children = AccessibleTestBase::getAllChildren(xContext);
+ if (!children.size())
+ return children;
+
+ /* then, try and find flowing siblings at the same levels that are not included in the list */
+ /* first, backwards: */
+ auto child = getPreviousFlowingSibling(children.front());
+ while (child.is() && children.size() < AccessibilityTools::MAX_CHILDREN)
+ {
+ auto childParent = child->getAccessibleParent();
+ if (childParent.is()
+ && AccessibilityTools::equals(xContext, childParent->getAccessibleContext()))
+ children.push_front(child);
+ child = getPreviousFlowingSibling(child);
+ }
+ /* then forward */
+ child = getNextFlowingSibling(children.back());
+ while (child.is() && children.size() < AccessibilityTools::MAX_CHILDREN)
+ {
+ auto childParent = child->getAccessibleParent();
+ if (childParent.is()
+ && AccessibilityTools::equals(xContext, childParent->getAccessibleContext()))
+ children.push_back(child);
+ child = getNextFlowingSibling(child);
+ }
+
+ return children;
+}
+
+void test::SwAccessibleTestBase::collectText(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext, rtl::OUStringBuffer& buffer,
+ bool onlyChildren)
+{
+ const auto& roleName = AccessibilityTools::getRoleName(xContext->getAccessibleRole());
+
+ std::cout << "collecting text for child of role " << roleName << "..." << std::endl;
+
+ if (!onlyChildren)
+ {
+ const struct
+ {
+ std::u16string_view name;
+ rtl::OUString value;
+ } attrs[] = {
+ { u"name", xContext->getAccessibleName() },
+ { u"description", xContext->getAccessibleDescription() },
+ };
+
+ buffer.append('<');
+ buffer.append(roleName);
+ for (auto& attr : attrs)
+ {
+ if (attr.value.getLength() == 0)
+ continue;
+ buffer.append(' ');
+ buffer.append(attr.name);
+ buffer.append(u"=\"" + attr.value.replaceAll(u"\"", u"&quot;") + "\"");
+ }
+ buffer.append('>');
+ }
+ auto openTagLength = buffer.getLength();
+
+ uno::Reference<accessibility::XAccessibleText> xText(xContext, uno::UNO_QUERY);
+ if (xText.is())
+ buffer.append(xText->getText());
+
+ for (auto& childContext : getAllChildren(xContext))
+ collectText(childContext, buffer);
+
+ if (!onlyChildren)
+ {
+ if (buffer.getLength() != openTagLength)
+ buffer.append("</" + roleName + ">");
+ else
+ {
+ /* there was no content, so make is a short tag for more concise output */
+ buffer[openTagLength - 1] = '/';
+ buffer.append('>');
+ }
+ }
+}
+
+OUString test::SwAccessibleTestBase::collectText(
+ const uno::Reference<accessibility::XAccessibleContext>& xContext)
+{
+ rtl::OUStringBuffer buf;
+ collectText(xContext, buf, isDocumentRole(xContext->getAccessibleRole()));
+ return buf.makeStringAndClear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */