summaryrefslogtreecommitdiffstats
path: root/tests/auto/other/qaccessibility
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/other/qaccessibility')
-rw-r--r--tests/auto/other/qaccessibility/CMakeLists.txt18
-rw-r--r--tests/auto/other/qaccessibility/accessiblewidgets.h4
-rw-r--r--tests/auto/other/qaccessibility/tst_qaccessibility.cpp524
3 files changed, 494 insertions, 52 deletions
diff --git a/tests/auto/other/qaccessibility/CMakeLists.txt b/tests/auto/other/qaccessibility/CMakeLists.txt
index 1d7b1c3901..9160bafe52 100644
--- a/tests/auto/other/qaccessibility/CMakeLists.txt
+++ b/tests/auto/other/qaccessibility/CMakeLists.txt
@@ -1,4 +1,11 @@
-# Generated from qaccessibility.pro.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qaccessibility LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
if(NOT QT_FEATURE_accessibility)
return()
@@ -12,7 +19,7 @@ qt_internal_add_test(tst_qaccessibility
SOURCES
accessiblewidgets.h
tst_qaccessibility.cpp
- PUBLIC_LIBRARIES
+ LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
@@ -20,19 +27,16 @@ qt_internal_add_test(tst_qaccessibility
Qt::WidgetsPrivate
)
-#### Keys ignored in scope 1:.:.:qaccessibility.pro:<TRUE>:
-# _REQUIREMENTS = "qtConfig(accessibility)"
-
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qaccessibility CONDITION UNIX AND NOT APPLE AND NOT HAIKU AND NOT INTEGRITY
- PUBLIC_LIBRARIES
+ LIBRARIES
m
)
qt_internal_extend_target(tst_qaccessibility CONDITION WIN32
- PUBLIC_LIBRARIES
+ LIBRARIES
ole32
oleacc
oleaut32
diff --git a/tests/auto/other/qaccessibility/accessiblewidgets.h b/tests/auto/other/qaccessibility/accessiblewidgets.h
index 2e93f24fd3..76b1f1f473 100644
--- a/tests/auto/other/qaccessibility/accessiblewidgets.h
+++ b/tests/auto/other/qaccessibility/accessiblewidgets.h
@@ -1,5 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef ACCESSIBLEWIDGETS_H
@@ -116,7 +116,7 @@ public:
int cursorPosition() const override { return textWidget()->cursorPosition; }
void setCursorPosition(int position) override { textWidget()->cursorPosition = position; }
QString text(int startOffset, int endOffset) const override { return textWidget()->text.mid(startOffset, endOffset); }
- int characterCount() const override { return textWidget()->text.length(); }
+ int characterCount() const override { return textWidget()->text.size(); }
QRect characterRect(int) const override { return QRect(); }
int offsetAtPoint(const QPoint &) const override { return 0; }
void scrollToSubstring(int, int) override {}
diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
index 779e5021e1..0c019544bd 100644
--- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
+++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
@@ -1,12 +1,11 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtCore/qglobal.h>
#ifdef Q_OS_WIN
# include <QtCore/qt_windows.h>
# include <oleacc.h>
-# include <QtGui/private/qwindowsuiawrapper_p.h>
+# include <uiautomation.h>
# include <servprov.h>
# include <winuser.h>
#endif
@@ -18,9 +17,15 @@
#include <qpa/qplatformnativeinterface.h>
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformaccessibility.h>
+#ifdef Q_OS_WIN
+#include <QtCore/private/qfunctions_win_p.h>
+#endif
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtWidgets/private/qapplication_p.h>
+#include <QtWidgets/private/qdialog_p.h>
+
#if defined(Q_OS_WIN) && defined(interface)
# undef interface
#endif
@@ -34,6 +39,7 @@
#include <QtTest/private/qtesthelpers_p.h>
using namespace QTestPrivate;
+using namespace Qt::StringLiterals;
static inline bool verifyChild(QWidget *child, QAccessibleInterface *interface,
int index, const QRect &domain)
@@ -161,6 +167,7 @@ public slots:
void cleanup();
private slots:
void eventTest();
+ void eventWithChildTest();
void customWidget();
void deletedWidget();
void subclassedWidget();
@@ -207,18 +214,24 @@ private slots:
void listTest();
void treeTest();
void tableTest();
+ void rootIndexView();
+ void uniqueIdTest();
void calendarWidgetTest();
void dockWidgetTest();
void comboBoxTest();
void accessibleName();
#if QT_CONFIG(shortcut)
void labelTest();
+ void relationTest();
void accelerators();
#endif
void bridgeTest();
void focusChild();
+ void messageBoxTest_data();
+ void messageBoxTest();
+
protected slots:
void onClicked();
private:
@@ -287,9 +300,9 @@ void tst_QAccessibility::cleanup()
{
const EventList list = QTestAccessibility::events();
if (!list.isEmpty()) {
- qWarning("%zd accessibility event(s) were not handled in testfunction '%s':", size_t(list.count()),
+ qWarning("%zd accessibility event(s) were not handled in testfunction '%s':", size_t(list.size()),
QString(QTest::currentTestFunction()).toLatin1().constData());
- for (int i = 0; i < list.count(); ++i)
+ for (int i = 0; i < list.size(); ++i)
qWarning(" %d: Object: %p Event: '%s' Child: %d", i + 1, list.at(i)->object(),
qAccessibleEventString(list.at(i)->type()), list.at(i)->child());
}
@@ -348,6 +361,33 @@ void tst_QAccessibility::eventTest()
QTestAccessibility::clearEvents();
}
+void tst_QAccessibility::eventWithChildTest()
+{
+ // make sure that QAccessibleEvent created using either of the two QAccessibleEvent
+ // behaves the same when the same underlying QObject is used
+ QWidget widget;
+ QWidget childWidget(&widget);
+
+ // QAccessibleEvent constructor called with the QObject*
+ QAccessibleEvent event1(&widget, QAccessible::Focus);
+
+ // QAccessibleEvent constructor called with the QAccessibleInterface* for the same QObject*
+ QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(&widget);
+ QAccessibleEvent event2(iface, QAccessible::Focus);
+
+ QVERIFY(event1.accessibleInterface() != nullptr);
+ QVERIFY(event2.accessibleInterface() != nullptr);
+ QCOMPARE(event1.accessibleInterface(), event2.accessibleInterface());
+
+ // set same child for both
+ event1.setChild(0);
+ event2.setChild(0);
+
+ QVERIFY(event1.accessibleInterface() != nullptr);
+ QVERIFY(event2.accessibleInterface() != nullptr);
+ QCOMPARE(event1.accessibleInterface(), event2.accessibleInterface());
+}
+
void tst_QAccessibility::customWidget()
{
{
@@ -625,7 +665,7 @@ void tst_QAccessibility::textAttributes_data()
defaultComplexFont.setStyle(QFont::StyleItalic);
defaultComplexFont.setUnderline(true);
- static QStringList defaults = QString("font-style:normal;font-weight:normal;text-align:left;text-position:baseline;font-size:13pt").split(';');
+ static QStringList defaults = QString("font-style:normal;font-weight:normal;text-align:left;text-position:baseline;font-size:13pt;text-line-through-type:none").split(';');
static QStringList bold = defaults;
bold[1] = QString::fromLatin1("font-weight:bold");
@@ -661,7 +701,7 @@ void tst_QAccessibility::textAttributes_data()
defaultFontDifferentBoldItalic[1] = QString::fromLatin1("font-weight:bold");
static QStringList defaultFontDifferentMonospace = defaultFontDifferent;
- defaultFontDifferentMonospace[7] = (QLatin1String("font-family:\"monospace\""));
+ defaultFontDifferentMonospace[8] = (QLatin1String("font-family:\"monospace\""));
static QStringList defaultFontDifferentFont8pt = defaultFontDifferent;
defaultFontDifferentFont8pt[4] = (QLatin1String("font-size:8pt"));
@@ -748,7 +788,7 @@ void tst_QAccessibility::textAttributes()
QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(&textEdit);
QAccessibleTextInterface *textInterface=interface->textInterface();
QVERIFY(textInterface);
- QCOMPARE(textInterface->characterCount(), textEdit.toPlainText().length());
+ QCOMPARE(textInterface->characterCount(), textEdit.toPlainText().size());
int startOffset = -1;
int endOffset = -1;
@@ -881,7 +921,7 @@ void tst_QAccessibility::applicationTest()
QWidget widget;
widget.show();
- qApp->setActiveWindow(&widget);
+ QApplicationPrivate::setActiveWindow(&widget);
QVERIFY(QTest::qWaitForWindowActive(&widget));
QAccessibleInterface *widgetIface = QAccessible::queryAccessibleInterface(&widget);
@@ -908,7 +948,7 @@ void tst_QAccessibility::mainWindowTest()
auto mw = mwHolder.get();
mw->resize(300, 200);
mw->show(); // triggers layout
- qApp->setActiveWindow(mw);
+ QApplicationPrivate::setActiveWindow(mw);
QLatin1String name = QLatin1String("I am the main window");
mw->setWindowTitle(name);
@@ -1079,7 +1119,10 @@ void tst_QAccessibility::buttonTest()
interface = QAccessible::queryAccessibleInterface(&toggleButton);
actionInterface = interface->actionInterface();
QCOMPARE(interface->role(), QAccessible::CheckBox);
- QCOMPARE(actionInterface->actionNames(), QStringList() << QAccessibleActionInterface::toggleAction() << QAccessibleActionInterface::setFocusAction());
+ QCOMPARE(actionInterface->actionNames(),
+ QStringList() << QAccessibleActionInterface::toggleAction()
+ << QAccessibleActionInterface::pressAction()
+ << QAccessibleActionInterface::setFocusAction());
QCOMPARE(actionInterface->localizedActionDescription(QAccessibleActionInterface::toggleAction()), QString("Toggles the state"));
QVERIFY(!toggleButton.isChecked());
QVERIFY(!interface->state().checked);
@@ -1115,12 +1158,18 @@ void tst_QAccessibility::buttonTest()
interface = QAccessible::queryAccessibleInterface(&checkBox);
actionInterface = interface->actionInterface();
QCOMPARE(interface->role(), QAccessible::CheckBox);
- QCOMPARE(actionInterface->actionNames(), QStringList() << QAccessibleActionInterface::toggleAction() << QAccessibleActionInterface::setFocusAction());
+ QCOMPARE(actionInterface->actionNames(),
+ QStringList() << QAccessibleActionInterface::toggleAction()
+ << QAccessibleActionInterface::pressAction()
+ << QAccessibleActionInterface::setFocusAction());
QVERIFY(!interface->state().checked);
actionInterface->doAction(QAccessibleActionInterface::toggleAction());
QTest::qWait(500);
- QCOMPARE(actionInterface->actionNames(), QStringList() << QAccessibleActionInterface::toggleAction() << QAccessibleActionInterface::setFocusAction());
+ QCOMPARE(actionInterface->actionNames(),
+ QStringList() << QAccessibleActionInterface::toggleAction()
+ << QAccessibleActionInterface::pressAction()
+ << QAccessibleActionInterface::setFocusAction());
QVERIFY(interface->state().checked);
QVERIFY(checkBox.isChecked());
QAccessible::State st;
@@ -1910,7 +1959,7 @@ void tst_QAccessibility::mdiAreaTest()
mdiArea.addSubWindow(new QWidget, Qt::Dialog)->show();
QList<QMdiSubWindow *> subWindows = mdiArea.subWindowList();
- QCOMPARE(subWindows.count(), subWindowCount);
+ QCOMPARE(subWindows.size(), subWindowCount);
QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(&mdiArea);
QVERIFY(interface);
@@ -1925,7 +1974,7 @@ void tst_QAccessibility::mdiSubWindowTest()
{
QMdiArea mdiArea;
mdiArea.show();
- qApp->setActiveWindow(&mdiArea);
+ QApplicationPrivate::setActiveWindow(&mdiArea);
QVERIFY(QTest::qWaitForWindowActive(&mdiArea));
@@ -1948,7 +1997,7 @@ void tst_QAccessibility::mdiSubWindowTest()
}
QList<QMdiSubWindow *> subWindows = mdiArea.subWindowList();
- QCOMPARE(subWindows.count(), subWindowCount);
+ QCOMPARE(subWindows.size(), subWindowCount);
QMdiSubWindow *testWindow = subWindows.at(3);
QVERIFY(testWindow);
@@ -1993,7 +2042,7 @@ void tst_QAccessibility::mdiSubWindowTest()
testWindow->setEnabled(false);
QVERIFY(interface->state().disabled);
testWindow->setEnabled(true);
- qApp->setActiveWindow(&mdiArea);
+ QApplicationPrivate::setActiveWindow(&mdiArea);
mdiArea.setActiveSubWindow(testWindow);
testWindow->setFocus();
QVERIFY(testWindow->isAncestorOf(qApp->focusWidget()));
@@ -2072,10 +2121,10 @@ void tst_QAccessibility::lineEditTest()
QCOMPARE(iface->text(QAccessible::Value), QString());
le->setEchoMode(QLineEdit::Password);
QVERIFY(iface->state().passwordEdit);
- QCOMPARE(iface->text(QAccessible::Value), QString(secret.length(), QLatin1Char('*')));
+ QCOMPARE(iface->text(QAccessible::Value), QString(secret.size(), QLatin1Char('*')));
le->setEchoMode(QLineEdit::PasswordEchoOnEdit);
QVERIFY(iface->state().passwordEdit);
- QCOMPARE(iface->text(QAccessible::Value), QString(secret.length(), QLatin1Char('*')));
+ QCOMPARE(iface->text(QAccessible::Value), QString(secret.size(), QLatin1Char('*')));
le->setEchoMode(QLineEdit::Normal);
QVERIFY(!(iface->state().passwordEdit));
QCOMPARE(iface->text(QAccessible::Value), secret);
@@ -2167,7 +2216,7 @@ void tst_QAccessibility::lineEditTest()
QCOMPARE(textIface->textAtOffset(5, QAccessible::ParagraphBoundary,&start,&end), cite);
QCOMPARE(start, 0);
- QCOMPARE(end, cite.length());
+ QCOMPARE(end, cite.size());
QCOMPARE(textIface->textAtOffset(5, QAccessible::LineBoundary,&start,&end), cite);
QCOMPARE(textIface->textAtOffset(5, QAccessible::NoBoundary,&start,&end), cite);
@@ -2211,8 +2260,8 @@ void tst_QAccessibility::lineEditTest()
QVERIFY_EVENT(&sel);
lineEdit->selectAll();
- sel.setSelection(0, lineEdit->text().length());
- sel.setCursorPosition(lineEdit->text().length());
+ sel.setSelection(0, lineEdit->text().size());
+ sel.setCursorPosition(lineEdit->text().size());
QVERIFY_EVENT(&sel);
lineEdit->setSelection(10, -4);
@@ -2568,7 +2617,7 @@ void tst_QAccessibility::dialogButtonBoxTest()
std::sort(buttons.begin(), buttons.end(), accessibleInterfaceLeftOf);
- for (int i = 0; i < buttons.count(); ++i)
+ for (int i = 0; i < buttons.size(); ++i)
actualOrder << buttons.at(i)->text(QAccessible::Name);
QStringList expectedOrder;
@@ -2620,7 +2669,7 @@ void tst_QAccessibility::dialogButtonBoxTest()
std::sort(buttons.begin(), buttons.end(), accessibleInterfaceAbove);
- for (int i = 0; i < buttons.count(); ++i)
+ for (int i = 0; i < buttons.size(); ++i)
actualOrder << buttons.at(i)->text(QAccessible::Name);
QStringList expectedOrder;
@@ -2830,6 +2879,12 @@ void tst_QAccessibility::listTest()
QCOMPARE(iface->indexOfChild(child3), 2);
QCOMPARE(child3->text(QAccessible::Name), QString("Brisbane"));
}
+
+ // Check that application is accessible parent, since it's a top-level widget
+ QAccessibleInterface *parentIface = iface->parent();
+ QVERIFY(parentIface);
+ QVERIFY(parentIface->role() == QAccessible::Application);
+
QTestAccessibility::clearEvents();
// Check for events
@@ -3352,6 +3407,70 @@ void tst_QAccessibility::tableTest()
QTestAccessibility::clearEvents();
}
+void tst_QAccessibility::rootIndexView()
+{
+ QStandardItemModel model;
+ for (int i = 0; i < 2; ++i) {
+ QStandardItem *item = new QStandardItem(u"root %1"_s.arg(i));
+ for (int j = 0; j < 5 * (i + 1); ++j) {
+ switch (i) {
+ case 0:
+ item->appendRow(new QStandardItem(u"child0/%1"_s.arg(j)));
+ break;
+ case 1:
+ item->appendRow({new QStandardItem(u"column0 1/%1"_s.arg(j)),
+ new QStandardItem(u"column1 1/%1"_s.arg(j))
+ });
+ break;
+ }
+ }
+ model.appendRow(item);
+ }
+
+ QListView view;
+ view.setModel(&model);
+ QTestAccessibility::clearEvents();
+
+ QAccessibleInterface *accView = QAccessible::queryAccessibleInterface(&view);
+ QVERIFY(accView);
+ QAccessibleTableInterface *accTable = accView->tableInterface();
+ QVERIFY(accTable);
+ QCOMPARE(accTable->rowCount(), 2);
+ QCOMPARE(accTable->columnCount(), 1);
+
+ view.setRootIndex(model.index(0, 0));
+ QAccessibleTableModelChangeEvent resetEvent(&view, QAccessibleTableModelChangeEvent::ModelReset);
+ QVERIFY(QTestAccessibility::containsEvent(&resetEvent));
+
+ QCOMPARE(accTable->rowCount(), 5);
+ QCOMPARE(accTable->columnCount(), 1);
+
+ view.setRootIndex(model.index(1, 0));
+ QCOMPARE(accTable->rowCount(), 10);
+ QCOMPARE(accTable->columnCount(), 2);
+
+ QTestAccessibility::clearEvents();
+}
+
+void tst_QAccessibility::uniqueIdTest()
+{
+ // Test that an ID isn't reassigned to another interface right away when an accessible interface
+ // that has just been created is removed from the cache and deleted before the next
+ // accessible interface is registered.
+ // For example for AT-SPI, that would result in the same object path being used, and thus
+ // data from the old and new interface can get confused due to caching.
+ QWidget widget1;
+ QAccessibleInterface *iface1 = QAccessible::queryAccessibleInterface(&widget1);
+ QAccessible::Id id1 = QAccessible::uniqueId(iface1);
+ QAccessible::deleteAccessibleInterface(id1);
+
+ QWidget widget2;
+ QAccessibleInterface *iface2 = QAccessible::queryAccessibleInterface(&widget2);
+ QAccessible::Id id2 = QAccessible::uniqueId(iface2);
+
+ QVERIFY(id1 != id2);
+}
+
void tst_QAccessibility::calendarWidgetTest()
{
#if QT_CONFIG(calendarwidget)
@@ -3379,7 +3498,7 @@ void tst_QAccessibility::calendarWidgetTest()
QCOMPARE(interface->rect(), globalGeometry);
QWidget *navigationBar = 0;
- foreach (QObject *child, calendarWidget.children()) {
+ for (QObject *child : calendarWidget.children()) {
if (child->objectName() == QLatin1String("qt_calendar_navigationbar")) {
navigationBar = static_cast<QWidget *>(child);
break;
@@ -3389,7 +3508,7 @@ void tst_QAccessibility::calendarWidgetTest()
QVERIFY(verifyChild(navigationBar, interface, 0, globalGeometry));
QAbstractItemView *calendarView = 0;
- foreach (QObject *child, calendarWidget.children()) {
+ for (QObject *child : calendarWidget.children()) {
if (child->objectName() == QLatin1String("qt_calendar_calendarview")) {
calendarView = static_cast<QAbstractItemView *>(child);
break;
@@ -3591,6 +3710,8 @@ void tst_QAccessibility::comboBoxTest()
QCOMPARE(iface->text(QAccessible::Name), QLatin1String("one"));
#endif
QCOMPARE(iface->text(QAccessible::Value), QLatin1String("one"));
+ QCOMPARE(combo.view()->selectionModel()->currentIndex().row(), 0);
+
combo.setCurrentIndex(2);
#ifdef Q_OS_UNIX
QCOMPARE(iface->text(QAccessible::Name), QLatin1String("three"));
@@ -3601,7 +3722,13 @@ void tst_QAccessibility::comboBoxTest()
QCOMPARE(listIface->role(), QAccessible::List);
QCOMPARE(listIface->childCount(), 3);
+ QAccessibleSelectionInterface *selectionIface = listIface->selectionInterface();
+ QVERIFY(selectionIface);
+ QCOMPARE(selectionIface->selectedItemCount(), 1);
+ QCOMPARE(listIface->indexOfChild(selectionIface->selectedItem(0)), 2);
+
QVERIFY(!combo.view()->isVisible());
+ QCOMPARE(combo.view()->selectionModel()->currentIndex().row(), 2);
QVERIFY(iface->actionInterface());
QCOMPARE(iface->actionInterface()->actionNames(), QStringList() << QAccessibleActionInterface::showMenuAction() << QAccessibleActionInterface::pressAction());
iface->actionInterface()->doAction(QAccessibleActionInterface::showMenuAction());
@@ -3631,6 +3758,69 @@ void tst_QAccessibility::comboBoxTest()
QTestAccessibility::clearEvents();
}
+void tst_QAccessibility::relationTest()
+{
+ auto windowHolder = std::make_unique<QWidget>();
+ auto window = windowHolder.get();
+ QString text = "Hello World";
+ QLabel *label = new QLabel(text, window);
+ setFrameless(label);
+ QSpinBox *spinBox = new QSpinBox(window);
+ label->setBuddy(spinBox);
+ QProgressBar *pb = new QProgressBar(window);
+ pb->setRange(0, 99);
+ connect(spinBox, SIGNAL(valueChanged(int)), pb, SLOT(setValue(int)));
+
+ window->resize(320, 200);
+ window->show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+#if defined(Q_OS_UNIX)
+ QCoreApplication::processEvents();
+#endif
+ QTest::qWait(100);
+
+ QAccessibleInterface *acc_label = QAccessible::queryAccessibleInterface(label);
+ QVERIFY(acc_label);
+ QAccessibleInterface *acc_spinBox = QAccessible::queryAccessibleInterface(spinBox);
+ QVERIFY(acc_spinBox);
+ QAccessibleInterface *acc_pb = QAccessible::queryAccessibleInterface(pb);
+ QVERIFY(acc_pb);
+
+ typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
+ {
+ const QList<RelationPair> rels = acc_label->relations(QAccessible::Labelled);
+ QCOMPARE(rels.size(), 1);
+ const RelationPair relPair = rels.first();
+
+ // spinBox is Labelled by acc_label
+ QCOMPARE(relPair.first->object(), spinBox);
+ QCOMPARE(relPair.second, QAccessible::Labelled);
+ }
+
+ {
+ // Test multiple relations (spinBox have two)
+ const QList<RelationPair> rels = acc_spinBox->relations();
+ QCOMPARE(rels.size(), 2);
+ int visitCount = 0;
+ for (const auto &relPair : rels) {
+ if (relPair.second & QAccessible::Label) {
+ // label is the Label of spinBox
+ QCOMPARE(relPair.first->object(), label);
+ ++visitCount;
+ } else if (relPair.second & QAccessible::Controlled) {
+ // progressbar is Controlled by the spinBox
+ QCOMPARE(relPair.first->object(), pb);
+ ++visitCount;
+ }
+ }
+ QCOMPARE(visitCount, rels.size());
+ }
+
+ windowHolder.reset();
+ QTestAccessibility::clearEvents();
+}
+
#if QT_CONFIG(shortcut)
void tst_QAccessibility::labelTest()
@@ -3653,6 +3843,8 @@ void tst_QAccessibility::labelTest()
QAccessibleInterface *acc_label = QAccessible::queryAccessibleInterface(label);
QVERIFY(acc_label);
+ QAccessibleInterface *acc_lineEdit = QAccessible::queryAccessibleInterface(buddy);
+ QVERIFY(acc_lineEdit);
QCOMPARE(acc_label->text(QAccessible::Name), text);
QCOMPARE(acc_label->state().editable, false);
@@ -3662,13 +3854,23 @@ void tst_QAccessibility::labelTest()
QCOMPARE(acc_label->state().focusable, false);
QCOMPARE(acc_label->state().readOnly, true);
- QList<QPair<QAccessibleInterface *, QAccessible::Relation>> rels = acc_label->relations();
- QCOMPARE(rels.count(), 1);
- QAccessibleInterface *iface = rels.first().first;
- QAccessible::Relation rel = rels.first().second;
- QCOMPARE(rel, QAccessible::Labelled);
- QCOMPARE(iface->role(), QAccessible::EditableText);
+ typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
+ {
+ const QList<RelationPair> rels = acc_label->relations(QAccessible::Labelled);
+ QCOMPARE(rels.size(), 1);
+ const RelationPair relPair = rels.first();
+ QCOMPARE(relPair.first->object(), buddy);
+ QCOMPARE(relPair.second, QAccessible::Labelled);
+ }
+
+ {
+ const QList<RelationPair> rels = acc_lineEdit->relations(QAccessible::Label);
+ QCOMPARE(rels.size(), 1);
+ const RelationPair relPair = rels.first();
+ QCOMPARE(relPair.first->object(), label);
+ QCOMPARE(relPair.second, QAccessible::Label);
+ }
windowHolder.reset();
QTestAccessibility::clearEvents();
@@ -3764,6 +3966,7 @@ void tst_QAccessibility::bridgeTest()
// For now this is a simple test to see if the bridge is working at all.
// Ideally it should be extended to test all aspects of the bridge.
#if defined(Q_OS_WIN)
+ auto guard = qScopeGuard([]() { QTestAccessibility::clearEvents(); });
QWidget window;
QVBoxLayout *lay = new QVBoxLayout(&window);
@@ -3812,14 +4015,14 @@ void tst_QAccessibility::bridgeTest()
POINT pt{nativePos.x(), nativePos.y()};
// Initialize COM stuff.
- HRESULT hr = CoInitialize(nullptr);
- QVERIFY(SUCCEEDED(hr));
+ QComHelper comHelper;
+ QVERIFY(comHelper.isValid());
// Get UI Automation interface.
const GUID CLSID_CUIAutomation_test{0xff48dba4, 0x60ef, 0x4201,
{0xaa,0x87, 0x54,0x10,0x3e,0xef,0x59,0x4e}};
IUIAutomation *automation = nullptr;
- hr = CoCreateInstance(CLSID_CUIAutomation_test, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
+ HRESULT hr = CoCreateInstance(CLSID_CUIAutomation_test, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
QVERIFY(SUCCEEDED(hr));
// Get button element from UI Automation using point.
@@ -3901,10 +4104,105 @@ void tst_QAccessibility::bridgeTest()
QCOMPARE(controlTypeId, UIA_ButtonControlTypeId);
// Edit
- hr = nodeList.at(2)->get_CurrentControlType(&controlTypeId);
+ IUIAutomationElement *uiaElement = nodeList.at(2);
+ hr = uiaElement->get_CurrentControlType(&controlTypeId);
QVERIFY(SUCCEEDED(hr));
QCOMPARE(controlTypeId, UIA_EditControlTypeId);
+ // "hello world\nhow are you today?\n"
+ IUIAutomationTextPattern *textPattern = nullptr;
+ hr = uiaElement->GetCurrentPattern(UIA_TextPattern2Id, reinterpret_cast<IUnknown**>(&textPattern));
+ QVERIFY(SUCCEEDED(hr));
+ QVERIFY(textPattern);
+
+ IUIAutomationTextRange *docRange = nullptr;
+ hr = textPattern->get_DocumentRange(&docRange);
+ QVERIFY(SUCCEEDED(hr));
+ QVERIFY(docRange);
+
+ IUIAutomationTextRange *textRange = nullptr;
+ hr = docRange->Clone(&textRange);
+ QVERIFY(SUCCEEDED(hr));
+ QVERIFY(textRange);
+ int moved;
+
+ auto rangeText = [](IUIAutomationTextRange *textRange) {
+ BSTR str;
+ QString res = "IUIAutomationTextRange::GetText() failed";
+ HRESULT hr = textRange->GetText(-1, &str);
+ if (SUCCEEDED(hr)) {
+ res = QString::fromWCharArray(str);
+ ::SysFreeString(str);
+ }
+ return res;
+ };
+
+ // Move start endpoint past "hello " to "world"
+ hr = textRange->Move(TextUnit_Character, 6, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(moved, 6);
+ // If the range was not empty, it should be collapsed to contain a single text unit
+ QCOMPARE(rangeText(textRange), QString("w"));
+
+ // Move end endpoint to end of "world"
+ hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, 4, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(moved, 4);
+ QCOMPARE(rangeText(textRange), QString("world"));
+
+ // MSDN: "Zero has no effect". This behavior was also verified with native controls.
+ hr = textRange->Move(TextUnit_Character, 0, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(moved, 0);
+ QCOMPARE(rangeText(textRange), QString("world"));
+
+ hr = textRange->Move(TextUnit_Character, 1, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(rangeText(textRange), QString("o"));
+
+ // move as far towards the end as possible
+ hr = textRange->Move(TextUnit_Character, 999, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(rangeText(textRange), QString(""));
+
+ hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, -1, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(rangeText(textRange), QString("\n"));
+
+ // move one forward (last possible position again)
+ hr = textRange->Move(TextUnit_Character, 1, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(rangeText(textRange), QString(""));
+
+ hr = textRange->Move(TextUnit_Character, -7, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(moved, -7);
+ QCOMPARE(rangeText(textRange), QString(""));
+ // simulate moving cursor (empty range) towards (and past) the end
+ QString today(" today?\n");
+ for (int i = 1; i < 9; ++i) { // 9 is deliberately too much
+ // peek one character back
+ hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, -1, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(rangeText(textRange), today.mid(i - 1, 1));
+
+ hr = textRange->Move(TextUnit_Character, 1, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ QCOMPARE(rangeText(textRange), today.mid(i, moved)); // when we cannot move further, moved will be 0
+
+ // Make the range empty again
+ hr = textRange->MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -moved, &moved);
+ QVERIFY(SUCCEEDED(hr));
+
+ // advance the empty range
+ hr = textRange->Move(TextUnit_Character, 1, &moved);
+ QVERIFY(SUCCEEDED(hr));
+ }
+ docRange->Release();
+ textRange->Release();
+ textPattern->Release();
+
+
// Table
hr = nodeList.at(3)->get_CurrentControlType(&controlTypeId);
QVERIFY(SUCCEEDED(hr));
@@ -3922,9 +4220,6 @@ void tst_QAccessibility::bridgeTest()
controlWalker->Release();
windowElement->Release();
automation->Release();
- CoUninitialize();
-
- QTestAccessibility::clearEvents();
#endif
}
@@ -4015,6 +4310,9 @@ private:
void tst_QAccessibility::focusChild()
{
+ if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
+ QSKIP("Platform does not support window activation");
+
{
QMainWindow mainWindow;
QtTestAccessibleWidget *widget1 = new QtTestAccessibleWidget(0, "Widget1");
@@ -4120,7 +4418,7 @@ void tst_QAccessibility::focusChild()
spy.clear();
tableView->setCurrentCell(2, 1);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
QAccessibleInterface *child = iface->focusChild();
QVERIFY(child);
@@ -4128,7 +4426,7 @@ void tst_QAccessibility::focusChild()
spy.clear();
tableView->setCurrentCell(1, 2);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
child = iface->focusChild();
QVERIFY(child);
@@ -4180,7 +4478,7 @@ void tst_QAccessibility::focusChild()
spy.clear();
treeView->setCurrentItem(item2);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
QAccessibleInterface *child = iface->focusChild();
QVERIFY(child);
@@ -4188,14 +4486,154 @@ void tst_QAccessibility::focusChild()
spy.clear();
treeView->setCurrentItem(item3);
- QTRY_COMPARE(spy.count(), 1);
+ QTRY_COMPARE(spy.size(), 1);
child = iface->focusChild();
QVERIFY(child);
QCOMPARE(child->text(QAccessible::Name), QStringLiteral("Klimt"));
}
+ {
+ QWidget window;
+ // takes the initial focus
+ QLineEdit lineEdit;
+ QComboBox comboBox;
+ comboBox.addItems({"One", "Two", "Three"});
+ QComboBox editableComboBox;
+ editableComboBox.setEditable(true);
+ editableComboBox.addItems({"A", "B", "C"});
+ QVBoxLayout vbox;
+ vbox.addWidget(&lineEdit);
+ vbox.addWidget(&comboBox);
+ vbox.addWidget(&editableComboBox);
+ window.setLayout(&vbox);
+
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+ QTestAccessibility::clearEvents();
+ QAccessibleInterface *iface = nullptr;
+
+ comboBox.setFocus();
+ {
+ QAccessibleEvent focusEvent(&comboBox, QAccessible::Focus);
+ QVERIFY(QTestAccessibility::containsEvent(&focusEvent));
+ }
+ iface = QAccessible::queryAccessibleInterface(&comboBox);
+ QVERIFY(iface);
+ QCOMPARE(iface->focusChild(), nullptr);
+
+ editableComboBox.setFocus();
+ // Qt updates about the editable combobox, not the lineedit, as the
+ // combobox is the lineedit's focus proxy.
+ {
+ QAccessibleEvent focusEvent(&editableComboBox, QAccessible::Focus);
+ QVERIFY(QTestAccessibility::containsEvent(&focusEvent));
+ }
+ iface = QAccessible::queryAccessibleInterface(&editableComboBox);
+ QVERIFY(iface);
+ QVERIFY(iface->focusChild());
+ QCOMPARE(iface->focusChild()->role(), QAccessible::EditableText);
+ }
}
+void tst_QAccessibility::messageBoxTest_data()
+{
+ QTest::addColumn<QMessageBox::Icon>("icon");
+ QTest::addColumn<QMessageBox::StandardButtons>("buttons");
+ QTest::addColumn<QString>("title");
+ QTest::addColumn<QString>("text");
+ QTest::addColumn<QString>("infoText");
+ QTest::addColumn<QString>("details");
+ QTest::addColumn<bool>("checkbox");
+ QTest::addColumn<bool>("textInteractive");
+
+ QTest::addRow("Information") << QMessageBox::Information
+ << QMessageBox::StandardButtons(QMessageBox::Ok)
+ << "Information"
+ << "Here, have some information."
+ << QString()
+ << QString()
+ << false
+ << false;
+
+ QTest::addRow("Warning") << QMessageBox::Warning
+ << QMessageBox::StandardButtons(QMessageBox::Ok | QMessageBox::Cancel)
+ << "Warning"
+ << "This is a dangerous operation!"
+ << "Ok or Cancel?"
+ << QString()
+ << true
+ << false;
+
+ QTest::addRow("Error") << QMessageBox::Critical
+ << QMessageBox::StandardButtons(QMessageBox::Abort | QMessageBox::Retry | QMessageBox::Ignore)
+ << "Error"
+ << "Operation failed for http://example.com"
+ << "You have to decide what to do"
+ << "Detailed log output"
+ << false
+ << true;
+}
+
+void tst_QAccessibility::messageBoxTest()
+{
+ QFETCH(QMessageBox::Icon, icon);
+ QFETCH(QMessageBox::StandardButtons, buttons);
+ QFETCH(QString, title);
+ QFETCH(QString, text);
+ QFETCH(QString, infoText);
+ QFETCH(QString, details);
+ QFETCH(bool, checkbox);
+ QFETCH(bool, textInteractive);
+
+ QMessageBox box(icon, title, text, buttons);
+
+ QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(&box);
+ QVERIFY(iface);
+ QCOMPARE(iface->role(), QAccessible::AlertMessage);
+#ifndef Q_OS_DARWIN // macOS message boxes show no title
+ QCOMPARE(iface->text(QAccessible::Name), title);
+#endif
+ QCOMPARE(iface->text(QAccessible::Value), text);
+
+ int expectedChildCount = 3;
+ QCOMPARE(iface->childCount(), expectedChildCount);
+
+ if (textInteractive)
+ box.setTextInteractionFlags(Qt::TextBrowserInteraction);
+
+ if (!infoText.isEmpty()) {
+ box.setInformativeText(infoText);
+ QCOMPARE(iface->childCount(), ++expectedChildCount);
+ }
+ QCOMPARE(iface->text(QAccessible::Help), infoText);
+
+ if (!details.isEmpty()) {
+ box.setDetailedText(details);
+ QCOMPARE(iface->childCount(), ++expectedChildCount);
+ }
+ if (checkbox) {
+ box.setCheckBox(new QCheckBox("Don't show again"));
+ QCOMPARE(iface->childCount(), ++expectedChildCount);
+ }
+
+ QTestAccessibility::clearEvents();
+
+ QDialogPrivate *boxPrivate = static_cast<QDialogPrivate *>(QDialogPrivate::get(&box));
+ if (!boxPrivate->canBeNativeDialog()) {
+ // platforms that use a native message box will not emit accessibility events
+ box.show();
+
+ QAccessibleEvent showEvent(&box, QAccessible::DialogStart);
+ QVERIFY(QTestAccessibility::containsEvent(&showEvent));
+
+ box.hide();
+
+ QAccessibleEvent hideEvent(&box, QAccessible::DialogEnd);
+ QVERIFY(QTestAccessibility::containsEvent(&hideEvent));
+ }
+
+ QTestAccessibility::clearEvents();
+}
QTEST_MAIN(tst_QAccessibility)
#include "tst_qaccessibility.moc"