File indexing completed on 2024-04-28 08:34:20
0001 /* 0002 SPDX-FileCopyrightText: 2012 Frederik Gladhorn <gladhorn@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include <QTest> 0008 0009 #include <QMainWindow> 0010 #include <QPushButton> 0011 #include <QTextEdit> 0012 #include <QLabel> 0013 #include <QLineEdit> 0014 #include <QBoxLayout> 0015 #include <QAccessible> 0016 #include <QDebug> 0017 #include <QProcess> 0018 #include <QFileInfo> 0019 0020 #include "qaccessibilityclient/registry.h" 0021 #include "qaccessibilityclient/accessibleobject.h" 0022 0023 #include "atspi/dbusconnection.h" 0024 0025 typedef QSharedPointer<QAccessibleInterface> QAIPointer; 0026 0027 using namespace QAccessibleClient; 0028 0029 struct Event { 0030 Event(const AccessibleObject &obj) 0031 : object(obj) 0032 {} 0033 0034 AccessibleObject object; 0035 }; 0036 0037 class EventListener : public QObject 0038 { 0039 Q_OBJECT 0040 public Q_SLOTS: 0041 void focus(const QAccessibleClient::AccessibleObject &object) { 0042 focusEvents.append(Event(object)); 0043 } 0044 0045 public: 0046 QList<Event> focusEvents; 0047 }; 0048 0049 class AccessibilityClientTest :public QObject 0050 { 0051 Q_OBJECT 0052 0053 private Q_SLOTS: 0054 void initTestCase(); 0055 void cleanup(); 0056 0057 void tst_registry(); 0058 void tst_accessibleObject(); 0059 void tst_hashable(); 0060 void tst_application(); 0061 void tst_navigation(); 0062 void tst_focus(); 0063 void tst_states(); 0064 0065 void tst_extents(); 0066 0067 void tst_characterExtents(); 0068 0069 private: 0070 bool startHelperProcess(); 0071 Registry registry; 0072 QProcess helperProcess; 0073 }; 0074 0075 void AccessibilityClientTest::initTestCase() 0076 { 0077 } 0078 0079 0080 AccessibleObject getAppObject(const Registry &r, const QString &appName) 0081 { 0082 AccessibleObject accApp; 0083 0084 QApplication::processEvents(); 0085 const QList<AccessibleObject> apps = r.applications(); 0086 for (const AccessibleObject &app : apps) { 0087 if (app.name() == appName) { 0088 accApp = app; 0089 break; 0090 } 0091 } 0092 return accApp; 0093 } 0094 0095 void AccessibilityClientTest::cleanup() 0096 { 0097 registry.subscribeEventListeners(Registry::NoEventListeners); 0098 } 0099 0100 void AccessibilityClientTest::tst_registry() 0101 { 0102 QVERIFY(registry.subscribedEventListeners() == Registry::NoEventListeners); 0103 registry.subscribeEventListeners(Registry::Window); 0104 QVERIFY(registry.subscribedEventListeners() == Registry::Window); 0105 registry.subscribeEventListeners(Registry::Focus); 0106 QVERIFY(registry.subscribedEventListeners() == Registry::Focus); 0107 registry.subscribeEventListeners(Registry::Focus | Registry::Window); 0108 QVERIFY(registry.subscribedEventListeners() == (Registry::Focus | Registry::Window)); 0109 0110 registry.subscribeEventListeners(Registry::NoEventListeners); 0111 QVERIFY(registry.subscribedEventListeners() == Registry::NoEventListeners); 0112 registry.subscribeEventListeners(Registry::AllEventListeners); 0113 QVERIFY(registry.subscribedEventListeners() == Registry::AllEventListeners); 0114 QVERIFY(registry.subscribedEventListeners() & Registry::Window); 0115 } 0116 0117 void AccessibilityClientTest::tst_accessibleObject() 0118 { 0119 AccessibleObject invalidObject; 0120 QVERIFY(!invalidObject.isValid()); 0121 AccessibleObject invalid2(invalidObject); 0122 QVERIFY(!invalid2.isValid()); 0123 } 0124 0125 void AccessibilityClientTest::tst_hashable() 0126 { 0127 AccessibleObject testObject; 0128 QHash<AccessibleObject, int> testHash; 0129 testHash[testObject] = 1; 0130 QCOMPARE(testHash[testObject], 1); 0131 } 0132 0133 void AccessibilityClientTest::tst_application() 0134 { 0135 QString appName = QLatin1String("Lib QAccessibleClient test"); 0136 qApp->setApplicationName(appName); 0137 QWidget w; 0138 w.setAccessibleName(QStringLiteral("Foobar 99")); 0139 w.show(); 0140 0141 AccessibleObject accApp; 0142 QVERIFY(!accApp.isValid()); 0143 accApp = getAppObject(registry, appName); 0144 QVERIFY(accApp.isValid()); 0145 QCOMPARE(accApp.name(), appName); 0146 QCOMPARE(accApp.childCount(), 1); 0147 0148 AccessibleObject copy1(accApp); 0149 AccessibleObject copy2 = accApp; 0150 QVERIFY(copy1.isValid()); 0151 QCOMPARE(copy1.name(), appName); 0152 QVERIFY(copy2.isValid()); 0153 QCOMPARE(copy2.name(), appName); 0154 } 0155 0156 void AccessibilityClientTest::tst_navigation() 0157 { 0158 QString appName = QLatin1String("Lib QAccessibleClient test"); 0159 qApp->setApplicationName(appName); 0160 QWidget w; 0161 w.setAccessibleName(QStringLiteral("Root Widget")); 0162 w.setAccessibleDescription(QStringLiteral("This is a useless widget")); 0163 QVBoxLayout *layout = new QVBoxLayout; 0164 w.setLayout(layout); 0165 0166 QPushButton *button = new QPushButton; 0167 layout->addWidget(button); 0168 button->setText(QLatin1String("Hello a11y")); 0169 QString desc = QStringLiteral("This is a button..."); 0170 button->setAccessibleDescription(desc); 0171 w.show(); 0172 w.activateWindow(); 0173 button->setFocus(); 0174 0175 QVERIFY(QTest::qWaitForWindowExposed(&w)); 0176 QVERIFY(QTest::qWaitForWindowActive(&w)); 0177 0178 // App 0179 AccessibleObject accApp = getAppObject(registry, appName); 0180 QVERIFY(accApp.isValid()); 0181 QCOMPARE(accApp.name(), appName); 0182 QCOMPARE(accApp.childCount(), 1); 0183 0184 // What should this return? 0185 QCOMPARE(accApp.indexInParent(), -1); 0186 0187 // Root widget 0188 AccessibleObject accW = accApp.child(0); 0189 QVERIFY(accW.isValid()); 0190 qDebug() << "NAME: " << accW.name(); 0191 QCOMPARE(accW.name(), w.accessibleName()); 0192 QCOMPARE(accW.description(), w.accessibleDescription()); 0193 QCOMPARE(accW.role(), AccessibleObject::Filler); 0194 QCOMPARE(accW.roleName(), QLatin1String("filler")); 0195 QCOMPARE(accW.childCount(), 1); 0196 QCOMPARE(accW.indexInParent(), 0); 0197 QVERIFY(accW.isActive()); 0198 0199 // Button 0200 AccessibleObject accButton = accW.child(0); 0201 QVERIFY(accButton.isValid()); 0202 QCOMPARE(accButton.name(), button->text()); 0203 QCOMPARE(accButton.description(), desc); 0204 QCOMPARE(accButton.role(), AccessibleObject::Button); 0205 QCOMPARE(accButton.roleName(), QLatin1String("push button")); 0206 QVERIFY(!accButton.localizedRoleName().isEmpty()); 0207 QCOMPARE(accButton.indexInParent(), 0); 0208 0209 AccessibleObject accButton2 = accW.children().first(); 0210 QCOMPARE(accButton, accButton2); 0211 AccessibleObject parent = accButton.parent(); 0212 QCOMPARE(parent, accW); 0213 AccessibleObject parentParent = parent.parent(); 0214 QCOMPARE(parentParent, accApp); 0215 0216 AccessibleObject invalidChild = accButton.child(0); 0217 QVERIFY(!invalidChild.isValid()); 0218 QVERIFY(invalidChild.name().isEmpty()); 0219 0220 AccessibleObject invalidParent = accApp.parent(); 0221 QVERIFY(!invalidParent.isValid()); 0222 QVERIFY(invalidParent.name().isEmpty()); 0223 0224 // Add a label and line edit 0225 QLabel *label = new QLabel; 0226 label->setText(QStringLiteral("Name:")); 0227 layout->addWidget(label); 0228 QLineEdit *line = new QLineEdit; 0229 layout->addWidget(line); 0230 label->setBuddy(line); 0231 QApplication::processEvents(); 0232 QCOMPARE(accW.childCount(), 3); 0233 0234 AccessibleObject accLabel = accW.child(1); 0235 QVERIFY(accLabel.isValid()); 0236 QCOMPARE(accLabel.name(), label->text()); 0237 QCOMPARE(accLabel.role(), AccessibleObject::Label); 0238 QCOMPARE(accLabel.roleName(), QLatin1String("label")); 0239 QCOMPARE(accLabel.indexInParent(), 1); 0240 QVERIFY(accLabel.isVisible()); 0241 QVERIFY(!accLabel.isCheckable()); 0242 QVERIFY(!accLabel.isChecked()); 0243 QVERIFY(!accLabel.isFocusable()); 0244 QVERIFY(!accLabel.isFocused()); 0245 QVERIFY(!accLabel.isEditable()); 0246 0247 AccessibleObject accLine = accW.child(2); 0248 QVERIFY(accLine.isValid()); 0249 QCOMPARE(accLine.name(), label->text()); 0250 QCOMPARE(accLine.role(), AccessibleObject::Text); 0251 QCOMPARE(accLine.roleName(), QLatin1String("text")); 0252 QCOMPARE(accLine.indexInParent(), 2); 0253 QVERIFY(accLine.isEditable()); 0254 AccessibleObject parent1 = accLine.parent(); 0255 QCOMPARE(parent1, accW); 0256 0257 QVERIFY(accLine.isFocusable()); 0258 QVERIFY(accButton.isFocusable()); 0259 QVERIFY(accButton.isFocused()); 0260 QVERIFY(!accLine.isFocused()); 0261 line->setFocus(); 0262 QApplication::processEvents(); 0263 QVERIFY(accLine.isFocused()); 0264 QVERIFY(!accButton.isFocused()); 0265 0266 label->setVisible(false); 0267 line->setVisible(false); 0268 QApplication::processEvents(); 0269 QTest::qWait(1000); 0270 QVERIFY(!accLabel.isVisible()); 0271 QVERIFY(!accLine.isVisible()); 0272 } 0273 0274 bool AccessibilityClientTest::startHelperProcess() 0275 { 0276 if (!QFileInfo(QCoreApplication::applicationDirPath() + QStringLiteral("/simplewidgetapp")).exists()) { 0277 qWarning() << "WARNING: Could not find test case helper executable." 0278 " Please run this test in the path where the executable is located."; 0279 return false; 0280 } 0281 0282 // start peer server 0283 helperProcess.setProgram(QCoreApplication::applicationDirPath() + QStringLiteral("/simplewidgetapp")); 0284 helperProcess.start(); 0285 if (!helperProcess.waitForStarted()) { 0286 qWarning() << "WARNING: Could not start helper executable. Test will not run."; 0287 return false; 0288 } 0289 return true; 0290 } 0291 0292 void AccessibilityClientTest::tst_focus() 0293 { 0294 registry.subscribeEventListeners(Registry::Focus); 0295 EventListener *listener = new EventListener; 0296 connect(®istry, SIGNAL(focusChanged(QAccessibleClient::AccessibleObject)), listener, SLOT(focus(QAccessibleClient::AccessibleObject))); 0297 0298 QVERIFY(startHelperProcess()); 0299 0300 AccessibleObject remoteApp; 0301 QString appName = QLatin1String("LibKdeAccessibilityClient Simple Widget App"); 0302 // startup and init takes some time, give up to two seconds and a few dbus calls 0303 int attempts = 0; 0304 while (attempts < 20) { 0305 ++attempts; 0306 QTest::qWait(100); 0307 remoteApp = getAppObject(registry, appName); 0308 if (remoteApp.isValid()) 0309 break; 0310 } 0311 0312 // waiting for two events, may take some time 0313 for (int i = 0; i < 20; ++i) { 0314 QTest::qWait(10); 0315 if (listener->focusEvents.size() >= 2) 0316 break; 0317 } 0318 0319 QVERIFY(remoteApp.isValid()); 0320 QCOMPARE(remoteApp.name(), appName); 0321 0322 AccessibleObject window = remoteApp.child(0); 0323 AccessibleObject button1 = window.child(0); 0324 AccessibleObject button2 = window.child(1); 0325 0326 // we can get other focus events, check that we only use the ones from our app 0327 for (int i = 0; i < listener->focusEvents.count(); ++i) { 0328 AccessibleObject ev = listener->focusEvents.at(i).object; 0329 if (ev.application() != remoteApp) 0330 listener->focusEvents.removeAt(i);; 0331 } 0332 QVERIFY(listener->focusEvents.size() == 2); 0333 QCOMPARE(listener->focusEvents.at(0).object, button1); 0334 QCOMPARE(listener->focusEvents.at(1).object, button2); 0335 0336 // use action interface to select the first button again and check that we get an event 0337 0338 delete listener; 0339 helperProcess.terminate(); 0340 } 0341 0342 void AccessibilityClientTest::tst_states() 0343 { 0344 registry.subscribeEventListeners(Registry::StateChanged); 0345 0346 QString appName = QLatin1String("Lib QAccessibleClient test"); 0347 qApp->setApplicationName(appName); 0348 QWidget w; 0349 w.setAccessibleName(QStringLiteral("Root Widget")); 0350 w.setAccessibleDescription(QStringLiteral("This is a useless widget")); 0351 QVBoxLayout *layout = new QVBoxLayout; 0352 w.setLayout(layout); 0353 0354 QPushButton *button1 = new QPushButton; 0355 layout->addWidget(button1); 0356 button1->setText(QLatin1String("Hello a11y")); 0357 QString desc = QStringLiteral("This is a button..."); 0358 button1->setAccessibleDescription(desc); 0359 0360 QPushButton *button2 = new QPushButton; 0361 layout->addWidget(button2); 0362 button2->setText(QLatin1String("Hello a11y")); 0363 0364 w.show(); 0365 button1->setFocus(); 0366 0367 QVERIFY(QTest::qWaitForWindowExposed(&w)); 0368 AccessibleObject accApp = getAppObject(registry, appName); 0369 QVERIFY(accApp.isValid()); 0370 0371 // Root widget 0372 AccessibleObject accW = accApp.child(0); 0373 QVERIFY(accW.isValid()); 0374 0375 // Buttons 0376 AccessibleObject accButton1 = accW.child(0); 0377 QVERIFY(accButton1.isValid()); 0378 QCOMPARE(accButton1.name(), button1->text()); 0379 0380 AccessibleObject accButton2 = accW.child(1); 0381 QVERIFY(accButton2.isValid()); 0382 QCOMPARE(accButton2.name(), button2->text()); 0383 0384 QVERIFY(accButton1.isVisible()); 0385 button1->setVisible(false); 0386 QVERIFY(!accButton1.isVisible()); 0387 button1->setVisible(true); 0388 QVERIFY(accButton1.isVisible()); 0389 0390 QVERIFY(accButton1.isEnabled()); 0391 button1->setEnabled(false); 0392 QVERIFY(!accButton1.isEnabled()); 0393 button1->setEnabled(true); 0394 QVERIFY(accButton1.isEnabled()); 0395 } 0396 0397 void AccessibilityClientTest::tst_extents() 0398 { 0399 QVERIFY(startHelperProcess()); 0400 0401 AccessibleObject remoteApp; 0402 QString appName = QLatin1String("LibKdeAccessibilityClient Simple Widget App"); 0403 0404 int attempts = 0; 0405 while(attempts < 20) { 0406 ++attempts; 0407 QTest::qWait(100); 0408 remoteApp = getAppObject(registry,appName); 0409 if(remoteApp.isValid()) 0410 break; 0411 } 0412 0413 QVERIFY(remoteApp.isValid()); 0414 QCOMPARE(remoteApp.name(), appName); 0415 0416 AccessibleObject window = remoteApp.child(0); 0417 QVERIFY(window.supportedInterfaces() & QAccessibleClient::AccessibleObject::ComponentInterface); 0418 QCOMPARE(window.boundingRect().size(), QSize(200,100)); 0419 0420 AccessibleObject button1 = window.child(0); 0421 QVERIFY(button1.name()==QStringLiteral("Button 1")); 0422 QCOMPARE(button1.boundingRect().size(), QSize(100,20)); 0423 helperProcess.terminate(); 0424 } 0425 0426 void AccessibilityClientTest::tst_characterExtents() 0427 { 0428 QString appName = QLatin1String("Lib QAccessibleClient test"); 0429 0430 QWidget w; 0431 w.setAccessibleName(QStringLiteral("Root Widget")); 0432 QTextEdit *textEdit = new QTextEdit(&w); 0433 textEdit->setGeometry(10,10,600,400); 0434 w.show(); 0435 QVERIFY(QTest::qWaitForWindowExposed(&w)); 0436 AccessibleObject app = getAppObject(registry, appName); 0437 0438 //Check if the widget is correct 0439 QVERIFY(app.isValid()); 0440 QCOMPARE(app.name(), appName); 0441 QCOMPARE(app.childCount(), 1); 0442 0443 AccessibleObject textArea = app.child(0).child(0); 0444 QVERIFY(textArea.supportedInterfaces() & QAccessibleClient::AccessibleObject::TextInterface); 0445 0446 textEdit->setText(QStringLiteral("This is useless text that is being used to test this text area.\n I \n hope \n this will get correct\n\t\t\tCharacterExtents!")); 0447 QPoint pos = w.pos(); 0448 0449 int start; 0450 int end; 0451 QString textWord = textArea.textWithBoundary(0, AccessibleObject::WordStartBoundary, &start, &end); 0452 QCOMPARE(textWord, QStringLiteral("This")); 0453 QCOMPARE(start, 0); 0454 QCOMPARE(end, 4); 0455 textWord = textArea.textWithBoundary(6, AccessibleObject::WordStartBoundary, &start, &end); 0456 QCOMPARE(textWord , QStringLiteral("is")); 0457 QCOMPARE(start, 5); 0458 QCOMPARE(end, 7); 0459 textWord = textArea.textWithBoundary(3, AccessibleObject::WordEndBoundary); 0460 QCOMPARE(textWord , QStringLiteral("This")); 0461 0462 QString textSentence = textArea.textWithBoundary(0, AccessibleObject::SentenceEndBoundary); 0463 QCOMPARE(textSentence, QStringLiteral("This is useless text that is being used to test this text area.")); 0464 QString textLine = textArea.textWithBoundary(0, AccessibleObject::LineEndBoundary); 0465 QCOMPARE(textLine, QStringLiteral("This is useless text that is being used to test this text area.")); 0466 textLine = textArea.textWithBoundary(0, AccessibleObject::LineEndBoundary); 0467 QCOMPARE(textLine, QStringLiteral("This is useless text that is being used to test this text area.")); 0468 0469 QAccessibleInterface *textEditInterface = QAccessible::queryAccessibleInterface(textEdit); 0470 QCOMPARE(textArea.characterRect(0), textEditInterface->textInterface()->characterRect(0)); 0471 QCOMPARE(textArea.characterRect(1), textEditInterface->textInterface()->characterRect(1)); 0472 } 0473 0474 0475 QTEST_MAIN(AccessibilityClientTest) 0476 0477 #include "tst_accessibilityclient.moc"