summaryrefslogtreecommitdiff
path: root/test
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 /test
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>
Diffstat (limited to 'test')
-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
4 files changed, 434 insertions, 13 deletions
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: */