tdf#160051 PDF export: Artifact present inside tagged content
[LibreOffice.git] / include / test / a11y / accessibletestbase.hxx
blob3c87005c12fbc77dafed4865f711f22b6fa522b0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #pragma once
12 #include <test/testdllapi.hxx>
14 #include <deque>
15 #include <string>
17 #include <com/sun/star/accessibility/AccessibleRole.hpp>
18 #include <com/sun/star/accessibility/XAccessible.hpp>
19 #include <com/sun/star/accessibility/XAccessibleAction.hpp>
20 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
21 #include <com/sun/star/awt/XDialog2.hpp>
22 #include <com/sun/star/awt/XWindow.hpp>
23 #include <com/sun/star/frame/Desktop.hpp>
24 #include <com/sun/star/lang/XComponent.hpp>
25 #include <com/sun/star/uno/Reference.hxx>
27 #include <vcl/ITiledRenderable.hxx>
28 #include <vcl/window.hxx>
30 #include <rtl/ustring.hxx>
31 #include <test/bootstrapfixture.hxx>
32 #include <test/a11y/eventposter.hxx>
34 #include "AccessibilityTools.hxx"
36 namespace test
38 class OOO_DLLPUBLIC_TEST AccessibleTestBase : public test::BootstrapFixture
40 private:
41 void collectText(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext,
42 rtl::OUStringBuffer& buffer, bool onlyChildren = false);
44 protected:
45 css::uno::Reference<css::frame::XDesktop2> mxDesktop;
46 css::uno::Reference<css::lang::XComponent> mxDocument;
47 css::uno::Reference<css::awt::XWindow> mxWindow;
49 static bool isDocumentRole(const sal_Int16 role);
51 virtual void load(const rtl::OUString& sURL);
52 virtual void loadFromSrc(const rtl::OUString& sSrcPath);
53 void close();
54 css::uno::Reference<css::accessibility::XAccessibleContext> getWindowAccessibleContext();
55 virtual css::uno::Reference<css::accessibility::XAccessibleContext>
56 getDocumentAccessibleContext();
58 static css::uno::Reference<css::accessibility::XAccessibleContext> getPreviousFlowingSibling(
59 const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
60 static css::uno::Reference<css::accessibility::XAccessibleContext> getNextFlowingSibling(
61 const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
63 /** Collects contents of @p xContext in a dummy markup form */
64 OUString
65 collectText(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
67 /** Collects contents of the current document */
68 OUString collectText() { return collectText(getDocumentAccessibleContext()); }
70 void documentPostKeyEvent(int nType, int nCharCode, int nKeyCode)
72 vcl::ITiledRenderable* pTiledRenderable
73 = dynamic_cast<vcl::ITiledRenderable*>(mxDocument.get());
74 CPPUNIT_ASSERT(pTiledRenderable);
75 pTiledRenderable->postKeyEvent(nType, nCharCode, nKeyCode);
78 static css::uno::Reference<css::accessibility::XAccessibleContext> getFirstRelationTargetOfType(
79 const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext,
80 css::accessibility::AccessibleRelationType relationType);
82 /**
83 * @brief Tries to list all children of an accessible
84 * @param xContext An XAccessibleContext object
85 * @returns The list of all children (but no more than @c AccessibilityTools::MAX_CHILDREN)
87 * This fetches children of @p xContext. This would ideally just be the same than iterating
88 * over children the regular way up to @c AccessibilityTools::MAX_CHILDREN, but unfortunately
89 * some components (Writer, Impress, ...) do not provide all their children the regular way and
90 * require specifics to include them.
92 * There is no guarantee on *which* children are returned if there are more than
93 * @c AccessibilityTools::MAX_CHILDREN -- yet they will always be the same in a given context.
95 virtual std::deque<css::uno::Reference<css::accessibility::XAccessibleContext>>
96 getAllChildren(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext);
98 void dumpA11YTree(const css::uno::Reference<css::accessibility::XAccessibleContext>& xContext,
99 const int depth = 0);
101 css::uno::Reference<css::accessibility::XAccessibleContext>
102 getItemFromName(const css::uno::Reference<css::accessibility::XAccessibleContext>& xMenuCtx,
103 std::u16string_view name);
104 bool
105 activateMenuItem(const css::uno::Reference<css::accessibility::XAccessibleAction>& xAction);
106 /* just convenience not to have to query accessibility::XAccessibleAction manually */
107 bool activateMenuItem(const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx)
109 return activateMenuItem(css::uno::Reference<css::accessibility::XAccessibleAction>(
110 xCtx, css::uno::UNO_QUERY_THROW));
113 /* convenience to get a menu item from a list of menu item names. Unlike
114 * getItemFromName(context, name), this requires subsequently found items to implement
115 * XAccessibleAction, as each but the last item will be activated before looking for
116 * the next one, to account for the fact menus might not be fully populated before being
117 * activated. */
118 template <typename... Ts>
119 css::uno::Reference<css::accessibility::XAccessibleContext>
120 getItemFromName(const css::uno::Reference<css::accessibility::XAccessibleContext>& xMenuCtx,
121 std::u16string_view name, Ts... names)
123 auto item = getItemFromName(xMenuCtx, name);
124 CPPUNIT_ASSERT(item.is());
125 activateMenuItem(item);
126 return getItemFromName(item, names...);
129 /* convenience to activate an item by its name and all its parent menus up to xMenuCtx.
130 * @see getItemFromName() */
131 template <typename... Ts>
132 bool
133 activateMenuItem(const css::uno::Reference<css::accessibility::XAccessibleContext>& xMenuCtx,
134 Ts... names)
136 auto item = getItemFromName(xMenuCtx, names...);
137 CPPUNIT_ASSERT(item.is());
138 return activateMenuItem(item);
141 /* convenience to activate an item by its name and all its parent menus up to the main window
142 * menu bar */
143 template <typename... Ts> bool activateMenuItem(Ts... names)
145 auto menuBar = AccessibilityTools::getAccessibleObjectForRole(
146 getWindowAccessibleContext(), css::accessibility::AccessibleRole::MENU_BAR);
147 CPPUNIT_ASSERT(menuBar.is());
148 return activateMenuItem(menuBar, names...);
152 * @brief Gets the focused accessible object at @p xAcc level or below
153 * @param xAcc An accessible object
154 * @returns The accessible context of the focused object, or @c nullptr
156 * Finds the accessible object context at or under @p xAcc that has the focused state (and is
157 * showing). Normally only one such object should exist in a given hierarchy, but in all cases
158 * this function will return the first one found.
160 * @see AccessibilityTools::getAccessibleObjectForPredicate()
162 static css::uno::Reference<css::accessibility::XAccessibleContext>
163 getFocusedObject(const css::uno::Reference<css::accessibility::XAccessibleContext>& xCtx);
165 static inline css::uno::Reference<css::accessibility::XAccessibleContext>
166 getFocusedObject(const css::uno::Reference<css::accessibility::XAccessible>& xAcc)
168 return getFocusedObject(xAcc->getAccessibleContext());
172 * @brief Navigates through focusable elements using the Tab keyboard shortcut.
173 * @param xRoot The root element to look for focused elements in.
174 * @param role The accessible role of the element to tab to.
175 * @param name The accessible name of the element to tab to.
176 * @param pEventPosterHelper Pointer to a @c EventPosterHelper instance, or @c nullptr to obtain
177 * it from @p xRoot.
178 * @returns The element tabbed to, or @c nullptr if not found.
180 * Navigates through focusable elements in the top level containing @p xRoot using the Tab
181 * keyboard key until the focused elements matches @p role and @p name.
183 * Note that usually @p xRoot should be the toplevel accessible, or at least contain all
184 * focusable elements within that window. It is however *not* a requirement, but only elements
185 * actually inside it will be candidate for a match, and thus if focus goes outside it, it might
186 * lead to not finding the target element.
188 * If @p pEventPosterHelper is @c nullptr, this function will try to construct one from
189 * @p xRoot. @see EventPosterHelper.
191 static css::uno::Reference<css::accessibility::XAccessibleContext>
192 tabTo(const css::uno::Reference<css::accessibility::XAccessible>& xRoot, const sal_Int16 role,
193 const std::u16string_view name,
194 const EventPosterHelperBase* pEventPosterHelper = nullptr);
196 static bool tabTo(const css::uno::Reference<css::accessibility::XAccessible>& xRoot,
197 const css::uno::Reference<css::accessibility::XAccessibleContext>& xChild,
198 const EventPosterHelperBase* pEventPosterHelper = nullptr);
200 #if !defined(MACOSX)
201 /* Dialog handling */
202 class Dialog : public test::AccessibleEventPosterHelper
204 private:
205 bool mbAutoClose;
206 css::uno::Reference<css::awt::XDialog2> mxDialog2;
207 css::uno::Reference<css::accessibility::XAccessible> mxAccessible;
209 public:
210 Dialog(css::uno::Reference<css::awt::XDialog2>& xDialog2, bool bAutoClose = true);
211 virtual ~Dialog();
213 void setAutoClose(bool bAutoClose) { mbAutoClose = bAutoClose; }
215 const css::uno::Reference<css::accessibility::XAccessible>& getAccessible() const
217 return mxAccessible;
220 void close(sal_Int32 result = VclResponseType::RET_CANCEL);
222 css::uno::Reference<css::accessibility::XAccessibleContext>
223 tabTo(const sal_Int16 role, const std::u16string_view name)
225 return AccessibleTestBase::tabTo(getAccessible(), role, name, this);
228 bool tabTo(const css::uno::Reference<css::accessibility::XAccessibleContext>& xChild)
230 return AccessibleTestBase::tabTo(getAccessible(), xChild, this);
234 class DialogWaiter
236 public:
237 virtual ~DialogWaiter() {}
240 * @brief Waits for the associated dialog to close
241 * @param nTimeoutMs Maximum delay to wait the dialog for
242 * @returns @c true if the dialog closed, @c false if timeout was reached
244 * @throws css::uno::RuntimeException if an unexpected dialog popped up instead of the
245 * expected one.
246 * @throws Any exception that the user callback supplied to awaitDialog() might have thrown.
248 virtual bool waitEndDialog(sal_uInt64 nTimeoutMs = 3000) = 0;
252 * @brief Helper to call user code when a given dialog opens
253 * @param name The title of the dialog window to wait for
254 * @param callback The user code to run when the given dialog opens
255 * @param bAutoClose Whether to automatically cancel the dialog after the user code finished, if
256 * the dialog is still there. You should leave this to @c true unless you
257 * know exactly what you are doing, see below.
258 * @returns A @c DialogWaiter wrapper on which call waitEndDialog() after having triggered the
259 * dialog in some way.
261 * This function makes it fairly easy and safe to execute code once a dialog pops up:
262 * @code
263 * auto waiter = awaitDialog(u"Special Characters", [this](Dialog &dialog) {
264 * // for example, something like this:
265 * // something();
266 * // CPPUNIT_ASSERT(dialog.tabTo(...));
267 * // CPPUNIT_ASSERT(somethingElse);
268 * // dialog.postKeyEventAsync(0, awt::Key::RETURN);
269 * });
270 * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog..."));
271 * CPPUNIT_ASSERT(waiter->waitEndDialog());
272 * @endcode
274 * @note The user code might actually be executed before DialogWaiter::waitEndDialog() is
275 * called. It is actually likely to be called at the time the call that triggers the
276 * dialog happens. However, as letting an exception slip in a event handler is likely to
277 * cause problems, exceptions are forwarded to the DialogWaiter::waitEndDialog() call.
278 * However, note that you cannot rely on something like this:
279 * @code
280 * int foo = 0;
281 * auto waiter = awaitDialog(u"Some Dialog", [&foo](Dialog&) {
282 * CPPUNIT_ASSERT_EQUAL(1, foo);
283 * });
284 * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog..."));
285 * foo = 1; // here, the callback likely already ran as a result of the
286 * // Scheduler::ProcessEventsToIdle() call that activateMenuItem() did.
287 * CPPUNIT_ASSERT(waiter->waitEndDialog());
288 * @endcode
290 * @warning You should almost certainly always leave @p bAutoClose to @c true. If it is set to
291 * @c false, you have to take extreme care:
292 * - The dialog will not be canceled if the user code raises an exception.
293 * - If the dialog is run through Dialog::Execute(), control won't return to the test
294 * body until the dialog is closed. This means that the only ways to execute code
295 * until then is a separate thread or via code dispatched by the main loop.
296 * Thus, you have to make sure you DO close the dialog some way or another yourself
297 * in order for the test code to terminate at some point.
298 * - If the dialog doesn't use Dialog::Execute() but is rather similar to a second
299 * separate window (e.g. non-modal), you might still have to close the dialog before
300 * closing the test document is possible without a CloseVetoException -- which might
301 * badly break the test run.
303 static std::shared_ptr<DialogWaiter> awaitDialog(const std::u16string_view name,
304 std::function<void(Dialog&)> callback,
305 bool bAutoClose = true);
306 #endif //defined(MACOSX)
308 public:
309 virtual void setUp() override;
310 virtual void tearDown() override;
314 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */