File indexing completed on 2024-05-12 04:33:28

0001 /*
0002     SPDX-FileCopyrightText: 2014 Albert Astals Cid <aacid@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // clazy:excludeall=qstring-allocations
0008 
0009 #include <QTest>
0010 #include <config-okular.h>
0011 
0012 #include <KConfigGroup>
0013 #include <KLineEdit>
0014 #include <KRecentFilesAction>
0015 
0016 #if HAVE_DBUS
0017 #include <QDBusConnection>
0018 #include <QDBusConnectionInterface>
0019 #endif // HAVE_DBUS
0020 #include <QPrintDialog>
0021 #include <QStandardPaths>
0022 #include <QTabBar>
0023 #include <QTabWidget>
0024 #include <QTemporaryFile>
0025 #include <QTimer>
0026 #include <qwidget.h>
0027 
0028 #include "../core/document_p.h"
0029 #include "../part/findbar.h"
0030 #include "../part/part.h"
0031 #include "../part/presentationwidget.h"
0032 #include "../settings.h"
0033 #include "../shell/okular_main.h"
0034 #include "../shell/shell.h"
0035 #include "../shell/shellutils.h"
0036 #include "closedialoghelper.h"
0037 
0038 #include <sys/types.h>
0039 #ifndef Q_OS_WIN
0040 #include <unistd.h>
0041 #else
0042 #include <process.h>
0043 #endif
0044 
0045 namespace Okular
0046 {
0047 class PartTest
0048 {
0049 public:
0050     Okular::Document *partDocument(Okular::Part *part) const
0051     {
0052         return part->m_document;
0053     }
0054     QWidget *presentationWidget(Okular::Part *part) const
0055     {
0056         return part->m_presentationWidget;
0057     }
0058     FindBar *findWidget(Okular::Part *part) const
0059     {
0060         return part->m_findBar;
0061     }
0062 };
0063 }
0064 
0065 class ClosePrintDialogHelper : public QObject
0066 {
0067     Q_OBJECT
0068 
0069 public:
0070     explicit ClosePrintDialogHelper(int expectedTab)
0071         : foundDialog(false)
0072         , m_expectedTab(expectedTab)
0073     {
0074     }
0075     bool foundDialog;
0076 
0077 public Q_SLOTS:
0078     void closePrintDialog();
0079 
0080 private:
0081     int m_expectedTab;
0082 };
0083 
0084 class MainShellTest : public QObject, public Okular::PartTest
0085 {
0086     Q_OBJECT
0087 
0088 public:
0089     static QTabWidget *tabWidget(Shell *s)
0090     {
0091         return s->m_tabWidget;
0092     }
0093 
0094 private Q_SLOTS:
0095     void initTestCase();
0096     void cleanupTestCase();
0097     void init();
0098     void cleanup();
0099 
0100     void testShell_data();
0101     void testShell();
0102     void testFileRemembersPagePosition_data();
0103     void testFileRemembersPagePosition();
0104     void test2FilesError_data();
0105     void test2FilesError();
0106     void testMiddleButtonCloseUndo();
0107     void testSessionRestore_data();
0108     void testSessionRestore();
0109     void testOpenInvalidFiles_data();
0110     void testOpenInvalidFiles();
0111     void testOpenTheSameFileSeveralTimes();
0112 
0113 private:
0114 };
0115 
0116 QList<Shell *> getShells()
0117 {
0118     QList<Shell *> shells;
0119     const QList<KMainWindow *> mainWindows = KMainWindow::memberList();
0120     for (KMainWindow *kmw : mainWindows) {
0121         Shell *shell = qobject_cast<Shell *>(kmw);
0122         if (shell) {
0123             shells.append(shell);
0124         }
0125     }
0126     return shells;
0127 }
0128 
0129 Shell *findShell(Shell *ignore = nullptr)
0130 {
0131     const QWidgetList wList = QApplication::topLevelWidgets();
0132     for (QWidget *widget : wList) {
0133         Shell *s = qobject_cast<Shell *>(widget);
0134         if (s && s != ignore) {
0135             return s;
0136         }
0137     }
0138     return nullptr;
0139 }
0140 
0141 void MainShellTest::initTestCase()
0142 {
0143     QStandardPaths::setTestModeEnabled(true);
0144     // Don't pollute people's okular settings
0145     Okular::Settings::instance(QStringLiteral("mainshelltest"));
0146 
0147     // Register in bus as okular
0148 #if HAVE_DBUS
0149     QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
0150     const QString myPid = QString::number(getpid());
0151     const QString serviceName = QStringLiteral("org.kde.okular-") + myPid;
0152     QVERIFY(bus->registerService(serviceName) == QDBusConnectionInterface::ServiceRegistered);
0153 #endif
0154     // Tell the presentationWidget and queryClose to not be annoying
0155     KSharedConfigPtr c = KSharedConfig::openConfig(QStringLiteral("mainshelltest.kmessagebox"));
0156     KConfigGroup cg = c->group(QStringLiteral("General"));
0157     cg.writeEntry("presentationInfo", 4);
0158     cg.writeEntry("ShowTabWarning", 4);
0159 }
0160 
0161 void MainShellTest::cleanupTestCase()
0162 {
0163 }
0164 
0165 void MainShellTest::init()
0166 {
0167     // Default settings for every test
0168     Okular::Settings::self()->setDefaults();
0169 
0170     // Clean docdatas
0171     const QList<QUrl> urls = {QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/file1.pdf")),
0172                               QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/tocreload.pdf")),
0173                               QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/contents.epub"))};
0174 
0175     for (const QUrl &url : urls) {
0176         QFileInfo fileReadTest(url.toLocalFile());
0177         const QString docDataPath = Okular::DocumentPrivate::docDataFileName(url, fileReadTest.size());
0178         QFile::remove(docDataPath);
0179     }
0180 }
0181 
0182 void MainShellTest::cleanup()
0183 {
0184     Shell *s;
0185     while ((s = findShell())) {
0186         delete s;
0187     }
0188 }
0189 
0190 void MainShellTest::testShell_data()
0191 {
0192     QTest::addColumn<QStringList>("paths");
0193     QTest::addColumn<QString>("serializedOptions");
0194     QTest::addColumn<bool>("useTabs");
0195     QTest::addColumn<QString>("externalProcessPath");
0196     QTest::addColumn<uint>("expectedPage");
0197     QTest::addColumn<bool>("expectPresentation");
0198     QTest::addColumn<bool>("expectPrintDialog");
0199     QTest::addColumn<bool>("unique");
0200     QTest::addColumn<uint>("externalProcessExpectedPage");
0201     QTest::addColumn<bool>("externalProcessExpectPresentation");
0202     QTest::addColumn<bool>("externalProcessExpectPrintDialog");
0203     QTest::addColumn<QString>("externalProcessExpectFind");
0204 
0205     const QStringList contentsEpub = QStringList(QStringLiteral(KDESRCDIR "data/contents.epub"));
0206     const QStringList file1 = QStringList(QStringLiteral(KDESRCDIR "data/file1.pdf"));
0207     QStringList file1AndToc;
0208     file1AndToc << QStringLiteral(KDESRCDIR "data/file1.pdf");
0209     file1AndToc << QStringLiteral(KDESRCDIR "data/tocreload.pdf");
0210     const QString tocReload = QStringLiteral(KDESRCDIR "data/tocreload.pdf");
0211 
0212     const QString optionsPage2 = ShellUtils::serializeOptions(false, false, false, false, false, QStringLiteral("2"), QString(), QString());
0213     const QString optionsPage2Presentation = ShellUtils::serializeOptions(true, false, false, false, false, QStringLiteral("2"), QString(), QString());
0214     const QString optionsPrint = ShellUtils::serializeOptions(false, true, false, false, false, QString(), QString(), QString());
0215     const QString optionsUnique = ShellUtils::serializeOptions(false, false, false, true, false, QString(), QString(), QString());
0216     const QString optionsFind = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QStringLiteral("si:next-testing parameters!"), QString());
0217 
0218     QTest::newRow("just show shell") << QStringList() << QString() << false << QString() << 0u << false << false << false << 0u << false << false << QString();
0219     QTest::newRow("open file") << file1 << QString() << false << QString() << 0u << false << false << false << 0u << false << false << QString();
0220     QTest::newRow("two files no tabs") << file1AndToc << QString() << false << QString() << 0u << false << false << false << 0u << false << false << QString();
0221     QTest::newRow("two files with tabs") << file1AndToc << QString() << true << QString() << 0u << false << false << false << 0u << false << false << QString();
0222     QTest::newRow("two files sequence no tabs") << file1 << QString() << false << tocReload << 0u << false << false << false << 0u << false << false << QString();
0223 #if HAVE_DBUS
0224     QTest::newRow("two files sequence with tabs") << file1 << QString() << true << tocReload << 0u << false << false << false << 0u << false << false << QString();
0225 #endif // HAVE_DBUS
0226     QTest::newRow("open file page number") << contentsEpub << optionsPage2 << false << QString() << 1u << false << false << false << 0u << false << false << QString();
0227     QTest::newRow("open file page number and presentation") << contentsEpub << optionsPage2Presentation << false << QString() << 1u << true << false << false << 0u << false << false << QString();
0228     QTest::newRow("open file find") << file1 << optionsFind << false << QString() << 0u << false << false << false << 0u << false << false << QStringLiteral("si:next-testing parameters!");
0229     QTest::newRow("open file print") << file1 << optionsPrint << false << QString() << 0u << false << true << false << 0u << false << false << QString();
0230 #if HAVE_DBUS
0231     QTest::newRow("open two files unique") << file1 << optionsUnique << false << tocReload << 0u << false << false << true << 0u << false << false << QString();
0232     QTest::newRow("open two files unique tabs") << file1 << optionsUnique << true << tocReload << 0u << false << false << true << 0u << false << false << QString();
0233     QTest::newRow("page number attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << false << false << 2u << false << false << QString();
0234     QTest::newRow("presentation attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << false << false << 2u << true << false << QString();
0235     QTest::newRow("print attach tabs") << file1 << QString() << true << contentsEpub[0] << 0u << false << true << false << 2u << false << true << QString();
0236     QTest::newRow("page number attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 3u << false << false << QString();
0237     QTest::newRow("presentation attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 2u << true << false << QString();
0238     QTest::newRow("print attach unique") << file1 << optionsUnique << false << contentsEpub[0] << 0u << false << false << true << 2u << false << true << QString();
0239     QTest::newRow("page number attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 3u << false << false << QString();
0240     QTest::newRow("presentation attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 2u << true << false << QString();
0241     QTest::newRow("print attach unique tabs") << file1 << optionsUnique << true << contentsEpub[0] << 0u << false << false << true << 2u << false << true << QString();
0242 #endif // HAVE_DBUS
0243 }
0244 
0245 void MainShellTest::testShell()
0246 {
0247     QFETCH(QStringList, paths);
0248     QFETCH(QString, serializedOptions);
0249     QFETCH(bool, useTabs);
0250     QFETCH(QString, externalProcessPath);
0251     QFETCH(uint, expectedPage);
0252     QFETCH(bool, expectPresentation);
0253     QFETCH(bool, expectPrintDialog);
0254     QFETCH(bool, unique);
0255     QFETCH(uint, externalProcessExpectedPage);
0256     QFETCH(bool, externalProcessExpectPresentation);
0257     QFETCH(bool, externalProcessExpectPrintDialog);
0258     QFETCH(QString, externalProcessExpectFind);
0259 
0260     QScopedPointer<ClosePrintDialogHelper> helper;
0261 
0262     Okular::Settings::self()->setShellOpenFileInTabs(useTabs);
0263 
0264     if (expectPrintDialog || externalProcessExpectPrintDialog) {
0265         const int expectedTab = externalProcessExpectPrintDialog && !unique ? 1 : 0;
0266         helper.reset(new ClosePrintDialogHelper(expectedTab));
0267         QTimer::singleShot(0, helper.data(), &ClosePrintDialogHelper::closePrintDialog);
0268     }
0269 
0270     Okular::Status status = Okular::main(paths, serializedOptions);
0271     QCOMPARE(status, Okular::Success);
0272     Shell *s = findShell();
0273     QVERIFY(s);
0274 
0275     if (paths.count() == 1) {
0276         QCOMPARE(s->m_tabs.count(), 1);
0277         Okular::Part *part = s->findChild<Okular::Part *>();
0278         QVERIFY(part);
0279         QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0]));
0280         QCOMPARE(partDocument(part)->currentPage(), expectedPage);
0281         // Testing if the bar is shown or hidden as expected
0282         QCOMPARE(findWidget(part)->isHidden(), externalProcessExpectFind.isEmpty());
0283         QCOMPARE(findWidget(part)->findChild<KLineEdit *>()->text(), externalProcessExpectFind);
0284         // Checking if the encryption/decryption worked
0285         QCOMPARE(externalProcessExpectFind, ShellUtils::find(serializedOptions));
0286 
0287     } else if (paths.count() == 2) {
0288         if (useTabs) {
0289             Shell *s = findShell();
0290             QVERIFY(s);
0291             Okular::Part *part = dynamic_cast<Okular::Part *>(s->m_tabs[0].part);
0292             Okular::Part *part2 = dynamic_cast<Okular::Part *>(s->m_tabs[1].part);
0293             QCOMPARE(s->m_tabs.count(), 2);
0294             QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0]));
0295             QCOMPARE(part2->url().url(), QStringLiteral("file://%1").arg(paths[1]));
0296             QCOMPARE(partDocument(part)->currentPage(), expectedPage);
0297             QCOMPARE(partDocument(part2)->currentPage(), expectedPage);
0298         } else {
0299             QSet<QString> openUrls;
0300             Shell *s = findShell();
0301             QVERIFY(s);
0302             QCOMPARE(s->m_tabs.count(), 1);
0303             Okular::Part *part = s->findChild<Okular::Part *>();
0304             QVERIFY(part);
0305             QCOMPARE(partDocument(part)->currentPage(), expectedPage);
0306             openUrls << part->url().url();
0307 
0308             Shell *s2 = findShell(s);
0309             QVERIFY(s2);
0310             QCOMPARE(s2->m_tabs.count(), 1);
0311             Okular::Part *part2 = s2->findChild<Okular::Part *>();
0312             QVERIFY(part2);
0313             QCOMPARE(partDocument(part2)->currentPage(), expectedPage);
0314             openUrls << part2->url().url();
0315 
0316             for (const QString &path : std::as_const(paths)) {
0317                 QVERIFY(openUrls.contains(QStringLiteral("file://%1").arg(path)));
0318             }
0319         }
0320     }
0321 
0322     if (!externalProcessPath.isEmpty()) {
0323         Okular::Part *part = s->findChild<Okular::Part *>();
0324 
0325         QProcess p;
0326         QStringList args;
0327         args << externalProcessPath;
0328         if (unique) {
0329             args << QStringLiteral("-unique");
0330         }
0331         if (externalProcessExpectedPage != 0) {
0332             args << QStringLiteral("-page") << QString::number(externalProcessExpectedPage + 1);
0333         }
0334         if (externalProcessExpectPresentation) {
0335             args << QStringLiteral("-presentation");
0336         }
0337         if (externalProcessExpectPrintDialog) {
0338             args << QStringLiteral("-print");
0339         }
0340         p.start(QStringLiteral(OKULAR_BINARY), args);
0341         p.waitForStarted();
0342         QCOMPARE(p.state(), QProcess::Running);
0343 
0344         if (useTabs || unique) {
0345             // It is attaching to us, so will eventually stop
0346             QTRY_COMPARE_WITH_TIMEOUT(p.state(), QProcess::NotRunning, 20000);
0347             QCOMPARE(p.exitStatus(), QProcess::NormalExit);
0348             QCOMPARE(p.exitCode(), 0);
0349 
0350             if (unique) {
0351                 // It is unique so part got "overwritten"
0352                 QCOMPARE(s->m_tabs.count(), 1);
0353                 QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(externalProcessPath));
0354                 QCOMPARE(partDocument(part)->currentPage(), externalProcessExpectedPage);
0355             } else {
0356                 // It is attaching to us so a second tab is there
0357                 QCOMPARE(s->m_tabs.count(), 2);
0358                 Okular::Part *part2 = dynamic_cast<Okular::Part *>(s->m_tabs[1].part);
0359                 QCOMPARE(part2->url().url(), QStringLiteral("file://%1").arg(externalProcessPath));
0360                 QCOMPARE(partDocument(part2)->currentPage(), externalProcessExpectedPage);
0361             }
0362         } else {
0363             QTest::qWait(750);
0364 
0365             // It opened on a new process, so it is still running, we need to kill it
0366             QCOMPARE(p.state(), QProcess::Running);
0367             p.terminate();
0368             p.waitForFinished();
0369             QVERIFY(p.state() != QProcess::Running);
0370             // It opened on a new process, so no change for us
0371             QCOMPARE(s->m_tabs.count(), 1);
0372             QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0]));
0373             QCOMPARE(partDocument(part)->currentPage(), externalProcessExpectedPage);
0374         }
0375     }
0376 
0377     if (expectPresentation) {
0378         QCOMPARE(paths.count(), 1);
0379         Okular::Part *part = s->findChild<Okular::Part *>();
0380         QTRY_VERIFY(presentationWidget(part) != nullptr);
0381     }
0382 
0383     if (externalProcessExpectPresentation) {
0384         Okular::Part *part;
0385         if (unique) {
0386             QCOMPARE(s->m_tabs.count(), 1);
0387             part = dynamic_cast<Okular::Part *>(s->m_tabs[0].part);
0388         } else {
0389             QCOMPARE(s->m_tabs.count(), 2);
0390             part = dynamic_cast<Okular::Part *>(s->m_tabs[1].part);
0391         }
0392 
0393         QTRY_VERIFY(presentationWidget(part) != nullptr);
0394     }
0395 
0396     if (helper) {
0397         QVERIFY(helper->foundDialog);
0398     }
0399 }
0400 
0401 void ClosePrintDialogHelper::closePrintDialog()
0402 {
0403     Shell *s = findShell();
0404     QPrintDialog *dialog = s->findChild<QPrintDialog *>();
0405     if (!dialog) {
0406         QTimer::singleShot(0, this, &ClosePrintDialogHelper::closePrintDialog);
0407         return;
0408     }
0409     QVERIFY(dialog);
0410     QCOMPARE(MainShellTest::tabWidget(s)->currentIndex(), m_expectedTab);
0411     dialog->close();
0412     foundDialog = true;
0413 }
0414 
0415 void MainShellTest::testFileRemembersPagePosition_data()
0416 {
0417     QTest::addColumn<int>("mode");
0418 
0419     QTest::newRow("normal") << 1;
0420     QTest::newRow("unique") << 2;
0421     QTest::newRow("tabs") << 3;
0422 }
0423 
0424 void MainShellTest::testFileRemembersPagePosition()
0425 {
0426     QFETCH(int, mode);
0427 
0428     const QStringList paths = QStringList(QStringLiteral(KDESRCDIR "data/contents.epub"));
0429     QString serializedOptions;
0430     if (mode == 1 || mode == 3) {
0431         serializedOptions = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString(), QString());
0432     } else {
0433         serializedOptions = ShellUtils::serializeOptions(false, false, false, true, false, QString(), QString(), QString());
0434     }
0435 
0436     Okular::Settings::self()->setShellOpenFileInTabs(mode == 3);
0437 
0438     Okular::Status status = Okular::main(paths, serializedOptions);
0439     QCOMPARE(status, Okular::Success);
0440     Shell *s = findShell();
0441     QVERIFY(s);
0442     Okular::Part *part = s->findChild<Okular::Part *>();
0443     QVERIFY(part);
0444     QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0]));
0445     QCOMPARE(partDocument(part)->currentPage(), 0u);
0446     partDocument(part)->setViewportPage(3);
0447     QCOMPARE(partDocument(part)->currentPage(), 3u);
0448     s->closeUrl();
0449     QCOMPARE(part->url().url(), QString());
0450 
0451     if (mode == 1) {
0452         delete s;
0453         status = Okular::main(paths, serializedOptions);
0454         QCOMPARE(status, Okular::Success);
0455     } else {
0456         QProcess p;
0457         QStringList args;
0458         args << paths[0];
0459         if (mode == 2) {
0460             args << QStringLiteral("-unique");
0461         }
0462         p.start(QStringLiteral(OKULAR_BINARY), args);
0463         p.waitForStarted();
0464         QCOMPARE(p.state(), QProcess::Running);
0465 
0466         // It is attaching to us, so will eventually stop
0467         QTRY_COMPARE_WITH_TIMEOUT((int)p.state(), (int)QProcess::NotRunning, 20000);
0468         QCOMPARE((int)p.exitStatus(), (int)QProcess::NormalExit);
0469         QCOMPARE(p.exitCode(), 0);
0470     }
0471     s = findShell();
0472     QVERIFY(s);
0473     part = s->findChild<Okular::Part *>();
0474     QVERIFY(part);
0475     QCOMPARE(part->url().url(), QStringLiteral("file://%1").arg(paths[0]));
0476     QCOMPARE(partDocument(part)->currentPage(), 3u);
0477 }
0478 
0479 void MainShellTest::test2FilesError_data()
0480 {
0481     QTest::addColumn<QString>("serializedOptions");
0482 
0483     QTest::newRow("startInPresentation") << ShellUtils::serializeOptions(true, false, false, false, false, QString(), QString(), QString());
0484     QTest::newRow("showPrintDialog") << ShellUtils::serializeOptions(false, true, false, false, false, QString(), QString(), QString());
0485 #if HAVE_DBUS
0486     QTest::newRow("unique") << ShellUtils::serializeOptions(false, false, false, true, false, QString(), QString(), QString());
0487     QTest::newRow("pageNumber") << ShellUtils::serializeOptions(false, false, false, false, false, QStringLiteral("3"), QString(), QString());
0488 #endif // HAVE_DBUS
0489     QTest::newRow("find") << ShellUtils::serializeOptions(false, false, false, false, false, QString(), QStringLiteral("silly"), QString());
0490 }
0491 
0492 void MainShellTest::test2FilesError()
0493 {
0494     QFETCH(QString, serializedOptions);
0495 
0496     QStringList paths;
0497     paths << QStringLiteral(KDESRCDIR "data/file1.pdf") << QStringLiteral(KDESRCDIR "data/tocreload.pdf");
0498     Okular::Status status = Okular::main(paths, serializedOptions);
0499     QCOMPARE(status, Okular::Error);
0500 
0501     Shell *s = findShell();
0502     QVERIFY(!s);
0503 }
0504 
0505 void MainShellTest::testSessionRestore_data()
0506 {
0507     QTest::addColumn<QStringList>("paths");
0508     QTest::addColumn<QString>("options");
0509     QTest::addColumn<bool>("useTabsOpen");
0510     QTest::addColumn<bool>("useTabsRestore");
0511 
0512     QStringList oneDocPaths(QStringLiteral(KDESRCDIR "data/file1.pdf"));
0513     QStringList twoDocPaths(oneDocPaths);
0514     twoDocPaths << QStringLiteral(KDESRCDIR "data/formSamples.pdf");
0515 
0516     const QString options = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString(), QString());
0517 
0518     QTest::newRow("1 doc, 1 window, tabs") << oneDocPaths << options << true << true;
0519     QTest::newRow("2 docs, 1 window, tabs") << twoDocPaths << options << true << true;
0520     QTest::newRow("2 docs, 2 windows, tabs") << twoDocPaths << options << false << true;
0521     QTest::newRow("2 docs, 2 windows, no tabs") << twoDocPaths << options << false << false;
0522     QTest::newRow("2 docs, 1 window, no tabs") << twoDocPaths << options << true << false;
0523 }
0524 
0525 void MainShellTest::testSessionRestore()
0526 {
0527     QFETCH(QStringList, paths);
0528     QFETCH(QString, options);
0529     QFETCH(bool, useTabsOpen);
0530     QFETCH(bool, useTabsRestore);
0531 
0532     Okular::Settings::self()->setShellOpenFileInTabs(useTabsOpen);
0533 
0534     Okular::Status status = Okular::main(paths, options);
0535     QCOMPARE(status, Okular::Success);
0536 
0537     // Gather some information about the state
0538     // Verify that the correct number of windows/tabs were opened
0539     QList<Shell *> shells = getShells();
0540     QVERIFY(!shells.isEmpty());
0541     int numDocs = 0;
0542     for (Shell *shell : std::as_const(shells)) {
0543         QVERIFY(QTest::qWaitForWindowExposed(shell));
0544         numDocs += shell->m_tabs.size();
0545     }
0546 
0547     QCOMPARE(numDocs, paths.size());
0548     QCOMPARE(shells.size(), useTabsOpen ? 1 : paths.size());
0549     QTest::qWait(100);
0550 
0551     // Simulate session shutdown. The actual shutdown path comes through
0552     // QSessionManager XSMP handlers, then KApplication::commitData/saveState,
0553     // then KMWSessionManager::commitData/saveState. Without simulating an X
0554     // session manager, the best we can do here is to make a temporary Config
0555     // and call KMainWindows save functions directly.
0556     QTemporaryFile configFile;
0557     QVERIFY(configFile.open());
0558 
0559     int numWindows = 0;
0560     { // Scope for config so that we can reconstruct from file
0561         KConfig config(configFile.fileName(), KConfig::SimpleConfig);
0562         for (Shell *shell : std::as_const(shells)) {
0563             shell->savePropertiesInternal(&config, ++numWindows);
0564             // Windows aren't necessarily closed on shutdown, but we'll use
0565             // this as a way to trigger the destructor code, which is normally
0566             // connected to the aboutToQuit signal
0567             shell->close();
0568         }
0569     }
0570 
0571     // Wait for shells to delete themselves. QTest::qWait doesn't do deferred
0572     // deletions so we'll set up a full event loop to do that.
0573     QEventLoop eventLoop;
0574     QTimer::singleShot(100, &eventLoop, &QEventLoop::quit);
0575     eventLoop.exec(QEventLoop::AllEvents);
0576     // Sometimes the event loop is not enough, so try a bit more to get deferred delete happen
0577     QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
0578     shells = getShells();
0579     QCOMPARE(shells.size(), 0);
0580 
0581     Okular::Settings::self()->setShellOpenFileInTabs(useTabsRestore);
0582 
0583     // Simulate session restore. We can't call KMainWindow::restore() directly
0584     // because it asks for info from the session manager, which doesn't know
0585     // about our temporary config. But the logic here mostly mirrors restore().
0586     KConfig config(configFile.fileName(), KConfig::SimpleConfig);
0587     for (int i = 1; i <= numWindows; ++i) {
0588         Shell *shell = new Shell;
0589         shell->readPropertiesInternal(&config, i);
0590         shell->show();
0591     }
0592 
0593     // Verify that the restore state is reasonable
0594     shells = getShells();
0595     QVERIFY(!shells.isEmpty());
0596     numDocs = 0;
0597     for (Shell *shell : std::as_const(shells)) {
0598         QVERIFY(QTest::qWaitForWindowExposed(shell));
0599         numDocs += shell->m_tabs.size();
0600     }
0601 
0602     QCOMPARE(numDocs, paths.size());
0603     QCOMPARE(shells.size(), useTabsRestore ? numWindows : paths.size());
0604 }
0605 
0606 void MainShellTest::testOpenInvalidFiles_data()
0607 {
0608     QTest::addColumn<QList<QUrl>>("files");
0609     QTest::addColumn<QString>("options");
0610 
0611     QString options = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString(), QString());
0612     QUrl validFile1 = ShellUtils::urlFromArg(QStringLiteral(KDESRCDIR "data/file1.pdf"), ShellUtils::qfileExistFunc(), QString());
0613     QUrl validFile2 = ShellUtils::urlFromArg(QStringLiteral(KDESRCDIR "data/file2.pdf"), ShellUtils::qfileExistFunc(), QString());
0614     QUrl invalidFile = ShellUtils::urlFromArg(QStringLiteral(KDESRCDIR "data/non-existing-doc.pdf"), ShellUtils::qfileExistFunc(), QString());
0615 
0616     QList<QUrl> firstCase {invalidFile, validFile1, validFile2};
0617     QList<QUrl> secondCase {validFile1, validFile2, invalidFile};
0618 
0619     QTest::newRow("opening the invalid file first") << firstCase << options;
0620     QTest::newRow("opening the valids file first") << secondCase << options;
0621 }
0622 
0623 void MainShellTest::testOpenInvalidFiles()
0624 {
0625     QFETCH(QList<QUrl>, files);
0626     QFETCH(QString, options);
0627 
0628     /*
0629      *  The purpose of this test is to verify that when we open an invalid file, no tab is created in the
0630      * shell.
0631      *
0632      */
0633 
0634     Okular::Settings::self()->setShellOpenFileInTabs(true);
0635     Okular::Status status = Okular::main(QStringList(), options);
0636     QCOMPARE(status, Okular::Success);
0637 
0638     Shell *shell = findShell();
0639     QVERIFY(shell);
0640 
0641     /*
0642      *  We need to make sure that the KrecentFilesAction is empty before starting, because we will also test that
0643      * the file gets removed from the recent documents
0644      *
0645      */
0646     shell->m_recent->clear();
0647 
0648     QScopedPointer<TestingUtils::CloseDialogHelper> closeDialogHelper {new TestingUtils::CloseDialogHelper(QDialogButtonBox::StandardButton::Ok)};
0649 
0650     for (const QUrl &file : files) {
0651         shell->openUrl(file);
0652     }
0653 
0654     QList<QUrl> recentFiles = shell->m_recent->urls();
0655 
0656     QVERIFY(shell->m_tabs.size() == 2);
0657     QVERIFY(shell->m_tabWidget->tabBar()->isVisible());
0658 
0659     QVERIFY(!shell->m_tabWidget->tabIcon(0).isNull());
0660     QVERIFY(!shell->m_tabWidget->tabIcon(1).isNull());
0661 
0662     QVERIFY(recentFiles.size() == 2);
0663 }
0664 
0665 void MainShellTest::testOpenTheSameFileSeveralTimes()
0666 {
0667     QString options = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString(), QString());
0668 
0669     Okular::Settings::self()->setShellOpenFileInTabs(true);
0670     Okular::Status status = Okular::main(QStringList(), options);
0671     QCOMPARE(status, Okular::Success);
0672 
0673     Shell *shell = findShell();
0674     QVERIFY(shell);
0675 
0676     QUrl file1 = ShellUtils::urlFromArg(QStringLiteral(KDESRCDIR "data/file1.pdf"), ShellUtils::qfileExistFunc(), QString());
0677     QUrl file2 = ShellUtils::urlFromArg(QStringLiteral(KDESRCDIR "data/file2.pdf"), ShellUtils::qfileExistFunc(), QString());
0678     QUrl file3 = ShellUtils::urlFromArg(QStringLiteral(KDESRCDIR "data/formattest.pdf"), ShellUtils::qfileExistFunc(), QString());
0679 
0680     shell->openUrl(file1);
0681     shell->openUrl(file2);
0682     shell->openUrl(file2);
0683 
0684     QVERIFY(shell->m_tabs.size() == 2);
0685 
0686     QVERIFY(shell->m_tabWidget->currentIndex() == 1);
0687 
0688     Okular::Settings::self()->setSwitchToTabIfOpen(false);
0689 
0690     shell->openUrl(file2);
0691 
0692     QVERIFY(shell->m_tabs.size() == 3);
0693 
0694     shell->openUrl(file3);
0695     QVERIFY(shell->m_tabWidget->currentIndex() == 3);
0696 
0697     QVERIFY(shell->m_tabs.size() == 4);
0698 
0699     Okular::Settings::self()->setSwitchToTabIfOpen(true);
0700 }
0701 
0702 void MainShellTest::testMiddleButtonCloseUndo()
0703 {
0704     const QStringList paths = {QStringLiteral(KDESRCDIR "data/file1.pdf"), QStringLiteral(KDESRCDIR "data/file2.pdf")};
0705     QString serializedOptions;
0706     serializedOptions = ShellUtils::serializeOptions(false, false, false, false, false, QString(), QString(), QString());
0707 
0708     Okular::Settings::self()->setShellOpenFileInTabs(true);
0709     Okular::Status status = Okular::main(paths, serializedOptions);
0710     QCOMPARE(status, Okular::Success);
0711     Shell *s = findShell();
0712     QVERIFY(s);
0713 
0714     QCOMPARE(s->m_tabWidget->count(), paths.size());
0715     // Close a tab using middle key
0716     QWidget *firstTab = s->m_tabWidget->tabBar()->tabButton(0, QTabBar::RightSide);
0717     QVERIFY(firstTab);
0718     QTest::mouseClick(firstTab, Qt::MiddleButton);
0719     QCOMPARE(s->m_tabWidget->count(), paths.size() - 1);
0720     // Undo tab close
0721     s->undoCloseTab();
0722     QCOMPARE(s->m_tabWidget->count(), paths.size());
0723 }
0724 
0725 QTEST_MAIN(MainShellTest)
0726 #include "mainshelltest.moc"