1 /*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2007-2009 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "kxmlgui_unittest.h"
9 #include "testguiclient.h"
10 #include "testxmlguiwindow.h"
11
12 #include <QAction>
13 #include <QDebug>
14 #include <QDialogButtonBox>
15 #include <QDir>
16 #include <QMenuBar>
17 #include <QPushButton>
18 #include <QShowEvent>
19 #include <QTest>
20
21 #include <KConfigGroup>
22 #include <KSharedConfig>
23
24 #include <kedittoolbar.h>
25 #include <kswitchlanguagedialog_p.h>
26 #include <kxmlguibuilder.h>
27 #include <kxmlguiclient.h>
28 #include <kxmlguiversionhandler.cpp> // it's not exported, so we need to include the code here
29
30 QTEST_MAIN(KXmlGui_UnitTest)
31
32 enum Flags {
33 NoFlags = 0,
34 AddToolBars = 1,
35 AddModifiedToolBars = 2,
36 AddActionProperties = 4,
37 AddModifiedMenus = 8,
38 // next item is 16
39 };
40
createXmlFile(QFile & file,int version,int flags,const QByteArray & toplevelTag="gui")41 static void createXmlFile(QFile &file, int version, int flags, const QByteArray &toplevelTag = "gui")
42 {
43 const QByteArray xml =
44 "<?xml version = '1.0'?>\n"
45 "<!DOCTYPE " + toplevelTag + " SYSTEM \"kpartgui.dtd\">\n"
46 "<" + toplevelTag + " version=\"" + QByteArray::number(version) + "\" name=\"foo\" >\n"
47 "<MenuBar>\n";
48 file.write(xml);
49 if (flags & AddModifiedMenus) {
50 file.write(
51 "<Menu noMerge=\"1\" name=\"file\" >\n"
52 "<text>&File</text>\n"
53 "<Action name=\"file_open\" />\n"
54 "</Menu>\n");
55 }
56 file.write("</MenuBar>\n");
57 if (flags & AddToolBars) {
58 file.write(
59 "<ToolBar name=\"mainToolBar\">\n"
60 " <text>Main Toolbar</text>\n"
61 " <Action name=\"print\" />\n"
62 "</ToolBar>\n"
63 "<ToolBar name=\"bookmarkToolBar\">\n"
64 " <text>Bookmark Toolbar</text>\n"
65 "</ToolBar>\n"
66 "<ToolBar name=\"newToolBar\">\n"
67 " <text>New Toolbar</text>\n"
68 "</ToolBar>\n");
69 }
70 if (flags & AddModifiedToolBars) {
71 file.write(
72 "<ToolBar name=\"mainToolBar\">\n"
73 " <text>Main Toolbar</text>\n"
74 " <Action name=\"home\" />\n"
75 "</ToolBar>\n"
76 "<ToolBar name=\"bookmarkToolBar\">\n"
77 " <text>Modified toolbars</text>\n"
78 "</ToolBar>\n");
79 }
80 if (flags & AddActionProperties) {
81 file.write(
82 "<ActionProperties>\n"
83 " <Action shortcut=\"F9\" name=\"konq_sidebartng\" />\n"
84 "</ActionProperties>\n");
85 }
86 file.write("</" + toplevelTag + ">\n");
87 }
88
clickApply(KEditToolBar * dialog)89 static void clickApply(KEditToolBar *dialog)
90 {
91 QDialogButtonBox *box = dialog->findChild<QDialogButtonBox *>();
92 Q_ASSERT(box != nullptr);
93 box->button(QDialogButtonBox::Apply)->setEnabled(true);
94 box->button(QDialogButtonBox::Apply)->click();
95 }
96
initTestCase()97 void KXmlGui_UnitTest::initTestCase()
98 {
99 QStandardPaths::setTestModeEnabled(true);
100 // Leftover configuration breaks testAutoSaveSettings
101 const QString configFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, KSharedConfig::openConfig()->name());
102 if (!configFile.isEmpty()) {
103 qDebug() << "Removing old config file";
104 QFile::remove(configFile);
105 KSharedConfig::openConfig()->reparseConfiguration();
106 }
107 }
108
testFindVersionNumber_data()109 void KXmlGui_UnitTest::testFindVersionNumber_data()
110 {
111 QTest::addColumn<QString>("xml");
112 QTest::addColumn<QString>("version");
113
114 QTest::newRow("simple test") << "<?xml version = '1.0'?>\n"
115 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
116 "<gui version=\"3\" name=\"foo\"/>\n"
117 << "3";
118 QTest::newRow("two digits") << "<?xml version = '1.0'?>\n"
119 "<gui version=\"42\" name=\"foo\"/>\n"
120 << "42";
121 QTest::newRow("with spaces") << // as found in dirfilterplugin.rc for instance
122 "<?xml version = '1.0'?>\n"
123 "<gui version = \"1\" name=\"foo\"/>\n"
124 << "1";
125 QTest::newRow("with a dot") << // as was found in autorefresh.rc
126 "<?xml version = '1.0'?>\n"
127 "<gui version = \"0.2\" name=\"foo\"/>\n"
128 << QString() /*error*/;
129 QTest::newRow("with a comment") << // as found in kmail.rc
130 "<!DOCTYPE gui>\n"
131 "<!-- This file should be synchronized with kmail_part.rc to provide\n"
132 "the same menu entries at the same place in KMail and Kontact -->\n"
133 "<gui version=\"452\" name=\"kmmainwin\">\n"
134 << "452";
135 }
136
testFindVersionNumber()137 void KXmlGui_UnitTest::testFindVersionNumber()
138 {
139 QFETCH(QString, xml);
140 QFETCH(QString, version);
141 QCOMPARE(KXMLGUIClient::findVersionNumber(xml), version);
142 }
143
testVersionHandlerSameVersion()144 void KXmlGui_UnitTest::testVersionHandlerSameVersion()
145 {
146 // This emulates the case where the user has modified stuff locally
147 // and the application hasn't changed since, so the version number is unchanged.
148 QTemporaryFile userFile;
149 QVERIFY(userFile.open());
150 createXmlFile(userFile, 2, AddActionProperties | AddModifiedToolBars);
151 const QString firstFile = userFile.fileName();
152
153 QTemporaryFile appFile;
154 QVERIFY(appFile.open());
155 createXmlFile(appFile, 2, AddToolBars);
156 const QString secondFile = appFile.fileName();
157
158 QStringList files;
159 files << firstFile << secondFile;
160
161 userFile.close();
162 appFile.close();
163
164 KXmlGuiVersionHandler versionHandler(files);
165 QCOMPARE(versionHandler.finalFile(), firstFile);
166 QString finalDoc = versionHandler.finalDocument();
167 QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
168 // Check that the shortcuts defined by the user were kept
169 QVERIFY(finalDoc.contains(QLatin1String("<ActionProperties>")));
170 QVERIFY(finalDoc.contains(QLatin1String("sidebartng")));
171 // Check that the toolbars modified by the user were kept
172 QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"home\"")));
173 // and that the toolbar that isn't in the local file, isn't there in the GUI
174 QVERIFY(!finalDoc.contains(QLatin1String("<ToolBar name=\"newToolBar\"")));
175
176 QVERIFY(userFile.open());
177 const QString userFileContents = QString::fromUtf8(userFile.readAll());
178 QCOMPARE(finalDoc, userFileContents);
179 }
180
testVersionHandlerNewVersionNothingKept()181 void KXmlGui_UnitTest::testVersionHandlerNewVersionNothingKept()
182 {
183 // This emulates the case where the application has been upgraded
184 // and the user has a local ui.rc file, but without shortcuts or toolbar changes.
185 // Not sure how this can happen - would be a menu-only app and hand editing !?
186 // Anyway the point is to test version number comparison :)
187
188 QMap<QString, int> fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames
189
190 const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
191 QDir().mkpath(dir);
192 QFile fileV2(dir + QStringLiteral("/testui.rc"));
193 QVERIFY2(fileV2.open(QIODevice::WriteOnly), qPrintable(fileV2.fileName()));
194 createXmlFile(fileV2, 2, NoFlags);
195 fileToVersionMap.insert(fileV2.fileName(), 2);
196
197 QTemporaryFile fileV5;
198 QVERIFY(fileV5.open());
199 createXmlFile(fileV5, 5, NoFlags);
200 fileToVersionMap.insert(fileV5.fileName(), 5);
201
202 // The highest version is neither the first nor last one in the list,
203 // to make sure the code really selects the highest version, not just by chance :)
204 // (This is why we add the v1 version at the end of the list)
205 QTemporaryFile fileV1;
206 QVERIFY(fileV1.open());
207 createXmlFile(fileV1, 1, NoFlags);
208 fileToVersionMap.insert(fileV1.fileName(), 1);
209
210 QStringList files;
211 files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName();
212
213 fileV2.close();
214 fileV5.close();
215 fileV1.close();
216
217 KXmlGuiVersionHandler versionHandler(files);
218 QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 5);
219 QString finalDoc = versionHandler.finalDocument();
220 QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
221 QVERIFY(finalDoc.contains(QLatin1String("version=\"5\"")));
222
223 QVERIFY(fileV5.open());
224 const QString fileV5Contents = QString::fromUtf8(fileV5.readAll());
225 QCOMPARE(finalDoc, fileV5Contents);
226 }
227
testVersionHandlerNewVersionUserChanges()228 void KXmlGui_UnitTest::testVersionHandlerNewVersionUserChanges()
229 {
230 // This emulates the case where the application has been upgraded
231 // after the user has changed shortcuts and toolbars
232
233 QMap<QString, int> fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames
234
235 // local file
236 const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
237 QFile fileV2(dir + QLatin1String("/testui.rc"));
238 QVERIFY(fileV2.open(QIODevice::WriteOnly));
239 createXmlFile(fileV2, 2, AddActionProperties | AddModifiedToolBars);
240 fileToVersionMap.insert(fileV2.fileName(), 2);
241
242 // more-global (application) file
243 QTemporaryFile fileV5;
244 QVERIFY(fileV5.open());
245 createXmlFile(fileV5, 5, AddToolBars | AddModifiedMenus, "kpartgui");
246 fileToVersionMap.insert(fileV5.fileName(), 5);
247
248 // The highest version is neither the first nor last one in the list,
249 // to make sure the code really selects the highest version, not just by chance :)
250 // (This is why we add the v1 version at the end of the list)
251 QTemporaryFile fileV1;
252 QVERIFY(fileV1.open());
253 createXmlFile(fileV1, 1, AddToolBars);
254 fileToVersionMap.insert(fileV1.fileName(), 1);
255
256 QStringList files;
257 files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName();
258
259 fileV2.close();
260 fileV5.close();
261 fileV1.close();
262
263 KXmlGuiVersionHandler versionHandler(files);
264 // We end up with the local file, so in our map it has version 2.
265 // But of course by now it says "version=5" in it :)
266 QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 2);
267 const QString finalDoc = versionHandler.finalDocument();
268 // qDebug() << finalDoc;
269 QVERIFY(finalDoc.startsWith(QLatin1String("<?xml")));
270 QVERIFY(finalDoc.contains(QLatin1String("version=\"5\"")));
271 // Check that the shortcuts defined by the user were kept
272 QVERIFY(finalDoc.contains(QLatin1String("<ActionProperties>")));
273 QVERIFY(finalDoc.contains(QLatin1String("sidebartng")));
274 // Check that the menus modified by the app are still there
275 QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"file_open\"")));
276 // Check that the toolbars modified by the user were kept
277 QVERIFY(finalDoc.contains(QLatin1String("<Action name=\"home\"")));
278 // Check that the toolbars added by the application were kept (https://invent.kde.org/graphics/okular/-/merge_requests/197)
279 QVERIFY(finalDoc.contains(QLatin1String("<ToolBar name=\"newToolBar\"")));
280 }
281
collectMenuNames(KXMLGUIFactory & factory)282 static QStringList collectMenuNames(KXMLGUIFactory &factory)
283 {
284 const QList<QWidget *> containers = factory.containers(QStringLiteral("Menu"));
285 QStringList containerNames;
286 for (QWidget *w : containers) {
287 containerNames << w->objectName();
288 }
289 return containerNames;
290 }
291
debugActions(const QList<QAction * > & actions)292 void debugActions(const QList<QAction *> &actions)
293 {
294 for (QAction *action : actions) {
295 qDebug() << (action->isSeparator() ? QString::fromLatin1("separator") : action->objectName());
296 }
297 }
298
checkActions(const QList<QAction * > & actions,const QStringList & expectedActions)299 static void checkActions(const QList<QAction *> &actions, const QStringList &expectedActions)
300 {
301 for (int i = 0; i < expectedActions.count(); ++i) {
302 if (i >= actions.count()) {
303 break;
304 }
305 QAction *action = actions.at(i);
306 if (action->isSeparator()) {
307 QCOMPARE(QStringLiteral("separator"), expectedActions[i]);
308 } else {
309 QCOMPARE(action->objectName(), expectedActions[i]);
310 }
311 }
312 QCOMPARE(actions.count(), expectedActions.count());
313 }
314
testPartMerging()315 void KXmlGui_UnitTest::testPartMerging()
316 {
317 const QByteArray hostXml =
318 "<?xml version = '1.0'?>\n"
319 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
320 "<gui version=\"1\" name=\"foo\" >\n"
321 "<MenuBar>\n"
322 " <Menu name=\"go\"><text>&Go</text>\n"
323 " <!-- go_up, go_back, go_forward, go_home, separator: coming from ui_standards.rc -->\n"
324 " <DefineGroup name=\"before_merge\"/>\n"
325 " <Merge/>\n"
326 " <Action name=\"host_after_merge\"/>\n"
327 " <Action name=\"host_after_merge_2\"/>\n"
328 " <Separator/>\n"
329 " <DefineGroup name=\"new_merge\"/>\n"
330 " <Title>Section title</Title>\n"
331 " <Action name=\"last_from_host\"/>\n"
332 " </Menu>\n"
333 " <Menu name=\"file\"><text>&File</text>\n"
334 " <DefineGroup name=\"placed_merge\" append=\"new_merge\"/>\n"
335 " </Menu>\n"
336 "</MenuBar>\n"
337 "</gui>\n";
338
339 TestGuiClient hostClient;
340 hostClient.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("go_back") << QStringLiteral("go_forward") << QStringLiteral("go_home")
341 << QStringLiteral("host_after_merge") << QStringLiteral("host_after_merge_2") << QStringLiteral("last_from_host")
342 << QStringLiteral("file_new") << QStringLiteral("file_open") << QStringLiteral("file_quit"));
343 hostClient.createGUI(hostXml, true /*ui_standards.rc*/);
344 KMainWindow mainWindow;
345 KXMLGUIBuilder builder(&mainWindow);
346 KXMLGUIFactory factory(&builder);
347 factory.addClient(&hostClient);
348
349 const QString hostDomDoc = hostClient.domDocument().toString();
350
351 QWidget *goMenuW = factory.container(QStringLiteral("go"), &hostClient);
352 QVERIFY(goMenuW);
353 QMenu *goMenu = qobject_cast<QMenu *>(goMenuW);
354 QVERIFY(goMenu);
355 QMenu *fileMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("file"), &hostClient));
356
357 // debugActions(goMenu->actions());
358 // clang-format off
359 checkActions(goMenu->actions(), QStringList()
360 << QStringLiteral("go_up")
361 << QStringLiteral("go_back")
362 << QStringLiteral("go_forward")
363 << QStringLiteral("go_home")
364 << QStringLiteral("separator")
365 << QStringLiteral("host_after_merge")
366 << QStringLiteral("host_after_merge_2")
367 << QStringLiteral("separator")
368 << QStringLiteral("separator") // <Title> separator
369 << QStringLiteral("last_from_host"));
370 checkActions(fileMenu->actions(), QStringList()
371 << QStringLiteral("file_new")
372 << QStringLiteral("file_open")
373 << QStringLiteral("separator")
374 << QStringLiteral("file_quit"));
375 // clang-format on
376
377 qDebug() << "Now merging the part";
378
379 const QByteArray partXml =
380 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
381 "<gui version=\"1\" name=\"part\" >\n"
382 "<MenuBar>\n"
383 " <Menu name=\"go\"><text>&Go</text>\n"
384 " <Action name=\"go_previous\" group=\"before_merge\"/>\n"
385 " <Action name=\"go_next\" group=\"before_merge\"/>\n"
386 " <Separator/>\n"
387 " <Action name=\"first_page\"/>\n"
388 " <Action name=\"last_page\"/>\n"
389 " <Title>Part Section</Title>\n"
390 " <ActionList name=\"action_list\"/>\n"
391 " <Action name=\"action_in_merge_group\" group=\"new_merge\"/>\n"
392 " <Action name=\"undefined_group\" group=\"no_such_merge\"/>\n"
393 " <Action name=\"last_from_part\"/>\n"
394 " </Menu>\n"
395 " <Menu name=\"file\"><text>&File</text>\n"
396 " <Action group=\"placed_merge\" name=\"action_in_placed_merge\"/>\n"
397 " <Action name=\"other_file_action\"/>\n"
398 " </Menu>\n"
399 "</MenuBar>\n"
400 "</gui>\n";
401
402 TestGuiClient partClient(partXml);
403 partClient.createActions(QStringList() << QStringLiteral("go_previous") << QStringLiteral("go_next") << QStringLiteral("first_page")
404 << QStringLiteral("last_page") << QStringLiteral("last_from_part") << QStringLiteral("action_in_merge_group")
405 << QStringLiteral("undefined_group") << QStringLiteral("action_in_placed_merge")
406 << QStringLiteral("other_file_action") << QStringLiteral("action1") << QStringLiteral("action2"));
407 const QList<QAction *> actionList = {partClient.actionCollection()->action(QStringLiteral("action1")),
408 partClient.actionCollection()->action(QStringLiteral("action2"))};
409
410 for (int i = 0; i < 5; ++i) {
411 // qDebug() << "addClient, iteration" << i;
412 factory.addClient(&partClient);
413 partClient.plugActionList(QStringLiteral("action_list"), actionList);
414
415 // clang-format off
416 //debugActions(goMenu->actions());
417 checkActions(goMenu->actions(), QStringList()
418 << QStringLiteral("go_up")
419 << QStringLiteral("go_back")
420 << QStringLiteral("go_forward")
421 << QStringLiteral("go_home")
422 << QStringLiteral("separator")
423 << QStringLiteral("go_previous")
424 << QStringLiteral("go_next")
425 // Contents of the <Merge>:
426 << QStringLiteral("separator")
427 << QStringLiteral("first_page")
428 << QStringLiteral("last_page")
429 << QStringLiteral("separator") // <title> in the part
430 << QStringLiteral("action1")
431 << QStringLiteral("action2")
432 << QStringLiteral("undefined_group")
433 << QStringLiteral("last_from_part")
434 // End of <Merge>
435 << QStringLiteral("host_after_merge")
436 << QStringLiteral("host_after_merge_2")
437 << QStringLiteral("separator")
438 // Contents of <DefineGroup>
439 << QStringLiteral("action_in_merge_group")
440 // End of <DefineGroup>
441 << QStringLiteral("separator") // <title> is a separator qaction with text
442 << QStringLiteral("last_from_host")
443 );
444 checkActions(fileMenu->actions(), QStringList()
445 << QStringLiteral("file_new")
446 << QStringLiteral("action_in_placed_merge")
447 << QStringLiteral("file_open")
448 << QStringLiteral("separator")
449 << QStringLiteral("file_quit")
450 << QStringLiteral("other_file_action"));
451 factory.removeClient(&partClient);
452 QCOMPARE(hostClient.domDocument().toString(), hostDomDoc);
453 // clang-format on
454 }
455 factory.removeClient(&hostClient);
456 }
457
testPartMergingSettings()458 void KXmlGui_UnitTest::testPartMergingSettings() // #252911
459 {
460 const QByteArray hostXml =
461 "<?xml version = '1.0'?>\n"
462 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
463 "<gui version=\"1\" name=\"foo\" >\n"
464 "<MenuBar>\n"
465 // The solution was to remove the duplicated definition
466 // " <Menu name=\"settings\"><text>&Settings</text>\n"
467 // " <Action name=\"options_configure_keybinding\"/>\n"
468 // " <Action name=\"options_configure_toolbars\"/>\n"
469 // " <Merge name=\"configure_merge\"/>\n"
470 // " <Separator/>\n"
471 // " <Merge/>\n"
472 // " </Menu>\n"
473 "</MenuBar></gui>\n";
474 TestGuiClient hostClient;
475 hostClient.createActions(QStringList() << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars"));
476 hostClient.createGUI(hostXml, true /*ui_standards.rc*/);
477 // qDebug() << hostClient.domDocument().toString();
478 KMainWindow mainWindow;
479 KXMLGUIBuilder builder(&mainWindow);
480 KXMLGUIFactory factory(&builder);
481 factory.addClient(&hostClient);
482 QWidget *settingsMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("settings"), &hostClient));
483 QVERIFY(settingsMenu);
484 // debugActions(settingsMenu->actions());
485
486 const QByteArray partXml =
487 "<?xml version = '1.0'?>\n"
488 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
489 "<gui version=\"1\" name=\"foo\" >\n"
490 "<MenuBar>\n"
491 " <Menu name=\"settings\"><text>&Settings</text>\n"
492 " <Action name=\"configure_klinkstatus\"/>\n"
493 " </Menu>\n"
494 "</MenuBar></gui>\n";
495 TestGuiClient partClient(partXml);
496 partClient.createActions(QStringList() << QStringLiteral("configure_klinkstatus"));
497 factory.addClient(&partClient);
498 // debugActions(settingsMenu->actions());
499 checkActions(settingsMenu->actions(),
500 QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it
501 << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars")
502 << QStringLiteral("configure_klinkstatus"));
503 factory.removeClient(&partClient);
504 factory.removeClient(&hostClient);
505 }
506
testUiStandardsMerging_data()507 void KXmlGui_UnitTest::testUiStandardsMerging_data()
508 {
509 QTest::addColumn<QByteArray>("xml");
510 QTest::addColumn<QStringList>("actions");
511 QTest::addColumn<QStringList>("expectedMenus");
512
513 const QByteArray xmlBegin =
514 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
515 "<gui version=\"1\" name=\"foo\" >\n"
516 "<MenuBar>\n";
517 const QByteArray xmlEnd =
518 "</MenuBar>\n"
519 "</gui>";
520
521 // Merging an empty menu (or a menu with only non-existing actions) would make
522 // the empty menu appear at the end after all other menus (fixed for KDE-4.2)
523 /* clang-format off */
524 QTest::newRow("empty file menu, implicit settings menu")
525 << QByteArray(xmlBegin + "<Menu name=\"file\"/>\n" + xmlEnd)
526 << (QStringList() << QStringLiteral("options_configure_toolbars"))
527 << (QStringList() << QStringLiteral("settings"));
528 QTest::newRow("file menu with non existing action, implicit settings menu")
529 << QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"foo\"/></Menu>\n" + xmlEnd)
530 << (QStringList() << QStringLiteral("options_configure_toolbars"))
531 << (QStringList() << QStringLiteral("settings"));
532 QTest::newRow("file menu with existing action, implicit settings menu")
533 << QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"open\"/></Menu>\n" + xmlEnd)
534 << (QStringList() << QStringLiteral("open") << QStringLiteral("options_configure_toolbars"))
535 << (QStringList() << QStringLiteral("file") << QStringLiteral("settings"));
536 QTest::newRow("implicit file and settings menu")
537 << QByteArray(xmlBegin + xmlEnd)
538 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
539 << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we could check that file_open is in the mainToolBar, too
540
541 // Check that unknown non-empty menus are added at the "MergeLocal" position (before settings).
542 QTest::newRow("foo menu added at the end")
543 << QByteArray(xmlBegin + "<Menu name=\"foo\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
544 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars") << QStringLiteral("foo_action"))
545 << (QStringList() << QStringLiteral("file") << QStringLiteral("foo") << QStringLiteral("settings"));
546
547 QTest::newRow("Bille's testcase: menu patch + menu edit")
548 << QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n"
549 + "<Menu name=\"edit\"><Action name=\"edit_foo\"/></Menu>\n" + xmlEnd)
550 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo"))
551 << (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch"));
552 QTest::newRow("Bille's testcase: menu patch + menu edit, lowercase tag")
553 << QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n"
554 + "<menu name=\"edit\"><Action name=\"edit_foo\"/></menu>\n" + xmlEnd)
555 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo"))
556 << (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch"));
557
558 // Check that <Menu append="..."> allows to insert menus at specific positions
559 QTest::newRow("Menu append")
560 << QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"settings_merge\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
561 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars")
562 << QStringLiteral("foo_action") << QStringLiteral("help_contents"))
563 << (QStringList() << QStringLiteral("file") << QStringLiteral("settings") << QStringLiteral("foo") << QStringLiteral("help"));
564 QTest::newRow("Custom first menu")
565 << QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"first_menu\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd)
566 << (QStringList() << QStringLiteral("edit_undo") << QStringLiteral("foo_action") << QStringLiteral("help_contents"))
567 << (QStringList() << QStringLiteral("foo") << QStringLiteral("edit") << QStringLiteral("help"));
568
569 // Tests for noMerge="1"
570 QTest::newRow("noMerge empty file menu, implicit settings menu")
571 << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n" + xmlEnd)
572 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
573 << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we keep empty menus, see #186382
574 QTest::newRow("noMerge empty file menu, file_open moved elsewhere")
575 << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd)
576 << (QStringList() << QStringLiteral("file_open"))
577 << (QStringList() << QStringLiteral("file") << QStringLiteral("foo"));
578 QTest::newRow("noMerge file menu with open before new")
579 << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd)
580 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new"))
581 << (QStringList() << QStringLiteral("file")); // TODO check the order of the actions in the menu? how?
582
583 // Tests for deleted="true"
584 QTest::newRow("deleted file menu, implicit settings menu")
585 << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n" + xmlEnd)
586 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars"))
587 << (QStringList() << QStringLiteral("settings"));
588 QTest::newRow("deleted file menu, file_open moved elsewhere")
589 << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd)
590 << (QStringList() << QStringLiteral("file_open"))
591 << (QStringList() << QStringLiteral("foo"));
592 QTest::newRow("deleted file menu with actions (contradiction)")
593 << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd)
594 << (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new"))
595 << (QStringList());
596 /* clang-format on */
597 }
598
testUiStandardsMerging()599 void KXmlGui_UnitTest::testUiStandardsMerging()
600 {
601 QFETCH(QByteArray, xml);
602 QFETCH(QStringList, actions);
603 QFETCH(QStringList, expectedMenus);
604
605 TestGuiClient client;
606 client.createActions(actions);
607 client.createGUI(xml, true /*ui_standards.rc*/);
608
609 const QDomDocument domDocument = client.domDocument();
610 const QDomElement docElem = domDocument.documentElement();
611 QCOMPARE(docElem.attribute(QStringLiteral("name")), QStringLiteral("foo")); // not standard_containers from ui_standards.rc
612 QCOMPARE(docElem.attribute(QStringLiteral("version")), QStringLiteral("1")); // not standard_containers from ui_standards.rc
613
614 KMainWindow mainWindow;
615 KXMLGUIBuilder builder(&mainWindow);
616 KXMLGUIFactory factory(&builder);
617 factory.addClient(&client);
618
619 const QStringList containerNames = collectMenuNames(factory);
620 // qDebug() << containerNames;
621 QCOMPARE(containerNames, expectedMenus);
622
623 factory.removeClient(&client);
624 }
625
testActionListAndSeparator()626 void KXmlGui_UnitTest::testActionListAndSeparator()
627 {
628 const QByteArray xml =
629 "<?xml version = '1.0'?>\n"
630 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
631 "<gui version=\"1\" name=\"foo\" >\n"
632 "<MenuBar>\n"
633 " <Menu name=\"groups\"><text>Add to Group</text>\n"
634 " <ActionList name=\"view_groups_list\"/>\n"
635 " <Separator />"
636 " <Action name=\"view_add_to_new_group\" />\n"
637 " <ActionList name=\"second_list\"/>\n"
638 " </Menu>\n"
639 "</MenuBar>\n"
640 "</gui>";
641
642 TestGuiClient client(xml);
643 client.createActions(QStringList() << QStringLiteral("view_add_to_new_group"));
644 KMainWindow mainWindow;
645 KXMLGUIBuilder builder(&mainWindow);
646 KXMLGUIFactory factory(&builder);
647 factory.addClient(&client);
648
649 QWidget *menuW = factory.container(QStringLiteral("groups"), &client);
650 QVERIFY(menuW);
651 QMenu *menu = qobject_cast<QMenu *>(menuW);
652 QVERIFY(menu);
653
654 // debugActions(menu->actions());
655 checkActions(menu->actions(),
656 QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it
657 << QStringLiteral("view_add_to_new_group"));
658
659 qDebug() << "Now plugging the actionlist";
660
661 QAction *action1 = new QAction(this);
662 action1->setObjectName(QStringLiteral("action1"));
663 client.actionCollection()->setDefaultShortcut(action1, QKeySequence(QStringLiteral("Ctrl+2")));
664 QAction *action2 = new QAction(this);
665 action2->setObjectName(QStringLiteral("action2"));
666 const QList<QAction *> actionList = {action1, action2};
667 client.plugActionList(QStringLiteral("view_groups_list"), actionList);
668 QCOMPARE(QKeySequence::listToString(action1->shortcuts()), QStringLiteral("Ctrl+2"));
669
670 const QStringList expectedActionsOneList = {QStringLiteral("action1"),
671 QStringLiteral("action2"),
672 QStringLiteral("separator"),
673 QStringLiteral("view_add_to_new_group")};
674 // debugActions(menu->actions());
675 checkActions(menu->actions(), expectedActionsOneList);
676
677 QAction *action3 = new QAction(this);
678 action3->setObjectName(QStringLiteral("action3"));
679 const QList<QAction *> secondActionList = {action3};
680 client.plugActionList(QStringLiteral("second_list"), secondActionList);
681 QStringList expectedActions = expectedActionsOneList;
682 expectedActions << QStringLiteral("action3");
683 checkActions(menu->actions(), expectedActions);
684
685 qDebug() << "Now remove+add gui client";
686
687 // While I'm here, what happens with the action list if I remove+add the guiclient,
688 // like KXmlGuiWindow::newToolBarConfig does?
689 factory.removeClient(&client);
690 factory.addClient(&client);
691 // We need to get the container widget again, it was re-created.
692 menuW = factory.container(QStringLiteral("groups"), &client);
693 QVERIFY(menuW);
694 menu = qobject_cast<QMenu *>(menuW);
695 // debugActions(menu->actions());
696 checkActions(menu->actions(),
697 QStringList() << QStringLiteral("separator") // yep, it removed the actionlist thing...
698 << QStringLiteral("view_add_to_new_group"));
699 qDebug() << "Now plugging the actionlist again";
700 client.plugActionList(QStringLiteral("second_list"), secondActionList);
701 client.plugActionList(QStringLiteral("view_groups_list"), actionList);
702 checkActions(menu->actions(), expectedActions);
703 factory.removeClient(&client);
704 }
705
testHiddenToolBar()706 void KXmlGui_UnitTest::testHiddenToolBar()
707 {
708 const QByteArray xml =
709 "<?xml version = '1.0'?>\n"
710 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
711 "<gui version=\"1\" name=\"foo\" >\n"
712 "<MenuBar>\n"
713 "</MenuBar>\n"
714 "<ToolBar hidden=\"true\" name=\"mainToolBar\">\n"
715 " <Action name=\"go_up\"/>\n"
716 "</ToolBar>\n"
717 "<ToolBar name=\"visibleToolBar\">\n"
718 " <Action name=\"go_up\"/>\n"
719 "</ToolBar>\n"
720 "<ToolBar hidden=\"true\" name=\"hiddenToolBar\">\n"
721 " <Action name=\"go_up\"/>\n"
722 "</ToolBar>\n"
723 "</gui>\n";
724 KConfigGroup cg(KSharedConfig::openConfig(), "testHiddenToolBar");
725 TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
726 mainWindow.setAutoSaveSettings(cg);
727 mainWindow.createActions(QStringList() << QStringLiteral("go_up"));
728 mainWindow.createGUI();
729
730 KToolBar *mainToolBar = mainWindow.toolBarByName(QStringLiteral("mainToolBar"));
731 QVERIFY(mainToolBar->isHidden());
732
733 KXMLGUIFactory *factory = mainWindow.guiFactory();
734 QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
735 KToolBar *hiddenToolBar = qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow));
736 qDebug() << hiddenToolBar;
737 QVERIFY(hiddenToolBar->isHidden());
738
739 // Now open KEditToolBar (#105525)
740 KEditToolBar editToolBar(factory);
741 // KEditToolBar loads the stuff in showEvent...
742 QShowEvent ev;
743 qApp->sendEvent(&editToolBar, &ev);
744 clickApply(&editToolBar);
745 QVERIFY(qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow))->isHidden());
746
747 mainWindow.close();
748 }
749
750 // taken from KMainWindow_UnitTest::testAutoSaveSettings()
testAutoSaveSettings()751 void KXmlGui_UnitTest::testAutoSaveSettings()
752 {
753 const QByteArray xml =
754 "<?xml version = '1.0'?>\n"
755 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
756 "<gui version=\"1\" name=\"foo\" >\n"
757 "<MenuBar>\n"
758 "</MenuBar>\n"
759 "<ToolBar name=\"mainToolBar\">\n"
760 " <Action name=\"go_up\"/>\n"
761 "</ToolBar>\n"
762 "<ToolBar name=\"secondToolBar\">\n"
763 " <Action name=\"go_up\"/>\n"
764 "</ToolBar>\n"
765 "</gui>\n";
766 {
767 // do not interfere with the "toolbarVisibility" unit test
768 KConfigGroup cg(KSharedConfig::openConfig(), "testAutoSaveSettings");
769 TestXmlGuiWindow mw(xml, "kxmlgui_unittest.rc");
770 mw.show();
771 mw.setAutoSaveSettings(cg);
772
773 // Test resizing first (like show() does).
774 mw.reallyResize(400, 400);
775 QTest::qWait(200);
776
777 mw.createActions(QStringList() << QStringLiteral("go_up"));
778 mw.createGUI();
779
780 // Resize again, should be saved
781 mw.reallyResize(800, 600);
782 QTest::qWait(200);
783
784 KToolBar *mainToolBar = mw.toolBarByName(QStringLiteral("mainToolBar"));
785 QCOMPARE(mw.toolBarArea(mainToolBar), Qt::TopToolBarArea);
786 KToolBar *secondToolBar = mw.toolBarByName(QStringLiteral("secondToolBar"));
787 QCOMPARE((int)mw.toolBarArea(secondToolBar), (int)Qt::TopToolBarArea); // REFERENCE #1 (see below)
788
789 // Move second toolbar to bottom
790 const QPoint oldPos = secondToolBar->pos();
791 mw.addToolBar(Qt::BottomToolBarArea, secondToolBar);
792 const QPoint newPos = secondToolBar->pos();
793 QCOMPARE(mw.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
794 // Calling to addToolBar is not enough to trigger the event filter for move events
795 // in KMainWindow, because there is no layouting happening in hidden mainwindows.
796 QMoveEvent moveEvent(newPos, oldPos);
797 QApplication::sendEvent(secondToolBar, &moveEvent);
798
799 mw.close();
800 }
801
802 {
803 KConfigGroup cg(KSharedConfig::openConfig(), "testAutoSaveSettings");
804 TestXmlGuiWindow mw2(xml, "kxmlgui_unittest.rc");
805 mw2.show();
806 mw2.setAutoSaveSettings(cg);
807 QTest::qWait(200);
808 // Check window size was restored
809 QCOMPARE(mw2.size(), QSize(800, 600));
810
811 mw2.createActions(QStringList() << QStringLiteral("go_up"));
812 mw2.createGUI();
813
814 // Force window layout to happen
815 mw2.reallyResize(800, 600);
816 QTest::qWait(200);
817
818 // Check toolbar positions were restored
819 KToolBar *mainToolBar = mw2.toolBarByName(QStringLiteral("mainToolBar"));
820 QCOMPARE(mw2.toolBarArea(mainToolBar), Qt::TopToolBarArea);
821 KToolBar *secondToolBar = mw2.toolBarByName(QStringLiteral("secondToolBar"));
822 QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
823 mw2.applyMainWindowSettings(mw2.autoSaveConfigGroup());
824 QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea);
825 }
826 }
827
testDeletedContainers()828 void KXmlGui_UnitTest::testDeletedContainers() // deleted="true"
829 {
830 const QByteArray xml =
831 "<?xml version = '1.0'?>\n"
832 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
833 "<gui version=\"1\" name=\"foo\" >\n"
834 "<MenuBar>\n"
835 " <Menu deleted=\"true\" name=\"game\"/>\n"
836 "</MenuBar>\n"
837 "<ToolBar deleted=\"true\" name=\"mainToolBar\">\n"
838 " <Action name=\"go_up\"/>\n"
839 "</ToolBar>\n"
840 "<ToolBar name=\"visibleToolBar\">\n"
841 " <Action name=\"go_up\"/>\n"
842 "</ToolBar>\n"
843 "<ToolBar deleted=\"true\" name=\"deletedToolBar\">\n"
844 " <Action name=\"go_up\"/>\n"
845 "</ToolBar>\n"
846 "</gui>\n";
847 KConfigGroup cg(KSharedConfig::openConfig(), "testDeletedToolBar");
848 TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
849 mainWindow.setAutoSaveSettings(cg);
850 mainWindow.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("file_new") << QStringLiteral("game_new"));
851 mainWindow.createGUI();
852 KXMLGUIFactory *factory = mainWindow.guiFactory();
853
854 // qDebug() << "containers:" << factory->containers("ToolBar");
855 QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow));
856 QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
857 QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow));
858 QVERIFY(factory->container(QStringLiteral("file"), &mainWindow)); // File menu was created
859 QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow)); // Game menu was not created
860
861 // Now open KEditToolBar, just to check it doesn't crash.
862 KEditToolBar editToolBar(factory);
863 // KEditToolBar loads the stuff in showEvent...
864 QShowEvent ev;
865 qApp->sendEvent(&editToolBar, &ev);
866 clickApply(&editToolBar);
867 QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow));
868 QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden());
869 QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow));
870 QVERIFY(factory->container(QStringLiteral("file"), &mainWindow));
871 QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow));
872
873 mainWindow.close();
874 }
875
testTopLevelSeparator()876 void KXmlGui_UnitTest::testTopLevelSeparator()
877 {
878 const QByteArray xml =
879 "<?xml version = '1.0'?>\n"
880 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
881 "<gui version=\"1\" name=\"foo\" >\n"
882 "<MenuBar>\n"
883 " <Menu name=\"before_separator\"><text>Before Separator</text></Menu>\n"
884 " <Separator />\n"
885 " <Menu name=\"after_separator\"><text>After Separator</text></Menu>\n"
886 "</MenuBar>\n"
887 "</gui>";
888
889 TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
890 mainWindow.createGUI();
891
892 checkActions(mainWindow.menuBar()->actions(),
893 QStringList() << QStringLiteral("before_separator") << QStringLiteral("separator") << QStringLiteral("after_separator")
894 << QStringLiteral("settings") << QStringLiteral("separator") << QStringLiteral("help"));
895 }
896
897 // Check that the objectName() of the menus is set from the name in the XML file
testMenuNames()898 void KXmlGui_UnitTest::testMenuNames()
899 {
900 const QByteArray xml =
901 "<?xml version = '1.0'?>\n"
902 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
903 "<gui version=\"1\" name=\"foo\" >\n"
904 "<MenuBar>\n"
905 " <Menu name=\"filemenu\"><text>File Menu</text></Menu>\n"
906 "</MenuBar>\n"
907 "</gui>";
908
909 TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
910 mainWindow.createGUI();
911
912 checkActions(mainWindow.menuBar()->actions(),
913 QStringList() << QStringLiteral("filemenu") << QStringLiteral("settings") << QStringLiteral("separator") << QStringLiteral("help"));
914 }
915
916 // Test what happens when the application's rc file isn't found
917 // We want a warning to be printed, but we don't want to see all menus from ui_standards.rc
testMenusNoXmlFile()918 void KXmlGui_UnitTest::testMenusNoXmlFile()
919 {
920 TestXmlGuiWindow mainWindow(QByteArray(), "nolocalfile_either.rc");
921 mainWindow.createGUIBad();
922
923 checkActions(mainWindow.menuBar()->actions(), QStringList{QStringLiteral("settings"), QStringLiteral("separator"), QStringLiteral("help")});
924 }
925
testXMLFileReplacement()926 void KXmlGui_UnitTest::testXMLFileReplacement()
927 {
928 // to differentiate "original" and replacement xml file, one is created with "modified" toolbars
929 QTemporaryFile fileOrig;
930 QVERIFY(fileOrig.open());
931 createXmlFile(fileOrig, 2, AddToolBars);
932 const QString filenameOrig = fileOrig.fileName();
933 fileOrig.close();
934
935 QTemporaryFile fileReplace;
936 QVERIFY(fileReplace.open());
937 createXmlFile(fileReplace, 2, AddModifiedToolBars);
938 const QString filenameReplace = fileReplace.fileName();
939 fileReplace.close();
940
941 // finally, our local xml file has <ActionProperties/>
942 const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui_unittest");
943 QFile fileLocal(dir + QLatin1String("/testui.rc"));
944 QVERIFY2(fileLocal.open(QIODevice::WriteOnly), qPrintable(fileLocal.fileName()));
945 createXmlFile(fileLocal, 1, AddActionProperties);
946 const QString filenameLocal = fileLocal.fileName();
947 fileLocal.close();
948
949 TestGuiClient client;
950 // first make sure that the "original" file is loaded, correctly
951 client.setXMLFilePublic(filenameOrig);
952 QString xml = client.domDocument().toString();
953 // qDebug() << xml;
954 QVERIFY(xml.contains(QLatin1String("<Action name=\"print\"")));
955 QVERIFY(!xml.contains(QLatin1String("<Action name=\"home\"")));
956 QVERIFY(!xml.contains(QLatin1String("<ActionProperties>")));
957
958 // now test the replacement (+ local file)
959 client.replaceXMLFile(filenameReplace, filenameLocal);
960 xml = client.domDocument().toString();
961 QVERIFY(!xml.contains(QLatin1String("<Action name=\"print\"")));
962 QVERIFY(xml.contains(QLatin1String("<Action name=\"home\"")));
963 QVERIFY(xml.contains(QLatin1String("<ActionProperties>")));
964
965 // re-check after a reload
966 client.reloadXML();
967 QString reloadedXml = client.domDocument().toString();
968 QVERIFY(!reloadedXml.contains(QLatin1String("<Action name=\"print\"")));
969 QVERIFY(reloadedXml.contains(QLatin1String("<Action name=\"home\"")));
970 QVERIFY(reloadedXml.contains(QLatin1String("<ActionProperties>")));
971
972 // Check what happens when the local file doesn't exist
973 TestGuiClient client2;
974 QFile::remove(filenameLocal);
975 client2.replaceXMLFile(filenameReplace, filenameLocal);
976 xml = client2.domDocument().toString();
977 // qDebug() << xml;
978 QVERIFY(!xml.contains(QLatin1String("<Action name=\"print\"")));
979 QVERIFY(xml.contains(QLatin1String("<Action name=\"home\""))); // modified toolbars
980 QVERIFY(!xml.contains(QLatin1String("<ActionProperties>"))); // but no local xml file
981 }
982
testClientDestruction()983 void KXmlGui_UnitTest::testClientDestruction() // #170806
984 {
985 const QByteArray hostXml =
986 "<?xml version = '1.0'?>\n"
987 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
988 "<gui version=\"1\" name=\"foo\" >\n"
989 "<MenuBar>\n"
990 " <Menu name=\"file\"><text>&File</text>\n"
991 " </Menu>\n"
992 " <Merge/>\n"
993 "</MenuBar>\n"
994 "</gui>";
995 const QByteArray xml =
996 "<?xml version = '1.0'?>\n"
997 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
998 "<gui version=\"1\" name=\"foo\" >\n"
999 "<MenuBar>\n"
1000 " <Menu name=\"file\"><text>&File</text>\n"
1001 " <Action name=\"file_open\"/>\n"
1002 " <Action name=\"file_quit\"/>\n"
1003 " </Menu>\n"
1004 "</MenuBar>\n"
1005 "</gui>";
1006
1007 TestXmlGuiWindow mainWindow(hostXml, "kxmlgui_unittest.rc");
1008 TestGuiClient *client = new TestGuiClient(xml);
1009 client->createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit"));
1010 mainWindow.insertChildClient(client);
1011 mainWindow.createGUI();
1012
1013 const QStringList menus = {QStringLiteral("file"), QStringLiteral("settings"), QStringLiteral("separator"), QStringLiteral("help")};
1014 checkActions(mainWindow.menuBar()->actions(), menus);
1015
1016 QVERIFY(mainWindow.factory()->clients().contains(client));
1017 delete client;
1018 QVERIFY(!mainWindow.factory()->clients().contains(client));
1019
1020 // No change, because deletion is fast, it doesn't do manual unplugging.
1021 checkActions(mainWindow.menuBar()->actions(), menus);
1022 }
1023
testShortcuts()1024 void KXmlGui_UnitTest::testShortcuts()
1025 {
1026 const QByteArray xml =
1027 "<?xml version = '1.0'?>\n"
1028 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
1029 "<gui version=\"1\" name=\"foo\" >\n"
1030 "<MenuBar>\n"
1031 " <Menu name=\"file\"><text>&File</text>\n"
1032 " <Action name=\"file_open\" shortcut=\"Ctrl+O\"/>\n"
1033 " <Action name=\"file_quit\" shortcut=\"Ctrl+Q; Ctrl+D\"/>\n"
1034 " </Menu>\n"
1035 "</MenuBar>\n"
1036 "<ActionProperties scheme=\"Default\">\n"
1037 " <Action shortcut=\"Ctrl+O\" name=\"file_open\"/>\n"
1038 " <Action shortcut=\"Ctrl+Q; Ctrl+D\" name=\"file_quit\"/>\n"
1039 "</ActionProperties>\n"
1040 "</gui>";
1041
1042 TestGuiClient client;
1043 client.createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit"));
1044 client.createGUI(xml, false /*ui_standards.rc*/);
1045
1046 KMainWindow mainWindow;
1047 KXMLGUIBuilder builder(&mainWindow);
1048 KXMLGUIFactory factory(&builder);
1049 factory.addClient(&client);
1050
1051 QAction *actionOpen = client.action("file_open");
1052 QAction *actionQuit = client.action("file_quit");
1053 QVERIFY(actionOpen && actionQuit);
1054 QCOMPARE(actionOpen->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+O")));
1055 // #345411
1056 QCOMPARE(actionQuit->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+Q")) << QKeySequence(QStringLiteral("Ctrl+D")));
1057
1058 factory.removeClient(&client);
1059 }
1060
testPopupMenuParent()1061 void KXmlGui_UnitTest::testPopupMenuParent()
1062 {
1063 const QByteArray xml =
1064 "<?xml version = '1.0'?>\n"
1065 "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n"
1066 "<gui version=\"1\" name=\"foo\" >\n"
1067 "<Menu name=\"foo\"><text>Foo</text></Menu>\n"
1068 "</gui>";
1069
1070 TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc");
1071 mainWindow.createGUI();
1072
1073 auto popupMenu = mainWindow.menuByName(QStringLiteral("foo"));
1074 QVERIFY(popupMenu);
1075 QCOMPARE(popupMenu->parent(), &mainWindow);
1076 }
1077
testSpecificApplicationLanguageQLocale()1078 void KXmlGui_UnitTest::testSpecificApplicationLanguageQLocale()
1079 {
1080 qunsetenv("LC_ALL"); // In case the user sets the LC_ALL ENV variable this test could fail
1081 const QLocale originalSystemLocale = QLocale::system();
1082
1083 KDEPrivate::setApplicationSpecificLanguage("ru_RU");
1084 KDEPrivate::initializeLanguages();
1085
1086 #ifdef Q_OS_WIN
1087 QEXPECT_FAIL("",
1088 "KDEPrivate::initializeLanguages assumes that setting $LANGUAGE will have an effect, this isn't the case on Windows. Needs to be fixed.",
1089 Abort);
1090 #endif
1091 QCOMPARE(QLocale::system().language(), QLocale::Russian);
1092
1093 KDEPrivate::setApplicationSpecificLanguage("wa");
1094 KDEPrivate::initializeLanguages();
1095
1096 QCOMPARE(QLocale::system().language(), QLocale::Walloon);
1097
1098 KDEPrivate::setApplicationSpecificLanguage(QByteArray());
1099 KDEPrivate::initializeLanguages();
1100
1101 QCOMPARE(QLocale::system(), originalSystemLocale);
1102 }
1103
testSingleModifierQKeySequenceEndsWithPlus()1104 void KXmlGui_UnitTest::testSingleModifierQKeySequenceEndsWithPlus()
1105 {
1106 // Check that native texts of the Meta, Alt, Control, Shift, Keypad modifiers end in "+"
1107 // we depend on that in KKeySequenceWidgetPrivate::updateShortcutDisplay()
1108 QVERIFY(QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1109 QVERIFY(QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1110 QVERIFY(QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1111 QVERIFY(QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1112 QVERIFY(QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText).endsWith(QLatin1Char('+')));
1113 }
1114
testSaveShortcutsAndRefresh()1115 void KXmlGui_UnitTest::testSaveShortcutsAndRefresh()
1116 {
1117 QTemporaryFile xmlFile;
1118 QVERIFY(xmlFile.open());
1119 createXmlFile(xmlFile, 2, AddModifiedMenus);
1120 const QString filename = xmlFile.fileName();
1121 xmlFile.close();
1122
1123 QTemporaryFile localXmlFile;
1124 QVERIFY(localXmlFile.open());
1125 const QString localFilename = localXmlFile.fileName();
1126 localXmlFile.close();
1127
1128 TestGuiClient client;
1129 client.createActions({QStringLiteral("file_open")});
1130 client.setLocalXMLFilePublic(localFilename);
1131 client.setXMLFilePublic(filename);
1132
1133 QWidget w;
1134 KXMLGUIBuilder builder(&w);
1135 KXMLGUIFactory factory(&builder);
1136 factory.addClient(&client);
1137 factory.removeClient(&client);
1138 factory.addClient(&client);
1139
1140 QAction *a = client.actionCollection()->action(QStringLiteral("file_open"));
1141 QCOMPARE(a->shortcut(), QKeySequence());
1142 a->setShortcut(Qt::Key_F22);
1143 QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
1144
1145 client.actionCollection()->writeSettings();
1146
1147 factory.refreshActionProperties();
1148
1149 a = client.actionCollection()->action(QStringLiteral("file_open"));
1150 QCOMPARE(a->shortcut(), QKeySequence(Qt::Key_F22));
1151 }
1152