Merge lp:~macslow/unity8/swipe-to-act-fix-1358343 into lp:unity8

Proposed by Mirco Müller
Status: Merged
Approved by: Albert Astals Cid
Approved revision: 1317
Merged at revision: 1412
Proposed branch: lp:~macslow/unity8/swipe-to-act-fix-1358343
Merge into: lp:unity8
Diff against target: 2297 lines (+1471/-666)
15 files modified
qml/Notifications/Notification.qml (+22/-3)
qml/Notifications/SwipeToAct.qml (+309/-0)
tests/autopilot/unity8/shell/tests/test_notifications.py (+0/-51)
tests/mocks/Unity/Notifications/CMakeLists.txt (+15/-1)
tests/mocks/Unity/Notifications/MockActionModel.cpp (+72/-0)
tests/mocks/Unity/Notifications/MockActionModel.h (+51/-0)
tests/mocks/Unity/Notifications/MockNotificationTypes.cpp (+26/-0)
tests/mocks/Unity/Notifications/MockNotificationTypes.h (+36/-0)
tests/mocks/Unity/Notifications/notification.js (+0/-23)
tests/mocks/Unity/Notifications/plugin.cpp (+31/-0)
tests/mocks/Unity/Notifications/plugin.h (+35/-0)
tests/mocks/Unity/Notifications/qmldir (+2/-1)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Notifications/tst_Notifications.qml (+595/-587)
tests/qmltests/Notifications/tst_SwipeToAct.qml (+276/-0)
To merge this branch: bzr merge lp:~macslow/unity8/swipe-to-act-fix-1358343
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Albert Astals Cid (community) Approve
Josh Arenson Approve
Review via email: mp+236091@code.launchpad.net

Commit message

Added dedicated swipe-to-act button for snap-decisions, which avoids accidental taps/button-presses.

Description of the change

Added dedicated swipe-to-act button for snap-decisions, which avoids accidental taps/button-presses.

You can test it with the examples/sd-example-incoming-call.py Python-script from the corresponding lp:~macslow/unity-notifications/swipe-to-act-fix-1358343 branch.

For the reviewers convenience, here's a video of all two branches in action: http://www.youtube.com/watch?v=bLNWI4GvplQ

* Are there any related MPs required for this MP to build/function as expected? Please list.
Yes. For correct operation lp:~macslow/unity-notifications/swipe-to-act-fix-1358343 needs to be merged to lp:unity-notifications first. Due to changes in trunk lp:~aacid/unity8/multimediaMocks is also needed now.

* 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
Albert Astals Cid (aacid) wrote :

Please use Loaders so that either SwipeToAct or the Buttons are created but not both, will save us CPU time and make showing the notification faster since less things need to be created.

review: Needs Fixing
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?
Let's let it run again after the last bunch of changes

 * 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
Josh Arenson (josharenson) wrote :

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

 * Did CI run pass?
No, the usual AP failures.

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

While I was fairly sure it wouldn't have an effect, I tested this branch with https://code.launchpad.net/~mzanetti/unity8/fix_snap_decision_test-rtm/+merge/238282 merged as well. Everything works as expected.

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

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

Reminder: Was already top approved, should be again after merge is fixed.

review: Needs Fixing
1316. By Mirco Müller

Merged with trunk... resolved conflicts.

1317. By Mirco Müller

Adapted the SwipeToAct qml-test too after the merge with trunk.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'qml/Notifications/Notification.qml'
--- qml/Notifications/Notification.qml 2014-10-30 21:42:32 +0000
+++ qml/Notifications/Notification.qml 2014-10-31 10:46:25 +0000
@@ -398,7 +398,7 @@
398398
399 spacing: contentSpacing399 spacing: contentSpacing
400400
401 visible: notification.type == Notification.SnapDecision && oneOverTwoRepeaterTop.count == 3401 visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
402402
403 Repeater {403 Repeater {
404 id: oneOverTwoRepeaterTop404 id: oneOverTwoRepeaterTop
@@ -468,22 +468,41 @@
468 spacing: units.gu(2)468 spacing: units.gu(2)
469 layoutDirection: Qt.RightToLeft469 layoutDirection: Qt.RightToLeft
470470
471 Loader {
472 id: notifySwipeButtonLoader
473 active: notification.hints["x-canonical-snap-decisions-swipe"] === "true"
474
475 sourceComponent: SwipeToAct {
476 objectName: "notify_swipe_button"
477 width: buttonRow.width
478 leftIconName: "call-end"
479 rightIconName: "call-start"
480 onLeftTriggered: {
481 notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
482 }
483
484 onRightTriggered: {
485 notification.notification.invokeAction(notification.actions.data(1, ActionModel.RoleActionId))
486 }
487 }
488 }
489
471 Repeater {490 Repeater {
472 id: actionRepeater491 id: actionRepeater
473
474 model: notification.actions492 model: notification.actions
475 delegate: Loader {493 delegate: Loader {
476 id: loader494 id: loader
477495
478 property string actionId: id496 property string actionId: id
479 property string actionLabel: label497 property string actionLabel: label
498 active: !notifySwipeButtonLoader.active
480499
481 Component {500 Component {
482 id: actionButton501 id: actionButton
483502
484 Button {503 Button {
485 objectName: "notify_button" + index504 objectName: "notify_button" + index
486 width: buttonRow.width / 2 - spacing*2505 width: buttonRow.width / 2 - spacing * 2
487 text: loader.actionLabel506 text: loader.actionLabel
488 color: {507 color: {
489 var result = sdDarkGrey;508 var result = sdDarkGrey;
490509
=== added file 'qml/Notifications/SwipeToAct.qml'
--- qml/Notifications/SwipeToAct.qml 1970-01-01 00:00:00 +0000
+++ qml/Notifications/SwipeToAct.qml 2014-10-31 10:46:25 +0000
@@ -0,0 +1,309 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU 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 General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.3
18import Ubuntu.Components 1.1
19import QtGraphicalEffects 1.0
20
21Item {
22 id: swipeToAct
23
24 width: parent.width
25 height: childrenRect.height
26
27 signal leftTriggered()
28 signal rightTriggered()
29
30 property string leftIconName
31 property string rightIconName
32 readonly property double sliderHeight: units.gu(6)
33 readonly property double gap: units.gu(1)
34 readonly property double halfWay: mouseArea.drag.maximumX / 2
35
36 Rectangle {
37 id: gradient
38 width: parent.width * 5
39 height: sliderHeight
40 visible: false
41 LinearGradient {
42 anchors.fill: parent
43 start: Qt.point(parent.x, parent.y)
44 end: Qt.point(parent.width, parent.y)
45 gradient: Gradient {
46 GradientStop { position: 0.0; color: UbuntuColors.red }
47 GradientStop { position: 0.2; color: UbuntuColors.red }
48 GradientStop { position: 0.4; color: "#dddddd" }
49 GradientStop { position: 0.6; color: "#dddddd" }
50 GradientStop { position: 0.8; color: UbuntuColors.green }
51 GradientStop { position: 1.0; color: UbuntuColors.green }
52 }
53 }
54 }
55
56 ShaderEffectSource {
57 id: effectSourceGradient
58 sourceItem: gradient
59 width: gradient.width
60 height: gradient.height
61 sourceRect: Qt.rect(0.4 * gradient.width * (slider.x / halfWay), 0, mask.width, mask.height)
62 visible: false
63 hideSource: true
64 }
65
66 UbuntuShape {
67 id: mask
68 color: "black"
69 width: parent.width
70 height: sliderHeight
71 borderSource: "none"
72 visible: false
73 }
74
75 ShaderEffectSource {
76 id: effectSourceMask
77 sourceItem: mask
78 width: mask.width
79 height: mask.height
80 visible: false
81 hideSource: true
82 }
83
84 ShaderEffect {
85 width: parent.width
86 height: sliderHeight
87 property variant mask: effectSourceMask
88 property variant gradient: effectSourceGradient
89 vertexShader: "
90 uniform highp mat4 qt_Matrix;
91 attribute highp vec4 qt_Vertex;
92 attribute highp vec2 qt_MultiTexCoord0;
93 varying highp vec2 coord;
94 void main() {
95 coord = qt_MultiTexCoord0;
96 gl_Position = qt_Matrix * qt_Vertex;
97 }"
98 fragmentShader: "
99 varying highp vec2 coord;
100 uniform sampler2D mask;
101 uniform sampler2D gradient;
102 void main() {
103 lowp vec4 texMask = texture2D(mask, coord);
104 lowp vec4 texGradient = texture2D(gradient, coord);
105 gl_FragColor = texGradient.rgba * texMask.a ;
106 }"
107
108 Row {
109 id: row
110 anchors.fill: parent
111 spacing: gap
112 anchors.margins: gap
113
114 UbuntuShape {
115 id: leftShape
116 states: [
117 State {
118 name: "normal"
119 PropertyChanges {
120 target: leftShape
121 color: UbuntuColors.red
122 }
123 PropertyChanges {
124 target: innerLeftShape
125 color: UbuntuColors.red
126 visible: false
127 }
128 },
129 State {
130 name: "selected"
131 PropertyChanges {
132 target: leftShape
133 color: "white"
134 }
135 PropertyChanges {
136 target: innerLeftShape
137 color: UbuntuColors.red
138 visible: true
139 }
140 }
141 ]
142 state: "normal"
143 height: units.gu(4)
144 width: units.gu(7)
145 borderSource: "none"
146 opacity: slider.x <= halfWay ? 1.0 : 1.0 - ((slider.x - halfWay) / halfWay)
147 UbuntuShape {
148 id: innerLeftShape
149 anchors.centerIn: parent
150 borderSource: "none"
151 width: parent.width - units.gu(.5)
152 height: parent.height - units.gu(.5)
153 }
154 Icon {
155 anchors.centerIn: parent
156 width: units.gu(2)
157 height: units.gu(2)
158 name: leftIconName
159 color: "white"
160 }
161 }
162
163 Rectangle {
164 id: leftSpacer
165 width: (row.width - (leftShape.width + slider.width + rightShape.width + 4 * row.spacing)) / 2
166 height: units.gu(4)
167 opacity: 0
168 }
169
170 UbuntuShape {
171 id: slider
172 objectName: "slider"
173
174 Behavior on x {
175 UbuntuNumberAnimation {
176 duration: UbuntuAnimation.FastDuration
177 easing.type: Easing.OutBounce
178 }
179 }
180
181 Behavior on opacity {
182 UbuntuNumberAnimation {
183 duration: UbuntuAnimation.FastDuration
184 }
185 }
186
187 onOpacityChanged: {
188 if (opacity === 0) {
189 if (rightShape.state === "selected") {
190 rightTriggered()
191 }
192 if (leftShape.state === "selected") {
193 leftTriggered()
194 }
195 }
196 }
197
198 z: 1
199 color: "white"
200 height: units.gu(4)
201 width: units.gu(7)
202 borderSource: "none"
203 Row {
204 anchors.fill: parent
205 spacing: 2 * gap
206 anchors.leftMargin: units.gu(.5)
207 anchors.rightMargin: units.gu(.5)
208 Icon {
209 anchors.verticalCenter: parent.verticalCenter
210 name: "back"
211 width: units.gu(2)
212 height: units.gu(2)
213 }
214 Icon {
215 anchors.verticalCenter: parent.verticalCenter
216 name: "next"
217 width: units.gu(2)
218 height: units.gu(2)
219 }
220 }
221 }
222
223 Rectangle {
224 id: rightSpacer
225 width: leftSpacer.width
226 height: units.gu(4)
227 opacity: 0
228 }
229
230 UbuntuShape {
231 id: rightShape
232 states: [
233 State {
234 name: "normal"
235 PropertyChanges {
236 target: rightShape
237 color: UbuntuColors.green
238 }
239 PropertyChanges {
240 target: innerRightShape
241 color: UbuntuColors.green
242 visible: false
243 }
244 },
245 State {
246 name: "selected"
247 PropertyChanges {
248 target: rightShape
249 color: "white"
250 }
251 PropertyChanges {
252 target: innerRightShape
253 color: UbuntuColors.green
254 visible: true
255 }
256 }
257 ]
258 state: "normal"
259 height: units.gu(4)
260 width: units.gu(7)
261 borderSource: "none"
262 opacity: slider.x >= halfWay ? 1.0 : slider.x / halfWay
263 UbuntuShape {
264 id: innerRightShape
265 anchors.centerIn: parent
266 borderSource: "none"
267 width: parent.width - units.gu(.5)
268 height: parent.height - units.gu(.5)
269 }
270 Icon {
271 anchors.centerIn: parent
272 width: units.gu(2)
273 height: units.gu(2)
274 name: rightIconName
275 color: "white"
276 }
277 }
278 }
279
280 MouseArea {
281 id: mouseArea
282 objectName: "swipeMouseArea"
283
284 anchors.fill: row
285 drag.target: slider
286 drag.axis: Drag.XAxis
287 drag.minimumX: 0
288 drag.maximumX: row.width - slider.width
289
290 onReleased: {
291 if (slider.x !== drag.minimumX || slider.x !== drag.maximumX) {
292 slider.x = halfWay
293 }
294 if (slider.x === drag.minimumX) {
295 slider.x = drag.minimumX
296 slider.opacity = 0
297 enabled = false
298 leftShape.state = "selected"
299 }
300 if (slider.x === drag.maximumX) {
301 slider.x = drag.maximumX
302 slider.opacity = 0
303 enabled = false
304 rightShape.state = "selected"
305 }
306 }
307 }
308 }
309}
0310
=== modified file 'tests/autopilot/unity8/shell/tests/test_notifications.py'
--- tests/autopilot/unity8/shell/tests/test_notifications.py 2014-10-30 21:43:06 +0000
+++ tests/autopilot/unity8/shell/tests/test_notifications.py 2014-10-31 10:46:25 +0000
@@ -157,57 +157,6 @@
157157
158 self.assert_notification_action_id_was_called('action_id')158 self.assert_notification_action_id_was_called('action_id')
159159
160 def test_sd_incoming_call(self):
161 """Rejecting a call should make notification expand and
162 offer more options."""
163 unity_proxy = self.launch_unity()
164 unlock_unity(unity_proxy)
165
166 summary = "Incoming call"
167 body = "Frank Zappa\n+44 (0)7736 027340"
168 icon_path = self._get_icon_path('avatars/anna_olsson.png')
169 hints = [
170 ("x-canonical-secondary-icon", "incoming-call"),
171 ("x-canonical-snap-decisions", "true"),
172 ("x-canonical-private-affirmative-tint", "true"),
173 ("x-canonical-private-rejection-tint", "true"),
174 ]
175
176 actions = [
177 ('action_accept', 'Hold + Answer'),
178 ('action_decline_1', 'End + Answer'),
179 ('action_decline_2', 'Decline'),
180 ('action_decline_3', 'message:I missed your call - can you call me now?'),
181 ('action_decline_4', 'message:I\'m running late. I\'m on my way.'),
182 ('action_decline_5', 'message:I\'m busy at the moment. I\'ll call later.'),
183 ('action_decline_6', 'edit:Custom'),
184 ]
185
186 self._create_interactive_notification(
187 summary,
188 body,
189 icon_path,
190 "NORMAL",
191 actions,
192 hints
193 )
194
195 notify_list = self._get_notifications_list()
196 get_notification = lambda: notify_list.wait_select_single(
197 'Notification', objectName='notification1')
198 notification = get_notification()
199 self._assert_notification(notification, summary, body, True, True, 1.0)
200 notification.pointing_device.click_object(
201 notification.select_single(objectName="combobutton_dropdown"))
202 self.assertThat(
203 notification.select_single(objectName="notify_button2").expanded,
204 Eventually(Equals(True)))
205 time.sleep(2)
206 notification.pointing_device.click_object(
207 notification.select_single(objectName="notify_button4"))
208 self.assert_notification_action_id_was_called("action_decline_4")
209
210
211 def test_sd_one_over_two_layout(self):160 def test_sd_one_over_two_layout(self):
212 """Snap-decision with three actions should use one-over two button layout."""161 """Snap-decision with three actions should use one-over two button layout."""
213 unity_proxy = self.launch_unity()162 unity_proxy = self.launch_unity()
214163
=== modified file 'tests/mocks/Unity/Notifications/CMakeLists.txt'
--- tests/mocks/Unity/Notifications/CMakeLists.txt 2014-05-02 23:27:02 +0000
+++ tests/mocks/Unity/Notifications/CMakeLists.txt 2014-10-31 10:46:25 +0000
@@ -1,1 +1,15 @@
1add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications)1include_directories(
2 ${CMAKE_CURRENT_SOURCE_DIR}
3)
4
5set(MockNotificationsPlugin_SOURCES
6 plugin.cpp
7 MockNotificationTypes.cpp
8 MockActionModel.cpp
9)
10
11add_library(MockNotificationsPlugin MODULE ${MockNotificationsPlugin_SOURCES})
12
13qt5_use_modules(MockNotificationsPlugin Core Quick)
14
15add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications TARGETS MockNotificationsPlugin)
216
=== added file 'tests/mocks/Unity/Notifications/MockActionModel.cpp'
--- tests/mocks/Unity/Notifications/MockActionModel.cpp 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/MockActionModel.cpp 2014-10-31 10:46:25 +0000
@@ -0,0 +1,72 @@
1/*
2 * Copyright 2014 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 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#include "MockActionModel.h"
21
22struct ActionModelPrivate {
23 QList<QString> labels;
24 QList<QString> ids;
25};
26
27ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
28 insertAction("ok_id", "Ok");
29 insertAction("cancel_id", "Cancel");
30}
31
32ActionModel::~ActionModel() {
33}
34
35int ActionModel::rowCount(const QModelIndex &index) const {
36 return p->labels.size();
37}
38
39QVariant ActionModel::data(const QModelIndex &index, int role) const {
40 if (!index.isValid())
41 return QVariant();
42
43 switch(role) {
44 case RoleActionLabel:
45 return QVariant(p->labels[index.row()]);
46
47 case RoleActionId:
48 return QVariant(p->ids[index.row()]);
49
50 default:
51 return QVariant();
52 }
53}
54
55QHash<int, QByteArray> ActionModel::roleNames() const {
56 QHash<int, QByteArray> roles;
57
58 roles.insert(RoleActionLabel, "label");
59 roles.insert(RoleActionId, "id");
60
61 return roles;
62}
63
64Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
65{
66 return data(index(row, 0), role);
67}
68
69void ActionModel::insertAction(const QString &id, const QString &label) {
70 p->ids.push_back(id);
71 p->labels.push_back(label);
72}
073
=== added file 'tests/mocks/Unity/Notifications/MockActionModel.h'
--- tests/mocks/Unity/Notifications/MockActionModel.h 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/MockActionModel.h 2014-10-31 10:46:25 +0000
@@ -0,0 +1,51 @@
1/*
2 * Copyright 2014 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 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#ifndef MOCK_ACTION_MODEL_H
21#define MOCK_ACTION_MODEL_H
22
23#include <QStringListModel>
24
25struct ActionModelPrivate;
26
27class ActionModel : public QStringListModel {
28 Q_OBJECT
29
30public:
31 ActionModel(QObject *parent=nullptr);
32 virtual ~ActionModel();
33
34 virtual int rowCount(const QModelIndex &index) const;
35 virtual QVariant data(const QModelIndex &index, int role) const;
36 virtual QHash<int, QByteArray> roleNames() const;
37
38 Q_ENUMS(ActionsRoles)
39 enum ActionsRoles {
40 RoleActionLabel = Qt::UserRole + 1,
41 RoleActionId = Qt::UserRole + 2
42 };
43 Q_INVOKABLE QVariant data(int row, int role) const;
44
45 void insertAction(const QString &id, const QString &label);
46
47private:
48 QScopedPointer<ActionModelPrivate> p;
49};
50
51#endif // MOCK_ACTION_MODEL_H
052
=== added file 'tests/mocks/Unity/Notifications/MockNotificationTypes.cpp'
--- tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2014-10-31 10:46:25 +0000
@@ -0,0 +1,26 @@
1/*
2 * Copyright 2014 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 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#include "MockNotificationTypes.h"
21
22MockNotification::MockNotification(QObject *parent) : QObject(parent) {
23}
24
25MockNotification::~MockNotification() {
26}
027
=== added file 'tests/mocks/Unity/Notifications/MockNotificationTypes.h'
--- tests/mocks/Unity/Notifications/MockNotificationTypes.h 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/MockNotificationTypes.h 2014-10-31 10:46:25 +0000
@@ -0,0 +1,36 @@
1/*
2 * Copyright 2014 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 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#ifndef MOCK_NOTIFICATION_TYPES_H
21#define MOCK_NOTIFICATION_TYPES_H
22
23#include <QObject>
24
25class MockNotification : public QObject {
26 Q_OBJECT
27 Q_ENUMS(Type)
28
29public:
30 MockNotification(QObject *parent=nullptr);
31 virtual ~MockNotification();
32
33 enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
34};
35
36#endif // MOCK_NOTIFICATION_TYPES_H
037
=== removed file 'tests/mocks/Unity/Notifications/notification.js'
--- tests/mocks/Unity/Notifications/notification.js 2013-06-19 10:24:31 +0000
+++ tests/mocks/Unity/Notifications/notification.js 1970-01-01 00:00:00 +0000
@@ -1,23 +0,0 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU 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 General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17.pragma library
18
19var Confirmation = 0;
20var Ephemeral = 1;
21var Interactive = 2;
22var SnapDecision = 3;
23var PlaceHolder = 4;
240
=== added file 'tests/mocks/Unity/Notifications/plugin.cpp'
--- tests/mocks/Unity/Notifications/plugin.cpp 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/plugin.cpp 2014-10-31 10:46:25 +0000
@@ -0,0 +1,31 @@
1/*
2 * Copyright 2014 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 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#include "plugin.h"
21#include "MockActionModel.h"
22#include "MockNotificationTypes.h"
23
24#include <QtQml/qqml.h>
25
26void TestNotificationPlugin::registerTypes(const char* uri)
27{
28 // @uri Unity.Notifications
29 qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");
30 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
31}
032
=== added file 'tests/mocks/Unity/Notifications/plugin.h'
--- tests/mocks/Unity/Notifications/plugin.h 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/plugin.h 2014-10-31 10:46:25 +0000
@@ -0,0 +1,35 @@
1/*
2 * Copyright 2014 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 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20
21#ifndef TESTNOTIFICATION_PLUGIN_H
22#define TESTNOTIFICATION_PLUGIN_H
23
24#include <QtQml/QQmlExtensionPlugin>
25
26class TestNotificationPlugin : public QQmlExtensionPlugin
27{
28 Q_OBJECT
29 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
30
31public:
32 void registerTypes(const char* uri);
33};
34
35#endif // TESTNOTIFICATION_PLUGIN_H
036
=== modified file 'tests/mocks/Unity/Notifications/qmldir'
--- tests/mocks/Unity/Notifications/qmldir 2014-05-02 22:57:21 +0000
+++ tests/mocks/Unity/Notifications/qmldir 2014-10-31 10:46:25 +0000
@@ -1,3 +1,4 @@
1module Unity.Notifications1module Unity.Notifications
2Notification 1.0 notification.js2plugin MockNotificationsPlugin
3typeinfo Notifications.qmltypes3typeinfo Notifications.qmltypes
4
45
=== modified file 'tests/qmltests/CMakeLists.txt'
--- tests/qmltests/CMakeLists.txt 2014-10-13 09:23:34 +0000
+++ tests/qmltests/CMakeLists.txt 2014-10-31 10:46:25 +0000
@@ -70,6 +70,7 @@
70add_qml_test(Launcher Launcher)70add_qml_test(Launcher Launcher)
71add_qml_test(Notifications Notifications)71add_qml_test(Notifications Notifications)
72add_qml_test(Notifications VisualSnapDecisionsQueue)72add_qml_test(Notifications VisualSnapDecisionsQueue)
73add_qml_test(Notifications SwipeToAct)
73add_qml_test(Panel ActiveCallHint)74add_qml_test(Panel ActiveCallHint)
74add_qml_test(Panel IndicatorItem)75add_qml_test(Panel IndicatorItem)
75add_qml_test(Panel IndicatorItemRow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")76add_qml_test(Panel IndicatorItemRow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
7677
=== modified file 'tests/qmltests/Notifications/tst_Notifications.qml'
--- tests/qmltests/Notifications/tst_Notifications.qml 2014-10-14 10:07:44 +0000
+++ tests/qmltests/Notifications/tst_Notifications.qml 2014-10-31 10:46:25 +0000
@@ -23,280 +23,51 @@
23import Unity.Notifications 1.023import Unity.Notifications 1.0
24import QtMultimedia 5.024import QtMultimedia 5.0
2525
26Row {26Item {
27 id: rootRow27 width: notificationsRect.width + interactiveControls.width
2828 height: notificationsRect.height
29 Component {29
30 id: mockNotification30 Row {
3131 id: rootRow
32 QtObject {32
33 function invokeAction(actionId) {33 Component {
34 mockModel.actionInvoked(actionId)34 id: mockNotification
35 }35
36 }36 QtObject {
37 }37 function invokeAction(actionId) {
3838 mockModel.actionInvoked(actionId)
39 ListModel {39 }
40 id: mockModel40 }
4141 }
42 signal actionInvoked(string actionId)42
4343 ListModel {
44 function getRaw(id) {44 id: mockModel
45 return mockNotification.createObject(mockModel)45 dynamicRoles: true
46 }46
4747 signal actionInvoked(string actionId)
48 // add the default/PlaceHolder notification to the model48
49 Component.onCompleted: {49 function getRaw(id) {
50 return mockNotification.createObject(mockModel)
51 }
52
53 // add the default/PlaceHolder notification to the model
54 Component.onCompleted: {
55 var n = {
56 type: Notification.PlaceHolder,
57 hints: {},
58 summary: "",
59 body: "",
60 icon: "",
61 secondaryIcon: "",
62 actions: []
63 }
64
65 append(n)
66 }
67 }
68
69 function addSnapDecisionNotification() {
50 var n = {70 var n = {
51 type: Notification.PlaceHolder,
52 hints: {},
53 summary: "",
54 body: "",
55 icon: "",
56 value: 0,
57 secondaryIcon: "",
58 actions: []
59 }
60
61 append(n)
62 }
63 }
64
65 function addSnapDecisionNotification() {
66 var n = {
67 type: Notification.SnapDecision,
68 hints: {"x-canonical-private-affirmative-tint": "true"},
69 summary: "Tom Ato",
70 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
71 icon: "../graphics/avatars/funky.png",
72 secondaryIcon: "../graphics/applicationIcons/facebook.png",
73 actions: [{ id: "ok_id", label: "Ok"},
74 { id: "cancel_id", label: "Cancel"},
75 { id: "notreally_id", label: "Not really"},
76 { id: "noway_id", label: "messages:No way"},
77 { id: "nada_id", label: "messages:Nada"}]
78 }
79
80 mockModel.append(n)
81 }
82
83 function add2over1SnapDecisionNotification() {
84 var n = {
85 type: Notification.SnapDecision,
86 hints: {"x-canonical-private-affirmative-tint": "true",},
87 summary: "Theatre at Ferria Stadium",
88 body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
89 icon: "",
90 secondaryIcon: "",
91 actions: [{ id: "ok_id", label: "Ok"},
92 { id: "snooze_id", label: "Snooze"},
93 { id: "view_id", label: "View"}]
94 }
95
96 mockModel.append(n)
97 }
98
99 function addEphemeralNotification() {
100 var n = {
101 type: Notification.Ephemeral,
102 summary: "Cole Raby",
103 body: "I did not expect it to be that late.",
104 icon: "../graphics/avatars/amanda.png",
105 secondaryIcon: "../graphics/applicationIcons/facebook.png",
106 actions: []
107 }
108
109 mockModel.append(n)
110 }
111
112 function addEphemeralNonShapedIconNotification() {
113 var n = {
114 type: Notification.Ephemeral,
115 hints: {"x-canonical-non-shaped-icon": "true"},
116 summary: "Contacts",
117 body: "Synchronised contacts-database with cloud-storage.",
118 icon: "../graphics/applicationIcons/contacts-app.png",
119 secondaryIcon: "",
120 actions: []
121 }
122
123 mockModel.append(n)
124 }
125
126 function addEphemeralIconSummaryNotification() {
127 var n = {
128 type: Notification.Ephemeral,
129 hints: {"x-canonical-non-shaped-icon": "false"},
130 summary: "Photo upload completed",
131 body: "",
132 icon: "../graphics/applicationIcons/facebook.png",
133 secondaryIcon: "",
134 actions: []
135 }
136
137 mockModel.append(n)
138 }
139
140 function addInteractiveNotification() {
141 var n = {
142 type: Notification.Interactive,
143 summary: "Interactive notification",
144 body: "This is a notification that can be clicked",
145 icon: "../graphics/avatars/anna_olsson.png",
146 secondaryIcon: "",
147 actions: [{ id: "reply_id", label: "Dummy"}],
148 }
149
150 mockModel.append(n)
151 }
152
153 function addConfirmationNotification() {
154 var n = {
155 type: Notification.Confirmation,
156 hints: {"x-canonical-non-shaped-icon": "true"},
157 summary: "Confirmation notification",
158 body: "",
159 icon: "image://theme/audio-volume-medium",
160 secondaryIcon: "",
161 value: 50,
162 actions: [],
163 }
164
165 mockModel.append(n)
166 }
167
168 function add2ndConfirmationNotification() {
169 var n = {
170 type: Notification.Confirmation,
171 hints: {"x-canonical-non-shaped-icon": "true",
172 "x-canonical-value-bar-tint": "true"},
173 summary: "Confirmation notification",
174 body: "High Volume",
175 icon: "image://theme/audio-volume-high",
176 secondaryIcon: "",
177 value: 85,
178 actions: [],
179 }
180
181 mockModel.append(n)
182 }
183
184 function clearNotifications() {
185 while(mockModel.count > 1) {
186 remove1stNotification()
187 }
188 }
189
190 function remove1stNotification() {
191 if (mockModel.count > 1)
192 mockModel.remove(1)
193 }
194
195 Rectangle {
196 id: notificationsRect
197
198 width: units.gu(40)
199 height: units.gu(115)
200
201 MouseArea{
202 id: clickThroughCatcher
203
204 anchors.fill: parent
205 }
206
207 Notifications {
208 id: notifications
209
210 margin: units.gu(1)
211
212 anchors.fill: parent
213 model: mockModel
214 }
215 }
216
217 Rectangle {
218 id: interactiveControls
219
220 width: units.gu(30)
221 height: units.gu(115)
222 color: "grey"
223
224 Column {
225 spacing: units.gu(1)
226 anchors.fill: parent
227 anchors.margins: units.gu(1)
228
229 Button {
230 width: parent.width
231 text: "add a snap-decision"
232 onClicked: addSnapDecisionNotification()
233 }
234
235 Button {
236 width: parent.width
237 text: "add a 2over1 snap-decision"
238 onClicked: add2over1SnapDecisionNotification()
239 }
240
241 Button {
242 width: parent.width
243 text: "add an ephemeral"
244 onClicked: addEphemeralNotification()
245 }
246
247 Button {
248 width: parent.width
249 text: "add an non-shaped-icon-summary-body"
250 onClicked: addEphemeralNonShapedIconNotification()
251 }
252
253 Button {
254 width: parent.width
255 text: "add an icon-summary"
256 onClicked: addEphemeralIconSummaryNotification()
257 }
258
259 Button {
260 width: parent.width
261 text: "add an interactive"
262 onClicked: addInteractiveNotification()
263 }
264
265 Button {
266 width: parent.width
267 text: "add a confirmation"
268 onClicked: addConfirmationNotification()
269 }
270
271 Button {
272 width: parent.width
273 text: "add a 2nd confirmation"
274 onClicked: add2ndConfirmationNotification()
275 }
276
277 Button {
278 width: parent.width
279 text: "remove 1st notification"
280 onClicked: remove1stNotification()
281 }
282
283 Button {
284 width: parent.width
285 text: "clear model"
286 onClicked: clearNotifications()
287 }
288 }
289 }
290
291 UnityTestCase {
292 id: root
293 name: "NotificationRendererTest"
294 when: windowShown
295
296 function test_NotificationRenderer_data() {
297 return [
298 {
299 tag: "Snap Decision with secondary icon and button-tint",
300 type: Notification.SnapDecision,71 type: Notification.SnapDecision,
301 hints: {"x-canonical-private-affirmative-tint": "true"},72 hints: {"x-canonical-private-affirmative-tint": "true"},
302 summary: "Tom Ato",73 summary: "Tom Ato",
@@ -307,23 +78,14 @@
307 { id: "cancel_id", label: "Cancel"},78 { id: "cancel_id", label: "Cancel"},
308 { id: "notreally_id", label: "Not really"},79 { id: "notreally_id", label: "Not really"},
309 { id: "noway_id", label: "messages:No way"},80 { id: "noway_id", label: "messages:No way"},
310 { id: "nada_id", label: "messages:Nada"}],81 { id: "nada_id", label: "messages:Nada"}]
311 summaryVisible: true,82 }
312 bodyVisible: true,83
313 iconVisible: true,84 mockModel.append(n)
314 centeredIconVisible: false,85 }
315 shaped: true,86
316 nonShaped: false,87 function add2over1SnapDecisionNotification() {
317 secondaryIconVisible: true,88 var n = {
318 buttonRowVisible: true,
319 buttonTinted: true,
320 hasSound: false,
321 valueVisible: false,
322 valueLabelVisible: false,
323 valueTinted: false
324 },
325 {
326 tag: "2-over-1 Snap Decision with button-tint",
327 type: Notification.SnapDecision,89 type: Notification.SnapDecision,
328 hints: {"x-canonical-private-affirmative-tint": "true"},90 hints: {"x-canonical-private-affirmative-tint": "true"},
329 summary: "Theatre at Ferria Stadium",91 summary: "Theatre at Ferria Stadium",
@@ -332,327 +94,573 @@
332 secondaryIcon: "",94 secondaryIcon: "",
333 actions: [{ id: "ok_id", label: "Ok"},95 actions: [{ id: "ok_id", label: "Ok"},
334 { id: "snooze_id", label: "Snooze"},96 { id: "snooze_id", label: "Snooze"},
335 { id: "view_id", label: "View"}],97 { id: "view_id", label: "View"}]
336 summaryVisible: true,98 }
337 bodyVisible: true,99
338 iconVisible: false,100 mockModel.append(n)
339 centeredIconVisible: false,101 }
340 shaped: false,102
341 secondaryIconVisible: false,103 function addEphemeralNotification() {
342 buttonRowVisible: false,104 var n = {
343 buttonTinted: true,105 type: Notification.Ephemeral,
344 hasSound: false,106 summary: "Cole Raby",
345 valueVisible: false,107 body: "I did not expect it to be that late.",
346 valueLabelVisible: false,108 icon: "../graphics/avatars/amanda.png",
347 valueTinted: false109 secondaryIcon: "../graphics/applicationIcons/facebook.png",
348 },110 actions: []
349 {111 }
350 tag: "Ephemeral notification - icon-summary layout",112
351 type: Notification.Ephemeral,113 mockModel.append(n)
352 hints: {},114 }
115
116 function addEphemeralNonShapedIconNotification() {
117 var n = {
118 type: Notification.Ephemeral,
119 hints: {"x-canonical-non-shaped-icon": "true"},
120 summary: "Contacts",
121 body: "Synchronised contacts-database with cloud-storage.",
122 icon: "../graphics/applicationIcons/contacts-app.png",
123 secondaryIcon: "",
124 actions: []
125 }
126
127 mockModel.append(n)
128 }
129
130 function addEphemeralIconSummaryNotification() {
131 var n = {
132 type: Notification.Ephemeral,
133 hints: {"x-canonical-non-shaped-icon": "false"},
353 summary: "Photo upload completed",134 summary: "Photo upload completed",
354 body: "",135 body: "",
355 icon: "../graphics/applicationIcons/facebook.png",136 icon: "../graphics/applicationIcons/facebook.png",
356 secondaryIcon: "",137 secondaryIcon: "",
357 actions: [],138 actions: []
358 summaryVisible: true,139 }
359 bodyVisible: false,140
360 iconVisible: true,141 mockModel.append(n)
361 centeredIconVisible: false,142 }
362 shaped: true,143
363 secondaryIconVisible: false,144 function addInteractiveNotification() {
364 buttonRowVisible: false,145 var n = {
365 buttonTinted: false,
366 hasSound: false,
367 valueVisible: false,
368 valueLabelVisible: false,
369 valueTinted: false
370 },
371 {
372 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
373 type: Notification.Ephemeral,
374 hints: {"x-canonical-private-affirmative-tint": "false",
375 "sound-file": "dummy.ogg",
376 "suppress-sound": "true"},
377 summary: "New comment successfully published",
378 body: "",
379 icon: "",
380 secondaryIcon: "../graphics/applicationIcons/facebook.png",
381 actions: [],
382 summaryVisible: true,
383 bodyVisible: false,
384 interactiveAreaEnabled: false,
385 iconVisible: false,
386 centeredIconVisible: false,
387 shaped: false,
388 secondaryIconVisible: true,
389 buttonRowVisible: false,
390 buttonTinted: false,
391 hasSound: false,
392 valueVisible: false,
393 valueLabelVisible: false,
394 valueTinted: false
395 },
396 {
397 tag: "Interactive notification",
398 type: Notification.Interactive,146 type: Notification.Interactive,
399 hints: {"x-canonical-private-affirmative-tint": "false",
400 "sound-file": "dummy.ogg"},
401 summary: "Interactive notification",147 summary: "Interactive notification",
402 body: "This is a notification that can be clicked",148 body: "This is a notification that can be clicked",
403 icon: "../graphics/avatars/amanda.png",149 icon: "../graphics/avatars/anna_olsson.png",
404 secondaryIcon: "",150 secondaryIcon: "",
405 actions: [{ id: "reply_id", label: "Dummy"}],151 actions: [{ id: "reply_id", label: "Dummy"}],
406 summaryVisible: true,152 }
407 bodyVisible: true,153
408 iconVisible: true,154 mockModel.append(n)
409 centeredIconVisible: false,155 }
410 shaped: true,156
411 secondaryIconVisible: false,157 function addConfirmationNotification() {
412 buttonRowVisible: false,158 var n = {
413 buttonTinted: false,
414 hasSound: true,
415 valueVisible: false,
416 valueLabelVisible: false,
417 valueTinted: false
418 },
419 {
420 tag: "Snap Decision without secondary icon and no button-tint",
421 type: Notification.SnapDecision,
422 hints: {"x-canonical-private-affirmative-tint": "false",
423 "sound-file": "dummy.ogg"},
424 summary: "Bro Coly",
425 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.",
426 icon: "../graphics/avatars/anna_olsson.png",
427 secondaryIcon: "",
428 actions: [{ id: "accept_id", label: "Accept"},
429 { id: "reject_id", label: "Reject"}],
430 summaryVisible: true,
431 bodyVisible: true,
432 iconVisible: true,
433 centeredIconVisible: false,
434 shaped: true,
435 secondaryIconVisible: false,
436 buttonRowVisible: true,
437 buttonTinted: false,
438 hasSound: true,
439 valueVisible: false,
440 valueLabelVisible: false,
441 valueTinted: false
442 },
443 {
444 tag: "Ephemeral notification",
445 type: Notification.Ephemeral,
446 hints: {"x-canonical-private-affirmative-tint": "false",
447 "sound-file": "dummy.ogg"},
448 summary: "Cole Raby",
449 body: "I did not expect it to be that late.",
450 icon: "../graphics/avatars/funky.png",
451 secondaryIcon: "../graphics/applicationIcons/facebook.png",
452 actions: [],
453 summaryVisible: true,
454 bodyVisible: true,
455 iconVisible: true,
456 centeredIconVisible: false,
457 shaped: true,
458 secondaryIconVisible: true,
459 buttonRowVisible: false,
460 buttonTinted: false,
461 hasSound: true,
462 valueVisible: false,
463 valueLabelVisible: false,
464 valueTinted: false
465 },
466 {
467 tag: "Ephemeral notification with non-shaped icon",
468 type: Notification.Ephemeral,
469 hints: {"x-canonical-private-affirmative-tint": "false",
470 "x-canonical-non-shaped-icon": "true"},
471 summary: "Contacts",
472 body: "Synchronised contacts-database with cloud-storage.",
473 icon: "image://theme/contacts-app",
474 secondaryIcon: "",
475 actions: [],
476 summaryVisible: true,
477 bodyVisible: true,
478 iconVisible: true,
479 centeredIconVisible: false,
480 shaped: false,
481 secondaryIconVisible: false,
482 buttonRowVisible: false,
483 buttonTinted: false,
484 hasSound: false,
485 valueVisible: false,
486 valueLabelVisible: false,
487 valueTinted: false
488 },
489 {
490 tag: "Confirmation notification with value",
491 type: Notification.Confirmation,159 type: Notification.Confirmation,
492 hints: {"x-canonical-non-shaped-icon": "true"},160 hints: {"x-canonical-non-shaped-icon": "true"},
493 summary: "",161 summary: "Confirmation notification",
494 body: "",162 body: "",
495 icon: "image://theme/audio-volume-medium",163 icon: "image://theme/audio-volume-medium",
496 secondaryIcon: "",164 secondaryIcon: "",
497 value: 50,165 value: 50,
498 actions: [],166 actions: [],
499 summaryVisible: false,167 }
500 bodyVisible: false,168
501 iconVisible: false,169 mockModel.append(n)
502 centeredIconVisible: true,170 }
503 shaped: false,171
504 secondaryIconVisible: false,172 function add2ndConfirmationNotification() {
505 buttonRowVisible: false,173 var n = {
506 buttonTinted: false,
507 hasSound: false,
508 valueVisible: true,
509 valueLabelVisible: false,
510 valueTinted: false
511 },
512 {
513 tag: "Confirmation notification with value, label and tint",
514 type: Notification.Confirmation,174 type: Notification.Confirmation,
515 hints: {"x-canonical-non-shaped-icon": "true",175 hints: {"x-canonical-non-shaped-icon": "true",
516 "x-canonical-value-bar-tint" : "true"},176 "x-canonical-value-bar-tint": "true"},
517 summary: "",177 summary: "Confirmation notification",
518 body: "High Volume",178 body: "High Volume",
519 icon: "image://theme/audio-volume-high",179 icon: "image://theme/audio-volume-high",
520 secondaryIcon: "",180 secondaryIcon: "",
521 value: 85,181 value: 85,
522 actions: [],182 actions: [],
523 summaryVisible: false,183 }
524 bodyVisible: false,184
525 iconVisible: false,185 mockModel.append(n)
526 centeredIconVisible: true,186 }
527 shaped: false,187
528 secondaryIconVisible: false,188 function clearNotifications() {
529 buttonRowVisible: false,189 while(mockModel.count > 1) {
530 buttonTinted: false,190 remove1stNotification()
531 hasSound: false,191 }
532 valueVisible: true,192 }
533 valueLabelVisible: true,193
534 valueTinted: true194 function remove1stNotification() {
535 }195 if (mockModel.count > 1)
536 ]196 mockModel.remove(1)
537 }197 }
538198
539 SignalSpy {199 Rectangle {
540 id: clickThroughSpy200 id: notificationsRect
541201
542 target: clickThroughCatcher202 width: units.gu(40)
543 signalName: "clicked"203 height: units.gu(115)
544 }204
545205 MouseArea{
546 SignalSpy {206 id: clickThroughCatcher
547 id: actionSpy207
548208 anchors.fill: parent
549 target: mockModel209 }
550 signalName: "actionInvoked"210
551 }211 Notifications {
552212 id: notifications
553 function cleanup() {213
554 clickThroughSpy.clear()214 margin: units.gu(1)
555 actionSpy.clear()215
556 }216 anchors.fill: parent
557217 model: mockModel
558 function test_NotificationRenderer(data) {218 }
559 // populate model with some mock notifications219 }
560 mockModel.append(data)220
561221 Rectangle {
562 // make sure the view is properly updated before going on222 id: interactiveControls
563 notifications.forceLayout();223
564 waitForRendering(notifications);224 width: units.gu(30)
565225 height: units.gu(115)
566 var notification = findChild(notifications, "notification" + (mockModel.count - 1))226 color: "grey"
567 verify(notification !== undefined, "notification wasn't found");227
568228 Column {
569 waitForRendering(notification);229 spacing: units.gu(1)
570230 anchors.fill: parent
571 var icon = findChild(notification, "icon")231 anchors.margins: units.gu(1)
572 var centeredIcon = findChild(notification, "centeredIcon")232
573 var interactiveArea = findChild(notification, "interactiveArea")233 Button {
574 var secondaryIcon = findChild(notification, "secondaryIcon")234 width: parent.width
575 var summaryLabel = findChild(notification, "summaryLabel")235 text: "add a snap-decision"
576 var bodyLabel = findChild(notification, "bodyLabel")236 onClicked: rootRow.addSnapDecisionNotification()
577 var buttonRow = findChild(notification, "buttonRow")237 }
578 var valueIndicator = findChild(notification, "valueIndicator")238
579 var valueLabel = findChild(notification, "valueLabel")239 Button {
580 var innerBar = findChild(notification, "innerBar")240 width: parent.width
581241 text: "add a 2over1 snap-decision"
582 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")242 onClicked: rootRow.add2over1SnapDecisionNotification()
583 if (icon.visible) {243 }
584 compare(icon.shaped, data.shaped, "shaped-status is incorrect")244
585 }245 Button {
586 compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")246 width: parent.width
587 if (centeredIcon.visible) {247 text: "add an ephemeral"
588 compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")248 onClicked: rootRow.addEphemeralNotification()
589 }249 }
590 compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")250
591 if (valueIndicator.visible) {251 Button {
592 verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")252 width: parent.width
593 }253 text: "add an non-shaped-icon-summary-body"
594 compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")254 onClicked: rootRow.addEphemeralNonShapedIconNotification()
595255 }
596 // test input does not fall through256
597 mouseClick(notification, notification.width / 2, notification.height / 2)257 Button {
598 if(data.type == Notification.Interactive) {258 width: parent.width
599 actionSpy.wait()259 text: "add an icon-summary"
600 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")260 onClicked: rootRow.addEphemeralIconSummaryNotification()
601 }261 }
602 compare(clickThroughSpy.count, 0, "click on notification fell through")262
603263 Button {
604 compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")264 width: parent.width
605 compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")265 text: "add an interactive"
606 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")266 onClicked: rootRow.addInteractiveNotification()
607 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")267 }
608268
609 var audioItem = findInvisibleChild(notification, "sound")269 Button {
610 compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")270 width: parent.width
611271 text: "add a confirmation"
612 if(data.buttonRowVisible) {272 onClicked: rootRow.addConfirmationNotification()
613 var buttonCancel = findChild(buttonRow, "notify_button1")273 }
614 var buttonAccept = findChild(buttonRow, "notify_button0")274
615275 Button {
616 // only test the left/cancel-button if two actions have been passed in276 width: parent.width
617 if (data.actions.length == 2) {277 text: "add a 2nd confirmation"
618 tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);278 onClicked: rootRow.add2ndConfirmationNotification()
619 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")279 }
620 actionSpy.clear()280
621 }281 Button {
622282 width: parent.width
623 // check the tinting of the positive/right button283 text: "remove 1st notification"
624 verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")284 onClicked: rootRow.remove1stNotification()
625285 }
626 // click the positive/right button286
627 tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);287 Button {
628 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")288 width: parent.width
289 text: "clear model"
290 onClicked: rootRow.clearNotifications()
291 }
292 }
293 }
294
295 ActionModel {
296 id: myActionModel
297 }
298
299 UnityTestCase {
300 id: root
301 name: "NotificationRendererTest"
302 when: windowShown
303
304 function test_NotificationRenderer_data() {
305 return [
306 {
307 tag: "Snap Decision with secondary icon and button-tint",
308 type: Notification.SnapDecision,
309 hints: {"x-canonical-private-affirmative-tint": "true"},
310 summary: "Tom Ato",
311 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
312 icon: "../graphics/avatars/funky.png",
313 secondaryIcon: "../graphics/applicationIcons/facebook.png",
314 actions: [{ id: "ok_id", label: "Ok"},
315 { id: "cancel_id", label: "Cancel"},
316 { id: "notreally_id", label: "Not really"},
317 { id: "noway_id", label: "messages:No way"},
318 { id: "nada_id", label: "messages:Nada"}],
319 summaryVisible: true,
320 bodyVisible: true,
321 iconVisible: true,
322 centeredIconVisible: false,
323 shaped: true,
324 secondaryIconVisible: true,
325 buttonRowVisible: true,
326 buttonTinted: true,
327 hasSound: false,
328 valueVisible: false,
329 valueLabelVisible: false,
330 valueTinted: false
331 },
332 {
333 tag: "2-over-1 Snap Decision with button-tint",
334 type: Notification.SnapDecision,
335 hints: {"x-canonical-private-affirmative-tint": "true"},
336 summary: "Theatre at Ferria Stadium",
337 body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
338 icon: "",
339 secondaryIcon: "",
340 actions: [{ id: "ok_id", label: "Ok"},
341 { id: "snooze_id", label: "Snooze"},
342 { id: "view_id", label: "View"}],
343 summaryVisible: true,
344 bodyVisible: true,
345 iconVisible: false,
346 centeredIconVisible: false,
347 shaped: false,
348 secondaryIconVisible: false,
349 buttonRowVisible: false,
350 buttonTinted: true,
351 hasSound: false,
352 valueVisible: false,
353 valueLabelVisible: false,
354 valueTinted: false
355 },
356 {
357 tag: "Ephemeral notification - icon-summary layout",
358 type: Notification.Ephemeral,
359 hints: {},
360 summary: "Photo upload completed",
361 body: "",
362 icon: "../graphics/applicationIcons/facebook.png",
363 secondaryIcon: "",
364 actions: [],
365 summaryVisible: true,
366 bodyVisible: false,
367 iconVisible: true,
368 centeredIconVisible: false,
369 shaped: true,
370 secondaryIconVisible: false,
371 buttonRowVisible: false,
372 buttonTinted: false,
373 hasSound: false,
374 valueVisible: false,
375 valueLabelVisible: false,
376 valueTinted: false
377 },
378 {
379 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
380 type: Notification.Ephemeral,
381 hints: {"x-canonical-private-affirmative-tint": "false",
382 "sound-file": "dummy.ogg",
383 "suppress-sound": "true"},
384 summary: "New comment successfully published",
385 body: "",
386 icon: "",
387 secondaryIcon: "../graphics/applicationIcons/facebook.png",
388 actions: [],
389 summaryVisible: true,
390 bodyVisible: false,
391 interactiveAreaEnabled: false,
392 iconVisible: false,
393 centeredIconVisible: false,
394 shaped: false,
395 secondaryIconVisible: true,
396 buttonRowVisible: false,
397 buttonTinted: false,
398 hasSound: false,
399 valueVisible: false,
400 valueLabelVisible: false,
401 valueTinted: false
402 },
403 {
404 tag: "Interactive notification",
405 type: Notification.Interactive,
406 hints: {"x-canonical-private-affirmative-tint": "false",
407 "sound-file": "dummy.ogg"},
408 summary: "Interactive notification",
409 body: "This is a notification that can be clicked",
410 icon: "../graphics/avatars/amanda.png",
411 secondaryIcon: "",
412 actions: [{ id: "reply_id", label: "Dummy"}],
413 summaryVisible: true,
414 bodyVisible: true,
415 iconVisible: true,
416 centeredIconVisible: false,
417 shaped: true,
418 secondaryIconVisible: false,
419 buttonRowVisible: false,
420 buttonTinted: false,
421 hasSound: true,
422 valueVisible: false,
423 valueLabelVisible: false,
424 valueTinted: false
425 },
426 {
427 tag: "Snap Decision without secondary icon and no button-tint",
428 type: Notification.SnapDecision,
429 hints: {"x-canonical-private-affirmative-tint": "false",
430 "sound-file": "dummy.ogg"},
431 summary: "Bro Coly",
432 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.",
433 icon: "../graphics/avatars/anna_olsson.png",
434 secondaryIcon: "",
435 actions: [{ id: "accept_id", label: "Accept"},
436 { id: "reject_id", label: "Reject"}],
437 summaryVisible: true,
438 bodyVisible: true,
439 iconVisible: true,
440 centeredIconVisible: false,
441 shaped: true,
442 secondaryIconVisible: false,
443 buttonRowVisible: true,
444 buttonTinted: false,
445 hasSound: true,
446 valueVisible: false,
447 valueLabelVisible: false,
448 valueTinted: false
449 },
450 {
451 tag: "Ephemeral notification",
452 type: Notification.Ephemeral,
453 hints: {"x-canonical-private-affirmative-tint": "false",
454 "sound-file": "dummy.ogg"},
455 summary: "Cole Raby",
456 body: "I did not expect it to be that late.",
457 icon: "../graphics/avatars/funky.png",
458 secondaryIcon: "../graphics/applicationIcons/facebook.png",
459 actions: [],
460 summaryVisible: true,
461 bodyVisible: true,
462 iconVisible: true,
463 centeredIconVisible: false,
464 shaped: true,
465 secondaryIconVisible: true,
466 buttonRowVisible: false,
467 buttonTinted: false,
468 hasSound: true,
469 valueVisible: false,
470 valueLabelVisible: false,
471 valueTinted: false
472 },
473 {
474 tag: "Ephemeral notification with non-shaped icon",
475 type: Notification.Ephemeral,
476 hints: {"x-canonical-private-affirmative-tint": "false",
477 "x-canonical-non-shaped-icon": "true"},
478 summary: "Contacts",
479 body: "Synchronised contacts-database with cloud-storage.",
480 icon: "image://theme/contacts-app",
481 secondaryIcon: "",
482 actions: [],
483 summaryVisible: true,
484 bodyVisible: true,
485 iconVisible: true,
486 centeredIconVisible: false,
487 shaped: false,
488 secondaryIconVisible: false,
489 buttonRowVisible: false,
490 buttonTinted: false,
491 hasSound: false,
492 valueVisible: false,
493 valueLabelVisible: false,
494 valueTinted: false
495 },
496 {
497 tag: "Confirmation notification with value",
498 type: Notification.Confirmation,
499 hints: {"x-canonical-non-shaped-icon": "true"},
500 summary: "",
501 body: "",
502 icon: "image://theme/audio-volume-medium",
503 secondaryIcon: "",
504 value: 50,
505 actions: [],
506 summaryVisible: false,
507 bodyVisible: false,
508 iconVisible: false,
509 centeredIconVisible: true,
510 shaped: false,
511 secondaryIconVisible: false,
512 buttonRowVisible: false,
513 buttonTinted: false,
514 hasSound: false,
515 valueVisible: true,
516 valueLabelVisible: false,
517 valueTinted: false
518 },
519 {
520 tag: "Confirmation notification with value, label and tint",
521 type: Notification.Confirmation,
522 hints: {"x-canonical-non-shaped-icon": "true",
523 "x-canonical-value-bar-tint" : "true"},
524 summary: "",
525 body: "High Volume",
526 icon: "image://theme/audio-volume-high",
527 secondaryIcon: "",
528 value: 85,
529 actions: [],
530 summaryVisible: false,
531 bodyVisible: false,
532 iconVisible: false,
533 centeredIconVisible: true,
534 shaped: false,
535 secondaryIconVisible: false,
536 buttonRowVisible: false,
537 buttonTinted: false,
538 hasSound: false,
539 valueVisible: true,
540 valueLabelVisible: true,
541 valueTinted: true
542 }
543 ]
544 }
545
546 SignalSpy {
547 id: clickThroughSpy
548
549 target: clickThroughCatcher
550 signalName: "clicked"
551 }
552
553 SignalSpy {
554 id: actionSpy
555
556 target: mockModel
557 signalName: "actionInvoked"
558 }
559
560 function cleanup() {
561 clickThroughSpy.clear()
629 actionSpy.clear()562 actionSpy.clear()
630 waitForRendering(notification)563 }
631564
632 // check if there's a ComboButton created due to more actions being passed565 function test_NotificationRenderer(data) {
633 if (data.actions.length > 2) {566 // populate model with some mock notifications
634 var comboButton = findChild(notification, "notify_button2")567 mockModel.append(data)
635 tryCompareFunction(function() { return comboButton.expanded == false; }, true);568
636569 // make sure the view is properly updated before going on
637 // click to expand570 notifications.forceLayout();
638 tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);571 waitForRendering(notifications);
639572
640 // try clicking on choices in expanded comboList573 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
641 var choiceButton1 = findChild(notification, "notify_button3")574 verify(notification !== undefined, "notification wasn't found");
642 tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);575
643 compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")576 waitForRendering(notification);
644 actionSpy.clear()577
645578 var icon = findChild(notification, "icon")
646 var choiceButton2 = findChild(notification, "notify_button4")579 var centeredIcon = findChild(notification, "centeredIcon")
647 tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);580 var interactiveArea = findChild(notification, "interactiveArea")
648 compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")581 var secondaryIcon = findChild(notification, "secondaryIcon")
649 actionSpy.clear()582 var summaryLabel = findChild(notification, "summaryLabel")
650583 var bodyLabel = findChild(notification, "bodyLabel")
651 // click to collapse584 var buttonRow = findChild(notification, "buttonRow")
652 //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);585 var valueIndicator = findChild(notification, "valueIndicator")
653 } else {586 var valueLabel = findChild(notification, "valueLabel")
654 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)587 var innerBar = findChild(notification, "innerBar")
655 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")588
589 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
590 if (icon.visible) {
591 compare(icon.shaped, data.shaped, "shaped-status is incorrect")
592 }
593 compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
594 if (centeredIcon.visible) {
595 compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
596 }
597 compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
598 if (valueIndicator.visible) {
599 verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
600 }
601 compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
602
603 // test input does not fall through
604 mouseClick(notification, notification.width / 2, notification.height / 2)
605 if(data.type == Notification.Interactive) {
606 actionSpy.wait()
607 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
608 }
609 compare(clickThroughSpy.count, 0, "click on notification fell through")
610
611 compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
612 compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
613 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
614 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
615
616 var audioItem = findInvisibleChild(notification, "sound")
617 compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
618
619 if(data.buttonRowVisible) {
620 var buttonCancel = findChild(buttonRow, "notify_button1")
621 var buttonAccept = findChild(buttonRow, "notify_button0")
622
623 // only test the left/cancel-button if two actions have been passed in
624 if (data.actions.length == 2) {
625 tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
626 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
627 actionSpy.clear()
628 }
629
630 // check the tinting of the positive/right button
631 verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
632
633 // click the positive/right button
634 tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
635 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
636 actionSpy.clear()
637 waitForRendering (notification)
638
639 // check if there's a ComboButton created due to more actions being passed
640 if (data.actions.length > 2) {
641 var comboButton = findChild(notification, "notify_button2")
642 tryCompareFunction(function() { return comboButton.expanded == false; }, true);
643
644 // click to expand
645 tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
646
647 // try clicking on choices in expanded comboList
648 var choiceButton1 = findChild(notification, "notify_button3")
649 tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
650 compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
651 actionSpy.clear()
652
653 var choiceButton2 = findChild(notification, "notify_button4")
654 tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
655 compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
656 actionSpy.clear()
657
658 // click to collapse
659 //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
660 } else {
661 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
662 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
663 }
656 }664 }
657 }665 }
658 }666 }
659667
=== added file 'tests/qmltests/Notifications/tst_SwipeToAct.qml'
--- tests/qmltests/Notifications/tst_SwipeToAct.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Notifications/tst_SwipeToAct.qml 2014-10-31 10:46:25 +0000
@@ -0,0 +1,276 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU 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 General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtTest 1.0
19import ".."
20import "../../../qml/Notifications"
21import Ubuntu.Components 0.1
22import Unity.Test 0.1
23import Unity.Notifications 1.0
24
25Item {
26 width: notificationsRect.width + interactiveControls.width
27 height: notificationsRect.height
28
29 Row {
30 id: rootRow
31
32 Component {
33 id: mockNotification
34
35 QtObject {
36 function invokeAction(actionId) {
37 mockModel.actionInvoked(actionId)
38 }
39 }
40 }
41
42 ListModel {
43 id: mockModel
44 dynamicRoles: true
45
46 signal actionInvoked(string actionId)
47
48 function getRaw(id) {
49 return mockNotification.createObject(mockModel)
50 }
51
52 // add the default/PlaceHolder notification to the model
53 Component.onCompleted: {
54 var n = {
55 type: Notification.PlaceHolder,
56 hints: {},
57 summary: "",
58 body: "",
59 icon: "",
60 secondaryIcon: "",
61 actions: []
62 }
63
64 append(n)
65 }
66 }
67
68 function addSwipeToActNotification() {
69 var n = {
70 type: Notification.SnapDecision,
71 hints: {"x-canonical-snap-decisions-swipe": "true"},
72 summary: "Incoming call",
73 body: "Frank Zappa\n+44 (0)7736 027340",
74 icon: "../graphics/avatars/amanda.png",
75 secondaryIcon: "incoming-call",
76 actions: [{ id: "ok_id", label: "Ok"},
77 { id: "cancel_id", label: "Cancel"}]
78 }
79
80 mockModel.append(n)
81 }
82
83 function clearNotifications() {
84 mockModel.clear()
85 }
86
87 function remove1stNotification() {
88 if (mockModel.count > 0)
89 mockModel.remove(0)
90 }
91
92 Rectangle {
93 id: notificationsRect
94
95 width: units.gu(40)
96 height: units.gu(71)
97
98 MouseArea{
99 id: clickThroughCatcher
100
101 anchors.fill: parent
102 }
103
104 Notifications {
105 id: notifications
106
107 margin: units.gu(1)
108
109 anchors.fill: parent
110 model: mockModel
111 }
112 }
113
114 Rectangle {
115 id: interactiveControls
116
117 width: units.gu(30)
118 height: units.gu(81)
119 color: "grey"
120
121 Column {
122 spacing: units.gu(1)
123 anchors.fill: parent
124 anchors.margins: units.gu(1)
125
126 Button {
127 width: parent.width
128 text: "add a SwipeToAct snap-decision"
129 onClicked: rootRow.addSwipeToActNotification()
130 }
131
132 Button {
133 width: parent.width
134 text: "remove 1st notification"
135 onClicked: rootRow.remove1stNotification()
136 }
137
138 Button {
139 width: parent.width
140 text: "clear model"
141 onClicked: rootRow.clearNotifications()
142 }
143 }
144 }
145
146 ActionModel {
147 id: myActionModel
148 }
149
150 UnityTestCase {
151 id: root
152 name: "NotificationRendererTest"
153 when: windowShown
154
155 function test_NotificationRenderer_data() {
156 return [
157 {
158 tag: "Snap Decision with SwipeToAct-widget (accept)",
159 type: Notification.SnapDecision,
160 hints: {"x-canonical-snap-decisions-swipe": "true"},
161 summary: "Incoming call",
162 body: "Frank Zappa\n+44 (0)7736 027340",
163 icon: "../graphics/avatars/amanda.png",
164 secondaryIcon: "../graphics/applicationIcons/facebook.png",
165 actions: myActionModel,
166 summaryVisible: true,
167 bodyVisible: true,
168 iconVisible: true,
169 shaped: true,
170 secondaryIconVisible: true,
171 buttonRowVisible: true,
172 buttonTinted: false,
173 checkSwipeToActAccept: true,
174 checkSwipeToActReject: false
175 },
176 {
177 tag: "Snap Decision with SwipeToAct-widget (reject)",
178 type: Notification.SnapDecision,
179 hints: {"x-canonical-snap-decisions-swipe": "true"},
180 summary: "Incoming call",
181 body: "Bro Coly\n+49 (0)221 426973",
182 icon: "../graphics/avatars/funky.png",
183 secondaryIcon: "../graphics/applicationIcons/facebook.png",
184 actions: myActionModel,
185 summaryVisible: true,
186 bodyVisible: true,
187 iconVisible: true,
188 shaped: true,
189 secondaryIconVisible: true,
190 buttonRowVisible: true,
191 buttonTinted: false,
192 checkSwipeToActAccept: false,
193 checkSwipeToActReject: true
194 }
195 ]
196 }
197
198 SignalSpy {
199 id: clickThroughSpy
200
201 target: clickThroughCatcher
202 signalName: "clicked"
203 }
204
205 SignalSpy {
206 id: actionSpy
207
208 target: mockModel
209 signalName: "actionInvoked"
210 }
211
212 function cleanup() {
213 clickThroughSpy.clear()
214 actionSpy.clear()
215 }
216
217 function test_NotificationRenderer(data) {
218 // populate model with some mock notifications
219 mockModel.append(data)
220
221 // make sure the view is properly updated before going on
222 notifications.forceLayout();
223 waitForRendering(notifications);
224
225 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
226 verify(notification !== undefined, "notification wasn't found");
227
228 waitForRendering(notification);
229
230 var icon = findChild(notification, "icon")
231 var interactiveArea = findChild(notification, "interactiveArea")
232 var secondaryIcon = findChild(notification, "secondaryIcon")
233 var summaryLabel = findChild(notification, "summaryLabel")
234 var bodyLabel = findChild(notification, "bodyLabel")
235 var buttonRow = findChild(notification, "buttonRow")
236
237 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
238 if (icon.visible) {
239 compare(icon.shaped, data.shaped, "shaped-status is incorrect")
240 }
241
242 // test input does not fall through
243 mouseClick(notification, notification.width / 2, notification.height / 2)
244 if(data.type == Notification.Interactive) {
245 actionSpy.wait()
246 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
247 }
248 compare(clickThroughSpy.count, 0, "click on notification fell through")
249
250 compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
251 compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
252 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
253 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
254
255 if(data.buttonRowVisible) {
256 var swipeButton = findChild(buttonRow, "notify_swipe_button")
257 var slider = findChild(swipeButton, "slider")
258 var swipeMouseArea = findChild(swipeButton, "swipeMouseArea")
259 var x = swipeMouseArea.width / 2
260 var y = swipeMouseArea.height / 2
261
262 if(data.checkSwipeToActReject) {
263 tryCompareFunction(function() { mouseDrag(slider, x, y, -(swipeMouseArea.width / 2), 0); return actionSpy.signalArguments.length > 0; }, true);
264 compare(actionSpy.signalArguments[0][0], data.actions.data(0, ActionModel.RoleActionId), "got wrong id for negative action")
265 actionSpy.clear()
266 }
267 if(data.checkSwipeToActAccept) {
268 tryCompareFunction(function() { mouseDrag(slider, x, y, (swipeMouseArea.width / 2) - slider.width, 0); return actionSpy.signalArguments.length > 0; }, true);
269 compare(actionSpy.signalArguments[0][0], data.actions.data(1, ActionModel.RoleActionId), "got wrong id for positive action")
270 actionSpy.clear()
271 }
272 }
273 }
274 }
275 }
276}

Subscribers

People subscribed via source and target branches