File indexing completed on 2024-03-24 15:40:40

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2007-2009 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kxmlgui_unittest.h"
0009 #include "testguiclient.h"
0010 #include "testxmlguiwindow.h"
0011 
0012 #include <QAction>
0013 #include <QDebug>
0014 #include <QDialogButtonBox>
0015 #include <QDir>
0016 #include <QHBoxLayout>
0017 #include <QMenuBar>
0018 #include <QPushButton>
0019 #include <QShowEvent>
0020 #include <QTest>
0021 #include <QWidget>
0022 
0023 #include <KConfigGroup>
0024 #include <KSharedConfig>
0025 
0026 #include <kedittoolbar.h>
0027 #include <kswitchlanguagedialog_p.h>
0028 #include <kxmlguibuilder.h>
0029 #include <kxmlguiclient.h>
0030 #include <kxmlguiversionhandler.cpp> // it's not exported, so we need to include the code here
0031 
0032 QTEST_MAIN(KXmlGui_UnitTest)
0033 
0034 enum Flags {
0035     NoFlags = 0,
0036     AddToolBars = 1,
0037     AddModifiedToolBars = 2,
0038     AddActionProperties = 4,
0039     AddModifiedMenus = 8,
0040     // next item is 16
0041 };
0042 
0043 static void createXmlFile(QFile &file, int version, int flags, const QByteArray &toplevelTag = "gui")
0044 {
0045     const QByteArray xml =
0046         "<?xml version = '1.0'?>\n"
0047         "<!DOCTYPE " + toplevelTag + " SYSTEM \"kpartgui.dtd\">\n"
0048         "<" + toplevelTag + " version=\"" + QByteArray::number(version) + "\" name=\"foo\" >\n"
0049         "<MenuBar>\n";
0050     file.write(xml);
0051     if (flags & AddModifiedMenus) {
0052         file.write(
0053             "<Menu noMerge=\"1\" name=\"file\" >\n"
0054             "<text>&amp;File</text>\n"
0055             "<Action name=\"file_open\" />\n"
0056             "</Menu>\n");
0057     }
0058     file.write("</MenuBar>\n");
0059     if (flags & AddToolBars) {
0060         file.write(
0061             "<ToolBar name=\"mainToolBar\">\n"
0062             "  <text>Main Toolbar</text>\n"
0063             "  <Action name=\"print\" />\n"
0064             "</ToolBar>\n"
0065             "<ToolBar name=\"bookmarkToolBar\">\n"
0066             "  <text>Bookmark Toolbar</text>\n"
0067             "</ToolBar>\n"
0068             "<ToolBar name=\"newToolBar\">\n"
0069             "  <text>New Toolbar</text>\n"
0070             "</ToolBar>\n");
0071     }
0072     if (flags & AddModifiedToolBars) {
0073         file.write(
0074             "<ToolBar name=\"mainToolBar\">\n"
0075             "  <text>Main Toolbar</text>\n"
0076             "  <Action name=\"home\" />\n"
0077             "</ToolBar>\n"
0078             "<ToolBar name=\"bookmarkToolBar\">\n"
0079             "  <text>Modified toolbars</text>\n"
0080             "</ToolBar>\n");
0081     }
0082     if (flags & AddActionProperties) {
0083         file.write(
0084             "<ActionProperties>\n"
0085             "  <Action shortcut=\"F9\" name=\"konq_sidebartng\" />\n"
0086             "</ActionProperties>\n");
0087     }
0088     file.write("</" + toplevelTag + ">\n");
0089 }
0090 
0091 static void clickApply(KEditToolBar *dialog)
0092 {
0093     QDialogButtonBox *box = dialog->findChild<QDialogButtonBox *>();
0094     Q_ASSERT(box != nullptr);
0095     box->button(QDialogButtonBox::Apply)->setEnabled(true);
0096     box->button(QDialogButtonBox::Apply)->click();
0097 }
0098 
0099 void KXmlGui_UnitTest::initTestCase()
0100 {
0101     QStandardPaths::setTestModeEnabled(true);
0102     // Leftover configuration breaks testAutoSaveSettings
0103     const QString configFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, KSharedConfig::openConfig()->name());
0104     if (!configFile.isEmpty()) {
0105         qDebug() << "Removing old config file";
0106         QFile::remove(configFile);
0107         KSharedConfig::openConfig()->reparseConfiguration();
0108     }
0109 }
0110 
0111 void KXmlGui_UnitTest::testFindVersionNumber_data()
0112 {
0113     QTest::addColumn<QString>("xml");
0114     QTest::addColumn<QString>("version");
0115 
0116     QTest::newRow("simple test") << "<?xml version = '1.0'?>\n"
0117                                     "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0118                                     "<gui version=\"3\" name=\"foo\"/>\n"
0119                                  << "3";
0120     QTest::newRow("two digits") << "<?xml version = '1.0'?>\n"
0121                                    "<gui version=\"42\" name=\"foo\"/>\n"
0122                                 << "42";
0123     QTest::newRow("with spaces") << // as found in dirfilterplugin.rc for instance
0124         "<?xml version = '1.0'?>\n"
0125         "<gui version = \"1\" name=\"foo\"/>\n"
0126                                  << "1";
0127     QTest::newRow("with a dot") << // as was found in autorefresh.rc
0128         "<?xml version = '1.0'?>\n"
0129         "<gui version = \"0.2\" name=\"foo\"/>\n"
0130                                 << QString() /*error*/;
0131     QTest::newRow("with a comment") << // as found in kmail.rc
0132         "<!DOCTYPE gui>\n"
0133         "<!-- This file should be synchronized with kmail_part.rc to provide\n"
0134         "the same menu entries at the same place in KMail and Kontact  -->\n"
0135         "<gui version=\"452\" name=\"kmmainwin\">\n"
0136                                     << "452";
0137 }
0138 
0139 void KXmlGui_UnitTest::testFindVersionNumber()
0140 {
0141     QFETCH(QString, xml);
0142     QFETCH(QString, version);
0143     QCOMPARE(KXMLGUIClient::findVersionNumber(xml), version);
0144 }
0145 
0146 void KXmlGui_UnitTest::testVersionHandlerSameVersion()
0147 {
0148     // This emulates the case where the user has modified stuff locally
0149     // and the application hasn't changed since, so the version number is unchanged.
0150     QTemporaryFile userFile;
0151     QVERIFY(userFile.open());
0152     createXmlFile(userFile, 2, AddActionProperties | AddModifiedToolBars);
0153     const QString firstFile = userFile.fileName();
0154 
0155     QTemporaryFile appFile;
0156     QVERIFY(appFile.open());
0157     createXmlFile(appFile, 2, AddToolBars);
0158     const QString secondFile = appFile.fileName();
0159 
0160     QStringList files;
0161     files << firstFile << secondFile;
0162 
0163     userFile.close();
0164     appFile.close();
0165 
0166     KXmlGuiVersionHandler versionHandler(files);
0167     QCOMPARE(versionHandler.finalFile(), firstFile);
0168     QString finalDoc = versionHandler.finalDocument();
0169     QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
0170     // Check that the shortcuts defined by the user were kept
0171     QVERIFY(finalDoc.contains(QLatin1String("<ActionProperties>")));
0172     QVERIFY(finalDoc.contains(QLatin1String("sidebartng")));
0173     // Check that the toolbars modified by the user were kept
0174     QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"home\"")));
0175     // and that the toolbar that isn't in the local file, isn't there in the GUI
0176     QVERIFY(!finalDoc.contains(QLatin1String("<ToolBar name=\"newToolBar\"")));
0177 
0178     QVERIFY(userFile.open());
0179     const QString userFileContents = QString::fromUtf8(userFile.readAll());
0180     QCOMPARE(finalDoc, userFileContents);
0181 }
0182 
0183 void KXmlGui_UnitTest::testVersionHandlerNewVersionNothingKept()
0184 {
0185     // This emulates the case where the application has been upgraded
0186     // and the user has a local ui.rc file, but without shortcuts or toolbar changes.
0187     // Not sure how this can happen - would be a menu-only app and hand editing !?
0188     // Anyway the point is to test version number comparison :)
0189 
0190     QMap<QString, int> fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames
0191 
0192     const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
0193     QDir().mkpath(dir);
0194     QFile fileV2(dir + QStringLiteral("/testui.rc"));
0195     QVERIFY2(fileV2.open(QIODevice::WriteOnly), qPrintable(fileV2.fileName()));
0196     createXmlFile(fileV2, 2, NoFlags);
0197     fileToVersionMap.insert(fileV2.fileName(), 2);
0198 
0199     QTemporaryFile fileV5;
0200     QVERIFY(fileV5.open());
0201     createXmlFile(fileV5, 5, NoFlags);
0202     fileToVersionMap.insert(fileV5.fileName(), 5);
0203 
0204     // The highest version is neither the first nor last one in the list,
0205     // to make sure the code really selects the highest version, not just by chance :)
0206     // (This is why we add the v1 version at the end of the list)
0207     QTemporaryFile fileV1;
0208     QVERIFY(fileV1.open());
0209     createXmlFile(fileV1, 1, NoFlags);
0210     fileToVersionMap.insert(fileV1.fileName(), 1);
0211 
0212     QStringList files;
0213     files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName();
0214 
0215     fileV2.close();
0216     fileV5.close();
0217     fileV1.close();
0218 
0219     KXmlGuiVersionHandler versionHandler(files);
0220     QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 5);
0221     QString finalDoc = versionHandler.finalDocument();
0222     QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
0223     QVERIFY(finalDoc.contains(QLatin1String("version=\"5\"")));
0224 
0225     QVERIFY(fileV5.open());
0226     const QString fileV5Contents = QString::fromUtf8(fileV5.readAll());
0227     QCOMPARE(finalDoc, fileV5Contents);
0228 }
0229 
0230 void KXmlGui_UnitTest::testVersionHandlerNewVersionUserChanges()
0231 {
0232     // This emulates the case where the application has been upgraded
0233     // after the user has changed shortcuts and toolbars
0234 
0235     QMap<QString, int> fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames
0236 
0237     // local file
0238     const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
0239     QFile fileV2(dir + QLatin1String("/testui.rc"));
0240     QVERIFY(fileV2.open(QIODevice::WriteOnly));
0241     createXmlFile(fileV2, 2, AddActionProperties | AddModifiedToolBars);
0242     fileToVersionMap.insert(fileV2.fileName(), 2);
0243 
0244     // more-global (application) file
0245     QTemporaryFile fileV5;
0246     QVERIFY(fileV5.open());
0247     createXmlFile(fileV5, 5, AddToolBars | AddModifiedMenus, "kpartgui");
0248     fileToVersionMap.insert(fileV5.fileName(), 5);
0249 
0250     // The highest version is neither the first nor last one in the list,
0251     // to make sure the code really selects the highest version, not just by chance :)
0252     // (This is why we add the v1 version at the end of the list)
0253     QTemporaryFile fileV1;
0254     QVERIFY(fileV1.open());
0255     createXmlFile(fileV1, 1, AddToolBars);
0256     fileToVersionMap.insert(fileV1.fileName(), 1);
0257 
0258     QStringList files;
0259     files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName();
0260 
0261     fileV2.close();
0262     fileV5.close();
0263     fileV1.close();
0264 
0265     KXmlGuiVersionHandler versionHandler(files);
0266     // We end up with the local file, so in our map it has version 2.
0267     // But of course by now it says "version=5" in it :)
0268     QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 2);
0269     const QString finalDoc = versionHandler.finalDocument();
0270     // qDebug() << finalDoc;
0271     QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
0272     QVERIFY(finalDoc.contains(QLatin1String("version=\"5\"")));
0273     // Check that the shortcuts defined by the user were kept
0274     QVERIFY(finalDoc.contains(QLatin1String("<ActionProperties>")));
0275     QVERIFY(finalDoc.contains(QLatin1String("sidebartng")));
0276     // Check that the menus modified by the app are still there
0277     QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"file_open\"")));
0278     // Check that the toolbars modified by the user were kept
0279     QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"home\"")));
0280     // Check that the toolbars added by the application were kept (https://invent.kde.org/graphics/okular/-/merge_requests/197)
0281     QVERIFY(finalDoc.contains(QLatin1String("<ToolBar name=\"newToolBar\"")));
0282 }
0283 
0284 static QStringList collectMenuNames(KXMLGUIFactory &factory)
0285 {
0286     const QList<QWidget *> containers = factory.containers(QStringLiteral("Menu"));
0287     QStringList containerNames;
0288     for (QWidget *w : containers) {
0289         containerNames << w->objectName();
0290     }
0291     return containerNames;
0292 }
0293 
0294 void debugActions(const QList<QAction *> &actions)
0295 {
0296     for (QAction *action : actions) {
0297         qDebug() << (action->isSeparator() ? QString::fromLatin1("separator") : action->objectName());
0298     }
0299 }
0300 
0301 static void checkActions(const QList<QAction *> &actions, const QStringList &expectedActions)
0302 {
0303     for (int i = 0; i < expectedActions.count(); ++i) {
0304         if (i >= actions.count()) {
0305             break;
0306         }
0307         QAction *action = actions.at(i);
0308         if (action->isSeparator()) {
0309             QCOMPARE(QStringLiteral("separator"), expectedActions[i]);
0310         } else {
0311             QCOMPARE(action->objectName(), expectedActions[i]);
0312         }
0313     }
0314     QCOMPARE(actions.count(), expectedActions.count());
0315 }
0316 
0317 void KXmlGui_UnitTest::testPartMerging()
0318 {
0319     const QByteArray hostXml =
0320         "<?xml version = '1.0'?>\n"
0321         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0322         "<gui version=\"1\" name=\"foo\" >\n"
0323         "<MenuBar>\n"
0324         " <Menu name=\"go\"><text>&amp;Go</text>\n"
0325         "  <!-- go_up, go_back, go_forward, go_home, separator: coming from ui_standards.rc -->\n"
0326         "  <DefineGroup name=\"before_merge\"/>\n"
0327         "  <Merge/>\n"
0328         "  <Action name=\"host_after_merge\"/>\n"
0329         "  <Action name=\"host_after_merge_2\"/>\n"
0330         "  <Separator/>\n"
0331         "  <DefineGroup name=\"new_merge\"/>\n"
0332         "  <Title>Section title</Title>\n"
0333         "  <Action name=\"last_from_host\"/>\n"
0334         " </Menu>\n"
0335         " <Menu name=\"file\"><text>&amp;File</text>\n"
0336         "  <DefineGroup name=\"placed_merge\" append=\"new_merge\"/>\n"
0337         " </Menu>\n"
0338         "</MenuBar>\n"
0339         "</gui>\n";
0340 
0341     TestGuiClient hostClient;
0342     hostClient.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("go_back") << QStringLiteral("go_forward") << QStringLiteral("go_home")
0343                                            << QStringLiteral("host_after_merge") << QStringLiteral("host_after_merge_2") << QStringLiteral("last_from_host")
0344                                            << QStringLiteral("file_new") << QStringLiteral("file_open") << QStringLiteral("file_quit"));
0345     hostClient.createGUI(hostXml, true /*ui_standards.rc*/);
0346     KMainWindow mainWindow;
0347     KXMLGUIBuilder builder(&mainWindow);
0348     KXMLGUIFactory factory(&builder);
0349     factory.addClient(&hostClient);
0350 
0351     const QString hostDomDoc = hostClient.domDocument().toString();
0352 
0353     QWidget *goMenuW = factory.container(QStringLiteral("go"), &hostClient);
0354     QVERIFY(goMenuW);
0355     QMenu *goMenu = qobject_cast<QMenu *>(goMenuW);
0356     QVERIFY(goMenu);
0357     QMenu *fileMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("file"), &hostClient));
0358 
0359     // debugActions(goMenu->actions());
0360     // clang-format off
0361     checkActions(goMenu->actions(), QStringList()
0362                  << QStringLiteral("go_up")
0363                  << QStringLiteral("go_back")
0364                  << QStringLiteral("go_forward")
0365                  << QStringLiteral("go_home")
0366                  << QStringLiteral("separator")
0367                  << QStringLiteral("host_after_merge")
0368                  << QStringLiteral("host_after_merge_2")
0369                  << QStringLiteral("separator")
0370                  << QStringLiteral("separator") // <Title> separator
0371                  << QStringLiteral("last_from_host"));
0372     checkActions(fileMenu->actions(), QStringList()
0373                  << QStringLiteral("file_new")
0374                  << QStringLiteral("file_open")
0375                  << QStringLiteral("separator")
0376                  << QStringLiteral("file_quit"));
0377     // clang-format on
0378 
0379     qDebug() << "Now merging the part";
0380 
0381     const QByteArray partXml =
0382         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0383         "<gui version=\"1\" name=\"part\" >\n"
0384         "<MenuBar>\n"
0385         " <Menu name=\"go\"><text>&amp;Go</text>\n"
0386         "  <Action name=\"go_previous\" group=\"before_merge\"/>\n"
0387         "  <Action name=\"go_next\" group=\"before_merge\"/>\n"
0388         "  <Separator/>\n"
0389         "  <Action name=\"first_page\"/>\n"
0390         "  <Action name=\"last_page\"/>\n"
0391         "  <Title>Part Section</Title>\n"
0392         "  <ActionList name=\"action_list\"/>\n"
0393         "  <Action name=\"action_in_merge_group\" group=\"new_merge\"/>\n"
0394         "  <Action name=\"undefined_group\" group=\"no_such_merge\"/>\n"
0395         "  <Action name=\"last_from_part\"/>\n"
0396         " </Menu>\n"
0397         " <Menu name=\"file\"><text>&amp;File</text>\n"
0398         "  <Action group=\"placed_merge\" name=\"action_in_placed_merge\"/>\n"
0399         "  <Action name=\"other_file_action\"/>\n"
0400         " </Menu>\n"
0401         "</MenuBar>\n"
0402         "</gui>\n";
0403 
0404     TestGuiClient partClient(partXml);
0405     partClient.createActions(QStringList() << QStringLiteral("go_previous") << QStringLiteral("go_next") << QStringLiteral("first_page")
0406                                            << QStringLiteral("last_page") << QStringLiteral("last_from_part") << QStringLiteral("action_in_merge_group")
0407                                            << QStringLiteral("undefined_group") << QStringLiteral("action_in_placed_merge")
0408                                            << QStringLiteral("other_file_action") << QStringLiteral("action1") << QStringLiteral("action2"));
0409     const QList<QAction *> actionList = {partClient.actionCollection()->action(QStringLiteral("action1")),
0410                                          partClient.actionCollection()->action(QStringLiteral("action2"))};
0411 
0412     for (int i = 0; i < 5; ++i) {
0413         // qDebug() << "addClient, iteration" << i;
0414         factory.addClient(&partClient);
0415         partClient.plugActionList(QStringLiteral("action_list"), actionList);
0416 
0417         // clang-format off
0418         //debugActions(goMenu->actions());
0419         checkActions(goMenu->actions(), QStringList()
0420                 << QStringLiteral("go_up")
0421                 << QStringLiteral("go_back")
0422                 << QStringLiteral("go_forward")
0423                 << QStringLiteral("go_home")
0424                 << QStringLiteral("separator")
0425                 << QStringLiteral("go_previous")
0426                 << QStringLiteral("go_next")
0427                 // Contents of the <Merge>:
0428                 << QStringLiteral("separator")
0429                 << QStringLiteral("first_page")
0430                 << QStringLiteral("last_page")
0431                 << QStringLiteral("separator") // <title> in the part
0432                 << QStringLiteral("action1")
0433                 << QStringLiteral("action2")
0434                 << QStringLiteral("undefined_group")
0435                 << QStringLiteral("last_from_part")
0436                 // End of <Merge>
0437                 << QStringLiteral("host_after_merge")
0438                 << QStringLiteral("host_after_merge_2")
0439                 << QStringLiteral("separator")
0440                 // Contents of <DefineGroup>
0441                 << QStringLiteral("action_in_merge_group")
0442                 // End of <DefineGroup>
0443                 << QStringLiteral("separator") // <title> is a separator qaction with text
0444                 << QStringLiteral("last_from_host")
0445                 );
0446         checkActions(fileMenu->actions(), QStringList()
0447                 << QStringLiteral("file_new")
0448                 << QStringLiteral("action_in_placed_merge")
0449                 << QStringLiteral("file_open")
0450                 << QStringLiteral("separator")
0451                 << QStringLiteral("file_quit")
0452                 << QStringLiteral("other_file_action"));
0453         factory.removeClient(&partClient);
0454         QCOMPARE(hostClient.domDocument().toString(), hostDomDoc);
0455         // clang-format on
0456     }
0457     factory.removeClient(&hostClient);
0458 }
0459 
0460 void KXmlGui_UnitTest::testPartMergingSettings() // #252911
0461 {
0462     const QByteArray hostXml =
0463         "<?xml version = '1.0'?>\n"
0464         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0465         "<gui version=\"1\" name=\"foo\" >\n"
0466         "<MenuBar>\n"
0467         // The solution was to remove the duplicated definition
0468         //        " <Menu name=\"settings\"><text>&amp;Settings</text>\n"
0469         //        "    <Action name=\"options_configure_keybinding\"/>\n"
0470         //        "    <Action name=\"options_configure_toolbars\"/>\n"
0471         //        "    <Merge name=\"configure_merge\"/>\n"
0472         //        "    <Separator/>\n"
0473         //        "    <Merge/>\n"
0474         //        " </Menu>\n"
0475         "</MenuBar></gui>\n";
0476     TestGuiClient hostClient;
0477     hostClient.createActions(QStringList() << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars"));
0478     hostClient.createGUI(hostXml, true /*ui_standards.rc*/);
0479     // qDebug() << hostClient.domDocument().toString();
0480     KMainWindow mainWindow;
0481     KXMLGUIBuilder builder(&mainWindow);
0482     KXMLGUIFactory factory(&builder);
0483     factory.addClient(&hostClient);
0484     QWidget *settingsMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("settings"), &hostClient));
0485     QVERIFY(settingsMenu);
0486     // debugActions(settingsMenu->actions());
0487 
0488     const QByteArray partXml =
0489         "<?xml version = '1.0'?>\n"
0490         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0491         "<gui version=\"1\" name=\"foo\" >\n"
0492         "<MenuBar>\n"
0493         " <Menu name=\"settings\"><text>&amp;Settings</text>\n"
0494         "    <Action name=\"configure_klinkstatus\"/>\n"
0495         " </Menu>\n"
0496         "</MenuBar></gui>\n";
0497     TestGuiClient partClient(partXml);
0498     partClient.createActions(QStringList() << QStringLiteral("configure_klinkstatus"));
0499     factory.addClient(&partClient);
0500     // debugActions(settingsMenu->actions());
0501     checkActions(settingsMenu->actions(),
0502                  QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it
0503                                << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars")
0504                                << QStringLiteral("configure_klinkstatus"));
0505     factory.removeClient(&partClient);
0506     factory.removeClient(&hostClient);
0507 }
0508 
0509 void KXmlGui_UnitTest::testUiStandardsMerging_data()
0510 {
0511     QTest::addColumn<QByteArray>("xml");
0512     QTest::addColumn<QStringList>("actions");
0513     QTest::addColumn<QStringList>("expectedMenus");
0514 
0515     const QByteArray xmlBegin =
0516         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0517         "<gui version=\"1\" name=\"foo\" >\n"
0518         "<MenuBar>\n";
0519     const QByteArray xmlEnd =
0520         "</MenuBar>\n"
0521         "</gui>";
0522 
0523     // Merging an empty menu (or a menu with only non-existing actions) would make
0524     // the empty menu appear at the end after all other menus (fixed for KDE-4.2)
0525     /* clang-format off */
0526     QTest::newRow("empty file menu, implicit settings menu")
0527         << QByteArray(xmlBegin + "<Menu name=\"file\"/>\n" + xmlEnd)
0528         << (QStringList() << QStringLiteral("options_configure_toolbars"))
0529         << (QStringList() << QStringLiteral("settings"));
0530     QTest::newRow("file menu with non existing action, implicit settings menu")
0531         << QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"foo\"/></Menu>\n" + xmlEnd)
0532         << (QStringList() << QStringLiteral("options_configure_toolbars"))
0533         << (QStringList() << QStringLiteral("settings"));
0534     QTest::newRow("file menu with existing action, implicit settings menu")
0535         << QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"open\"/></Menu>\n" + xmlEnd)
0536         << (QStringList() << QStringLiteral("open") << QStringLiteral("options_configure_toolbars"))
0537         << (QStringList() << QStringLiteral("file") << QStringLiteral("settings"));
0538     QTest::newRow("implicit file and settings menu")
0539         << QByteArray(xmlBegin + xmlEnd)
0540         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
0541         << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we could check that file_open is in the mainToolBar, too
0542 
0543     // Check that unknown non-empty menus are added at the "MergeLocal" position (before settings).
0544     QTest::newRow("foo menu added at the end")
0545         << QByteArray(xmlBegin + "<Menu name=\"foo\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
0546         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars") << QStringLiteral("foo_action"))
0547         << (QStringList() << QStringLiteral("file") << QStringLiteral("foo") << QStringLiteral("settings"));
0548 
0549     QTest::newRow("Bille's testcase: menu patch + menu edit")
0550         << QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n"
0551                       + "<Menu name=\"edit\"><Action name=\"edit_foo\"/></Menu>\n" + xmlEnd)
0552         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo"))
0553         << (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch"));
0554     QTest::newRow("Bille's testcase: menu patch + menu edit, lowercase tag")
0555         << QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n"
0556                       + "<menu name=\"edit\"><Action name=\"edit_foo\"/></menu>\n" + xmlEnd)
0557         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo"))
0558         << (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch"));
0559 
0560     // Check that <Menu append="..."> allows to insert menus at specific positions
0561     QTest::newRow("Menu append")
0562         << QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"settings_merge\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
0563         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars")
0564                           << QStringLiteral("foo_action") << QStringLiteral("help_contents"))
0565         << (QStringList() << QStringLiteral("file") << QStringLiteral("settings") << QStringLiteral("foo") << QStringLiteral("help"));
0566     QTest::newRow("Custom first menu")
0567         << QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"first_menu\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
0568         << (QStringList() << QStringLiteral("edit_undo") << QStringLiteral("foo_action") << QStringLiteral("help_contents"))
0569         << (QStringList() << QStringLiteral("foo") << QStringLiteral("edit") << QStringLiteral("help"));
0570 
0571     // Tests for noMerge="1"
0572     QTest::newRow("noMerge empty file menu, implicit settings menu")
0573         << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n" + xmlEnd)
0574         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
0575         << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we keep empty menus, see #186382
0576     QTest::newRow("noMerge empty file menu, file_open moved elsewhere")
0577         << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd)
0578         << (QStringList() << QStringLiteral("file_open"))
0579         << (QStringList() << QStringLiteral("file") << QStringLiteral("foo"));
0580     QTest::newRow("noMerge file menu with open before new")
0581         << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd)
0582         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new"))
0583         << (QStringList() << QStringLiteral("file")); // TODO check the order of the actions in the menu? how?
0584 
0585     // Tests for deleted="true"
0586     QTest::newRow("deleted file menu, implicit settings menu")
0587         << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n" + xmlEnd)
0588         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
0589         << (QStringList() << QStringLiteral("settings"));
0590     QTest::newRow("deleted file menu, file_open moved elsewhere")
0591         << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd)
0592         << (QStringList() << QStringLiteral("file_open"))
0593         << (QStringList() << QStringLiteral("foo"));
0594     QTest::newRow("deleted file menu with actions (contradiction)")
0595         << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd)
0596         << (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new"))
0597         << (QStringList());
0598     /* clang-format on */
0599 }
0600 
0601 void KXmlGui_UnitTest::testUiStandardsMerging()
0602 {
0603     QFETCH(QByteArray, xml);
0604     QFETCH(QStringList, actions);
0605     QFETCH(QStringList, expectedMenus);
0606 
0607     TestGuiClient client;
0608     client.createActions(actions);
0609     client.createGUI(xml, true /*ui_standards.rc*/);
0610 
0611     const QDomDocument domDocument = client.domDocument();
0612     const QDomElement docElem = domDocument.documentElement();
0613     QCOMPARE(docElem.attribute(QStringLiteral("name")), QStringLiteral("foo")); // not standard_containers from ui_standards.rc
0614     QCOMPARE(docElem.attribute(QStringLiteral("version")), QStringLiteral("1")); // not standard_containers from ui_standards.rc
0615 
0616     KMainWindow mainWindow;
0617     KXMLGUIBuilder builder(&mainWindow);
0618     KXMLGUIFactory factory(&builder);
0619     factory.addClient(&client);
0620 
0621     const QStringList containerNames = collectMenuNames(factory);
0622     // qDebug() << containerNames;
0623     QCOMPARE(containerNames, expectedMenus);
0624 
0625     factory.removeClient(&client);
0626 }
0627 
0628 void KXmlGui_UnitTest::testActionListAndSeparator()
0629 {
0630     const QByteArray xml =
0631         "<?xml version = '1.0'?>\n"
0632         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0633         "<gui version=\"1\" name=\"foo\" >\n"
0634         "<MenuBar>\n"
0635         " <Menu name=\"groups\"><text>Add to Group</text>\n"
0636         "  <ActionList name=\"view_groups_list\"/>\n"
0637         "  <Separator />"
0638         "  <Action name=\"view_add_to_new_group\" />\n"
0639         "  <ActionList name=\"second_list\"/>\n"
0640         " </Menu>\n"
0641         "</MenuBar>\n"
0642         "</gui>";
0643 
0644     TestGuiClient client(xml);
0645     client.createActions(QStringList() << QStringLiteral("view_add_to_new_group"));
0646     KMainWindow mainWindow;
0647     KXMLGUIBuilder builder(&mainWindow);
0648     KXMLGUIFactory factory(&builder);
0649     factory.addClient(&client);
0650 
0651     QWidget *menuW = factory.container(QStringLiteral("groups"), &client);
0652     QVERIFY(menuW);
0653     QMenu *menu = qobject_cast<QMenu *>(menuW);
0654     QVERIFY(menu);
0655 
0656     // debugActions(menu->actions());
0657     checkActions(menu->actions(),
0658                  QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it
0659                                << QStringLiteral("view_add_to_new_group"));
0660 
0661     qDebug() << "Now plugging the actionlist";
0662 
0663     QAction *action1 = new QAction(this);
0664     action1->setObjectName(QStringLiteral("action1"));
0665     client.actionCollection()->setDefaultShortcut(action1, QKeySequence(QStringLiteral("Ctrl+2")));
0666     QAction *action2 = new QAction(this);
0667     action2->setObjectName(QStringLiteral("action2"));
0668     const QList<QAction *> actionList = {action1, action2};
0669     client.plugActionList(QStringLiteral("view_groups_list"), actionList);
0670     QCOMPARE(QKeySequence::listToString(action1->shortcuts()), QStringLiteral("Ctrl+2"));
0671 
0672     const QStringList expectedActionsOneList = {QStringLiteral("action1"),
0673                                                 QStringLiteral("action2"),
0674                                                 QStringLiteral("separator"),
0675                                                 QStringLiteral("view_add_to_new_group")};
0676     // debugActions(menu->actions());
0677     checkActions(menu->actions(), expectedActionsOneList);
0678 
0679     QAction *action3 = new QAction(this);
0680     action3->setObjectName(QStringLiteral("action3"));
0681     const QList<QAction *> secondActionList = {action3};
0682     client.plugActionList(QStringLiteral("second_list"), secondActionList);
0683     QStringList expectedActions = expectedActionsOneList;
0684     expectedActions << QStringLiteral("action3");
0685     checkActions(menu->actions(), expectedActions);
0686 
0687     qDebug() << "Now remove+add gui client";
0688 
0689     // While I'm here, what happens with the action list if I remove+add the guiclient,
0690     // like KXmlGuiWindow::newToolBarConfig does?
0691     factory.removeClient(&client);
0692     factory.addClient(&client);
0693     // We need to get the container widget again, it was re-created.
0694     menuW = factory.container(QStringLiteral("groups"), &client);
0695     QVERIFY(menuW);
0696     menu = qobject_cast<QMenu *>(menuW);
0697     // debugActions(menu->actions());
0698     checkActions(menu->actions(),
0699                  QStringList() << QStringLiteral("separator") // yep, it removed the actionlist thing...
0700                                << QStringLiteral("view_add_to_new_group"));
0701     qDebug() << "Now plugging the actionlist again";
0702     client.plugActionList(QStringLiteral("second_list"), secondActionList);
0703     client.plugActionList(QStringLiteral("view_groups_list"), actionList);
0704     checkActions(menu->actions(), expectedActions);
0705     factory.removeClient(&client);
0706 }
0707 
0708 void KXmlGui_UnitTest::testHiddenToolBar()
0709 {
0710     const QByteArray xml =
0711         "<?xml version = '1.0'?>\n"
0712         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0713         "<gui version=\"1\" name=\"foo\" >\n"
0714         "<MenuBar>\n"
0715         "</MenuBar>\n"
0716         "<ToolBar hidden=\"true\" name=\"mainToolBar\">\n"
0717         "  <Action name=\"go_up\"/>\n"
0718         "</ToolBar>\n"
0719         "<ToolBar name=\"visibleToolBar\">\n"
0720         "  <Action name=\"go_up\"/>\n"
0721         "</ToolBar>\n"
0722         "<ToolBar hidden=\"true\" name=\"hiddenToolBar\">\n"
0723         "  <Action name=\"go_up\"/>\n"
0724         "</ToolBar>\n"
0725         "</gui>\n";
0726     KConfigGroup cg(KSharedConfig::openConfig(), "testHiddenToolBar");
0727     TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
0728     mainWindow.setAutoSaveSettings(cg);
0729     mainWindow.createActions(QStringList() << QStringLiteral("go_up"));
0730     mainWindow.createGUI();
0731 
0732     KToolBar *mainToolBar = mainWindow.toolBarByName(QStringLiteral("mainToolBar"));
0733     QVERIFY(mainToolBar->isHidden());
0734 
0735     KXMLGUIFactory *factory = mainWindow.guiFactory();
0736     QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
0737     KToolBar *hiddenToolBar = qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow));
0738     qDebug() << hiddenToolBar;
0739     QVERIFY(hiddenToolBar->isHidden());
0740 
0741     // Now open KEditToolBar (#105525)
0742     KEditToolBar editToolBar(factory);
0743     // KEditToolBar loads the stuff in showEvent...
0744     QShowEvent ev;
0745     qApp->sendEvent(&editToolBar, &ev);
0746     clickApply(&editToolBar);
0747     QVERIFY(qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow))->isHidden());
0748 
0749     mainWindow.close();
0750 }
0751 
0752 void KXmlGui_UnitTest::testCustomPlaceToolBar()
0753 {
0754     const QByteArray xml =
0755         "<?xml version = '1.0'?>\n"
0756         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0757         "<gui version=\"1\" name=\"foo\" >\n"
0758         "<MenuBar>\n"
0759         "</MenuBar>\n"
0760         "<ToolBar name=\"mainToolBar\" noMerge=\"1\">\n"
0761         "  <text>Main Toolbar</text>\n"
0762         "  <Action name=\"go_up\"/>\n"
0763         "</ToolBar>\n"
0764         "<ToolBar name=\"customToolBar\" noMerge=\"1\">\n"
0765         "  <text>Custom Toolbar</text>\n"
0766         "  <Action name=\"go_up\"/>\n"
0767         "  <Action name=\"file_open\"/>\n"
0768         "  <Action name=\"help_about_kde\"/>\n"
0769         "</ToolBar>\n"
0770         "</gui>\n";
0771     KConfigGroup cg(KSharedConfig::openConfig(), "testHiddenToolBar");
0772     TestXmlGuiWindow mw(xml, "kxmlgui_unittest.rc");
0773     mw.setAutoSaveSettings(cg);
0774 
0775     QWidget *centralWidget = new QWidget(&mw);
0776     centralWidget->setGeometry(0, 0, 100, 100);
0777     mw.setCentralWidget(centralWidget);
0778     QHBoxLayout *layout = new QHBoxLayout(centralWidget);
0779     KToolBar *customToolBar = new KToolBar(QStringLiteral("customToolBar"), centralWidget);
0780     layout->addWidget(customToolBar);
0781 
0782     mw.createActions(QStringList() << QStringLiteral("go_up"));
0783     mw.createGUI();
0784 
0785     QVERIFY(centralWidget->findChild<KToolBar *>(QStringLiteral("customToolBar")) != nullptr);
0786 
0787     QVERIFY2(mw.toolBarArea(customToolBar) == Qt::NoToolBarArea, "The custom toolbar should not be in a ToolBarArea of the main window");
0788 
0789     // Now open KEditToolBar, just to check it doesn't crash on apply
0790     KXMLGUIFactory *factory = mw.guiFactory();
0791     KEditToolBar editToolBar(factory);
0792     // KEditToolBar loads the stuff in showEvent...
0793     QShowEvent ev;
0794     qApp->sendEvent(&editToolBar, &ev);
0795     clickApply(&editToolBar);
0796 
0797     QVERIFY2(centralWidget->findChild<KToolBar *>(QStringLiteral("customToolBar")) != nullptr,
0798              "After a KEditToolBar action the custom toolbar should stay in the widget it was orignally added");
0799     QVERIFY2(mw.toolBarArea(mw.toolBarByName(QStringLiteral("customToolBar"))) == Qt::NoToolBarArea,
0800              "After a KEditToolBar action the custom toolbar should not be in a ToolBarArea of the main window");
0801 
0802     mw.close();
0803 }
0804 
0805 // taken from KMainWindow_UnitTest::testAutoSaveSettings()
0806 void KXmlGui_UnitTest::testAutoSaveSettings()
0807 {
0808     const QByteArray xml =
0809         "<?xml version = '1.0'?>\n"
0810         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0811         "<gui version=\"1\" name=\"foo\" >\n"
0812         "<MenuBar>\n"
0813         "</MenuBar>\n"
0814         "<ToolBar name=\"mainToolBar\">\n"
0815         "  <Action name=\"go_up\"/>\n"
0816         "</ToolBar>\n"
0817         "<ToolBar name=\"secondToolBar\">\n"
0818         "  <Action name=\"go_up\"/>\n"
0819         "</ToolBar>\n"
0820         "</gui>\n";
0821     {
0822         // do not interfere with the "toolbarVisibility" unit test
0823         KConfigGroup cg(KSharedConfig::openConfig(), "testAutoSaveSettings");
0824         TestXmlGuiWindow mw(xml, "kxmlgui_unittest.rc");
0825         mw.show();
0826         mw.setAutoSaveSettings(cg);
0827 
0828         // Test resizing first (like show() does).
0829         mw.reallyResize(400, 400);
0830         QTest::qWait(200);
0831 
0832         mw.createActions(QStringList() << QStringLiteral("go_up"));
0833         mw.createGUI();
0834 
0835         // Resize again, should be saved
0836         mw.reallyResize(800, 600);
0837         QTest::qWait(200);
0838 
0839         KToolBar *mainToolBar = mw.toolBarByName(QStringLiteral("mainToolBar"));
0840         QCOMPARE(mw.toolBarArea(mainToolBar), Qt::TopToolBarArea);
0841         KToolBar *secondToolBar = mw.toolBarByName(QStringLiteral("secondToolBar"));
0842         QCOMPARE((int)mw.toolBarArea(secondToolBar), (int)Qt::TopToolBarArea); // REFERENCE #1 (see below)
0843 
0844         // Move second toolbar to bottom
0845         const QPoint oldPos = secondToolBar->pos();
0846         mw.addToolBar(Qt::BottomToolBarArea, secondToolBar);
0847         const QPoint newPos = secondToolBar->pos();
0848         QCOMPARE(mw.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
0849         // Calling to addToolBar is not enough to trigger the event filter for move events
0850         // in KMainWindow, because there is no layouting happening in hidden mainwindows.
0851         QMoveEvent moveEvent(newPos, oldPos);
0852         QApplication::sendEvent(secondToolBar, &moveEvent);
0853 
0854         mw.close();
0855     }
0856 
0857     {
0858         KConfigGroup cg(KSharedConfig::openConfig(), "testAutoSaveSettings");
0859         TestXmlGuiWindow mw2(xml, "kxmlgui_unittest.rc");
0860         mw2.show();
0861         mw2.setAutoSaveSettings(cg);
0862         QTest::qWait(200);
0863         // Check window size was restored
0864         QCOMPARE(mw2.size(), QSize(800, 600));
0865 
0866         mw2.createActions(QStringList() << QStringLiteral("go_up"));
0867         mw2.createGUI();
0868 
0869         // Force window layout to happen
0870         mw2.reallyResize(800, 600);
0871         QTest::qWait(200);
0872 
0873         // Check toolbar positions were restored
0874         KToolBar *mainToolBar = mw2.toolBarByName(QStringLiteral("mainToolBar"));
0875         QCOMPARE(mw2.toolBarArea(mainToolBar), Qt::TopToolBarArea);
0876         KToolBar *secondToolBar = mw2.toolBarByName(QStringLiteral("secondToolBar"));
0877         QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
0878         mw2.applyMainWindowSettings(mw2.autoSaveConfigGroup());
0879         QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
0880     }
0881 }
0882 
0883 void KXmlGui_UnitTest::testDeletedContainers() // deleted="true"
0884 {
0885     const QByteArray xml =
0886         "<?xml version = '1.0'?>\n"
0887         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0888         "<gui version=\"1\" name=\"foo\" >\n"
0889         "<MenuBar>\n"
0890         "  <Menu deleted=\"true\" name=\"game\"/>\n"
0891         "</MenuBar>\n"
0892         "<ToolBar deleted=\"true\" name=\"mainToolBar\">\n"
0893         "  <Action name=\"go_up\"/>\n"
0894         "</ToolBar>\n"
0895         "<ToolBar name=\"visibleToolBar\">\n"
0896         "  <Action name=\"go_up\"/>\n"
0897         "</ToolBar>\n"
0898         "<ToolBar deleted=\"true\" name=\"deletedToolBar\">\n"
0899         "  <Action name=\"go_up\"/>\n"
0900         "</ToolBar>\n"
0901         "</gui>\n";
0902     KConfigGroup cg(KSharedConfig::openConfig(), "testDeletedToolBar");
0903     TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
0904     mainWindow.setAutoSaveSettings(cg);
0905     mainWindow.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("file_new") << QStringLiteral("game_new"));
0906     mainWindow.createGUI();
0907     KXMLGUIFactory *factory = mainWindow.guiFactory();
0908 
0909     // qDebug() << "containers:" << factory->containers("ToolBar");
0910     QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow));
0911     QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
0912     QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow));
0913     QVERIFY(factory->container(QStringLiteral("file"), &mainWindow)); // File menu was created
0914     QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow)); // Game menu was not created
0915 
0916     // Now open KEditToolBar, just to check it doesn't crash.
0917     KEditToolBar editToolBar(factory);
0918     // KEditToolBar loads the stuff in showEvent...
0919     QShowEvent ev;
0920     qApp->sendEvent(&editToolBar, &ev);
0921     clickApply(&editToolBar);
0922     QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow));
0923     QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
0924     QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow));
0925     QVERIFY(factory->container(QStringLiteral("file"), &mainWindow));
0926     QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow));
0927 
0928     mainWindow.close();
0929 }
0930 
0931 void KXmlGui_UnitTest::testTopLevelSeparator()
0932 {
0933     const QByteArray xml =
0934         "<?xml version = '1.0'?>\n"
0935         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0936         "<gui version=\"1\" name=\"foo\" >\n"
0937         "<MenuBar>\n"
0938         " <Menu name=\"before_separator\"><text>Before Separator</text></Menu>\n"
0939         " <Separator />\n"
0940         " <Menu name=\"after_separator\"><text>After Separator</text></Menu>\n"
0941         "</MenuBar>\n"
0942         "</gui>";
0943 
0944     TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
0945     mainWindow.createGUI();
0946 
0947     checkActions(mainWindow.menuBar()->actions(),
0948                  QStringList() << QStringLiteral("before_separator") << QStringLiteral("separator") << QStringLiteral("after_separator")
0949                                << QStringLiteral("settings") << QStringLiteral("separator") << QStringLiteral("help"));
0950 }
0951 
0952 // Check that the objectName() of the menus is set from the name in the XML file
0953 void KXmlGui_UnitTest::testMenuNames()
0954 {
0955     const QByteArray xml =
0956         "<?xml version = '1.0'?>\n"
0957         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
0958         "<gui version=\"1\" name=\"foo\" >\n"
0959         "<MenuBar>\n"
0960         " <Menu name=\"filemenu\"><text>File Menu</text></Menu>\n"
0961         "</MenuBar>\n"
0962         "</gui>";
0963 
0964     TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
0965     mainWindow.createGUI();
0966 
0967     checkActions(mainWindow.menuBar()->actions(),
0968                  QStringList() << QStringLiteral("filemenu") << QStringLiteral("settings") << QStringLiteral("separator") << QStringLiteral("help"));
0969 }
0970 
0971 // Test what happens when the application's rc file isn't found
0972 // We want a warning to be printed, but we don't want to see all menus from ui_standards.rc
0973 void KXmlGui_UnitTest::testMenusNoXmlFile()
0974 {
0975     TestXmlGuiWindow mainWindow(QByteArray(), "nolocalfile_either.rc");
0976     mainWindow.createGUIBad();
0977 
0978     checkActions(mainWindow.menuBar()->actions(), QStringList{QStringLiteral("settings"), QStringLiteral("separator"), QStringLiteral("help")});
0979 }
0980 
0981 void KXmlGui_UnitTest::testXMLFileReplacement()
0982 {
0983     // to differentiate "original" and replacement xml file, one is created with "modified" toolbars
0984     QTemporaryFile fileOrig;
0985     QVERIFY(fileOrig.open());
0986     createXmlFile(fileOrig, 2, AddToolBars);
0987     const QString filenameOrig = fileOrig.fileName();
0988     fileOrig.close();
0989 
0990     QTemporaryFile fileReplace;
0991     QVERIFY(fileReplace.open());
0992     createXmlFile(fileReplace, 2, AddModifiedToolBars);
0993     const QString filenameReplace = fileReplace.fileName();
0994     fileReplace.close();
0995 
0996     // finally, our local xml file has <ActionProperties/>
0997     const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
0998     QFile fileLocal(dir + QLatin1String("/testui.rc"));
0999     QVERIFY2(fileLocal.open(QIODevice::WriteOnly), qPrintable(fileLocal.fileName()));
1000     createXmlFile(fileLocal, 1, AddActionProperties);
1001     const QString filenameLocal = fileLocal.fileName();
1002     fileLocal.close();
1003 
1004     TestGuiClient client;
1005     // first make sure that the "original" file is loaded, correctly
1006     client.setXMLFilePublic(filenameOrig);
1007     QString xml = client.domDocument().toString();
1008     // qDebug() << xml;
1009     QVERIFY(xml.contains(QLatin1String("<Action name=\"print\"")));
1010     QVERIFY(!xml.contains(QLatin1String("<Action name=\"home\"")));
1011     QVERIFY(!xml.contains(QLatin1String("<ActionProperties>")));
1012 
1013     // now test the replacement (+ local file)
1014     client.replaceXMLFile(filenameReplace, filenameLocal);
1015     xml = client.domDocument().toString();
1016     QVERIFY(!xml.contains(QLatin1String("<Action name=\"print\"")));
1017     QVERIFY(xml.contains(QLatin1String("<Action name=\"home\"")));
1018     QVERIFY(xml.contains(QLatin1String("<ActionProperties>")));
1019 
1020     // re-check after a reload
1021     client.reloadXML();
1022     QString reloadedXml = client.domDocument().toString();
1023     QVERIFY(!reloadedXml.contains(QLatin1String("<Action name=\"print\"")));
1024     QVERIFY(reloadedXml.contains(QLatin1String("<Action name=\"home\"")));
1025     QVERIFY(reloadedXml.contains(QLatin1String("<ActionProperties>")));
1026 
1027     // Check what happens when the local file doesn't exist
1028     TestGuiClient client2;
1029     QFile::remove(filenameLocal);
1030     client2.replaceXMLFile(filenameReplace, filenameLocal);
1031     xml = client2.domDocument().toString();
1032     // qDebug() << xml;
1033     QVERIFY(!xml.contains(QLatin1String("<Action name=\"print\"")));
1034     QVERIFY(xml.contains(QLatin1String("<Action name=\"home\""))); // modified toolbars
1035     QVERIFY(!xml.contains(QLatin1String("<ActionProperties>"))); // but no local xml file
1036 }
1037 
1038 void KXmlGui_UnitTest::testClientDestruction() // #170806
1039 {
1040     const QByteArray hostXml =
1041         "<?xml version = '1.0'?>\n"
1042         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
1043         "<gui version=\"1\" name=\"foo\" >\n"
1044         "<MenuBar>\n"
1045         " <Menu name=\"file\"><text>&amp;File</text>\n"
1046         " </Menu>\n"
1047         " <Merge/>\n"
1048         "</MenuBar>\n"
1049         "</gui>";
1050     const QByteArray xml =
1051         "<?xml version = '1.0'?>\n"
1052         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
1053         "<gui version=\"1\" name=\"foo\" >\n"
1054         "<MenuBar>\n"
1055         " <Menu name=\"file\"><text>&amp;File</text>\n"
1056         "  <Action name=\"file_open\"/>\n"
1057         "  <Action name=\"file_quit\"/>\n"
1058         " </Menu>\n"
1059         "</MenuBar>\n"
1060         "</gui>";
1061 
1062     TestXmlGuiWindow mainWindow(hostXml, "kxmlgui_unittest.rc");
1063     TestGuiClient *client = new TestGuiClient(xml);
1064     client->createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit"));
1065     mainWindow.insertChildClient(client);
1066     mainWindow.createGUI();
1067 
1068     const QStringList menus = {QStringLiteral("file"), QStringLiteral("settings"), QStringLiteral("separator"), QStringLiteral("help")};
1069     checkActions(mainWindow.menuBar()->actions(), menus);
1070 
1071     QVERIFY(mainWindow.factory()->clients().contains(client));
1072     delete client;
1073     QVERIFY(!mainWindow.factory()->clients().contains(client));
1074 
1075     // No change, because deletion is fast, it doesn't do manual unplugging.
1076     checkActions(mainWindow.menuBar()->actions(), menus);
1077 }
1078 
1079 void KXmlGui_UnitTest::testShortcuts()
1080 {
1081     const QByteArray xml =
1082         "<?xml version = '1.0'?>\n"
1083         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
1084         "<gui version=\"1\" name=\"foo\" >\n"
1085         "<MenuBar>\n"
1086         " <Menu name=\"file\"><text>&amp;File</text>\n"
1087         "  <Action name=\"file_open\" shortcut=\"Ctrl+O\"/>\n"
1088         "  <Action name=\"file_quit\" shortcut=\"Ctrl+Q; Ctrl+D\"/>\n"
1089         " </Menu>\n"
1090         "</MenuBar>\n"
1091         "<ActionProperties scheme=\"Default\">\n"
1092         "  <Action shortcut=\"Ctrl+O\" name=\"file_open\"/>\n"
1093         "  <Action shortcut=\"Ctrl+Q; Ctrl+D\" name=\"file_quit\"/>\n"
1094         "</ActionProperties>\n"
1095         "</gui>";
1096 
1097     TestGuiClient client;
1098     client.createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit"));
1099     client.createGUI(xml, false /*ui_standards.rc*/);
1100 
1101     KMainWindow mainWindow;
1102     KXMLGUIBuilder builder(&mainWindow);
1103     KXMLGUIFactory factory(&builder);
1104     factory.addClient(&client);
1105 
1106     QAction *actionOpen = client.action("file_open");
1107     QAction *actionQuit = client.action("file_quit");
1108     QVERIFY(actionOpen && actionQuit);
1109     QCOMPARE(actionOpen->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+O")));
1110     // #345411
1111     QCOMPARE(actionQuit->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+Q")) << QKeySequence(QStringLiteral("Ctrl+D")));
1112 
1113     factory.removeClient(&client);
1114 }
1115 
1116 void KXmlGui_UnitTest::testPopupMenuParent()
1117 {
1118     const QByteArray xml =
1119         "<?xml version = '1.0'?>\n"
1120         "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
1121         "<gui version=\"1\" name=\"foo\" >\n"
1122         "<Menu name=\"foo\"><text>Foo</text></Menu>\n"
1123         "</gui>";
1124 
1125     TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
1126     mainWindow.createGUI();
1127 
1128     auto popupMenu = mainWindow.menuByName(QStringLiteral("foo"));
1129     QVERIFY(popupMenu);
1130     QCOMPARE(popupMenu->parent(), &mainWindow);
1131 }
1132 
1133 void KXmlGui_UnitTest::testSpecificApplicationLanguageQLocale()
1134 {
1135     qunsetenv("LC_ALL"); // In case the user sets the LC_ALL ENV variable this test could fail
1136     const QLocale originalSystemLocale = QLocale::system();
1137 
1138     KDEPrivate::setApplicationSpecificLanguage("ru_RU");
1139     KDEPrivate::initializeLanguages();
1140 
1141 #ifdef Q_OS_WIN
1142     QEXPECT_FAIL("",
1143                  "KDEPrivate::initializeLanguages assumes that setting $LANGUAGE will have an effect, this isn't the case on Windows. Needs to be fixed.",
1144                  Abort);
1145 #endif
1146     QCOMPARE(QLocale::system().language(), QLocale::Russian);
1147 
1148     KDEPrivate::setApplicationSpecificLanguage("wa");
1149     KDEPrivate::initializeLanguages();
1150 
1151     QCOMPARE(QLocale::system().language(), QLocale::Walloon);
1152 
1153     KDEPrivate::setApplicationSpecificLanguage(QByteArray());
1154     KDEPrivate::initializeLanguages();
1155 
1156     QCOMPARE(QLocale::system(), originalSystemLocale);
1157 }
1158 
1159 void KXmlGui_UnitTest::testSingleModifierQKeySequenceEndsWithPlus()
1160 {
1161     // Check that native texts of the Meta, Alt, Control, Shift, Keypad modifiers end in "+"
1162     // we depend on that in KKeySequenceWidgetPrivate::updateShortcutDisplay()
1163     QVERIFY(QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1164     QVERIFY(QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1165     QVERIFY(QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1166     QVERIFY(QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1167     QVERIFY(QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1168 }
1169 
1170 void KXmlGui_UnitTest::testSaveShortcutsAndRefresh()
1171 {
1172     QTemporaryFile xmlFile;
1173     QVERIFY(xmlFile.open());
1174     createXmlFile(xmlFile, 2, AddModifiedMenus);
1175     const QString filename = xmlFile.fileName();
1176     xmlFile.close();
1177 
1178     QTemporaryFile localXmlFile;
1179     QVERIFY(localXmlFile.open());
1180     const QString localFilename = localXmlFile.fileName();
1181     localXmlFile.close();
1182 
1183     TestGuiClient client;
1184     client.createActions({QStringLiteral("file_open")});
1185     client.setLocalXMLFilePublic(localFilename);
1186     client.setXMLFilePublic(filename);
1187 
1188     QWidget w;
1189     KXMLGUIBuilder builder(&w);
1190     KXMLGUIFactory factory(&builder);
1191     factory.addClient(&client);
1192     factory.removeClient(&client);
1193     factory.addClient(&client);
1194 
1195     QAction *a = client.actionCollection()->action(QStringLiteral("file_open"));
1196     QCOMPARE(a->shortcut(), QKeySequence());
1197     a->setShortcut(Qt::Key_F22);
1198     QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
1199 
1200     client.actionCollection()->writeSettings();
1201 
1202     factory.refreshActionProperties();
1203 
1204     a = client.actionCollection()->action(QStringLiteral("file_open"));
1205     QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
1206 }
1207 
1208 #include "moc_kxmlgui_unittest.cpp"