on bc88941 help sign in
unity8/tests/plugins/Unity/Launcher/launchermodeltest.cpp
836 lines 35 kB history raw
1/*
2 * Copyright 2013-2017 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17// unity-api
18#include <unity/shell/launcher/LauncherModelInterface.h>
19#include <unity/shell/application/ApplicationInfoInterface.h>
20#include <unity/shell/application/MirSurfaceListInterface.h>
22#include "launcheritem.h"
23#include "launchermodel.h"
24#include "dbusinterface.h"
25#include "gsettings.h"
26#include "asadapter.h"
27#include "AccountsServiceDBusAdaptor.h"
29#include <QtTest>
30#include <QDBusInterface>
31#include <QDBusReply>
32#include <QDBusMetaType>
33#include <QDomDocument>
35#include <glib.h>
36#include <paths.h>
38namespace unityapi = unity::shell::application;
40// This is a mock, specifically to test the LauncherModel
41class MockSurface: public unity::shell::application::MirSurfaceInterface
43 Q_OBJECT
44public:
45 MockSurface(const QString &id, const QString &appId, QObject* parent): unityapi::MirSurfaceInterface(parent), m_id(id), m_appId(appId) {}
46 Mir::Type type() const override { return Mir::NormalType;}
47 QString name() const override { return QStringLiteral("mock surface"); }
48 QString persistentId() const override { return m_id; }
49 QString appId() const override { return m_appId; }
50 QPoint position() const override { return QPoint(); }
51 QSize size() const override { return QSize(); }
52 void resize(const QSize &size) override { Q_UNUSED(size) }
53 void resize(int width, int height) override { Q_UNUSED(width); Q_UNUSED(height) }
54 Mir::State state() const override { return Mir::RestoredState; }
55 bool live() const override { return true; }
56 bool visible() const override { return true; }
57 Mir::OrientationAngle orientationAngle() const override { return Mir::Angle0; }
58 void setOrientationAngle(Mir::OrientationAngle angle) override { Q_UNUSED(angle); }
59 int minimumWidth() const override { return 0; }
60 int minimumHeight() const override { return 0; }
61 int maximumWidth() const override { return 0; }
62 int maximumHeight() const override { return 0; }
63 int widthIncrement() const override { return 0; }
64 int heightIncrement() const override { return 0; }
65 void setKeymap(const QString &) override {}
66 Mir::ShellChrome shellChrome() const override { return Mir::NormalChrome; }
67 QString keymap() const override { return QStringLiteral("de_DE"); }
68 bool focused() const override { return true; }
69 QRect inputBounds() const override { return QRect(); }
70 bool confinesMousePointer() const override { return false; }
71 bool allowClientResize() const override { return true; }
72 void setAllowClientResize(bool) override {}
73 QPoint requestedPosition() const override { return QPoint(); }
74 void setRequestedPosition(const QPoint &) override {}
75 MirSurfaceInterface* parentSurface() const override { return nullptr; }
76 MirSurfaceListInterface* childSurfaceList() const override { return nullptr; }
77 void close() override {}
78 void forceClose() override {}
79 void activate() override {}
80 void requestState(Mir::State) override {}
82private:
83 QString m_id;
84 QString m_appId;
85};
87class MockSurfaceList: public unity::shell::application::MirSurfaceListInterface
89 Q_OBJECT
90public:
91 MockSurfaceList(QObject *parent): unityapi::MirSurfaceListInterface(parent) {}
92 int rowCount(const QModelIndex & = QModelIndex()) const override { return m_list.count(); }
93 QVariant data(const QModelIndex &, int) const override { return QVariant(); }
94 Q_INVOKABLE MirSurfaceInterface* get(int index) override { return m_list.at(index); }
95 void append(MirSurfaceInterface* surface) {
96 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
97 m_list.append(surface);
98 endInsertRows();
99 }
100private:
101 QList<MirSurfaceInterface*> m_list;
103};
105class MockApp: public unity::shell::application::ApplicationInfoInterface
107 Q_OBJECT
108public:
109 MockApp(const QString &appId, QObject *parent = 0): ApplicationInfoInterface(appId, parent), m_appId(appId), m_focused(false) {
110 m_surfaces = new MockSurfaceList(this);
111 }
113 RequestedState requestedState() const override { return RequestedRunning; }
114 void setRequestedState(RequestedState) override {}
115 QString appId() const override { return m_appId; }
116 QString name() const override { return "mock"; }
117 QString comment() const override { return "this is a mock"; }
118 QUrl icon() const override { return QUrl(); }
119 ApplicationInfoInterface::State state() const override { return ApplicationInfoInterface::Running; }
120 bool focused() const override { return m_focused; }
121 QString splashTitle() const override { return QString(); }
122 QUrl splashImage() const override { return QUrl(); }
123 bool splashShowHeader() const override { return true; }
124 QColor splashColor() const override { return QColor(0,0,0,0); }
125 QColor splashColorHeader() const override { return QColor(0,0,0,0); }
126 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
127 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
128 bool rotatesWindowContents() const override { return false; }
129 bool isTouchApp() const override { return true; }
130 bool exemptFromLifecycle() const override { return false; }
131 void setExemptFromLifecycle(bool) override {}
132 QSize initialSurfaceSize() const override { return QSize(); }
133 void setInitialSurfaceSize(const QSize &) override {}
134 MirSurfaceListInterface* surfaceList() const override { return m_surfaces; }
135 MirSurfaceListInterface* promptSurfaceList() const override { return nullptr; }
136 int surfaceCount() const override { return m_surfaces->count(); }
137 void setSurfaces(MockSurfaceList* surfaces) { m_surfaces = surfaces; Q_EMIT surfaceCountChanged(m_surfaces->count()); }
138 void close() override {}
140 // Methods used for mocking (not in the interface)
141 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
142private:
143 QString m_appId;
144 bool m_focused;
145 MockSurfaceList *m_surfaces;
146};
148// This is a mock, specifically to test the LauncherModel
149class MockAppManager: public unity::shell::application::ApplicationManagerInterface
151 Q_OBJECT
152public:
153 MockAppManager(QObject *parent = 0): ApplicationManagerInterface(parent) {}
154 ~MockAppManager() {}
155 int rowCount(const QModelIndex &) const override { return m_list.count(); }
156 QVariant data(const QModelIndex &, int ) const override { return QVariant(); }
157 QString focusedApplicationId() const override {
158 Q_FOREACH(MockApp *app, m_list) {
159 if (app->focused()) return app->appId();
160 }
161 return QString();
162 }
163 unity::shell::application::ApplicationInfoInterface *get(int index) const override { return m_list.at(index); }
164 unity::shell::application::ApplicationInfoInterface *findApplication(const QString &appId) const override {
165 Q_FOREACH(MockApp* app, m_list) {
166 if (app->appId() == appId) {
167 return app;
168 }
169 }
170 return nullptr;
171 }
172 unityapi::ApplicationInfoInterface *findApplicationWithSurface(unityapi::MirSurfaceInterface* /*surface*/) const override {
173 return nullptr;
174 }
175 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) override { return nullptr; }
176 bool stopApplication(const QString &appId) override {
177 Q_FOREACH(MockApp* app, m_list) {
178 if (app->appId() == appId) {
179 removeApplication(m_list.indexOf(app));
180 return true;
181 }
182 }
183 return false;
184 }
185 bool focusApplication(const QString &appId) {
186 Q_FOREACH(MockApp* app, m_list) {
187 app->setFocused(app->appId() == appId);
188 }
189 Q_EMIT focusedApplicationIdChanged();
190 return true;
191 }
193 void addApplication(MockApp *app) {
194 beginInsertRows(QModelIndex(), count(), count());
195 m_list.append(app);
196 endInsertRows();
197 }
198 void removeApplication(int index) {
199 beginRemoveRows(QModelIndex(), index, index);
200 m_list.takeAt(index)->deleteLater();
201 endRemoveRows();
202 }
203 bool requestFocusApplication(const QString &appId) override { Q_UNUSED(appId); return true; }
205private:
206 QList<MockApp*> m_list;
207};
209class LauncherModelTest : public QObject
211 Q_OBJECT
213private:
214 LauncherModel *launcherModel;
215 MockAppManager *appManager;
216 QTemporaryDir tmpDir;
218 QList<QVariantMap> getASConfig() {
219 AccountsServiceDBusAdaptor *as = launcherModel->m_asAdapter->m_accounts;
220 QDBusReply<QVariant> reply = as->getUserPropertyAsync(QString::fromUtf8(g_get_user_name()),
221 "com.canonical.unity.AccountsService",
222 "LauncherItems");
223 return qdbus_cast<QList<QVariantMap>>(reply.value().value<QDBusArgument>());
224 }
226 // Link our app data from a tempdir & tell glib/UAL to look there.
227 // We do this because we want to be able to delete the applications dir
228 // during testing, but that dir may be read-only (installed on system).
229 void setUpAppDir() {
230 QFile appDir(qgetenv("APPDIR"));
231 appDir.link(tmpDir.path() + "/applications");
233 qputenv("XDG_DATA_HOME", tmpDir.path().toUtf8());
234 }
236private Q_SLOTS:
238 void initTestCase() {
239 qDBusRegisterMetaType<QList<QVariantMap>>();
241 setUpAppDir();
243 launcherModel = new LauncherModel(this);
244 QCoreApplication::processEvents(); // to let the model register on DBus
245 QCOMPARE(launcherModel->rowCount(QModelIndex()), 0);
247 appManager = new MockAppManager(this);
248 launcherModel->setApplicationManager(appManager);
249 }
251 // Adding 2 apps to the mock appmanager. Both should appear in the launcher.
252 void init() {
253 // Switching to tmpDir makes manipulating our appdir symlink easier.
254 QDir::setCurrent(tmpDir.path());
256 QDBusInterface accountsInterface(QStringLiteral("org.freedesktop.Accounts"),
257 QStringLiteral("/org/freedesktop/Accounts"),
258 QStringLiteral("org.freedesktop.Accounts"));
259 QDBusReply<bool> addReply = accountsInterface.call(QStringLiteral("AddUser"),
260 QString::fromUtf8(g_get_user_name()));
261 QVERIFY(addReply.isValid());
262 QCOMPARE(addReply.value(), true);
264 appManager->addApplication(new MockApp("abs-icon"));
265 QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
267 appManager->addApplication(new MockApp("rel-icon"));
268 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
270 launcherModel->m_settings->setStoredApplications(QStringList());
271 }
273 // Removing apps from appmanager and launcher as pinned ones would stick
274 void cleanup() {
275 while (appManager->count() > 0) {
276 appManager->removeApplication(0);
277 }
278 while (launcherModel->rowCount(QModelIndex()) > 0) {
279 launcherModel->requestRemove(launcherModel->get(0)->appId());
280 }
282 QDBusInterface accountsInterface(QStringLiteral("org.freedesktop.Accounts"),
283 QStringLiteral("/org/freedesktop/Accounts"),
284 QStringLiteral("org.freedesktop.Accounts"));
285 QDBusReply<bool> removeReply = accountsInterface.call(QStringLiteral("RemoveUser"),
286 QString::fromUtf8(g_get_user_name()));
287 QVERIFY(removeReply.isValid());
288 QCOMPARE(removeReply.value(), true);
290 // Some tests move the directory, so lets move it back if so.
291 // But this will usually fail.
292 QFile::rename("applications.old", "applications");
293 }
295 void testMove() {
296 QCOMPARE(launcherModel->get(0)->pinned(), false);
297 QCOMPARE(launcherModel->get(1)->pinned(), false);
299 LauncherItemInterface *item0BeforeMove = launcherModel->get(0);
300 LauncherItemInterface *item1BeforeMove = launcherModel->get(1);
301 launcherModel->move(1, 0);
303 QCOMPARE(item0BeforeMove, launcherModel->get(1));
304 QCOMPARE(item1BeforeMove, launcherModel->get(0));
306 // moved item must be pinned now
307 QCOMPARE(item0BeforeMove->pinned(), false);
308 QCOMPARE(item1BeforeMove->pinned(), true);
309 }
311 void testPinning() {
312 QSignalSpy spy(launcherModel, &LauncherModel::dataChanged);
313 QCOMPARE(launcherModel->get(0)->pinned(), false);
314 QCOMPARE(launcherModel->get(1)->pinned(), false);
315 launcherModel->pin(launcherModel->get(0)->appId());
316 QCOMPARE(launcherModel->get(0)->pinned(), true);
317 QCOMPARE(launcherModel->get(1)->pinned(), false);
318 QCOMPARE(spy.count() > 0, true);
319 QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
321 // App should be pinned now
322 spy.clear();
323 launcherModel->requestRemove(launcherModel->get(0)->appId());
324 QCOMPARE(launcherModel->get(0)->pinned(), false);
325 QCOMPARE(launcherModel->get(1)->pinned(), false);
326 QCOMPARE(spy.count(), 1);
327 QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
329 // Now that the app is unpinned, nothing should change and the signal should not be emitted
330 spy.clear();
331 launcherModel->requestRemove(launcherModel->get(0)->appId());
332 QCOMPARE(launcherModel->get(0)->pinned(), false);
333 QCOMPARE(launcherModel->get(1)->pinned(), false);
334 QCOMPARE(spy.count(), 0);
335 }
337 void testRemove_data() {
339 QTest::addColumn<bool>("pinned");
340 QTest::addColumn<bool>("running");
342 QTest::newRow("non-pinned, running") << false << true;
343 QTest::newRow("pinned, running") << true << true;
344 QTest::newRow("pinned, non-running") << true << false;
345 }
347 void testRemove() {
348 QFETCH(bool, pinned);
349 QFETCH(bool, running);
351 // In the beginning we always have two items
352 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
354 // pin one if required
355 if (pinned) {
356 launcherModel->pin("abs-icon");
357 }
359 // stop it if required
360 if (!running) {
361 appManager->stopApplication("abs-icon");
362 }
364 // Now remove it
365 launcherModel->requestRemove("abs-icon");
367 if (running) {
368 // both apps are running, both apps must still be here
369 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
371 // Item must be unpinned now
372 int index = launcherModel->findApplication("abs-icon");
373 QCOMPARE(launcherModel->get(index)->pinned(), false);
375 } else if (pinned) {
376 // Item 1 must go away, item 0 is here to stay
377 QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
378 }
380 // done our checks. now stop the app if was still running
381 if (running) {
382 appManager->stopApplication("abs-icon");
383 }
385 // It needs to go away in any case now
386 QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
387 }
389 void testQuickListPinningRemoving() {
390 // we start with 2 unpinned items
391 QCOMPARE(launcherModel->get(0)->pinned(), false);
392 QCOMPARE(launcherModel->get(1)->pinned(), false);
393 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
395 // find the Pin item in the quicklist
396 QuickListModel *model = qobject_cast<QuickListModel*>(launcherModel->get(0)->quickList());
397 int pinActionIndex = -1;
398 for (int i = 0; i < model->rowCount(QModelIndex()); ++i) {
399 if (model->get(i).actionId() == "pin_item") {
400 pinActionIndex = i;
401 break;
402 }
403 }
404 QVERIFY(pinActionIndex >= 0);
406 // trigger pin item quicklist action => Item must be pinned now.
407 launcherModel->quickListActionInvoked(launcherModel->get(0)->appId(), pinActionIndex);
408 QCOMPARE(launcherModel->get(0)->pinned(), true);
409 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
411 // quicklist needs to transform to remove item. trigger it and check it item goes away
412 launcherModel->quickListActionInvoked(launcherModel->get(0)->appId(), pinActionIndex);
413 QCOMPARE(launcherModel->get(0)->pinned(), false);
415 // still needs to be here as the app is still here
416 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
417 // close the app
418 appManager->removeApplication(0);
419 // Now it needs to go away
420 QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
421 }
423 void testApplicationRunning() {
424 launcherModel->pin("abs-icon");
425 launcherModel->pin("rel-icon");
427 QCOMPARE(launcherModel->get(0)->running(), true);
428 QCOMPARE(launcherModel->get(1)->running(), true);
430 appManager->stopApplication("abs-icon");
431 QCOMPARE(launcherModel->get(0)->running(), false);
432 QCOMPARE(launcherModel->get(1)->running(), true);
434 appManager->stopApplication("rel-icon");
435 QCOMPARE(launcherModel->get(0)->running(), false);
436 QCOMPARE(launcherModel->get(1)->running(), false);
437 }
439 void testApplicationFocused() {
440 // all apps unfocused at beginning...
441 QCOMPARE(launcherModel->get(0)->focused(), false);
442 QCOMPARE(launcherModel->get(1)->focused(), false);
444 appManager->focusApplication("abs-icon");
445 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
446 QCOMPARE(launcherModel->get(0)->focused(), true);
447 QCOMPARE(launcherModel->get(1)->focused(), false);
449 appManager->focusApplication("rel-icon");
450 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
451 QCOMPARE(launcherModel->get(0)->focused(), false);
452 QCOMPARE(launcherModel->get(1)->focused(), true);
453 }
455 void testClosingApps() {
456 // At the start there are 2 items. Let's pin one.
457 launcherModel->pin("abs-icon");
458 while (appManager->count() > 0) {
459 appManager->removeApplication(0);
460 }
461 // The pinned one needs to stay, the other needs to disappear
462 QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
463 QCOMPARE(launcherModel->get(0)->appId(), QLatin1String("abs-icon"));
464 }
466 void testQuitMenuItem() {
467 // we have 2 apps running, both should have the Quit action in its quick list
468 QCOMPARE(launcherModel->rowCount(), 2);
470 // stop the second one keeping it pinned so that it doesn't go away
471 launcherModel->pin("rel-icon");
472 appManager->stopApplication("rel-icon");
474 // find the first Quit item, should be there
475 QuickListModel *model = qobject_cast<QuickListModel*>(launcherModel->get(0)->quickList());
476 int quitActionIndex = -1;
477 for (int i = 0; i < model->rowCount(); ++i) {
478 if (model->get(i).actionId() == "stop_item") {
479 quitActionIndex = i;
480 break;
481 }
482 }
483 QVERIFY(quitActionIndex >= 0);
485 // find the second Quit item, should NOT be there, the app is stopped
486 QuickListModel *model2 = qobject_cast<QuickListModel*>(launcherModel->get(1)->quickList());
487 int quitActionIndex2 = -1;
488 for (int i = 0; i < model2->rowCount(); ++i) {
489 if (model2->get(i).actionId() == "stop_item") {
490 quitActionIndex2 = i;
491 break;
492 }
493 }
494 QVERIFY(quitActionIndex2 == -1);
496 // trigger the first quit item quicklist action
497 launcherModel->quickListActionInvoked(launcherModel->get(0)->appId(), quitActionIndex);
498 // first app should be gone...
499 QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
500 // ... the second app (now at index 0) should still be there, pinned and stopped
501 QCOMPARE(launcherModel->get(0)->appId(), QStringLiteral("rel-icon"));
502 QCOMPARE(launcherModel->get(0)->pinned(), true);
503 QCOMPARE(launcherModel->get(0)->running(), false);
504 }
506 void testGetUrlForAppId() {
507 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
508 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
509 QCOMPARE(launcherModel->getUrlForAppId("no-name"), QString("application:///no-name.desktop"));
510 QCOMPARE(launcherModel->getUrlForAppId("com.test.good"), QString("application:///com.test.good.desktop"));
511 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application"), QString("appid://com.test.good/application/current-user-version"));
512 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application_1.2.3"), QString("appid://com.test.good/application/current-user-version"));
513 }
515 void testIntrospection() {
516 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "org.freedesktop.DBus.Introspectable");
517 QDBusReply<QString> reply = interface.call("Introspect");
518 QStringList nodes = extractNodes(reply.value());
519 QCOMPARE(nodes.count(), launcherModel->rowCount());
521 appManager->addApplication(new MockApp("foobar"));
522 reply = interface.call("Introspect");
523 nodes = extractNodes(reply.value());
524 QCOMPARE(nodes.contains("foobar"), true);
526 appManager->removeApplication(2);
527 reply = interface.call("Introspect");
528 nodes = extractNodes(reply.value());
529 QCOMPARE(nodes.contains("foobar"), false);
530 }
532 QStringList extractNodes(const QString &introspectionXml) {
533 QXmlStreamReader introspectReply(introspectionXml);
535 QStringList ret;
536 while (!introspectReply.atEnd() && !introspectReply.hasError()) {
537 QXmlStreamReader::TokenType token = introspectReply.readNext();
539 if (token == QXmlStreamReader::StartElement) {
540 if (introspectReply.name() == "node" && introspectReply.attributes().count() > 0) {
541 ret << introspectReply.attributes().value("name").toString();
542 }
543 }
544 }
545 return ret;
546 }
548 void testCountEmblems() {
549 QSignalSpy spy(launcherModel, &LauncherModel::dataChanged);
551 // Call GetAll on abs-icon
552 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
553 QDBusReply<QVariantMap> reply = interface.call("GetAll");
554 QVariantMap map = reply.value();
556 // Check that the alerting-status is still false, and the item on the upper side of the API
557 int index = launcherModel->findApplication("abs-icon");
558 QCOMPARE(index >= 0, true);
559 QVERIFY(launcherModel->get(index)->alerting() == false);
561 // Make sure GetAll returns a map with count and countVisible props
562 QCOMPARE(map.contains("count"), true);
563 QCOMPARE(map.contains("countVisible"), true);
565 // Make sure count is intitilized to 0 and non-visible
566 QCOMPARE(map.value("count").toInt(), 0);
567 QCOMPARE(map.value("countVisible").toBool(), false);
569 // Now make it visible and set it to 55 through D-Bus
570 interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", QVariant::fromValue(QDBusVariant(55)));
571 interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(true)));
573 // Fetch it again using GetAll
574 reply = interface.call("GetAll");
575 map = reply.value();
577 // Make sure values have changed on the D-Bus interface
578 QCOMPARE(map.value("count").toInt(), 55);
579 QCOMPARE(map.value("countVisible").toBool(), true);
581 // Finally check, that the change to "count" implicitly also set the alerting-state to true
582 QVERIFY(launcherModel->get(index)->alerting() == true);
584 // Check if the launcher emitted the changed signals
585 QCOMPARE(spy.count(), 2);
587 QVariantList countEmissionArgs = spy.takeFirst();
588 QCOMPARE(countEmissionArgs.at(0).toModelIndex().row(), index);
589 QCOMPARE(countEmissionArgs.at(1).toModelIndex().row(), index);
590 QVector<int> roles = countEmissionArgs.at(2).value<QVector<int> >();
591 QCOMPARE(roles.first(), (int)LauncherModel::RoleCount);
593 QVariantList countVisibleEmissionArgs = spy.takeFirst();
594 QCOMPARE(countVisibleEmissionArgs.at(0).toModelIndex().row(), index);
595 QCOMPARE(countVisibleEmissionArgs.at(1).toModelIndex().row(), index);
596 roles = countVisibleEmissionArgs.at(2).value<QVector<int> >();
597 QVERIFY(roles.contains(LauncherModel::RoleCountVisible));
598 QVERIFY(roles.contains(LauncherModel::RoleAlerting));
600 // Check if the values match
601 QCOMPARE(launcherModel->get(index)->countVisible(), true);
602 QCOMPARE(launcherModel->get(index)->count(), 55);
603 QCOMPARE(launcherModel->get(index)->alerting(), true);
605 // Focus the app, make sure the alert gets cleared
606 appManager->focusApplication("abs-icon");
607 QVERIFY(launcherModel->get(index)->alerting() == false);
608 }
610 void testCountEmblemAddsRemovesItem_data() {
611 QTest::addColumn<bool>("isPinned");
612 QTest::addColumn<bool>("isRunning");
613 QTest::addColumn<bool>("startWhenVisible");
614 QTest::newRow("not pinned, not running") << false << false << false;
615 QTest::newRow("pinned, not running") << true << false << false;
616 QTest::newRow("not pinned, not running, starting from notification") << false << false << true;
617 QTest::newRow("pinned, not running, starting from notification") << true << false << true;
618 QTest::newRow("not pinned, running") << false << true << false;
619 QTest::newRow("pinned, running") << true << true << false;
620 }
622 void testCountEmblemAddsRemovesItem() {
623 QFETCH(bool, isPinned);
624 QFETCH(bool, isRunning);
625 QFETCH(bool, startWhenVisible);
627 // Make sure item is here as expected after init() and that count is not visible
628 int index = launcherModel->findApplication("abs-icon");
629 QCOMPARE(index == -1, false);
630 QCOMPARE(launcherModel->get(index)->countVisible(), false);
632 // Pin if we need to
633 if (isPinned) {
634 launcherModel->pin("abs-icon");
635 }
636 QCOMPARE(launcherModel->get(0)->pinned(), isPinned);
638 // Stop it if we need to
639 if (!isRunning) {
640 appManager->stopApplication("abs-icon");
641 }
642 QCOMPARE(launcherModel->findApplication("abs-icon") >= 0, isRunning || isPinned);
645 // set the count emblem to visible
646 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
647 interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", QVariant::fromValue(QDBusVariant(55)));
648 interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(true)));
650 // Make sure item is here and that count is visible
651 index = launcherModel->findApplication("abs-icon");
652 QCOMPARE(index == -1, false);
653 QCOMPARE(launcherModel->get(index)->countVisible(), true);
655 if (!isRunning && startWhenVisible) {
656 appManager->addApplication(new MockApp("abs-icon"));
657 }
659 // Hide count emblem again
660 interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(false)));
662 // Make sure item is shown/hidden as expected
663 index = launcherModel->findApplication("abs-icon");
664 QCOMPARE(index == -1, !isRunning && !isPinned && !startWhenVisible);
665 }
667 void testAlert() {
668 // Check that the alerting-status is still false
669 int index = launcherModel->findApplication("abs-icon");
670 QCOMPARE(index >= 0, true);
671 QVERIFY(launcherModel->get(index)->alerting() == false);
673 // Call Alert() on "abs-icon"
674 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "com.canonical.Unity.Launcher.Item");
675 interface.call("Alert");
677 // Check that the alerting-status is now true
678 QVERIFY(launcherModel->get(index)->alerting() == true);
680 // Focus the app, make sure the alert gets cleared
681 appManager->focusApplication("abs-icon");
682 QVERIFY(launcherModel->get(index)->alerting() == false);
683 }
685 void testRefreshAfterDeletedDesktopFiles_data() {
686 QTest::addColumn<bool>("deleted");
687 QTest::newRow("have .desktop files") << false;
688 QTest::newRow("deleted .desktop files") << true;
689 }
691 void testRefreshAfterDeletedDesktopFiles() {
692 QFETCH(bool, deleted);
694 // pin both apps
695 launcherModel->pin("abs-icon");
696 launcherModel->pin("rel-icon");
697 // close both apps
698 appManager->removeApplication(0);
699 appManager->removeApplication(0);
701 // "delete" the .desktop files
702 if (deleted) {
703 // In testing mode, the launcher searches the current dir for the sample .desktop file
704 // We can make that fail by moving the applications dir
705 QFile::rename("applications", "applications.old");
706 }
708 // Call refresh
709 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher");
710 QDBusReply<void> reply = interface.call("Refresh");
712 // Make sure the call to Refresh returned without error.
713 QCOMPARE(reply.isValid(), true);
715 QCOMPARE(launcherModel->rowCount(), deleted ? 0 : 2);
716 }
718 void testSettings() {
719 GSettings *settings = launcherModel->m_settings;
720 QSignalSpy spy(launcherModel, &LauncherModel::hint);
722 // Nothing pinned at startup
723 QCOMPARE(settings->storedApplications().count(), 0);
725 // pin both apps
726 launcherModel->pin("abs-icon");
727 launcherModel->pin("rel-icon");
728 QCOMPARE(spy.count(), 0);
730 // Now settings should have 2 apps
731 QCOMPARE(settings->storedApplications().count(), 2);
733 // close both apps
734 appManager->removeApplication(0);
735 appManager->removeApplication(0);
737 // Now settings should have 2 apps
738 QCOMPARE(settings->storedApplications().count(), 2);
740 // Now remove 1 app through the backend, make sure one is still there
741 settings->simulateDConfChanged(QStringList() << "abs-icon");
742 QCOMPARE(settings->storedApplications().count(), 1);
743 QCOMPARE(spy.count(), 1);
745 // Check if it disappeared from the frontend too
746 QCOMPARE(launcherModel->rowCount(), 1);
748 // Add them back but in reverse order
749 settings->simulateDConfChanged(QStringList() << "rel-icon" << "abs-icon");
750 QCOMPARE(launcherModel->rowCount(), 2);
751 QCOMPARE(launcherModel->get(0)->appId(), QString("rel-icon"));
752 QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon"));
753 QCOMPARE(spy.count(), 2);
754 }
756 void testAddSyncsToAS() {
757 // Make sure launcher and AS are in sync when we start the test
758 QCOMPARE(launcherModel->rowCount(), getASConfig().count());
760 int oldCount = launcherModel->rowCount();
761 appManager->addApplication(new MockApp("click-icon"));
762 QCOMPARE(launcherModel->rowCount(), oldCount + 1);
763 QCOMPARE(launcherModel->rowCount(), getASConfig().count());
764 }
766 void testRemoveSyncsToAS() {
767 // Make sure launcher and AS are in sync when we start the test
768 QCOMPARE(launcherModel->rowCount(), getASConfig().count());
770 int oldCount = launcherModel->rowCount();
771 appManager->stopApplication("abs-icon");
772 QCOMPARE(launcherModel->rowCount(), oldCount - 1);
773 QCOMPARE(launcherModel->rowCount(), getASConfig().count());
774 }
776 void testMoveSyncsToAS() {
777 // Make sure launcher and AS are in sync when we start the test
778 QCOMPARE(launcherModel->rowCount(), getASConfig().count());
780 for (int i = 0; i < launcherModel->rowCount(); i++) {
781 QString launcherAppId = launcherModel->get(i)->appId();
782 QString asAppId = getASConfig().at(i).value("id").toString();
783 QCOMPARE(launcherAppId, asAppId);
784 }
786 launcherModel->move(0, 1);
788 for (int i = 0; i < launcherModel->rowCount(); i++) {
789 QString launcherAppId = launcherModel->get(i)->appId();
790 QString asAppId = getASConfig().at(i).value("id").toString();
791 QCOMPARE(launcherAppId, asAppId);
792 }
793 }
795 void testCountChangeSyncsToAS() {
796 // Find the index of the abs-icon app
797 int index = launcherModel->findApplication("abs-icon");
799 // Make sure it's invisible and 0 at the beginning
800 QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), false);
801 QCOMPARE(getASConfig().at(index).value("count").toInt(), 0);
803 // Change the count of the abs-icon app through D-Bus
804 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
805 interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", QVariant::fromValue(QDBusVariant(55)));
806 interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(true)));
808 // Make sure it changed to visible and 55
809 QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), true);
810 QCOMPARE(getASConfig().at(index).value("count").toInt(), 55);
811 }
813 void testSurfaceCountUpdates() {
814 QString appId = launcherModel->get(0)->appId();
816 QCOMPARE(launcherModel->get(0)->surfaceCount(), 0);
817 MockApp *app = qobject_cast<MockApp*>(appManager->findApplication(appId));
818 MockSurfaceList* surfaces = new MockSurfaceList(appManager);
819 surfaces->append(new MockSurface("foobar", "foobar", surfaces));
820 app->setSurfaces(surfaces);
821 QCOMPARE(launcherModel->get(0)->surfaceCount(), 1);
823 // Make sure the new surface appears in the quicklist
824 QuickListModel *quickList = qobject_cast<QuickListModel*>(launcherModel->get(0)->quickList());
825 bool found = false;
826 for (int i = 0; i < quickList->rowCount(); ++i) {
827 if (quickList->get(i).actionId() == "surface_foobar") {
828 found = true;
829 }
830 }
831 QCOMPARE(found, true);
832 }
833};
835QTEST_GUILESS_MAIN(LauncherModelTest)
836#include "launchermodeltest.moc"