Files
kxmlgui/autotests/kxmlgui_unittest.cpp
Akshaj Gopalakrishnan d4502ab654 Restore KConfig shortcuts after refresh
ToolBarHandler uses the KConfig file instead of .rc files to store
shortcuts. When refreshActionProperties() is called, it removes all
shortcuts, and sets shortcuts according to the scheme, and then
user-defined shortcuts from .rc file. This change makes it restore
KConfig shortcuts also.

With this commit, user-defined KConfig shortcuts persist across
schemes instead of being reset. Ideally these shortcuts should also be
scheme aware, but that change is out of scope.

CCBUG: #429743
2026-10-03 12:39:21 +02:00

1328 lines
55 KiB
C++

/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kxmlgui_unittest.h"
#include "testguiclient.h"
#include "testxmlguiwindow.h"
#include <QAction>
#include <QDebug>
#include <QDialogButtonBox>
#include <QDir>
#include <QHBoxLayout>
#include <QMenuBar>
#include <QPushButton>
#include <QShowEvent>
#include <QTest>
#include <QWidget>
#include <KConfigGroup>
#include <KSharedConfig>
#include <kedittoolbar.h>
#include <kswitchlanguagedialog_p.h>
#include <kxmlguibuilder.h>
#include <kxmlguiclient.h>
#include <kxmlguiversionhandler.cpp> // it's not exported, so we need to include the code here
QTEST_MAIN(KXmlGui_UnitTest)
enum Flags {
NoFlags = 0,
AddToolBars = 1,
AddModifiedToolBars = 2,
AddActionProperties = 4,
AddModifiedMenus = 8,
// next item is 16
};
static void createXmlFile(QFile &file, int version, int flags, const QByteArray &toplevelTag = "gui")
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE " + toplevelTag + " SYSTEM \"kpartgui.dtd\">\n"
"<" + toplevelTag + " version=\"" + QByteArray::number(version) + "\" name=\"foo\" >\n"
"<MenuBar>\n";
file.write(xml);
if (flags & AddModifiedMenus) {
file.write(
"<Menu noMerge=\"1\" name=\"file\" >\n"
"<text>&amp;File</text>\n"
"<Action name=\"file_open\" />\n"
"</Menu>\n");
}
file.write("</MenuBar>\n");
if (flags & AddToolBars) {
file.write(
"<ToolBar name=\"mainToolBar\">\n"
" <text>Main Toolbar</text>\n"
" <Action name=\"print\" />\n"
"</ToolBar>\n"
"<ToolBar name=\"bookmarkToolBar\">\n"
" <text>Bookmark Toolbar</text>\n"
"</ToolBar>\n"
"<ToolBar name=\"newToolBar\">\n"
" <text>New Toolbar</text>\n"
"</ToolBar>\n");
}
if (flags & AddModifiedToolBars) {
file.write(
"<ToolBar name=\"mainToolBar\">\n"
" <text>Main Toolbar</text>\n"
" <Action name=\"home\" />\n"
"</ToolBar>\n"
"<ToolBar name=\"bookmarkToolBar\">\n"
" <text>Modified toolbars</text>\n"
"</ToolBar>\n");
}
if (flags & AddActionProperties) {
file.write(
"<ActionProperties>\n"
" <Action shortcut=\"F9\" name=\"konq_sidebartng\" />\n"
"</ActionProperties>\n");
}
file.write("</" + toplevelTag + ">\n");
}
class ShortcutSchemeHandler
{
public:
ShortcutSchemeHandler(const QString &scheme)
: cgScheme(KSharedConfig::openConfig(), QStringLiteral("Shortcut Schemes"))
, prevScheme(cgScheme.readEntry("Current Scheme", QStringLiteral("Default")))
{
cgScheme.writeEntry("Current Scheme", scheme);
}
~ShortcutSchemeHandler()
{
cgScheme.writeEntry("Current Scheme", prevScheme);
}
private:
KConfigGroup cgScheme;
const QString prevScheme;
};
static void clickApply(KEditToolBar *dialog)
{
QDialogButtonBox *box = dialog->findChild<QDialogButtonBox *>();
Q_ASSERT(box != nullptr);
box->button(QDialogButtonBox::Apply)->setEnabled(true);
box->button(QDialogButtonBox::Apply)->click();
}
void KXmlGui_UnitTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
// Leftover configuration breaks testAutoSaveSettings
const QString configFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, KSharedConfig::openConfig()->name());
if (!configFile.isEmpty()) {
qDebug() << "Removing old config file";
QFile::remove(configFile);
KSharedConfig::openConfig()->reparseConfiguration();
}
// Create "Test" shortcut scheme to eliminate the KF warning
QFile testScheme = QFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1String("/%1/shortcuts/%2").arg(QCoreApplication::applicationName(), QStringLiteral("Test")));
if (!testScheme.exists()) {
QVERIFY(QFileInfo(testScheme).dir().mkpath(QStringLiteral(".")));
QVERIFY(testScheme.open(QIODevice::WriteOnly));
testScheme.write(QByteArray("<gui><ActionProperties/></gui>"));
}
}
void KXmlGui_UnitTest::testFindVersionNumber_data()
{
QTest::addColumn<QString>("xml");
QTest::addColumn<QString>("version");
QTest::newRow("simple test") << "<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"3\" name=\"foo\"/>\n"
<< "3";
QTest::newRow("two digits") << "<?xml version = '1.0'?>\n"
"<gui version=\"42\" name=\"foo\"/>\n"
<< "42";
QTest::newRow("with spaces") << // as found in dirfilterplugin.rc for instance
"<?xml version = '1.0'?>\n"
"<gui version = \"1\" name=\"foo\"/>\n"
<< "1";
QTest::newRow("with a dot") << // as was found in autorefresh.rc
"<?xml version = '1.0'?>\n"
"<gui version = \"0.2\" name=\"foo\"/>\n"
<< QString() /*error*/;
QTest::newRow("with a comment") << // as found in kmail.rc
"<!DOCTYPE gui>\n"
"<!-- This file should be synchronized with kmail_part.rc to provide\n"
"the same menu entries at the same place in KMail and Kontact -->\n"
"<gui version=\"452\" name=\"kmmainwin\">\n"
<< "452";
}
void KXmlGui_UnitTest::testFindVersionNumber()
{
QFETCH(QString, xml);
QFETCH(QString, version);
QCOMPARE(KXMLGUIClient::findVersionNumber(xml), version);
}
void KXmlGui_UnitTest::testVersionHandlerSameVersion()
{
// This emulates the case where the user has modified stuff locally
// and the application hasn't changed since, so the version number is unchanged.
QTemporaryFile userFile;
QVERIFY(userFile.open());
createXmlFile(userFile, 2, AddActionProperties | AddModifiedToolBars);
const QString firstFile = userFile.fileName();
QTemporaryFile appFile;
QVERIFY(appFile.open());
createXmlFile(appFile, 2, AddToolBars);
const QString secondFile = appFile.fileName();
QStringList files;
files << firstFile << secondFile;
userFile.close();
appFile.close();
KXmlGuiVersionHandler versionHandler(files);
QCOMPARE(versionHandler.finalFile(), firstFile);
QString finalDoc = versionHandler.finalDocument();
QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
// Check that the shortcuts defined by the user were kept
QVERIFY(finalDoc.contains(QLatin1String("<ActionProperties>")));
QVERIFY(finalDoc.contains(QLatin1String("sidebartng")));
// Check that the toolbars modified by the user were kept
QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"home\"")));
// and that the toolbar that isn't in the local file, isn't there in the GUI
QVERIFY(!finalDoc.contains(QLatin1String("<ToolBar name=\"newToolBar\"")));
QVERIFY(userFile.open());
const QString userFileContents = QString::fromUtf8(userFile.readAll());
QCOMPARE(finalDoc, userFileContents);
}
void KXmlGui_UnitTest::testVersionHandlerNewVersionNothingKept()
{
// This emulates the case where the application has been upgraded
// and the user has a local ui.rc file, but without shortcuts or toolbar changes.
// Not sure how this can happen - would be a menu-only app and hand editing !?
// Anyway the point is to test version number comparison :)
QMap<QString, int> fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames
const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
QDir().mkpath(dir);
QFile fileV2(dir + QStringLiteral("/testui.rc"));
QVERIFY2(fileV2.open(QIODevice::WriteOnly), qPrintable(fileV2.fileName()));
createXmlFile(fileV2, 2, NoFlags);
fileToVersionMap.insert(fileV2.fileName(), 2);
QTemporaryFile fileV5;
QVERIFY(fileV5.open());
createXmlFile(fileV5, 5, NoFlags);
fileToVersionMap.insert(fileV5.fileName(), 5);
// The highest version is neither the first nor last one in the list,
// to make sure the code really selects the highest version, not just by chance :)
// (This is why we add the v1 version at the end of the list)
QTemporaryFile fileV1;
QVERIFY(fileV1.open());
createXmlFile(fileV1, 1, NoFlags);
fileToVersionMap.insert(fileV1.fileName(), 1);
QStringList files;
files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName();
fileV2.close();
fileV5.close();
fileV1.close();
KXmlGuiVersionHandler versionHandler(files);
QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 5);
QString finalDoc = versionHandler.finalDocument();
QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
QVERIFY(finalDoc.contains(QLatin1String("version=\"5\"")));
QVERIFY(fileV5.open());
const QString fileV5Contents = QString::fromUtf8(fileV5.readAll());
QCOMPARE(finalDoc, fileV5Contents);
}
void KXmlGui_UnitTest::testVersionHandlerNewVersionUserChanges()
{
// This emulates the case where the application has been upgraded
// after the user has changed shortcuts and toolbars
QMap<QString, int> fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames
// local file
const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
QFile fileV2(dir + QLatin1String("/testui.rc"));
QVERIFY(fileV2.open(QIODevice::WriteOnly));
createXmlFile(fileV2, 2, AddActionProperties | AddModifiedToolBars);
fileToVersionMap.insert(fileV2.fileName(), 2);
// more-global (application) file
QTemporaryFile fileV5;
QVERIFY(fileV5.open());
createXmlFile(fileV5, 5, AddToolBars | AddModifiedMenus, "kpartgui");
fileToVersionMap.insert(fileV5.fileName(), 5);
// The highest version is neither the first nor last one in the list,
// to make sure the code really selects the highest version, not just by chance :)
// (This is why we add the v1 version at the end of the list)
QTemporaryFile fileV1;
QVERIFY(fileV1.open());
createXmlFile(fileV1, 1, AddToolBars);
fileToVersionMap.insert(fileV1.fileName(), 1);
QStringList files;
files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName();
fileV2.close();
fileV5.close();
fileV1.close();
KXmlGuiVersionHandler versionHandler(files);
// We end up with the local file, so in our map it has version 2.
// But of course by now it says "version=5" in it :)
QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 2);
const QString finalDoc = versionHandler.finalDocument();
// qDebug() << finalDoc;
QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
QVERIFY(finalDoc.contains(QLatin1String("version=\"5\"")));
// Check that the shortcuts defined by the user were kept
QVERIFY(finalDoc.contains(QLatin1String("<ActionProperties>")));
QVERIFY(finalDoc.contains(QLatin1String("sidebartng")));
// Check that the menus modified by the app are still there
QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"file_open\"")));
// Check that the toolbars modified by the user were kept
QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"home\"")));
// Check that the toolbars added by the application were kept (https://invent.kde.org/graphics/okular/-/merge_requests/197)
QVERIFY(finalDoc.contains(QLatin1String("<ToolBar name=\"newToolBar\"")));
}
static QStringList collectMenuNames(KXMLGUIFactory &factory)
{
const QList<QWidget *> containers = factory.containers(QStringLiteral("Menu"));
QStringList containerNames;
for (QWidget *w : containers) {
containerNames << w->objectName();
}
return containerNames;
}
void debugActions(const QList<QAction *> &actions)
{
for (QAction *action : actions) {
qDebug() << (action->isSeparator() ? QString::fromLatin1("separator") : action->objectName());
}
}
static void checkActions(const QList<QAction *> &actions, const QStringList &expectedActions)
{
for (int i = 0; i < expectedActions.count(); ++i) {
if (i >= actions.count()) {
break;
}
QAction *action = actions.at(i);
if (action->isSeparator()) {
QCOMPARE(QStringLiteral("separator"), expectedActions[i]);
} else {
QCOMPARE(action->objectName(), expectedActions[i]);
}
}
QCOMPARE(actions.count(), expectedActions.count());
}
void KXmlGui_UnitTest::testPartMerging()
{
const QByteArray hostXml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"go\"><text>&amp;Go</text>\n"
" <!-- go_up, go_back, go_forward, go_home, separator: coming from ui_standards.rc -->\n"
" <DefineGroup name=\"before_merge\"/>\n"
" <Merge/>\n"
" <Action name=\"host_after_merge\"/>\n"
" <Action name=\"host_after_merge_2\"/>\n"
" <Separator/>\n"
" <DefineGroup name=\"new_merge\"/>\n"
" <Title>Section title</Title>\n"
" <Action name=\"last_from_host\"/>\n"
" </Menu>\n"
" <Menu name=\"file\"><text>&amp;File</text>\n"
" <DefineGroup name=\"placed_merge\" append=\"new_merge\"/>\n"
" </Menu>\n"
"</MenuBar>\n"
"</gui>\n";
TestGuiClient hostClient;
hostClient.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("go_back") << QStringLiteral("go_forward") << QStringLiteral("go_home")
<< QStringLiteral("host_after_merge") << QStringLiteral("host_after_merge_2") << QStringLiteral("last_from_host")
<< QStringLiteral("file_new") << QStringLiteral("file_open") << QStringLiteral("file_quit"));
hostClient.createGUI(hostXml, true /*ui_standards.rc*/);
KMainWindow mainWindow;
KXMLGUIBuilder builder(&mainWindow);
KXMLGUIFactory factory(&builder);
factory.addClient(&hostClient);
const QString hostDomDoc = hostClient.domDocument().toString();
QWidget *goMenuW = factory.container(QStringLiteral("go"), &hostClient);
QVERIFY(goMenuW);
QMenu *goMenu = qobject_cast<QMenu *>(goMenuW);
QVERIFY(goMenu);
QMenu *fileMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("file"), &hostClient));
// debugActions(goMenu->actions());
// clang-format off
checkActions(goMenu->actions(), QStringList()
<< QStringLiteral("go_up")
<< QStringLiteral("go_back")
<< QStringLiteral("go_forward")
<< QStringLiteral("go_home")
<< QStringLiteral("separator")
<< QStringLiteral("host_after_merge")
<< QStringLiteral("host_after_merge_2")
<< QStringLiteral("separator")
<< QStringLiteral("separator") // <Title> separator
<< QStringLiteral("last_from_host"));
checkActions(fileMenu->actions(), QStringList()
<< QStringLiteral("file_new")
<< QStringLiteral("file_open")
<< QStringLiteral("separator")
<< QStringLiteral("file_quit"));
// clang-format on
qDebug() << "Now merging the part";
const QByteArray partXml =
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"part\" >\n"
"<MenuBar>\n"
" <Menu name=\"go\"><text>&amp;Go</text>\n"
" <Action name=\"go_previous\" group=\"before_merge\"/>\n"
" <Action name=\"go_next\" group=\"before_merge\"/>\n"
" <Separator/>\n"
" <Action name=\"first_page\"/>\n"
" <Action name=\"last_page\"/>\n"
" <Title>Part Section</Title>\n"
" <ActionList name=\"action_list\"/>\n"
" <Action name=\"action_in_merge_group\" group=\"new_merge\"/>\n"
" <Action name=\"undefined_group\" group=\"no_such_merge\"/>\n"
" <Action name=\"last_from_part\"/>\n"
" </Menu>\n"
" <Menu name=\"file\"><text>&amp;File</text>\n"
" <Action group=\"placed_merge\" name=\"action_in_placed_merge\"/>\n"
" <Action name=\"other_file_action\"/>\n"
" </Menu>\n"
"</MenuBar>\n"
"</gui>\n";
TestGuiClient partClient(partXml);
partClient.createActions(QStringList() << QStringLiteral("go_previous") << QStringLiteral("go_next") << QStringLiteral("first_page")
<< QStringLiteral("last_page") << QStringLiteral("last_from_part") << QStringLiteral("action_in_merge_group")
<< QStringLiteral("undefined_group") << QStringLiteral("action_in_placed_merge")
<< QStringLiteral("other_file_action") << QStringLiteral("action1") << QStringLiteral("action2"));
const QList<QAction *> actionList = {partClient.actionCollection()->action(QStringLiteral("action1")),
partClient.actionCollection()->action(QStringLiteral("action2"))};
for (int i = 0; i < 5; ++i) {
// qDebug() << "addClient, iteration" << i;
factory.addClient(&partClient);
partClient.plugActionList(QStringLiteral("action_list"), actionList);
// clang-format off
//debugActions(goMenu->actions());
checkActions(goMenu->actions(), QStringList()
<< QStringLiteral("go_up")
<< QStringLiteral("go_back")
<< QStringLiteral("go_forward")
<< QStringLiteral("go_home")
<< QStringLiteral("separator")
<< QStringLiteral("go_previous")
<< QStringLiteral("go_next")
// Contents of the <Merge>:
<< QStringLiteral("separator")
<< QStringLiteral("first_page")
<< QStringLiteral("last_page")
<< QStringLiteral("separator") // <title> in the part
<< QStringLiteral("action1")
<< QStringLiteral("action2")
<< QStringLiteral("undefined_group")
<< QStringLiteral("last_from_part")
// End of <Merge>
<< QStringLiteral("host_after_merge")
<< QStringLiteral("host_after_merge_2")
<< QStringLiteral("separator")
// Contents of <DefineGroup>
<< QStringLiteral("action_in_merge_group")
// End of <DefineGroup>
<< QStringLiteral("separator") // <title> is a separator qaction with text
<< QStringLiteral("last_from_host")
);
checkActions(fileMenu->actions(), QStringList()
<< QStringLiteral("file_new")
<< QStringLiteral("action_in_placed_merge")
<< QStringLiteral("file_open")
<< QStringLiteral("separator")
<< QStringLiteral("file_quit")
<< QStringLiteral("other_file_action"));
factory.removeClient(&partClient);
QCOMPARE(hostClient.domDocument().toString(), hostDomDoc);
// clang-format on
}
factory.removeClient(&hostClient);
}
void KXmlGui_UnitTest::testShortcutSchemeMerging()
{
TestGuiClient client;
ShortcutSchemeHandler sss(QStringLiteral("Test"));
KActionCollection *ac = client.actionCollection();
QAction *a = ac->addAction(QStringLiteral("test_action"));
ac->setDefaultShortcut(a, QKeySequence(QStringLiteral("Ctrl+A")));
const QByteArray appXml = R"(<?xml version = "1.0"?>
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="foo" version="5">
<MenuBar>
<Menu name="file"><text>&amp;File</text>
<Action name="test_action" />
</Menu>
</MenuBar></kpartgui>
)";
client.createGUI(appXml, false);
const QByteArray settingsXml = R"(<!DOCTYPE kpartgui SYSTEM 'kpartgui.dtd'>
<kpartgui name="foo" version="1">
<MenuBar>
<Menu name="file">
<text>&amp;File</text>
<Action name="test_action" />
</Menu>
</MenuBar>
<ActionProperties scheme="Default">
<Action name="test_action" shortcut="Ctrl+B"/>
</ActionProperties>
<ActionProperties scheme="Test">
<Action name="test_action" shortcut="Ctrl+C"/>
</ActionProperties>
</kpartgui>
)";
client.mergeXML(settingsXml);
KMainWindow mainWindow;
KXMLGUIBuilder builder(&mainWindow);
KXMLGUIFactory factory(&builder);
factory.addClient(&client);
QCOMPARE(a->shortcut(), QKeySequence(QStringLiteral("Ctrl+C")));
factory.removeClient(&client);
}
void KXmlGui_UnitTest::testPartMergingSettings() // #252911
{
const QByteArray hostXml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
// The solution was to remove the duplicated definition
// " <Menu name=\"settings\"><text>&amp;Settings</text>\n"
// " <Action name=\"options_configure_keybinding\"/>\n"
// " <Action name=\"options_configure_toolbars\"/>\n"
// " <Merge name=\"configure_merge\"/>\n"
// " <Separator/>\n"
// " <Merge/>\n"
// " </Menu>\n"
"</MenuBar></gui>\n";
TestGuiClient hostClient;
hostClient.createActions(QStringList() << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars"));
hostClient.createGUI(hostXml, true /*ui_standards.rc*/);
// qDebug() << hostClient.domDocument().toString();
KMainWindow mainWindow;
KXMLGUIBuilder builder(&mainWindow);
KXMLGUIFactory factory(&builder);
factory.addClient(&hostClient);
QWidget *settingsMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("settings"), &hostClient));
QVERIFY(settingsMenu);
// debugActions(settingsMenu->actions());
const QByteArray partXml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"settings\"><text>&amp;Settings</text>\n"
" <Action name=\"configure_klinkstatus\"/>\n"
" </Menu>\n"
"</MenuBar></gui>\n";
TestGuiClient partClient(partXml);
partClient.createActions(QStringList() << QStringLiteral("configure_klinkstatus"));
factory.addClient(&partClient);
// debugActions(settingsMenu->actions());
checkActions(settingsMenu->actions(),
QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it
<< QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars")
<< QStringLiteral("configure_klinkstatus"));
factory.removeClient(&partClient);
factory.removeClient(&hostClient);
}
void KXmlGui_UnitTest::testUiStandardsMerging_data()
{
QTest::addColumn<QByteArray>("xml");
QTest::addColumn<QStringList>("actions");
QTest::addColumn<QStringList>("expectedMenus");
const QByteArray xmlBegin =
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n";
const QByteArray xmlEnd =
"</MenuBar>\n"
"</gui>";
// Merging an empty menu (or a menu with only non-existing actions) would make
// the empty menu appear at the end after all other menus (fixed for KDE-4.2)
/* clang-format off */
QTest::newRow("empty file menu, implicit settings menu")
<< QByteArray(xmlBegin + "<Menu name=\"file\"/>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("options_configure_toolbars"))
<< (QStringList() << QStringLiteral("settings"));
QTest::newRow("file menu with non existing action, implicit settings menu")
<< QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"foo\"/></Menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("options_configure_toolbars"))
<< (QStringList() << QStringLiteral("settings"));
QTest::newRow("file menu with existing action, implicit settings menu")
<< QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"open\"/></Menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("open") << QStringLiteral("options_configure_toolbars"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("settings"));
QTest::newRow("implicit file and settings menu")
<< QByteArray(xmlBegin + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we could check that file_open is in the mainToolBar, too
// Check that unknown non-empty menus are added at the "MergeLocal" position (before settings).
QTest::newRow("foo menu added at the end")
<< QByteArray(xmlBegin + "<Menu name=\"foo\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars") << QStringLiteral("foo_action"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("foo") << QStringLiteral("settings"));
QTest::newRow("Bille's testcase: menu patch + menu edit")
<< QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n"
+ "<Menu name=\"edit\"><Action name=\"edit_foo\"/></Menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch"));
QTest::newRow("Bille's testcase: menu patch + menu edit, lowercase tag")
<< QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n"
+ "<menu name=\"edit\"><Action name=\"edit_foo\"/></menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch"));
// Check that <Menu append="..."> allows to insert menus at specific positions
QTest::newRow("Menu append")
<< QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"settings_merge\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars")
<< QStringLiteral("foo_action") << QStringLiteral("help_contents"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("settings") << QStringLiteral("foo") << QStringLiteral("help"));
QTest::newRow("Custom first menu")
<< QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"first_menu\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("edit_undo") << QStringLiteral("foo_action") << QStringLiteral("help_contents"))
<< (QStringList() << QStringLiteral("foo") << QStringLiteral("edit") << QStringLiteral("help"));
// Tests for noMerge="1"
QTest::newRow("noMerge empty file menu, implicit settings menu")
<< QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we keep empty menus, see #186382
QTest::newRow("noMerge empty file menu, file_open moved elsewhere")
<< QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open"))
<< (QStringList() << QStringLiteral("file") << QStringLiteral("foo"));
QTest::newRow("noMerge file menu with open before new")
<< QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new"))
<< (QStringList() << QStringLiteral("file")); // TODO check the order of the actions in the menu? how?
// Tests for deleted="true"
QTest::newRow("deleted file menu, implicit settings menu")
<< QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
<< (QStringList() << QStringLiteral("settings"));
QTest::newRow("deleted file menu, file_open moved elsewhere")
<< QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open"))
<< (QStringList() << QStringLiteral("foo"));
QTest::newRow("deleted file menu with actions (contradiction)")
<< QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd)
<< (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new"))
<< (QStringList());
/* clang-format on */
}
void KXmlGui_UnitTest::testUiStandardsMerging()
{
QFETCH(QByteArray, xml);
QFETCH(QStringList, actions);
QFETCH(QStringList, expectedMenus);
TestGuiClient client;
client.createActions(actions);
client.createGUI(xml, true /*ui_standards.rc*/);
const QDomDocument domDocument = client.domDocument();
const QDomElement docElem = domDocument.documentElement();
QCOMPARE(docElem.attribute(QStringLiteral("name")), QStringLiteral("foo")); // not standard_containers from ui_standards.rc
QCOMPARE(docElem.attribute(QStringLiteral("version")), QStringLiteral("1")); // not standard_containers from ui_standards.rc
KMainWindow mainWindow;
KXMLGUIBuilder builder(&mainWindow);
KXMLGUIFactory factory(&builder);
factory.addClient(&client);
const QStringList containerNames = collectMenuNames(factory);
// qDebug() << containerNames;
QCOMPARE(containerNames, expectedMenus);
factory.removeClient(&client);
}
void KXmlGui_UnitTest::testActionListAndSeparator()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"groups\"><text>Add to Group</text>\n"
" <ActionList name=\"view_groups_list\"/>\n"
" <Separator />"
" <Action name=\"view_add_to_new_group\" />\n"
" <ActionList name=\"second_list\"/>\n"
" </Menu>\n"
"</MenuBar>\n"
"</gui>";
TestGuiClient client(xml);
client.createActions(QStringList() << QStringLiteral("view_add_to_new_group"));
KMainWindow mainWindow;
KXMLGUIBuilder builder(&mainWindow);
KXMLGUIFactory factory(&builder);
factory.addClient(&client);
QWidget *menuW = factory.container(QStringLiteral("groups"), &client);
QVERIFY(menuW);
QMenu *menu = qobject_cast<QMenu *>(menuW);
QVERIFY(menu);
// debugActions(menu->actions());
checkActions(menu->actions(),
QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it
<< QStringLiteral("view_add_to_new_group"));
qDebug() << "Now plugging the actionlist";
QAction *action1 = new QAction(this);
action1->setObjectName(QStringLiteral("action1"));
KActionCollection::setDefaultShortcut(action1, QKeySequence(QStringLiteral("Ctrl+2")));
QAction *action2 = new QAction(this);
action2->setObjectName(QStringLiteral("action2"));
const QList<QAction *> actionList = {action1, action2};
client.plugActionList(QStringLiteral("view_groups_list"), actionList);
QCOMPARE(QKeySequence::listToString(action1->shortcuts()), QStringLiteral("Ctrl+2"));
const QStringList expectedActionsOneList = {QStringLiteral("action1"),
QStringLiteral("action2"),
QStringLiteral("separator"),
QStringLiteral("view_add_to_new_group")};
// debugActions(menu->actions());
checkActions(menu->actions(), expectedActionsOneList);
QAction *action3 = new QAction(this);
action3->setObjectName(QStringLiteral("action3"));
const QList<QAction *> secondActionList = {action3};
client.plugActionList(QStringLiteral("second_list"), secondActionList);
QStringList expectedActions = expectedActionsOneList;
expectedActions << QStringLiteral("action3");
checkActions(menu->actions(), expectedActions);
qDebug() << "Now remove+add gui client";
// While I'm here, what happens with the action list if I remove+add the guiclient,
// like KXmlGuiWindow::newToolBarConfig does?
factory.removeClient(&client);
factory.addClient(&client);
// We need to get the container widget again, it was re-created.
menuW = factory.container(QStringLiteral("groups"), &client);
QVERIFY(menuW);
menu = qobject_cast<QMenu *>(menuW);
// debugActions(menu->actions());
checkActions(menu->actions(),
QStringList() << QStringLiteral("separator") // yep, it removed the actionlist thing...
<< QStringLiteral("view_add_to_new_group"));
qDebug() << "Now plugging the actionlist again";
client.plugActionList(QStringLiteral("second_list"), secondActionList);
client.plugActionList(QStringLiteral("view_groups_list"), actionList);
checkActions(menu->actions(), expectedActions);
factory.removeClient(&client);
}
void KXmlGui_UnitTest::testHiddenToolBar()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
"</MenuBar>\n"
"<ToolBar hidden=\"true\" name=\"mainToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"<ToolBar name=\"visibleToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"<ToolBar hidden=\"true\" name=\"hiddenToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"</gui>\n";
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("testHiddenToolBar"));
TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
mainWindow.setAutoSaveSettings(cg);
mainWindow.createActions(QStringList() << QStringLiteral("go_up"));
mainWindow.createGUI();
KToolBar *mainToolBar = mainWindow.toolBarByName(QStringLiteral("mainToolBar"));
QVERIFY(mainToolBar->isHidden());
KXMLGUIFactory *factory = mainWindow.guiFactory();
QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
KToolBar *hiddenToolBar = qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow));
qDebug() << hiddenToolBar;
QVERIFY(hiddenToolBar->isHidden());
// Now open KEditToolBar (#105525)
KEditToolBar editToolBar(factory);
// KEditToolBar loads the stuff in showEvent...
QShowEvent ev;
qApp->sendEvent(&editToolBar, &ev);
clickApply(&editToolBar);
QVERIFY(qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow))->isHidden());
mainWindow.close();
}
void KXmlGui_UnitTest::testCustomPlaceToolBar()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
"</MenuBar>\n"
"<ToolBar name=\"mainToolBar\" noMerge=\"1\">\n"
" <text>Main Toolbar</text>\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"<ToolBar name=\"customToolBar\" noMerge=\"1\">\n"
" <text>Custom Toolbar</text>\n"
" <Action name=\"go_up\"/>\n"
" <Action name=\"file_open\"/>\n"
" <Action name=\"help_about_kde\"/>\n"
"</ToolBar>\n"
"</gui>\n";
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("testHiddenToolBar"));
TestXmlGuiWindow mw(xml, "kxmlgui_unittest.rc");
mw.setAutoSaveSettings(cg);
QWidget *centralWidget = new QWidget(&mw);
centralWidget->setGeometry(0, 0, 100, 100);
mw.setCentralWidget(centralWidget);
QHBoxLayout *layout = new QHBoxLayout(centralWidget);
KToolBar *customToolBar = new KToolBar(QStringLiteral("customToolBar"), centralWidget);
layout->addWidget(customToolBar);
mw.createActions(QStringList() << QStringLiteral("go_up"));
mw.createGUI();
QVERIFY(centralWidget->findChild<KToolBar *>(QStringLiteral("customToolBar")) != nullptr);
QVERIFY2(mw.toolBarArea(customToolBar) == Qt::NoToolBarArea, "The custom toolbar should not be in a ToolBarArea of the main window");
// Now open KEditToolBar, just to check it doesn't crash on apply
KXMLGUIFactory *factory = mw.guiFactory();
KEditToolBar editToolBar(factory);
// KEditToolBar loads the stuff in showEvent...
QShowEvent ev;
qApp->sendEvent(&editToolBar, &ev);
clickApply(&editToolBar);
QVERIFY2(centralWidget->findChild<KToolBar *>(QStringLiteral("customToolBar")) != nullptr,
"After a KEditToolBar action the custom toolbar should stay in the widget it was orignally added");
QVERIFY2(mw.toolBarArea(mw.toolBarByName(QStringLiteral("customToolBar"))) == Qt::NoToolBarArea,
"After a KEditToolBar action the custom toolbar should not be in a ToolBarArea of the main window");
mw.close();
}
// taken from KMainWindow_UnitTest::testAutoSaveSettings()
void KXmlGui_UnitTest::testAutoSaveSettings()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
"</MenuBar>\n"
"<ToolBar name=\"mainToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"<ToolBar name=\"secondToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"</gui>\n";
{
// do not interfere with the "toolbarVisibility" unit test
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("testAutoSaveSettings"));
TestXmlGuiWindow mw(xml, "kxmlgui_unittest.rc");
mw.show();
mw.setAutoSaveSettings(cg);
// Test resizing first (like show() does).
mw.reallyResize(400, 400);
QTest::qWait(200);
mw.createActions(QStringList() << QStringLiteral("go_up"));
mw.createGUI();
// Resize again, should be saved
mw.reallyResize(800, 600);
QTest::qWait(200);
KToolBar *mainToolBar = mw.toolBarByName(QStringLiteral("mainToolBar"));
QCOMPARE(mw.toolBarArea(mainToolBar), Qt::TopToolBarArea);
KToolBar *secondToolBar = mw.toolBarByName(QStringLiteral("secondToolBar"));
QCOMPARE((int)mw.toolBarArea(secondToolBar), (int)Qt::TopToolBarArea); // REFERENCE #1 (see below)
// Move second toolbar to bottom
const QPoint oldPos = secondToolBar->pos();
mw.addToolBar(Qt::BottomToolBarArea, secondToolBar);
const QPoint newPos = secondToolBar->pos();
QCOMPARE(mw.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
// Calling to addToolBar is not enough to trigger the event filter for move events
// in KMainWindow, because there is no layouting happening in hidden mainwindows.
QMoveEvent moveEvent(newPos, oldPos);
QApplication::sendEvent(secondToolBar, &moveEvent);
mw.close();
}
{
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("testAutoSaveSettings"));
TestXmlGuiWindow mw2(xml, "kxmlgui_unittest.rc");
mw2.show();
mw2.setAutoSaveSettings(cg);
QTest::qWait(200);
// Check window size was restored
QCOMPARE(mw2.size(), QSize(800, 600));
mw2.createActions(QStringList() << QStringLiteral("go_up"));
mw2.createGUI();
// Force window layout to happen
mw2.reallyResize(800, 600);
QTest::qWait(200);
// Check toolbar positions were restored
KToolBar *mainToolBar = mw2.toolBarByName(QStringLiteral("mainToolBar"));
QCOMPARE(mw2.toolBarArea(mainToolBar), Qt::TopToolBarArea);
KToolBar *secondToolBar = mw2.toolBarByName(QStringLiteral("secondToolBar"));
QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
mw2.applyMainWindowSettings(mw2.autoSaveConfigGroup());
QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
}
}
void KXmlGui_UnitTest::testDeletedContainers() // deleted="true"
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu deleted=\"true\" name=\"game\"/>\n"
"</MenuBar>\n"
"<ToolBar deleted=\"true\" name=\"mainToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"<ToolBar name=\"visibleToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"<ToolBar deleted=\"true\" name=\"deletedToolBar\">\n"
" <Action name=\"go_up\"/>\n"
"</ToolBar>\n"
"</gui>\n";
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("testDeletedToolBar"));
TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
mainWindow.setAutoSaveSettings(cg);
mainWindow.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("file_new") << QStringLiteral("game_new"));
mainWindow.createGUI();
KXMLGUIFactory *factory = mainWindow.guiFactory();
// qDebug() << "containers:" << factory->containers("ToolBar");
QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow));
QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow));
QVERIFY(factory->container(QStringLiteral("file"), &mainWindow)); // File menu was created
QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow)); // Game menu was not created
// Now open KEditToolBar, just to check it doesn't crash.
KEditToolBar editToolBar(factory);
// KEditToolBar loads the stuff in showEvent...
QShowEvent ev;
qApp->sendEvent(&editToolBar, &ev);
clickApply(&editToolBar);
QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow));
QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow));
QVERIFY(factory->container(QStringLiteral("file"), &mainWindow));
QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow));
mainWindow.close();
}
void KXmlGui_UnitTest::testTopLevelSeparator()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"before_separator\"><text>Before Separator</text></Menu>\n"
" <Separator />\n"
" <Menu name=\"after_separator\"><text>After Separator</text></Menu>\n"
"</MenuBar>\n"
"</gui>";
TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
mainWindow.createGUI();
checkActions(mainWindow.menuBar()->actions(),
QStringList() << QStringLiteral("before_separator") << QStringLiteral("separator") << QStringLiteral("after_separator")
<< QStringLiteral("settings") << QStringLiteral("separator") << QStringLiteral("help"));
}
// Check that the objectName() of the menus is set from the name in the XML file
void KXmlGui_UnitTest::testMenuNames()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"filemenu\"><text>File Menu</text></Menu>\n"
"</MenuBar>\n"
"</gui>";
TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
mainWindow.createGUI();
checkActions(mainWindow.menuBar()->actions(),
QStringList() << QStringLiteral("filemenu") << QStringLiteral("settings") << QStringLiteral("separator") << QStringLiteral("help"));
}
// Test what happens when the application's rc file isn't found
// We want a warning to be printed, but we don't want to see all menus from ui_standards.rc
void KXmlGui_UnitTest::testMenusNoXmlFile()
{
TestXmlGuiWindow mainWindow(QByteArray(), "nolocalfile_either.rc");
mainWindow.createGUIBad();
checkActions(mainWindow.menuBar()->actions(), QStringList{QStringLiteral("settings"), QStringLiteral("separator"), QStringLiteral("help")});
}
void KXmlGui_UnitTest::testXMLFileReplacement()
{
// to differentiate "original" and replacement xml file, one is created with "modified" toolbars
QTemporaryFile fileOrig;
QVERIFY(fileOrig.open());
createXmlFile(fileOrig, 2, AddToolBars);
const QString filenameOrig = fileOrig.fileName();
fileOrig.close();
QTemporaryFile fileReplace;
QVERIFY(fileReplace.open());
createXmlFile(fileReplace, 2, AddModifiedToolBars);
const QString filenameReplace = fileReplace.fileName();
fileReplace.close();
// finally, our local xml file has <ActionProperties/>
const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
QFile fileLocal(dir + QLatin1String("/testui.rc"));
QVERIFY2(fileLocal.open(QIODevice::WriteOnly), qPrintable(fileLocal.fileName()));
createXmlFile(fileLocal, 1, AddActionProperties);
const QString filenameLocal = fileLocal.fileName();
fileLocal.close();
TestGuiClient client;
// first make sure that the "original" file is loaded, correctly
client.setXMLFilePublic(filenameOrig);
QString xml = client.domDocument().toString();
// qDebug() << xml;
QVERIFY(xml.contains(QLatin1String("<Action name=\"print\"")));
QVERIFY(!xml.contains(QLatin1String("<Action name=\"home\"")));
QVERIFY(!xml.contains(QLatin1String("<ActionProperties>")));
// now test the replacement (+ local file)
client.replaceXMLFile(filenameReplace, filenameLocal);
xml = client.domDocument().toString();
QVERIFY(!xml.contains(QLatin1String("<Action name=\"print\"")));
QVERIFY(xml.contains(QLatin1String("<Action name=\"home\"")));
QVERIFY(xml.contains(QLatin1String("<ActionProperties>")));
// re-check after a reload
client.reloadXML();
QString reloadedXml = client.domDocument().toString();
QVERIFY(!reloadedXml.contains(QLatin1String("<Action name=\"print\"")));
QVERIFY(reloadedXml.contains(QLatin1String("<Action name=\"home\"")));
QVERIFY(reloadedXml.contains(QLatin1String("<ActionProperties>")));
// Check what happens when the local file doesn't exist
TestGuiClient client2;
QFile::remove(filenameLocal);
client2.replaceXMLFile(filenameReplace, filenameLocal);
xml = client2.domDocument().toString();
// qDebug() << xml;
QVERIFY(!xml.contains(QLatin1String("<Action name=\"print\"")));
QVERIFY(xml.contains(QLatin1String("<Action name=\"home\""))); // modified toolbars
QVERIFY(!xml.contains(QLatin1String("<ActionProperties>"))); // but no local xml file
}
void KXmlGui_UnitTest::testClientDestruction() // #170806
{
const QByteArray hostXml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"file\"><text>&amp;File</text>\n"
" </Menu>\n"
" <Merge/>\n"
"</MenuBar>\n"
"</gui>";
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"file\"><text>&amp;File</text>\n"
" <Action name=\"file_open\"/>\n"
" <Action name=\"file_quit\"/>\n"
" </Menu>\n"
"</MenuBar>\n"
"</gui>";
TestXmlGuiWindow mainWindow(hostXml, "kxmlgui_unittest.rc");
TestGuiClient *client = new TestGuiClient(xml);
client->createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit"));
mainWindow.insertChildClient(client);
mainWindow.createGUI();
const QStringList menus = {QStringLiteral("file"), QStringLiteral("settings"), QStringLiteral("separator"), QStringLiteral("help")};
checkActions(mainWindow.menuBar()->actions(), menus);
QVERIFY(mainWindow.factory()->clients().contains(client));
delete client;
QVERIFY(!mainWindow.factory()->clients().contains(client));
// No change, because deletion is fast, it doesn't do manual unplugging.
checkActions(mainWindow.menuBar()->actions(), menus);
}
void KXmlGui_UnitTest::testShortcuts()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<MenuBar>\n"
" <Menu name=\"file\"><text>&amp;File</text>\n"
" <Action name=\"file_open\" shortcut=\"Ctrl+O\"/>\n"
" <Action name=\"file_quit\" shortcut=\"Ctrl+Q; Ctrl+D\"/>\n"
" </Menu>\n"
"</MenuBar>\n"
"<ActionProperties scheme=\"Default\">\n"
" <Action shortcut=\"Ctrl+O\" name=\"file_open\"/>\n"
" <Action shortcut=\"Ctrl+Q; Ctrl+D\" name=\"file_quit\"/>\n"
"</ActionProperties>\n"
"</gui>";
TestGuiClient client;
client.createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit"));
client.createGUI(xml, false /*ui_standards.rc*/);
KMainWindow mainWindow;
KXMLGUIBuilder builder(&mainWindow);
KXMLGUIFactory factory(&builder);
factory.addClient(&client);
QAction *actionOpen = client.action(QStringLiteral("file_open"));
QAction *actionQuit = client.action(QStringLiteral("file_quit"));
QVERIFY(actionOpen && actionQuit);
QCOMPARE(actionOpen->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+O")));
// #345411
QCOMPARE(actionQuit->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+Q")) << QKeySequence(QStringLiteral("Ctrl+D")));
factory.removeClient(&client);
}
void KXmlGui_UnitTest::testPopupMenuParent()
{
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"1\" name=\"foo\" >\n"
"<Menu name=\"foo\"><text>Foo</text></Menu>\n"
"</gui>";
TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
mainWindow.createGUI();
auto popupMenu = mainWindow.menuByName(QStringLiteral("foo"));
QVERIFY(popupMenu);
QCOMPARE(popupMenu->parent(), &mainWindow);
}
void KXmlGui_UnitTest::testSpecificApplicationLanguageQLocale()
{
qunsetenv("LC_ALL"); // In case the user sets the LC_ALL ENV variable this test could fail
const QLocale originalSystemLocale = QLocale::system();
KDEPrivate::setApplicationSpecificLanguage("ru_RU");
KDEPrivate::initializeLanguages();
#ifdef Q_OS_WIN
QEXPECT_FAIL("",
"KDEPrivate::initializeLanguages assumes that setting $LANGUAGE will have an effect, this isn't the case on Windows. Needs to be fixed.",
Abort);
#endif
QCOMPARE(QLocale::system().language(), QLocale::Russian);
KDEPrivate::setApplicationSpecificLanguage("wa");
KDEPrivate::initializeLanguages();
QCOMPARE(QLocale::system().language(), QLocale::Walloon);
KDEPrivate::setApplicationSpecificLanguage(QByteArray());
KDEPrivate::initializeLanguages();
QCOMPARE(QLocale::system(), originalSystemLocale);
}
void KXmlGui_UnitTest::testSingleModifierQKeySequenceEndsWithPlus()
{
// Check that native texts of the Meta, Alt, Control, Shift, Keypad modifiers end in "+"
// we depend on that in KKeySequenceWidgetPrivate::updateShortcutDisplay()
QVERIFY(QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
QVERIFY(QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
QVERIFY(QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
QVERIFY(QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
QVERIFY(QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
}
void KXmlGui_UnitTest::testSaveShortcutsAndRefresh()
{
QTemporaryFile xmlFile;
QVERIFY(xmlFile.open());
createXmlFile(xmlFile, 2, AddModifiedMenus);
const QString filename = xmlFile.fileName();
xmlFile.close();
QTemporaryFile localXmlFile;
QVERIFY(localXmlFile.open());
const QString localFilename = localXmlFile.fileName();
localXmlFile.close();
TestGuiClient client;
client.createActions({QStringLiteral("file_open")});
client.setLocalXMLFilePublic(localFilename);
client.setXMLFilePublic(filename);
QWidget w;
KXMLGUIBuilder builder(&w);
KXMLGUIFactory factory(&builder);
factory.addClient(&client);
factory.removeClient(&client);
factory.addClient(&client);
QAction *a = client.actionCollection()->action(QStringLiteral("file_open"));
QCOMPARE(a->shortcut(), QKeySequence());
a->setShortcut(Qt::Key_F22);
QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
client.actionCollection()->writeSettings();
factory.refreshActionProperties();
a = client.actionCollection()->action(QStringLiteral("file_open"));
QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
}
void KXmlGui_UnitTest::testSaveShortcutsAndRefreshNoXmlFile()
{
KSharedConfig::openConfig()->deleteGroup(QStringLiteral("Shortcuts"));
const QByteArray xml =
"<?xml version = '1.0'?>\n"
"<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
"<gui version=\"2\" name=\"foo\" >\n"
"<MenuBar>\n"
"<Menu noMerge=\"1\" name=\"file\" >\n"
"<text>&amp;File</text>\n"
"<Action name=\"file_open\" />\n"
"</Menu>\n"
"</MenuBar>\n"
"</gui>\n";
TestGuiClient client;
client.createActions({QStringLiteral("file_open")});
client.createGUI(xml, false);
QWidget w;
KXMLGUIBuilder builder(&w);
KXMLGUIFactory factory(&builder);
factory.addClient(&client);
factory.removeClient(&client);
factory.addClient(&client);
QAction *a = client.actionCollection()->action(QStringLiteral("file_open"));
QCOMPARE(a->shortcut(), QKeySequence());
a->setShortcut(Qt::Key_F22);
QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
client.actionCollection()->writeSettings();
factory.refreshActionProperties();
a = client.actionCollection()->action(QStringLiteral("file_open"));
QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
}
#include "moc_kxmlgui_unittest.cpp"