1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
12 #include <test/testdllapi.hxx>
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"
38 class OOO_DLLPUBLIC_TEST AccessibleTestBase
: public test::BootstrapFixture
41 css::uno::Reference
<css::frame::XDesktop2
> mxDesktop
;
42 css::uno::Reference
<css::lang::XComponent
> mxDocument
;
43 css::uno::Reference
<css::awt::XWindow
> mxWindow
;
45 static bool isDocumentRole(const sal_Int16 role
);
47 virtual void load(const rtl::OUString
& sURL
);
48 virtual void loadFromSrc(const rtl::OUString
& sSrcPath
);
50 css::uno::Reference
<css::accessibility::XAccessibleContext
> getWindowAccessibleContext();
51 virtual css::uno::Reference
<css::accessibility::XAccessibleContext
>
52 getDocumentAccessibleContext();
54 void documentPostKeyEvent(int nType
, int nCharCode
, int nKeyCode
)
56 vcl::ITiledRenderable
* pTiledRenderable
57 = dynamic_cast<vcl::ITiledRenderable
*>(mxDocument
.get());
58 CPPUNIT_ASSERT(pTiledRenderable
);
59 pTiledRenderable
->postKeyEvent(nType
, nCharCode
, nKeyCode
);
62 static css::uno::Reference
<css::accessibility::XAccessibleContext
> getFirstRelationTargetOfType(
63 const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xContext
,
64 sal_Int16 relationType
);
67 * @brief Tries to list all children of an accessible
68 * @param xContext An XAccessibleContext object
69 * @returns The list of all children (but no more than @c AccessibilityTools::MAX_CHILDREN)
71 * This fetches children of @p xContext. This would ideally just be the same than iterating
72 * over children the regular way up to @c AccessibilityTools::MAX_CHILDREN, but unfortunately
73 * some components (Writer, Impress, ...) do not provide all their children the regular way and
74 * require specifics to include them.
76 * There is no guarantee on *which* children are returned if there are more than
77 * @c AccessibilityTools::MAX_CHILDREN -- yet they will always be the same in a given context.
79 virtual std::deque
<css::uno::Reference
<css::accessibility::XAccessibleContext
>>
80 getAllChildren(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xContext
);
82 void dumpA11YTree(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xContext
,
85 css::uno::Reference
<css::accessibility::XAccessibleContext
>
86 getItemFromName(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xMenuCtx
,
87 std::u16string_view name
);
89 activateMenuItem(const css::uno::Reference
<css::accessibility::XAccessibleAction
>& xAction
);
90 /* just convenience not to have to query accessibility::XAccessibleAction manually */
91 bool activateMenuItem(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xCtx
)
93 return activateMenuItem(css::uno::Reference
<css::accessibility::XAccessibleAction
>(
94 xCtx
, css::uno::UNO_QUERY_THROW
));
97 /* convenience to get a menu item from a list of menu item names. Unlike
98 * getItemFromName(context, name), this requires subsequently found items to implement
99 * XAccessibleAction, as each but the last item will be activated before looking for
100 * the next one, to account for the fact menus might not be fully populated before being
102 template <typename
... Ts
>
103 css::uno::Reference
<css::accessibility::XAccessibleContext
>
104 getItemFromName(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xMenuCtx
,
105 std::u16string_view name
, Ts
... names
)
107 auto item
= getItemFromName(xMenuCtx
, name
);
108 CPPUNIT_ASSERT(item
.is());
109 activateMenuItem(item
);
110 return getItemFromName(item
, names
...);
113 /* convenience to activate an item by its name and all its parent menus up to xMenuCtx.
114 * @see getItemFromName() */
115 template <typename
... Ts
>
117 activateMenuItem(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xMenuCtx
,
120 auto item
= getItemFromName(xMenuCtx
, names
...);
121 CPPUNIT_ASSERT(item
.is());
122 return activateMenuItem(item
);
125 /* convenience to activate an item by its name and all its parent menus up to the main window
127 template <typename
... Ts
> bool activateMenuItem(Ts
... names
)
129 auto menuBar
= AccessibilityTools::getAccessibleObjectForRole(
130 getWindowAccessibleContext(), css::accessibility::AccessibleRole::MENU_BAR
);
131 CPPUNIT_ASSERT(menuBar
.is());
132 return activateMenuItem(menuBar
, names
...);
136 * @brief Gets the focused accessible object at @p xAcc level or below
137 * @param xAcc An accessible object
138 * @returns The accessible context of the focused object, or @c nullptr
140 * Finds the accessible object context at or under @p xAcc that has the focused state (and is
141 * showing). Normally only one such object should exist in a given hierarchy, but in all cases
142 * this function will return the first one found.
144 * @see AccessibilityTools::getAccessibleObjectForPredicate()
146 static css::uno::Reference
<css::accessibility::XAccessibleContext
>
147 getFocusedObject(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xCtx
);
149 static inline css::uno::Reference
<css::accessibility::XAccessibleContext
>
150 getFocusedObject(const css::uno::Reference
<css::accessibility::XAccessible
>& xAcc
)
152 return getFocusedObject(xAcc
->getAccessibleContext());
156 * @brief Navigates through focusable elements using the Tab keyboard shortcut.
157 * @param xRoot The root element to look for focused elements in.
158 * @param role The accessible role of the element to tab to.
159 * @param name The accessible name of the element to tab to.
160 * @param pEventPosterHelper Pointer to a @c EventPosterHelper instance, or @c nullptr to obtain
162 * @returns The element tabbed to, or @c nullptr if not found.
164 * Navigates through focusable elements in the top level containing @p xRoot using the Tab
165 * keyboard key until the focused elements matches @p role and @p name.
167 * Note that usually @p xRoot should be the toplevel accessible, or at least contain all
168 * focusable elements within that window. It is however *not* a requirement, but only elements
169 * actually inside it will be candidate for a match, and thus if focus goes outside it, it might
170 * lead to not finding the target element.
172 * If @p pEventPosterHelper is @c nullptr, this function will try to construct one from
173 * @p xRoot. @see EventPosterHelper.
175 static css::uno::Reference
<css::accessibility::XAccessibleContext
>
176 tabTo(const css::uno::Reference
<css::accessibility::XAccessible
>& xRoot
, const sal_Int16 role
,
177 const std::u16string_view name
,
178 const EventPosterHelperBase
* pEventPosterHelper
= nullptr);
180 static bool tabTo(const css::uno::Reference
<css::accessibility::XAccessible
>& xRoot
,
181 const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xChild
,
182 const EventPosterHelperBase
* pEventPosterHelper
= nullptr);
185 /* Dialog handling */
186 class Dialog
: public test::AccessibleEventPosterHelper
190 css::uno::Reference
<css::awt::XDialog2
> mxDialog2
;
191 css::uno::Reference
<css::accessibility::XAccessible
> mxAccessible
;
194 Dialog(css::uno::Reference
<css::awt::XDialog2
>& xDialog2
, bool bAutoClose
= true);
197 void setAutoClose(bool bAutoClose
) { mbAutoClose
= bAutoClose
; }
199 css::uno::Reference
<css::accessibility::XAccessible
> getAccessible() const
204 void close(sal_Int32 result
= VclResponseType::RET_CANCEL
);
206 css::uno::Reference
<css::accessibility::XAccessibleContext
>
207 tabTo(const sal_Int16 role
, const std::u16string_view name
)
209 return AccessibleTestBase::tabTo(getAccessible(), role
, name
, this);
212 bool tabTo(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xChild
)
214 return AccessibleTestBase::tabTo(getAccessible(), xChild
, this);
221 virtual ~DialogWaiter() {}
224 * @brief Waits for the associated dialog to close
225 * @param nTimeoutMs Maximum delay to wait the dialog for
226 * @returns @c true if the dialog closed, @c false if timeout was reached
228 * @throws css::uno::RuntimeException if an unexpected dialog popped up instead of the
230 * @throws Any exception that the user callback supplied to awaitDialog() might have thrown.
232 virtual bool waitEndDialog(sal_uInt64 nTimeoutMs
= 3000) = 0;
236 * @brief Helper to call user code when a given dialog opens
237 * @param name The title of the dialog window to wait for
238 * @param callback The user code to run when the given dialog opens
239 * @param bAutoClose Whether to automatically cancel the dialog after the user code finished, if
240 * the dialog is still there. You should leave this to @c true unless you
241 * know exactly what you are doing, see below.
242 * @returns A @c DialogWaiter wrapper on which call waitEndDialog() after having triggered the
243 * dialog in some way.
245 * This function makes it fairly easy and safe to execute code once a dialog pops up:
247 * auto waiter = awaitDialog(u"Special Characters", [this](Dialog &dialog) {
248 * // for example, something like this:
250 * // CPPUNIT_ASSERT(dialog.tabTo(...));
251 * // CPPUNIT_ASSERT(somethingElse);
252 * // dialog.postKeyEventAsync(0, awt::Key::RETURN);
254 * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog..."));
255 * CPPUNIT_ASSERT(waiter->waitEndDialog());
258 * @note The user code might actually be executed before DialogWaiter::waitEndDialog() is
259 * called. It is actually likely to be called at the time the call that triggers the
260 * dialog happens. However, as letting an exception slip in a event handler is likely to
261 * cause problems, exceptions are forwarded to the DialogWaiter::waitEndDialog() call.
262 * However, note that you cannot rely on something like this:
265 * auto waiter = awaitDialog(u"Some Dialog", [&foo](Dialog&) {
266 * CPPUNIT_ASSERT_EQUAL(1, foo);
268 * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog..."));
269 * foo = 1; // here, the callback likely already ran as a result of the
270 * // Scheduler::ProcessEventsToIdle() call that activateMenuItem() did.
271 * CPPUNIT_ASSERT(waiter->waitEndDialog());
274 * @warning You should almost certainly always leave @p bAutoClose to @c true. If it is set to
275 * @c false, you have to take extreme care:
276 * - The dialog will not be canceled if the user code raises an exception.
277 * - If the dialog is run through Dialog::Execute(), control won't return to the test
278 * body until the dialog is closed. This means that the only ways to execute code
279 * until then is a separate thread or via code dispatched by the main loop.
280 * Thus, you have to make sure you DO close the dialog some way or another yourself
281 * in order for the test code to terminate at some point.
282 * - If the dialog doesn't use Dialog::Execute() but is rather similar to a second
283 * separate window (e.g. non-modal), you might still have to close the dialog before
284 * closing the test document is possible without a CloseVetoException -- which might
285 * badly break the test run.
287 static std::shared_ptr
<DialogWaiter
> awaitDialog(const std::u16string_view name
,
288 std::function
<void(Dialog
&)> callback
,
289 bool bAutoClose
= true);
290 #endif //defined(MACOSX)
293 virtual void setUp() override
;
294 virtual void tearDown() override
;
298 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */