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(&registry, 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"