Warning, file /graphics/okular/autotests/jsfunctionstest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2019 João Netto <joaonetto901@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include <QAction> 0008 #include <QJsonArray> 0009 #include <QJsonDocument> 0010 #include <QMenu> 0011 #include <QTest> 0012 #include <QTimer> 0013 0014 #include "../settings_core.h" 0015 #include "core/action.h" 0016 #include "core/document.h" 0017 #include "core/scripter.h" 0018 #include <QAbstractButton> 0019 #include <QAbstractItemModel> 0020 #include <QMap> 0021 #include <QMessageBox> 0022 #include <QMimeDatabase> 0023 #include <QMimeType> 0024 #include <QThread> 0025 #include <core/form.h> 0026 #include <core/page.h> 0027 #include <core/script/event_p.h> 0028 0029 class MessageBoxHelper : public QObject 0030 { 0031 Q_OBJECT 0032 0033 public: 0034 MessageBoxHelper(QMessageBox::StandardButton b, QString message, QMessageBox::Icon icon, QString title, bool hasCheckBox) 0035 : m_button(b) 0036 , m_clicked(false) 0037 , m_message(std::move(message)) 0038 , m_icon(icon) 0039 , m_title(std::move(title)) 0040 , m_checkBox(hasCheckBox) 0041 { 0042 QTimer::singleShot(0, this, &MessageBoxHelper::closeMessageBox); 0043 } 0044 0045 ~MessageBoxHelper() override 0046 { 0047 QVERIFY(m_clicked); 0048 } 0049 0050 private Q_SLOTS: 0051 void closeMessageBox() 0052 { 0053 const QWidgetList allToplevelWidgets = QApplication::topLevelWidgets(); 0054 QMessageBox *mb = nullptr; 0055 for (QWidget *w : allToplevelWidgets) { 0056 if (w->inherits("QMessageBox")) { 0057 mb = qobject_cast<QMessageBox *>(w); 0058 QCOMPARE(mb->text(), m_message); 0059 QCOMPARE(mb->windowTitle(), m_title); 0060 QCOMPARE(mb->icon(), m_icon); 0061 QCheckBox *box = mb->checkBox(); 0062 QCOMPARE(box != nullptr, m_checkBox); 0063 mb->button(m_button)->click(); 0064 } 0065 } 0066 if (!mb) { 0067 QTimer::singleShot(0, this, &MessageBoxHelper::closeMessageBox); 0068 return; 0069 } 0070 m_clicked = true; 0071 } 0072 0073 private: 0074 QMessageBox::StandardButton m_button; 0075 bool m_clicked; 0076 QString m_message; 0077 QMessageBox::Icon m_icon; 0078 QString m_title; 0079 bool m_checkBox; 0080 }; 0081 0082 class JSFunctionsTest : public QObject 0083 { 0084 Q_OBJECT 0085 0086 private Q_SLOTS: 0087 void initTestCase(); 0088 void testNthFieldName(); 0089 void testDisplay(); 0090 void testSetClearInterval(); 0091 void testSetClearTimeOut(); 0092 void testGetOCGs(); 0093 void cleanupTestCase(); 0094 void testAlert(); 0095 void testPopUpMenu(); 0096 void testPopUpMenuEx(); 0097 void testPrintD(); 0098 void testPrintD_data(); 0099 0100 private: 0101 Okular::Document *m_document; 0102 QMap<QString, Okular::FormField *> m_fields; 0103 }; 0104 0105 void JSFunctionsTest::initTestCase() 0106 { 0107 Okular::SettingsCore::instance(QStringLiteral("jsfunctionstest")); 0108 m_document = new Okular::Document(nullptr); 0109 0110 const QString testFile = QStringLiteral(KDESRCDIR "data/kjsfunctionstest.pdf"); 0111 QMimeDatabase db; 0112 const QMimeType mime = db.mimeTypeForFile(testFile); 0113 QCOMPARE(m_document->openDocument(testFile, QUrl(), mime), Okular::Document::OpenSuccess); 0114 0115 const Okular::Page *page = m_document->page(0); 0116 const QList<Okular::FormField *> pageFormFields = page->formFields(); 0117 for (Okular::FormField *ff : pageFormFields) { 0118 m_fields.insert(ff->name(), ff); 0119 } 0120 } 0121 0122 void JSFunctionsTest::testNthFieldName() 0123 { 0124 for (int i = 0; i < 21; ++i) { 0125 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, 0126 QStringLiteral("var field = Doc.getField( Doc.getNthFieldName(%1) );\ 0127 field.display = display.visible;") 0128 .arg(i)); 0129 m_document->processAction(action); 0130 QVERIFY(m_fields[QStringLiteral("0.%1").arg(i)]->isVisible()); 0131 m_fields[QStringLiteral("0.%1").arg(i)]->setVisible(false); 0132 delete action; 0133 } 0134 } 0135 0136 void JSFunctionsTest::testDisplay() 0137 { 0138 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("field = Doc.getField(\"0.0\");field.display=display.hidden;\ 0139 field = Doc.getField(\"0.10\");field.display=display.visible;")); 0140 m_document->processAction(action); 0141 QVERIFY(!m_fields[QStringLiteral("0.0")]->isVisible()); 0142 QVERIFY(!m_fields[QStringLiteral("0.0")]->isPrintable()); 0143 QVERIFY(m_fields[QStringLiteral("0.10")]->isVisible()); 0144 QVERIFY(m_fields[QStringLiteral("0.10")]->isPrintable()); 0145 delete action; 0146 0147 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("field = Doc.getField(\"0.10\");field.display=display.noView;\ 0148 field = Doc.getField(\"0.15\");field.display=display.noPrint;")); 0149 m_document->processAction(action); 0150 QVERIFY(!m_fields[QStringLiteral("0.10")]->isVisible()); 0151 QVERIFY(m_fields[QStringLiteral("0.10")]->isPrintable()); 0152 QVERIFY(m_fields[QStringLiteral("0.15")]->isVisible()); 0153 QVERIFY(!m_fields[QStringLiteral("0.15")]->isPrintable()); 0154 delete action; 0155 0156 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("field = Doc.getField(\"0.15\");field.display=display.hidden;\ 0157 field = Doc.getField(\"0.20\");field.display=display.visible;")); 0158 m_document->processAction(action); 0159 QVERIFY(!m_fields[QStringLiteral("0.15")]->isVisible()); 0160 QVERIFY(!m_fields[QStringLiteral("0.15")]->isPrintable()); 0161 QVERIFY(m_fields[QStringLiteral("0.20")]->isVisible()); 0162 QVERIFY(m_fields[QStringLiteral("0.20")]->isPrintable()); 0163 delete action; 0164 0165 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("field = Doc.getField(\"0.20\");field.display=display.hidden;\ 0166 field = Doc.getField(\"0.0\");field.display=display.visible;")); 0167 m_document->processAction(action); 0168 QVERIFY(!m_fields[QStringLiteral("0.20")]->isVisible()); 0169 QVERIFY(!m_fields[QStringLiteral("0.20")]->isPrintable()); 0170 QVERIFY(m_fields[QStringLiteral("0.0")]->isVisible()); 0171 QVERIFY(m_fields[QStringLiteral("0.0")]->isPrintable()); 0172 delete action; 0173 } 0174 0175 void delay() 0176 { 0177 QTime dieTime = QTime::currentTime().addSecs(2); 0178 while (QTime::currentTime() < dieTime) { 0179 QCoreApplication::processEvents(QEventLoop::AllEvents, 100); 0180 } 0181 } 0182 0183 void JSFunctionsTest::testSetClearInterval() 0184 { 0185 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("obj = new Object();obj.idx=0;\ 0186 obj.inc=function(){field = Doc.getField(Doc.getNthFieldName(obj.idx));\ 0187 field.display = display.visible;\ 0188 obj.idx = obj.idx + 1;};\ 0189 intv = app.setInterval('obj.inc()', 450);obj.idx;")); 0190 m_document->processAction(action); 0191 QVERIFY(m_fields[QStringLiteral("0.0")]->isVisible()); 0192 QVERIFY(!m_fields[QStringLiteral("0.3")]->isVisible()); 0193 delete action; 0194 delay(); 0195 0196 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("app.clearInterval(intv);obj.idx;")); 0197 m_document->processAction(action); 0198 QVERIFY(m_fields[QStringLiteral("0.3")]->isVisible()); 0199 delete action; 0200 } 0201 0202 void JSFunctionsTest::testSetClearTimeOut() 0203 { 0204 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("intv = app.setTimeOut('obj.inc()', 1);obj.idx;")); 0205 m_document->processAction(action); 0206 QVERIFY(m_fields[QStringLiteral("0.3")]->isVisible()); 0207 QVERIFY(!m_fields[QStringLiteral("0.4")]->isVisible()); 0208 delay(); 0209 delete action; 0210 0211 QVERIFY(m_fields[QStringLiteral("0.4")]->isVisible()); 0212 0213 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("intv = app.setTimeOut('obj.inc()', 2000);obj.idx;")); 0214 m_document->processAction(action); 0215 QVERIFY(m_fields[QStringLiteral("0.4")]->isVisible()); 0216 delete action; 0217 0218 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("app.clearTimeOut(intv);obj.idx;")); 0219 m_document->processAction(action); 0220 QVERIFY(m_fields[QStringLiteral("0.4")]->isVisible()); 0221 delay(); 0222 QVERIFY(m_fields[QStringLiteral("0.4")]->isVisible()); 0223 delete action; 0224 } 0225 0226 void JSFunctionsTest::testGetOCGs() 0227 { 0228 QAbstractItemModel *model = m_document->layersModel(); 0229 0230 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("var ocg = this.getOCGs(this.pageNum);\ 0231 ocg[0].state = false;")); 0232 m_document->processAction(action); 0233 QVERIFY(!model->data(model->index(0, 0), Qt::CheckStateRole).toBool()); 0234 delete action; 0235 0236 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[0].state = true;")); 0237 m_document->processAction(action); 0238 QVERIFY(model->data(model->index(0, 0), Qt::CheckStateRole).toBool()); 0239 delete action; 0240 0241 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[1].state = false;")); 0242 m_document->processAction(action); 0243 QVERIFY(!model->data(model->index(1, 0), Qt::CheckStateRole).toBool()); 0244 delete action; 0245 0246 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[1].state = true;")); 0247 m_document->processAction(action); 0248 QVERIFY(model->data(model->index(1, 0), Qt::CheckStateRole).toBool()); 0249 delete action; 0250 0251 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[2].state = false;")); 0252 m_document->processAction(action); 0253 QVERIFY(!model->data(model->index(2, 0), Qt::CheckStateRole).toBool()); 0254 delete action; 0255 0256 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[2].state = true;")); 0257 m_document->processAction(action); 0258 QVERIFY(model->data(model->index(2, 0), Qt::CheckStateRole).toBool()); 0259 delete action; 0260 0261 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[3].state = false;")); 0262 m_document->processAction(action); 0263 QVERIFY(!model->data(model->index(3, 0), Qt::CheckStateRole).toBool()); 0264 delete action; 0265 0266 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ocg[3].state = true;")); 0267 m_document->processAction(action); 0268 QVERIFY(model->data(model->index(3, 0), Qt::CheckStateRole).toBool()); 0269 delete action; 0270 } 0271 0272 void JSFunctionsTest::testAlert() 0273 { 0274 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ret = app.alert( \"Random Message\" );")); 0275 QScopedPointer<MessageBoxHelper> messageBoxHelper; 0276 messageBoxHelper.reset(new MessageBoxHelper(QMessageBox::Ok, QStringLiteral("Random Message"), QMessageBox::Critical, QStringLiteral("Okular"), false)); 0277 m_document->processAction(action); 0278 delete action; 0279 0280 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ret = app.alert( \"Empty Message\", 1 );")); 0281 messageBoxHelper.reset(new MessageBoxHelper(QMessageBox::Ok, QStringLiteral("Empty Message"), QMessageBox::Warning, QStringLiteral("Okular"), false)); 0282 m_document->processAction(action); 0283 delete action; 0284 0285 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ret = app.alert( \"No Message\", 2, 2 );")); 0286 messageBoxHelper.reset(new MessageBoxHelper(QMessageBox::Yes, QStringLiteral("No Message"), QMessageBox::Question, QStringLiteral("Okular"), false)); 0287 m_document->processAction(action); 0288 delete action; 0289 0290 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ret = app.alert( \"No\", 3, 2, \"Test Dialog\" );")); 0291 messageBoxHelper.reset(new MessageBoxHelper(QMessageBox::No, QStringLiteral("No"), QMessageBox::Information, QStringLiteral("Test Dialog"), false)); 0292 m_document->processAction(action); 0293 delete action; 0294 0295 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("var oCheckBox = new Object();\ 0296 ret = app.alert( \"Cancel\", 3, 3, \"Test Dialog\", 0, oCheckBox );")); 0297 messageBoxHelper.reset(new MessageBoxHelper(QMessageBox::Cancel, QStringLiteral("Cancel"), QMessageBox::Information, QStringLiteral("Test Dialog"), true)); 0298 m_document->processAction(action); 0299 delete action; 0300 } 0301 0302 class PopupMenuHelper : public QObject 0303 { 0304 Q_OBJECT 0305 0306 public: 0307 PopupMenuHelper() 0308 : m_menuFound(false) 0309 { 0310 QTimer::singleShot(0, this, &PopupMenuHelper::closeMenu); 0311 } 0312 0313 ~PopupMenuHelper() override 0314 { 0315 } 0316 0317 bool menuFound() 0318 { 0319 return m_menuFound; 0320 } 0321 0322 const QJsonArray &menuTree() 0323 { 0324 return m_menuTree; 0325 } 0326 0327 private Q_SLOTS: 0328 void closeMenu() 0329 { 0330 const QWidgetList allToplevelWidgets = QApplication::topLevelWidgets(); 0331 QMenu *menu = nullptr; 0332 for (QWidget *w : allToplevelWidgets) { 0333 if (w->objectName() == QStringLiteral("popUpMenuEx") && w->inherits("QMenu")) { 0334 menu = qobject_cast<QMenu *>(w); 0335 0336 // Generate an tree of string with all the menu trees 0337 processQMenuToJS(menu, m_menuTree); 0338 0339 menu->close(); 0340 m_menuFound = true; 0341 break; 0342 } 0343 } 0344 if (!menu) { 0345 QTimer::singleShot(0, this, &PopupMenuHelper::closeMenu); 0346 } 0347 } 0348 0349 private: 0350 static void processQMenuToJS(QMenu *menu, QJsonArray &array) 0351 { 0352 QList<QAction *> actions = menu->actions(); 0353 0354 for (QAction *action : actions) { 0355 QMenu *itMenu = action->menu(); 0356 0357 if (itMenu != nullptr) { 0358 QJsonArray subMenus; 0359 subMenus.append(action->text()); 0360 processQMenuToJS(itMenu, subMenus); 0361 array.append(subMenus); 0362 } else { 0363 array.append(action->text()); 0364 } 0365 } 0366 } 0367 0368 private: 0369 bool m_menuFound; 0370 QJsonArray m_menuTree; 0371 }; 0372 0373 void JSFunctionsTest::testPopUpMenu() 0374 { 0375 Okular::ScriptAction *action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("ret = app.popUpMenu( [\"Fruits\",\"Apples\",\"Oranges\"], \"-\",\"Beans\",\"Corn\" );")); 0376 0377 QScopedPointer<PopupMenuHelper> popupMenuHelper; 0378 popupMenuHelper.reset(new PopupMenuHelper()); 0379 m_document->processAction(action); 0380 QJsonArray expected = { 0381 QJsonArray { 0382 QJsonValue {QStringLiteral("Fruits")}, 0383 QJsonValue {QStringLiteral("Apples")}, 0384 QJsonValue {QStringLiteral("Oranges")}, 0385 }, 0386 QJsonValue {QStringLiteral("-")}, 0387 QJsonValue {QStringLiteral("Beans")}, 0388 QJsonValue {QStringLiteral("Corn")}, 0389 }; 0390 0391 QString expectedString = QString::fromUtf8(QJsonDocument(expected).toJson()); 0392 QString resultString = QString::fromUtf8(QJsonDocument(popupMenuHelper->menuTree()).toJson()); 0393 QString description = QStringLiteral("Expected:\n") + expectedString + QStringLiteral("But got:\n") + resultString; 0394 QVERIFY2(expected == popupMenuHelper->menuTree(), description.toUtf8().constData()); 0395 0396 delete action; 0397 } 0398 0399 void JSFunctionsTest::testPopUpMenuEx() 0400 { 0401 QScopedPointer<PopupMenuHelper> popupMenuHelper; 0402 0403 Okular::ScriptAction *action = new Okular::ScriptAction( 0404 Okular::JavaScript, 0405 QStringLiteral("ret = app.popUpMenuEx( {cName:\"Fruits\", oSubMenu:[{cName:\"Apples\", bMarked:false},{cName:\"Oranges\", bMarked:true}]}, {cName:\"-\"},{cName:\"Beans\", bEnabled:false},{cName:\"Corn\", bEnabled:true} )")); 0406 0407 popupMenuHelper.reset(new PopupMenuHelper()); 0408 m_document->processAction(action); 0409 QVERIFY(popupMenuHelper->menuFound()); 0410 0411 QJsonArray expected = { 0412 QJsonArray { 0413 QJsonValue {QStringLiteral("Fruits")}, 0414 QJsonValue {QStringLiteral("Apples")}, 0415 QJsonValue {QStringLiteral("Oranges")}, 0416 }, 0417 QJsonValue {QStringLiteral("-")}, 0418 QJsonValue {QStringLiteral("Beans")}, 0419 QJsonValue {QStringLiteral("Corn")}, 0420 }; 0421 0422 QString expectedString = QString::fromUtf8(QJsonDocument(expected).toJson()); 0423 QString resultString = QString::fromUtf8(QJsonDocument(popupMenuHelper->menuTree()).toJson()); 0424 QString description = QStringLiteral("Expected:\n") + expectedString + QStringLiteral("But got:\n") + resultString; 0425 QVERIFY2(expected == popupMenuHelper->menuTree(), description.toUtf8().constData()); 0426 0427 delete action; 0428 0429 // Test infinite recursion 0430 action = new Okular::ScriptAction(Okular::JavaScript, QStringLiteral("\ 0431 var recursiveMenu = {\"cName\": \"Devil menu\"};\n\ 0432 recursiveMenu.oSubMenu = [ recursiveMenu ];\n\ 0433 ret = app.popUpMenuEx( recursiveMenu );")); 0434 0435 popupMenuHelper.reset(new PopupMenuHelper()); 0436 m_document->processAction(action); 0437 QVERIFY(popupMenuHelper->menuFound()); 0438 // Must not crash 0439 delete action; 0440 } 0441 0442 /** @brief Checks a single JS action against an expected result 0443 * 0444 * Runs an action with the given @p script and checks that it 0445 * does pop-up a messagebox with the given @p result text. 0446 */ 0447 class PrintDHelper 0448 { 0449 public: 0450 PrintDHelper(Okular::Document *document, const QString &script, const QString &result) 0451 : action(new Okular::ScriptAction(Okular::JavaScript, script)) 0452 , box(new MessageBoxHelper(QMessageBox::Ok, result, QMessageBox::Critical, QStringLiteral("Okular"), false)) 0453 { 0454 document->processAction(action.data()); 0455 } 0456 0457 private: 0458 QScopedPointer<Okular::ScriptAction> action; 0459 QScopedPointer<MessageBoxHelper> box; 0460 }; 0461 0462 void JSFunctionsTest::testPrintD_data() 0463 { 0464 // Force consistent locale 0465 QLocale locale(QStringLiteral("en_US")); 0466 QLocale::setDefault(locale); 0467 0468 QTest::addColumn<QString>("script"); 0469 QTest::addColumn<QString>("result"); 0470 0471 QTest::newRow("mmyyy") << QStringLiteral( 0472 "var date = new Date( 2010, 0, 5, 11, 10, 32, 1 );\ 0473 ret = app.alert( util.printd( \"mm\\\\yyyy\", date ) );") 0474 << QStringLiteral("01\\2010"); 0475 QTest::newRow("myy") << QStringLiteral("ret = app.alert( util.printd( \"m\\\\yy\", date ) );") << QStringLiteral("1\\10"); 0476 QTest::newRow("ddmmHHMM") << QStringLiteral("ret = app.alert( util.printd( \"dd\\\\mm HH:MM\", date ) );") << QStringLiteral("05\\01 11:10"); 0477 QTest::newRow("ddmmHHMMss") << QStringLiteral("ret = app.alert( util.printd( \"dd\\\\mm HH:MM:ss\", date ) );") << QStringLiteral("05\\01 11:10:32"); 0478 QTest::newRow("yyyymmHHMMss") << QStringLiteral("ret = app.alert( util.printd( \"yyyy\\\\mm HH:MM:ss\", date ) );") << QStringLiteral("2010\\01 11:10:32"); 0479 QTest::newRow("0") << QStringLiteral("ret = app.alert( util.printd( 0, date ) );") << QStringLiteral("D:20100105111032"); 0480 QTest::newRow("1") << QStringLiteral("ret = app.alert( util.printd( 1, date ) );") << QStringLiteral("2010.01.05 11:10:32"); 0481 0482 QDate date(2010, 1, 5); 0483 QTest::newRow("2") << QStringLiteral("ret = app.alert( util.printd( 2, date ) );") << QString(date.toString(locale.dateFormat(QLocale::ShortFormat)) + QStringLiteral(" 11:10:32\u202FAM")); 0484 } 0485 0486 void JSFunctionsTest::testPrintD() 0487 { 0488 QFETCH(QString, script); 0489 QFETCH(QString, result); 0490 0491 QVERIFY(script.contains(QLatin1String("printd"))); 0492 PrintDHelper test(m_document, script, result); 0493 } 0494 0495 void JSFunctionsTest::cleanupTestCase() 0496 { 0497 m_document->closeDocument(); 0498 delete m_document; 0499 } 0500 0501 QTEST_MAIN(JSFunctionsTest) 0502 #include "jsfunctionstest.moc"