File indexing completed on 2024-04-21 03:59:31

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