on xenial help sign in
Fix lp:1370240 by making stages interactive when a snap decision is dismissed. Fixes: 1370240
b1b310fMichael Zanetti
qml/Notifications/Notifications.qml+1
@@ -32,6 +32,7 @@ Flickables.ListView {
32
32
33 SortFilterProxyModel {
33 SortFilterProxyModel {
34 id: snapDecisionProxyModel
34 id: snapDecisionProxyModel
35+ objectName: "snapDecisionProxyModel"
35
36
36 model: notificationList.model
37 model: notificationList.model
37 filterRole: UnityNotifications.ModelInterface != undefined ? UnityNotifications.ModelInterface.RoleType : 0
38 filterRole: UnityNotifications.ModelInterface != undefined ? UnityNotifications.ModelInterface.RoleType : 0
qml/Shell.qml+1−1
@@ -248,7 +248,7 @@ Item {
248 Binding {
248 Binding {
249 target: applicationsDisplayLoader.item
249 target: applicationsDisplayLoader.item
250 property: "interactive"
250 property: "interactive"
251− value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
251+ value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
252 }
252 }
253 Binding {
253 Binding {
254 target: applicationsDisplayLoader.item
254 target: applicationsDisplayLoader.item
tests/qmltests/tst_Shell.qml+127−41
@@ -25,14 +25,16 @@ import Ubuntu.Components 1.1
25import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Application 0.1
26import Unity.Application 0.1
27import Unity.Connectivity 0.1
27import Unity.Connectivity 0.1
28+import Unity.Notifications 1.0
28import Unity.Test 0.1 as UT
29import Unity.Test 0.1 as UT
29import Powerd 0.1
30import Powerd 0.1
30
31
31import "../../qml"
32import "../../qml"
32
33
33−Row {
34+Item {
34 id: root
35 id: root
35− spacing: 0
36+ width: units.gu(60)
37+ height: units.gu(71)
36
38
37 QtObject {
39 QtObject {
38 id: applicationArguments
40 id: applicationArguments
@@ -50,49 +52,43 @@ Row {
50 }
52 }
51 }
53 }
52
54
55+ Row {
56+ anchors.fill: parent
53− Loader {
57+ Loader {
54− id: shellLoader
58+ id: shellLoader
55−
59+
56− // Copied from Shell.qml
57− property bool tablet: false
58− width: tablet ? units.gu(160)
59− : applicationArguments.hasGeometry() ? applicationArguments.width()
60− : units.gu(40)
61− height: tablet ? units.gu(100)
62− : applicationArguments.hasGeometry() ? applicationArguments.height()
63− : units.gu(71)
64−
65− property bool itemDestroyed: false
60+ property bool itemDestroyed: false
66− sourceComponent: Component {
61+ sourceComponent: Component {
67− Shell {
62+ Shell {
68− property string indicatorProfile: "phone"
63+ property string indicatorProfile: "phone"
69−
64+
70− Component.onDestruction: {
65+ Component.onDestruction: {
71− shellLoader.itemDestroyed = true;
66+ shellLoader.itemDestroyed = true;
67+ }
72 }
68 }
73 }
69 }
74 }
70 }
75− }
76
71
77− Rectangle {
72+ Rectangle {
78− color: "white"
73+ color: "white"
79− width: units.gu(30)
74+ width: units.gu(30)
80− height: shellLoader.height
75+ height: shellLoader.height
81−
76+
82− Column {
77+ Column {
83− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
78+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
84− spacing: units.gu(1)
79+ spacing: units.gu(1)
85− Row {
80+ Row {
86− anchors { left: parent.left; right: parent.right }
81+ anchors { left: parent.left; right: parent.right }
87− Button {
82+ Button {
88− text: "Show Greeter"
83+ text: "Show Greeter"
89− onClicked: {
84+ onClicked: {
90− if (shellLoader.status !== Loader.Ready)
85+ if (shellLoader.status !== Loader.Ready)
91− return;
86+ return;
92−
87+
93− var greeter = testCase.findChild(shellLoader.item, "greeter");
88+ var greeter = testCase.findChild(shellLoader.item, "greeter");
94− if (!greeter.shown) {
89+ if (!greeter.shown) {
95− greeter.show();
90+ greeter.show();
91+ }
96 }
92 }
97 }
93 }
98 }
94 }
@@ -100,6 +96,31 @@ Row {
100 }
96 }
101 }
97 }
102
98
99+ Component {
100+ id: mockNotification
101+
102+ QtObject {
103+ function invokeAction(actionId) {
104+ mockNotificationsModel.actionInvoked(actionId)
105+ }
106+ }
107+ }
108+ ListModel {
109+ id: mockNotificationsModel
110+
111+ signal actionInvoked(string actionId)
112+
113+ function getRaw(id) {
114+ return mockNotification.createObject(mockNotificationsModel)
115+ }
116+
117+ onActionInvoked: {
118+ if(actionId == "ok_id") {
119+ mockNotificationsModel.clear()
120+ }
121+ }
122+ }
123+
103 SignalSpy {
124 SignalSpy {
104 id: launcherShowDashHomeSpy
125 id: launcherShowDashHomeSpy
105 signalName: "showDashHome"
126 signalName: "showDashHome"
@@ -121,6 +142,12 @@ Row {
121 signalName: "unlockingAllModems"
142 signalName: "unlockingAllModems"
122 }
143 }
123
144
145+ SignalSpy {
146+ id: notificationActionSpy
147+ target: mockNotificationsModel
148+ signalName: "actionInvoked"
149+ }
150+
124 Telephony.CallEntry {
151 Telephony.CallEntry {
125 id: phoneCall
152 id: phoneCall
126 phoneNumber: "+447812221111"
153 phoneNumber: "+447812221111"
@@ -180,6 +207,65 @@ Row {
180 compare(ApplicationManager.count, 1)
207 compare(ApplicationManager.count, 1)
181 }
208 }
182
209
210+ function test_snapDecisionDismissalReturnsFocus() {
211+ var notifications = findChild(shell, "notificationList");
212+ var app = ApplicationManager.startApplication("camera-app");
213+ var stage = findChild(shell, "stage")
214+ // Open an application and focus
215+ waitUntilApplicationWindowIsFullyVisible(app);
216+ ApplicationManager.focusApplication(app);
217+ tryCompare(app.session.surface, "activeFocus", true);
218+
219+ notifications.model = mockNotificationsModel;
220+
221+ // FIXME: Hack: SortFilterProxyModelQML doesn't work with QML ListModels which we use
222+ // for mocking here (RoleType can't be found in the QML model). As we only need to show
223+ // one SnapDecision lets just disable the filtering and make appear any notification as a
224+ // SnapDecision.
225+ var snapDecisionProxyModel = findInvisibleChild(shell, "snapDecisionProxyModel");
226+ snapDecisionProxyModel.filterRegExp = RegExp("");
227+
228+ // Pop-up a notification
229+ addSnapDecisionNotification();
230+ waitForRendering(shell);
231+
232+ // Make sure the notification really opened
233+ var notification = findChild(notifications, "notification" + (mockNotificationsModel.count - 1));
234+ verify(notification !== undefined && notification != null, "notification wasn't found");
235+ tryCompare(notification, "height", notification.implicitHeight)
236+ waitForRendering(notification);
237+
238+ // Make sure activeFocus went away from the app window
239+ tryCompare(app.session.surface, "activeFocus", false);
240+ tryCompare(stage, "interactive", false);
241+
242+ // Clicking the button should dismiss the notification and return focus
243+ var buttonAccept = findChild(notification, "notify_button0");
244+ mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2);
245+
246+ // Make sure we're back to normal
247+ tryCompare(app.session.surface, "activeFocus", true);
248+ compare(stage.interactive, true, "Stages not interactive again after modal notification has closed");
249+ }
250+
251+ function addSnapDecisionNotification() {
252+ var n = {
253+ type: Notification.SnapDecision,
254+ hints: {"x-canonical-private-affirmative-tint": "true"},
255+ summary: "Tom Ato",
256+ 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.",
257+ icon: "../graphics/avatars/funky.png",
258+ secondaryIcon: "../graphics/applicationIcons/facebook.png",
259+ actions: [{ id: "ok_id", label: "Ok"},
260+ { id: "cancel_id", label: "Cancel"},
261+ { id: "notreally_id", label: "Not really"},
262+ { id: "noway_id", label: "messages:No way"},
263+ { id: "nada_id", label: "messages:Nada"}]
264+ }
265+
266+ mockNotificationsModel.append(n)
267+ }
268+
183 function test_leftEdgeDrag_data() {
269 function test_leftEdgeDrag_data() {
184 return [
270 return [
185 {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
271 {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},