on xenial help sign in
Added dedicated swipe-to-act button for snap-decisions, which avoids accidental taps/button-presses. Fixes: 1358343

Approved by: Josh Arenson, Albert Astals Cid

commit9fe1a47c381effd305ad04312e2183a3328ef362 authorMirco Müller <mirco.mueller@canonical.com> dateTue 4 Nov 2014, 12:54 (12 years ago) parent5aaaf3aaddc914
15 files changed+1432−627
qml/Notifications/Notification.qml+22−3 qml/Notifications/SwipeToAct.qml+309 tests/autopilot/unity8/shell/tests/test_notifications.py−51 tests/mocks/Unity/Notifications/CMakeLists.txt+15−1 tests/mocks/Unity/Notifications/MockActionModel.cpp+72 tests/mocks/Unity/Notifications/MockActionModel.h+51 tests/mocks/Unity/Notifications/MockNotificationTypes.cpp+26 tests/mocks/Unity/Notifications/MockNotificationTypes.h+36 tests/mocks/Unity/Notifications/notification.js−23 tests/mocks/Unity/Notifications/plugin.cpp+31 tests/mocks/Unity/Notifications/plugin.h+35 tests/mocks/Unity/Notifications/qmldir+2−1 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Notifications/tst_Notifications.qml+556−548 tests/qmltests/Notifications/tst_SwipeToAct.qml+276
qml/Notifications/Notification.qml+22−3
@@ -398,7 +398,7 @@ Item {
398398
399399 spacing: contentSpacing
400400
401− visible: notification.type == Notification.SnapDecision && oneOverTwoRepeaterTop.count == 3
401+ visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
402402
403403 Repeater {
404404 id: oneOverTwoRepeaterTop
@@ -468,22 +468,41 @@ Item {
468468 spacing: units.gu(2)
469469 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+
471490 Repeater {
472491 id: actionRepeater
473−
474492 model: notification.actions
475493 delegate: Loader {
476494 id: loader
477495
478496 property string actionId: id
479497 property string actionLabel: label
498+ active: !notifySwipeButtonLoader.active
480499
481500 Component {
482501 id: actionButton
483502
484503 Button {
485504 objectName: "notify_button" + index
486− width: buttonRow.width / 2 - spacing*2
505+ width: buttonRow.width / 2 - spacing * 2
487506 text: loader.actionLabel
488507 color: {
489508 var result = sdDarkGrey;
qml/Notifications/SwipeToAct.qmlnew file+309
@@ -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+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import QtGraphicalEffects 1.0
20+
21+Item {
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+}
tests/autopilot/unity8/shell/tests/test_notifications.py−51
@@ -157,57 +157,6 @@ class InteractiveNotificationBase(NotificationsBase):
157157
158158 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−
211160 def test_sd_one_over_two_layout(self):
212161 """Snap-decision with three actions should use one-over two button layout."""
213162 unity_proxy = self.launch_unity()
tests/mocks/Unity/Notifications/CMakeLists.txt+15−1
@@ -1 +1,15 @@
1−add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications)
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+)
4+
5+set(MockNotificationsPlugin_SOURCES
6+ plugin.cpp
7+ MockNotificationTypes.cpp
8+ MockActionModel.cpp
9+)
10+
11+add_library(MockNotificationsPlugin MODULE ${MockNotificationsPlugin_SOURCES})
12+
13+qt5_use_modules(MockNotificationsPlugin Core Quick)
14+
15+add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications TARGETS MockNotificationsPlugin)
tests/mocks/Unity/Notifications/MockActionModel.cppnew file+72
@@ -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+
22+struct ActionModelPrivate {
23+ QList<QString> labels;
24+ QList<QString> ids;
25+};
26+
27+ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
28+ insertAction("ok_id", "Ok");
29+ insertAction("cancel_id", "Cancel");
30+}
31+
32+ActionModel::~ActionModel() {
33+}
34+
35+int ActionModel::rowCount(const QModelIndex &index) const {
36+ return p->labels.size();
37+}
38+
39+QVariant 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+
55+QHash<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+
64+Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
65+{
66+ return data(index(row, 0), role);
67+}
68+
69+void ActionModel::insertAction(const QString &id, const QString &label) {
70+ p->ids.push_back(id);
71+ p->labels.push_back(label);
72+}
tests/mocks/Unity/Notifications/MockActionModel.hnew file+51
@@ -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+
25+struct ActionModelPrivate;
26+
27+class ActionModel : public QStringListModel {
28+ Q_OBJECT
29+
30+public:
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+
47+private:
48+ QScopedPointer<ActionModelPrivate> p;
49+};
50+
51+#endif // MOCK_ACTION_MODEL_H
tests/mocks/Unity/Notifications/MockNotificationTypes.cppnew file+26
@@ -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+
22+MockNotification::MockNotification(QObject *parent) : QObject(parent) {
23+}
24+
25+MockNotification::~MockNotification() {
26+}
tests/mocks/Unity/Notifications/MockNotificationTypes.hnew file+36
@@ -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+
25+class MockNotification : public QObject {
26+ Q_OBJECT
27+ Q_ENUMS(Type)
28+
29+public:
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
tests/mocks/Unity/Notifications/notification.jsdeleted−23
@@ -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−
19−var Confirmation = 0;
20−var Ephemeral = 1;
21−var Interactive = 2;
22−var SnapDecision = 3;
23−var PlaceHolder = 4;
tests/mocks/Unity/Notifications/plugin.cppnew file+31
@@ -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+
26+void 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+}
tests/mocks/Unity/Notifications/plugin.hnew file+35
@@ -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+
26+class TestNotificationPlugin : public QQmlExtensionPlugin
27+{
28+ Q_OBJECT
29+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
30+
31+public:
32+ void registerTypes(const char* uri);
33+};
34+
35+#endif // TESTNOTIFICATION_PLUGIN_H
tests/mocks/Unity/Notifications/qmldir+2−1
@@ -1,3 +1,4 @@
11module Unity.Notifications
2−Notification 1.0 notification.js
2+plugin MockNotificationsPlugin
33typeinfo Notifications.qmltypes
4+
tests/qmltests/CMakeLists.txt+1
@@ -70,6 +70,7 @@ add_qml_test(Hud Result)
7070add_qml_test(Launcher Launcher)
7171add_qml_test(Notifications Notifications)
7272add_qml_test(Notifications VisualSnapDecisionsQueue)
73+add_qml_test(Notifications SwipeToAct)
7374add_qml_test(Panel ActiveCallHint)
7475add_qml_test(Panel IndicatorItem)
7576add_qml_test(Panel IndicatorItemRow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
tests/qmltests/Notifications/tst_Notifications.qml+556−548
@@ -23,280 +23,51 @@ import Unity.Test 0.1
2323import Unity.Notifications 1.0
2424import QtMultimedia 5.0
2525
26−Row {
27− id: rootRow
26+Item {
27+ width: notificationsRect.width + interactiveControls.width
28+ height: notificationsRect.height
2829
29− Component {
30− id: mockNotification
30+ Row {
31+ id: rootRow
3132
32− QtObject {
33− function invokeAction(actionId) {
34− mockModel.actionInvoked(actionId)
35− }
36− }
37− }
38−
39− ListModel {
40− id: mockModel
41−
42− signal actionInvoked(string actionId)
43−
44− function getRaw(id) {
45− return mockNotification.createObject(mockModel)
46− }
33+ Component {
34+ id: mockNotification
4735
48− // add the default/PlaceHolder notification to the model
49− Component.onCompleted: {
50− var n = {
51− type: Notification.PlaceHolder,
52− hints: {},
53− summary: "",
54− body: "",
55− icon: "",
56− value: 0,
57− secondaryIcon: "",
58− actions: []
36+ QtObject {
37+ function invokeAction(actionId) {
38+ mockModel.actionInvoked(actionId)
39+ }
5940 }
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"}]
7841 }
7942
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− }
43+ ListModel {
44+ id: mockModel
45+ dynamicRoles: true
19446
195− Rectangle {
196− id: notificationsRect
47+ signal actionInvoked(string actionId)
19748
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()
49+ function getRaw(id) {
50+ return mockNotification.createObject(mockModel)
27551 }
27652
277− Button {
278− width: parent.width
279− text: "remove 1st notification"
280− onClicked: remove1stNotification()
281− }
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+ }
28264
283− Button {
284− width: parent.width
285− text: "clear model"
286− onClicked: clearNotifications()
65+ append(n)
28766 }
28867 }
289− }
29068
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",
69+ function addSnapDecisionNotification() {
70+ var n = {
30071 type: Notification.SnapDecision,
30172 hints: {"x-canonical-private-affirmative-tint": "true"},
30273 summary: "Tom Ato",
@@ -307,23 +78,14 @@ Row {
30778 { id: "cancel_id", label: "Cancel"},
30879 { id: "notreally_id", label: "Not really"},
30980 { id: "noway_id", label: "messages:No way"},
310− { id: "nada_id", label: "messages:Nada"}],
311− summaryVisible: true,
312− bodyVisible: true,
313− iconVisible: true,
314− centeredIconVisible: false,
315− shaped: true,
316− nonShaped: false,
317− secondaryIconVisible: true,
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",
81+ { id: "nada_id", label: "messages:Nada"}]
82+ }
83+
84+ mockModel.append(n)
85+ }
86+
87+ function add2over1SnapDecisionNotification() {
88+ var n = {
32789 type: Notification.SnapDecision,
32890 hints: {"x-canonical-private-affirmative-tint": "true"},
32991 summary: "Theatre at Ferria Stadium",
@@ -332,327 +94,573 @@ Row {
33294 secondaryIcon: "",
33395 actions: [{ id: "ok_id", label: "Ok"},
33496 { id: "snooze_id", label: "Snooze"},
335− { id: "view_id", label: "View"}],
336− summaryVisible: true,
337− bodyVisible: true,
338− iconVisible: false,
339− centeredIconVisible: false,
340− shaped: false,
341− secondaryIconVisible: false,
342− buttonRowVisible: false,
343− buttonTinted: true,
344− hasSound: false,
345− valueVisible: false,
346− valueLabelVisible: false,
347− valueTinted: false
348− },
349− {
350− tag: "Ephemeral notification - icon-summary layout",
97+ { id: "view_id", label: "View"}]
98+ }
99+
100+ mockModel.append(n)
101+ }
102+
103+ function addEphemeralNotification() {
104+ var n = {
105+ type: Notification.Ephemeral,
106+ summary: "Cole Raby",
107+ body: "I did not expect it to be that late.",
108+ icon: "../graphics/avatars/amanda.png",
109+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
110+ actions: []
111+ }
112+
113+ mockModel.append(n)
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 = {
351132 type: Notification.Ephemeral,
352− hints: {},
133+ hints: {"x-canonical-non-shaped-icon": "false"},
353134 summary: "Photo upload completed",
354135 body: "",
355136 icon: "../graphics/applicationIcons/facebook.png",
356137 secondaryIcon: "",
357− actions: [],
358− summaryVisible: true,
359− bodyVisible: false,
360− iconVisible: true,
361− centeredIconVisible: false,
362− shaped: true,
363− secondaryIconVisible: false,
364− buttonRowVisible: false,
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",
138+ actions: []
139+ }
140+
141+ mockModel.append(n)
142+ }
143+
144+ function addInteractiveNotification() {
145+ var n = {
398146 type: Notification.Interactive,
399− hints: {"x-canonical-private-affirmative-tint": "false",
400− "sound-file": "dummy.ogg"},
401147 summary: "Interactive notification",
402148 body: "This is a notification that can be clicked",
403− icon: "../graphics/avatars/amanda.png",
404− secondaryIcon: "",
405− actions: [{ id: "reply_id", label: "Dummy"}],
406− summaryVisible: true,
407− bodyVisible: true,
408− iconVisible: true,
409− centeredIconVisible: false,
410− shaped: true,
411− secondaryIconVisible: false,
412− buttonRowVisible: false,
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.",
426149 icon: "../graphics/avatars/anna_olsson.png",
427150 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",
151+ actions: [{ id: "reply_id", label: "Dummy"}],
152+ }
153+
154+ mockModel.append(n)
155+ }
156+
157+ function addConfirmationNotification() {
158+ var n = {
491159 type: Notification.Confirmation,
492160 hints: {"x-canonical-non-shaped-icon": "true"},
493− summary: "",
161+ summary: "Confirmation notification",
494162 body: "",
495163 icon: "image://theme/audio-volume-medium",
496164 secondaryIcon: "",
497165 value: 50,
498166 actions: [],
499− summaryVisible: false,
500− bodyVisible: false,
501− iconVisible: false,
502− centeredIconVisible: true,
503− shaped: false,
504− secondaryIconVisible: false,
505− buttonRowVisible: false,
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",
167+ }
168+
169+ mockModel.append(n)
170+ }
171+
172+ function add2ndConfirmationNotification() {
173+ var n = {
514174 type: Notification.Confirmation,
515175 hints: {"x-canonical-non-shaped-icon": "true",
516− "x-canonical-value-bar-tint" : "true"},
517− summary: "",
176+ "x-canonical-value-bar-tint": "true"},
177+ summary: "Confirmation notification",
518178 body: "High Volume",
519179 icon: "image://theme/audio-volume-high",
520180 secondaryIcon: "",
521181 value: 85,
522182 actions: [],
523− summaryVisible: false,
524− bodyVisible: false,
525− iconVisible: false,
526− centeredIconVisible: true,
527− shaped: false,
528− secondaryIconVisible: false,
529− buttonRowVisible: false,
530− buttonTinted: false,
531− hasSound: false,
532− valueVisible: true,
533− valueLabelVisible: true,
534− valueTinted: true
535183 }
536− ]
184+
185+ mockModel.append(n)
537186 }
538187
539− SignalSpy {
540− id: clickThroughSpy
188+ function clearNotifications() {
189+ while(mockModel.count > 1) {
190+ remove1stNotification()
191+ }
192+ }
541193
542− target: clickThroughCatcher
543− signalName: "clicked"
194+ function remove1stNotification() {
195+ if (mockModel.count > 1)
196+ mockModel.remove(1)
544197 }
545198
546− SignalSpy {
547− id: actionSpy
199+ Rectangle {
200+ id: notificationsRect
548201
549− target: mockModel
550− signalName: "actionInvoked"
551− }
202+ width: units.gu(40)
203+ height: units.gu(115)
552204
553− function cleanup() {
554− clickThroughSpy.clear()
555− actionSpy.clear()
556− }
205+ MouseArea{
206+ id: clickThroughCatcher
557207
558− function test_NotificationRenderer(data) {
559− // populate model with some mock notifications
560− mockModel.append(data)
561−
562− // make sure the view is properly updated before going on
563− notifications.forceLayout();
564− waitForRendering(notifications);
565−
566− var notification = findChild(notifications, "notification" + (mockModel.count - 1))
567− verify(notification !== undefined, "notification wasn't found");
568−
569− waitForRendering(notification);
570−
571− var icon = findChild(notification, "icon")
572− var centeredIcon = findChild(notification, "centeredIcon")
573− var interactiveArea = findChild(notification, "interactiveArea")
574− var secondaryIcon = findChild(notification, "secondaryIcon")
575− var summaryLabel = findChild(notification, "summaryLabel")
576− var bodyLabel = findChild(notification, "bodyLabel")
577− var buttonRow = findChild(notification, "buttonRow")
578− var valueIndicator = findChild(notification, "valueIndicator")
579− var valueLabel = findChild(notification, "valueLabel")
580− var innerBar = findChild(notification, "innerBar")
581−
582− compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
583− if (icon.visible) {
584− compare(icon.shaped, data.shaped, "shaped-status is incorrect")
208+ anchors.fill: parent
585209 }
586− compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
587− if (centeredIcon.visible) {
588− compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
210+
211+ Notifications {
212+ id: notifications
213+
214+ margin: units.gu(1)
215+
216+ anchors.fill: parent
217+ model: mockModel
218+ }
219+ }
220+
221+ Rectangle {
222+ id: interactiveControls
223+
224+ width: units.gu(30)
225+ height: units.gu(115)
226+ color: "grey"
227+
228+ Column {
229+ spacing: units.gu(1)
230+ anchors.fill: parent
231+ anchors.margins: units.gu(1)
232+
233+ Button {
234+ width: parent.width
235+ text: "add a snap-decision"
236+ onClicked: rootRow.addSnapDecisionNotification()
237+ }
238+
239+ Button {
240+ width: parent.width
241+ text: "add a 2over1 snap-decision"
242+ onClicked: rootRow.add2over1SnapDecisionNotification()
243+ }
244+
245+ Button {
246+ width: parent.width
247+ text: "add an ephemeral"
248+ onClicked: rootRow.addEphemeralNotification()
249+ }
250+
251+ Button {
252+ width: parent.width
253+ text: "add an non-shaped-icon-summary-body"
254+ onClicked: rootRow.addEphemeralNonShapedIconNotification()
255+ }
256+
257+ Button {
258+ width: parent.width
259+ text: "add an icon-summary"
260+ onClicked: rootRow.addEphemeralIconSummaryNotification()
261+ }
262+
263+ Button {
264+ width: parent.width
265+ text: "add an interactive"
266+ onClicked: rootRow.addInteractiveNotification()
267+ }
268+
269+ Button {
270+ width: parent.width
271+ text: "add a confirmation"
272+ onClicked: rootRow.addConfirmationNotification()
273+ }
274+
275+ Button {
276+ width: parent.width
277+ text: "add a 2nd confirmation"
278+ onClicked: rootRow.add2ndConfirmationNotification()
279+ }
280+
281+ Button {
282+ width: parent.width
283+ text: "remove 1st notification"
284+ onClicked: rootRow.remove1stNotification()
285+ }
286+
287+ Button {
288+ width: parent.width
289+ text: "clear model"
290+ onClicked: rootRow.clearNotifications()
291+ }
589292 }
590− compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
591− if (valueIndicator.visible) {
592− verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
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+ ]
593544 }
594− compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
595545
596− // test input does not fall through
597− mouseClick(notification, notification.width / 2, notification.height / 2)
598− if(data.type == Notification.Interactive) {
599− actionSpy.wait()
600− compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
546+ SignalSpy {
547+ id: clickThroughSpy
548+
549+ target: clickThroughCatcher
550+ signalName: "clicked"
601551 }
602− compare(clickThroughSpy.count, 0, "click on notification fell through")
603552
604− compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
605− compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
606− compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
607− compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
553+ SignalSpy {
554+ id: actionSpy
608555
609− var audioItem = findInvisibleChild(notification, "sound")
610− compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
556+ target: mockModel
557+ signalName: "actionInvoked"
558+ }
611559
612− if(data.buttonRowVisible) {
613− var buttonCancel = findChild(buttonRow, "notify_button1")
614− var buttonAccept = findChild(buttonRow, "notify_button0")
560+ function cleanup() {
561+ clickThroughSpy.clear()
562+ actionSpy.clear()
563+ }
615564
616− // only test the left/cancel-button if two actions have been passed in
617− if (data.actions.length == 2) {
618− tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
619− compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
620− actionSpy.clear()
565+ function test_NotificationRenderer(data) {
566+ // populate model with some mock notifications
567+ mockModel.append(data)
568+
569+ // make sure the view is properly updated before going on
570+ notifications.forceLayout();
571+ waitForRendering(notifications);
572+
573+ var notification = findChild(notifications, "notification" + (mockModel.count - 1))
574+ verify(notification !== undefined, "notification wasn't found");
575+
576+ waitForRendering(notification);
577+
578+ var icon = findChild(notification, "icon")
579+ var centeredIcon = findChild(notification, "centeredIcon")
580+ var interactiveArea = findChild(notification, "interactiveArea")
581+ var secondaryIcon = findChild(notification, "secondaryIcon")
582+ var summaryLabel = findChild(notification, "summaryLabel")
583+ var bodyLabel = findChild(notification, "bodyLabel")
584+ var buttonRow = findChild(notification, "buttonRow")
585+ var valueIndicator = findChild(notification, "valueIndicator")
586+ var valueLabel = findChild(notification, "valueLabel")
587+ var innerBar = findChild(notification, "innerBar")
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")
621596 }
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")
622602
623− // check the tinting of the positive/right button
624− verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
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")
625610
626− // click the positive/right button
627− tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
628− compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
629− actionSpy.clear()
630− waitForRendering(notification)
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")
631615
632− // check if there's a ComboButton created due to more actions being passed
633− if (data.actions.length > 2) {
634− var comboButton = findChild(notification, "notify_button2")
635− tryCompareFunction(function() { return comboButton.expanded == false; }, true);
616+ var audioItem = findInvisibleChild(notification, "sound")
617+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
636618
637− // click to expand
638− tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
619+ if(data.buttonRowVisible) {
620+ var buttonCancel = findChild(buttonRow, "notify_button1")
621+ var buttonAccept = findChild(buttonRow, "notify_button0")
639622
640− // try clicking on choices in expanded comboList
641− var choiceButton1 = findChild(notification, "notify_button3")
642− tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
643− compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
644− actionSpy.clear()
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+ }
645629
646− var choiceButton2 = findChild(notification, "notify_button4")
647− tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
648− compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
649− actionSpy.clear()
630+ // check the tinting of the positive/right button
631+ verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
650632
651− // click to collapse
652− //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
653− } else {
654− mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
655− compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
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+ }
656664 }
657665 }
658666 }
tests/qmltests/Notifications/tst_SwipeToAct.qmlnew file+276
@@ -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+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Notifications"
21+import Ubuntu.Components 0.1
22+import Unity.Test 0.1
23+import Unity.Notifications 1.0
24+
25+Item {
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+}