1 /********************************************************************
2 Copyright © 2016  Martin Gräßlin <mgraesslin@kde.org>
3 Copyright © 2020 Roman Gilg <subdiff@gmail.com>
4 
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) version 3, or any
9 later version accepted by the membership of KDE e.V. (or its
10 successor approved by the membership of KDE e.V.), which shall
11 act as a proxy defined in Section 6 of version 3 of the license.
12 
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 Lesser General Public License for more details.
17 
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20 *********************************************************************/
21 #include "../../src/client/connection_thread.h"
22 #include "../../src/client/event_queue.h"
23 #include "../../src/client/plasmawindowmanagement.h"
24 #include "../../src/client/plasmawindowmodel.h"
25 #include "../../src/client/registry.h"
26 
27 #include "../../server/display.h"
28 #include "../../server/plasma_virtual_desktop.h"
29 #include "../../server/plasma_window.h"
30 
31 #include <QtTest>
32 
33 #include <linux/input.h>
34 
35 namespace Clt = Wrapland::Client;
36 namespace Srv = Wrapland::Server;
37 
38 Q_DECLARE_METATYPE(Qt::MouseButton)
39 
40 typedef void (Clt::PlasmaWindow::*ClientWindowSignal)();
41 Q_DECLARE_METATYPE(ClientWindowSignal)
42 
43 typedef void (Srv::PlasmaWindow::*ServerWindowBoolSetter)(bool);
44 Q_DECLARE_METATYPE(ServerWindowBoolSetter)
45 
46 typedef void (Srv::PlasmaWindow::*ServerWindowStringSetter)(const QString&);
47 Q_DECLARE_METATYPE(ServerWindowStringSetter)
48 
49 typedef void (Srv::PlasmaWindow::*ServerWindowQuint32Setter)(quint32);
50 Q_DECLARE_METATYPE(ServerWindowQuint32Setter)
51 
52 typedef void (Srv::PlasmaWindow::*ServerWindowVoidSetter)();
53 Q_DECLARE_METATYPE(ServerWindowVoidSetter)
54 
55 typedef void (Srv::PlasmaWindow::*ServerWindowIconSetter)(const QIcon&);
56 Q_DECLARE_METATYPE(ServerWindowIconSetter)
57 
58 class PlasmaWindowModelTest : public QObject
59 {
60     Q_OBJECT
61 private Q_SLOTS:
62     void init();
63     void cleanup();
64 
65     void testRoleNames_data();
66     void testRoleNames();
67 
68     void testAddRemoveRows();
69 
70     void testDefaultData_data();
71     void testDefaultData();
72 
73     void testIsActive();
74     void testIsFullscreenable();
75     void testIsFullscreen();
76     void testIsMaximizable();
77     void testIsMaximized();
78     void testIsMinimizable();
79     void testIsMinimized();
80     void testIsKeepAbove();
81     void testIsKeepBelow();
82 
83     void testIsDemandingAttention();
84     void testSkipTaskbar();
85     void testSkipSwitcher();
86 
87     void testIsShadeable();
88     void testIsShaded();
89     void testIsMovable();
90     void testIsResizable();
91     void testIsVirtualDesktopChangeable();
92     void testIsCloseable();
93 
94     void testGeometry();
95     void testTitle();
96     void testAppId();
97     void testPid();
98     void testVirtualDesktops();
99 
100     // TODO icon: can we ensure a theme is installed on CI?
101     void testRequests();
102 
103     // TODO: minimized geometry
104     // TODO: model reset
105     void testCreateWithUnmappedWindow();
106     void testChangeWindowAfterModelDestroy_data();
107     void testChangeWindowAfterModelDestroy();
108     void testCreateWindowAfterModelDestroy();
109 
110 private:
111     bool testBooleanData(Clt::PlasmaWindowModel::AdditionalRoles role,
112                          void (Srv::PlasmaWindow::*function)(bool));
113 
114     Srv::Display* m_display = nullptr;
115 
116     Srv::PlasmaWindowManager* m_serverWindowManager = nullptr;
117     Clt::PlasmaWindowManagement* m_pw = nullptr;
118 
119     Srv::PlasmaVirtualDesktopManager* m_serverPlasmaVirtualDesktopManager = nullptr;
120 
121     Clt::ConnectionThread* m_connection = nullptr;
122     QThread* m_thread = nullptr;
123     Clt::EventQueue* m_queue = nullptr;
124 };
125 
126 static const QString s_socketName = QStringLiteral("wrapland-test-fake-input-0");
127 
init()128 void PlasmaWindowModelTest::init()
129 {
130     delete m_display;
131     m_display = new Srv::Display(this);
132     m_display->setSocketName(s_socketName);
133     m_display->start();
134     QVERIFY(m_display->running());
135 
136     m_display->createShm();
137     m_serverWindowManager = m_display->createPlasmaWindowManager();
138 
139     m_serverPlasmaVirtualDesktopManager = m_display->createPlasmaVirtualDesktopManager(m_display);
140 
141     m_serverPlasmaVirtualDesktopManager->createDesktop("desktop1");
142     m_serverPlasmaVirtualDesktopManager->createDesktop("desktop2");
143     m_serverWindowManager->setVirtualDesktopManager(m_serverPlasmaVirtualDesktopManager);
144 
145     // Setup connection.
146     m_connection = new Clt::ConnectionThread;
147     QSignalSpy connectedSpy(m_connection, &Clt::ConnectionThread::establishedChanged);
148     QVERIFY(connectedSpy.isValid());
149     m_connection->setSocketName(s_socketName);
150 
151     m_thread = new QThread(this);
152     m_connection->moveToThread(m_thread);
153     m_thread->start();
154 
155     m_connection->establishConnection();
156     QVERIFY(connectedSpy.count() || connectedSpy.wait());
157     QCOMPARE(connectedSpy.count(), 1);
158 
159     m_queue = new Clt::EventQueue(this);
160     m_queue->setup(m_connection);
161 
162     Clt::Registry registry;
163     QSignalSpy interfacesAnnouncedSpy(&registry, &Clt::Registry::interfacesAnnounced);
164     QVERIFY(interfacesAnnouncedSpy.isValid());
165 
166     registry.setEventQueue(m_queue);
167     registry.create(m_connection);
168     QVERIFY(registry.isValid());
169 
170     registry.setup();
171     QVERIFY(interfacesAnnouncedSpy.wait());
172 
173     m_pw = registry.createPlasmaWindowManagement(
174         registry.interface(Clt::Registry::Interface::PlasmaWindowManagement).name,
175         registry.interface(Clt::Registry::Interface::PlasmaWindowManagement).version,
176         this);
177     QVERIFY(m_pw->isValid());
178 }
179 
cleanup()180 void PlasmaWindowModelTest::cleanup()
181 {
182 #define CLEANUP(variable)                                                                          \
183     if (variable) {                                                                                \
184         delete variable;                                                                           \
185         variable = nullptr;                                                                        \
186     }
187     CLEANUP(m_pw)
188     CLEANUP(m_serverPlasmaVirtualDesktopManager)
189     CLEANUP(m_queue)
190     if (m_connection) {
191         m_connection->deleteLater();
192         m_connection = nullptr;
193     }
194     if (m_thread) {
195         m_thread->quit();
196         m_thread->wait();
197         delete m_thread;
198         m_thread = nullptr;
199     }
200 
201     CLEANUP(m_serverWindowManager)
202     CLEANUP(m_display)
203 #undef CLEANUP
204 }
205 
testBooleanData(Clt::PlasmaWindowModel::AdditionalRoles role,void (Srv::PlasmaWindow::* function)(bool))206 bool PlasmaWindowModelTest::testBooleanData(Clt::PlasmaWindowModel::AdditionalRoles role,
207                                             void (Srv::PlasmaWindow::*function)(bool))
208 {
209 #define VERIFY(statement)                                                                          \
210     if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))                          \
211         return false;
212 #define COMPARE(actual, expected)                                                                  \
213     if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))                \
214         return false;
215 
216     auto* model = m_pw->createWindowModel();
217     VERIFY(model);
218 
219     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
220     VERIFY(rowInsertedSpy.isValid());
221 
222     QSignalSpy initSpy(m_pw, &Clt::PlasmaWindowManagement::windowCreated);
223     VERIFY(initSpy.isValid());
224     QSignalSpy dataChangedSpy(model, &Clt::PlasmaWindowModel::dataChanged);
225     VERIFY(dataChangedSpy.isValid());
226 
227     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
228     VERIFY(serverWindow);
229 
230     COMPARE(dataChangedSpy.count(), 0);
231     COMPARE(initSpy.count(), 0);
232 
233     VERIFY(rowInsertedSpy.wait());
234     COMPARE(initSpy.count(), 1);
235 
236     // Wait for the first event announcing the icon on resource creation.
237     VERIFY(dataChangedSpy.count() == 1 || dataChangedSpy.wait());
238     COMPARE(dataChangedSpy.count(), 1);
239     COMPARE(initSpy.count(), 1);
240 
241     // The current API is not very well defined in regards to which evente we can except when.
242     // Make sure we do not get additional data changed signals before actually setting the data.
243     VERIFY(!dataChangedSpy.wait(100));
244 
245     const QModelIndex index = model->index(0);
246     COMPARE(model->data(index, role).toBool(), false);
247 
248     (serverWindow->*(function))(true);
249 
250     VERIFY(dataChangedSpy.wait());
251     COMPARE(dataChangedSpy.count(), 2);
252 
253     // Check that there is only one data changed signal we receive.
254     VERIFY(!dataChangedSpy.wait(100));
255 
256     COMPARE(dataChangedSpy.last().first().toModelIndex(), index);
257     COMPARE(dataChangedSpy.last().last().value<QVector<int>>(), QVector<int>{int(role)});
258     COMPARE(model->data(index, role).toBool(), true);
259 
260     (serverWindow->*(function))(false);
261     VERIFY(dataChangedSpy.wait());
262     COMPARE(dataChangedSpy.count(), 3);
263 
264     COMPARE(dataChangedSpy.last().first().toModelIndex(), index);
265     COMPARE(dataChangedSpy.last().last().value<QVector<int>>(), QVector<int>{int(role)});
266     COMPARE(model->data(index, role).toBool(), false);
267 
268 #undef COMPARE
269 #undef VERIFY
270     return true;
271 }
272 
testRoleNames_data()273 void PlasmaWindowModelTest::testRoleNames_data()
274 {
275     QTest::addColumn<int>("role");
276     QTest::addColumn<QByteArray>("name");
277 
278     QTest::newRow("display") << int(Qt::DisplayRole) << QByteArrayLiteral("DisplayRole");
279     QTest::newRow("decoration") << int(Qt::DecorationRole) << QByteArrayLiteral("DecorationRole");
280 
281     QTest::newRow("AppId") << int(Clt::PlasmaWindowModel::AppId) << QByteArrayLiteral("AppId");
282     QTest::newRow("Pid") << int(Clt::PlasmaWindowModel::Pid) << QByteArrayLiteral("Pid");
283     QTest::newRow("IsActive") << int(Clt::PlasmaWindowModel::IsActive)
284                               << QByteArrayLiteral("IsActive");
285     QTest::newRow("IsFullscreenable")
286         << int(Clt::PlasmaWindowModel::IsFullscreenable) << QByteArrayLiteral("IsFullscreenable");
287     QTest::newRow("IsFullscreen") << int(Clt::PlasmaWindowModel::IsFullscreen)
288                                   << QByteArrayLiteral("IsFullscreen");
289     QTest::newRow("IsMaximizable")
290         << int(Clt::PlasmaWindowModel::IsMaximizable) << QByteArrayLiteral("IsMaximizable");
291     QTest::newRow("IsMaximized") << int(Clt::PlasmaWindowModel::IsMaximized)
292                                  << QByteArrayLiteral("IsMaximized");
293     QTest::newRow("IsMinimizable")
294         << int(Clt::PlasmaWindowModel::IsMinimizable) << QByteArrayLiteral("IsMinimizable");
295     QTest::newRow("IsMinimized") << int(Clt::PlasmaWindowModel::IsMinimized)
296                                  << QByteArrayLiteral("IsMinimized");
297     QTest::newRow("IsKeepAbove") << int(Clt::PlasmaWindowModel::IsKeepAbove)
298                                  << QByteArrayLiteral("IsKeepAbove");
299     QTest::newRow("IsKeepBelow") << int(Clt::PlasmaWindowModel::IsKeepBelow)
300                                  << QByteArrayLiteral("IsKeepBelow");
301     QTest::newRow("IsOnAllDesktops")
302         << int(Clt::PlasmaWindowModel::IsOnAllDesktops) << QByteArrayLiteral("IsOnAllDesktops");
303     QTest::newRow("IsDemandingAttention") << int(Clt::PlasmaWindowModel::IsDemandingAttention)
304                                           << QByteArrayLiteral("IsDemandingAttention");
305     QTest::newRow("SkipTaskbar") << int(Clt::PlasmaWindowModel::SkipTaskbar)
306                                  << QByteArrayLiteral("SkipTaskbar");
307     QTest::newRow("SkipSwitcher") << int(Clt::PlasmaWindowModel::SkipSwitcher)
308                                   << QByteArrayLiteral("SkipSwitcher");
309     QTest::newRow("IsShadeable") << int(Clt::PlasmaWindowModel::IsShadeable)
310                                  << QByteArrayLiteral("IsShadeable");
311     QTest::newRow("IsShaded") << int(Clt::PlasmaWindowModel::IsShaded)
312                               << QByteArrayLiteral("IsShaded");
313     QTest::newRow("IsMovable") << int(Clt::PlasmaWindowModel::IsMovable)
314                                << QByteArrayLiteral("IsMovable");
315     QTest::newRow("IsResizable") << int(Clt::PlasmaWindowModel::IsResizable)
316                                  << QByteArrayLiteral("IsResizable");
317     QTest::newRow("IsVirtualDesktopChangeable")
318         << int(Clt::PlasmaWindowModel::IsVirtualDesktopChangeable)
319         << QByteArrayLiteral("IsVirtualDesktopChangeable");
320     QTest::newRow("IsCloseable") << int(Clt::PlasmaWindowModel::IsCloseable)
321                                  << QByteArrayLiteral("IsCloseable");
322     QTest::newRow("Geometry") << int(Clt::PlasmaWindowModel::Geometry)
323                               << QByteArrayLiteral("Geometry");
324 }
325 
testRoleNames()326 void PlasmaWindowModelTest::testRoleNames()
327 {
328     // Just verifies that all role names are available.
329     auto* model = m_pw->createWindowModel();
330     QVERIFY(model);
331 
332     const QHash<int, QByteArray> roles = model->roleNames();
333 
334     QFETCH(int, role);
335     auto it = roles.find(role);
336     QVERIFY(it != roles.end());
337     QTEST(it.value(), "name");
338 }
339 
testAddRemoveRows()340 void PlasmaWindowModelTest::testAddRemoveRows()
341 {
342     // This test verifies that adding/removing rows to the Model works.
343     auto* model = m_pw->createWindowModel();
344     QVERIFY(model);
345 
346     QCOMPARE(model->rowCount(), 0);
347     QVERIFY(!model->index(0).isValid());
348 
349     // Now let's add a row.
350     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
351     QVERIFY(rowInsertedSpy.isValid());
352 
353     // This happens by creating a PlasmaWindow on server side.
354     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
355     QVERIFY(serverWindow);
356 
357     QVERIFY(rowInsertedSpy.wait());
358     QCOMPARE(rowInsertedSpy.count(), 1);
359     QVERIFY(!rowInsertedSpy.first().at(0).toModelIndex().isValid());
360     QCOMPARE(rowInsertedSpy.first().at(1).toInt(), 0);
361     QCOMPARE(rowInsertedSpy.first().at(2).toInt(), 0);
362 
363     // The model should have a row now.
364     QCOMPARE(model->rowCount(), 1);
365     QVERIFY(model->index(0).isValid());
366     // That index doesn't have children.
367     QCOMPARE(model->rowCount(model->index(0)), 0);
368 
369     // Process events in order to ensure that the resource is created on server side before we
370     // unmap the window.
371     QCoreApplication::instance()->processEvents(QEventLoop::WaitForMoreEvents);
372 
373     // Now let's remove that again.
374     QSignalSpy rowRemovedSpy(model, &Clt::PlasmaWindowModel::rowsRemoved);
375     QVERIFY(rowRemovedSpy.isValid());
376 
377     QSignalSpy windowDestroyedSpy(serverWindow, &QObject::destroyed);
378     QVERIFY(windowDestroyedSpy.isValid());
379 
380     serverWindow->unmap();
381     QVERIFY(rowRemovedSpy.wait());
382     QCOMPARE(rowRemovedSpy.count(), 1);
383     QVERIFY(!rowRemovedSpy.first().at(0).toModelIndex().isValid());
384     QCOMPARE(rowRemovedSpy.first().at(1).toInt(), 0);
385     QCOMPARE(rowRemovedSpy.first().at(2).toInt(), 0);
386 
387     // Now the model is empty again.
388     QCOMPARE(model->rowCount(), 0);
389     QVERIFY(!model->index(0).isValid());
390 
391     QCOMPARE(windowDestroyedSpy.count(), 1);
392 }
393 
testDefaultData_data()394 void PlasmaWindowModelTest::testDefaultData_data()
395 {
396     QTest::addColumn<int>("role");
397     QTest::addColumn<QVariant>("value");
398 
399     QTest::newRow("display") << int(Qt::DisplayRole) << QVariant(QString());
400     QTest::newRow("decoration") << int(Qt::DecorationRole) << QVariant(QIcon());
401 
402     QTest::newRow("AppId") << int(Clt::PlasmaWindowModel::AppId) << QVariant(QString());
403     QTest::newRow("IsActive") << int(Clt::PlasmaWindowModel::IsActive) << QVariant(false);
404     QTest::newRow("IsFullscreenable")
405         << int(Clt::PlasmaWindowModel::IsFullscreenable) << QVariant(false);
406     QTest::newRow("IsFullscreen") << int(Clt::PlasmaWindowModel::IsFullscreen) << QVariant(false);
407     QTest::newRow("IsMaximizable") << int(Clt::PlasmaWindowModel::IsMaximizable) << QVariant(false);
408     QTest::newRow("IsMaximized") << int(Clt::PlasmaWindowModel::IsMaximized) << QVariant(false);
409     QTest::newRow("IsMinimizable") << int(Clt::PlasmaWindowModel::IsMinimizable) << QVariant(false);
410     QTest::newRow("IsMinimized") << int(Clt::PlasmaWindowModel::IsMinimized) << QVariant(false);
411     QTest::newRow("IsKeepAbove") << int(Clt::PlasmaWindowModel::IsKeepAbove) << QVariant(false);
412     QTest::newRow("IsKeepBelow") << int(Clt::PlasmaWindowModel::IsKeepBelow) << QVariant(false);
413     QTest::newRow("IsOnAllDesktops")
414         << int(Clt::PlasmaWindowModel::IsOnAllDesktops) << QVariant(true);
415     QTest::newRow("IsDemandingAttention")
416         << int(Clt::PlasmaWindowModel::IsDemandingAttention) << QVariant(false);
417     QTest::newRow("IsShadeable") << int(Clt::PlasmaWindowModel::IsShadeable) << QVariant(false);
418     QTest::newRow("IsShaded") << int(Clt::PlasmaWindowModel::IsShaded) << QVariant(false);
419     QTest::newRow("SkipTaskbar") << int(Clt::PlasmaWindowModel::SkipTaskbar) << QVariant(false);
420     QTest::newRow("IsMovable") << int(Clt::PlasmaWindowModel::IsMovable) << QVariant(false);
421     QTest::newRow("IsResizable") << int(Clt::PlasmaWindowModel::IsResizable) << QVariant(false);
422     QTest::newRow("IsVirtualDesktopChangeable")
423         << int(Clt::PlasmaWindowModel::IsVirtualDesktopChangeable) << QVariant(false);
424     QTest::newRow("IsCloseable") << int(Clt::PlasmaWindowModel::IsCloseable) << QVariant(false);
425     QTest::newRow("Geometry") << int(Clt::PlasmaWindowModel::Geometry) << QVariant(QRect());
426     QTest::newRow("Pid") << int(Clt::PlasmaWindowModel::Pid) << QVariant(0);
427 }
428 
testDefaultData()429 void PlasmaWindowModelTest::testDefaultData()
430 {
431     // This test validates the default data of a PlasmaWindow without having set any values.
432     // First create a model with a window.
433     auto* model = m_pw->createWindowModel();
434     QVERIFY(model);
435 
436     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
437     QVERIFY(rowInsertedSpy.isValid());
438 
439     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
440     QVERIFY(serverWindow);
441     QVERIFY(rowInsertedSpy.wait());
442 
443     QModelIndex index = model->index(0);
444     QFETCH(int, role);
445     QTEST(model->data(index, role), "value");
446 }
447 
testIsActive()448 void PlasmaWindowModelTest::testIsActive()
449 {
450     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsActive, &Srv::PlasmaWindow::setActive));
451 }
452 
testIsFullscreenable()453 void PlasmaWindowModelTest::testIsFullscreenable()
454 {
455     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsFullscreenable,
456                             &Srv::PlasmaWindow::setFullscreenable));
457 }
458 
testIsFullscreen()459 void PlasmaWindowModelTest::testIsFullscreen()
460 {
461     QVERIFY(
462         testBooleanData(Clt::PlasmaWindowModel::IsFullscreen, &Srv::PlasmaWindow::setFullscreen));
463 }
464 
testIsMaximizable()465 void PlasmaWindowModelTest::testIsMaximizable()
466 {
467     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsMaximizable,
468                             &Srv::PlasmaWindow::setMaximizeable));
469 }
470 
testIsMaximized()471 void PlasmaWindowModelTest::testIsMaximized()
472 {
473     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsMaximized, &Srv::PlasmaWindow::setMaximized));
474 }
475 
testIsMinimizable()476 void PlasmaWindowModelTest::testIsMinimizable()
477 {
478     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsMinimizable,
479                             &Srv::PlasmaWindow::setMinimizeable));
480 }
481 
testIsMinimized()482 void PlasmaWindowModelTest::testIsMinimized()
483 {
484     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsMinimized, &Srv::PlasmaWindow::setMinimized));
485 }
486 
testIsKeepAbove()487 void PlasmaWindowModelTest::testIsKeepAbove()
488 {
489     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsKeepAbove, &Srv::PlasmaWindow::setKeepAbove));
490 }
491 
testIsKeepBelow()492 void PlasmaWindowModelTest::testIsKeepBelow()
493 {
494     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsKeepBelow, &Srv::PlasmaWindow::setKeepBelow));
495 }
496 
testIsDemandingAttention()497 void PlasmaWindowModelTest::testIsDemandingAttention()
498 {
499     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsDemandingAttention,
500                             &Srv::PlasmaWindow::setDemandsAttention));
501 }
502 
testSkipTaskbar()503 void PlasmaWindowModelTest::testSkipTaskbar()
504 {
505     QVERIFY(
506         testBooleanData(Clt::PlasmaWindowModel::SkipTaskbar, &Srv::PlasmaWindow::setSkipTaskbar));
507 }
508 
testSkipSwitcher()509 void PlasmaWindowModelTest::testSkipSwitcher()
510 {
511     QVERIFY(
512         testBooleanData(Clt::PlasmaWindowModel::SkipSwitcher, &Srv::PlasmaWindow::setSkipSwitcher));
513 }
514 
testIsShadeable()515 void PlasmaWindowModelTest::testIsShadeable()
516 {
517     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsShadeable, &Srv::PlasmaWindow::setShadeable));
518 }
519 
testIsShaded()520 void PlasmaWindowModelTest::testIsShaded()
521 {
522     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsShaded, &Srv::PlasmaWindow::setShaded));
523 }
524 
testIsMovable()525 void PlasmaWindowModelTest::testIsMovable()
526 {
527     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsMovable, &Srv::PlasmaWindow::setMovable));
528 }
529 
testIsResizable()530 void PlasmaWindowModelTest::testIsResizable()
531 {
532     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsResizable, &Srv::PlasmaWindow::setResizable));
533 }
534 
testIsVirtualDesktopChangeable()535 void PlasmaWindowModelTest::testIsVirtualDesktopChangeable()
536 {
537     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsVirtualDesktopChangeable,
538                             &Srv::PlasmaWindow::setVirtualDesktopChangeable));
539 }
540 
testIsCloseable()541 void PlasmaWindowModelTest::testIsCloseable()
542 {
543     QVERIFY(testBooleanData(Clt::PlasmaWindowModel::IsCloseable, &Srv::PlasmaWindow::setCloseable));
544 }
545 
testGeometry()546 void PlasmaWindowModelTest::testGeometry()
547 {
548     auto* model = m_pw->createWindowModel();
549     QVERIFY(model);
550 
551     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
552     QVERIFY(rowInsertedSpy.isValid());
553 
554     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
555     QVERIFY(serverWindow);
556     QVERIFY(rowInsertedSpy.wait());
557 
558     const QModelIndex index = model->index(0);
559 
560     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::Geometry).toRect(), QRect());
561 
562     QSignalSpy dataChangedSpy(model, &Clt::PlasmaWindowModel::dataChanged);
563     QVERIFY(dataChangedSpy.isValid());
564 
565     const QRect geom(0, 15, 50, 75);
566     serverWindow->setGeometry(geom);
567 
568     QVERIFY(dataChangedSpy.wait());
569 
570     // An icon and the geometry will be sent.
571     QTRY_COMPARE(dataChangedSpy.count(), 2);
572     QCOMPARE(dataChangedSpy.first().first().toModelIndex(), index);
573 
574     // The icon is received with QtConcurrent in the beginning. So it can arrive before or after
575     // the geometry.
576     const bool last
577         = dataChangedSpy[0].last().value<QVector<int>>() == QVector<int>{int(Qt::DecorationRole)};
578 
579     QCOMPARE(dataChangedSpy[last ? 1 : 0].last().value<QVector<int>>(),
580              QVector<int>{int(Clt::PlasmaWindowModel::Geometry)});
581 
582     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::Geometry).toRect(), geom);
583 }
584 
testTitle()585 void PlasmaWindowModelTest::testTitle()
586 {
587     auto* model = m_pw->createWindowModel();
588     QVERIFY(model);
589 
590     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
591     QVERIFY(rowInsertedSpy.isValid());
592 
593     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
594     QVERIFY(serverWindow);
595     QVERIFY(rowInsertedSpy.wait());
596 
597     m_connection->flush();
598     m_display->dispatchEvents();
599 
600     QSignalSpy dataChangedSpy(model, &Clt::PlasmaWindowModel::dataChanged);
601     QVERIFY(dataChangedSpy.isValid());
602 
603     const QModelIndex index = model->index(0);
604     QCOMPARE(model->data(index, Qt::DisplayRole).toString(), QString());
605 
606     serverWindow->setTitle(QStringLiteral("foo"));
607     QVERIFY(dataChangedSpy.wait());
608 
609     // An icon and the title will be sent.
610     QTRY_COMPARE(dataChangedSpy.count(), 2);
611     QCOMPARE(dataChangedSpy.first().first().toModelIndex(), index);
612 
613     // The icon is received with QtConcurrent in the beginning. So it can arrive before or after
614     // the title.
615     const bool last
616         = dataChangedSpy[0].last().value<QVector<int>>() == QVector<int>{int(Qt::DecorationRole)};
617 
618     QCOMPARE(dataChangedSpy[last ? 1 : 0].last().value<QVector<int>>(),
619              QVector<int>{int(Qt::DisplayRole)});
620     QCOMPARE(model->data(index, Qt::DisplayRole).toString(), QStringLiteral("foo"));
621 }
622 
testAppId()623 void PlasmaWindowModelTest::testAppId()
624 {
625     auto* model = m_pw->createWindowModel();
626     QVERIFY(model);
627 
628     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
629     QVERIFY(rowInsertedSpy.isValid());
630 
631     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
632     QVERIFY(serverWindow);
633     QVERIFY(rowInsertedSpy.wait());
634 
635     m_connection->flush();
636     m_display->dispatchEvents();
637 
638     QSignalSpy dataChangedSpy(model, &Clt::PlasmaWindowModel::dataChanged);
639     QVERIFY(dataChangedSpy.isValid());
640 
641     const QModelIndex index = model->index(0);
642     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::AppId).toString(), QString());
643 
644     serverWindow->setAppId(QStringLiteral("org.kde.testapp"));
645     QVERIFY(dataChangedSpy.count() || dataChangedSpy.wait());
646 
647     // The App Id and the geometry will be sent.
648     QTRY_COMPARE(dataChangedSpy.count(), 2);
649     QCOMPARE(dataChangedSpy.first().first().toModelIndex(), index);
650 
651     // The icon is received with QtConcurrent in the beginning. So it can arrive before or after
652     // the app id.
653     const bool last
654         = dataChangedSpy[0].last().value<QVector<int>>() == QVector<int>{int(Qt::DecorationRole)};
655 
656     QCOMPARE(dataChangedSpy[last ? 1 : 0].last().value<QVector<int>>(),
657              QVector<int>{int(Clt::PlasmaWindowModel::AppId)});
658     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::AppId).toString(),
659              QStringLiteral("org.kde.testapp"));
660 }
661 
testPid()662 void PlasmaWindowModelTest::testPid()
663 {
664     auto* model = m_pw->createWindowModel();
665     QVERIFY(model);
666 
667     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
668     QVERIFY(rowInsertedSpy.isValid());
669 
670     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
671     serverWindow->setPid(1337);
672     QVERIFY(serverWindow);
673 
674     m_connection->flush();
675     m_display->dispatchEvents();
676     QVERIFY(rowInsertedSpy.wait());
677 
678     // pid should be set as soon as the new row appears
679     const QModelIndex index = model->index(0);
680     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::Pid).toInt(), 1337);
681 }
682 
testVirtualDesktops()683 void PlasmaWindowModelTest::testVirtualDesktops()
684 {
685     auto* model = m_pw->createWindowModel();
686     QVERIFY(model);
687 
688     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
689     QVERIFY(rowInsertedSpy.isValid());
690     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
691     QVERIFY(serverWindow);
692     QVERIFY(rowInsertedSpy.wait());
693 
694     m_connection->flush();
695     m_display->dispatchEvents();
696     QSignalSpy dataChangedSpy(model, &Clt::PlasmaWindowModel::dataChanged);
697     QVERIFY(dataChangedSpy.isValid());
698 
699     const QModelIndex index = model->index(0);
700     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::VirtualDesktops).toStringList(),
701              QStringList());
702 
703     serverWindow->addPlasmaVirtualDesktop("desktop1");
704     QVERIFY(dataChangedSpy.wait());
705     QTRY_COMPARE(dataChangedSpy.count(), 3);
706 
707     QCOMPARE(dataChangedSpy.first().first().toModelIndex(), index);
708     QCOMPARE(dataChangedSpy.last().first().toModelIndex(), index);
709 
710     // The icon is received with QtConcurrent in the beginning. So it can arrive before or after
711     // the virtual desktop.
712     const bool last
713         = dataChangedSpy[0].last().value<QVector<int>>() == QVector<int>{int(Qt::DecorationRole)};
714 
715     QCOMPARE(dataChangedSpy[last ? 1 : 0].last().value<QVector<int>>(),
716              QVector<int>{int(Clt::PlasmaWindowModel::VirtualDesktops)});
717     QCOMPARE(dataChangedSpy[last ? 2 : 1].last().value<QVector<int>>(),
718              QVector<int>{int(Clt::PlasmaWindowModel::IsOnAllDesktops)});
719 
720     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::VirtualDesktops).toStringList(),
721              QStringList({"desktop1"}));
722     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::IsOnAllDesktops).toBool(), false);
723 
724     dataChangedSpy.clear();
725     QVERIFY(!dataChangedSpy.count());
726 
727     serverWindow->addPlasmaVirtualDesktop("desktop2");
728     QVERIFY(dataChangedSpy.wait());
729     QTRY_COMPARE(dataChangedSpy.count(), 1);
730 
731     QCOMPARE(dataChangedSpy.first().first().toModelIndex(), index);
732     QCOMPARE(dataChangedSpy.first().last().value<QVector<int>>(),
733              QVector<int>{int(Clt::PlasmaWindowModel::VirtualDesktops)});
734 
735     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::VirtualDesktops).toStringList(),
736              QStringList({"desktop1", "desktop2"}));
737     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::IsOnAllDesktops).toBool(), false);
738 
739     serverWindow->removePlasmaVirtualDesktop("desktop2");
740     serverWindow->removePlasmaVirtualDesktop("desktop1");
741 
742     QVERIFY(dataChangedSpy.wait());
743     QTRY_COMPARE(dataChangedSpy.count(), 5);
744 
745     QCOMPARE(dataChangedSpy.last().last().value<QVector<int>>(),
746              QVector<int>{int(Clt::PlasmaWindowModel::IsOnAllDesktops)});
747 
748     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::VirtualDesktops).toStringList(),
749              QStringList({}));
750     QCOMPARE(model->data(index, Clt::PlasmaWindowModel::IsOnAllDesktops).toBool(), true);
751     QVERIFY(!dataChangedSpy.wait(100));
752 }
753 
testRequests()754 void PlasmaWindowModelTest::testRequests()
755 {
756     // This test verifies that the various requests are properly passed to the server.
757     auto* model = m_pw->createWindowModel();
758     QVERIFY(model);
759 
760     QSignalSpy rowInsertedSpy(model, &Clt::PlasmaWindowModel::rowsInserted);
761     QVERIFY(rowInsertedSpy.isValid());
762 
763     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
764     QVERIFY(serverWindow);
765     QVERIFY(rowInsertedSpy.wait());
766 
767     QSignalSpy activateRequestedSpy(serverWindow, &Srv::PlasmaWindow::activeRequested);
768     QVERIFY(activateRequestedSpy.isValid());
769     QSignalSpy closeRequestedSpy(serverWindow, &Srv::PlasmaWindow::closeRequested);
770     QVERIFY(closeRequestedSpy.isValid());
771     QSignalSpy moveRequestedSpy(serverWindow, &Srv::PlasmaWindow::moveRequested);
772     QVERIFY(moveRequestedSpy.isValid());
773     QSignalSpy resizeRequestedSpy(serverWindow, &Srv::PlasmaWindow::resizeRequested);
774     QVERIFY(resizeRequestedSpy.isValid());
775     QSignalSpy keepAboveRequestedSpy(serverWindow, &Srv::PlasmaWindow::keepAboveRequested);
776     QVERIFY(keepAboveRequestedSpy.isValid());
777     QSignalSpy keepBelowRequestedSpy(serverWindow, &Srv::PlasmaWindow::keepBelowRequested);
778     QVERIFY(keepBelowRequestedSpy.isValid());
779     QSignalSpy minimizedRequestedSpy(serverWindow, &Srv::PlasmaWindow::minimizedRequested);
780     QVERIFY(minimizedRequestedSpy.isValid());
781     QSignalSpy maximizeRequestedSpy(serverWindow, &Srv::PlasmaWindow::maximizedRequested);
782     QVERIFY(maximizeRequestedSpy.isValid());
783     QSignalSpy shadeRequestedSpy(serverWindow, &Srv::PlasmaWindow::shadedRequested);
784     QVERIFY(shadeRequestedSpy.isValid());
785 
786     // First let's use some invalid row numbers.
787     model->requestActivate(-1);
788     model->requestClose(-1);
789     model->requestToggleKeepAbove(-1);
790     model->requestToggleKeepBelow(-1);
791     model->requestToggleMinimized(-1);
792     model->requestToggleMaximized(-1);
793     model->requestActivate(1);
794     model->requestClose(1);
795     model->requestMove(1);
796     model->requestResize(1);
797     model->requestToggleKeepAbove(1);
798     model->requestToggleKeepBelow(1);
799     model->requestToggleMinimized(1);
800     model->requestToggleMaximized(1);
801     model->requestToggleShaded(1);
802 
803     // That should not have triggered any signals.
804     QVERIFY(!activateRequestedSpy.wait(100));
805     QVERIFY(activateRequestedSpy.isEmpty());
806     QVERIFY(closeRequestedSpy.isEmpty());
807     QVERIFY(moveRequestedSpy.isEmpty());
808     QVERIFY(resizeRequestedSpy.isEmpty());
809     QVERIFY(minimizedRequestedSpy.isEmpty());
810     QVERIFY(maximizeRequestedSpy.isEmpty());
811     QVERIFY(shadeRequestedSpy.isEmpty());
812 
813     // Now with the proper row.
814     // Activate
815     model->requestActivate(0);
816     QVERIFY(activateRequestedSpy.wait());
817     QCOMPARE(activateRequestedSpy.count(), 1);
818     QCOMPARE(activateRequestedSpy.first().first().toBool(), true);
819     QCOMPARE(closeRequestedSpy.count(), 0);
820     QCOMPARE(moveRequestedSpy.count(), 0);
821     QCOMPARE(resizeRequestedSpy.count(), 0);
822     QCOMPARE(minimizedRequestedSpy.count(), 0);
823     QCOMPARE(maximizeRequestedSpy.count(), 0);
824     QCOMPARE(shadeRequestedSpy.count(), 0);
825     // Close
826     model->requestClose(0);
827     QVERIFY(closeRequestedSpy.wait());
828     QCOMPARE(activateRequestedSpy.count(), 1);
829     QCOMPARE(closeRequestedSpy.count(), 1);
830     QCOMPARE(moveRequestedSpy.count(), 0);
831     QCOMPARE(resizeRequestedSpy.count(), 0);
832     QCOMPARE(minimizedRequestedSpy.count(), 0);
833     QCOMPARE(maximizeRequestedSpy.count(), 0);
834     QCOMPARE(shadeRequestedSpy.count(), 0);
835     // Move
836     model->requestMove(0);
837     QVERIFY(moveRequestedSpy.wait());
838     QCOMPARE(activateRequestedSpy.count(), 1);
839     QCOMPARE(closeRequestedSpy.count(), 1);
840     QCOMPARE(moveRequestedSpy.count(), 1);
841     QCOMPARE(resizeRequestedSpy.count(), 0);
842     QCOMPARE(minimizedRequestedSpy.count(), 0);
843     QCOMPARE(maximizeRequestedSpy.count(), 0);
844     QCOMPARE(shadeRequestedSpy.count(), 0);
845     // Resize
846     model->requestResize(0);
847     QVERIFY(resizeRequestedSpy.wait());
848     QCOMPARE(activateRequestedSpy.count(), 1);
849     QCOMPARE(closeRequestedSpy.count(), 1);
850     QCOMPARE(moveRequestedSpy.count(), 1);
851     QCOMPARE(resizeRequestedSpy.count(), 1);
852     QCOMPARE(minimizedRequestedSpy.count(), 0);
853     QCOMPARE(maximizeRequestedSpy.count(), 0);
854     QCOMPARE(shadeRequestedSpy.count(), 0);
855     // Virtual desktop
856     QCOMPARE(activateRequestedSpy.count(), 1);
857     QCOMPARE(closeRequestedSpy.count(), 1);
858     QCOMPARE(moveRequestedSpy.count(), 1);
859     QCOMPARE(resizeRequestedSpy.count(), 1);
860     QCOMPARE(minimizedRequestedSpy.count(), 0);
861     QCOMPARE(maximizeRequestedSpy.count(), 0);
862     QCOMPARE(shadeRequestedSpy.count(), 0);
863     // Keep above
864     model->requestToggleKeepAbove(0);
865     QVERIFY(keepAboveRequestedSpy.wait());
866     QCOMPARE(keepAboveRequestedSpy.count(), 1);
867     QCOMPARE(keepAboveRequestedSpy.first().first().toBool(), true);
868     QCOMPARE(activateRequestedSpy.count(), 1);
869     QCOMPARE(closeRequestedSpy.count(), 1);
870     QCOMPARE(moveRequestedSpy.count(), 1);
871     QCOMPARE(resizeRequestedSpy.count(), 1);
872     QCOMPARE(maximizeRequestedSpy.count(), 0);
873     QCOMPARE(shadeRequestedSpy.count(), 0);
874     // Keep below
875     model->requestToggleKeepBelow(0);
876     QVERIFY(keepBelowRequestedSpy.wait());
877     QCOMPARE(keepBelowRequestedSpy.count(), 1);
878     QCOMPARE(keepBelowRequestedSpy.first().first().toBool(), true);
879     QCOMPARE(activateRequestedSpy.count(), 1);
880     QCOMPARE(closeRequestedSpy.count(), 1);
881     QCOMPARE(moveRequestedSpy.count(), 1);
882     QCOMPARE(resizeRequestedSpy.count(), 1);
883     QCOMPARE(maximizeRequestedSpy.count(), 0);
884     QCOMPARE(shadeRequestedSpy.count(), 0);
885     // Minimize
886     model->requestToggleMinimized(0);
887     QVERIFY(minimizedRequestedSpy.wait());
888     QCOMPARE(minimizedRequestedSpy.count(), 1);
889     QCOMPARE(minimizedRequestedSpy.first().first().toBool(), true);
890     QCOMPARE(activateRequestedSpy.count(), 1);
891     QCOMPARE(closeRequestedSpy.count(), 1);
892     QCOMPARE(moveRequestedSpy.count(), 1);
893     QCOMPARE(resizeRequestedSpy.count(), 1);
894     QCOMPARE(maximizeRequestedSpy.count(), 0);
895     QCOMPARE(shadeRequestedSpy.count(), 0);
896     // Maximize
897     model->requestToggleMaximized(0);
898     QVERIFY(maximizeRequestedSpy.wait());
899     QCOMPARE(maximizeRequestedSpy.count(), 1);
900     QCOMPARE(maximizeRequestedSpy.first().first().toBool(), true);
901     QCOMPARE(activateRequestedSpy.count(), 1);
902     QCOMPARE(closeRequestedSpy.count(), 1);
903     QCOMPARE(moveRequestedSpy.count(), 1);
904     QCOMPARE(minimizedRequestedSpy.count(), 1);
905     QCOMPARE(shadeRequestedSpy.count(), 0);
906     // Shade
907     model->requestToggleShaded(0);
908     QVERIFY(shadeRequestedSpy.wait());
909     QCOMPARE(shadeRequestedSpy.count(), 1);
910     QCOMPARE(shadeRequestedSpy.first().first().toBool(), true);
911     QCOMPARE(activateRequestedSpy.count(), 1);
912     QCOMPARE(closeRequestedSpy.count(), 1);
913     QCOMPARE(moveRequestedSpy.count(), 1);
914     QCOMPARE(resizeRequestedSpy.count(), 1);
915     QCOMPARE(minimizedRequestedSpy.count(), 1);
916     QCOMPARE(maximizeRequestedSpy.count(), 1);
917 
918     // The toggles can also support a different state.
919     QSignalSpy dataChangedSpy(model, &Clt::PlasmaWindowModel::dataChanged);
920     QVERIFY(dataChangedSpy.isValid());
921     // Keep above
922     serverWindow->setKeepAbove(true);
923     QVERIFY(dataChangedSpy.wait());
924     model->requestToggleKeepAbove(0);
925     QVERIFY(keepAboveRequestedSpy.wait());
926     QCOMPARE(keepAboveRequestedSpy.count(), 2);
927     QCOMPARE(keepAboveRequestedSpy.last().first().toBool(), false);
928     // Keep below
929     serverWindow->setKeepBelow(true);
930     QVERIFY(dataChangedSpy.wait());
931     model->requestToggleKeepBelow(0);
932     QVERIFY(keepBelowRequestedSpy.wait());
933     QCOMPARE(keepBelowRequestedSpy.count(), 2);
934     QCOMPARE(keepBelowRequestedSpy.last().first().toBool(), false);
935     // Minimize
936     serverWindow->setMinimized(true);
937     QVERIFY(dataChangedSpy.wait());
938     model->requestToggleMinimized(0);
939     QVERIFY(minimizedRequestedSpy.wait());
940     QCOMPARE(minimizedRequestedSpy.count(), 2);
941     QCOMPARE(minimizedRequestedSpy.last().first().toBool(), false);
942     // Maximized
943     serverWindow->setMaximized(true);
944     QVERIFY(dataChangedSpy.wait());
945     model->requestToggleMaximized(0);
946     QVERIFY(maximizeRequestedSpy.wait());
947     QCOMPARE(maximizeRequestedSpy.count(), 2);
948     QCOMPARE(maximizeRequestedSpy.last().first().toBool(), false);
949     // Shaded
950     serverWindow->setShaded(true);
951     QVERIFY(dataChangedSpy.wait());
952     model->requestToggleShaded(0);
953     QVERIFY(shadeRequestedSpy.wait());
954     QCOMPARE(shadeRequestedSpy.count(), 2);
955     QCOMPARE(shadeRequestedSpy.last().first().toBool(), false);
956 }
957 
testCreateWithUnmappedWindow()958 void PlasmaWindowModelTest::testCreateWithUnmappedWindow()
959 {
960     // This test verifies that creating the model just when an unmapped window exists doesn't cause
961     // problems.
962     // That is the unmapped window should be added (as expected), but also be removed again.
963 
964     // Create a window in "normal way".
965     QSignalSpy windowCreatedSpy(m_pw, &Clt::PlasmaWindowManagement::windowCreated);
966     QVERIFY(windowCreatedSpy.isValid());
967 
968     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
969     QVERIFY(serverWindow);
970     QVERIFY(windowCreatedSpy.wait());
971 
972     auto* window = windowCreatedSpy.first().first().value<Clt::PlasmaWindow*>();
973     QVERIFY(window);
974 
975     // Make sure the resource is properly created on server side.
976     QCoreApplication::instance()->processEvents(QEventLoop::WaitForMoreEvents);
977 
978     QSignalSpy unmappedSpy(window, &Clt::PlasmaWindow::unmapped);
979     QVERIFY(unmappedSpy.isValid());
980     QSignalSpy destroyedSpy(window, &Clt::PlasmaWindow::destroyed);
981     QVERIFY(destroyedSpy.isValid());
982 
983     // Unmap should be triggered, but not yet the destroyed.
984     serverWindow->unmap();
985     QVERIFY(unmappedSpy.wait());
986     QVERIFY(destroyedSpy.isEmpty());
987 
988     auto* model = m_pw->createWindowModel();
989     QVERIFY(model);
990     QCOMPARE(model->rowCount(), 1);
991     QSignalSpy rowRemovedSpy(model, &Clt::PlasmaWindowModel::rowsRemoved);
992     QVERIFY(rowRemovedSpy.isValid());
993     QVERIFY(rowRemovedSpy.wait());
994     QCOMPARE(rowRemovedSpy.count(), 1);
995     QCOMPARE(model->rowCount(), 0);
996     QCOMPARE(destroyedSpy.count(), 1);
997 }
998 
testChangeWindowAfterModelDestroy_data()999 void PlasmaWindowModelTest::testChangeWindowAfterModelDestroy_data()
1000 {
1001     QTest::addColumn<ClientWindowSignal>("changedSignal");
1002     QTest::addColumn<QVariant>("setter");
1003     QTest::addColumn<QVariant>("value");
1004 
1005     QTest::newRow("active") << &Clt::PlasmaWindow::activeChanged
1006                             << QVariant::fromValue(&Srv::PlasmaWindow::setActive) << QVariant(true);
1007     QTest::newRow("minimized") << &Clt::PlasmaWindow::minimizedChanged
1008                                << QVariant::fromValue(&Srv::PlasmaWindow::setMinimized)
1009                                << QVariant(true);
1010     QTest::newRow("fullscreen") << &Clt::PlasmaWindow::fullscreenChanged
1011                                 << QVariant::fromValue(&Srv::PlasmaWindow::setFullscreen)
1012                                 << QVariant(true);
1013     QTest::newRow("keepAbove") << &Clt::PlasmaWindow::keepAboveChanged
1014                                << QVariant::fromValue(&Srv::PlasmaWindow::setKeepAbove)
1015                                << QVariant(true);
1016     QTest::newRow("keepBelow") << &Clt::PlasmaWindow::keepBelowChanged
1017                                << QVariant::fromValue(&Srv::PlasmaWindow::setKeepBelow)
1018                                << QVariant(true);
1019     QTest::newRow("maximized") << &Clt::PlasmaWindow::maximizedChanged
1020                                << QVariant::fromValue(&Srv::PlasmaWindow::setMaximized)
1021                                << QVariant(true);
1022     QTest::newRow("demandsAttention")
1023         << &Clt::PlasmaWindow::demandsAttentionChanged
1024         << QVariant::fromValue(&Srv::PlasmaWindow::setDemandsAttention) << QVariant(true);
1025     QTest::newRow("closeable") << &Clt::PlasmaWindow::closeableChanged
1026                                << QVariant::fromValue(&Srv::PlasmaWindow::setCloseable)
1027                                << QVariant(true);
1028     QTest::newRow("minimizeable") << &Clt::PlasmaWindow::minimizeableChanged
1029                                   << QVariant::fromValue(&Srv::PlasmaWindow::setMinimizeable)
1030                                   << QVariant(true);
1031     QTest::newRow("maximizeable") << &Clt::PlasmaWindow::maximizeableChanged
1032                                   << QVariant::fromValue(&Srv::PlasmaWindow::setMaximizeable)
1033                                   << QVariant(true);
1034     QTest::newRow("fullscreenable")
1035         << &Clt::PlasmaWindow::fullscreenableChanged
1036         << QVariant::fromValue(&Srv::PlasmaWindow::setFullscreenable) << QVariant(true);
1037     QTest::newRow("skipTaskbar") << &Clt::PlasmaWindow::skipTaskbarChanged
1038                                  << QVariant::fromValue(&Srv::PlasmaWindow::setSkipTaskbar)
1039                                  << QVariant(true);
1040     QTest::newRow("shadeable") << &Clt::PlasmaWindow::shadeableChanged
1041                                << QVariant::fromValue(&Srv::PlasmaWindow::setShadeable)
1042                                << QVariant(true);
1043     QTest::newRow("shaded") << &Clt::PlasmaWindow::shadedChanged
1044                             << QVariant::fromValue(&Srv::PlasmaWindow::setShaded) << QVariant(true);
1045     QTest::newRow("movable") << &Clt::PlasmaWindow::movableChanged
1046                              << QVariant::fromValue(&Srv::PlasmaWindow::setMovable)
1047                              << QVariant(true);
1048     QTest::newRow("resizable") << &Clt::PlasmaWindow::resizableChanged
1049                                << QVariant::fromValue(&Srv::PlasmaWindow::setResizable)
1050                                << QVariant(true);
1051     QTest::newRow("vdChangeable") << &Clt::PlasmaWindow::virtualDesktopChangeableChanged
1052                                   << QVariant::fromValue(
1053                                          &Srv::PlasmaWindow::setVirtualDesktopChangeable)
1054                                   << QVariant(true);
1055     QTest::newRow("onallDesktop") << &Clt::PlasmaWindow::onAllDesktopsChanged
1056                                   << QVariant::fromValue(&Srv::PlasmaWindow::setOnAllDesktops)
1057                                   << QVariant(true);
1058     QTest::newRow("title") << &Clt::PlasmaWindow::titleChanged
1059                            << QVariant::fromValue(&Srv::PlasmaWindow::setTitle)
1060                            << QVariant(QStringLiteral("foo"));
1061     QTest::newRow("appId") << &Clt::PlasmaWindow::appIdChanged
1062                            << QVariant::fromValue(&Srv::PlasmaWindow::setAppId)
1063                            << QVariant(QStringLiteral("foo"));
1064 
1065     // Disable the icon test for now. Our way of providing icons is fundamentally wrong and the
1066     // whole concept needs to be redone so it works on all setups and in particular in a CI setting.
1067     // See issue #8.
1068 #if 0
1069     QTest::newRow("icon" )
1070             << &Clt::PlasmaWindow::iconChanged
1071             << QVariant::fromValue(&Srv::PlasmaWindow::setIcon)
1072             << QVariant::fromValue(QIcon::fromTheme(QStringLiteral("foo")));
1073 #endif
1074 
1075     QTest::newRow("unmapped") << &Clt::PlasmaWindow::unmapped
1076                               << QVariant::fromValue(&Srv::PlasmaWindow::unmap) << QVariant();
1077 }
1078 
testChangeWindowAfterModelDestroy()1079 void PlasmaWindowModelTest::testChangeWindowAfterModelDestroy()
1080 {
1081     // This test verifies that changes in a window after the model got destroyed doesn't crash.
1082     auto* model = m_pw->createWindowModel();
1083     QVERIFY(model);
1084 
1085     QSignalSpy windowCreatedSpy(m_pw, &Clt::PlasmaWindowManagement::windowCreated);
1086     QVERIFY(windowCreatedSpy.isValid());
1087 
1088     auto* serverWindow = m_serverWindowManager->createWindow(m_serverWindowManager);
1089     QVERIFY(windowCreatedSpy.wait());
1090     Clt::PlasmaWindow* window = windowCreatedSpy.first().first().value<Clt::PlasmaWindow*>();
1091 
1092     // Make sure the resource is properly created on server side.
1093     QCoreApplication::instance()->processEvents(QEventLoop::WaitForMoreEvents);
1094     QCOMPARE(model->rowCount(), 1);
1095     delete model;
1096 
1097     QFETCH(ClientWindowSignal, changedSignal);
1098     QSignalSpy changedSpy(window, changedSignal);
1099     QVERIFY(changedSpy.isValid());
1100     QVERIFY(!window->isActive());
1101 
1102     QFETCH(QVariant, setter);
1103     QFETCH(QVariant, value);
1104 
1105     if (QMetaType::Type(value.type()) == QMetaType::Bool) {
1106         (serverWindow->*(setter.value<ServerWindowBoolSetter>()))(value.toBool());
1107     } else if (QMetaType::Type(value.type()) == QMetaType::QString) {
1108         (serverWindow->*(setter.value<ServerWindowStringSetter>()))(value.toString());
1109     } else if (QMetaType::Type(value.type()) == QMetaType::UInt) {
1110         (serverWindow->*(setter.value<ServerWindowQuint32Setter>()))(value.toUInt());
1111     } else if (!value.isValid()) {
1112         (serverWindow->*(setter.value<ServerWindowVoidSetter>()))();
1113     }
1114 
1115     QVERIFY(changedSpy.wait());
1116 }
1117 
testCreateWindowAfterModelDestroy()1118 void PlasmaWindowModelTest::testCreateWindowAfterModelDestroy()
1119 {
1120     // This test verifies that creating a window after the model got destroyed doesn't crash.
1121     auto* model = m_pw->createWindowModel();
1122     QVERIFY(model);
1123 
1124     delete model;
1125 
1126     QSignalSpy windowCreatedSpy(m_pw, &Clt::PlasmaWindowManagement::windowCreated);
1127     QVERIFY(windowCreatedSpy.isValid());
1128 
1129     m_serverWindowManager->createWindow(m_serverWindowManager);
1130     QVERIFY(windowCreatedSpy.wait());
1131 }
1132 
1133 QTEST_GUILESS_MAIN(PlasmaWindowModelTest)
1134 #include "plasma_window_model.moc"
1135