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
1=== modified file 'qml/Notifications/Notification.qml'
2--- qml/Notifications/Notification.qml 2014-10-30 21:42:32 +0000
3+++ qml/Notifications/Notification.qml 2014-10-31 10:46:25 +0000
4@@ -398,7 +398,7 @@
5
6 spacing: contentSpacing
7
8- visible: notification.type == Notification.SnapDecision && oneOverTwoRepeaterTop.count == 3
9+ visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
10
11 Repeater {
12 id: oneOverTwoRepeaterTop
13@@ -468,22 +468,41 @@
14 spacing: units.gu(2)
15 layoutDirection: Qt.RightToLeft
16
17+ Loader {
18+ id: notifySwipeButtonLoader
19+ active: notification.hints["x-canonical-snap-decisions-swipe"] === "true"
20+
21+ sourceComponent: SwipeToAct {
22+ objectName: "notify_swipe_button"
23+ width: buttonRow.width
24+ leftIconName: "call-end"
25+ rightIconName: "call-start"
26+ onLeftTriggered: {
27+ notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
28+ }
29+
30+ onRightTriggered: {
31+ notification.notification.invokeAction(notification.actions.data(1, ActionModel.RoleActionId))
32+ }
33+ }
34+ }
35+
36 Repeater {
37 id: actionRepeater
38-
39 model: notification.actions
40 delegate: Loader {
41 id: loader
42
43 property string actionId: id
44 property string actionLabel: label
45+ active: !notifySwipeButtonLoader.active
46
47 Component {
48 id: actionButton
49
50 Button {
51 objectName: "notify_button" + index
52- width: buttonRow.width / 2 - spacing*2
53+ width: buttonRow.width / 2 - spacing * 2
54 text: loader.actionLabel
55 color: {
56 var result = sdDarkGrey;
57
58=== added file 'qml/Notifications/SwipeToAct.qml'
59--- qml/Notifications/SwipeToAct.qml 1970-01-01 00:00:00 +0000
60+++ qml/Notifications/SwipeToAct.qml 2014-10-31 10:46:25 +0000
61@@ -0,0 +1,309 @@
62+/*
63+ * Copyright (C) 2014 Canonical, Ltd.
64+ *
65+ * This program is free software; you can redistribute it and/or modify
66+ * it under the terms of the GNU General Public License as published by
67+ * the Free Software Foundation; version 3.
68+ *
69+ * This program is distributed in the hope that it will be useful,
70+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
71+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
72+ * GNU General Public License for more details.
73+ *
74+ * You should have received a copy of the GNU General Public License
75+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
76+ */
77+
78+import QtQuick 2.3
79+import Ubuntu.Components 1.1
80+import QtGraphicalEffects 1.0
81+
82+Item {
83+ id: swipeToAct
84+
85+ width: parent.width
86+ height: childrenRect.height
87+
88+ signal leftTriggered()
89+ signal rightTriggered()
90+
91+ property string leftIconName
92+ property string rightIconName
93+ readonly property double sliderHeight: units.gu(6)
94+ readonly property double gap: units.gu(1)
95+ readonly property double halfWay: mouseArea.drag.maximumX / 2
96+
97+ Rectangle {
98+ id: gradient
99+ width: parent.width * 5
100+ height: sliderHeight
101+ visible: false
102+ LinearGradient {
103+ anchors.fill: parent
104+ start: Qt.point(parent.x, parent.y)
105+ end: Qt.point(parent.width, parent.y)
106+ gradient: Gradient {
107+ GradientStop { position: 0.0; color: UbuntuColors.red }
108+ GradientStop { position: 0.2; color: UbuntuColors.red }
109+ GradientStop { position: 0.4; color: "#dddddd" }
110+ GradientStop { position: 0.6; color: "#dddddd" }
111+ GradientStop { position: 0.8; color: UbuntuColors.green }
112+ GradientStop { position: 1.0; color: UbuntuColors.green }
113+ }
114+ }
115+ }
116+
117+ ShaderEffectSource {
118+ id: effectSourceGradient
119+ sourceItem: gradient
120+ width: gradient.width
121+ height: gradient.height
122+ sourceRect: Qt.rect(0.4 * gradient.width * (slider.x / halfWay), 0, mask.width, mask.height)
123+ visible: false
124+ hideSource: true
125+ }
126+
127+ UbuntuShape {
128+ id: mask
129+ color: "black"
130+ width: parent.width
131+ height: sliderHeight
132+ borderSource: "none"
133+ visible: false
134+ }
135+
136+ ShaderEffectSource {
137+ id: effectSourceMask
138+ sourceItem: mask
139+ width: mask.width
140+ height: mask.height
141+ visible: false
142+ hideSource: true
143+ }
144+
145+ ShaderEffect {
146+ width: parent.width
147+ height: sliderHeight
148+ property variant mask: effectSourceMask
149+ property variant gradient: effectSourceGradient
150+ vertexShader: "
151+ uniform highp mat4 qt_Matrix;
152+ attribute highp vec4 qt_Vertex;
153+ attribute highp vec2 qt_MultiTexCoord0;
154+ varying highp vec2 coord;
155+ void main() {
156+ coord = qt_MultiTexCoord0;
157+ gl_Position = qt_Matrix * qt_Vertex;
158+ }"
159+ fragmentShader: "
160+ varying highp vec2 coord;
161+ uniform sampler2D mask;
162+ uniform sampler2D gradient;
163+ void main() {
164+ lowp vec4 texMask = texture2D(mask, coord);
165+ lowp vec4 texGradient = texture2D(gradient, coord);
166+ gl_FragColor = texGradient.rgba * texMask.a ;
167+ }"
168+
169+ Row {
170+ id: row
171+ anchors.fill: parent
172+ spacing: gap
173+ anchors.margins: gap
174+
175+ UbuntuShape {
176+ id: leftShape
177+ states: [
178+ State {
179+ name: "normal"
180+ PropertyChanges {
181+ target: leftShape
182+ color: UbuntuColors.red
183+ }
184+ PropertyChanges {
185+ target: innerLeftShape
186+ color: UbuntuColors.red
187+ visible: false
188+ }
189+ },
190+ State {
191+ name: "selected"
192+ PropertyChanges {
193+ target: leftShape
194+ color: "white"
195+ }
196+ PropertyChanges {
197+ target: innerLeftShape
198+ color: UbuntuColors.red
199+ visible: true
200+ }
201+ }
202+ ]
203+ state: "normal"
204+ height: units.gu(4)
205+ width: units.gu(7)
206+ borderSource: "none"
207+ opacity: slider.x <= halfWay ? 1.0 : 1.0 - ((slider.x - halfWay) / halfWay)
208+ UbuntuShape {
209+ id: innerLeftShape
210+ anchors.centerIn: parent
211+ borderSource: "none"
212+ width: parent.width - units.gu(.5)
213+ height: parent.height - units.gu(.5)
214+ }
215+ Icon {
216+ anchors.centerIn: parent
217+ width: units.gu(2)
218+ height: units.gu(2)
219+ name: leftIconName
220+ color: "white"
221+ }
222+ }
223+
224+ Rectangle {
225+ id: leftSpacer
226+ width: (row.width - (leftShape.width + slider.width + rightShape.width + 4 * row.spacing)) / 2
227+ height: units.gu(4)
228+ opacity: 0
229+ }
230+
231+ UbuntuShape {
232+ id: slider
233+ objectName: "slider"
234+
235+ Behavior on x {
236+ UbuntuNumberAnimation {
237+ duration: UbuntuAnimation.FastDuration
238+ easing.type: Easing.OutBounce
239+ }
240+ }
241+
242+ Behavior on opacity {
243+ UbuntuNumberAnimation {
244+ duration: UbuntuAnimation.FastDuration
245+ }
246+ }
247+
248+ onOpacityChanged: {
249+ if (opacity === 0) {
250+ if (rightShape.state === "selected") {
251+ rightTriggered()
252+ }
253+ if (leftShape.state === "selected") {
254+ leftTriggered()
255+ }
256+ }
257+ }
258+
259+ z: 1
260+ color: "white"
261+ height: units.gu(4)
262+ width: units.gu(7)
263+ borderSource: "none"
264+ Row {
265+ anchors.fill: parent
266+ spacing: 2 * gap
267+ anchors.leftMargin: units.gu(.5)
268+ anchors.rightMargin: units.gu(.5)
269+ Icon {
270+ anchors.verticalCenter: parent.verticalCenter
271+ name: "back"
272+ width: units.gu(2)
273+ height: units.gu(2)
274+ }
275+ Icon {
276+ anchors.verticalCenter: parent.verticalCenter
277+ name: "next"
278+ width: units.gu(2)
279+ height: units.gu(2)
280+ }
281+ }
282+ }
283+
284+ Rectangle {
285+ id: rightSpacer
286+ width: leftSpacer.width
287+ height: units.gu(4)
288+ opacity: 0
289+ }
290+
291+ UbuntuShape {
292+ id: rightShape
293+ states: [
294+ State {
295+ name: "normal"
296+ PropertyChanges {
297+ target: rightShape
298+ color: UbuntuColors.green
299+ }
300+ PropertyChanges {
301+ target: innerRightShape
302+ color: UbuntuColors.green
303+ visible: false
304+ }
305+ },
306+ State {
307+ name: "selected"
308+ PropertyChanges {
309+ target: rightShape
310+ color: "white"
311+ }
312+ PropertyChanges {
313+ target: innerRightShape
314+ color: UbuntuColors.green
315+ visible: true
316+ }
317+ }
318+ ]
319+ state: "normal"
320+ height: units.gu(4)
321+ width: units.gu(7)
322+ borderSource: "none"
323+ opacity: slider.x >= halfWay ? 1.0 : slider.x / halfWay
324+ UbuntuShape {
325+ id: innerRightShape
326+ anchors.centerIn: parent
327+ borderSource: "none"
328+ width: parent.width - units.gu(.5)
329+ height: parent.height - units.gu(.5)
330+ }
331+ Icon {
332+ anchors.centerIn: parent
333+ width: units.gu(2)
334+ height: units.gu(2)
335+ name: rightIconName
336+ color: "white"
337+ }
338+ }
339+ }
340+
341+ MouseArea {
342+ id: mouseArea
343+ objectName: "swipeMouseArea"
344+
345+ anchors.fill: row
346+ drag.target: slider
347+ drag.axis: Drag.XAxis
348+ drag.minimumX: 0
349+ drag.maximumX: row.width - slider.width
350+
351+ onReleased: {
352+ if (slider.x !== drag.minimumX || slider.x !== drag.maximumX) {
353+ slider.x = halfWay
354+ }
355+ if (slider.x === drag.minimumX) {
356+ slider.x = drag.minimumX
357+ slider.opacity = 0
358+ enabled = false
359+ leftShape.state = "selected"
360+ }
361+ if (slider.x === drag.maximumX) {
362+ slider.x = drag.maximumX
363+ slider.opacity = 0
364+ enabled = false
365+ rightShape.state = "selected"
366+ }
367+ }
368+ }
369+ }
370+}
371
372=== modified file 'tests/autopilot/unity8/shell/tests/test_notifications.py'
373--- tests/autopilot/unity8/shell/tests/test_notifications.py 2014-10-30 21:43:06 +0000
374+++ tests/autopilot/unity8/shell/tests/test_notifications.py 2014-10-31 10:46:25 +0000
375@@ -157,57 +157,6 @@
376
377 self.assert_notification_action_id_was_called('action_id')
378
379- def test_sd_incoming_call(self):
380- """Rejecting a call should make notification expand and
381- offer more options."""
382- unity_proxy = self.launch_unity()
383- unlock_unity(unity_proxy)
384-
385- summary = "Incoming call"
386- body = "Frank Zappa\n+44 (0)7736 027340"
387- icon_path = self._get_icon_path('avatars/anna_olsson.png')
388- hints = [
389- ("x-canonical-secondary-icon", "incoming-call"),
390- ("x-canonical-snap-decisions", "true"),
391- ("x-canonical-private-affirmative-tint", "true"),
392- ("x-canonical-private-rejection-tint", "true"),
393- ]
394-
395- actions = [
396- ('action_accept', 'Hold + Answer'),
397- ('action_decline_1', 'End + Answer'),
398- ('action_decline_2', 'Decline'),
399- ('action_decline_3', 'message:I missed your call - can you call me now?'),
400- ('action_decline_4', 'message:I\'m running late. I\'m on my way.'),
401- ('action_decline_5', 'message:I\'m busy at the moment. I\'ll call later.'),
402- ('action_decline_6', 'edit:Custom'),
403- ]
404-
405- self._create_interactive_notification(
406- summary,
407- body,
408- icon_path,
409- "NORMAL",
410- actions,
411- hints
412- )
413-
414- notify_list = self._get_notifications_list()
415- get_notification = lambda: notify_list.wait_select_single(
416- 'Notification', objectName='notification1')
417- notification = get_notification()
418- self._assert_notification(notification, summary, body, True, True, 1.0)
419- notification.pointing_device.click_object(
420- notification.select_single(objectName="combobutton_dropdown"))
421- self.assertThat(
422- notification.select_single(objectName="notify_button2").expanded,
423- Eventually(Equals(True)))
424- time.sleep(2)
425- notification.pointing_device.click_object(
426- notification.select_single(objectName="notify_button4"))
427- self.assert_notification_action_id_was_called("action_decline_4")
428-
429-
430 def test_sd_one_over_two_layout(self):
431 """Snap-decision with three actions should use one-over two button layout."""
432 unity_proxy = self.launch_unity()
433
434=== modified file 'tests/mocks/Unity/Notifications/CMakeLists.txt'
435--- tests/mocks/Unity/Notifications/CMakeLists.txt 2014-05-02 23:27:02 +0000
436+++ tests/mocks/Unity/Notifications/CMakeLists.txt 2014-10-31 10:46:25 +0000
437@@ -1,1 +1,15 @@
438-add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications)
439+include_directories(
440+ ${CMAKE_CURRENT_SOURCE_DIR}
441+)
442+
443+set(MockNotificationsPlugin_SOURCES
444+ plugin.cpp
445+ MockNotificationTypes.cpp
446+ MockActionModel.cpp
447+)
448+
449+add_library(MockNotificationsPlugin MODULE ${MockNotificationsPlugin_SOURCES})
450+
451+qt5_use_modules(MockNotificationsPlugin Core Quick)
452+
453+add_unity8_mock(Unity.Notifications 1.0 Unity/Notifications TARGETS MockNotificationsPlugin)
454
455=== added file 'tests/mocks/Unity/Notifications/MockActionModel.cpp'
456--- tests/mocks/Unity/Notifications/MockActionModel.cpp 1970-01-01 00:00:00 +0000
457+++ tests/mocks/Unity/Notifications/MockActionModel.cpp 2014-10-31 10:46:25 +0000
458@@ -0,0 +1,72 @@
459+/*
460+ * Copyright 2014 Canonical Ltd.
461+ *
462+ * This program is free software; you can redistribute it and/or modify
463+ * it under the terms of the GNU Lesser General Public License as published by
464+ * the Free Software Foundation; version 3.
465+ *
466+ * This program is distributed in the hope that it will be useful,
467+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
468+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
469+ * GNU Lesser General Public License for more details.
470+ *
471+ * You should have received a copy of the GNU Lesser General Public License
472+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
473+ *
474+ * Authors:
475+ * Mirco Mueller <mirco.mueller@canonical.com>
476+ */
477+
478+#include "MockActionModel.h"
479+
480+struct ActionModelPrivate {
481+ QList<QString> labels;
482+ QList<QString> ids;
483+};
484+
485+ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
486+ insertAction("ok_id", "Ok");
487+ insertAction("cancel_id", "Cancel");
488+}
489+
490+ActionModel::~ActionModel() {
491+}
492+
493+int ActionModel::rowCount(const QModelIndex &index) const {
494+ return p->labels.size();
495+}
496+
497+QVariant ActionModel::data(const QModelIndex &index, int role) const {
498+ if (!index.isValid())
499+ return QVariant();
500+
501+ switch(role) {
502+ case RoleActionLabel:
503+ return QVariant(p->labels[index.row()]);
504+
505+ case RoleActionId:
506+ return QVariant(p->ids[index.row()]);
507+
508+ default:
509+ return QVariant();
510+ }
511+}
512+
513+QHash<int, QByteArray> ActionModel::roleNames() const {
514+ QHash<int, QByteArray> roles;
515+
516+ roles.insert(RoleActionLabel, "label");
517+ roles.insert(RoleActionId, "id");
518+
519+ return roles;
520+}
521+
522+Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
523+{
524+ return data(index(row, 0), role);
525+}
526+
527+void ActionModel::insertAction(const QString &id, const QString &label) {
528+ p->ids.push_back(id);
529+ p->labels.push_back(label);
530+}
531
532=== added file 'tests/mocks/Unity/Notifications/MockActionModel.h'
533--- tests/mocks/Unity/Notifications/MockActionModel.h 1970-01-01 00:00:00 +0000
534+++ tests/mocks/Unity/Notifications/MockActionModel.h 2014-10-31 10:46:25 +0000
535@@ -0,0 +1,51 @@
536+/*
537+ * Copyright 2014 Canonical Ltd.
538+ *
539+ * This program is free software; you can redistribute it and/or modify
540+ * it under the terms of the GNU Lesser General Public License as published by
541+ * the Free Software Foundation; version 3.
542+ *
543+ * This program is distributed in the hope that it will be useful,
544+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
545+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
546+ * GNU Lesser General Public License for more details.
547+ *
548+ * You should have received a copy of the GNU Lesser General Public License
549+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
550+ *
551+ * Authors:
552+ * Mirco Mueller <mirco.mueller@canonical.com>
553+ */
554+
555+#ifndef MOCK_ACTION_MODEL_H
556+#define MOCK_ACTION_MODEL_H
557+
558+#include <QStringListModel>
559+
560+struct ActionModelPrivate;
561+
562+class ActionModel : public QStringListModel {
563+ Q_OBJECT
564+
565+public:
566+ ActionModel(QObject *parent=nullptr);
567+ virtual ~ActionModel();
568+
569+ virtual int rowCount(const QModelIndex &index) const;
570+ virtual QVariant data(const QModelIndex &index, int role) const;
571+ virtual QHash<int, QByteArray> roleNames() const;
572+
573+ Q_ENUMS(ActionsRoles)
574+ enum ActionsRoles {
575+ RoleActionLabel = Qt::UserRole + 1,
576+ RoleActionId = Qt::UserRole + 2
577+ };
578+ Q_INVOKABLE QVariant data(int row, int role) const;
579+
580+ void insertAction(const QString &id, const QString &label);
581+
582+private:
583+ QScopedPointer<ActionModelPrivate> p;
584+};
585+
586+#endif // MOCK_ACTION_MODEL_H
587
588=== added file 'tests/mocks/Unity/Notifications/MockNotificationTypes.cpp'
589--- tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 1970-01-01 00:00:00 +0000
590+++ tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2014-10-31 10:46:25 +0000
591@@ -0,0 +1,26 @@
592+/*
593+ * Copyright 2014 Canonical Ltd.
594+ *
595+ * This program is free software; you can redistribute it and/or modify
596+ * it under the terms of the GNU Lesser General Public License as published by
597+ * the Free Software Foundation; version 3.
598+ *
599+ * This program is distributed in the hope that it will be useful,
600+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
601+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
602+ * GNU Lesser General Public License for more details.
603+ *
604+ * You should have received a copy of the GNU Lesser General Public License
605+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
606+ *
607+ * Authors:
608+ * Mirco Mueller <mirco.mueller@canonical.com>
609+ */
610+
611+#include "MockNotificationTypes.h"
612+
613+MockNotification::MockNotification(QObject *parent) : QObject(parent) {
614+}
615+
616+MockNotification::~MockNotification() {
617+}
618
619=== added file 'tests/mocks/Unity/Notifications/MockNotificationTypes.h'
620--- tests/mocks/Unity/Notifications/MockNotificationTypes.h 1970-01-01 00:00:00 +0000
621+++ tests/mocks/Unity/Notifications/MockNotificationTypes.h 2014-10-31 10:46:25 +0000
622@@ -0,0 +1,36 @@
623+/*
624+ * Copyright 2014 Canonical Ltd.
625+ *
626+ * This program is free software; you can redistribute it and/or modify
627+ * it under the terms of the GNU Lesser General Public License as published by
628+ * the Free Software Foundation; version 3.
629+ *
630+ * This program is distributed in the hope that it will be useful,
631+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
632+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
633+ * GNU Lesser General Public License for more details.
634+ *
635+ * You should have received a copy of the GNU Lesser General Public License
636+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
637+ *
638+ * Authors:
639+ * Mirco Mueller <mirco.mueller@canonical.com>
640+ */
641+
642+#ifndef MOCK_NOTIFICATION_TYPES_H
643+#define MOCK_NOTIFICATION_TYPES_H
644+
645+#include <QObject>
646+
647+class MockNotification : public QObject {
648+ Q_OBJECT
649+ Q_ENUMS(Type)
650+
651+public:
652+ MockNotification(QObject *parent=nullptr);
653+ virtual ~MockNotification();
654+
655+ enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
656+};
657+
658+#endif // MOCK_NOTIFICATION_TYPES_H
659
660=== removed file 'tests/mocks/Unity/Notifications/notification.js'
661--- tests/mocks/Unity/Notifications/notification.js 2013-06-19 10:24:31 +0000
662+++ tests/mocks/Unity/Notifications/notification.js 1970-01-01 00:00:00 +0000
663@@ -1,23 +0,0 @@
664-/*
665- * Copyright (C) 2013 Canonical, Ltd.
666- *
667- * This program is free software; you can redistribute it and/or modify
668- * it under the terms of the GNU General Public License as published by
669- * the Free Software Foundation; version 3.
670- *
671- * This program is distributed in the hope that it will be useful,
672- * but WITHOUT ANY WARRANTY; without even the implied warranty of
673- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
674- * GNU General Public License for more details.
675- *
676- * You should have received a copy of the GNU General Public License
677- * along with this program. If not, see <http://www.gnu.org/licenses/>.
678- */
679-
680-.pragma library
681-
682-var Confirmation = 0;
683-var Ephemeral = 1;
684-var Interactive = 2;
685-var SnapDecision = 3;
686-var PlaceHolder = 4;
687
688=== added file 'tests/mocks/Unity/Notifications/plugin.cpp'
689--- tests/mocks/Unity/Notifications/plugin.cpp 1970-01-01 00:00:00 +0000
690+++ tests/mocks/Unity/Notifications/plugin.cpp 2014-10-31 10:46:25 +0000
691@@ -0,0 +1,31 @@
692+/*
693+ * Copyright 2014 Canonical Ltd.
694+ *
695+ * This program is free software; you can redistribute it and/or modify
696+ * it under the terms of the GNU Lesser General Public License as published by
697+ * the Free Software Foundation; version 3.
698+ *
699+ * This program is distributed in the hope that it will be useful,
700+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
701+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
702+ * GNU Lesser General Public License for more details.
703+ *
704+ * You should have received a copy of the GNU Lesser General Public License
705+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
706+ *
707+ * Authors:
708+ * Mirco Mueller <mirco.mueller@canonical.com>
709+ */
710+
711+#include "plugin.h"
712+#include "MockActionModel.h"
713+#include "MockNotificationTypes.h"
714+
715+#include <QtQml/qqml.h>
716+
717+void TestNotificationPlugin::registerTypes(const char* uri)
718+{
719+ // @uri Unity.Notifications
720+ qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");
721+ qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
722+}
723
724=== added file 'tests/mocks/Unity/Notifications/plugin.h'
725--- tests/mocks/Unity/Notifications/plugin.h 1970-01-01 00:00:00 +0000
726+++ tests/mocks/Unity/Notifications/plugin.h 2014-10-31 10:46:25 +0000
727@@ -0,0 +1,35 @@
728+/*
729+ * Copyright 2014 Canonical Ltd.
730+ *
731+ * This program is free software; you can redistribute it and/or modify
732+ * it under the terms of the GNU Lesser General Public License as published by
733+ * the Free Software Foundation; version 3.
734+ *
735+ * This program is distributed in the hope that it will be useful,
736+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
737+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
738+ * GNU Lesser General Public License for more details.
739+ *
740+ * You should have received a copy of the GNU Lesser General Public License
741+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
742+ *
743+ * Authors:
744+ * Mirco Mueller <mirco.mueller@canonical.com>
745+ */
746+
747+
748+#ifndef TESTNOTIFICATION_PLUGIN_H
749+#define TESTNOTIFICATION_PLUGIN_H
750+
751+#include <QtQml/QQmlExtensionPlugin>
752+
753+class TestNotificationPlugin : public QQmlExtensionPlugin
754+{
755+ Q_OBJECT
756+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
757+
758+public:
759+ void registerTypes(const char* uri);
760+};
761+
762+#endif // TESTNOTIFICATION_PLUGIN_H
763
764=== modified file 'tests/mocks/Unity/Notifications/qmldir'
765--- tests/mocks/Unity/Notifications/qmldir 2014-05-02 22:57:21 +0000
766+++ tests/mocks/Unity/Notifications/qmldir 2014-10-31 10:46:25 +0000
767@@ -1,3 +1,4 @@
768 module Unity.Notifications
769-Notification 1.0 notification.js
770+plugin MockNotificationsPlugin
771 typeinfo Notifications.qmltypes
772+
773
774=== modified file 'tests/qmltests/CMakeLists.txt'
775--- tests/qmltests/CMakeLists.txt 2014-10-13 09:23:34 +0000
776+++ tests/qmltests/CMakeLists.txt 2014-10-31 10:46:25 +0000
777@@ -70,6 +70,7 @@
778 add_qml_test(Launcher Launcher)
779 add_qml_test(Notifications Notifications)
780 add_qml_test(Notifications VisualSnapDecisionsQueue)
781+add_qml_test(Notifications SwipeToAct)
782 add_qml_test(Panel ActiveCallHint)
783 add_qml_test(Panel IndicatorItem)
784 add_qml_test(Panel IndicatorItemRow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
785
786=== modified file 'tests/qmltests/Notifications/tst_Notifications.qml'
787--- tests/qmltests/Notifications/tst_Notifications.qml 2014-10-14 10:07:44 +0000
788+++ tests/qmltests/Notifications/tst_Notifications.qml 2014-10-31 10:46:25 +0000
789@@ -23,280 +23,51 @@
790 import Unity.Notifications 1.0
791 import QtMultimedia 5.0
792
793-Row {
794- id: rootRow
795-
796- Component {
797- id: mockNotification
798-
799- QtObject {
800- function invokeAction(actionId) {
801- mockModel.actionInvoked(actionId)
802- }
803- }
804- }
805-
806- ListModel {
807- id: mockModel
808-
809- signal actionInvoked(string actionId)
810-
811- function getRaw(id) {
812- return mockNotification.createObject(mockModel)
813- }
814-
815- // add the default/PlaceHolder notification to the model
816- Component.onCompleted: {
817+Item {
818+ width: notificationsRect.width + interactiveControls.width
819+ height: notificationsRect.height
820+
821+ Row {
822+ id: rootRow
823+
824+ Component {
825+ id: mockNotification
826+
827+ QtObject {
828+ function invokeAction(actionId) {
829+ mockModel.actionInvoked(actionId)
830+ }
831+ }
832+ }
833+
834+ ListModel {
835+ id: mockModel
836+ dynamicRoles: true
837+
838+ signal actionInvoked(string actionId)
839+
840+ function getRaw(id) {
841+ return mockNotification.createObject(mockModel)
842+ }
843+
844+ // add the default/PlaceHolder notification to the model
845+ Component.onCompleted: {
846+ var n = {
847+ type: Notification.PlaceHolder,
848+ hints: {},
849+ summary: "",
850+ body: "",
851+ icon: "",
852+ secondaryIcon: "",
853+ actions: []
854+ }
855+
856+ append(n)
857+ }
858+ }
859+
860+ function addSnapDecisionNotification() {
861 var n = {
862- type: Notification.PlaceHolder,
863- hints: {},
864- summary: "",
865- body: "",
866- icon: "",
867- value: 0,
868- secondaryIcon: "",
869- actions: []
870- }
871-
872- append(n)
873- }
874- }
875-
876- function addSnapDecisionNotification() {
877- var n = {
878- type: Notification.SnapDecision,
879- hints: {"x-canonical-private-affirmative-tint": "true"},
880- summary: "Tom Ato",
881- 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.",
882- icon: "../graphics/avatars/funky.png",
883- secondaryIcon: "../graphics/applicationIcons/facebook.png",
884- actions: [{ id: "ok_id", label: "Ok"},
885- { id: "cancel_id", label: "Cancel"},
886- { id: "notreally_id", label: "Not really"},
887- { id: "noway_id", label: "messages:No way"},
888- { id: "nada_id", label: "messages:Nada"}]
889- }
890-
891- mockModel.append(n)
892- }
893-
894- function add2over1SnapDecisionNotification() {
895- var n = {
896- type: Notification.SnapDecision,
897- hints: {"x-canonical-private-affirmative-tint": "true",},
898- summary: "Theatre at Ferria Stadium",
899- body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
900- icon: "",
901- secondaryIcon: "",
902- actions: [{ id: "ok_id", label: "Ok"},
903- { id: "snooze_id", label: "Snooze"},
904- { id: "view_id", label: "View"}]
905- }
906-
907- mockModel.append(n)
908- }
909-
910- function addEphemeralNotification() {
911- var n = {
912- type: Notification.Ephemeral,
913- summary: "Cole Raby",
914- body: "I did not expect it to be that late.",
915- icon: "../graphics/avatars/amanda.png",
916- secondaryIcon: "../graphics/applicationIcons/facebook.png",
917- actions: []
918- }
919-
920- mockModel.append(n)
921- }
922-
923- function addEphemeralNonShapedIconNotification() {
924- var n = {
925- type: Notification.Ephemeral,
926- hints: {"x-canonical-non-shaped-icon": "true"},
927- summary: "Contacts",
928- body: "Synchronised contacts-database with cloud-storage.",
929- icon: "../graphics/applicationIcons/contacts-app.png",
930- secondaryIcon: "",
931- actions: []
932- }
933-
934- mockModel.append(n)
935- }
936-
937- function addEphemeralIconSummaryNotification() {
938- var n = {
939- type: Notification.Ephemeral,
940- hints: {"x-canonical-non-shaped-icon": "false"},
941- summary: "Photo upload completed",
942- body: "",
943- icon: "../graphics/applicationIcons/facebook.png",
944- secondaryIcon: "",
945- actions: []
946- }
947-
948- mockModel.append(n)
949- }
950-
951- function addInteractiveNotification() {
952- var n = {
953- type: Notification.Interactive,
954- summary: "Interactive notification",
955- body: "This is a notification that can be clicked",
956- icon: "../graphics/avatars/anna_olsson.png",
957- secondaryIcon: "",
958- actions: [{ id: "reply_id", label: "Dummy"}],
959- }
960-
961- mockModel.append(n)
962- }
963-
964- function addConfirmationNotification() {
965- var n = {
966- type: Notification.Confirmation,
967- hints: {"x-canonical-non-shaped-icon": "true"},
968- summary: "Confirmation notification",
969- body: "",
970- icon: "image://theme/audio-volume-medium",
971- secondaryIcon: "",
972- value: 50,
973- actions: [],
974- }
975-
976- mockModel.append(n)
977- }
978-
979- function add2ndConfirmationNotification() {
980- var n = {
981- type: Notification.Confirmation,
982- hints: {"x-canonical-non-shaped-icon": "true",
983- "x-canonical-value-bar-tint": "true"},
984- summary: "Confirmation notification",
985- body: "High Volume",
986- icon: "image://theme/audio-volume-high",
987- secondaryIcon: "",
988- value: 85,
989- actions: [],
990- }
991-
992- mockModel.append(n)
993- }
994-
995- function clearNotifications() {
996- while(mockModel.count > 1) {
997- remove1stNotification()
998- }
999- }
1000-
1001- function remove1stNotification() {
1002- if (mockModel.count > 1)
1003- mockModel.remove(1)
1004- }
1005-
1006- Rectangle {
1007- id: notificationsRect
1008-
1009- width: units.gu(40)
1010- height: units.gu(115)
1011-
1012- MouseArea{
1013- id: clickThroughCatcher
1014-
1015- anchors.fill: parent
1016- }
1017-
1018- Notifications {
1019- id: notifications
1020-
1021- margin: units.gu(1)
1022-
1023- anchors.fill: parent
1024- model: mockModel
1025- }
1026- }
1027-
1028- Rectangle {
1029- id: interactiveControls
1030-
1031- width: units.gu(30)
1032- height: units.gu(115)
1033- color: "grey"
1034-
1035- Column {
1036- spacing: units.gu(1)
1037- anchors.fill: parent
1038- anchors.margins: units.gu(1)
1039-
1040- Button {
1041- width: parent.width
1042- text: "add a snap-decision"
1043- onClicked: addSnapDecisionNotification()
1044- }
1045-
1046- Button {
1047- width: parent.width
1048- text: "add a 2over1 snap-decision"
1049- onClicked: add2over1SnapDecisionNotification()
1050- }
1051-
1052- Button {
1053- width: parent.width
1054- text: "add an ephemeral"
1055- onClicked: addEphemeralNotification()
1056- }
1057-
1058- Button {
1059- width: parent.width
1060- text: "add an non-shaped-icon-summary-body"
1061- onClicked: addEphemeralNonShapedIconNotification()
1062- }
1063-
1064- Button {
1065- width: parent.width
1066- text: "add an icon-summary"
1067- onClicked: addEphemeralIconSummaryNotification()
1068- }
1069-
1070- Button {
1071- width: parent.width
1072- text: "add an interactive"
1073- onClicked: addInteractiveNotification()
1074- }
1075-
1076- Button {
1077- width: parent.width
1078- text: "add a confirmation"
1079- onClicked: addConfirmationNotification()
1080- }
1081-
1082- Button {
1083- width: parent.width
1084- text: "add a 2nd confirmation"
1085- onClicked: add2ndConfirmationNotification()
1086- }
1087-
1088- Button {
1089- width: parent.width
1090- text: "remove 1st notification"
1091- onClicked: remove1stNotification()
1092- }
1093-
1094- Button {
1095- width: parent.width
1096- text: "clear model"
1097- onClicked: clearNotifications()
1098- }
1099- }
1100- }
1101-
1102- UnityTestCase {
1103- id: root
1104- name: "NotificationRendererTest"
1105- when: windowShown
1106-
1107- function test_NotificationRenderer_data() {
1108- return [
1109- {
1110- tag: "Snap Decision with secondary icon and button-tint",
1111 type: Notification.SnapDecision,
1112 hints: {"x-canonical-private-affirmative-tint": "true"},
1113 summary: "Tom Ato",
1114@@ -307,23 +78,14 @@
1115 { id: "cancel_id", label: "Cancel"},
1116 { id: "notreally_id", label: "Not really"},
1117 { id: "noway_id", label: "messages:No way"},
1118- { id: "nada_id", label: "messages:Nada"}],
1119- summaryVisible: true,
1120- bodyVisible: true,
1121- iconVisible: true,
1122- centeredIconVisible: false,
1123- shaped: true,
1124- nonShaped: false,
1125- secondaryIconVisible: true,
1126- buttonRowVisible: true,
1127- buttonTinted: true,
1128- hasSound: false,
1129- valueVisible: false,
1130- valueLabelVisible: false,
1131- valueTinted: false
1132- },
1133- {
1134- tag: "2-over-1 Snap Decision with button-tint",
1135+ { id: "nada_id", label: "messages:Nada"}]
1136+ }
1137+
1138+ mockModel.append(n)
1139+ }
1140+
1141+ function add2over1SnapDecisionNotification() {
1142+ var n = {
1143 type: Notification.SnapDecision,
1144 hints: {"x-canonical-private-affirmative-tint": "true"},
1145 summary: "Theatre at Ferria Stadium",
1146@@ -332,327 +94,573 @@
1147 secondaryIcon: "",
1148 actions: [{ id: "ok_id", label: "Ok"},
1149 { id: "snooze_id", label: "Snooze"},
1150- { id: "view_id", label: "View"}],
1151- summaryVisible: true,
1152- bodyVisible: true,
1153- iconVisible: false,
1154- centeredIconVisible: false,
1155- shaped: false,
1156- secondaryIconVisible: false,
1157- buttonRowVisible: false,
1158- buttonTinted: true,
1159- hasSound: false,
1160- valueVisible: false,
1161- valueLabelVisible: false,
1162- valueTinted: false
1163- },
1164- {
1165- tag: "Ephemeral notification - icon-summary layout",
1166- type: Notification.Ephemeral,
1167- hints: {},
1168+ { id: "view_id", label: "View"}]
1169+ }
1170+
1171+ mockModel.append(n)
1172+ }
1173+
1174+ function addEphemeralNotification() {
1175+ var n = {
1176+ type: Notification.Ephemeral,
1177+ summary: "Cole Raby",
1178+ body: "I did not expect it to be that late.",
1179+ icon: "../graphics/avatars/amanda.png",
1180+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
1181+ actions: []
1182+ }
1183+
1184+ mockModel.append(n)
1185+ }
1186+
1187+ function addEphemeralNonShapedIconNotification() {
1188+ var n = {
1189+ type: Notification.Ephemeral,
1190+ hints: {"x-canonical-non-shaped-icon": "true"},
1191+ summary: "Contacts",
1192+ body: "Synchronised contacts-database with cloud-storage.",
1193+ icon: "../graphics/applicationIcons/contacts-app.png",
1194+ secondaryIcon: "",
1195+ actions: []
1196+ }
1197+
1198+ mockModel.append(n)
1199+ }
1200+
1201+ function addEphemeralIconSummaryNotification() {
1202+ var n = {
1203+ type: Notification.Ephemeral,
1204+ hints: {"x-canonical-non-shaped-icon": "false"},
1205 summary: "Photo upload completed",
1206 body: "",
1207 icon: "../graphics/applicationIcons/facebook.png",
1208 secondaryIcon: "",
1209- actions: [],
1210- summaryVisible: true,
1211- bodyVisible: false,
1212- iconVisible: true,
1213- centeredIconVisible: false,
1214- shaped: true,
1215- secondaryIconVisible: false,
1216- buttonRowVisible: false,
1217- buttonTinted: false,
1218- hasSound: false,
1219- valueVisible: false,
1220- valueLabelVisible: false,
1221- valueTinted: false
1222- },
1223- {
1224- tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
1225- type: Notification.Ephemeral,
1226- hints: {"x-canonical-private-affirmative-tint": "false",
1227- "sound-file": "dummy.ogg",
1228- "suppress-sound": "true"},
1229- summary: "New comment successfully published",
1230- body: "",
1231- icon: "",
1232- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1233- actions: [],
1234- summaryVisible: true,
1235- bodyVisible: false,
1236- interactiveAreaEnabled: false,
1237- iconVisible: false,
1238- centeredIconVisible: false,
1239- shaped: false,
1240- secondaryIconVisible: true,
1241- buttonRowVisible: false,
1242- buttonTinted: false,
1243- hasSound: false,
1244- valueVisible: false,
1245- valueLabelVisible: false,
1246- valueTinted: false
1247- },
1248- {
1249- tag: "Interactive notification",
1250+ actions: []
1251+ }
1252+
1253+ mockModel.append(n)
1254+ }
1255+
1256+ function addInteractiveNotification() {
1257+ var n = {
1258 type: Notification.Interactive,
1259- hints: {"x-canonical-private-affirmative-tint": "false",
1260- "sound-file": "dummy.ogg"},
1261 summary: "Interactive notification",
1262 body: "This is a notification that can be clicked",
1263- icon: "../graphics/avatars/amanda.png",
1264+ icon: "../graphics/avatars/anna_olsson.png",
1265 secondaryIcon: "",
1266 actions: [{ id: "reply_id", label: "Dummy"}],
1267- summaryVisible: true,
1268- bodyVisible: true,
1269- iconVisible: true,
1270- centeredIconVisible: false,
1271- shaped: true,
1272- secondaryIconVisible: false,
1273- buttonRowVisible: false,
1274- buttonTinted: false,
1275- hasSound: true,
1276- valueVisible: false,
1277- valueLabelVisible: false,
1278- valueTinted: false
1279- },
1280- {
1281- tag: "Snap Decision without secondary icon and no button-tint",
1282- type: Notification.SnapDecision,
1283- hints: {"x-canonical-private-affirmative-tint": "false",
1284- "sound-file": "dummy.ogg"},
1285- summary: "Bro Coly",
1286- 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.",
1287- icon: "../graphics/avatars/anna_olsson.png",
1288- secondaryIcon: "",
1289- actions: [{ id: "accept_id", label: "Accept"},
1290- { id: "reject_id", label: "Reject"}],
1291- summaryVisible: true,
1292- bodyVisible: true,
1293- iconVisible: true,
1294- centeredIconVisible: false,
1295- shaped: true,
1296- secondaryIconVisible: false,
1297- buttonRowVisible: true,
1298- buttonTinted: false,
1299- hasSound: true,
1300- valueVisible: false,
1301- valueLabelVisible: false,
1302- valueTinted: false
1303- },
1304- {
1305- tag: "Ephemeral notification",
1306- type: Notification.Ephemeral,
1307- hints: {"x-canonical-private-affirmative-tint": "false",
1308- "sound-file": "dummy.ogg"},
1309- summary: "Cole Raby",
1310- body: "I did not expect it to be that late.",
1311- icon: "../graphics/avatars/funky.png",
1312- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1313- actions: [],
1314- summaryVisible: true,
1315- bodyVisible: true,
1316- iconVisible: true,
1317- centeredIconVisible: false,
1318- shaped: true,
1319- secondaryIconVisible: true,
1320- buttonRowVisible: false,
1321- buttonTinted: false,
1322- hasSound: true,
1323- valueVisible: false,
1324- valueLabelVisible: false,
1325- valueTinted: false
1326- },
1327- {
1328- tag: "Ephemeral notification with non-shaped icon",
1329- type: Notification.Ephemeral,
1330- hints: {"x-canonical-private-affirmative-tint": "false",
1331- "x-canonical-non-shaped-icon": "true"},
1332- summary: "Contacts",
1333- body: "Synchronised contacts-database with cloud-storage.",
1334- icon: "image://theme/contacts-app",
1335- secondaryIcon: "",
1336- actions: [],
1337- summaryVisible: true,
1338- bodyVisible: true,
1339- iconVisible: true,
1340- centeredIconVisible: false,
1341- shaped: false,
1342- secondaryIconVisible: false,
1343- buttonRowVisible: false,
1344- buttonTinted: false,
1345- hasSound: false,
1346- valueVisible: false,
1347- valueLabelVisible: false,
1348- valueTinted: false
1349- },
1350- {
1351- tag: "Confirmation notification with value",
1352+ }
1353+
1354+ mockModel.append(n)
1355+ }
1356+
1357+ function addConfirmationNotification() {
1358+ var n = {
1359 type: Notification.Confirmation,
1360 hints: {"x-canonical-non-shaped-icon": "true"},
1361- summary: "",
1362+ summary: "Confirmation notification",
1363 body: "",
1364 icon: "image://theme/audio-volume-medium",
1365 secondaryIcon: "",
1366 value: 50,
1367 actions: [],
1368- summaryVisible: false,
1369- bodyVisible: false,
1370- iconVisible: false,
1371- centeredIconVisible: true,
1372- shaped: false,
1373- secondaryIconVisible: false,
1374- buttonRowVisible: false,
1375- buttonTinted: false,
1376- hasSound: false,
1377- valueVisible: true,
1378- valueLabelVisible: false,
1379- valueTinted: false
1380- },
1381- {
1382- tag: "Confirmation notification with value, label and tint",
1383+ }
1384+
1385+ mockModel.append(n)
1386+ }
1387+
1388+ function add2ndConfirmationNotification() {
1389+ var n = {
1390 type: Notification.Confirmation,
1391 hints: {"x-canonical-non-shaped-icon": "true",
1392- "x-canonical-value-bar-tint" : "true"},
1393- summary: "",
1394+ "x-canonical-value-bar-tint": "true"},
1395+ summary: "Confirmation notification",
1396 body: "High Volume",
1397 icon: "image://theme/audio-volume-high",
1398 secondaryIcon: "",
1399 value: 85,
1400 actions: [],
1401- summaryVisible: false,
1402- bodyVisible: false,
1403- iconVisible: false,
1404- centeredIconVisible: true,
1405- shaped: false,
1406- secondaryIconVisible: false,
1407- buttonRowVisible: false,
1408- buttonTinted: false,
1409- hasSound: false,
1410- valueVisible: true,
1411- valueLabelVisible: true,
1412- valueTinted: true
1413- }
1414- ]
1415- }
1416-
1417- SignalSpy {
1418- id: clickThroughSpy
1419-
1420- target: clickThroughCatcher
1421- signalName: "clicked"
1422- }
1423-
1424- SignalSpy {
1425- id: actionSpy
1426-
1427- target: mockModel
1428- signalName: "actionInvoked"
1429- }
1430-
1431- function cleanup() {
1432- clickThroughSpy.clear()
1433- actionSpy.clear()
1434- }
1435-
1436- function test_NotificationRenderer(data) {
1437- // populate model with some mock notifications
1438- mockModel.append(data)
1439-
1440- // make sure the view is properly updated before going on
1441- notifications.forceLayout();
1442- waitForRendering(notifications);
1443-
1444- var notification = findChild(notifications, "notification" + (mockModel.count - 1))
1445- verify(notification !== undefined, "notification wasn't found");
1446-
1447- waitForRendering(notification);
1448-
1449- var icon = findChild(notification, "icon")
1450- var centeredIcon = findChild(notification, "centeredIcon")
1451- var interactiveArea = findChild(notification, "interactiveArea")
1452- var secondaryIcon = findChild(notification, "secondaryIcon")
1453- var summaryLabel = findChild(notification, "summaryLabel")
1454- var bodyLabel = findChild(notification, "bodyLabel")
1455- var buttonRow = findChild(notification, "buttonRow")
1456- var valueIndicator = findChild(notification, "valueIndicator")
1457- var valueLabel = findChild(notification, "valueLabel")
1458- var innerBar = findChild(notification, "innerBar")
1459-
1460- compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
1461- if (icon.visible) {
1462- compare(icon.shaped, data.shaped, "shaped-status is incorrect")
1463- }
1464- compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
1465- if (centeredIcon.visible) {
1466- compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
1467- }
1468- compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
1469- if (valueIndicator.visible) {
1470- verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
1471- }
1472- compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
1473-
1474- // test input does not fall through
1475- mouseClick(notification, notification.width / 2, notification.height / 2)
1476- if(data.type == Notification.Interactive) {
1477- actionSpy.wait()
1478- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
1479- }
1480- compare(clickThroughSpy.count, 0, "click on notification fell through")
1481-
1482- compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
1483- compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
1484- compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
1485- compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
1486-
1487- var audioItem = findInvisibleChild(notification, "sound")
1488- compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
1489-
1490- if(data.buttonRowVisible) {
1491- var buttonCancel = findChild(buttonRow, "notify_button1")
1492- var buttonAccept = findChild(buttonRow, "notify_button0")
1493-
1494- // only test the left/cancel-button if two actions have been passed in
1495- if (data.actions.length == 2) {
1496- tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
1497- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
1498- actionSpy.clear()
1499- }
1500-
1501- // check the tinting of the positive/right button
1502- verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
1503-
1504- // click the positive/right button
1505- tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
1506- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
1507+ }
1508+
1509+ mockModel.append(n)
1510+ }
1511+
1512+ function clearNotifications() {
1513+ while(mockModel.count > 1) {
1514+ remove1stNotification()
1515+ }
1516+ }
1517+
1518+ function remove1stNotification() {
1519+ if (mockModel.count > 1)
1520+ mockModel.remove(1)
1521+ }
1522+
1523+ Rectangle {
1524+ id: notificationsRect
1525+
1526+ width: units.gu(40)
1527+ height: units.gu(115)
1528+
1529+ MouseArea{
1530+ id: clickThroughCatcher
1531+
1532+ anchors.fill: parent
1533+ }
1534+
1535+ Notifications {
1536+ id: notifications
1537+
1538+ margin: units.gu(1)
1539+
1540+ anchors.fill: parent
1541+ model: mockModel
1542+ }
1543+ }
1544+
1545+ Rectangle {
1546+ id: interactiveControls
1547+
1548+ width: units.gu(30)
1549+ height: units.gu(115)
1550+ color: "grey"
1551+
1552+ Column {
1553+ spacing: units.gu(1)
1554+ anchors.fill: parent
1555+ anchors.margins: units.gu(1)
1556+
1557+ Button {
1558+ width: parent.width
1559+ text: "add a snap-decision"
1560+ onClicked: rootRow.addSnapDecisionNotification()
1561+ }
1562+
1563+ Button {
1564+ width: parent.width
1565+ text: "add a 2over1 snap-decision"
1566+ onClicked: rootRow.add2over1SnapDecisionNotification()
1567+ }
1568+
1569+ Button {
1570+ width: parent.width
1571+ text: "add an ephemeral"
1572+ onClicked: rootRow.addEphemeralNotification()
1573+ }
1574+
1575+ Button {
1576+ width: parent.width
1577+ text: "add an non-shaped-icon-summary-body"
1578+ onClicked: rootRow.addEphemeralNonShapedIconNotification()
1579+ }
1580+
1581+ Button {
1582+ width: parent.width
1583+ text: "add an icon-summary"
1584+ onClicked: rootRow.addEphemeralIconSummaryNotification()
1585+ }
1586+
1587+ Button {
1588+ width: parent.width
1589+ text: "add an interactive"
1590+ onClicked: rootRow.addInteractiveNotification()
1591+ }
1592+
1593+ Button {
1594+ width: parent.width
1595+ text: "add a confirmation"
1596+ onClicked: rootRow.addConfirmationNotification()
1597+ }
1598+
1599+ Button {
1600+ width: parent.width
1601+ text: "add a 2nd confirmation"
1602+ onClicked: rootRow.add2ndConfirmationNotification()
1603+ }
1604+
1605+ Button {
1606+ width: parent.width
1607+ text: "remove 1st notification"
1608+ onClicked: rootRow.remove1stNotification()
1609+ }
1610+
1611+ Button {
1612+ width: parent.width
1613+ text: "clear model"
1614+ onClicked: rootRow.clearNotifications()
1615+ }
1616+ }
1617+ }
1618+
1619+ ActionModel {
1620+ id: myActionModel
1621+ }
1622+
1623+ UnityTestCase {
1624+ id: root
1625+ name: "NotificationRendererTest"
1626+ when: windowShown
1627+
1628+ function test_NotificationRenderer_data() {
1629+ return [
1630+ {
1631+ tag: "Snap Decision with secondary icon and button-tint",
1632+ type: Notification.SnapDecision,
1633+ hints: {"x-canonical-private-affirmative-tint": "true"},
1634+ summary: "Tom Ato",
1635+ 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.",
1636+ icon: "../graphics/avatars/funky.png",
1637+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
1638+ actions: [{ id: "ok_id", label: "Ok"},
1639+ { id: "cancel_id", label: "Cancel"},
1640+ { id: "notreally_id", label: "Not really"},
1641+ { id: "noway_id", label: "messages:No way"},
1642+ { id: "nada_id", label: "messages:Nada"}],
1643+ summaryVisible: true,
1644+ bodyVisible: true,
1645+ iconVisible: true,
1646+ centeredIconVisible: false,
1647+ shaped: true,
1648+ secondaryIconVisible: true,
1649+ buttonRowVisible: true,
1650+ buttonTinted: true,
1651+ hasSound: false,
1652+ valueVisible: false,
1653+ valueLabelVisible: false,
1654+ valueTinted: false
1655+ },
1656+ {
1657+ tag: "2-over-1 Snap Decision with button-tint",
1658+ type: Notification.SnapDecision,
1659+ hints: {"x-canonical-private-affirmative-tint": "true"},
1660+ summary: "Theatre at Ferria Stadium",
1661+ body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
1662+ icon: "",
1663+ secondaryIcon: "",
1664+ actions: [{ id: "ok_id", label: "Ok"},
1665+ { id: "snooze_id", label: "Snooze"},
1666+ { id: "view_id", label: "View"}],
1667+ summaryVisible: true,
1668+ bodyVisible: true,
1669+ iconVisible: false,
1670+ centeredIconVisible: false,
1671+ shaped: false,
1672+ secondaryIconVisible: false,
1673+ buttonRowVisible: false,
1674+ buttonTinted: true,
1675+ hasSound: false,
1676+ valueVisible: false,
1677+ valueLabelVisible: false,
1678+ valueTinted: false
1679+ },
1680+ {
1681+ tag: "Ephemeral notification - icon-summary layout",
1682+ type: Notification.Ephemeral,
1683+ hints: {},
1684+ summary: "Photo upload completed",
1685+ body: "",
1686+ icon: "../graphics/applicationIcons/facebook.png",
1687+ secondaryIcon: "",
1688+ actions: [],
1689+ summaryVisible: true,
1690+ bodyVisible: false,
1691+ iconVisible: true,
1692+ centeredIconVisible: false,
1693+ shaped: true,
1694+ secondaryIconVisible: false,
1695+ buttonRowVisible: false,
1696+ buttonTinted: false,
1697+ hasSound: false,
1698+ valueVisible: false,
1699+ valueLabelVisible: false,
1700+ valueTinted: false
1701+ },
1702+ {
1703+ tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
1704+ type: Notification.Ephemeral,
1705+ hints: {"x-canonical-private-affirmative-tint": "false",
1706+ "sound-file": "dummy.ogg",
1707+ "suppress-sound": "true"},
1708+ summary: "New comment successfully published",
1709+ body: "",
1710+ icon: "",
1711+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
1712+ actions: [],
1713+ summaryVisible: true,
1714+ bodyVisible: false,
1715+ interactiveAreaEnabled: false,
1716+ iconVisible: false,
1717+ centeredIconVisible: false,
1718+ shaped: false,
1719+ secondaryIconVisible: true,
1720+ buttonRowVisible: false,
1721+ buttonTinted: false,
1722+ hasSound: false,
1723+ valueVisible: false,
1724+ valueLabelVisible: false,
1725+ valueTinted: false
1726+ },
1727+ {
1728+ tag: "Interactive notification",
1729+ type: Notification.Interactive,
1730+ hints: {"x-canonical-private-affirmative-tint": "false",
1731+ "sound-file": "dummy.ogg"},
1732+ summary: "Interactive notification",
1733+ body: "This is a notification that can be clicked",
1734+ icon: "../graphics/avatars/amanda.png",
1735+ secondaryIcon: "",
1736+ actions: [{ id: "reply_id", label: "Dummy"}],
1737+ summaryVisible: true,
1738+ bodyVisible: true,
1739+ iconVisible: true,
1740+ centeredIconVisible: false,
1741+ shaped: true,
1742+ secondaryIconVisible: false,
1743+ buttonRowVisible: false,
1744+ buttonTinted: false,
1745+ hasSound: true,
1746+ valueVisible: false,
1747+ valueLabelVisible: false,
1748+ valueTinted: false
1749+ },
1750+ {
1751+ tag: "Snap Decision without secondary icon and no button-tint",
1752+ type: Notification.SnapDecision,
1753+ hints: {"x-canonical-private-affirmative-tint": "false",
1754+ "sound-file": "dummy.ogg"},
1755+ summary: "Bro Coly",
1756+ 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.",
1757+ icon: "../graphics/avatars/anna_olsson.png",
1758+ secondaryIcon: "",
1759+ actions: [{ id: "accept_id", label: "Accept"},
1760+ { id: "reject_id", label: "Reject"}],
1761+ summaryVisible: true,
1762+ bodyVisible: true,
1763+ iconVisible: true,
1764+ centeredIconVisible: false,
1765+ shaped: true,
1766+ secondaryIconVisible: false,
1767+ buttonRowVisible: true,
1768+ buttonTinted: false,
1769+ hasSound: true,
1770+ valueVisible: false,
1771+ valueLabelVisible: false,
1772+ valueTinted: false
1773+ },
1774+ {
1775+ tag: "Ephemeral notification",
1776+ type: Notification.Ephemeral,
1777+ hints: {"x-canonical-private-affirmative-tint": "false",
1778+ "sound-file": "dummy.ogg"},
1779+ summary: "Cole Raby",
1780+ body: "I did not expect it to be that late.",
1781+ icon: "../graphics/avatars/funky.png",
1782+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
1783+ actions: [],
1784+ summaryVisible: true,
1785+ bodyVisible: true,
1786+ iconVisible: true,
1787+ centeredIconVisible: false,
1788+ shaped: true,
1789+ secondaryIconVisible: true,
1790+ buttonRowVisible: false,
1791+ buttonTinted: false,
1792+ hasSound: true,
1793+ valueVisible: false,
1794+ valueLabelVisible: false,
1795+ valueTinted: false
1796+ },
1797+ {
1798+ tag: "Ephemeral notification with non-shaped icon",
1799+ type: Notification.Ephemeral,
1800+ hints: {"x-canonical-private-affirmative-tint": "false",
1801+ "x-canonical-non-shaped-icon": "true"},
1802+ summary: "Contacts",
1803+ body: "Synchronised contacts-database with cloud-storage.",
1804+ icon: "image://theme/contacts-app",
1805+ secondaryIcon: "",
1806+ actions: [],
1807+ summaryVisible: true,
1808+ bodyVisible: true,
1809+ iconVisible: true,
1810+ centeredIconVisible: false,
1811+ shaped: false,
1812+ secondaryIconVisible: false,
1813+ buttonRowVisible: false,
1814+ buttonTinted: false,
1815+ hasSound: false,
1816+ valueVisible: false,
1817+ valueLabelVisible: false,
1818+ valueTinted: false
1819+ },
1820+ {
1821+ tag: "Confirmation notification with value",
1822+ type: Notification.Confirmation,
1823+ hints: {"x-canonical-non-shaped-icon": "true"},
1824+ summary: "",
1825+ body: "",
1826+ icon: "image://theme/audio-volume-medium",
1827+ secondaryIcon: "",
1828+ value: 50,
1829+ actions: [],
1830+ summaryVisible: false,
1831+ bodyVisible: false,
1832+ iconVisible: false,
1833+ centeredIconVisible: true,
1834+ shaped: false,
1835+ secondaryIconVisible: false,
1836+ buttonRowVisible: false,
1837+ buttonTinted: false,
1838+ hasSound: false,
1839+ valueVisible: true,
1840+ valueLabelVisible: false,
1841+ valueTinted: false
1842+ },
1843+ {
1844+ tag: "Confirmation notification with value, label and tint",
1845+ type: Notification.Confirmation,
1846+ hints: {"x-canonical-non-shaped-icon": "true",
1847+ "x-canonical-value-bar-tint" : "true"},
1848+ summary: "",
1849+ body: "High Volume",
1850+ icon: "image://theme/audio-volume-high",
1851+ secondaryIcon: "",
1852+ value: 85,
1853+ actions: [],
1854+ summaryVisible: false,
1855+ bodyVisible: false,
1856+ iconVisible: false,
1857+ centeredIconVisible: true,
1858+ shaped: false,
1859+ secondaryIconVisible: false,
1860+ buttonRowVisible: false,
1861+ buttonTinted: false,
1862+ hasSound: false,
1863+ valueVisible: true,
1864+ valueLabelVisible: true,
1865+ valueTinted: true
1866+ }
1867+ ]
1868+ }
1869+
1870+ SignalSpy {
1871+ id: clickThroughSpy
1872+
1873+ target: clickThroughCatcher
1874+ signalName: "clicked"
1875+ }
1876+
1877+ SignalSpy {
1878+ id: actionSpy
1879+
1880+ target: mockModel
1881+ signalName: "actionInvoked"
1882+ }
1883+
1884+ function cleanup() {
1885+ clickThroughSpy.clear()
1886 actionSpy.clear()
1887- waitForRendering(notification)
1888-
1889- // check if there's a ComboButton created due to more actions being passed
1890- if (data.actions.length > 2) {
1891- var comboButton = findChild(notification, "notify_button2")
1892- tryCompareFunction(function() { return comboButton.expanded == false; }, true);
1893-
1894- // click to expand
1895- tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
1896-
1897- // try clicking on choices in expanded comboList
1898- var choiceButton1 = findChild(notification, "notify_button3")
1899- tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
1900- compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
1901- actionSpy.clear()
1902-
1903- var choiceButton2 = findChild(notification, "notify_button4")
1904- tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
1905- compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
1906- actionSpy.clear()
1907-
1908- // click to collapse
1909- //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
1910- } else {
1911- mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
1912- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
1913+ }
1914+
1915+ function test_NotificationRenderer(data) {
1916+ // populate model with some mock notifications
1917+ mockModel.append(data)
1918+
1919+ // make sure the view is properly updated before going on
1920+ notifications.forceLayout();
1921+ waitForRendering(notifications);
1922+
1923+ var notification = findChild(notifications, "notification" + (mockModel.count - 1))
1924+ verify(notification !== undefined, "notification wasn't found");
1925+
1926+ waitForRendering(notification);
1927+
1928+ var icon = findChild(notification, "icon")
1929+ var centeredIcon = findChild(notification, "centeredIcon")
1930+ var interactiveArea = findChild(notification, "interactiveArea")
1931+ var secondaryIcon = findChild(notification, "secondaryIcon")
1932+ var summaryLabel = findChild(notification, "summaryLabel")
1933+ var bodyLabel = findChild(notification, "bodyLabel")
1934+ var buttonRow = findChild(notification, "buttonRow")
1935+ var valueIndicator = findChild(notification, "valueIndicator")
1936+ var valueLabel = findChild(notification, "valueLabel")
1937+ var innerBar = findChild(notification, "innerBar")
1938+
1939+ compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
1940+ if (icon.visible) {
1941+ compare(icon.shaped, data.shaped, "shaped-status is incorrect")
1942+ }
1943+ compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
1944+ if (centeredIcon.visible) {
1945+ compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
1946+ }
1947+ compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
1948+ if (valueIndicator.visible) {
1949+ verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
1950+ }
1951+ compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
1952+
1953+ // test input does not fall through
1954+ mouseClick(notification, notification.width / 2, notification.height / 2)
1955+ if(data.type == Notification.Interactive) {
1956+ actionSpy.wait()
1957+ compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
1958+ }
1959+ compare(clickThroughSpy.count, 0, "click on notification fell through")
1960+
1961+ compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
1962+ compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
1963+ compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
1964+ compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
1965+
1966+ var audioItem = findInvisibleChild(notification, "sound")
1967+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
1968+
1969+ if(data.buttonRowVisible) {
1970+ var buttonCancel = findChild(buttonRow, "notify_button1")
1971+ var buttonAccept = findChild(buttonRow, "notify_button0")
1972+
1973+ // only test the left/cancel-button if two actions have been passed in
1974+ if (data.actions.length == 2) {
1975+ tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
1976+ compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
1977+ actionSpy.clear()
1978+ }
1979+
1980+ // check the tinting of the positive/right button
1981+ verify(buttonAccept.color === data.buttonTinted ? "#3fb24f" : "#dddddd", "button has the wrong color-tint")
1982+
1983+ // click the positive/right button
1984+ tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
1985+ compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
1986+ actionSpy.clear()
1987+ waitForRendering (notification)
1988+
1989+ // check if there's a ComboButton created due to more actions being passed
1990+ if (data.actions.length > 2) {
1991+ var comboButton = findChild(notification, "notify_button2")
1992+ tryCompareFunction(function() { return comboButton.expanded == false; }, true);
1993+
1994+ // click to expand
1995+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
1996+
1997+ // try clicking on choices in expanded comboList
1998+ var choiceButton1 = findChild(notification, "notify_button3")
1999+ tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
2000+ compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
2001+ actionSpy.clear()
2002+
2003+ var choiceButton2 = findChild(notification, "notify_button4")
2004+ tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
2005+ compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
2006+ actionSpy.clear()
2007+
2008+ // click to collapse
2009+ //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
2010+ } else {
2011+ mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
2012+ compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
2013+ }
2014 }
2015 }
2016 }
2017
2018=== added file 'tests/qmltests/Notifications/tst_SwipeToAct.qml'
2019--- tests/qmltests/Notifications/tst_SwipeToAct.qml 1970-01-01 00:00:00 +0000
2020+++ tests/qmltests/Notifications/tst_SwipeToAct.qml 2014-10-31 10:46:25 +0000
2021@@ -0,0 +1,276 @@
2022+/*
2023+ * Copyright (C) 2014 Canonical, Ltd.
2024+ *
2025+ * This program is free software; you can redistribute it and/or modify
2026+ * it under the terms of the GNU General Public License as published by
2027+ * the Free Software Foundation; version 3.
2028+ *
2029+ * This program is distributed in the hope that it will be useful,
2030+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2031+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2032+ * GNU General Public License for more details.
2033+ *
2034+ * You should have received a copy of the GNU General Public License
2035+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2036+ */
2037+
2038+import QtQuick 2.0
2039+import QtTest 1.0
2040+import ".."
2041+import "../../../qml/Notifications"
2042+import Ubuntu.Components 0.1
2043+import Unity.Test 0.1
2044+import Unity.Notifications 1.0
2045+
2046+Item {
2047+ width: notificationsRect.width + interactiveControls.width
2048+ height: notificationsRect.height
2049+
2050+ Row {
2051+ id: rootRow
2052+
2053+ Component {
2054+ id: mockNotification
2055+
2056+ QtObject {
2057+ function invokeAction(actionId) {
2058+ mockModel.actionInvoked(actionId)
2059+ }
2060+ }
2061+ }
2062+
2063+ ListModel {
2064+ id: mockModel
2065+ dynamicRoles: true
2066+
2067+ signal actionInvoked(string actionId)
2068+
2069+ function getRaw(id) {
2070+ return mockNotification.createObject(mockModel)
2071+ }
2072+
2073+ // add the default/PlaceHolder notification to the model
2074+ Component.onCompleted: {
2075+ var n = {
2076+ type: Notification.PlaceHolder,
2077+ hints: {},
2078+ summary: "",
2079+ body: "",
2080+ icon: "",
2081+ secondaryIcon: "",
2082+ actions: []
2083+ }
2084+
2085+ append(n)
2086+ }
2087+ }
2088+
2089+ function addSwipeToActNotification() {
2090+ var n = {
2091+ type: Notification.SnapDecision,
2092+ hints: {"x-canonical-snap-decisions-swipe": "true"},
2093+ summary: "Incoming call",
2094+ body: "Frank Zappa\n+44 (0)7736 027340",
2095+ icon: "../graphics/avatars/amanda.png",
2096+ secondaryIcon: "incoming-call",
2097+ actions: [{ id: "ok_id", label: "Ok"},
2098+ { id: "cancel_id", label: "Cancel"}]
2099+ }
2100+
2101+ mockModel.append(n)
2102+ }
2103+
2104+ function clearNotifications() {
2105+ mockModel.clear()
2106+ }
2107+
2108+ function remove1stNotification() {
2109+ if (mockModel.count > 0)
2110+ mockModel.remove(0)
2111+ }
2112+
2113+ Rectangle {
2114+ id: notificationsRect
2115+
2116+ width: units.gu(40)
2117+ height: units.gu(71)
2118+
2119+ MouseArea{
2120+ id: clickThroughCatcher
2121+
2122+ anchors.fill: parent
2123+ }
2124+
2125+ Notifications {
2126+ id: notifications
2127+
2128+ margin: units.gu(1)
2129+
2130+ anchors.fill: parent
2131+ model: mockModel
2132+ }
2133+ }
2134+
2135+ Rectangle {
2136+ id: interactiveControls
2137+
2138+ width: units.gu(30)
2139+ height: units.gu(81)
2140+ color: "grey"
2141+
2142+ Column {
2143+ spacing: units.gu(1)
2144+ anchors.fill: parent
2145+ anchors.margins: units.gu(1)
2146+
2147+ Button {
2148+ width: parent.width
2149+ text: "add a SwipeToAct snap-decision"
2150+ onClicked: rootRow.addSwipeToActNotification()
2151+ }
2152+
2153+ Button {
2154+ width: parent.width
2155+ text: "remove 1st notification"
2156+ onClicked: rootRow.remove1stNotification()
2157+ }
2158+
2159+ Button {
2160+ width: parent.width
2161+ text: "clear model"
2162+ onClicked: rootRow.clearNotifications()
2163+ }
2164+ }
2165+ }
2166+
2167+ ActionModel {
2168+ id: myActionModel
2169+ }
2170+
2171+ UnityTestCase {
2172+ id: root
2173+ name: "NotificationRendererTest"
2174+ when: windowShown
2175+
2176+ function test_NotificationRenderer_data() {
2177+ return [
2178+ {
2179+ tag: "Snap Decision with SwipeToAct-widget (accept)",
2180+ type: Notification.SnapDecision,
2181+ hints: {"x-canonical-snap-decisions-swipe": "true"},
2182+ summary: "Incoming call",
2183+ body: "Frank Zappa\n+44 (0)7736 027340",
2184+ icon: "../graphics/avatars/amanda.png",
2185+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
2186+ actions: myActionModel,
2187+ summaryVisible: true,
2188+ bodyVisible: true,
2189+ iconVisible: true,
2190+ shaped: true,
2191+ secondaryIconVisible: true,
2192+ buttonRowVisible: true,
2193+ buttonTinted: false,
2194+ checkSwipeToActAccept: true,
2195+ checkSwipeToActReject: false
2196+ },
2197+ {
2198+ tag: "Snap Decision with SwipeToAct-widget (reject)",
2199+ type: Notification.SnapDecision,
2200+ hints: {"x-canonical-snap-decisions-swipe": "true"},
2201+ summary: "Incoming call",
2202+ body: "Bro Coly\n+49 (0)221 426973",
2203+ icon: "../graphics/avatars/funky.png",
2204+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
2205+ actions: myActionModel,
2206+ summaryVisible: true,
2207+ bodyVisible: true,
2208+ iconVisible: true,
2209+ shaped: true,
2210+ secondaryIconVisible: true,
2211+ buttonRowVisible: true,
2212+ buttonTinted: false,
2213+ checkSwipeToActAccept: false,
2214+ checkSwipeToActReject: true
2215+ }
2216+ ]
2217+ }
2218+
2219+ SignalSpy {
2220+ id: clickThroughSpy
2221+
2222+ target: clickThroughCatcher
2223+ signalName: "clicked"
2224+ }
2225+
2226+ SignalSpy {
2227+ id: actionSpy
2228+
2229+ target: mockModel
2230+ signalName: "actionInvoked"
2231+ }
2232+
2233+ function cleanup() {
2234+ clickThroughSpy.clear()
2235+ actionSpy.clear()
2236+ }
2237+
2238+ function test_NotificationRenderer(data) {
2239+ // populate model with some mock notifications
2240+ mockModel.append(data)
2241+
2242+ // make sure the view is properly updated before going on
2243+ notifications.forceLayout();
2244+ waitForRendering(notifications);
2245+
2246+ var notification = findChild(notifications, "notification" + (mockModel.count - 1))
2247+ verify(notification !== undefined, "notification wasn't found");
2248+
2249+ waitForRendering(notification);
2250+
2251+ var icon = findChild(notification, "icon")
2252+ var interactiveArea = findChild(notification, "interactiveArea")
2253+ var secondaryIcon = findChild(notification, "secondaryIcon")
2254+ var summaryLabel = findChild(notification, "summaryLabel")
2255+ var bodyLabel = findChild(notification, "bodyLabel")
2256+ var buttonRow = findChild(notification, "buttonRow")
2257+
2258+ compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
2259+ if (icon.visible) {
2260+ compare(icon.shaped, data.shaped, "shaped-status is incorrect")
2261+ }
2262+
2263+ // test input does not fall through
2264+ mouseClick(notification, notification.width / 2, notification.height / 2)
2265+ if(data.type == Notification.Interactive) {
2266+ actionSpy.wait()
2267+ compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
2268+ }
2269+ compare(clickThroughSpy.count, 0, "click on notification fell through")
2270+
2271+ compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
2272+ compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
2273+ compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
2274+ compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
2275+
2276+ if(data.buttonRowVisible) {
2277+ var swipeButton = findChild(buttonRow, "notify_swipe_button")
2278+ var slider = findChild(swipeButton, "slider")
2279+ var swipeMouseArea = findChild(swipeButton, "swipeMouseArea")
2280+ var x = swipeMouseArea.width / 2
2281+ var y = swipeMouseArea.height / 2
2282+
2283+ if(data.checkSwipeToActReject) {
2284+ tryCompareFunction(function() { mouseDrag(slider, x, y, -(swipeMouseArea.width / 2), 0); return actionSpy.signalArguments.length > 0; }, true);
2285+ compare(actionSpy.signalArguments[0][0], data.actions.data(0, ActionModel.RoleActionId), "got wrong id for negative action")
2286+ actionSpy.clear()
2287+ }
2288+ if(data.checkSwipeToActAccept) {
2289+ tryCompareFunction(function() { mouseDrag(slider, x, y, (swipeMouseArea.width / 2) - slider.width, 0); return actionSpy.signalArguments.length > 0; }, true);
2290+ compare(actionSpy.signalArguments[0][0], data.actions.data(1, ActionModel.RoleActionId), "got wrong id for positive action")
2291+ actionSpy.clear()
2292+ }
2293+ }
2294+ }
2295+ }
2296+ }
2297+}

Subscribers

People subscribed via source and target branches