/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AccessibilityTools.hxx" namespace test { class OOO_DLLPUBLIC_TEST AccessibleTestBase : public test::BootstrapFixture { protected: css::uno::Reference mxDesktop; css::uno::Reference mxDocument; css::uno::Reference 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 getWindowAccessibleContext(); virtual css::uno::Reference getDocumentAccessibleContext(); void documentPostKeyEvent(int nType, int nCharCode, int nKeyCode) { vcl::ITiledRenderable* pTiledRenderable = dynamic_cast(mxDocument.get()); CPPUNIT_ASSERT(pTiledRenderable); pTiledRenderable->postKeyEvent(nType, nCharCode, nKeyCode); } static css::uno::Reference getFirstRelationTargetOfType( const css::uno::Reference& 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> getAllChildren(const css::uno::Reference& xContext); void dumpA11YTree(const css::uno::Reference& xContext, const int depth = 0); css::uno::Reference getItemFromName(const css::uno::Reference& xMenuCtx, std::u16string_view name); bool activateMenuItem(const css::uno::Reference& xAction); /* just convenience not to have to query accessibility::XAccessibleAction manually */ bool activateMenuItem(const css::uno::Reference& xCtx) { return activateMenuItem(css::uno::Reference( 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 css::uno::Reference getItemFromName(const css::uno::Reference& 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 bool activateMenuItem(const css::uno::Reference& 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 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 getFocusedObject(const css::uno::Reference& xCtx); static inline css::uno::Reference getFocusedObject(const css::uno::Reference& 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 tabTo(const css::uno::Reference& xRoot, const sal_Int16 role, const std::u16string_view name, const EventPosterHelperBase* pEventPosterHelper = nullptr); static bool tabTo(const css::uno::Reference& xRoot, const css::uno::Reference& xChild, const EventPosterHelperBase* pEventPosterHelper = nullptr); #if !defined(MACOSX) /* Dialog handling */ class Dialog : public test::AccessibleEventPosterHelper { private: bool mbAutoClose; css::uno::Reference mxDialog2; css::uno::Reference mxAccessible; public: Dialog(css::uno::Reference& xDialog2, bool bAutoClose = true); virtual ~Dialog(); void setAutoClose(bool bAutoClose) { mbAutoClose = bAutoClose; } css::uno::Reference getAccessible() const { return mxAccessible; } void close(sal_Int32 result = VclResponseType::RET_CANCEL); css::uno::Reference tabTo(const sal_Int16 role, const std::u16string_view name) { return AccessibleTestBase::tabTo(getAccessible(), role, name, this); } bool tabTo(const css::uno::Reference& 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 awaitDialog(const std::u16string_view name, std::function 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: */