diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /include/test/a11y | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'include/test/a11y')
-rw-r--r-- | include/test/a11y/AccessibilityTools.hxx | 289 | ||||
-rw-r--r-- | include/test/a11y/accessibletestbase.hxx | 298 | ||||
-rw-r--r-- | include/test/a11y/eventposter.hxx | 131 | ||||
-rw-r--r-- | include/test/a11y/swaccessibletestbase.hxx | 58 |
4 files changed, 776 insertions, 0 deletions
diff --git a/include/test/a11y/AccessibilityTools.hxx b/include/test/a11y/AccessibilityTools.hxx new file mode 100644 index 0000000000..5235faedd3 --- /dev/null +++ b/include/test/a11y/AccessibilityTools.hxx @@ -0,0 +1,289 @@ +/* -*- 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/. + * + * 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 . + */ + +#pragma once + +#include <test/testdllapi.hxx> + +#include <functional> +#include <string> + +#include <cppunit/TestAssert.h> + +#include <com/sun/star/accessibility/AccessibleEventObject.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/accessibility/XAccessibleText.hpp> + +class OOO_DLLPUBLIC_TEST AccessibilityTools +{ +public: + /** Maximum number of children to work on. This is especially useful for + * Calc which has a million elements, if not more. */ + 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); + + /** + * @brief Gets a descendant of @p xCtx (or @p xCtx itself) that matches the given role and name. + * @param xCtx An accessible context object to start the search from + * @param role The role of the object to look up. + * @param name The name of the object to look up. + * @returns The found object, or @c nullptr if not found. + * + * Finds a descendant of @p xCtx (or @p xCtx itself) that matches @p role and @p name. + * @code + * AccessibilityTools::getAccessibleObjectForName( + * css::accessibility::AccessibleRole::PUSH_BUTTON, u"Insert"); + * @endcode + * + * @see AccessibilityTools::getAccessibleObjectForPredicate() */ + static css::uno::Reference<css::accessibility::XAccessibleContext> getAccessibleObjectForName( + const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx, + const sal_Int16 role, std::u16string_view name); + static inline css::uno::Reference<css::accessibility::XAccessibleContext> + getAccessibleObjectForName(const css::uno::Reference<css::accessibility::XAccessible>& xAcc, + const sal_Int16 role, std::u16string_view name) + { + return getAccessibleObjectForName(xAcc->getAccessibleContext(), role, name); + } + + /** + * @brief Gets a descendant of @p xCtx (or @p xCtx itself) that matches the last given role and + * name pair, and has ancestors matching the leading pairs in the given order. + * @param xCtx An accessible context to start the search from. + * @param role The role of the first ancestor to match. + * @param name The name of the first ancestor to match. + * @param Ts...args Additional role and name pairs of ancestors, ending with the role and name + * pair of the target object to match. + * @returns The found object, or @c nullptr if not found. + * + * Specialized version allowing specifying arbitrary objects on the path to the target one. Not + * all objects have to be matched, but there have to be ancestors matching in the given order. + * This is useful to easily solve conflicts if there are more than one possible match. + * + * This can be used to find an "Insert" push button inside a panel named "Some group" for + * example, as shown below: + * + * @code + * AccessibilityTools::getAccessibleObjectForName( + * css::accessibility::AccessibleRole::PANEL, u"Some group", + * css::accessibility::AccessibleRole::PUSH_BUTTON, u"Insert"); + * @endcode + * + * @note This returns the first match in the object tree when walking it depth-first. Depending + * on the tree, this might not be able to find the expected match, e.g. if there is a + * first match with intermediate unmatched objects, and the target has the same tree but + * without intermediate objects that can be used to refine the search and prevent the + * unwanted tree to match. The same issue arises with two identical trees, yet in that + * case no walking scenario could solve it automatically anyway. + * In such situations, a custom @c getAccessibleObjectForPredicate() call, or successive + * lookups interleaved with specific child lookups are likely the best solution. + * + * @see getAccessibleObjectForPredicate(). + */ + /* TODO: reimplement as IDDFS or BFS? Not sure the additional complexity/performance costs + * warrant it. */ + template <typename... Ts> + static css::uno::Reference<css::accessibility::XAccessibleContext> getAccessibleObjectForName( + const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx, + const sal_Int16 role, std::u16string_view name, Ts... args) + { + auto nChildren = xCtx->getAccessibleChildCount(); + + // try self first + if (xCtx->getAccessibleRole() == role && nameEquals(xCtx, name)) + { + for (decltype(nChildren) i = 0; i < nChildren && i < MAX_CHILDREN; i++) + { + if (auto xMatchChild + = getAccessibleObjectForName(xCtx->getAccessibleChild(i), args...)) + return xMatchChild; + } + } + + // if not found, try at a deeper level + for (decltype(nChildren) i = 0; i < nChildren && i < MAX_CHILDREN; i++) + { + if (auto xMatchChild + = getAccessibleObjectForName(xCtx->getAccessibleChild(i), role, name, args...)) + return xMatchChild; + } + + return nullptr; + } + + template <typename... Ts> + static inline css::uno::Reference<css::accessibility::XAccessibleContext> + getAccessibleObjectForName(const css::uno::Reference<css::accessibility::XAccessible>& xAcc, + const sal_Int16 role, std::u16string_view name, Ts... args) + { + return getAccessibleObjectForName(xAcc->getAccessibleContext(), role, name, args...); + } + + static bool equals(const css::uno::Reference<css::accessibility::XAccessible>& xacc1, + const css::uno::Reference<css::accessibility::XAccessible>& xacc2); + static bool equals(const css::uno::Reference<css::accessibility::XAccessibleContext>& xctx1, + const css::uno::Reference<css::accessibility::XAccessibleContext>& xctx2); + + /** + * @brief Compares the accessible name against a string + * @param xCtx A XAccessibleContext on which compare the name + * @param name The string to compare to + * @returns @c true if @p xCtx name matches @p name. + * + * This is conceptually equivalent to @code xCtx->getAccessibleName() == name @endcode, but + * handles the case OSL debugging is active and inserts a type suffix. Unless you know for + * sure the accessible you are comparing is not subject to those suffixes under debugging, + * always use this function instead of direct comparison. + */ + static bool nameEquals(const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx, + const std::u16string_view name); + static bool nameEquals(const css::uno::Reference<css::accessibility::XAccessible>& xAcc, + const std::u16string_view name) + { + return nameEquals(xAcc->getAccessibleContext(), name); + } + + static OUString getRoleName(const sal_Int16 role); + static OUString getEventIdName(const sal_Int16 event_id); + static OUString getRelationTypeName(const sal_Int16 rel_type); + + template <typename T> static std::string debugString(const css::uno::Reference<T>& x) + { + return debugString(x.get()); + } + + template <typename T> static std::string debugString(const T& x) { return debugString(&x); } + + template <typename T> static std::string debugString(const T* p) + { + /* only the forwarding to debugName() might actually dereference @c p, + * and we rely on specializations to be as constant as possible and not + * violate the cast here. In practice it'll be the case for all types + * handle if we carefully write the specializations. In most case the + * specialization could take a const itself if the methods were + * properly marked const, but well. */ + return debugString(const_cast<T*>(p)); + } + + template <typename T> static std::string debugString(T* p) + { + CPPUNIT_NS::OStringStream ost; + + ost << "(" << static_cast<const void*>(p) << ")"; + if (p != nullptr) + ost << " " << debugName(p); + + return ost.str(); + } + + static OUString debugAccessibleStateSet(sal_Int64 p); + + /** + * @brief Process events until a condition or a timeout + * @param cUntilCallback Callback condition + * @param nTimeoutMs Maximum time in ms to wait for condition + * @returns @c true if the condition was met, or @c false if the timeout + * has been reached. + * + * Processes events until idle, and either until the given condition + * becomes @c true or a timeout is reached. + * + * This is similar to Scheduler::ProcessEventsToIdle() but awaits a + * condition up to a timeout. This is useful if the waited-on condition + * might happen after the first idle time. The timeout helps in case the + * condition is not satisfied in reasonable time. + * + * @p cUntilCallback is called each time the scheduler reaches idle to check + * whether the condition is met. + * + * Example: + * @code + * ProcessEvents([&]() { return taskHasRun; }); + * @endcode + * + * @see Scheduler::ProcessEventsToIdle() + */ + static bool Await(const std::function<bool()>& cUntilCallback, sal_uInt64 nTimeoutMs = 3000); + + /** + * @brief Process events for a given time + * @param nTimeoutMs Time to dispatch events for + * + * Process events for a given time. This can be useful if waiting is in + * order but there is no actual condition to wait on (e.g. expect + * something *not* to happen). This similar in spirit to + * @c sleep(nTimeoutMs), but dispatches events during the wait. + * + * This function should be used sparsely because waiting a given time is + * rarely a good solution for a problem, but in some specific situations + * there is no better alternative (like, again, waiting for something not + * to happen). + */ + static void Wait(sal_uInt64 nTimeoutMs); + +private: + static OUString debugName(css::accessibility::XAccessibleContext* xctx); + static OUString debugName(css::accessibility::XAccessible* xacc); + static OUString debugName(const css::accessibility::AccessibleEventObject* evobj); + static OUString debugName(css::accessibility::XAccessibleAction* xAct); + static OUString debugName(css::accessibility::XAccessibleText* xTxt); +}; + +CPPUNIT_NS_BEGIN +/* How to generate those automatically? We don't want to match all types + * not to mess up cppunit for types we don't support */ +#define AT_ASSERTION_TRAITS(T) \ + template <> struct assertion_traits<css::uno::Reference<T>> \ + { \ + static bool equal(const css::uno::Reference<T>& x, const css::uno::Reference<T>& y) \ + { \ + return AccessibilityTools::equals(x, y); \ + } \ + \ + static std::string toString(const css::uno::Reference<T>& x) \ + { \ + return AccessibilityTools::debugString(x); \ + } \ + } + +AT_ASSERTION_TRAITS(css::accessibility::XAccessible); +AT_ASSERTION_TRAITS(css::accessibility::XAccessibleContext); + +#undef AT_ASSERTION_TRAITS + +CPPUNIT_NS_END + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/test/a11y/accessibletestbase.hxx b/include/test/a11y/accessibletestbase.hxx new file mode 100644 index 0000000000..e23c2e1246 --- /dev/null +++ b/include/test/a11y/accessibletestbase.hxx @@ -0,0 +1,298 @@ +/* -*- 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/XDialog2.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 <vcl/window.hxx> + +#include <rtl/ustring.hxx> +#include <test/bootstrapfixture.hxx> +#include <test/a11y/eventposter.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...); + } + + /** + * @brief Gets the focused accessible object at @p xAcc level or below + * @param xAcc An accessible object + * @returns The accessible context of the focused object, or @c nullptr + * + * Finds the accessible object context at or under @p xAcc that has the focused state (and is + * showing). Normally only one such object should exist in a given hierarchy, but in all cases + * this function will return the first one found. + * + * @see AccessibilityTools::getAccessibleObjectForPredicate() + */ + static css::uno::Reference<css::accessibility::XAccessibleContext> + getFocusedObject(const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx); + + static inline css::uno::Reference<css::accessibility::XAccessibleContext> + getFocusedObject(const css::uno::Reference<css::accessibility::XAccessible>& xAcc) + { + return getFocusedObject(xAcc->getAccessibleContext()); + } + + /** + * @brief Navigates through focusable elements using the Tab keyboard shortcut. + * @param xRoot The root element to look for focused elements in. + * @param role The accessible role of the element to tab to. + * @param name The accessible name of the element to tab to. + * @param pEventPosterHelper Pointer to a @c EventPosterHelper instance, or @c nullptr to obtain + * it from @p xRoot. + * @returns The element tabbed to, or @c nullptr if not found. + * + * Navigates through focusable elements in the top level containing @p xRoot using the Tab + * keyboard key until the focused elements matches @p role and @p name. + * + * Note that usually @p xRoot should be the toplevel accessible, or at least contain all + * focusable elements within that window. It is however *not* a requirement, but only elements + * actually inside it will be candidate for a match, and thus if focus goes outside it, it might + * lead to not finding the target element. + * + * If @p pEventPosterHelper is @c nullptr, this function will try to construct one from + * @p xRoot. @see EventPosterHelper. + */ + static css::uno::Reference<css::accessibility::XAccessibleContext> + tabTo(const css::uno::Reference<css::accessibility::XAccessible>& xRoot, const sal_Int16 role, + const std::u16string_view name, + const EventPosterHelperBase* pEventPosterHelper = nullptr); + + static bool tabTo(const css::uno::Reference<css::accessibility::XAccessible>& xRoot, + const css::uno::Reference<css::accessibility::XAccessibleContext>& xChild, + const EventPosterHelperBase* pEventPosterHelper = nullptr); + +#if !defined(MACOSX) + /* Dialog handling */ + class Dialog : public test::AccessibleEventPosterHelper + { + private: + bool mbAutoClose; + css::uno::Reference<css::awt::XDialog2> mxDialog2; + css::uno::Reference<css::accessibility::XAccessible> mxAccessible; + + public: + Dialog(css::uno::Reference<css::awt::XDialog2>& xDialog2, bool bAutoClose = true); + virtual ~Dialog(); + + void setAutoClose(bool bAutoClose) { mbAutoClose = bAutoClose; } + + css::uno::Reference<css::accessibility::XAccessible> getAccessible() const + { + return mxAccessible; + } + + void close(sal_Int32 result = VclResponseType::RET_CANCEL); + + css::uno::Reference<css::accessibility::XAccessibleContext> + tabTo(const sal_Int16 role, const std::u16string_view name) + { + return AccessibleTestBase::tabTo(getAccessible(), role, name, this); + } + + bool tabTo(const css::uno::Reference<css::accessibility::XAccessibleContext>& xChild) + { + return AccessibleTestBase::tabTo(getAccessible(), xChild, this); + } + }; + + class DialogWaiter + { + public: + virtual ~DialogWaiter() {} + + /** + * @brief Waits for the associated dialog to close + * @param nTimeoutMs Maximum delay to wait the dialog for + * @returns @c true if the dialog closed, @c false if timeout was reached + * + * @throws css::uno::RuntimeException if an unexpected dialog popped up instead of the + * expected one. + * @throws Any exception that the user callback supplied to awaitDialog() might have thrown. + */ + virtual bool waitEndDialog(sal_uInt64 nTimeoutMs = 3000) = 0; + }; + + /** + * @brief Helper to call user code when a given dialog opens + * @param name The title of the dialog window to wait for + * @param callback The user code to run when the given dialog opens + * @param bAutoClose Whether to automatically cancel the dialog after the user code finished, if + * the dialog is still there. You should leave this to @c true unless you + * know exactly what you are doing, see below. + * @returns A @c DialogWaiter wrapper on which call waitEndDialog() after having triggered the + * dialog in some way. + * + * This function makes it fairly easy and safe to execute code once a dialog pops up: + * @code + * auto waiter = awaitDialog(u"Special Characters", [this](Dialog &dialog) { + * // for example, something like this: + * // something(); + * // CPPUNIT_ASSERT(dialog.tabTo(...)); + * // CPPUNIT_ASSERT(somethingElse); + * // dialog.postKeyEventAsync(0, awt::Key::RETURN); + * }); + * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog...")); + * CPPUNIT_ASSERT(waiter->waitEndDialog()); + * @endcode + * + * @note The user code might actually be executed before DialogWaiter::waitEndDialog() is + * called. It is actually likely to be called at the time the call that triggers the + * dialog happens. However, as letting an exception slip in a event handler is likely to + * cause problems, exceptions are forwarded to the DialogWaiter::waitEndDialog() call. + * However, note that you cannot rely on something like this: + * @code + * int foo = 0; + * auto waiter = awaitDialog(u"Some Dialog", [&foo](Dialog&) { + * CPPUNIT_ASSERT_EQUAL(1, foo); + * }); + * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog...")); + * foo = 1; // here, the callback likely already ran as a result of the + * // Scheduler::ProcessEventsToIdle() call that activateMenuItem() did. + * CPPUNIT_ASSERT(waiter->waitEndDialog()); + * @endcode + * + * @warning You should almost certainly always leave @p bAutoClose to @c true. If it is set to + * @c false, you have to take extreme care: + * - The dialog will not be canceled if the user code raises an exception. + * - If the dialog is run through Dialog::Execute(), control won't return to the test + * body until the dialog is closed. This means that the only ways to execute code + * until then is a separate thread or via code dispatched by the main loop. + * Thus, you have to make sure you DO close the dialog some way or another yourself + * in order for the test code to terminate at some point. + * - If the dialog doesn't use Dialog::Execute() but is rather similar to a second + * separate window (e.g. non-modal), you might still have to close the dialog before + * closing the test document is possible without a CloseVetoException -- which might + * badly break the test run. + */ + static std::shared_ptr<DialogWaiter> awaitDialog(const std::u16string_view name, + std::function<void(Dialog&)> callback, + bool bAutoClose = true); +#endif //defined(MACOSX) + +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/eventposter.hxx b/include/test/a11y/eventposter.hxx new file mode 100644 index 0000000000..d9bce5601f --- /dev/null +++ b/include/test/a11y/eventposter.hxx @@ -0,0 +1,131 @@ +/* -*- 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 <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/uno/Reference.hxx> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <rtl/ustring.hxx> +#include <vcl/window.hxx> + +namespace test +{ +/** + * @brief Base helper class to send events to a window. + * + * Implementations of this helper will usually just wrap an implementation of post*Event*() calls. + * This class is mostly useful to encapsulate the calls when getting the target window is not + * trivial or is only relevant to sending events, and to have a generic event poster interface. + * + * Additionally, this class provides simplified helpers to send event pairs, like key down/up, or + * text+commit, to make it easier on the common case for callers. + */ +class OOO_DLLPUBLIC_TEST EventPosterHelperBase +{ +public: + virtual ~EventPosterHelperBase(){}; + + /** @see SfxLokHelper::postKeyEventAsync */ + virtual void postKeyEventAsync(int nType, int nCharCode, int nKeyCode) const = 0; + + /** Posts a full key down/up cycle */ + void postKeyEventAsync(int nCharCode, int nKeyCode) const + { + postKeyEventAsync(LOK_KEYEVENT_KEYINPUT, nCharCode, nKeyCode); + postKeyEventAsync(LOK_KEYEVENT_KEYUP, nCharCode, nKeyCode); + } + + /** @see SfxLokHelper::postExtTextEventAsync */ + virtual void postExtTextEventAsync(int nType, const OUString& rText) const = 0; + + /** Posts a full text input + commit sequence */ + void postExtTextEventAsync(const OUString& rText) const + { + postExtTextEventAsync(LOK_EXT_TEXTINPUT, rText); + postExtTextEventAsync(LOK_EXT_TEXTINPUT_END, rText); + } +}; + +/** + * @brief Helper to send events to a window. + * + * This helper basically just wraps SfxLokHelper::post*EventAsync() calls to hold the target window + * reference in the class. + */ +class OOO_DLLPUBLIC_TEST EventPosterHelper : public EventPosterHelperBase +{ +protected: + VclPtr<vcl::Window> mxWindow; + +public: + EventPosterHelper(void) + : mxWindow(nullptr) + { + } + EventPosterHelper(VclPtr<vcl::Window> xWindow) + : mxWindow(xWindow) + { + } + EventPosterHelper(vcl::Window* pWindow) + : mxWindow(pWindow) + { + } + + vcl::Window* getWindow() const { return mxWindow; } + + void setWindow(VclPtr<vcl::Window> xWindow) { mxWindow = xWindow; } + void setWindow(vcl::Window* pWindow) { mxWindow = pWindow; } + + explicit operator bool() const { return mxWindow && !mxWindow->isDisposed(); } + bool operator!() const { return !bool(*this); } + + using EventPosterHelperBase::postKeyEventAsync; + using EventPosterHelperBase::postExtTextEventAsync; + + /** @see SfxLokHelper::postKeyEventAsync */ + virtual void postKeyEventAsync(int nType, int nCharCode, int nKeyCode) const override; + /** @see SfxLokHelper::postExtTextEventAsync */ + virtual void postExtTextEventAsync(int nType, const OUString& rText) const override; +}; + +/** + * @brief Accessibility-specialized helper to send events to a window. + * + * This augments @c test::EventPosterHelper to simplify usage in accessibility tests. + */ +class OOO_DLLPUBLIC_TEST AccessibleEventPosterHelper : public EventPosterHelper +{ +public: + AccessibleEventPosterHelper(void) + : EventPosterHelper() + { + } + AccessibleEventPosterHelper(const css::uno::Reference<css::accessibility::XAccessible> xAcc) + { + setWindow(xAcc); + } + + /** + * @brief Sets the window on which post events based on an accessible object inside it. + * @param xAcc An accessible object inside a toplevel. + * + * This method tries and find the top level window containing @p xAcc to use it to post events. + * + * This currently relies on a toplevel accessible being a @c VCLXWindow, and requires that + * window's output device to be set (@see VCLXWindow::GetWindow()). + */ + void setWindow(css::uno::Reference<css::accessibility::XAccessible> xAcc); +}; +} + +/* 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 0000000000..a8ed42a4dc --- /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: */ |