Merge lp:~macslow/unity8/swipe-dismiss-snap-decisions into lp:unity8

Proposed by Mirco Müller
Status: Merged
Approved by: Michał Sawicz
Approved revision: 1285
Merged at revision: 1599
Proposed branch: lp:~macslow/unity8/swipe-dismiss-snap-decisions
Merge into: lp:unity8
Diff against target: 1554 lines (+836/-252)
15 files modified
qml/Notifications/Notification.qml (+36/-6)
qml/Notifications/Notifications.qml (+2/-6)
tests/mocks/Unity/Notifications/CMakeLists.txt (+2/-1)
tests/mocks/Unity/Notifications/MockActionModel.cpp (+6/-4)
tests/mocks/Unity/Notifications/MockActionModel.h (+4/-2)
tests/mocks/Unity/Notifications/MockNotification.cpp (+156/-4)
tests/mocks/Unity/Notifications/MockNotification.h (+63/-5)
tests/mocks/Unity/Notifications/MockNotificationModel.cpp (+172/-0)
tests/mocks/Unity/Notifications/MockNotificationModel.h (+72/-0)
tests/mocks/Unity/Notifications/plugin.cpp (+5/-3)
tests/mocks/Unity/Notifications/plugin.h (+1/-2)
tests/qmltests/Notifications/Notification.qml (+31/-0)
tests/qmltests/Notifications/tst_Notifications.qml (+271/-213)
tests/qmltests/Notifications/tst_OptionToggle.qml (+11/-6)
tests/qmltests/Notifications/tst_SwipeToAct.qml (+4/-0)
To merge this branch: bzr merge lp:~macslow/unity8/swipe-dismiss-snap-decisions
Reviewer Review Type Date Requested Status
Michał Sawicz tests Approve
PS Jenkins bot (community) continuous-integration Approve
Albert Astals Cid (community) Approve
Michael Zanetti (community) Abstain
Antti Kaijanmäki (community) Needs Information
Review via email: mp+233347@code.launchpad.net

Commit message

Allow swipe-to-dismiss for contracted snap-decision notifications, interactive notifications and ephemeral notifications.

Description of the change

Allow swipe-to-dismiss for contracted snap-decision notifications, interactive notifications and ephemeral notifications.

For the reviewers convenience, here's a video of the branch in action: https://www.youtube.com/watch?v=3kHuR-3Mns0

* Are there any related MPs required for this MP to build/function as expected? Please list.
No.

* Did you perform an exploratory manual test run of your code change and any related functionality?
Yes.

* Did you make sure that your branch does not contain spurious tags?
Yes.

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.

* If you changed the UI, has there been a design review?
Yes.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in qml/Notifications/Notification.qml
1 conflicts encountered.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in qml/Notifications/Notification.qml
Text conflict in tests/qmltests/Notifications/tst_Notifications.qml
2 conflicts encountered.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Antti Kaijanmäki (kaijanmaki) wrote :

+ readonly property bool draggable: state == "contracted" || notification.type !== Notification.Confirmation

Does this cover pin-unlock dialog as well? I don't have the code at hand right now to check if it's type is set to Notification.Confirmation as otherwise it becomes draggable.

review: Needs Information
Revision history for this message
Mirco Müller (macslow) wrote :

> + readonly property bool draggable: state == "contracted" ||
> notification.type !== Notification.Confirmation
>
>
> Does this cover pin-unlock dialog as well? I don't have the code at hand right
> now to check if it's type is set to Notification.Confirmation as otherwise it
> becomes draggable.

No, that won't happen. A SIM-unlock "dialog" is an expanded (or non-contractable) snap-decision, thus it cannot be dragged.

Revision history for this message
Albert Astals Cid (aacid) wrote :

file:///tmp/buildd/unity8-8.01+15.04.20141104bzr1236pkg0vivid36/tests/qmltests/Notifications/tst_Notifications.qml:672:13: Unexpected token `if'
                 if (data.type !== Notification.SnapDecision && notification.state !== "expanded") {
                 ^
file:///tmp/buildd/unity8-8.01+15.04.20141104bzr1236pkg0vivid36/tests/qmltests/Notifications/tst_Notifications.qml:672:16: Expected a qualified name id
                 if (data.type !== Notification.SnapDecision && notification.state !== "expanded") {

Revision history for this message
Albert Astals Cid (aacid) wrote :

make testNotifications fails here

review: Needs Fixing
Revision history for this message
Mirco Müller (macslow) wrote :

> make testNotifications fails here

Like discussed on IRC, I can't reproduce your reported failures. Everything works and passes as expected.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> > make testNotifications fails here
>
> Like discussed on IRC, I can't reproduce your reported failures. Everything
> works and passes as expected.

Barring that for some reasons test don't pass for me the rest looks good, if everyone else wants to take over these two days i'm out, feel free :)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

I can reproduce the test failures Albert reported. Sometimes it seems that a swipe to remove removes 2 notifications instead of just one. I can also reproduce that manually with tryNotifications.

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

Seems fixing the FIXME in Notifications.qml:44 fixes this. Can't reproduce the issue any more neither using the test, nor manually.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

hmm.. sorry, after reverting the debug prints in the test it fails again here...

Revision history for this message
Michael Zanetti (mzanetti) wrote :

Test passing fine now.

review: Abstain
Revision history for this message
Albert Astals Cid (aacid) wrote :

I think relying on the JS->C++ binding ignoring the extra parameter to call an invokable function with less parameters is a bit too much

Revision history for this message
Albert Astals Cid (aacid) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass?
Waiting for it to finish

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

qmluitests failed on CI for some reason byt they all passed fine so i'm going to top approve anyway

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

There's a failing test I'm afraid:

FAIL! : qmltestrunner::NotificationRendererTest::test_NotificationRenderer(Ephemeral notification) property count
   Actual (): 3
   Expected (): 2
   Loc: [/home/michal/Pobrane/unity8-8.02+15.04.20150204.1/tests/qmltests/Notifications/tst_Notifications.qml(666)]

review: Needs Fixing
Revision history for this message
Mirco Müller (macslow) wrote :

Looking into it.

1284. By Mirco Müller

Merge with trunk.

Revision history for this message
Mirco Müller (macslow) wrote :

I've updated my system, re-merged this branch with lp:unity8 trunk and recompiled everything, but still can't reproduce this failure. All notification-related qml-tests pass.

1285. By Mirco Müller

Make sure all rendering operations are completed before doing the swipe-dismiss-checks, thus the tests also pass on very slow (jenkins) systems.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) :
review: Approve (tests)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Notifications/Notification.qml'
2--- qml/Notifications/Notification.qml 2014-11-07 14:31:12 +0000
3+++ qml/Notifications/Notification.qml 2015-02-06 15:16:01 +0000
4@@ -40,6 +40,7 @@
5 property bool fullscreen: false
6 property int maxHeight
7 property int margins
8+ readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
9 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
10 readonly property color red: "#fc4949"
11 readonly property color green: "#3fb24f"
12@@ -52,7 +53,7 @@
13 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
14
15 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
16- opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
17+ opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
18
19 state: {
20 var result = "";
21@@ -80,18 +81,28 @@
22 id: sound
23 objectName: "sound"
24 audioRole: MediaPlayer.alert
25- source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
26+ source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : ""
27 }
28
29 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
30 Component.onCompleted: {
31- if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
32+ if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
33 sound.play();
34 }
35 }
36
37+ Behavior on x {
38+ id: normalXBehavior
39+
40+ enabled: draggable
41+ UbuntuNumberAnimation {
42+ duration: UbuntuAnimation.FastDuration
43+ easing.type: Easing.OutBounce
44+ }
45+ }
46+
47 onHintsChanged: {
48- if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
49+ if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
50 sound.play();
51 }
52 }
53@@ -146,6 +157,12 @@
54 opacity: parent.opacity
55 }
56
57+ onXChanged: {
58+ if (draggable && notification.x > 0.75 * notification.width) {
59+ notification.notification.close()
60+ }
61+ }
62+
63 Item {
64 id: contents
65 anchors.fill: fullscreen ? nonShapedBack : shapedBack
66@@ -169,7 +186,7 @@
67 actions: paths.actions
68 menuObjectPath: paths.menuObjectPath
69 onNameOwnerChanged: {
70- if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {
71+ if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
72 notification.notification.close()
73 }
74 lastNameOwner = nameOwner
75@@ -181,6 +198,12 @@
76
77 anchors.fill: parent
78 objectName: "interactiveArea"
79+
80+ drag.target: draggable ? notification : undefined
81+ drag.axis: Drag.XAxis
82+ drag.minimumX: 0
83+ drag.maximumX: notification.width
84+
85 onClicked: {
86 if (notification.type == Notification.Interactive) {
87 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
88@@ -188,6 +211,13 @@
89 notificationList.currentIndex = index;
90 }
91 }
92+ onReleased: {
93+ if (notification.x < notification.width / 2) {
94+ notification.x = 0
95+ } else {
96+ notification.x = notification.width
97+ }
98+ }
99 }
100
101 Column {
102@@ -447,7 +477,7 @@
103 right: parent.right
104 margins: contentSpacing
105 }
106- visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
107+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
108 spacing: units.gu(2)
109 layoutDirection: Qt.RightToLeft
110
111
112=== modified file 'qml/Notifications/Notifications.qml'
113--- qml/Notifications/Notifications.qml 2014-12-19 14:51:35 +0000
114+++ qml/Notifications/Notifications.qml 2015-02-06 15:16:01 +0000
115@@ -41,15 +41,11 @@
116 property bool topmostIsFullscreen: false
117 spacing: topmostIsFullscreen ? 0 : units.gu(.5)
118
119- // FIXME: This doesn't make any sense and results in a binding loop
120- currentIndex: (currentIndex < 1 && count > 1) ? 1 : -1
121+ currentIndex: count > 1 ? 1 : -1
122
123 delegate: Notification {
124 objectName: "notification" + index
125- anchors {
126- left: parent.left
127- right: parent.right
128- }
129+ width: parent.width
130 type: model.type
131 hints: model.hints
132 iconSource: model.icon
133
134=== modified file 'tests/mocks/Unity/Notifications/CMakeLists.txt'
135--- tests/mocks/Unity/Notifications/CMakeLists.txt 2014-10-23 22:01:57 +0000
136+++ tests/mocks/Unity/Notifications/CMakeLists.txt 2015-02-06 15:16:01 +0000
137@@ -4,7 +4,8 @@
138
139 set(MockNotificationsPlugin_SOURCES
140 plugin.cpp
141- MockNotificationTypes.cpp
142+ MockNotification.cpp
143+ MockNotificationModel.cpp
144 MockActionModel.cpp
145 )
146
147
148=== modified file 'tests/mocks/Unity/Notifications/MockActionModel.cpp'
149--- tests/mocks/Unity/Notifications/MockActionModel.cpp 2014-10-23 22:01:57 +0000
150+++ tests/mocks/Unity/Notifications/MockActionModel.cpp 2015-02-06 15:16:01 +0000
151@@ -1,5 +1,5 @@
152 /*
153- * Copyright 2014 Canonical Ltd.
154+ * Copyright 2015 Canonical Ltd.
155 *
156 * This program is free software; you can redistribute it and/or modify
157 * it under the terms of the GNU Lesser General Public License as published by
158@@ -25,8 +25,6 @@
159 };
160
161 ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
162- insertAction("ok_id", "Ok");
163- insertAction("cancel_id", "Cancel");
164 }
165
166 ActionModel::~ActionModel() {
167@@ -66,7 +64,11 @@
168 return data(index(row, 0), role);
169 }
170
171-void ActionModel::insertAction(const QString &id, const QString &label) {
172+void ActionModel::append(const QString &id, const QString &label) {
173 p->ids.push_back(id);
174 p->labels.push_back(label);
175 }
176+
177+int ActionModel::getCount() const {
178+ return p->labels.size();
179+}
180
181=== modified file 'tests/mocks/Unity/Notifications/MockActionModel.h'
182--- tests/mocks/Unity/Notifications/MockActionModel.h 2014-10-23 22:01:57 +0000
183+++ tests/mocks/Unity/Notifications/MockActionModel.h 2015-02-06 15:16:01 +0000
184@@ -1,5 +1,5 @@
185 /*
186- * Copyright 2014 Canonical Ltd.
187+ * Copyright 2015 Canonical Ltd.
188 *
189 * This program is free software; you can redistribute it and/or modify
190 * it under the terms of the GNU Lesser General Public License as published by
191@@ -26,6 +26,7 @@
192
193 class ActionModel : public QStringListModel {
194 Q_OBJECT
195+ Q_PROPERTY(int count READ getCount)
196
197 public:
198 ActionModel(QObject *parent=nullptr);
199@@ -42,7 +43,8 @@
200 };
201 Q_INVOKABLE QVariant data(int row, int role) const;
202
203- void insertAction(const QString &id, const QString &label);
204+ Q_INVOKABLE void append(const QString &id, const QString &label);
205+ int getCount() const;
206
207 private:
208 QScopedPointer<ActionModelPrivate> p;
209
210=== renamed file 'tests/mocks/Unity/Notifications/MockNotificationTypes.cpp' => 'tests/mocks/Unity/Notifications/MockNotification.cpp'
211--- tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2014-10-23 22:01:57 +0000
212+++ tests/mocks/Unity/Notifications/MockNotification.cpp 2015-02-06 15:16:01 +0000
213@@ -1,5 +1,5 @@
214 /*
215- * Copyright 2014 Canonical Ltd.
216+ * Copyright 2015 Canonical Ltd.
217 *
218 * This program is free software; you can redistribute it and/or modify
219 * it under the terms of the GNU Lesser General Public License as published by
220@@ -17,10 +17,162 @@
221 * Mirco Mueller <mirco.mueller@canonical.com>
222 */
223
224-#include "MockNotificationTypes.h"
225-
226-MockNotification::MockNotification(QObject *parent) : QObject(parent) {
227+#include "MockNotification.h"
228+
229+#include <QDebug>
230+
231+struct MockNotificationPrivate {
232+ int id;
233+ QString summary;
234+ QString body;
235+ int value;
236+ MockNotification::Type type;
237+ QString icon;
238+ QString secondaryIcon;
239+ QStringList actions;
240+ ActionModel* actionsModel;
241+ QVariantMap hints;
242+};
243+
244+MockNotification::MockNotification(QObject *parent) : QObject(parent), p(new MockNotificationPrivate()) {
245+ p->actionsModel = new ActionModel();
246 }
247
248 MockNotification::~MockNotification() {
249 }
250+
251+QString MockNotification::getSummary() const {
252+ return p->summary;
253+}
254+
255+void MockNotification::setSummary(const QString &summary) {
256+ if(p->summary != summary) {
257+ p->summary = summary;
258+ Q_EMIT summaryChanged(p->summary);
259+ Q_EMIT dataChanged(p->id);
260+ }
261+}
262+
263+QString MockNotification::getBody() const {
264+ return p->body;
265+}
266+
267+void MockNotification::setBody(const QString &body) {
268+ if(p->body != body) {
269+ p->body = body;
270+ Q_EMIT bodyChanged(p->body);
271+ Q_EMIT dataChanged(p->id);
272+ }
273+}
274+
275+int MockNotification::getID() const {
276+ return p->id;
277+}
278+
279+void MockNotification::setID(const int id) {
280+ p->id = id;
281+}
282+
283+int MockNotification::getValue() const {
284+ return p->value;
285+}
286+
287+void MockNotification::setValue(int value) {
288+ if(p->value != value) {
289+ p->value = value;
290+ Q_EMIT valueChanged(p->value);
291+ Q_EMIT dataChanged(p->id);
292+ }
293+}
294+
295+QString MockNotification::getIcon() const {
296+ return p->icon;
297+}
298+
299+void MockNotification::setIcon(const QString &icon) {
300+ if (icon.startsWith(" ") || icon.size() == 0) {
301+ p->icon = nullptr;
302+ }
303+ else {
304+ p->icon = icon;
305+
306+ if (icon.indexOf("/") == -1) {
307+ p->icon.prepend("image://theme/");
308+ }
309+ }
310+
311+ Q_EMIT iconChanged(p->icon);
312+ Q_EMIT dataChanged(p->id);
313+}
314+
315+QString MockNotification::getSecondaryIcon() const {
316+ return p->secondaryIcon;
317+}
318+
319+void MockNotification::setSecondaryIcon(const QString &secondaryIcon) {
320+ if (secondaryIcon.startsWith(" ") || secondaryIcon.size() == 0) {
321+ p->secondaryIcon = nullptr;
322+ }
323+ else {
324+ p->secondaryIcon = secondaryIcon;
325+
326+ if (secondaryIcon.indexOf("/") == -1) {
327+ p->secondaryIcon.prepend("image://theme/");
328+ }
329+ }
330+
331+ Q_EMIT secondaryIconChanged(p->secondaryIcon);
332+ Q_EMIT dataChanged(p->id);
333+}
334+
335+MockNotification::Type MockNotification::getType() const {
336+ return p->type;
337+}
338+
339+void MockNotification::setType(Type type) {
340+ if(p->type != type) {
341+ p->type = type;
342+ Q_EMIT typeChanged(p->type);
343+ }
344+}
345+
346+ActionModel* MockNotification::getActions() const {
347+ return p->actionsModel;
348+}
349+
350+void MockNotification::setActions(const QStringList &actions) {
351+ if(p->actions != actions) {
352+ p->actions = actions;
353+ Q_EMIT actionsChanged(p->actions);
354+
355+ for (int i = 0; i < p->actions.size(); i += 2) {
356+ p->actionsModel->append(p->actions[i], p->actions[i+1]);
357+ }
358+ }
359+}
360+
361+QVariantMap MockNotification::getHints() const {
362+ return p->hints;
363+}
364+
365+void MockNotification::setHints(const QVariantMap& hints) {
366+ if (p->hints != hints) {
367+ p->hints = hints;
368+ Q_EMIT hintsChanged(p->hints);
369+ }
370+}
371+
372+void MockNotification::invokeAction(const QString &action) {
373+ for(int i=0; i<p->actions.size(); i++) {
374+ if(p->actions[i] == action) {
375+ Q_EMIT actionInvoked(action);
376+ qDebug() << "Info: invoked action" << action;
377+ return;
378+ }
379+ }
380+ fprintf(stderr, "Error: tried to invoke action not in actionList.\n");
381+}
382+
383+void MockNotification::close() {
384+ Q_EMIT completed(p->id);
385+}
386
387=== renamed file 'tests/mocks/Unity/Notifications/MockNotificationTypes.h' => 'tests/mocks/Unity/Notifications/MockNotification.h'
388--- tests/mocks/Unity/Notifications/MockNotificationTypes.h 2014-10-23 22:01:57 +0000
389+++ tests/mocks/Unity/Notifications/MockNotification.h 2015-02-06 15:16:01 +0000
390@@ -1,5 +1,5 @@
391 /*
392- * Copyright 2014 Canonical Ltd.
393+ * Copyright 2015 Canonical Ltd.
394 *
395 * This program is free software; you can redistribute it and/or modify
396 * it under the terms of the GNU Lesser General Public License as published by
397@@ -17,20 +17,78 @@
398 * Mirco Mueller <mirco.mueller@canonical.com>
399 */
400
401-#ifndef MOCK_NOTIFICATION_TYPES_H
402-#define MOCK_NOTIFICATION_TYPES_H
403+#ifndef MOCK_NOTIFICATION_H
404+#define MOCK_NOTIFICATION_H
405
406+#include "MockActionModel.h"
407 #include <QObject>
408+#include <QString>
409+#include <QStringList>
410+#include <QScopedPointer>
411+
412+struct MockNotificationPrivate;
413
414 class MockNotification : public QObject {
415 Q_OBJECT
416 Q_ENUMS(Type)
417+ Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged)
418+ Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
419+ Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged)
420+ Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)
421+ Q_PROPERTY(QString icon READ getIcon WRITE setIcon NOTIFY iconChanged)
422+ Q_PROPERTY(QString secondaryIcon READ getSecondaryIcon WRITE setSecondaryIcon NOTIFY secondaryIconChanged)
423+ Q_PROPERTY(Type type READ getType WRITE setType NOTIFY typeChanged)
424+ Q_PROPERTY(QStringList rawActions WRITE setActions)
425+ Q_PROPERTY(ActionModel* actions READ getActions NOTIFY actionsChanged)
426+ Q_PROPERTY(QVariantMap hints READ getHints WRITE setHints NOTIFY hintsChanged)
427+
428+private:
429+ QScopedPointer<MockNotificationPrivate> p;
430+
431+public:
432+ enum Urgency { Low, Normal, Critical };
433+ enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
434+
435+Q_SIGNALS:
436+ void summaryChanged(const QString &summary);
437+ void bodyChanged(const QString &body);
438+ void idChanged(const int id);
439+ void valueChanged(int value);
440+ void iconChanged(const QString &icon);
441+ void secondaryIconChanged(const QString &secondaryIcon);
442+ void typeChanged(Type type);
443+ void actionsChanged(const QStringList &actions);
444+ void hintsChanged(const QVariantMap& hints);
445+
446+ void dataChanged(int nid);
447+ void completed(int nid);
448+ void actionInvoked(const QString &action);
449
450 public:
451 MockNotification(QObject *parent=nullptr);
452 virtual ~MockNotification();
453
454- enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
455+ QString getSummary() const;
456+ void setSummary(const QString &summary);
457+ QString getBody() const;
458+ void setBody(const QString &body);
459+ int getID() const;
460+ void setID(const int id);
461+ int getValue() const;
462+ void setValue(int value);
463+ QString getIcon() const;
464+ void setIcon(const QString &icon);
465+ QString getSecondaryIcon() const;
466+ void setSecondaryIcon(const QString &secondaryIcon);
467+ Type getType() const;
468+ void setType(Type type);
469+ ActionModel* getActions() const;
470+ void setActions(const QStringList &actions);
471+ QVariantMap getHints() const;
472+ void setHints(const QVariantMap& hints);
473+
474+ Q_INVOKABLE void invokeAction(const QString &action);
475+ Q_INVOKABLE void close();
476 };
477
478-#endif // MOCK_NOTIFICATION_TYPES_H
479+#endif // MOCK_NOTIFICATION_H
480
481=== added file 'tests/mocks/Unity/Notifications/MockNotificationModel.cpp'
482--- tests/mocks/Unity/Notifications/MockNotificationModel.cpp 1970-01-01 00:00:00 +0000
483+++ tests/mocks/Unity/Notifications/MockNotificationModel.cpp 2015-02-06 15:16:01 +0000
484@@ -0,0 +1,172 @@
485+/*
486+ * Copyright 2015 Canonical Ltd.
487+ *
488+ * This program is free software; you can redistribute it and/or modify
489+ * it under the terms of the GNU Lesser General Public License as published by
490+ * the Free Software Foundation; version 3.
491+ *
492+ * This program is distributed in the hope that it will be useful,
493+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
494+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
495+ * GNU Lesser General Public License for more details.
496+ *
497+ * You should have received a copy of the GNU Lesser General Public License
498+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
499+ *
500+ * Authors:
501+ * Mirco Mueller <mirco.mueller@canonical.com>
502+ */
503+
504+#include "MockNotificationModel.h"
505+#include "MockNotification.h"
506+
507+#include <unity/shell/notifications/ModelInterface.h>
508+
509+#include <QTimer>
510+#include <QList>
511+#include <QVector>
512+#include <QMap>
513+#include <QStringListModel>
514+#include <QQmlEngine>
515+
516+using namespace unity::shell::notifications;
517+
518+MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListModel(parent) {
519+}
520+
521+MockNotificationModel::~MockNotificationModel() {
522+}
523+
524+int MockNotificationModel::rowCount(const QModelIndex &parent) const {
525+ return m_queue.size();
526+}
527+
528+int MockNotificationModel::getCount() const {
529+ return m_queue.size();
530+}
531+
532+QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
533+ if (!index.isValid())
534+ return QVariant();
535+
536+ switch(role) {
537+ case ModelInterface::RoleType:
538+ return QVariant(m_queue[index.row()]->getType());
539+
540+ case ModelInterface::RoleId:
541+ return QVariant(m_queue[index.row()]->getID());
542+
543+ case ModelInterface::RoleSummary:
544+ return QVariant(m_queue[index.row()]->getSummary());
545+
546+ case ModelInterface::RoleBody:
547+ return QVariant(m_queue[index.row()]->getBody());
548+
549+ case ModelInterface::RoleValue:
550+ return QVariant(m_queue[index.row()]->getValue());
551+
552+ case ModelInterface::RoleIcon:
553+ return QVariant(m_queue[index.row()]->getIcon());
554+
555+ case ModelInterface::RoleSecondaryIcon:
556+ return QVariant(m_queue[index.row()]->getSecondaryIcon());
557+
558+ case ModelInterface::RoleActions:
559+ return QVariant::fromValue(m_queue[index.row()]->getActions());
560+
561+ case ModelInterface::RoleHints:
562+ return QVariant(m_queue[index.row()]->getHints());
563+
564+ case ModelInterface::RoleNotification:
565+ return QVariant::fromValue(m_queue[index.row()]);
566+
567+ default:
568+ return QVariant();
569+ }
570+}
571+
572+void MockNotificationModel::append(MockNotification* n) {
573+ int location = m_queue.size();
574+ QModelIndex insertionPoint = QModelIndex();
575+ beginInsertRows(insertionPoint, location, location);
576+ m_queue.insert(location, n);
577+ endInsertRows();
578+}
579+
580+MockNotification* MockNotificationModel::getNotification(int id) const {
581+ for(int i=0; i < m_queue.size(); i++) {
582+ if(m_queue[i]->getID() == id) {
583+ return m_queue[i];
584+ }
585+ }
586+
587+ return nullptr;
588+}
589+
590+void MockNotificationModel::remove(const int id) {
591+ for(int i = 0; i < m_queue.size(); i++) {
592+ if(m_queue[i]->getID() == id) {
593+ removeInternal(i);
594+ return;
595+ }
596+ }
597+}
598+
599+void MockNotificationModel::removeSecond() {
600+ if(m_queue.size() < 2)
601+ return;
602+ removeInternal(1);
603+}
604+
605+void MockNotificationModel::removeInternal(int loc) {
606+ QModelIndex deletePoint = QModelIndex();
607+ beginRemoveRows(deletePoint, loc, loc);
608+ m_queue.erase(m_queue.begin() + loc);
609+ endRemoveRows();
610+}
611+
612+MockNotification* MockNotificationModel::getRaw(const int notificationId) const {
613+ for(int i = 0; i < m_queue.size(); i++) {
614+ if(m_queue[i]->getID() == notificationId) {
615+ MockNotification* n = m_queue[i];
616+ return n;
617+ }
618+ }
619+
620+ return nullptr;
621+}
622+
623+int MockNotificationModel::queued() const {
624+ return m_queue.size();
625+}
626+
627+QHash<int, QByteArray> MockNotificationModel::roleNames() const {
628+ QHash<int, QByteArray> roles;
629+
630+ roles.insert(ModelInterface::RoleType, "type");
631+ roles.insert(ModelInterface::RoleUrgency, "urgency");
632+ roles.insert(ModelInterface::RoleId, "id");
633+ roles.insert(ModelInterface::RoleSummary, "summary");
634+ roles.insert(ModelInterface::RoleBody, "body");
635+ roles.insert(ModelInterface::RoleValue, "value");
636+ roles.insert(ModelInterface::RoleIcon, "icon");
637+ roles.insert(ModelInterface::RoleSecondaryIcon, "secondaryIcon");
638+ roles.insert(ModelInterface::RoleActions, "actions");
639+ roles.insert(ModelInterface::RoleHints, "hints");
640+ roles.insert(ModelInterface::RoleNotification, "notification");
641+
642+ return roles;
643+}
644+
645+void MockNotificationModel::onCompleted(int id) {
646+ remove(id);
647+}
648+
649+void MockNotificationModel::onDataChanged(int id) {
650+ for(int i = 0; i < m_queue.size(); i++) {
651+ if(m_queue[i]->getID() == id) {
652+ Q_EMIT dataChanged(index(i, 0), index(i, 0));
653+ break;
654+ }
655+ }
656+}
657
658=== added file 'tests/mocks/Unity/Notifications/MockNotificationModel.h'
659--- tests/mocks/Unity/Notifications/MockNotificationModel.h 1970-01-01 00:00:00 +0000
660+++ tests/mocks/Unity/Notifications/MockNotificationModel.h 2015-02-06 15:16:01 +0000
661@@ -0,0 +1,72 @@
662+/*
663+ * Copyright 2015 Canonical Ltd.
664+ *
665+ * This program is free software; you can redistribute it and/or modify
666+ * it under the terms of the GNU Lesser General Public License as published by
667+ * the Free Software Foundation; version 3.
668+ *
669+ * This program is distributed in the hope that it will be useful,
670+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
671+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
672+ * GNU Lesser General Public License for more details.
673+ *
674+ * You should have received a copy of the GNU Lesser General Public License
675+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
676+ *
677+ * Authors:
678+ * Mirco Mueller <mirco.mueller@canonical.com>
679+ */
680+
681+#ifndef MOCK_NOTIFICATION_MODEL_H
682+#define MOCK_NOTIFICATION_MODEL_H
683+
684+#include <QAbstractListModel>
685+#include <QSharedPointer>
686+#include <QScopedPointer>
687+#include "MockNotification.h"
688+
689+class MockNotification;
690+
691+class MockNotificationModel : public QAbstractListModel {
692+ Q_OBJECT
693+ Q_PROPERTY(int count READ getCount)
694+
695+public:
696+ MockNotificationModel(QObject *parent=nullptr);
697+ virtual ~MockNotificationModel();
698+
699+ virtual int rowCount(const QModelIndex &parent) const;
700+ virtual QVariant data(const QModelIndex &index, int role) const;
701+ virtual QHash<int, QByteArray> roleNames() const;
702+
703+ Q_INVOKABLE void append(MockNotification* n);
704+ MockNotification* getNotification(int id) const;
705+
706+ // getRaw() is only meant to be used from QML, since QML cannot handle
707+ // QSharedPointers... on C++-side only use getNotification()
708+ Q_INVOKABLE MockNotification* getRaw(const int notificationId) const;
709+
710+ Q_INVOKABLE int queued() const;
711+ Q_INVOKABLE void remove(const int id);
712+ Q_INVOKABLE void removeSecond();
713+
714+ int getCount() const;
715+
716+Q_SIGNALS:
717+ void actionInvoked(const QString &action);
718+
719+public Q_SLOTS:
720+ void onCompleted(int id);
721+
722+private Q_SLOTS:
723+ void onDataChanged(int id);
724+
725+Q_SIGNALS:
726+ void queueSizeChanged(int newSize);
727+
728+private:
729+ QList<MockNotification*> m_queue;
730+ void removeInternal(int loc);
731+};
732+
733+#endif
734
735=== modified file 'tests/mocks/Unity/Notifications/plugin.cpp'
736--- tests/mocks/Unity/Notifications/plugin.cpp 2014-10-23 22:01:57 +0000
737+++ tests/mocks/Unity/Notifications/plugin.cpp 2015-02-06 15:16:01 +0000
738@@ -1,5 +1,5 @@
739 /*
740- * Copyright 2014 Canonical Ltd.
741+ * Copyright 2015 Canonical Ltd.
742 *
743 * This program is free software; you can redistribute it and/or modify
744 * it under the terms of the GNU Lesser General Public License as published by
745@@ -19,13 +19,15 @@
746
747 #include "plugin.h"
748 #include "MockActionModel.h"
749-#include "MockNotificationTypes.h"
750+#include "MockNotification.h"
751+#include "MockNotificationModel.h"
752
753 #include <QtQml/qqml.h>
754
755 void TestNotificationPlugin::registerTypes(const char* uri)
756 {
757 // @uri Unity.Notifications
758- qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");
759+ qmlRegisterType<MockNotification>(uri, 1, 0, "Notification");
760+ qmlRegisterType<MockNotificationModel>(uri, 1, 0, "NotificationModel");
761 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
762 }
763
764=== modified file 'tests/mocks/Unity/Notifications/plugin.h'
765--- tests/mocks/Unity/Notifications/plugin.h 2014-10-23 22:01:57 +0000
766+++ tests/mocks/Unity/Notifications/plugin.h 2015-02-06 15:16:01 +0000
767@@ -1,5 +1,5 @@
768 /*
769- * Copyright 2014 Canonical Ltd.
770+ * Copyright 2015 Canonical Ltd.
771 *
772 * This program is free software; you can redistribute it and/or modify
773 * it under the terms of the GNU Lesser General Public License as published by
774@@ -17,7 +17,6 @@
775 * Mirco Mueller <mirco.mueller@canonical.com>
776 */
777
778-
779 #ifndef TESTNOTIFICATION_PLUGIN_H
780 #define TESTNOTIFICATION_PLUGIN_H
781
782
783=== added file 'tests/qmltests/Notifications/Notification.qml'
784--- tests/qmltests/Notifications/Notification.qml 1970-01-01 00:00:00 +0000
785+++ tests/qmltests/Notifications/Notification.qml 2015-02-06 15:16:01 +0000
786@@ -0,0 +1,31 @@
787+/*
788+ * Copyright 2015 Canonical Ltd.
789+ *
790+ * This program is free software; you can redistribute it and/or modify
791+ * it under the terms of the GNU Lesser General Public License as published by
792+ * the Free Software Foundation; version 3.
793+ *
794+ * This program is distributed in the hope that it will be useful,
795+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
796+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
797+ * GNU Lesser General Public License for more details.
798+ *
799+ * You should have received a copy of the GNU Lesser General Public License
800+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
801+ *
802+ * Authors:
803+ * Mirco Mueller <mirco.mueller@canonical.com>
804+ */
805+
806+import Unity.Notifications 1.0
807+
808+Notification {
809+ nid: 0
810+ type: Notification.PlaceHolder
811+ summary: ""
812+ body: ""
813+ icon: ""
814+ secondaryIcon: ""
815+ value: 0
816+ rawActions: []
817+}
818
819=== modified file 'tests/qmltests/Notifications/tst_Notifications.qml'
820--- tests/qmltests/Notifications/tst_Notifications.qml 2015-01-09 09:15:45 +0000
821+++ tests/qmltests/Notifications/tst_Notifications.qml 2015-02-06 15:16:01 +0000
822@@ -1,17 +1,20 @@
823 /*
824- * Copyright (C) 2013 Canonical, Ltd.
825+ * Copyright 2015 Canonical Ltd.
826 *
827 * This program is free software; you can redistribute it and/or modify
828- * it under the terms of the GNU General Public License as published by
829+ * it under the terms of the GNU Lesser General Public License as published by
830 * the Free Software Foundation; version 3.
831 *
832 * This program is distributed in the hope that it will be useful,
833 * but WITHOUT ANY WARRANTY; without even the implied warranty of
834 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
835- * GNU General Public License for more details.
836+ * GNU Lesser General Public License for more details.
837 *
838- * You should have received a copy of the GNU General Public License
839+ * You should have received a copy of the GNU Lesser General Public License
840 * along with this program. If not, see <http://www.gnu.org/licenses/>.
841+ *
842+ * Authors:
843+ * Mirco Mueller <mirco.mueller@canonical.com>
844 */
845
846 import QtQuick 2.0
847@@ -24,146 +27,134 @@
848 import QtMultimedia 5.0
849
850 Item {
851+ id: foobar
852+
853 width: notificationsRect.width + interactiveControls.width
854 height: notificationsRect.height
855+ property int index: 0
856
857 Row {
858 id: rootRow
859
860- Component {
861- id: mockNotification
862-
863- QtObject {
864- function invokeAction(actionId) {
865- mockModel.actionInvoked(actionId)
866- }
867- }
868- }
869-
870- ListModel {
871+ NotificationModel {
872 id: mockModel
873- dynamicRoles: true
874-
875- signal actionInvoked(string actionId)
876-
877- function getRaw(id) {
878- return mockNotification.createObject(mockModel)
879- }
880
881 // add the default/PlaceHolder notification to the model
882 Component.onCompleted: {
883- var n = {
884- type: Notification.PlaceHolder,
885- hints: {},
886- summary: "",
887- body: "",
888- icon: "",
889- secondaryIcon: "",
890- actions: []
891- }
892-
893+ var component = Qt.createComponent("Notification.qml")
894+ var n = component.createObject("notification", {"nid": index++,
895+ "type": Notification.PlaceHolder,
896+ "hints": {},
897+ "summary": "",
898+ "body": "",
899+ "icon": "",
900+ "secondaryIcon": "",
901+ "rawActions": []})
902+ n.completed.connect(mockModel.onCompleted)
903 append(n)
904 }
905 }
906
907 function add2over1SnapDecisionNotification() {
908- var n = {
909- type: Notification.SnapDecision,
910- hints: {"x-canonical-private-affirmative-tint": "true"},
911- summary: "Theatre at Ferria Stadium",
912- body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
913- icon: "",
914- secondaryIcon: "",
915- actions: [{ id: "ok_id", label: "Ok"},
916- { id: "snooze_id", label: "Snooze"},
917- { id: "view_id", label: "View"}]
918- }
919-
920+ var component = Qt.createComponent("Notification.qml")
921+ var n = component.createObject("notification", {"nid": index++,
922+ "type": Notification.SnapDecision,
923+ "hints": {"x-canonical-private-affirmative-tint": "true"},
924+ "summary": "Theatre at Ferria Stadium",
925+ "body": "at Ferria Stadium in Bilbao, Spain\n07578545317",
926+ "icon": "",
927+ "secondaryIcon": "",
928+ "rawActions": ["ok_id", "Ok",
929+ "snooze_id", "Snooze",
930+ "view_id", "View"]})
931+ n.completed.connect(mockModel.onCompleted)
932 mockModel.append(n)
933 }
934
935 function addEphemeralNotification() {
936- var n = {
937- type: Notification.Ephemeral,
938- summary: "Cole Raby",
939- body: "I did not expect it to be that late.",
940- icon: "../graphics/avatars/amanda.png",
941- secondaryIcon: "../graphics/applicationIcons/facebook.png",
942- actions: []
943- }
944-
945+ var component = Qt.createComponent("Notification.qml")
946+ var n = component.createObject("notification", {"nid": index++,
947+ "type": Notification.Ephemeral,
948+ "hints": {},
949+ "summary": "Cole Raby",
950+ "body": "I did not expect it to be that late.",
951+ "icon": "../graphics/avatars/amanda.png",
952+ "secondaryIcon": "../graphics/applicationIcons/facebook.png",
953+ "rawActions": ["reply_id", "Dummy"]})
954+ n.completed.connect(mockModel.onCompleted)
955 mockModel.append(n)
956 }
957
958 function addEphemeralNonShapedIconNotification() {
959- var n = {
960- type: Notification.Ephemeral,
961- hints: {"x-canonical-non-shaped-icon": "true"},
962- summary: "Contacts",
963- body: "Synchronised contacts-database with cloud-storage.",
964- icon: "../graphics/applicationIcons/contacts-app.png",
965- secondaryIcon: "",
966- actions: []
967- }
968-
969+ var component = Qt.createComponent("Notification.qml")
970+ var n = component.createObject("notification", {"nid": index++,
971+ "type": Notification.Ephemeral,
972+ "hints": {"x-canonical-non-shaped-icon": "true"},
973+ "summary": "Contacts",
974+ "body": "Synchronised contacts-database with cloud-storage.",
975+ "icon": "../graphics/applicationIcons/contacts-app.png",
976+ "secondaryIcon": "",
977+ "rawActions": ["reply_id", "Dummy"]})
978+ n.completed.connect(mockModel.onCompleted)
979 mockModel.append(n)
980 }
981
982 function addEphemeralIconSummaryNotification() {
983- var n = {
984- type: Notification.Ephemeral,
985- hints: {"x-canonical-non-shaped-icon": "false"},
986- summary: "Photo upload completed",
987- body: "",
988- icon: "../graphics/applicationIcons/facebook.png",
989- secondaryIcon: "",
990- actions: []
991- }
992-
993+ var component = Qt.createComponent("Notification.qml")
994+ var n = component.createObject("notification", {"nid": index++,
995+ "type": Notification.Ephemeral,
996+ "hints": {"x-canonical-non-shaped-icon": "false"},
997+ "summary": "Photo upload completed",
998+ "body": "",
999+ "icon": "../graphics/applicationIcons/facebook.png",
1000+ "secondaryIcon": "",
1001+ "rawActions": ["reply_id", "Dummy"]})
1002+ n.completed.connect(mockModel.onCompleted)
1003 mockModel.append(n)
1004 }
1005
1006 function addInteractiveNotification() {
1007- var n = {
1008- type: Notification.Interactive,
1009- summary: "Interactive notification",
1010- body: "This is a notification that can be clicked",
1011- icon: "../graphics/avatars/anna_olsson.png",
1012- secondaryIcon: "",
1013- actions: [{ id: "reply_id", label: "Dummy"}],
1014- }
1015-
1016+ var component = Qt.createComponent("Notification.qml")
1017+ var n = component.createObject("notification", {"nid": index++,
1018+ "type": Notification.Interactive,
1019+ "hints": {},
1020+ "summary": "Interactive notification",
1021+ "body": "This is a notification that can be clicked",
1022+ "icon": "../graphics/avatars/anna_olsson.png",
1023+ "secondaryIcon": "",
1024+ "rawActions": ["reply_id", "Dummy"]})
1025+ n.completed.connect(mockModel.onCompleted)
1026 mockModel.append(n)
1027 }
1028
1029 function addConfirmationNotification() {
1030- var n = {
1031- type: Notification.Confirmation,
1032- hints: {"x-canonical-non-shaped-icon": "true"},
1033- summary: "Confirmation notification",
1034- body: "",
1035- icon: "image://theme/audio-volume-medium",
1036- secondaryIcon: "",
1037- value: 50,
1038- actions: [],
1039- }
1040-
1041+ var component = Qt.createComponent("Notification.qml")
1042+ var n = component.createObject("notification", {"nid": index++,
1043+ "type": Notification.Confirmation,
1044+ "hints": {"x-canonical-non-shaped-icon": "true"},
1045+ "summary": "Confirmation notification",
1046+ "body": "",
1047+ "icon": "image://theme/audio-volume-medium",
1048+ "secondaryIcon": "",
1049+ "value": 50,
1050+ "rawActions": ["reply_id", "Dummy"]})
1051+ n.completed.connect(mockModel.onCompleted)
1052 mockModel.append(n)
1053 }
1054
1055 function add2ndConfirmationNotification() {
1056- var n = {
1057- type: Notification.Confirmation,
1058- hints: {"x-canonical-non-shaped-icon": "true",
1059- "x-canonical-value-bar-tint": "true"},
1060- summary: "Confirmation notification",
1061- body: "High Volume",
1062- icon: "image://theme/audio-volume-high",
1063- secondaryIcon: "",
1064- value: 85,
1065- actions: [],
1066- }
1067-
1068+ var component = Qt.createComponent("Notification.qml")
1069+ var n = component.createObject("notification", {"nid": index++,
1070+ "type": Notification.Confirmation,
1071+ "hints": {"x-canonical-non-shaped-icon": "true",
1072+ "x-canonical-value-bar-tint": "true"},
1073+ "summary": "Confirmation notification",
1074+ "body": "High Volume",
1075+ "icon": "image://theme/audio-volume-high",
1076+ "secondaryIcon": "",
1077+ "value": 85,
1078+ "rawActions": ["reply_id", "Dummy"]})
1079+ n.completed.connect(mockModel.onCompleted)
1080 mockModel.append(n)
1081 }
1082
1083@@ -174,8 +165,9 @@
1084 }
1085
1086 function remove1stNotification() {
1087- if (mockModel.count > 1)
1088- mockModel.remove(1)
1089+ if (mockModel.count > 1) {
1090+ mockModel.removeSecond()
1091+ }
1092 }
1093
1094 Rectangle {
1095@@ -273,41 +265,122 @@
1096 name: "NotificationRendererTest"
1097 when: windowShown
1098
1099+ property list<Notification> nlist: [
1100+ Notification {
1101+ nid: 1
1102+ type: Notification.Ephemeral
1103+ summary: "Photo upload completed"
1104+ body: ""
1105+ icon: "../graphics/applicationIcons/facebook.png"
1106+ secondaryIcon: ""
1107+ value: 0
1108+ rawActions: []
1109+ },
1110+ Notification {
1111+ nid: 2
1112+ type: Notification.Ephemeral
1113+ hints: {"x-canonical-private-affirmative-tint": "false",
1114+ "sound-file": "dummy.ogg",
1115+ "suppress-sound": "true"}
1116+ summary: "New comment successfully published"
1117+ body: ""
1118+ icon: ""
1119+ secondaryIcon: "../graphics/applicationIcons/facebook.png"
1120+ value: 0
1121+ rawActions: []
1122+ },
1123+ Notification {
1124+ nid: 3
1125+ type: Notification.Interactive
1126+ hints: {"x-canonical-private-affirmative-tint": "false",
1127+ "sound-file": "dummy.ogg"}
1128+ summary: "Interactive notification"
1129+ body: "This is a notification that can be clicked"
1130+ icon: "../graphics/avatars/amanda.png"
1131+ secondaryIcon: ""
1132+ value: 0
1133+ rawActions: ["reply_id", "Dummy"]
1134+ },
1135+ Notification {
1136+ nid: 4
1137+ type: Notification.SnapDecision
1138+ hints: {"x-canonical-private-affirmative-tint": "false",
1139+ "sound-file": "dummy.ogg"}
1140+ summary: "Bro Coly"
1141+ body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
1142+ icon: "../graphics/avatars/anna_olsson.png"
1143+ secondaryIcon: ""
1144+ value: 0
1145+ rawActions: ["accept_id", "Accept",
1146+ "reject_id", "Reject"]
1147+ },
1148+ Notification {
1149+ nid: 5
1150+ type: Notification.Ephemeral
1151+ hints: {"x-canonical-private-affirmative-tint": "false",
1152+ "sound-file": "dummy.ogg"}
1153+ summary: "Cole Raby"
1154+ body: "I did not expect it to be that late."
1155+ icon: "../graphics/avatars/funky.png"
1156+ secondaryIcon: "../graphics/applicationIcons/facebook.png"
1157+ value: 0
1158+ rawActions: []
1159+ },
1160+ Notification {
1161+ nid: 6
1162+ type: Notification.Ephemeral
1163+ hints: {"x-canonical-private-affirmative-tint": "false",
1164+ "x-canonical-non-shaped-icon": "true"}
1165+ summary: "Contacts"
1166+ body: "Synchronised contacts-database with cloud-storage."
1167+ icon: "image://theme/contacts-app"
1168+ secondaryIcon: ""
1169+ value: 0
1170+ rawActions: []
1171+ },
1172+ Notification {
1173+ nid: 7
1174+ type: Notification.Confirmation
1175+ hints: {"x-canonical-non-shaped-icon": "true"}
1176+ summary: ""
1177+ body: ""
1178+ icon: "image://theme/audio-volume-medium"
1179+ secondaryIcon: ""
1180+ value: 50
1181+ rawActions: []
1182+ },
1183+ Notification {
1184+ nid: 8
1185+ type: Notification.Confirmation
1186+ hints: {"x-canonical-non-shaped-icon": "true",
1187+ "x-canonical-value-bar-tint" : "true"}
1188+ summary: ""
1189+ body: "High Volume"
1190+ icon: "image://theme/audio-volume-high"
1191+ secondaryIcon: ""
1192+ value: 85
1193+ rawActions: []
1194+ },
1195+ Notification {
1196+ nid: 9
1197+ type: Notification.SnapDecision
1198+ hints: {"x-canonical-private-affirmative-tint": "true"}
1199+ summary: "Theatre at Ferria Stadium"
1200+ body: "at Ferria Stadium in Bilbao, Spain\n07578545317"
1201+ icon: ""
1202+ secondaryIcon: ""
1203+ value: 0
1204+ rawActions: ["ok_id", "Ok",
1205+ "snooze_id", "Snooze",
1206+ "view_id", "View"]
1207+ }
1208+ ]
1209+
1210 function test_NotificationRenderer_data() {
1211 return [
1212 {
1213- tag: "2-over-1 Snap Decision with button-tint",
1214- type: Notification.SnapDecision,
1215- hints: {"x-canonical-private-affirmative-tint": "true"},
1216- summary: "Theatre at Ferria Stadium",
1217- body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
1218- icon: "",
1219- secondaryIcon: "",
1220- actions: [{ id: "ok_id", label: "Ok"},
1221- { id: "snooze_id", label: "Snooze"},
1222- { id: "view_id", label: "View"}],
1223- summaryVisible: true,
1224- bodyVisible: true,
1225- iconVisible: false,
1226- centeredIconVisible: false,
1227- shaped: false,
1228- secondaryIconVisible: false,
1229- buttonRowVisible: false,
1230- buttonTinted: true,
1231- hasSound: false,
1232- valueVisible: false,
1233- valueLabelVisible: false,
1234- valueTinted: false
1235- },
1236- {
1237 tag: "Ephemeral notification - icon-summary layout",
1238- type: Notification.Ephemeral,
1239- hints: {},
1240- summary: "Photo upload completed",
1241- body: "",
1242- icon: "../graphics/applicationIcons/facebook.png",
1243- secondaryIcon: "",
1244- actions: [],
1245+ n: nlist[0],
1246 summaryVisible: true,
1247 bodyVisible: false,
1248 iconVisible: true,
1249@@ -323,15 +396,7 @@
1250 },
1251 {
1252 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
1253- type: Notification.Ephemeral,
1254- hints: {"x-canonical-private-affirmative-tint": "false",
1255- "sound-file": "dummy.ogg",
1256- "suppress-sound": "true"},
1257- summary: "New comment successfully published",
1258- body: "",
1259- icon: "",
1260- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1261- actions: [],
1262+ n: nlist[1],
1263 summaryVisible: true,
1264 bodyVisible: false,
1265 interactiveAreaEnabled: false,
1266@@ -348,14 +413,7 @@
1267 },
1268 {
1269 tag: "Interactive notification",
1270- type: Notification.Interactive,
1271- hints: {"x-canonical-private-affirmative-tint": "false",
1272- "sound-file": "dummy.ogg"},
1273- summary: "Interactive notification",
1274- body: "This is a notification that can be clicked",
1275- icon: "../graphics/avatars/amanda.png",
1276- secondaryIcon: "",
1277- actions: [{ id: "reply_id", label: "Dummy"}],
1278+ n: nlist[2],
1279 summaryVisible: true,
1280 bodyVisible: true,
1281 iconVisible: true,
1282@@ -371,15 +429,7 @@
1283 },
1284 {
1285 tag: "Snap Decision without secondary icon and no button-tint",
1286- type: Notification.SnapDecision,
1287- hints: {"x-canonical-private-affirmative-tint": "false",
1288- "sound-file": "dummy.ogg"},
1289- summary: "Bro Coly",
1290- body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
1291- icon: "../graphics/avatars/anna_olsson.png",
1292- secondaryIcon: "",
1293- actions: [{ id: "accept_id", label: "Accept"},
1294- { id: "reject_id", label: "Reject"}],
1295+ n: nlist[3],
1296 summaryVisible: true,
1297 bodyVisible: true,
1298 iconVisible: true,
1299@@ -395,14 +445,7 @@
1300 },
1301 {
1302 tag: "Ephemeral notification",
1303- type: Notification.Ephemeral,
1304- hints: {"x-canonical-private-affirmative-tint": "false",
1305- "sound-file": "dummy.ogg"},
1306- summary: "Cole Raby",
1307- body: "I did not expect it to be that late.",
1308- icon: "../graphics/avatars/funky.png",
1309- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1310- actions: [],
1311+ n: nlist[4],
1312 summaryVisible: true,
1313 bodyVisible: true,
1314 iconVisible: true,
1315@@ -418,14 +461,7 @@
1316 },
1317 {
1318 tag: "Ephemeral notification with non-shaped icon",
1319- type: Notification.Ephemeral,
1320- hints: {"x-canonical-private-affirmative-tint": "false",
1321- "x-canonical-non-shaped-icon": "true"},
1322- summary: "Contacts",
1323- body: "Synchronised contacts-database with cloud-storage.",
1324- icon: "image://theme/contacts-app",
1325- secondaryIcon: "",
1326- actions: [],
1327+ n: nlist[5],
1328 summaryVisible: true,
1329 bodyVisible: true,
1330 iconVisible: true,
1331@@ -441,14 +477,7 @@
1332 },
1333 {
1334 tag: "Confirmation notification with value",
1335- type: Notification.Confirmation,
1336- hints: {"x-canonical-non-shaped-icon": "true"},
1337- summary: "",
1338- body: "",
1339- icon: "image://theme/audio-volume-medium",
1340- secondaryIcon: "",
1341- value: 50,
1342- actions: [],
1343+ n: nlist[6],
1344 summaryVisible: false,
1345 bodyVisible: false,
1346 iconVisible: false,
1347@@ -464,15 +493,7 @@
1348 },
1349 {
1350 tag: "Confirmation notification with value, label and tint",
1351- type: Notification.Confirmation,
1352- hints: {"x-canonical-non-shaped-icon": "true",
1353- "x-canonical-value-bar-tint" : "true"},
1354- summary: "",
1355- body: "High Volume",
1356- icon: "image://theme/audio-volume-high",
1357- secondaryIcon: "",
1358- value: 85,
1359- actions: [],
1360+ n: nlist[7],
1361 summaryVisible: false,
1362 bodyVisible: false,
1363 iconVisible: false,
1364@@ -485,6 +506,22 @@
1365 valueVisible: true,
1366 valueLabelVisible: true,
1367 valueTinted: true
1368+ },
1369+ {
1370+ tag: "2-over-1 Snap Decision with button-tint",
1371+ n: nlist[8],
1372+ summaryVisible: true,
1373+ bodyVisible: true,
1374+ iconVisible: false,
1375+ centeredIconVisible: false,
1376+ shaped: false,
1377+ secondaryIconVisible: false,
1378+ buttonRowVisible: false,
1379+ buttonTinted: true,
1380+ hasSound: false,
1381+ valueVisible: false,
1382+ valueLabelVisible: false,
1383+ valueTinted: false
1384 }
1385 ]
1386 }
1387@@ -509,8 +546,14 @@
1388 }
1389
1390 function test_NotificationRenderer(data) {
1391+ // make sure the clicks on mocked notifications can be checked against by "actionSpy" (mimicking the NotificationServer component)
1392+ data.n.actionInvoked.connect(mockModel.actionInvoked)
1393+
1394+ // hook up notification's completed-signal with model's onCompleted-slot, so that remove() (model) happens on close() (notification)
1395+ data.n.completed.connect(mockModel.onCompleted)
1396+
1397 // populate model with some mock notifications
1398- mockModel.append(data)
1399+ mockModel.append(data.n)
1400
1401 // make sure the view is properly updated before going on
1402 notifications.forceLayout();
1403@@ -548,9 +591,9 @@
1404
1405 // test input does not fall through
1406 mouseClick(notification)
1407- if(data.type == Notification.Interactive) {
1408+ if(data.n.type === Notification.Interactive) {
1409 actionSpy.wait()
1410- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
1411+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id for interactive action")
1412 }
1413 compare(clickThroughSpy.count, 0, "click on notification fell through")
1414
1415@@ -559,17 +602,19 @@
1416 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
1417 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
1418
1419- var audioItem = findInvisibleChild(notification, "sound")
1420- compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
1421+ if (data.hasSound) {
1422+ var audioItem = findInvisibleChild(notification, "sound")
1423+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
1424+ }
1425
1426 if(data.buttonRowVisible) {
1427 var buttonCancel = findChild(buttonRow, "notify_button1")
1428 var buttonAccept = findChild(buttonRow, "notify_button0")
1429
1430 // only test the left/cancel-button if two actions have been passed in
1431- if (data.actions.length == 2) {
1432+ if (data.n.actions.count === 2) {
1433 tryCompareFunction(function() { mouseClick(buttonCancel); return actionSpy.signalArguments.length > 0; }, true);
1434- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
1435+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
1436 actionSpy.clear()
1437 }
1438
1439@@ -578,36 +623,49 @@
1440
1441 // click the positive/right button
1442 tryCompareFunction(function() { mouseClick(buttonAccept); return actionSpy.signalArguments.length > 0; }, true);
1443- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
1444+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id positive action")
1445 actionSpy.clear()
1446- waitForRendering (notification)
1447
1448 // check if there's a ComboButton created due to more actions being passed
1449- if (data.actions.length > 2) {
1450+ if (data.n.actions.count > 3) {
1451 var comboButton = findChild(notification, "notify_button2")
1452- tryCompareFunction(function() { return comboButton.expanded == false; }, true);
1453+ tryCompareFunction(function() { return comboButton.expanded === false; }, true);
1454
1455 // click to expand
1456- tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
1457+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === true; }, true);
1458
1459 // try clicking on choices in expanded comboList
1460 var choiceButton1 = findChild(notification, "notify_button3")
1461 tryCompareFunction(function() { mouseClick(choiceButton1); return actionSpy.signalArguments.length > 0; }, true);
1462- compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
1463+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(3, ActionModel.RoleActionId), "got wrong id choice action 1")
1464 actionSpy.clear()
1465
1466 var choiceButton2 = findChild(notification, "notify_button4")
1467 tryCompareFunction(function() { mouseClick(choiceButton2); return actionSpy.signalArguments.length > 0; }, true);
1468- compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
1469+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(4, ActionModel.RoleActionId), "got wrong id choice action 2")
1470 actionSpy.clear()
1471
1472 // click to collapse
1473- //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
1474+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
1475 } else {
1476 mouseClick(buttonCancel)
1477- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
1478+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
1479 }
1480 }
1481+
1482+ // swipe-to-dismiss check
1483+ waitForRendering(notification)
1484+ var before = mockModel.count
1485+ var dragStart = notification.width * 0.25;
1486+ var dragEnd = notification.width;
1487+ var dragY = notification.height / 2;
1488+ touchFlick(notification, dragStart, dragY, dragEnd, dragY)
1489+ waitForRendering(notification)
1490+ if ((data.n.type === Notification.SnapDecision && notification.state === "expanded") || data.n.type === Notification.Confirmation) {
1491+ tryCompare(mockModel, "count", before)
1492+ } else {
1493+ tryCompare(mockModel, "count", before - 1)
1494+ }
1495 }
1496 }
1497 }
1498
1499=== modified file 'tests/qmltests/Notifications/tst_OptionToggle.qml'
1500--- tests/qmltests/Notifications/tst_OptionToggle.qml 2015-01-09 09:15:45 +0000
1501+++ tests/qmltests/Notifications/tst_OptionToggle.qml 2015-02-06 15:16:01 +0000
1502@@ -1,17 +1,20 @@
1503 /*
1504- * Copyright (C) 2014 Canonical, Ltd.
1505+ * Copyright 2015 Canonical Ltd.
1506 *
1507 * This program is free software; you can redistribute it and/or modify
1508- * it under the terms of the GNU General Public License as published by
1509+ * it under the terms of the GNU Lesser General Public License as published by
1510 * the Free Software Foundation; version 3.
1511 *
1512 * This program is distributed in the hope that it will be useful,
1513 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1514 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515- * GNU General Public License for more details.
1516+ * GNU Lesser General Public License for more details.
1517 *
1518- * You should have received a copy of the GNU General Public License
1519+ * You should have received a copy of the GNU Lesser General Public License
1520 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1521+ *
1522+ * Authors:
1523+ * Mirco Mueller <mirco.mueller@canonical.com>
1524 */
1525
1526 import QtQuick 2.0
1527@@ -235,8 +238,10 @@
1528 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
1529 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
1530
1531- var audioItem = findInvisibleChild(notification, "sound")
1532- compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
1533+ if (data.hasSound) {
1534+ var audioItem = findInvisibleChild(notification, "sound")
1535+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
1536+ }
1537
1538 if(data.buttonRowVisible) {
1539 var buttonCancel = findChild(buttonRow, "notify_button1")
1540
1541=== modified file 'tests/qmltests/Notifications/tst_SwipeToAct.qml'
1542--- tests/qmltests/Notifications/tst_SwipeToAct.qml 2015-01-09 09:15:45 +0000
1543+++ tests/qmltests/Notifications/tst_SwipeToAct.qml 2015-02-06 15:16:01 +0000
1544@@ -218,6 +218,10 @@
1545 // populate model with some mock notifications
1546 mockModel.append(data)
1547
1548+ // add actions to action-model to test against
1549+ myActionModel.append("ok_id", "Ok")
1550+ myActionModel.append("cancel_id", "Cancel")
1551+
1552 // make sure the view is properly updated before going on
1553 notifications.forceLayout();
1554 waitForRendering(notifications);

Subscribers

People subscribed via source and target branches