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 void collectText(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xContext
,
42 rtl::OUStringBuffer
& buffer
, bool onlyChildren
= false);
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
);
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 */
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
);
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
,
101 css::uno::Reference
<css::accessibility::XAccessibleContext
>
102 getItemFromName(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xMenuCtx
,
103 std::u16string_view name
);
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
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
>
133 activateMenuItem(const css::uno::Reference
<css::accessibility::XAccessibleContext
>& xMenuCtx
,
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
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
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);
201 /* Dialog handling */
202 class Dialog
: public test::AccessibleEventPosterHelper
206 css::uno::Reference
<css::awt::XDialog2
> mxDialog2
;
207 css::uno::Reference
<css::accessibility::XAccessible
> mxAccessible
;
210 Dialog(css::uno::Reference
<css::awt::XDialog2
>& xDialog2
, bool bAutoClose
= true);
213 void setAutoClose(bool bAutoClose
) { mbAutoClose
= bAutoClose
; }
215 const css::uno::Reference
<css::accessibility::XAccessible
>& getAccessible() const
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);
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
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:
263 * auto waiter = awaitDialog(u"Special Characters", [this](Dialog &dialog) {
264 * // for example, something like this:
266 * // CPPUNIT_ASSERT(dialog.tabTo(...));
267 * // CPPUNIT_ASSERT(somethingElse);
268 * // dialog.postKeyEventAsync(0, awt::Key::RETURN);
270 * CPPUNIT_ASSERT(activateMenuItem(u"Some menu", u"Some Item Triggering a Dialog..."));
271 * CPPUNIT_ASSERT(waiter->waitEndDialog());
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:
281 * auto waiter = awaitDialog(u"Some Dialog", [&foo](Dialog&) {
282 * CPPUNIT_ASSERT_EQUAL(1, foo);
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());
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)
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: */