on xenial help sign in
Merged Chris' branch (and trunk), added comments to the notification ap-test and split up the updating-notification ap-test into two separate tests
commit89ba48a3a9c2f74044530ee4f8286c7bb841830e authorMirco Müller <mirco.mueller@ubuntu.com> dateWed 31 Jul 2013, 11:41 (13 years ago) parent3c8ff57361be38
64 files changed+1516−1347
Components/ApplicationManagerWrapper.qml+8−1 Components/Stage.qml+14 Dash/DashApps.qml+1−1 Dash/DashHome.qml+1 Dash/DashMusic.qml+2−2 Dash/DashVideos.qml+2−3 Dash/GenericScopeView.qml+9 Panel/IndicatorItem.qml+5 Panel/IndicatorRow.qml+34−48 Panel/Indicators.qml+89−102 Panel/MenuContent.qml+2−2 Panel/Panel.qml+28−9 Panel/SearchIndicator.qml+3−5 Shell.qml+25−9 debian/changelog+97 debian/control+3−1 main.cpp+1−1 plugins/ListViewWithPageHeader/CMakeLists.txt+5−1 plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+71−5 plugins/ListViewWithPageHeader/listviewwithpageheader.h+18−1 plugins/Unity/CMakeLists.txt+1−1 plugins/Unity/categories.cpp+6−3 plugins/Unity/categories.h+3−2 plugins/Unity/previewinfohint.cpp+2−2 plugins/Unity/variantutils.cpp+2−118 plugins/Unity/variantutils.h−2 tests/autopilot/unity8/indicators_client/tests/test_battery.py+10−2 tests/autopilot/unity8/shell/__init__.py+1−27 tests/autopilot/unity8/shell/emulators/create_interactive_notification.py+2−2 tests/autopilot/unity8/shell/emulators/create_sd_notification.py−83 tests/autopilot/unity8/shell/emulators/notification_helper.py−247 tests/autopilot/unity8/shell/tests/__init__.py+53−1 tests/autopilot/unity8/shell/tests/test_hud.py−5 tests/autopilot/unity8/shell/tests/test_lock_screen.py−2 tests/autopilot/unity8/shell/tests/test_notifications.py+466−518 tests/mocks/CMakeLists.txt+1 tests/mocks/QMenuModel/CMakeLists.txt+11 tests/mocks/QMenuModel/QDBusActionGroup.qml+27 tests/mocks/QMenuModel/qmldir+2 tests/mocks/Ubuntu/Application/ApplicationManager.cpp+6−2 tests/mocks/Unity/CMakeLists.txt+1 tests/mocks/Unity/Notifications/CMakeLists.txt+11 tests/mocks/Unity/fake_categories.cpp+5−2 tests/mocks/Unity/fake_categories.h+2−1 tests/mocks/Unity/fake_scopes.cpp+2−2 tests/plugins/ListViewWithPageHeader/CMakeLists.txt+6−1 tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp+12 tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp+12 tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp+1−1 tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp+1−1 tests/plugins/Unity/Indicators/menucontentactivatortest.cpp+1−1 tests/plugins/Utils/qlimitproxymodeltest.cpp+1−1 tests/plugins/Utils/qsortfilterproxymodeltest.cpp+1−1 tests/qmltests/CMakeLists.txt+3 tests/qmltests/Components/tst_Stage.qml+1 tests/qmltests/Dash/tst_Dash.qml+4−4 tests/qmltests/Dash/tst_DashContent.qml+12−12 tests/qmltests/Panel/tst_IndicatorItem.qml+10−16 tests/qmltests/Panel/tst_IndicatorRow.qml+21−21 tests/qmltests/Panel/tst_MenuContent.qml+4−4 tests/qmltests/Panel/tst_Panel.qml+83−43 tests/qmltests/Panel/tst_SearchIndicator.qml+8−10 tests/qmltests/tst_Shell.qml+281 tests/utils/modules/Unity/Test/UnityTestCase.qml+22−18
Components/ApplicationManagerWrapper.qml+8−1
@@ -121,9 +121,16 @@ Item {
121121 desktopFile = "/usr/share/applications/share-app.desktop"
122122 } else if (favoriteApplication == ApplicationManager.BrowserApplication) {
123123 desktopFile = "/usr/share/applications/webbrowser-app.desktop"
124− } else if (favoriteApplication == 4) { // ApplicationManager.PhoneApplication
124+ } else if (favoriteApplication == ApplicationManager.PhoneApplication) {
125125 desktopFile = "/usr/share/applications/phone-app.desktop"
126+ } else if (favoriteApplication == ApplicationManager.DialerApplication) {
127+ desktopFile = "/usr/share/applications/dialer-app.desktop"
128+ } else if (favoriteApplication == ApplicationManager.MessagingApplication) {
129+ desktopFile = "/usr/share/applications/messaging-app.desktop"
130+ } else if (favoriteApplication == ApplicationManager.AddressbookApplication) {
131+ desktopFile = "/usr/share/applications/address-book-app.desktop"
126132 }
133+
127134 applicationManager.focusRequested(desktopFile);
128135 }
129136 }
Components/Stage.qml+14
@@ -39,6 +39,7 @@ Showable {
3939 type == ApplicationInfo.MainStage ? applicationManager.mainStageApplications
4040 : applicationManager.sideStageApplications
4141 property bool fullyShown: false
42+ property bool fullyHidden: true
4243 readonly property var focusedApplication:
4344 type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
4445 : applicationManager.sideStageFocusedApplication
@@ -249,6 +250,19 @@ Showable {
249250 }
250251 }
251252
253+ Connections {
254+ target: stage.applications
255+ onCountChanged: { __discardObsoleteScreenshots(); }
256+ ignoreUnknownSignals: true
257+ }
258+ onFullyHiddenChanged: { __discardObsoleteScreenshots(); }
259+ function __discardObsoleteScreenshots() {
260+ if (stage.fullyHidden && stage.applications && stage.applications.count == 0) {
261+ stage.focusedApplicationWhenUsingScreenshots = undefined;
262+ stage.__hideScreenshots();
263+ }
264+ }
265+
252266
253267 SequentialAnimation {
254268 id: showStartingApplicationAnimation
Dash/DashApps.qml+1−1
@@ -87,7 +87,7 @@ ScopeView {
8787 model: scopeView.scope.categories
8888
8989 delegate: Item {
90− Component.onCompleted: if (index == 3) scopeView.installedModel = Qt.binding(function() { return model.results; });
90+ Component.onCompleted: if (categoryId == "installed") scopeView.installedModel = Qt.binding(function() { return model.results; });
9191 }
9292 }
9393
Dash/DashHome.qml+1
@@ -26,6 +26,7 @@ import "Music"
2626
2727ScopeView {
2828 id: root
29+ objectName: "DashHome"
2930
3031 onMovementStarted: listView.showHeader()
3132
Dash/DashMusic.qml+2−2
@@ -52,7 +52,7 @@ ScopeView {
5252
5353 function getRenderer(categoryId) {
5454 switch (categoryId) {
55− case 1: return "Music/MusicCarousel.qml"
55+ case "featured": return "Music/MusicCarousel.qml"
5656 default: return "Music/MusicFilterGrid.qml"
5757 }
5858 }
@@ -69,7 +69,7 @@ ScopeView {
6969 id: base
7070 highlightWhenPressed: false
7171
72− property int categoryId: id
72+ property string categoryId: model.categoryId
7373
7474 Loader {
7575 anchors { top: parent.top; left: parent.left; right: parent.right }
Dash/DashVideos.qml+2−3
@@ -53,7 +53,7 @@ ScopeView {
5353
5454 function getRenderer(categoryId) {
5555 switch (categoryId) {
56− case 1: return "Video/VideosCarousel.qml"
56+ case "featured": return "Video/VideosCarousel.qml"
5757 default: return "Video/VideosFilterGrid.qml"
5858 }
5959 }
@@ -102,8 +102,7 @@ ScopeView {
102102 delegate: ListItems.Base {
103103 id: base
104104 highlightWhenPressed: false
105− property int categoryIndex: index
106− property int categoryId: id
105+ property string categoryId: model.categoryId
107106
108107 Loader {
109108 id: loader
Dash/GenericScopeView.qml+9
@@ -114,7 +114,16 @@ ScopeView {
114114 }
115115 }
116116
117+ function getDefaultRendererId(contentType) {
118+ switch (contentType) {
119+ default: return "grid";
120+ }
121+ }
122+
117123 function getRenderer(rendererId, contentType) {
124+ if (rendererId == "default") {
125+ rendererId = getDefaultRendererId(contentType);
126+ }
118127 switch (rendererId) {
119128 case "grid": {
120129 switch (contentType) {
Panel/IndicatorItem.qml+5
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity.Indicators 0.1 as Indicators
20+import "../Components"
2021
2122Item {
2223 id: indicatorItem
@@ -27,6 +28,7 @@ Item {
2728 property var indicatorProperties: undefined
2829
2930 opacity: dimmed ? 0.4 : 1
31+ Behavior on opacity { StandardAnimation {} }
3032
3133 // only visible when non-empty
3234 visible: loader.item != undefined && loader.status == Loader.Ready ? loader.item.enabled : false
@@ -53,5 +55,8 @@ Item {
5355 width: parent.width
5456 anchors.top: parent.bottom
5557 visible: highlighted
58+
59+ opacity: highlighted ? 1 : 0
60+ Behavior on opacity { StandardAnimation {} }
5661 }
5762}
Panel/IndicatorRow.qml+34−48
@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import "../Components"
1920
2021Item {
2122 id: indicatorRow
@@ -25,21 +26,18 @@ Item {
2526 property alias row: row
2627 property QtObject indicatorsModel: null
2728
28− Behavior on y {NumberAnimation {duration: 300; easing.type: Easing.OutCubic} }
29−
3029 width: units.gu(40)
3130 height: units.gu(3)
3231
33− Component.onCompleted: setDefaultItem()
34−
3532 function setDefaultItem() {
3633 // The leftmost indicator
37− var defaultItemIndex = 0
38− setItem(defaultItemIndex)
34+ setCurrentItem(0);
3935 }
4036
41− function setItem(index) {
42− currentItem = rowRepeater.itemAt(index)
37+ function setCurrentItem(index) {
38+ if (currentItemIndex !== index) {
39+ currentItem = rowRepeater.itemAt(index);
40+ }
4341 }
4442
4543 Row {
@@ -54,51 +52,39 @@ Item {
5452 objectName: "rowRepeater"
5553 model: indicatorsModel ? indicatorsModel : undefined
5654
57− IndicatorItem {
58− id: indicatorItem
55+ Item {
56+ id: itemWrapper
57+ height: indicatorRow.height
58+ width: childrenRect.width
5959
60− property int ownIndex: index
60+ property int ownIndex: index
61+ property alias highlighted: indicatorItem.highlighted
62+ property alias dimmed: indicatorItem.dimmed
6163
62− widgetSource: model.widgetSource
64+ IndicatorItem {
65+ id: indicatorItem
66+ height: parent.height
67+
68+ highlighted: indicatorRow.state != "initial" ? itemWrapper.ownIndex == indicatorRow.currentItemIndex : false
69+ dimmed: indicatorRow.state != "initial" ? itemWrapper.ownIndex != indicatorRow.currentItemIndex : false
70+
71+ widgetSource: model.widgetSource
72+ indicatorProperties : model.indicatorProperties
73+ }
6374
64− indicatorProperties : model.indicatorProperties
65− highlighted: indicatorRow.state == "reveal" || indicatorRow.state == "locked" || indicatorRow.state == "commit" ? ownIndex == indicatorRow.currentItemIndex : false
66− dimmed: { //See FIXME in Indicators regarding the "states" change
67− if (indicatorRow.state == "initial" || indicatorRow.state == "") {
68− return false;
69− } else if (indicatorRow.state == "hint") {
70− return true
71− } else {
72− return ownIndex != indicatorRow.currentItemIndex
73− }
74− }
75− height: indicatorRow.height
76− y: {
77− //FIXME: all indicators will be initial for now.
78− if (!highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")) {
79− return -indicatorRow.height
80− } else {
81− return 0
82− }
83− }
84− Behavior on y {
85− NumberAnimation{
86− duration: {
87− if (index == 0) {
88− return 250
89− } else if (index == 1) {
90− return 150
91− } else if (index == 2) {
92− return 550
93− } else if (index == 3) {
94− return 400
95− } else {
96− return 100 + Math.random() * 200
97− }
98− }
99− easing.type: Easing.OutCubic
75+ opacity: {
76+ if (!indicatorItem.highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")) {
77+ return 0.0;
78+ } else {
79+ return 1.0;
10080 }
10181 }
82+ Behavior on opacity {
83+ StandardAnimation {
84+ // flow away from current index
85+ duration: (rowRepeater.count - Math.abs(indicatorRow.currentItemIndex - index)) * (500/rowRepeater.count)
86+ }
87+ }
10288 }
10389 }
10490 }
Panel/Indicators.qml+89−102
@@ -25,16 +25,15 @@ import "../Components/Math.js" as MathLocal
2525Showable {
2626 id: indicators
2727
28− property int referenceOpenedHeight: units.gu(71)
29− property real openedHeight: pinnedMode ? referenceOpenedHeight - panelHeight
30− : referenceOpenedHeight
28+ property real openedHeight: units.gu(71)
3129 property int panelHeight: units.gu(3)
3230 property bool pinnedMode: true //should be set true if indicators menu can cover whole screen
3331
3432 property int hintValue
35− readonly property int lockThreshold: referenceOpenedHeight / 2
33+ readonly property int lockThreshold: openedHeight / 2
3634 property bool fullyOpened: height == openedHeight
3735 property bool partiallyOpened: height > panelHeight && !fullyOpened
36+ property real visualBottom: Math.max(y+height, y+indicatorRow.y+indicatorRow.height)
3837
3938 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
4039 // use its own values. Need to ask design about this.
@@ -43,6 +42,13 @@ Showable {
4342 duration: 350
4443 to: openedHeight
4544 easing.type: Easing.OutCubic
45+
46+ // Re-size if we've changed the openHeight while shown.
47+ onToChanged: {
48+ if (indicators.shown) {
49+ indicators.show();
50+ }
51+ }
4652 }
4753
4854 hideAnimation: StandardAnimation {
@@ -56,28 +62,30 @@ Showable {
5662
5763 onHeightChanged: {
5864 // need to use handle.get_height(). As the handle height depends on indicators.height changes (but this is called first!)
59− var contentProgress = indicators.height - handle.get_height();
65+ var revealProgress = indicators.height - panelHeight
6066 if (!showAnimation.running && !hideAnimation.running) {
61− if (contentProgress <= hintValue && indicators.state == "reveal") {
67+ if (revealProgress == 0) {
68+ indicators.state = "initial";
69+ } else if (revealProgress <= hintValue) {
6270 indicators.state = "hint";
63− } else if (contentProgress > hintValue && contentProgress < lockThreshold) {
71+ } else if (revealProgress > hintValue && revealProgress < lockThreshold) {
6472 indicators.state = "reveal";
65− } else if (contentProgress >= lockThreshold && lockThreshold > 0) {
73+ } else if (revealProgress >= lockThreshold && lockThreshold > 0) {
6674 indicators.state = "locked";
6775 }
6876 }
6977
70− if (contentProgress == 0) {
78+ if (revealProgress == 0) {
7179 menuContent.releaseContent();
7280 }
7381 }
7482
7583 function calculateCurrentItem(xValue, useBuffer) {
76− var rowCoordinates
77− var itemCoordinates
78− var currentItem
79− var distanceFromRightEdge
80− var bufferExceeded = false
84+ var rowCoordinates;
85+ var itemCoordinates;
86+ var currentItem;
87+ var distanceFromRightEdge;
88+ var bufferExceeded = false;
8189
8290 if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
8391
@@ -90,6 +98,14 @@ Showable {
9098 MathLocal.clamp((indicators.height - handle.height - hintValue) /
9199 (lockThreshold - hintValue), 0, 1);
92100
101+ /*
102+ Vertical velocity check. Don't change the indicator if we're moving too quickly.
103+ */
104+ var verticalSpeed = Math.abs(yVelocityCalculator.calculate());
105+ if (verticalSpeed >= 0.05 && indicatorRow.currentItem!=null) {
106+ return;
107+ }
108+
93109 /*
94110 Percentage of an indicator icon's width the user's press can stray horizontally from the
95111 focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
@@ -110,7 +126,7 @@ Showable {
110126 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
111127 if (currentItem && currentItem != indicatorRow.currentItem ) {
112128 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
113− distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width)
129+ distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
114130 if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
115131 bufferExceeded = true;
116132 } else {
@@ -123,6 +139,8 @@ Showable {
123139 if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null) {
124140 indicatorRow.currentItem = currentItem;
125141 }
142+ } else if (!currentItem) {
143+ indicatorRow.setDefaultItem();
126144 }
127145 }
128146
@@ -155,13 +173,20 @@ Showable {
155173 left: parent.left
156174 right: parent.right
157175 top: indicatorRow.bottom
158− bottom: parent.bottom
176+ bottom: handle.top
159177 }
160178 indicatorsModel: indicatorsModel
161179 clip: !indicators.fullyOpened
162180
163− onMenuSelected: {
164− indicatorRow.setItem(index)
181+ Connections {
182+ target: indicatorRow
183+ onCurrentItemIndexChanged: menuContent.updateCurrentMenu();
184+ }
185+
186+ function updateCurrentMenu() {
187+ if (currentMenuIndex != indicatorRow.currentItemIndex) {
188+ currentMenuIndex = indicatorRow.currentItemIndex;
189+ }
165190 }
166191
167192 //small shadow gradient at bottom of menu
@@ -188,7 +213,7 @@ Showable {
188213 anchors {
189214 left: parent.left
190215 right: parent.right
191− top: menuContent.bottom
216+ bottom: parent.bottom
192217 }
193218 height: get_height()
194219 clip: height < handleImage.height
@@ -212,33 +237,14 @@ Showable {
212237 }
213238 }
214239
215− PanelSeparatorLine {
216− id: indicatorsSeparatorLine
217− visible: true
218− anchors {
219− top: handle.bottom
220− left: indicators.left
221− right: indicators.right
222− }
223− }
224−
225− BorderImage {
226− id: dropShadow
227− anchors {
228− top: indicators.top
229− bottom: indicatorsSeparatorLine.bottom
230− left: indicators.left
231− right: indicators.right
232− margins: -units.gu(1)
233− }
234− visible: indicators.height > panelHeight
235− source: "graphics/rectangular_dropshadow.sci"
236− }
237−
238240 PanelBackground {
239241 anchors.fill: indicatorRow
240242 }
241243
244+ IndicatorsDataModel {
245+ id: indicatorsModel
246+ }
247+
242248 IndicatorRow {
243249 id: indicatorRow
244250 objectName: "indicatorRow"
@@ -250,58 +256,45 @@ Showable {
250256 indicatorsModel: indicatorsModel
251257 state: indicators.state
252258
253− onCurrentItemIndexChanged: menuContent.currentIndex = currentItemIndex
254−
255− }
256−
257− IndicatorsDataModel {
258− id: indicatorsModel
259− }
260−
261− Connections {
262− target: hideAnimation
263− onRunningChanged: {
264− if (hideAnimation.running) {
265− indicators.state = "initial"
266− } else {
267− if (state == "initial") {
268− indicatorRow.setDefaultItem();
269− menuContent.releaseContent();
270− }
259+ Connections {
260+ target: menuContent
261+ onCurrentMenuIndexChanged: {
262+ indicatorRow.setCurrentItem(menuContent.currentMenuIndex);
271263 }
272264 }
273265 }
266+
274267 Connections {
275268 target: showAnimation
276269 onRunningChanged: {
277270 if (showAnimation.running) {
278− indicators.calculateCurrentItem(dragHandle.touchX, false);
279271 indicators.state = "commit";
280272 }
281273 }
282274 }
283275
284276 Connections {
285− target: dragHandle
286− onTouchXChanged: {
287− var buffer = dragHandle.dragging ? true : false;
288− indicators.calculateCurrentItem(dragHandle.touchX, buffer);
277+ target: hideAnimation
278+ onRunningChanged: {
279+ if (hideAnimation.running) {
280+ indicators.state = "initial";
281+ indicatorRow.currentItem = null;
282+ }
289283 }
290284 }
291285
292− // We start with pinned states. (default pinnedMode: true)
293− states: pinnedModeStates
294− // because of dynamic assignment of states, we need to assign state after completion.
295− Component.onCompleted: state = "initial"
296−
297− // changing states will reset state to "".
298− onPinnedModeChanged: {
299− var last_state = state;
300− states = (pinnedMode) ? pinnedModeStates : offScreenModeStates;
301− state = last_state;
286+ property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
287+ // connections to the active drag handle
288+ Connections {
289+ target: activeDragHandle
290+ onTouchXChanged: {
291+ indicators.calculateCurrentItem(activeDragHandle.touchX, true);
292+ }
293+ onTouchSceneYChanged: {
294+ yVelocityCalculator.trackedPosition = activeDragHandle.touchSceneY;
295+ }
302296 }
303297
304− property var dragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle
305298 DragHandle {
306299 id: showDragHandle
307300 anchors.bottom: parent.bottom
@@ -315,7 +308,7 @@ Showable {
315308 hintDisplacement: pinnedMode ? indicators.hintValue : 0
316309 autoCompleteDragThreshold: maxTotalDragDistance / 2
317310 stretch: true
318− maxTotalDragDistance: openedHeight - handle.height
311+ maxTotalDragDistance: openedHeight - panelHeight
319312 distanceThreshold: pinnedMode ? 0 : units.gu(3)
320313
321314 onStatusChanged: {
@@ -327,28 +320,40 @@ Showable {
327320 DragHandle {
328321 id: hideDragHandle
329322 anchors.fill: handle
330− height: panelHeight
331323 direction: Direction.Upwards
332324 enabled: indicators.shown
333− hintDisplacement: units.gu(2)
325+ hintDisplacement: indicators.hintValue
334326 autoCompleteDragThreshold: maxTotalDragDistance / 6
335327 stretch: true
336− maxTotalDragDistance: referenceOpenedHeight - 2*panelHeight
328+ maxTotalDragDistance: openedHeight - panelHeight
337329 distanceThreshold: 0
338330 }
339331
340− property list<State> offScreenModeStates: [
332+ AxisVelocityCalculator {
333+ id: yVelocityCalculator
334+ }
335+
336+ states: [
341337 State {
342338 name: "initial"
343339 },
344340 State {
345341 name: "hint"
346− PropertyChanges { target: indicatorRow; y: panelHeight }
342+ PropertyChanges {
343+ target: indicatorRow;
344+ y: pinnedMode ? 0 : panelHeight
345+ }
346+ StateChangeScript {
347+ script: {
348+ if (activeDragHandle) {
349+ calculateCurrentItem(activeDragHandle.touchX, false);
350+ }
351+ }
352+ }
347353 },
348354 State {
349355 name: "reveal"
350356 extend: "hint"
351− StateChangeScript { script: calculateCurrentItem(dragHandle.touchX, false); }
352357 },
353358 State {
354359 name: "locked"
@@ -359,25 +364,7 @@ Showable {
359364 extend: "hint"
360365 }
361366 ]
362−
363− property list<State> pinnedModeStates: [
364− State {
365− name: "initial"
366− },
367− State {
368− name: "hint"
369− },
370− State {
371− name: "reveal"
372− StateChangeScript { script: calculateCurrentItem(dragHandle.touchX, false); }
373− },
374− State {
375− name: "locked"
376− },
377− State {
378− name: "commit"
379− }
380− ]
367+ state: "initial"
381368
382369 transitions: [
383370 Transition {
Panel/MenuContent.qml+2−2
@@ -24,7 +24,7 @@ Rectangle {
2424
2525 property QtObject indicatorsModel: null
2626 property bool __contentActive: false
27− property alias currentIndex : menus.currentIndex
27+ property alias currentMenuIndex : menus.currentIndex
2828 property color backgroundColor: "#221e1c" // FIXME not in palette yet
2929 property int contentReleaseInterval: 20000
3030
@@ -146,7 +146,7 @@ Rectangle {
146146 Indicators.MenuContentActivator {
147147 id: menuActivator
148148 running: content.__contentActive
149− baseIndex: content.currentIndex
149+ baseIndex: content.currentMenuIndex
150150 count: indicatorsModel.count
151151 }
152152}
Panel/Panel.qml+28−9
@@ -39,11 +39,8 @@ Item {
3939 }
4040 height: __panelMinusSeparatorLineHeight
4141 y: 0
42− onYChanged: indicatorsMenu.y = y;
4342
44− Behavior on y {
45− NumberAnimation { duration: 200 }
46− }
43+ Behavior on y { StandardAnimation {} }
4744 }
4845
4946 PanelSeparatorLine {
@@ -78,18 +75,40 @@ Item {
7875 id: indicatorsMenu
7976
8077 anchors.right: parent.right
81− y: 0
78+ y: panelBackground.y
8279 width: root.indicatorsMenuWidth
8380 shown: false
8481 hintValue: __panelMinusSeparatorLineHeight * 3
8582 panelHeight: __panelMinusSeparatorLineHeight
86− referenceOpenedHeight: parent.height
87−
83+ openedHeight: parent.height + (pinnedMode ? 0 : root.panelHeight)
8884 pinnedMode: !fullscreenMode
8985
9086 property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
9187 }
9288
89+ PanelSeparatorLine {
90+ id: indicatorsSeparatorLine
91+ visible: true
92+ anchors {
93+ left: indicatorsMenu.left
94+ right: indicatorsMenu.right
95+ }
96+ y: indicatorsMenu.visualBottom
97+ }
98+
99+ BorderImage {
100+ id: dropShadow
101+ anchors {
102+ top: indicators.top
103+ bottom: indicatorsSeparatorLine.bottom
104+ left: indicators.left
105+ right: indicators.right
106+ margins: -units.gu(1)
107+ }
108+ visible: indicatorsMenu.height > indicatorsMenu.panelHeight
109+ source: "graphics/rectangular_dropshadow.sci"
110+ }
111+
93112 SearchIndicator {
94113 id: search
95114 objectName: "search"
@@ -98,14 +117,14 @@ Item {
98117 state: {
99118 if (parent.width < indicatorsMenu.width + width) {
100119 if (indicatorsMenu.state != "initial") {
101− return "hiddenUp";
120+ return "hidden";
102121 }
103122 }
104123 if (root.searchVisible) {
105124 return "visible";
106125 }
107126
108− return "hiddenUp";
127+ return "hidden";
109128 }
110129
111130 width: units.gu(13)
Panel/SearchIndicator.qml+3−5
@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import "../Components"
1920
2021Item {
2122 id: search
@@ -40,8 +41,7 @@ Item {
4041 width: parent.width
4142 height: parent.height
4243
43− Behavior on opacity {NumberAnimation {}}
44− Behavior on y {NumberAnimation {duration: 300; easing.type: Easing.OutCubic}}
44+ Behavior on opacity { StandardAnimation {} }
4545
4646 Image {
4747 id: icon
@@ -67,15 +67,13 @@ Item {
6767 name: "visible"
6868 PropertyChanges {
6969 target: container
70− y: 0
7170 opacity: 1
7271 }
7372 },
7473 State {
75− name: "hiddenUp"
74+ name: "hidden"
7675 PropertyChanges {
7776 target: container
78− y: -height
7977 opacity: 0
8078 }
8179 }
Shell.qml+25−9
@@ -36,8 +36,8 @@ FocusScope {
3636
3737 // this is only here to select the width / height of the window if not running fullscreen
3838 property bool tablet: false
39− width: tablet ? units.gu(160) : ApplicationArguments.hasGeometry() ? ApplicationArguments.width() : units.gu(40)
40− height: tablet ? units.gu(100) : ApplicationArguments.hasGeometry() ? ApplicationArguments.height() : units.gu(71)
39+ width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
40+ height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
4141
4242 property real edgeSize: units.gu(2)
4343 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
@@ -129,11 +129,20 @@ FocusScope {
129129
130130 Item {
131131 id: underlay
132+ objectName: "underlay"
132133 anchors.fill: parent
133− visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
134− && (stages.fullyHidden
135− || (stages.fullyShown && mainStage.usingScreenshots)
136− || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
134+
135+ // Whether the underlay is fully covered by opaque UI elements.
136+ property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
137+
138+ // Whether the user should see the topmost application surface (if there's one at all).
139+ property bool applicationSurfaceShouldBeSeen:
140+ (mainStage.applications && mainStage.applications.count > 0)
141+ && (!stages.fullyHidden && !mainStage.usingScreenshots)
142+
143+ // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
144+ // through the translucent parts of the shell surface.
145+ visible: !fullyCovered && !applicationSurfaceShouldBeSeen
137146
138147 Image {
139148 id: backgroundImage
@@ -151,6 +160,7 @@ FocusScope {
151160
152161 Dash {
153162 id: dash
163+ objectName: "dash"
154164
155165 available: !greeter.shown && !lockscreen.shown
156166 hides: [stages, launcher, panel.indicators]
@@ -178,7 +188,7 @@ FocusScope {
178188 }
179189 }
180190
181− // FIXME: only necessary because stagesRevealer.animatedProgress and
191+ // FIXME: only necessary because stagesOuterContainer.showProgress and
182192 // greeterRevealer.animatedProgress are not animated
183193 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
184194 }
@@ -198,6 +208,7 @@ FocusScope {
198208
199209 Showable {
200210 id: stages
211+ objectName: "stages"
201212
202213 x: width
203214
@@ -254,6 +265,7 @@ FocusScope {
254265
255266 anchors.fill: parent
256267 fullyShown: stages.fullyShown
268+ fullyHidden: stages.fullyHidden
257269 shouldUseScreenshots: !fullyShown
258270 rightEdgeEnabled: !sideStage.enabled
259271
@@ -338,7 +350,7 @@ FocusScope {
338350 }
339351
340352 DragHandle {
341− id: stagesRevealer
353+ id: stagesDragHandle
342354
343355 anchors.top: parent.top
344356 anchors.bottom: parent.bottom
@@ -346,7 +358,11 @@ FocusScope {
346358
347359 width: shell.edgeSize
348360 direction: Direction.Leftwards
349− enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
361+ property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
362+
363+ maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
364+ // Make autocompletion impossible when !haveApps
365+ edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
350366 }
351367 }
352368 }
debian/changelog+97
@@ -1,3 +1,100 @@
1+unity8 (7.81.3+13.10.20130730ubuntu.unity.next-0ubuntu1) saucy; urgency=low
2+
3+ [ Michał Sawicz ]
4+ * Adapt to Qt 5.1.
5+
6+ [ Albert Astals ]
7+ * Adapt to Qt 5.1.
8+
9+ [ Ubuntu daily release ]
10+ * Automatic snapshot from revision 143 (ubuntu-unity/next)
11+
12+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 30 Jul 2013 02:33:12 +0000
13+
14+unity8 (7.81.3+13.10.20130729ubuntu.unity.next-0ubuntu1) saucy; urgency=low
15+
16+ [ Michal Hruby ]
17+ * Remove the variant conversions methods as they were moved to dee-qt.
18+
19+ [ Michał Sawicz ]
20+ * Re-enable passphrase tests under UInput.
21+ * Use the new string categoryIds in custom video and music scope
22+ views. (LP: #1199322)
23+
24+ [ Nick Dedekind ]
25+ * Behavioural changes for indicators - Part 1 - Use standard
26+ animations. - Search bar animation less distracting. - Hinting
27+ animation shows header. - Vertical velocity detector to reduce
28+ accidental menu switches in dragging phase.
29+
30+ [ Gustavo Pichorim Boiko ]
31+ * Add entries to the new applications resulted from the split of the
32+ phone-app.
33+
34+ [ Ubuntu daily release ]
35+ * Automatic snapshot from revision 141 (ubuntu-unity/next)
36+
37+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 29 Jul 2013 03:41:07 +0000
38+
39+unity8 (7.81.3+13.10.20130726ubuntu.unity.next-0ubuntu1) saucy; urgency=low
40+
41+ [ Michal Hruby ]
42+ * Expose real category ids and not just indices.
43+ * Provide fallbacks for default renderer.
44+
45+ [ Daniel d'Andrada ]
46+ * Give a visual feedback on right-edge drag with no running apps (LP:
47+ #1116207). (LP: #1116207)
48+
49+ [ Albert Astals ]
50+ * Update m_firstVisibleIndex if there's no visible items anymore Also
51+ remove the check for m_visibleItems.isEmpty() on insert, the
52+ m_firstVisibleIndex == 0 already protects us against that.
53+
54+ [ Christopher Lee ]
55+ * Tests now use a default lightdm mock if not decorated. (LP:
56+ #1204772)
57+
58+ [ Ubuntu daily release ]
59+ * Automatic snapshot from revision 135 (ubuntu-unity/next)
60+
61+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 26 Jul 2013 07:06:08 +0000
62+
63+unity8 (7.81.3+13.10.20130725ubuntu.unity.next-0ubuntu1) saucy; urgency=low
64+
65+ [ Michal Hruby ]
66+ * Fix adding items into an empty LVWPH.
67+
68+ [ Michał Sawicz ]
69+ * Move to using upstart in run_on_device.
70+ * Added heeding and qmltest for button-tint hint.
71+ * Refactoring and cleanup of the Unity8 Autopilot tests.
72+ * Hide the Unity launcher during autopiloting and skip battery tests
73+ if unavailable.
74+
75+ [ Ying-Chun Liu ]
76+ * Let GenericScope support loading different renderers.
77+
78+ [ Nick Dedekind ]
79+ * Removed overview from indicators.
80+ * Menus for indicators is now created prioritised by distance from
81+ current item to speed up user experience.
82+ * Add ApplicationArguments to know the geometry from start.
83+
84+ [ Mirco Müller ]
85+ * Added heeding and qmltest for button-tint hint.
86+
87+ [ Christopher Lee ]
88+ * Refactoring and cleanup of the Unity8 Autopilot tests.
89+
90+ [ Thomi Richards ]
91+ * Refactoring and cleanup of the Unity8 Autopilot tests.
92+
93+ [ Ubuntu daily release ]
94+ * Automatic snapshot from revision 129 (ubuntu-unity/next)
95+
96+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 25 Jul 2013 03:02:12 +0000
97+
198unity8 (7.81.3+13.10.20130718ubuntu.unity.next-0ubuntu1) saucy; urgency=low
299
3100 [ Andrea Cimitan ]
debian/control+3−1
@@ -4,7 +4,7 @@ Priority: optional
44Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
55Build-Depends: cmake,
66 debhelper (>= 9),
7− libdee-qt5-dev (>= 3.0),
7+ libdee-qt5-dev (>= 3.2),
88 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
99 libgl1-mesa-dri,
1010 libgles2-mesa-dev[armhf],
@@ -87,6 +87,8 @@ Depends: indicators-client (>= ${source:Version}),
8787 ${misc:Depends},
8888 ${python:Depends},
8989 ${shlibs:Depends},
90+Recommends: gir1.2-glib-2.0,
91+ python-gi,
9092Description: Test package for Unity 8 shell
9193 Autopilot tests for the unity8 package
9294
main.cpp+1−1
@@ -113,7 +113,7 @@ int main(int argc, char** argv)
113113 view->setResizeMode(QQuickView::SizeRootObjectToView);
114114 view->setTitle("Qml Phone Shell");
115115 view->engine()->setBaseUrl(QUrl::fromLocalFile(::shellAppDirectory()));
116− view->rootContext()->setContextProperty("ApplicationArguments", &qmlArgs);
116+ view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
117117 if (args.contains(QLatin1String("-frameless"))) {
118118 view->setFlags(Qt::FramelessWindowHint);
119119 }
plugins/ListViewWithPageHeader/CMakeLists.txt+5−1
@@ -4,7 +4,11 @@ include(Plugins)
44# There's no cmake var for v8 include path :-/
55# so create one
66LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
7−SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
7+if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.1.0")
8+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
9+else()
10+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
11+endif()
812
913include_directories(
1014 ${CMAKE_CURRENT_SOURCE_DIR}
plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+71−5
@@ -96,7 +96,11 @@
9696#include <qqmlengine.h>
9797#pragma GCC diagnostic push
9898#pragma GCC diagnostic ignored "-pedantic"
99+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
99100#include <private/qquickvisualdatamodel_p.h>
101+#else
102+#include <private/qqmldelegatemodel_p.h>
103+#endif
100104#include <private/qqmlglobal_p.h>
101105#include <private/qquickitem_p.h>
102106#include <private/qquickanimation_p.h>
@@ -183,10 +187,17 @@ void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
183187 if (!m_delegateModel) {
184188 createDelegateModel();
185189 } else {
190+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
186191 disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
187192 }
188193 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
189194 connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
195+#else
196+ disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
197+ }
198+ m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
199+ connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
200+#endif
190201 Q_EMIT modelChanged();
191202 // TODO?
192203// Q_EMIT contentHeightChanged();
@@ -446,8 +457,13 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
446457
447458void ListViewWithPageHeader::createDelegateModel()
448459{
460+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
449461 m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
450462 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
463+#else
464+ m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
465+ connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
466+#endif
451467 if (isComponentComplete())
452468 m_delegateModel->componentComplete();
453469 updateWatchedRoles();
@@ -528,8 +544,13 @@ bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool
528544void ListViewWithPageHeader::reallyReleaseItem(ListItem *listItem)
529545{
530546 QQuickItem *item = listItem->m_item;
547+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
531548 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
532549 if (flags & QQuickVisualModel::Destroyed) {
550+#else
551+ QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
552+ if (flags & QQmlDelegateModel::Destroyed) {
553+#endif
533554 item->setParentItem(nullptr);
534555 }
535556 delete listItem->m_sectionItem;
@@ -658,9 +679,27 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
658679 return nullptr;
659680
660681 m_asyncRequestedIndex = -1;
682+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
661683 QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
684+#else
685+ QObject* object = m_delegateModel->object(modelIndex, asynchronous);
686+ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
687+#endif
662688 if (!item) {
689+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
663690 m_asyncRequestedIndex = modelIndex;
691+#else
692+ if (object) {
693+ m_delegateModel->release(object);
694+ if (!m_delegateValidated) {
695+ m_delegateValidated = true;
696+ QObject* delegateObj = delegate();
697+ qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
698+ }
699+ } else {
700+ m_asyncRequestedIndex = modelIndex;
701+ }
702+#endif
664703 return 0;
665704 } else {
666705// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
@@ -711,8 +750,18 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
711750 }
712751}
713752
753+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
714754void ListViewWithPageHeader::itemCreated(int modelIndex, QQuickItem *item)
715755{
756+#else
757+void ListViewWithPageHeader::itemCreated(int modelIndex, QObject *object)
758+{
759+ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
760+ if (!item) {
761+ qWarning() << "ListViewWithPageHeader::itemCreated got a non item for index" << modelIndex;
762+ return;
763+ }
764+#endif
716765// qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
717766
718767 item->setParentItem(m_clipItem);
@@ -747,13 +796,22 @@ void ListViewWithPageHeader::onHeightChanged()
747796 polish();
748797}
749798
799+
800+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
750801void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bool /*reset*/)
802+#else
803+void ListViewWithPageHeader::onModelUpdated(const QQmlChangeSet &changeSet, bool /*reset*/)
804+#endif
751805{
752806 // TODO Do something with reset
753807// qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
754808 const auto oldFirstVisibleIndex = m_firstVisibleIndex;
755809
810+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
756811 Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
812+#else
813+ Q_FOREACH(const QQmlChangeSet::Remove &remove, changeSet.removes()) {
814+#endif
757815// qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove.index << remove.count;
758816 if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
759817 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
@@ -785,10 +843,14 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
785843 }
786844 if (growDown) {
787845 adjustMinYExtent();
788− } else if (remove.index <= m_firstVisibleIndex && !m_visibleItems.isEmpty()) {
789− // We removed the first item that is the one that positions the rest
790− // position the new first item correctly
791− m_visibleItems.first()->setY(oldFirstValidIndexPos);
846+ } else if (remove.index <= m_firstVisibleIndex) {
847+ if (!m_visibleItems.isEmpty()) {
848+ // We removed the first item that is the one that positions the rest
849+ // position the new first item correctly
850+ m_visibleItems.first()->setY(oldFirstValidIndexPos);
851+ } else {
852+ m_firstVisibleIndex = -1;
853+ }
792854 }
793855 } else if (remove.index + remove.count <= m_firstVisibleIndex) {
794856 m_firstVisibleIndex -= remove.count;
@@ -803,10 +865,14 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
803865 }
804866 }
805867
868+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
806869 Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
870+#else
871+ Q_FOREACH(const QQmlChangeSet::Insert &insert, changeSet.inserts()) {
872+#endif
807873// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
808874 const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
809− const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && !m_visibleItems.isEmpty() && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
875+ const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
810876 if (insertingInValidIndexes || firstItemWithViewOnTop)
811877 {
812878 // If the items we are adding won't be really visible
plugins/ListViewWithPageHeader/listviewwithpageheader.h+18−1
@@ -24,9 +24,14 @@
2424#pragma GCC diagnostic pop
2525
2626class QAbstractItemModel;
27−class QQuickChangeSet;
2827class QQuickNumberAnimation;
28+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
29+class QQuickChangeSet;
2930class QQuickVisualDataModel;
31+#else
32+class QQmlChangeSet;
33+class QQmlDelegateModel;
34+#endif
3035
3136
3237/**
@@ -100,11 +105,19 @@ protected:
100105 void updatePolish();
101106
102107private Q_SLOTS:
108+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
103109 void itemCreated(int modelIndex, QQuickItem *item);
110+#else
111+ void itemCreated(int modelIndex, QObject *object);
112+#endif
104113 void onContentHeightChanged();
105114 void onContentWidthChanged();
106115 void onHeightChanged();
116+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
107117 void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
118+#else
119+ void onModelUpdated(const QQmlChangeSet &changeSet, bool reset);
120+#endif
108121 void onShowHeaderAnimationFinished();
109122
110123private:
@@ -141,7 +154,11 @@ private:
141154 QQuickItem *getSectionItem(int modelIndex, bool alreadyInserted);
142155 QQuickItem *getSectionItem(const QString &sectionText);
143156
157+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
144158 QQuickVisualDataModel *m_delegateModel;
159+#else
160+ QQmlDelegateModel *m_delegateModel;
161+#endif
145162
146163 // Index we are waiting because we requested it asynchronously
147164 int m_asyncRequestedIndex;
plugins/Unity/CMakeLists.txt+1−1
@@ -5,7 +5,7 @@ add_subdirectory(Indicators)
55
66# Dependencies
77include(FindPkgConfig)
8−pkg_check_modules(QTDEE REQUIRED libdee-qt5)
8+pkg_check_modules(QTDEE REQUIRED libdee-qt5>=3.2)
99pkg_check_modules(UNITYCORE REQUIRED unity-core-6.0)
1010
1111include_directories(
plugins/Unity/categories.cpp+6−3
@@ -34,7 +34,7 @@ enum CategoryColumn {
3434Categories::Categories(QObject* parent)
3535 : DeeListModel(parent)
3636{
37− m_roles[Categories::RoleId] = "id";
37+ m_roles[Categories::RoleCategoryId] = "categoryId";
3838 m_roles[Categories::RoleName] = "name";
3939 m_roles[Categories::RoleIcon] = "icon";
4040 m_roles[Categories::RoleRenderer] = "renderer";
@@ -42,6 +42,7 @@ Categories::Categories(QObject* parent)
4242 m_roles[Categories::RoleHints] = "hints";
4343 m_roles[Categories::RoleResults] = "results";
4444 m_roles[Categories::RoleCount] = "count";
45+ m_roles[Categories::RoleCategoryIndex] = "categoryIndex";
4546
4647 // TODO This should not be needed but accumulatting the count changes
4748 // makes the visualization more stable and also makes crashes on fast
@@ -130,8 +131,8 @@ Categories::data(const QModelIndex& index, int role) const
130131 }
131132
132133 switch (role) {
133− case RoleId:
134− return QVariant::fromValue(index.row());
134+ case RoleCategoryId:
135+ return DeeListModel::data(index, CategoryColumn::ID);
135136 case RoleName:
136137 return DeeListModel::data(index, CategoryColumn::DISPLAY_NAME);
137138 case RoleIcon:
@@ -149,6 +150,8 @@ Categories::data(const QModelIndex& index, int role) const
149150 return QVariant::fromValue(getResults(index.row()));
150151 case RoleCount:
151152 return QVariant::fromValue(getResults(index.row())->rowCount());
153+ case RoleCategoryIndex:
154+ return QVariant::fromValue(index.row());
152155 default:
153156 return QVariant();
154157 }
plugins/Unity/categories.h+3−2
@@ -42,14 +42,15 @@ public:
4242 explicit Categories(QObject* parent = 0);
4343
4444 enum Roles {
45− RoleId,
45+ RoleCategoryId,
4646 RoleName,
4747 RoleIcon,
4848 RoleRenderer,
4949 RoleContentType,
5050 RoleHints,
5151 RoleResults,
52− RoleCount
52+ RoleCount,
53+ RoleCategoryIndex
5354 };
5455
5556 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
plugins/Unity/previewinfohint.cpp+2−2
@@ -17,9 +17,9 @@
1717 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1818 */
1919
20+#include <deelistmodel.h>
2021// local
2122#include "previewinfohint.h"
22−#include "variantutils.h"
2323
2424PreviewInfoHint::PreviewInfoHint(QObject *parent):
2525 QObject(parent),
@@ -61,7 +61,7 @@ QString PreviewInfoHint::iconHint() const
6161QVariant PreviewInfoHint::value() const
6262{
6363 if (m_unityInfoHint) {
64− return QVariantFromGVariant(m_unityInfoHint->value);
64+ return DeeListModel::VariantForData(m_unityInfoHint->value);
6565 }
6666 return QVariant();
6767}
plugins/Unity/variantutils.cpp+2−118
@@ -17,131 +17,15 @@
1717 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1818 */
1919
20+#include <deelistmodel.h>
2021#include "variantutils.h"
2122
22−/* Returns floating ref */
23−GVariant* GVariantFromQVariant(const QVariant &var)
24−{
25− GVariant *gv = nullptr;
26−
27− switch (var.type()) {
28− case QVariant::String:
29− gv = g_variant_new_string(var.toString().toUtf8());
30− break;
31− case QVariant::UInt:
32− gv = g_variant_new_uint32(var.toUInt());
33− break;
34− case QVariant::Int:
35− gv = g_variant_new_int32(var.toInt());
36− break;
37− case QVariant::LongLong:
38− gv = g_variant_new_int64(var.toLongLong());
39− break;
40− case QVariant::ULongLong:
41− gv = g_variant_new_uint64(var.toULongLong());
42− break;
43− case QVariant::Double:
44− gv = g_variant_new_double(var.toDouble());
45− break;
46− case QVariant::Bool:
47− gv = g_variant_new_boolean(var.toBool());
48− break;
49− case QVariant::Hash: {
50− const auto hash = var.toHash();
51− GVariant **children = new GVariant*[hash.size()];
52− int cnt = 0;
53− QHash<QString, QVariant>::const_iterator it = hash.constBegin();
54− while (it != hash.constEnd()) {
55− GVariant *key = g_variant_new_string(it.key().toUtf8());
56− children[cnt++] = g_variant_new_dict_entry(key, g_variant_new_variant(GVariantFromQVariant(it.value())));
57− ++it;
58− }
59− gv = g_variant_new_array(G_VARIANT_TYPE("{sv}"), children, hash.size());
60− delete [] children;
61− break;
62− }
63− default:
64− break;
65− }
66− return gv;
67−}
68−
69−// implementation of this conversion taken from libdee-qt private utility function.
70−QVariant QVariantFromGVariant(GVariant *value)
71−{
72− /* We need to special-case a{sv} here as G_VARIANT_CLASS_ARRAY handles simple arrays;
73− we need to create QVariantHash, not a list */
74− if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARDICT)) {
75− const gsize nChildren = g_variant_n_children(value);
76− QVariantHash hash;
77− for (gsize i = 0; i < nChildren; ++i)
78− {
79− GVariant* dict_entry = g_variant_get_child_value(value, i);
80−
81− gchar* dict_key;
82− GVariant *dict_var;
83− g_variant_get(dict_entry, "{&sv}", &dict_key, &dict_var);
84− hash.insert(QString::fromUtf8(dict_key), QVariantFromGVariant(dict_var));
85− g_variant_unref(dict_var);
86− g_variant_unref(dict_entry);
87− }
88− return hash;
89− }
90−
91− switch (g_variant_classify(value)) {
92− case G_VARIANT_CLASS_BOOLEAN:
93− return QVariant((bool) g_variant_get_boolean(value));
94− case G_VARIANT_CLASS_BYTE:
95− return QVariant((uchar) g_variant_get_byte(value));
96− case G_VARIANT_CLASS_INT16:
97− return QVariant((qint16) g_variant_get_int16(value));
98− case G_VARIANT_CLASS_UINT16:
99− return QVariant((quint16) g_variant_get_uint16(value));
100− case G_VARIANT_CLASS_INT32:
101− return QVariant((qint32) g_variant_get_int32(value));
102− case G_VARIANT_CLASS_UINT32:
103− return QVariant((quint32) g_variant_get_uint32(value));
104− case G_VARIANT_CLASS_INT64:
105− return QVariant((qint64) g_variant_get_int64(value));
106− case G_VARIANT_CLASS_UINT64:
107− return QVariant((quint64) g_variant_get_uint64(value));
108− case G_VARIANT_CLASS_DOUBLE:
109− return QVariant(g_variant_get_double(value));
110− case G_VARIANT_CLASS_STRING:
111− return QVariant(QString::fromUtf8(g_variant_get_string(value, NULL)));
112− case G_VARIANT_CLASS_ARRAY:
113− case G_VARIANT_CLASS_TUPLE:
114− {
115− const gsize nChildren = g_variant_n_children(value);
116− QList<QVariant> array;
117− for (gsize i = 0; i < nChildren; ++i)
118− {
119− GVariant* gvariant = g_variant_get_child_value(value, i);
120− array << QVariantFromGVariant(gvariant);
121− g_variant_unref(gvariant);
122− }
123− return array;
124− }
125− default:
126− /* Fallback on an empty QVariant.
127− FIXME: Missing conversion of following GVariant types:
128− - G_VARIANT_CLASS_HANDLE
129− - G_VARIANT_CLASS_OBJECT_PATH
130− - G_VARIANT_CLASS_SIGNATURE
131− - G_VARIANT_CLASS_VARIANT
132− - G_VARIANT_CLASS_MAYBE
133− - G_VARIANT_CLASS_DICT_ENTRY
134− */
135− return QVariant();
136− }
137−}
138−
13923unity::glib::HintsMap convertToHintsMap(const QHash<QString, QVariant> &val)
14024{
14125 unity::glib::HintsMap hintsMap;
14226 QHash<QString, QVariant>::const_iterator it = val.constBegin();
14327 while (it != val.constEnd()) {
144− hintsMap[it.key().toStdString()] = GVariantFromQVariant(it.value());
28+ hintsMap[it.key().toStdString()] = DeeListModel::DataFromVariant(it.value());
14529 ++it;
14630 }
14731 return hintsMap;
plugins/Unity/variantutils.h−2
@@ -26,8 +26,6 @@
2626// libunity-core
2727#include <UnityCore/Variant.h>
2828
29−GVariant* GVariantFromQVariant(const QVariant &value);
30−QVariant QVariantFromGVariant(GVariant *value);
3129unity::glib::HintsMap convertToHintsMap(const QHash<QString, QVariant> &val);
3230unity::glib::HintsMap convertToHintsMap(const QVariant &var);
3331
tests/autopilot/unity8/indicators_client/tests/test_battery.py+10−2
@@ -70,7 +70,11 @@ class TestDisplayMenus(IndicatorsTestCase):
7070
7171 # wait for the switch menu item
7272 fn_auto_brightness = lambda: self.app.select_single("SwitchMenuItem", objectName="auto-brightness");
73− self.assertThat(fn_auto_brightness, Eventually(NotEquals(None)));
73+ # FIXME: this should go away when we switch to indicator-power
74+ try:
75+ self.assertThat(fn_auto_brightness, Eventually(NotEquals(None)));
76+ except AssertionError:
77+ self.skipTest("WARNING: test skipped due to missing indicator-battery")
7478 auto_brightness = fn_auto_brightness();
7579
7680 old_ab_value = auto_brightness.checked
@@ -86,7 +90,11 @@ class TestDisplayMenus(IndicatorsTestCase):
8690 if self.input_device_class is Touch:
8791 self.skipTest("Dragging is broken with Touch input (LP: #1203808).")
8892 fn_brightness_menu = lambda: self.app.select_single("SliderMenuItem", objectName="brightness");
89− self.assertThat(fn_brightness_menu, Eventually(NotEquals(None)));
93+ # FIXME: this should go away when we switch to indicator-power
94+ try:
95+ self.assertThat(fn_brightness_menu, Eventually(NotEquals(None)));
96+ except AssertionError:
97+ self.skipTest("WARNING: test skipped due to missing indicator-battery")
9098 brightness_menu = fn_brightness_menu();
9199
92100 old_ab_value = brightness_menu.value
tests/autopilot/unity8/shell/__init__.py+1−27
@@ -21,9 +21,6 @@
2121
2222from functools import wraps
2323import logging
24−import os.path
25−
26−from unity8 import get_mocks_library_path, get_default_extra_mock_libraries
2724
2825
2926logger = logging.getLogger(__name__)
@@ -34,31 +31,8 @@ def with_lightdm_mock(mock_type):
3431 def with_lightdm_mock_internal(fn):
3532 @wraps(fn)
3633 def wrapper(*args, **kwargs):
37− logger.info("Setting up LightDM mock type '%s'", mock_type)
38− new_ld_library_path = "%s:%s" % (
39− get_default_extra_mock_libraries(),
40− _get_ld_library_path(mock_type)
41− )
42− logger.info("New library path: %s", new_ld_library_path)
4334 tests_self = args[0]
44− tests_self.patch_environment(
45− 'LD_LIBRARY_PATH',
46− new_ld_library_path
47− )
35+ tests_self.patch_lightdm_mock(mock_type)
4836 return fn(*args, **kwargs)
4937 return wrapper
5038 return with_lightdm_mock_internal
51−
52−
53−def _get_ld_library_path(mock_type):
54− lib_path = get_mocks_library_path()
55− new_ld_library_path = os.path.abspath(
56− os.path.join(lib_path, "LightDM", mock_type)
57− )
58−
59− if not os.path.exists(new_ld_library_path):
60− raise RuntimeError(
61− "LightDM mock '%s' does not exist at path '%s'."
62− % (mock_type, new_ld_library_path)
63− )
64− return new_ld_library_path
tests/autopilot/unity8/shell/emulators/create_interactive_notification.py+2−2
@@ -59,11 +59,11 @@ if __name__ == '__main__':
5959 notification = Notify.Notification.new(args.summary, args.body, args.icon)
6060
6161 for hint in args.hints:
62− key, value = hint.split(',')
62+ key, value = hint.split(',', 1)
6363 notification.set_hint_string(key, value)
6464
6565 for action in args.action:
66− action_id, action_label = action.split(',')
66+ action_id, action_label = action.split(',', 1)
6767 notification.add_action(
6868 action_id,
6969 action_label,
tests/autopilot/unity8/shell/emulators/create_sd_notification.pydeleted−83
@@ -1,83 +0,0 @@
1−#!/usr/bin/env python
2−
3−import argparse
4−from gi.repository import GLib, Notify
5−import signal
6−
7−
8−def action_callback(notification, action_id, data):
9− print action_id
10−
11−
12−def quit_callback(notification):
13− loop.quit()
14−
15−
16−if __name__ == '__main__':
17− parser = argparse.ArgumentParser(
18− description='Create an interactive notification'
19− )
20−
21− parser.add_argument(
22− '--summary',
23− help='summary text of the notification',
24− default='Summary'
25− )
26− parser.add_argument(
27− '--body',
28− help='body text of the notification',
29− default='Body'
30− )
31− parser.add_argument(
32− '--icon',
33− help='path to the icon to display',
34− default=None
35− )
36− parser.add_argument(
37− '--action',
38− help='id and label for the callback in the format: id,label',
39− action='append',
40− default=[]
41− )
42− parser.add_argument(
43− '--urgency',
44− help='LOW, NORMAL, CRITICAL',
45− choices=['LOW', 'NORMAL', 'CRITICAL'],
46− default='NORMAL'
47− )
48− parser.add_argument(
49− '--hints',
50− help='list of comma sep options',
51− action='append',
52− default=[]
53− )
54−
55− args = parser.parse_args()
56−
57− Notify.init('Snap-Decision Notifications')
58−
59− notification = Notify.Notification.new(args.summary, args.body, args.icon)
60−
61− for hint in args.hints:
62− key, value = hint.split(',')
63− notification.set_hint_string(key, value)
64−
65− for action in args.action:
66− action_id, action_label = action.split(',')
67− notification.add_action(
68− action_id,
69− action_label,
70− action_callback,
71− None,
72− None
73− )
74−
75− def signal_handler(signam, frame):
76− loop.quit()
77−
78− signal.signal(signal.SIGINT, signal_handler)
79−
80− loop = GLib.MainLoop.new(None, False)
81− notification.connect('closed', quit_callback)
82− notification.show()
83− loop.run()
tests/autopilot/unity8/shell/emulators/notification_helper.pydeleted−247
@@ -1,247 +0,0 @@
1−from multiprocessing import Process, JoinableQueue, Queue
2−from gi.repository import GLib, Notify
3−from Queue import Empty
4−
5−
6−class Notifications(object):
7−
8− """A simple context manager that allows tests to fire off notifications that
9− include callbacks and hints.
10−
11− """
12− LOW = Notify.Urgency.LOW
13− NORMAL = Notify.Urgency.NORMAL
14− CRITICAL = Notify.Urgency.CRITICAL
15−
16− def __enter__(self):
17− # A queue of tasks to the notification process. Each one represents a
18− # notification to send, or possibly a command.
19− self._task_queue = JoinableQueue(maxsize=128)
20−
21− # A queue of results from the notification process.
22− self._result_queue = Queue(maxsize=128)
23− self._process = Process(
24− target=_run,
25− args=(
26− self._task_queue,
27− self._result_queue
28− )
29− )
30− self._process.start()
31− return self
32−
33− def __exit__(self, exc_type, exc_value, traceback):
34− self._quit()
35−
36− def _quit(self):
37− self._task_queue.put(("Quit",))
38− self._process.join()
39−
40− def _wait_for_result(self, timeout):
41− return self._result_queue.get(timeout=timeout)
42−
43− def assert_callback_called(self, action_id, timeout):
44− """Assert that the callback specified by 'action_id' was called within
45− a certain timeout.
46−
47− """
48− try:
49− result = self._wait_for_result(timeout)
50− if result[0] != "callback":
51− raise AssertionError(
52− "While waiting for callback '%s', the notification was "
53− "closed." % action_id
54− )
55− if result[1] != action_id:
56− raise AssertionError(
57− "While waiting for callback '%s', the notification %s was "
58− "called instead." % (action_id, result)
59− )
60− except Empty:
61− raise AssertionError(
62− "Callback '%s' was not called within timeout of %d seconds"
63− % (action_id, timeout)
64− )
65−
66− def assert_notification_closed(self, timeout):
67− """Assert that the current notification was closed within the specified
68− timeout.
69−
70− """
71− try:
72− result = self._wait_for_result(timeout)
73− if result[0] != "close":
74− raise AssertionError(
75− "While waiting for callback to be closed, it was activated"
76− " instead"
77− )
78− except Empty:
79− raise AssertionError(
80− "Callback was not closed within timeout of %d seconds"
81− % timeout
82− )
83−
84− def interactive_notification(
85− self,
86− summary="",
87− body="",
88− icon=None,
89− urgency="NORMAL",
90− action_id="action_id",
91− action_label="action_label",
92− hint_strings=[],
93− ):
94− """Create an Interactive notification command.
95−
96− :param summary: Summary text for the notification
97− :param body: Body text to display in the notification
98− :param icon: Path string to the icon to use
99− :param urgency: Urgency for the noticiation, either: Notifications.LOW,
100− Notifications.NORMAL, Notifications.CRITICAL
101− :param action_id: String containing id to store for the callback
102− :param action_label: String to display on the notification, but really ignored visually
103− :param hint_strings: List of tuples containing the 'name' and value for
104− setting the hint strings for the notification
105−
106− """
107− self._task_queue.put((
108− "Interactive",
109− summary,
110− body,
111− icon,
112− urgency,
113− action_id,
114− action_label,
115− hint_strings,
116− ))
117− self._task_queue.join()
118−
119− def sd_notification(
120− self,
121− summary="",
122− body="",
123− icon=None,
124− urgency="NORMAL",
125− action_ids=[],
126− action_labels=[],
127− hint_strings=[],
128− ):
129− """Create a SnapDecision notification command.
130−
131− :param summary: Summary text for the notification
132− :param body: Body text to display in the notification
133− :param icon: Path string to the icon to use
134− :param urgency: Urgency for the noticiation, either: Notifications.LOW,
135− Notifications.NORMAL, Notifications.CRITICAL
136− :param action_ids: List of string containing ids to store for the callback
137− :param action_labels: List of string to display on the action-buttons
138− :param hint_strings: List of tuples containing the 'name' and value for
139− setting the hint strings for the notification
140−
141− """
142− self._task_queue.put((
143− "SnapDecision",
144− summary,
145− body,
146− icon,
147− urgency,
148− action_ids,
149− action_labels,
150− hint_strings,
151− ))
152− self._task_queue.join()
153−
154−
155−def _run(task_queue, result_queue):
156− # TODO: This list is a nasty workaround to keep a reference count alive to
157− # the notification object. If you don't do that, your callbacks never get
158− # called!
159− notifications = []
160−
161− def _check_queue_for_new_task(*args):
162− try:
163− command = task_queue.get(False)
164− except Empty:
165− pass
166− else:
167− try:
168− if command[0] == "Quit":
169− loop.quit()
170− elif command[0] == "Interactive":
171− summary = command[1]
172− body = command[2]
173− icon = command[3]
174− urgency = _translate_urgency(command[4])
175− action_id = command[5]
176− action_label = command[6]
177− hint_strings = command[7]
178−
179− notification = Notify.Notification.new(summary, body, icon)
180− for hint in hint_strings:
181− name, value = hint
182− notification.set_hint_string(name, value)
183− notification.add_action(
184− action_id,
185− action_label,
186− _action_callback,
187− None,
188− None
189− )
190− notification.connect('closed', _quit_callback)
191− notification.set_urgency(urgency)
192− notification.show()
193− notifications.append(notification)
194− elif command[0] == "SnapDecision":
195− summary = command[1]
196− body = command[2]
197− icon = command[3]
198− urgency = _translate_urgency(command[4])
199− action_ids = command[5]
200− action_labels = command[6]
201− hint_strings = command[7]
202− notification = Notify.Notification.new(summary, body, icon)
203− notification.set_hint_string("x-canonical-snap-decisions", "true")
204− for hint in hint_strings:
205− name, value = hint
206− notification.set_hint_string(name, value)
207− size_ids = len(action_ids)
208− size_labels = len(action_labels)
209− if size_ids != size_labels and size_ids:
210− logger.info("Array-sizes of action-ids (%d), action-labels (%d) and action-callbacks (%d) do not match!", size_ids, size_labels)
211− for i in range(size_ids):
212− notification.add_action(action_ids[i],
213− action_labels[i],
214− _action_callback,
215− None,
216− None)
217− notification.connect('closed', _quit_callback)
218− notification.set_urgency(urgency)
219− notification.show()
220− notifications.append(notification)
221− finally:
222− task_queue.task_done()
223−
224− return True
225−
226− def _action_callback(notification, action_id, data):
227− notifications.remove(notification)
228− result_queue.put(("callback", action_id))
229−
230− def _quit_callback(*args):
231− result_queue.put(("quit",))
232−
233− def _translate_urgency(urgency_string):
234− return {
235− "LOW": Notify.Urgency.LOW,
236− "NORMAL": Notify.Urgency.NORMAL,
237− "CRITICAL": Notify.Urgency.CRITICAL,
238− }.get(urgency_string, Notify.Urgency.NORMAL)
239−
240− # we'll check for new tasks in the input queue every 200 mS
241− GLib.timeout_add(200, _check_queue_for_new_task, None)
242−
243− # create the GLib mainloop and start it running:
244− Notify.init("Autopilot notification thingy")
245− loop = GLib.MainLoop()
246− loop.run()
247− Notify.uninit()
tests/autopilot/unity8/shell/tests/__init__.py+53−1
@@ -19,6 +19,11 @@
1919
2020"""unity autopilot tests."""
2121
22+try:
23+ from gi.repository import Gio
24+except ImportError:
25+ Gio = None
26+
2227from autopilot.platform import model
2328from autopilot.testcase import AutopilotTestCase
2429from autopilot.matchers import Eventually
@@ -28,7 +33,12 @@ import logging
2833import os.path
2934from testtools.matchers import Equals, NotEquals
3035
31−from unity8 import get_lib_path, get_binary_path, get_mocks_library_path
36+from unity8 import (
37+ get_lib_path,
38+ get_binary_path,
39+ get_mocks_library_path,
40+ get_default_extra_mock_libraries,
41+)
3242from unity8.shell.emulators import UnityEmulatorBase
3343from unity8.shell.emulators.dash import Dash
3444from unity8.shell.emulators.main_window import MainWindow
@@ -36,6 +46,10 @@ from unity8.shell.emulators.main_window import MainWindow
3646
3747logger = logging.getLogger(__name__)
3848
49+UNITYSHELL_GSETTINGS_SCHEMA = "org.compiz.unityshell"
50+UNITYSHELL_GSETTINGS_PATH = "/org/compiz/profiles/unity/plugins/unityshell/"
51+UNITYSHELL_LAUNCHER_KEY = "launcher-hide-mode"
52+UNITYSHELL_LAUNCHER_MODE = 1 # launcher hidden
3953
4054def _get_device_emulation_scenarios():
4155 if model() == 'Desktop':
@@ -64,10 +78,22 @@ class UnityTestCase(AutopilotTestCase):
6478
6579 def setUp(self):
6680 super(UnityTestCase, self).setUp()
81+ if Gio is not None and UNITYSHELL_GSETTINGS_SCHEMA in Gio.Settings.list_relocatable_schemas():
82+ # Hide Unity launcher
83+ self._unityshell_schema = Gio.Settings.new_with_path(UNITYSHELL_GSETTINGS_SCHEMA, UNITYSHELL_GSETTINGS_PATH)
84+ self._launcher_hide_mode = self._unityshell_schema.get_int(UNITYSHELL_LAUNCHER_KEY)
85+ self._unityshell_schema.set_int(UNITYSHELL_LAUNCHER_KEY, UNITYSHELL_LAUNCHER_MODE)
86+ self.addCleanup(self._reset_launcher)
87+
6788 self._proxy = None
89+ self._lightdm_mock_type = None
6890 self.touch = Touch.create()
6991 self._setup_display_details()
7092
93+ def _reset_launcher(self):
94+ """Reset Unity launcher hide mode"""
95+ self._unityshell_schema.set_int(UNITYSHELL_LAUNCHER_KEY, self._launcher_hide_mode)
96+
7197 def _setup_display_details(self):
7298 scale_divisor = self._determine_geometry()
7399 self._setup_grid_size(scale_divisor)
@@ -133,6 +159,8 @@ class UnityTestCase(AutopilotTestCase):
133159 binary_path
134160 )
135161
162+ if self._lightdm_mock_type is None:
163+ self.patch_lightdm_mock()
136164 self._setup_extra_mock_environment_patch()
137165
138166 app_proxy = self.launch_test_application(
@@ -151,6 +179,30 @@ class UnityTestCase(AutopilotTestCase):
151179
152180 return app_proxy
153181
182+ def patch_lightdm_mock(self, mock_type='single'):
183+ self._lightdm_mock_type = mock_type
184+ logger.info("Setting up LightDM mock type '%s'", mock_type)
185+ new_ld_library_path = "%s:%s" % (
186+ get_default_extra_mock_libraries(),
187+ self._get_lightdm_mock_path(mock_type)
188+ )
189+ logger.info("New library path: %s", new_ld_library_path)
190+
191+ self.patch_environment('LD_LIBRARY_PATH', new_ld_library_path)
192+
193+ def _get_lightdm_mock_path(self, mock_type):
194+ lib_path = get_mocks_library_path()
195+ lightdm_mock_path = os.path.abspath(
196+ os.path.join(lib_path, "LightDM", mock_type)
197+ )
198+
199+ if not os.path.exists(lightdm_mock_path):
200+ raise RuntimeError(
201+ "LightDM mock '%s' does not exist at path '%s'."
202+ % (mock_type, lightdm_mock_path)
203+ )
204+ return lightdm_mock_path
205+
154206 def _setup_extra_mock_environment_patch(self):
155207 mocks_library_path = get_mocks_library_path()
156208 self.patch_environment('QML2_IMPORT_PATH', mocks_library_path)
tests/autopilot/unity8/shell/tests/test_hud.py−5
@@ -32,7 +32,6 @@ class TestHud(UnityTestCase):
3232
3333 scenarios = _get_device_emulation_scenarios()
3434
35− @with_lightdm_mock("single")
3635 def test_show_hud_button_appears(self):
3736 """Swiping up while an app is active must show the 'show hud' button.
3837
@@ -54,7 +53,6 @@ class TestHud(UnityTestCase):
5453 self.touch._finger_move(swipe_coords.end_x, swipe_coords.end_y)
5554 self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
5655
57− @with_lightdm_mock("single")
5856 def test_show_hud_appears(self):
5957 """Releasing the touch on the 'show hud' button must display the hud.
6058
@@ -80,7 +78,6 @@ class TestHud(UnityTestCase):
8078 self.touch.release()
8179 self.assertThat(hud.shown, Eventually(Equals(True)))
8280
83− @with_lightdm_mock("single")
8481 def test_hide_hud_click(self):
8582 """Tapping the close button of the Hud must dismiss it."""
8683 self.launch_unity()
@@ -94,7 +91,6 @@ class TestHud(UnityTestCase):
9491 self.touch.tap(x, y)
9592 self.assertThat(hud.shown, Eventually(Equals(False)))
9693
97− @with_lightdm_mock("single")
9894 def test_hide_hud_dragging(self):
9995 """Once open the Hud must close if the upper bar is dragged and
10096 released downward.
@@ -115,7 +111,6 @@ class TestHud(UnityTestCase):
115111 self.touch.drag(start_x, start_y, end_x, end_y)
116112 self.assertThat(hud.shown, Eventually(Equals(False)))
117113
118− @with_lightdm_mock("single")
119114 def test_launcher_hides_hud(self):
120115 """Opening the Launcher while the Hud is active must close the Hud."""
121116 self.launch_unity()
tests/autopilot/unity8/shell/tests/test_lock_screen.py−2
@@ -47,7 +47,6 @@ class TestLockscreen(UnityTestCase):
4747
4848 self.assertThat(lockscreen.shown, Eventually(Equals(False)))
4949
50− @skipUnless(model() == 'Desktop', "Broken passphrase entry on UInput (LP: #1203788).")
5150 @with_lightdm_mock("single-passphrase")
5251 def test_can_unlock_passphrase_screen(self):
5352 """Must be able to unlock the passphrase entry screen."""
@@ -74,7 +73,6 @@ class TestLockscreen(UnityTestCase):
7473 self.assertThat(pinentryField.text, Eventually(Equals("")))
7574 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
7675
77− @skipUnless(model() == 'Desktop', "Broken passphrase entry on UInput (LP: #1203788).")
7876 @with_lightdm_mock("single-passphrase")
7977 def test_passphrase_screen_wrong_password(self):
8078 """Entering the wrong password must not dismiss the lock screen."""
tests/autopilot/unity8/shell/tests/test_notifications.py+466−518
@@ -1,42 +1,34 @@
11# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
32#
4−# This program is free software: you can redistribute it and/or modify it
5−# under the terms of the GNU General Public License version 3, as published
6−# by the Free Software Foundation.
7−
8−
9−# This file contains general purpose test cases for Unity.
10−# Each test written in this file will be executed for a variety of
11−# configurations, such as Phone, Tablet or Desktop form factors.
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
125#
13−# Sometimes there is the need to disable a certain test for a particular
14−# configuration. To do so, add this in a new line directly below your test:
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
1510#
16−# test_testname.blacklist = (FormFactors.Tablet, FormFactors.Desktop,)
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1718#
18−# Available form factors are:
19−# FormFactors.Phone
20−# FormFactors.Tablet
21−# FormFactors.Desktop
2219
23−"""Tests for the Shell"""
20+"""Tests for Notifications"""
2421
2522from __future__ import absolute_import
2623
27−from unity8.shell import with_lightdm_mock
28−from unity8.shell.emulators.notification_helper import Notifications
2924from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
3025
31−from testtools.matchers import Equals, NotEquals, GreaterThan, MismatchError
32−from testtools import skip
26+from testtools.matchers import Equals, NotEquals
3327from autopilot.matchers import Eventually
34−from autopilot.platform import model
3528
36−from gi.repository import GLib, Notify
29+from gi.repository import Notify
3730import time
3831import os
39−from os import path
4032import logging
4133import signal
4234import subprocess
@@ -44,73 +36,83 @@ import subprocess
4436logger = logging.getLogger(__name__)
4537
4638
47−class TestNotifications(UnityTestCase):
39+class NotificationsBase(UnityTestCase):
40+ """Base class for all notification tests that provides helper methods."""
4841
4942 scenarios = _get_device_emulation_scenarios()
50− action_interactive_triggered = False
51− action_accept_triggered = False
52− action_send_message_triggered = False
53− loop = None
5443
55− def setUp(self):
56− p = subprocess.Popen(['dbus-launch'], stdout=subprocess.PIPE)
57− output = p.communicate()
58− results = output[0].split("\n")
59− self.dbus_pid = int(results[1].split("=")[1])
60− self.dbus_address = results[0].split("=", 1)[1]
44+ def _get_icon_path(self, icon_name):
45+ """Given an icons file name returns the full path (either system or
46+ source tree.
6147
62− logger.info("Using DBUS_SESSION_BUS_ADDRESS: %s", self.dbus_address)
63− logger.info("Using DBUS PID: %d", self.dbus_pid)
48+ Consider the graphics directory as root so for example (runnign tests
49+ from installed unity8-autopilot package):
50+ >>> self.get_icon_path('clock@18.png')
51+ /usr/share/unity8/graphics/clock@18.png
6452
65− self.patch_environment("DBUS_SESSION_BUS_ADDRESS", self.dbus_address)
53+ >>> self.get_icon_path('applicationIcons/facebook@18.png')
54+ /usr/share/unity8/graphics/applicationIcons/facebook@18.png
6655
67− kill_dbus = lambda pid: os.killpg(pid, signal.SIGTERM)
68− self.addCleanup(kill_dbus, self.dbus_pid)
56+ """
57+ if os.path.abspath(__file__).startswith('/usr/'):
58+ return '/usr/share/unity8/graphics/' + icon_name
59+ else:
60+ return os.path.abspath(
61+ os.getcwd() + "/../../graphics/" + icon_name
62+ )
6963
70− super(TestNotifications, self).setUp()
71− self._notify_proc = None
64+ def _get_notifications_list(self):
65+ main_view = self.main_window.get_qml_view()
66+ return main_view.select_single(
67+ "QQuickListView",
68+ objectName='notificationList'
69+ )
7270
73− def tearDown(self):
74− super(TestNotifications, self).tearDown()
75− if self._notify_proc is not None and self._notify_proc.poll() is None:
76− logger.error("Notification process wasn't killed.")
77− os.killpg(self._notify_proc.pid, signal.SIGTERM)
71+ def _assert_notification(
72+ self,
73+ notification,
74+ summary=None,
75+ body=None,
76+ icon=True,
77+ secondary_icon=False,
78+ opacity=None
79+ ):
80+ """Assert that the expected qualities of a notification are as
81+ expected.
7882
79− @with_lightdm_mock("single")
80− def test_icon_summary_body(self):
81− """Notification must display the expected summary and body text."""
82− self.launch_unity(dbus_bus=self.dbus_address)
83− greeter = self.main_window.get_greeter()
84− greeter.unlock()
83+ """
8584
86− notify_list = self._get_notifications_list()
85+ if summary is not None:
86+ self.assertThat(notification.summary, Equals(summary))
8787
88− summary = "Icon-Summary-Body"
89− body = "Hey pal, what's up with the party next weekend? Will you join me and Anna?"
90− icon_path = self._get_icon_path('avatars/anna_olsson@12.png')
91− hints = [
92− (
93− "x-canonical-secondary-icon",
94− self._get_icon_path('applicationIcons/phone-app@18.png')
95− )
96− ]
88+ if body is not None:
89+ self.assertThat(notification.body, Equals(body))
9790
98− self._create_notification(
99− summary,
100− body,
101− icon_path,
102− "NORMAL",
103− hints
104− )
91+ if icon:
92+ self.assertThat(notification.iconSource, NotEquals(""))
93+ else:
94+ self.assertThat(notification.iconSource, Equals(""))
10595
106− get_notification = lambda: notify_list.select_single('Notification')
107− self.assertThat(get_notification, Eventually(NotEquals(None)))
108− notification = get_notification()
109− self._assert_notification(notification, summary, body, True, True, 1.0)
96+ if secondary_icon:
97+ self.assertThat(notification.secondaryIconSource, NotEquals(""))
98+ else:
99+ self.assertThat(notification.secondaryIconSource, Equals(""))
100+
101+ if opacity is not None:
102+ self.assertThat(notification.opacity, Eventually(Equals(opacity)))
103+
104+
105+class InteractiveNotificationBase(NotificationsBase):
106+ """Collection of test for Interactive tests including snap decisions."""
107+
108+ def setUp(self):
109+ super(InteractiveNotificationBase, self).setUp()
110+ # Need to keep track when we launch the notification script.
111+ self._notify_proc = None
110112
111− @with_lightdm_mock("single")
112113 def test_interactive(self):
113− self.launch_unity(dbus_bus=self.dbus_address)
114+ """Interactive notification must react upon click on itself."""
115+ self.launch_unity()
114116 greeter = self.main_window.get_greeter()
115117 greeter.unlock()
116118
@@ -119,6 +121,7 @@ class TestNotifications(UnityTestCase):
119121 summary = "Summary"
120122 body = "Body"
121123 icon_path = self._get_icon_path('avatars/anna_olsson@12.png')
124+ actions = [("action_id", "dummy")]
122125 hints = [
123126 ("x-canonical-switch-to-application", "true"),
124127 (
@@ -132,9 +135,8 @@ class TestNotifications(UnityTestCase):
132135 body,
133136 icon_path,
134137 "NORMAL",
135− "action_id",
136− "dummy",
137− hints
138+ actions,
139+ hints,
138140 )
139141
140142 get_notification = lambda: notify_list.select_single('Notification')
@@ -147,7 +149,6 @@ class TestNotifications(UnityTestCase):
147149
148150 self.assert_notification_action_id_was_called('action_id')
149151
150− @with_lightdm_mock("single")
151152 def test_sd_incoming_call(self):
152153 """Snap-decision simulating incoming call."""
153154 self.launch_unity()
@@ -163,7 +164,8 @@ class TestNotifications(UnityTestCase):
163164 (
164165 "x-canonical-secondary-icon",
165166 self._get_icon_path('applicationIcons/phone-app@18.png')
166− )
167+ ),
168+ ("x-canonical-snap-decisions", "true"),
167169 ]
168170
169171 actions = [
@@ -174,7 +176,7 @@ class TestNotifications(UnityTestCase):
174176 ('action_decline_4', 'Send custom message...'),
175177 ]
176178
177− self._create_sd_notification(
179+ self._create_interactive_notification(
178180 summary,
179181 body,
180182 icon_path,
@@ -188,14 +190,178 @@ class TestNotifications(UnityTestCase):
188190 notification = get_notification()
189191 #self._assert_notification(notification, None, None, True, True, 1.0)
190192 self.touch.tap_object(notification.select_single(objectName="button1"))
191− #self.assertThat(notification.select_single(objectName="buttonRow").expanded, Eventually(Equals(True)))
193+ # Veebers: this needs a better check as it's happening to quick.
194+ self.assertThat(
195+ notification.select_single(objectName="buttonRow").expanded,
196+ Eventually(Equals(True))
197+ )
198+ time.sleep(2)
192199 self.touch.tap_object(notification.select_single(objectName="button4"))
193− #self.assertThat(self.action_send_message_triggered, Equals(True))
194200 self.assert_notification_action_id_was_called("action_decline_4")
195201
196− @with_lightdm_mock("single")
202+ def _create_interactive_notification(
203+ self,
204+ summary="",
205+ body="",
206+ icon=None,
207+ urgency="NORMAL",
208+ actions=[],
209+ hints=[]
210+ ):
211+ """Create a interactive notification command.
212+
213+ :param summary: Summary text for the notification
214+ :param body: Body text to display in the notification
215+ :param icon: Path string to the icon to use
216+ :param urgency: Urgency string for the noticiation, either: 'LOW',
217+ 'NORMAL', 'CRITICAL'
218+ :param actions: List of tuples containing the 'id' and 'label' for all
219+ the actions to add
220+ :param hint_strings: List of tuples containing the 'name' and value for
221+ setting the hint strings for the notification
222+
223+ """
224+
225+ logger.info(
226+ "Creating snap-decision notification with summary(%s), body(%s) "
227+ "and urgency(%r)",
228+ summary,
229+ body,
230+ urgency
231+ )
232+
233+ script_args = [
234+ '--summary', summary,
235+ '--body', body,
236+ '--urgency', urgency
237+ ]
238+
239+ if icon is not None:
240+ script_args.extend(['--icon', icon])
241+
242+ for hint in hints:
243+ key, value = hint
244+ script_args.extend(['--hint', "%s,%s" % (key, value)])
245+
246+ for action in actions:
247+ action_id, action_label = action
248+ action_string = "%s,%s" % (action_id, action_label)
249+ script_args.extend(['--action', action_string])
250+
251+ command = [self._get_notify_script()] + script_args
252+ logger.info("Launching snap-decision notification as: %s", command)
253+ self._notify_proc = subprocess.Popen(
254+ command,
255+ stdin=subprocess.PIPE,
256+ stdout=subprocess.PIPE,
257+ stderr=subprocess.PIPE,
258+ close_fds=True,
259+ preexec_fn=os.setsid
260+ )
261+
262+ self.addCleanup(self._tidy_up_script_process)
263+
264+ poll_result = self._notify_proc.poll()
265+ if poll_result is not None and self._notify_proc.returncode != 0:
266+ error_output = self._notify_proc.communicate()[1]
267+ raise RuntimeError("Call to script failed with: %s" % error_output)
268+
269+ def _get_notify_script(self):
270+ """Returns the path to the interactive notification creation script."""
271+ file_path = "../../emulators/create_interactive_notification.py"
272+
273+ the_path = os.path.abspath(
274+ os.path.join(__file__, file_path))
275+
276+ return the_path
277+
278+ def _tidy_up_script_process(self):
279+ if self._notify_proc is not None and self._notify_proc.poll() is None:
280+ logger.error("Notification process wasn't killed, killing now.")
281+ os.killpg(self._notify_proc.pid, signal.SIGTERM)
282+
283+ def assert_notification_action_id_was_called(self, action_id, timeout=10):
284+ """Assert that the interactive notification callback of id *action_id*
285+ was called.
286+
287+ :raises AssertionError: If no interactive notification has actually
288+ been created.
289+ :raises AssertionError: When *action_id* does not match the actual
290+ returned.
291+ :raises AssertionError: If no callback was called at all.
292+ """
293+
294+ if self._notify_proc is None:
295+ raise AssertionError("No interactive notification was created.")
296+
297+ for i in xrange(timeout):
298+ self._notify_proc.poll()
299+ if self._notify_proc.returncode is not None:
300+ output = self._notify_proc.communicate()
301+ actual_action_id = output[0].strip("\n")
302+ if actual_action_id != action_id:
303+ raise AssertionError(
304+ "action id '%s' does not match actual returned '%s'"
305+ % (action_id, actual_action_id)
306+ )
307+ else:
308+ return
309+ time.sleep(1)
310+
311+ os.killpg(self._notify_proc.pid, signal.SIGTERM)
312+ self._notify_proc = None
313+ raise AssertionError(
314+ "No callback was called, killing interactivenotification script"
315+ )
316+
317+
318+class EphemeralNotificationsTests(NotificationsBase):
319+ """Collection of tests for Emphemeral notifications (non-interactive.)"""
320+
321+ def setUp(self):
322+ super(EphemeralNotificationsTests, self).setUp()
323+ # Because we are using the Notify library we need to init and un-init
324+ # otherwise we get crashes.
325+ Notify.init("Autopilot Ephemeral Notification Tests")
326+ self.addCleanup(Notify.uninit)
327+
328+ def test_icon_summary_body(self):
329+ """Notification must display the expected summary and body text."""
330+ self.launch_unity()
331+ greeter = self.main_window.get_greeter()
332+ greeter.unlock()
333+
334+ notify_list = self._get_notifications_list()
335+
336+ summary = "Icon-Summary-Body"
337+ body = "Hey pal, what's up with the party next weekend? Will you " \
338+ "join me and Anna?"
339+ icon_path = self._get_icon_path('avatars/anna_olsson@12.png')
340+ hints = [
341+ (
342+ "x-canonical-secondary-icon",
343+ self._get_icon_path('applicationIcons/phone-app@18.png')
344+ )
345+ ]
346+
347+ notification = self._create_ephemeral_notification(
348+ summary,
349+ body,
350+ icon_path,
351+ hints,
352+ "NORMAL",
353+ )
354+
355+ notification.show()
356+
357+ get_notification = lambda: notify_list.select_single('Notification')
358+ self.assertThat(get_notification, Eventually(NotEquals(None)))
359+ notification = get_notification()
360+ self._assert_notification(notification, summary, body, True, True, 1.0)
361+
197362 def test_icon_summary(self):
198− self.launch_unity(dbus_bus=self.dbus_address)
363+ """Notification must display the expected summary and secondary icon."""
364+ self.launch_unity()
199365 greeter = self.main_window.get_greeter()
200366 greeter.unlock()
201367
@@ -209,482 +375,353 @@ class TestNotifications(UnityTestCase):
209375 )
210376 ]
211377
212− self._create_notification(
378+ notification = self._create_ephemeral_notification(
213379 summary,
214380 None,
215381 None,
382+ hints,
216383 "NORMAL",
217− hints
218384 )
219385
386+ notification.show()
387+
220388 get_notification = lambda: notify_list.select_single('Notification')
221389 self.assertThat(get_notification, Eventually(NotEquals(None)))
222390 notification = get_notification()
223− self._assert_notification(notification, summary, None, False, True, 1.0)
391+ self._assert_notification(
392+ notification,
393+ summary,
394+ None,
395+ False,
396+ True,
397+ 1.0
398+ )
224399
225− @with_lightdm_mock("single")
226400 def test_urgency_order(self):
401+ """Notifications must be displayed in order according to their urgency."""
227402 self.launch_unity()
228403 greeter = self.main_window.get_greeter()
229404 greeter.unlock()
230− Notify.init("Autopilot Notification Tests")
405+
231406 notify_list = self._get_notifications_list()
232407
233408 summary_low = 'Low Urgency'
234409 body_low = "No, I'd rather see paint dry, pal *yawn*"
235410 icon_path_low = self._get_icon_path('avatars/amanda@12.png')
411+
236412 summary_normal = 'Normal Urgency'
237− body_normal = "Hey pal, what's up with the party next weekend? Will you join me and Anna?"
413+ body_normal = "Hey pal, what's up with the party next weekend? Will " \
414+ "you join me and Anna?"
238415 icon_path_normal = self._get_icon_path('avatars/funky@12.png')
416+
239417 summary_critical = 'Critical Urgency'
240418 body_critical = 'Dude, this is so urgent you have no idea :)'
241419 icon_path_critical = self._get_icon_path('avatars/anna_olsson@12.png')
242420
243− notification_normal = self._create_ephemeral(summary_normal, body_normal, icon_path_normal, None, "NORMAL")
421+ notification_normal = self._create_ephemeral_notification(
422+ summary_normal,
423+ body_normal,
424+ icon_path_normal,
425+ urgency="NORMAL"
426+ )
244427 notification_normal.show()
245− notification_low = self._create_ephemeral(summary_low, body_low, icon_path_low, None, "LOW")
428+
429+ notification_low = self._create_ephemeral_notification(
430+ summary_low,
431+ body_low,
432+ icon_path_low,
433+ urgency="LOW"
434+ )
246435 notification_low.show()
247− notification_critical = self._create_ephemeral(summary_critical, body_critical, icon_path_critical, None, "CRITICAL")
436+
437+ notification_critical = self._create_ephemeral_notification(
438+ summary_critical,
439+ body_critical,
440+ icon_path_critical,
441+ urgency="CRITICAL"
442+ )
248443 notification_critical.show()
249444
250− time.sleep(4)
251− get_notification = lambda: notify_list.select_single('Notification')
445+ get_notification = lambda: notify_list.select_single(
446+ 'Notification',
447+ summary=summary_critical
448+ )
252449 self.assertThat(get_notification, Eventually(NotEquals(None)))
253− notification = get_notification()
254− self._assert_notification(notification, summary_critical, body_critical, True, False, 1.0)
255450
256− time.sleep(4)
257451 notification = get_notification()
258− self._assert_notification(notification, summary_normal, body_normal, True, False, 1.0)
452+ self._assert_notification(
453+ notification,
454+ summary_critical,
455+ body_critical,
456+ True,
457+ False,
458+ 1.0
459+ )
259460
260− time.sleep(4)
261− notification = get_notification()
262− self._assert_notification(notification, summary_low, body_low, True, False, 1.0)
263− Notify.uninit()
461+ get_normal_notification = lambda: notify_list.select_single(
462+ 'Notification',
463+ summary=summary_normal
464+ )
465+ self.assertThat(get_normal_notification, Eventually(NotEquals(None)))
466+ notification = get_normal_notification()
467+ self._assert_notification(
468+ notification,
469+ summary_normal,
470+ body_normal,
471+ True,
472+ False,
473+ 1.0
474+ )
264475
265− @with_lightdm_mock("single")
266− def test_summary_body(self):
476+ get_low_notification = lambda: notify_list.select_single(
477+ 'Notification',
478+ summary=summary_low
479+ )
480+ self.assertThat(get_low_notification, Eventually(NotEquals(None)))
481+ notification = get_low_notification()
482+ self._assert_notification(
483+ notification,
484+ summary_low,
485+ body_low,
486+ True,
487+ False,
488+ 1.0
489+ )
490+
491+ def test_summary_and_body(self):
492+ """Notification must display the expected summary- and body-text."""
267493 self.launch_unity()
268494 greeter = self.main_window.get_greeter()
269495 greeter.unlock()
270− Notify.init("Autopilot Notification Tests")
496+
271497 notify_list = self._get_notifications_list()
272498
273499 summary = 'Summary-Body'
274500 body = 'This is a superfluous notification'
275501
276− notification = self._create_ephemeral(summary, body)
502+ notification = self._create_ephemeral_notification(summary, body)
277503 notification.show()
278504
279505 get_notification = lambda: notify_list.select_single('Notification')
280506 self.assertThat(get_notification, Eventually(NotEquals(None)))
281507 notification = get_notification()
282− self._assert_notification(notification, summary, body, False, False, 1.0)
283− Notify.uninit()
508+ self._assert_notification(
509+ notification,
510+ summary,
511+ body,
512+ False,
513+ False,
514+ 1.0
515+ )
284516
285− @with_lightdm_mock("single")
286517 def test_summary_only(self):
518+ """Notification must display only the expected summary-text."""
287519 self.launch_unity()
288520 greeter = self.main_window.get_greeter()
289521 greeter.unlock()
290− Notify.init("Autopilot Notification Tests")
522+
291523 notify_list = self._get_notifications_list()
292524
293525 summary = 'Summary-Only'
294526
295− notification = self._create_ephemeral(summary)
527+ notification = self._create_ephemeral_notification(summary)
296528 notification.show()
297529
298530 get_notification = lambda: notify_list.select_single('Notification')
299531 self.assertThat(get_notification, Eventually(NotEquals(None)))
300532 notification = get_notification()
533+
301534 self._assert_notification(notification, summary, '', False, False, 1.0)
302− Notify.uninit()
303535
304− @with_lightdm_mock("single")
305− def test_update_notification(self):
536+ def test_update_notification_same_layout(self):
537+ """Notification must allow updating its contents while being displayed."""
306538 self.launch_unity()
307539 greeter = self.main_window.get_greeter()
308540 greeter.unlock()
309− Notify.init("Autopilot Notification Tests")
541+
310542 notify_list = self._get_notifications_list()
311543
312− summary = 'Inital notification (1. notification)'
544+ summary = 'Inital notification'
313545 body = 'This is the original content of this notification-bubble.'
314546 icon_path = self._get_icon_path('avatars/funky@12.png')
315− notification = self._create_ephemeral(summary, body, icon_path)
547+
548+ notification = self._create_ephemeral_notification(
549+ summary,
550+ body,
551+ icon_path
552+ )
316553 notification.show()
554+
317555 get_notification = lambda: notify_list.select_single('Notification')
318556 self.assertThat(get_notification, Eventually(NotEquals(None)))
319− self._assert_notification(get_notification(), summary, body, True, False, 1.0)
557+ self._assert_notification(
558+ get_notification(),
559+ summary,
560+ body,
561+ True,
562+ False,
563+ 1.0
564+ )
320565
321566 time.sleep(3)
322− summary = 'Updated notification (1. notification)'
323− body = 'Here the same bubble with new title- and body-text, even the icon can be changed on the update.'
567+ summary = 'Updated notification'
568+ body = 'Here the same bubble with new title- and body-text, even ' \
569+ 'the icon can be changed on the update.'
324570 icon_path = self._get_icon_path('avatars/amanda@12.png')
325571 notification.update(summary, body, icon_path)
326572 notification.show()
327573 self.assertThat(get_notification, Eventually(NotEquals(None)))
328574 self._assert_notification(get_notification(), summary, body)
329575
330− time.sleep(6)
331− summary = 'Initial layout (2. notification)'
332− body = 'This bubble uses the icon-title-body layout with a secondary icon.'
576+ def test_update_notification_layout_change(self):
577+ """Notification must allow updating its contents and layout while being displayed."""
578+ self.launch_unity()
579+ greeter = self.main_window.get_greeter()
580+ greeter.unlock()
581+
582+ notify_list = self._get_notifications_list()
583+
584+ summary = 'Initial layout'
585+ body = 'This bubble uses the icon-title-body layout with a ' \
586+ 'secondary icon.'
333587 icon_path = self._get_icon_path('avatars/anna_olsson@12.png')
334588 hint_icon = self._get_icon_path('applicationIcons/phone-app@18.png')
335− notification = self._create_ephemeral(summary, body, icon_path)
336− notification.set_hint('x-canonical-secondary-icon', GLib.Variant.new_string(hint_icon))
589+
590+ notification = self._create_ephemeral_notification(
591+ summary,
592+ body,
593+ icon_path
594+ )
595+ notification.set_hint_string(
596+ 'x-canonical-secondary-icon',
597+ hint_icon
598+ )
337599 notification.show()
600+
601+ get_notification = lambda: notify_list.select_single('Notification')
338602 self.assertThat(get_notification, Eventually(NotEquals(None)))
339− self._assert_notification(get_notification(), summary, body, True, True, 1.0)
603+ self._assert_notification(
604+ get_notification(),
605+ summary,
606+ body,
607+ True,
608+ True,
609+ 1.0
610+ )
340611
341612 time.sleep(3)
342613 notification.clear_hints()
343− summary = 'Updated layout (2. notification)'
344− body = 'After the update we now have a bubble using the title-body layout.'
614+ summary = 'Updated layout'
615+ body = 'After the update we now have a bubble using the title-body ' \
616+ 'layout.'
345617 notification.update(summary, body, None)
346618 notification.show()
619+
347620 self.assertThat(get_notification, Eventually(NotEquals(None)))
348621 self._assert_notification(get_notification(), summary, body, False)
349− Notify.uninit()
350622
351− @with_lightdm_mock("single")
352623 def test_append_hint(self):
624+ """Notification has to accumulate body-text using append-hint."""
353625 self.launch_unity()
354626 greeter = self.main_window.get_greeter()
355627 greeter.unlock()
356− Notify.init("Autopilot Notification Tests")
628+
357629 notify_list = self._get_notifications_list()
358630
359631 summary = 'Cole Raby'
360632 body = 'Hey Bro Coly!'
361633 icon_path = self._get_icon_path('avatars/amanda@12.png')
362634 body_sum = body
363− notification = self._create_ephemeral(summary, body, icon_path)
364− notification.set_hint('x-canonical-append', GLib.Variant.new_string('true'))
635+ notification = self._create_ephemeral_notification(
636+ summary,
637+ body,
638+ icon_path,
639+ hints=[('x-canonical-append', 'true')]
640+ )
641+
365642 notification.show()
643+
366644 get_notification = lambda: notify_list.select_single('Notification')
367645 self.assertThat(get_notification, Eventually(NotEquals(None)))
368646 notification = get_notification()
369− self._assert_notification(notification, summary, body_sum, True, False, 1.0)
647+ self._assert_notification(
648+ notification,
649+ summary,
650+ body_sum,
651+ True,
652+ False,
653+ 1.0
654+ )
370655
371− bodies = ['What\'s up dude?',
372− 'Did you watch the air-race in Oshkosh last week?',
373− 'Phil owned the place like no one before him!',
374− 'Did really everything in the race work according to regulations?',
375− 'Somehow I think to remember Burt Williams did cut corners and was not punished for this.',
376− 'Hopefully the referees will watch the videos of the race.',
377− 'Burt could get fined with US$ 50000 for that rule-violation :)']
656+ bodies = [
657+ 'What\'s up dude?',
658+ 'Did you watch the air-race in Oshkosh last week?',
659+ 'Phil owned the place like no one before him!',
660+ 'Did really everything in the race work according to regulations?',
661+ 'Somehow I think to remember Burt Williams did cut corners and was not punished for this.',
662+ 'Hopefully the referees will watch the videos of the race.',
663+ 'Burt could get fined with US$ 50000 for that rule-violation :)'
664+ ]
378665
379666 for new_body in bodies:
380667 time.sleep(1)
381668 body = new_body
382669 body_sum += '\n' + body
383− notification = self._create_ephemeral(summary, body, icon_path)
384− notification.set_hint('x-canonical-append', GLib.Variant.new_string('true'))
670+ notification = self._create_ephemeral_notification(
671+ summary,
672+ body,
673+ icon_path,
674+ hints=[('x-canonical-append', 'true')]
675+ )
385676 notification.show()
386− get_notification = lambda: notify_list.select_single('Notification')
677+ get_notification = lambda: notify_list.select_single(
678+ 'Notification'
679+ )
387680 self.assertThat(get_notification, Eventually(NotEquals(None)))
388681 notification = get_notification()
389− self._assert_notification(notification, summary, body_sum, True, False, 1.0)
390−
391− Notify.uninit()
392−
393− def _run_loop_with_killswitch(self, loop):
394− logger.info("Running loop with timeout")
395− print "Running loop with timeout"
396−
397− def killer(loop):
398− logger.info("Killing Loop!")
399− print "Killing Loop!"
400− loop.quit()
401−
402− GLib.timeout_add_seconds(10, killer, loop)
403− loop.run()
404−
405− def _create_notification(
406− self,
407− summary="",
408− body="",
409− icon=None,
410− urgency="NORMAL",
411− hints=[]
412− ):
413− """Create a notification command.
414−
415− :param summary: Summary text for the notification
416− :param body: Body text to display in the notification
417− :param icon: Path string to the icon to use
418− :param urgency: Urgency string for the noticiation, either: 'LOW',
419− 'NORMAL', 'CRITICAL'
420− :param hint_strings: List of tuples containing the 'name' and value for
421− setting the hint strings for the notification
422−
423− """
424− logger.info(
425− "Creating notification with summary(%s), body(%s) "
426− "and urgency(%r)",
427− summary,
428− body,
429− urgency
430− )
431−
432− script_args = [
433− '--summary', summary,
434− '--body', body,
435− '--urgency', urgency
436− ]
437−
438− if icon is not None:
439− script_args.extend(['--icon', icon])
440−
441− for hint in hints:
442− key, value = hint
443− script_args.extend(['--hint', "%s,%s" % (key, value)])
444−
445− command = [self._get_notify_script()] + script_args
446− logger.info("Launching interactive notification as: %s", command)
447− self._notify_proc = subprocess.Popen(
448− command,
449− stdin=subprocess.PIPE,
450− stdout=subprocess.PIPE,
451− stderr=subprocess.PIPE,
452− close_fds=True,
453− preexec_fn=os.setsid
454− )
455−
456− def _create_interactive_notification(
457− self,
458− summary="",
459− body="",
460− icon=None,
461− urgency="NORMAL",
462− action_id="action_id",
463− action_label="action_label",
464− hints=[]
465− ):
466− """Create an Interactive notification command.
467−
468− :param summary: Summary text for the notification
469− :param body: Body text to display in the notification
470− :param icon: Path string to the icon to use
471− :param urgency: Urgency string for the noticiation, either: 'LOW',
472− 'NORMAL', 'CRITICAL'
473− :param action_id: String containing id to store for the callback
474− :param action_label: String to display on the notification
475− :param hint_strings: List of tuples containing the 'name' and value for
476− setting the hint strings for the notification
477−
478− """
479− logger.info(
480− "Creating interactive notification with summary(%s), body(%s) "
481− "and urgency(%r)",
482− summary,
483− body,
484− urgency
485− )
486−
487− script_args = [
488− '--summary', summary,
489− '--body', body,
490− '--urgency', urgency,
491− '--action', "%s,%s" % (action_id, action_label)
492− ]
493−
494− if icon is not None:
495− script_args.extend(['--icon', icon])
496−
497− for hint in hints:
498− key, value = hint
499− script_args.extend(['--hint', "%s,%s" % (key, value)])
500−
501− command = [self._get_notify_script("Interactive")] + script_args
502− logger.info("Launching interactive notification as: %s", command)
503− self._notify_proc = subprocess.Popen(
504− command,
505− stdin=subprocess.PIPE,
506− stdout=subprocess.PIPE,
507− stderr=subprocess.PIPE,
508− close_fds=True,
509− preexec_fn=os.setsid
510− )
682+ self._assert_notification(
683+ notification,
684+ summary,
685+ body_sum,
686+ True,
687+ False,
688+ 1.0
689+ )
511690
512− def _create_sd_notification(
691+ def _create_ephemeral_notification(
513692 self,
514693 summary="",
515694 body="",
516695 icon=None,
517− urgency="NORMAL",
518− actions=[],
519− hints=[]
696+ hints=[],
697+ urgency="NORMAL"
520698 ):
521− """Create a SnapDecision notification command.
699+ """Create an ephemeral (non-interactive) notification
522700
523− :param summary: Summary text for the notification
524− :param body: Body text to display in the notification
525− :param icon: Path string to the icon to use
526− :param urgency: Urgency for the noticiation, either: Notifications.LOW,
527− Notifications.NORMAL, Notifications.CRITICAL
528− :param actions: List of tuples containing the 'id' and 'label' for all
529− the actions to add
530− :param hint_strings: List of tuples containing the 'name' and value for
531− setting the hint strings for the notification
701+ :param summary: Summary text for the notification
702+ :param body: Body text to display in the notification
703+ :param icon: Path string to the icon to use
704+ :param hint_strings: List of tuples containing the 'name' and value
705+ for setting the hint strings for the notification
706+ :param urgency: Urgency string for the noticiation, either: 'LOW',
707+ 'NORMAL', 'CRITICAL'
532708
533709 """
534−
535710 logger.info(
536− "Creating snap-decision notification with summary(%s), body(%s) "
537− "and urgency(%r)",
711+ "Creating ephemeral: summary(%s), body(%s), urgency(%r) "
712+ "and Icon(%s)",
538713 summary,
539714 body,
540− urgency
715+ urgency,
716+ icon
541717 )
542718
543− script_args = [
544− '--summary', summary,
545− '--body', body,
546− '--urgency', urgency
547− ]
548−
549− if icon is not None:
550− script_args.extend(['--icon', icon])
719+ n = Notify.Notification.new(summary, body, icon)
551720
552721 for hint in hints:
553722 key, value = hint
554− script_args.extend(['--hint', "%s,%s" % (key, value)])
555−
556− for action in actions:
557− action_id, action_label = action
558− script_args.extend(['--action', "%s,%s" % (action_id, action_label)])
559−
560− command = [self._get_notify_script("SnapDecision")] + script_args
561− logger.info("Launching snap-decision notification as: %s", command)
562− self._notify_proc = subprocess.Popen(
563− command,
564− stdin=subprocess.PIPE,
565− stdout=subprocess.PIPE,
566− stderr=subprocess.PIPE,
567− close_fds=True,
568− preexec_fn=os.setsid
569− )
570−
571− def _get_notify_script(self, type):
572− """Returns the path to the interactive notification creation script."""
573− if type == "Interactive":
574− file = "../../emulators/create_interactive_notification.py"
575− elif type == "SnapDecision":
576− file = "../../emulators/create_sd_notification.py"
577− elif type == "Ephemeral":
578− file = "../../emulators/create_ephemeral_notification.py"
579−
580− the_path = os.path.abspath(
581− os.path.join(__file__, file))
582−
583− return the_path
584−
585− def assert_notification_action_id_was_called(self, action_id, timeout=10):
586− """Assert that the interactive notification callback of id *action_id*
587− was called.
588−
589− :raises AssertionError: If no interactive notification has actually been
590− created.
591− :raises AssertionError: When *action_id* does not match the actual
592− returned.
593− :raises AssertionError: If no callback was called at all.
594− """
595−
596− if self._notify_proc is None:
597− raise AssertionError("No interactive notification was created.")
598−
599− for i in xrange(timeout):
600− self._notify_proc.poll()
601− if self._notify_proc.returncode is not None:
602− output = self._notify_proc.communicate()
603− actual_action_id = output[0].strip("\n")
604− if actual_action_id != action_id:
605− raise AssertionError(
606− "action id '%s' does not match actual returned '%s'"
607− % (action_id, actual_action_id)
608− )
609− else:
610− return
611− time.sleep(1)
612−
613− os.killpg(self._notify_proc.pid, signal.SIGTERM)
614− self._notify_proc = None
615− raise AssertionError(
616− "No callback was called, killing interactivenotification script"
617− )
618−
619− def _create_ephemeral(self, summary="", body="", icon=None, secondary_icon=None, urgency="NORMAL"):
620− logger.info("Creating ephemeral notification with summary(%s), body(%s) and urgency(%r)", summary, body, urgency)
621− if icon is not None:
622− logger.info("Using icon(%s)", icon)
623− if secondary_icon is not None:
624− logger.info("Using secondary-icon(%s)", secondary_icon)
625−
626− n = Notify.Notification.new(summary, body, icon)
627− if secondary_icon is not None:
628− n.set_hint('x-canonical-secondary-icon', GLib.Variant.new_string(secondary_icon))
629− n.set_urgency(self._get_urgency(urgency))
630−
631− return n
632−
633− def _create_interactive(self, summary="", body="", icon=None, secondary_icon=None, urgency="NORMAL", action_id="action_id", action_label="action_label", action_cb=None):
634− logger.info("Creating interactive notification with summary(%s), body(%s) and urgency(%r)", summary, body, urgency)
635− if icon is not None:
636− logger.info("Using icon(%s)", icon)
637− if secondary_icon is not None:
638− logger.info("Using secondary-icon(%s)", secondary_icon)
639−
640− n = Notify.Notification.new(summary, body, icon)
641− if secondary_icon is not None:
642− n.set_hint('x-canonical-secondary-icon', GLib.Variant.new_string(secondary_icon))
643− n.set_hint('x-canonical-switch-to-application', GLib.Variant.new_string('true'))
644− n.add_action(action_id, action_label, action_cb, None, None)
645− n.connect('closed', self._close_cb)
646− n.set_urgency(self._get_urgency(urgency))
647−
648− return n
649−
650− def _create_snap_decision(self, summary="", body="", icon=None, secondary_icon=None, urgency="NORMAL", action_ids=None, action_labels=None, action_cbs=None):
651− logger.info("Creating snap-decision notification with summary(%s), body(%s) and urgency(%r)", summary, body, urgency)
652− if icon is not None:
653− logger.info("Using icon(%s)", icon)
654− if secondary_icon is not None:
655− logger.info("Using secondary-icon(%s)", secondary_icon)
656−
657− size_ids = len(action_ids)
658− size_labels = len(action_labels)
659− size_cbs = len(action_cbs)
660− if size_ids != size_labels and size_ids != size_cbs:
661− logger.info("Array-sizes of action-ids (%d), action-labels (%d) and action-callbacks (%d) do not match!", size_ids, size_labels, size_cbs)
662− return None
663−
664− n = Notify.Notification.new(summary, body, icon)
665− if secondary_icon is not None:
666− n.set_hint('x-canonical-secondary-icon', GLib.Variant.new_string(secondary_icon))
667− n.set_hint('x-canonical-snap-decisions', GLib.Variant.new_string('true'))
668− for i in range(size_ids):
669− n.add_action(action_ids[i], action_labels[i], action_cbs[i], None, None)
670− n.connect('closed', self._close_cb)
671− n.set_urgency(self._get_urgency(urgency))
672−
673− return n
674−
675− def _create_confirmation(self, summary="", body="", icon=None, secondary_icon=None, urgency="NORMAL"):
676− logger.info("Creating confirmation notification with summary(%s), body(%s) and urgency(%r)", summary, body, urgency)
677− if icon is not None:
678− logger.info("Using icon(%s)", icon)
679− if secondary_icon is not None:
680− logger.info("Using secondary-icon(%s)", secondary_icon)
681−
682− n = Notify.Notification.new(summary, body, icon)
683− if secondary_icon is not None:
684− n.set_hint('x-canonical-secondary-icon', GLib.Variant.new_string(secondary_icon))
685− n.set_hint('x-canonical-private-synchronous', GLib.Variant.new_string('true'))
686− if summary == "" and body == "":
687− n.set_hint('x-canonical-private-icon-only', GLib.Variant.new_string('true'))
723+ n.set_hint_string(key, value)
724+ logger.info("Adding hint to notification: (%s, %s)", key, value)
688725 n.set_urgency(self._get_urgency(urgency))
689726
690727 return n
@@ -695,92 +732,3 @@ class TestNotifications(UnityTestCase):
695732 'NORMAL': Notify.Urgency.NORMAL,
696733 'CRITICAL': Notify.Urgency.CRITICAL}
697734 return _urgency_enums.get(urgency.upper())
698−
699− def _get_icon_path(self, icon_name):
700− """Given an icons file name returns the full path (either system or
701− source tree.
702−
703− Consider the graphics directory as root so for example (runnign tests
704− from installed unity8-autopilot package):
705− >>> self.get_icon_path('clock@18.png')
706− /usr/share/unity8/graphics/clock@18.png
707−
708− >>> self.get_icon_path('applicationIcons/facebook@18.png')
709− /usr/share/unity8/graphics/applicationIcons/facebook@18.png
710−
711− """
712− if os.path.abspath(__file__).startswith('/usr/'):
713− return '/usr/share/unity8/graphics/' + icon_name
714− else:
715− return os.path.abspath(os.getcwd() + "/../../graphics/" + icon_name)
716−
717− def _get_notifications_list(self):
718− main_view = self.main_window.get_qml_view()
719− return main_view.select_single("QQuickListView", objectName='notificationList')
720−
721− def _assert_notification(self, notification, summary=None, body=None, icon=True, secondary_icon=False, opacity=None):
722− if summary is not None:
723− self.assertThat(notification.summary, Equals(summary))
724−
725− if body is not None:
726− self.assertThat(notification.body, Equals(body))
727−
728− if icon:
729− self.assertThat(notification.iconSource, NotEquals(""))
730− else:
731− self.assertThat(notification.iconSource, Equals(""))
732−
733− if secondary_icon:
734− self.assertThat(notification.secondaryIconSource, NotEquals(""))
735− else:
736− self.assertThat(notification.secondaryIconSource, Equals(""))
737−
738− if opacity is not None:
739− self.assertThat(notification.opacity, Eventually(Equals(opacity)))
740−
741− def _action_interactive_cb(self, notification, action, data):
742− if action == "action_id":
743− logger.info("--- Triggering action ---")
744− print "--- Triggering action ---"
745− self.action_interactive_triggered = True
746− else:
747− logger.info("--- That should not have happened (action_id)! ---")
748− print "--- That should not have happened (action_id)! ---"
749− self.action_interactive_triggered = False
750−
751− def _action_sd_decline1_cb(self, notification, action, data):
752− if action == "action_decline_1":
753− print "Decline"
754− else:
755− print "That should not have happened (action_decline_1)!"
756−
757− def _action_sd_decline2_cb(self, notification, action, data):
758− if action == "action_decline_2":
759− print "\"Can't talk now, what's up?\""
760− else:
761− print "That should not have happened (action_decline_2)!"
762−
763− def _action_sd_decline3_cb(self, notification, action, data):
764− if action == "action_decline_3":
765− print "\"I call you back.\""
766− else:
767− print "That should not have happened (action_decline_3)!"
768−
769− def _action_sd_decline4_cb(self, notification, action, data):
770− if action == "action_decline_4":
771− print "Send custom message..."
772− self.action_send_message_triggered = True
773− else:
774− print "That should not have happened (action_decline_4)!"
775− self.action_send_message_triggered = False
776−
777− def _action_sd_accept_cb(self, notification, action, data):
778− if action == "action_accept":
779− print "Accepting call"
780− self.action_accept_triggered = True
781− else:
782− print "That should not have happened (action_accept)!"
783− self.action_accept_triggered = False
784−
785− def _close_cb(self, notification):
786− self.loop.quit()
tests/mocks/CMakeLists.txt+1
@@ -1,5 +1,6 @@
11add_subdirectory(libusermetrics)
22add_subdirectory(LightDM)
3+add_subdirectory(QMenuModel)
34add_subdirectory(Ubuntu)
45add_subdirectory(Unity)
56add_subdirectory(HudClient)
tests/mocks/QMenuModel/CMakeLists.txtnew file+11
@@ -0,0 +1,11 @@
1+file(GLOB QMLFILES
2+ *.qml
3+ *.js
4+ qmldir
5+)
6+
7+# copy files into build directory for shadow builds
8+add_custom_target(QMenuModelQmlFiles ALL
9+ COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
10+ DEPENDS ${QMLFILES}
11+)
tests/mocks/QMenuModel/QDBusActionGroup.qmlnew file+27
@@ -0,0 +1,27 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Daniel d'Andrada <daniel.dandrada@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import QtQuick 2.0
21+
22+QtObject {
23+ property int busType
24+ property string busName
25+ property string objectPath
26+ function start() {}
27+}
tests/mocks/QMenuModel/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module QMenuModel
2+QDBusActionGroup 0.1 QDBusActionGroup.qml
tests/mocks/Ubuntu/Application/ApplicationManager.cpp+6−2
@@ -37,8 +37,6 @@ ApplicationManager::ApplicationManager(QObject *parent)
3737 , m_sideStage(0)
3838{
3939 buildListOfAvailableApplications();
40− createMainStageComponent();
41− createSideStageComponent();
4240}
4341
4442ApplicationManager::~ApplicationManager()
@@ -403,6 +401,9 @@ void ApplicationManager::createMainStageComponent()
403401
404402void ApplicationManager::createMainStage()
405403{
404+ if (!m_mainStageComponent)
405+ createMainStageComponent();
406+
406407 // The assumptions I make here really should hold.
407408 QQuickView *quickView =
408409 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
@@ -437,6 +438,9 @@ void ApplicationManager::createSideStageComponent()
437438
438439void ApplicationManager::createSideStage()
439440{
441+ if (!m_sideStageComponent)
442+ createSideStageComponent();
443+
440444 // The assumptions I make here really should hold.
441445 QQuickView *quickView =
442446 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
tests/mocks/Unity/CMakeLists.txt+1
@@ -44,3 +44,4 @@ file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
4444 )
4545
4646add_subdirectory(Launcher)
47+add_subdirectory(Notifications)
tests/mocks/Unity/Notifications/CMakeLists.txtnew file+11
@@ -0,0 +1,11 @@
1+file(GLOB QMLFILES
2+ *.qml
3+ *.js
4+ qmldir
5+)
6+
7+# copy files into build directory for shadow builds
8+add_custom_target(NotificationsQmlFiles ALL
9+ COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
10+ DEPENDS ${QMLFILES}
11+)
tests/mocks/Unity/fake_categories.cpp+5−2
@@ -32,10 +32,11 @@ Categories::Categories(QObject* parent)
3232 m_roles[Categories::RoleName] = "name";
3333 m_roles[Categories::RoleIcon] = "icon";
3434 m_roles[Categories::RoleRenderer] = "renderer";
35− m_roles[Categories::RoleContentType] = "content_type";
35+ m_roles[Categories::RoleContentType] = "contentType";
3636 m_roles[Categories::RoleHints] = "hints";
3737 m_roles[Categories::RoleResults] = "results";
3838 m_roles[Categories::RoleCount] = "count";
39+ m_roles[Categories::RoleCategoryIndex] = "categoryIndex";
3940
4041 // TODO This should not be needed but accumulatting the count changes
4142 // makes the visualization more stable and also makes crashes on fast
@@ -154,7 +155,7 @@ Categories::data(const QModelIndex& index, int role) const
154155
155156 switch (role) {
156157 case RoleId:
157− return QVariant::fromValue(index.row());
158+ return DeeListModel::data(index, 0); //ID
158159 case RoleName:
159160 return DeeListModel::data(index, 1); //DISPLAY_NAME
160161 case RoleIcon:
@@ -172,6 +173,8 @@ Categories::data(const QModelIndex& index, int role) const
172173 return QVariant::fromValue(getFilter(index.row()));
173174 case RoleCount:
174175 return QVariant::fromValue(getFilter(index.row())->rowCount());
176+ case RoleCategoryIndex:
177+ return QVariant::fromValue(index.row());
175178 default:
176179 return QVariant();
177180 }
tests/mocks/Unity/fake_categories.h+2−1
@@ -41,7 +41,8 @@ public:
4141 RoleContentType,
4242 RoleHints,
4343 RoleResults,
44− RoleCount
44+ RoleCount,
45+ RoleCategoryIndex
4546 };
4647
4748 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
tests/mocks/Unity/fake_scopes.cpp+2−2
@@ -52,8 +52,8 @@ void Scopes::updateScopes()
5252 clear();
5353 addScope(new Scope("MockScope1", "People", true, this));
5454 addScope(new Scope("MockScope2", "Music", false, this));
55− addScope(new Scope("MockScope3", "Home", true, this));
56− addScope(new Scope("MockScope4", "Applications", true, this));
55+ addScope(new Scope("home.scope", "Home", true, this));
56+ addScope(new Scope("applications.scope", "Applications", true, this));
5757 addScope(new Scope("MockScope5", "Videos", true, this));
5858
5959 if (!m_loaded) {
tests/plugins/ListViewWithPageHeader/CMakeLists.txt+6−1
@@ -1,7 +1,12 @@
11# There's no cmake var for v8 include path :-/
22# so create one
33LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
4−SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
4+if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.1.0")
5+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
6+else()
7+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
8+endif()
9+
510
611include_directories(
712 ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp+12
@@ -22,7 +22,11 @@
2222#include <QtTestGui>
2323#pragma GCC diagnostic push
2424#pragma GCC diagnostic ignored "-pedantic"
25+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
2526#include <private/qquicklistmodel_p.h>
27+#else
28+#include <private/qqmllistmodel_p.h>
29+#endif
2630#include <private/qquickanimation_p.h>
2731#include <private/qquickitem_p.h>
2832#pragma GCC diagnostic pop
@@ -115,7 +119,11 @@ private Q_SLOTS:
115119 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
116120 view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test.qml"));
117121 lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
122+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
118123 model = view->rootObject()->findChild<QQuickListModel*>();
124+#else
125+ model = view->rootObject()->findChild<QQmlListModel*>();
126+#endif
119127 otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
120128 QVERIFY(lvwph);
121129 QVERIFY(model);
@@ -1385,7 +1393,11 @@ private Q_SLOTS:
13851393private:
13861394 QQuickView *view;
13871395 ListViewWithPageHeader *lvwph;
1396+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
13881397 QQuickListModel *model;
1398+#else
1399+ QQmlListModel *model;
1400+#endif
13891401 QQmlComponent *otherDelegate;
13901402};
13911403
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertestsection.cpp+12
@@ -22,7 +22,11 @@
2222#include <QtTestGui>
2323#pragma GCC diagnostic push
2424#pragma GCC diagnostic ignored "-pedantic"
25+#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
2526#include <private/qquicklistmodel_p.h>
27+#else
28+#include <private/qqmllistmodel_p.h>
29+#endif
2630#include <private/qquickanimation_p.h>
2731#include <private/qquickitem_p.h>
2832#pragma GCC diagnostic pop
@@ -124,7 +128,11 @@ private Q_SLOTS:
124128 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
125129 view->setSource(QUrl::fromLocalFile(LISTVIEWWITHPAGEHEADER_FOLDER "/test_section.qml"));
126130 lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
131+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
127132 model = view->rootObject()->findChild<QQuickListModel*>();
133+#else
134+ model = view->rootObject()->findChild<QQmlListModel*>();
135+#endif
128136 otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
129137 QVERIFY(lvwph);
130138 QVERIFY(model);
@@ -1637,7 +1645,11 @@ private Q_SLOTS:
16371645private:
16381646 QQuickView *view;
16391647 ListViewWithPageHeader *lvwph;
1648+#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
16401649 QQuickListModel *model;
1650+#else
1651+ QQmlListModel *model;
1652+#endif
16411653 QQmlComponent *otherDelegate;
16421654};
16431655
tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp+1−1
@@ -134,5 +134,5 @@ private Q_SLOTS:
134134 }
135135};
136136
137−QTEST_MAIN(IndicatorsManagerTest)
137+QTEST_GUILESS_MAIN(IndicatorsManagerTest)
138138#include "indicatorsmanagertest.moc"
tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp+1−1
@@ -97,6 +97,6 @@ private Q_SLOTS:
9797 }
9898};
9999
100−QTEST_MAIN(IndicatorsModelTest)
100+QTEST_GUILESS_MAIN(IndicatorsModelTest)
101101
102102#include "indicatorsmodeltest.moc"
tests/plugins/Unity/Indicators/menucontentactivatortest.cpp+1−1
@@ -299,5 +299,5 @@ private:
299299 QList<int> m_deltas;
300300};
301301
302−QTEST_MAIN(MenuConentActivatorTest)
302+QTEST_GUILESS_MAIN(MenuConentActivatorTest)
303303#include "menucontentactivatortest.moc"
tests/plugins/Utils/qlimitproxymodeltest.cpp+1−1
@@ -481,6 +481,6 @@ private Q_SLOTS:
481481 }
482482};
483483
484−QTEST_MAIN(QLimitProxyModelTest)
484+QTEST_GUILESS_MAIN(QLimitProxyModelTest)
485485
486486#include "qlimitproxymodeltest.moc"
tests/plugins/Utils/qsortfilterproxymodeltest.cpp+1−1
@@ -285,6 +285,6 @@ private Q_SLOTS:
285285 }
286286};
287287
288−QTEST_MAIN(QSortFilterProxyModelTest)
288+QTEST_GUILESS_MAIN(QSortFilterProxyModelTest)
289289
290290#include "qsortfilterproxymodeltest.moc"
tests/qmltests/CMakeLists.txt+3
@@ -25,6 +25,9 @@ add_qml_test(utils/Unity/Test UnityTest)
2525set(qmltest_DEFAULT_TARGETS qmluitests)
2626set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
2727set(qmltest_DEFAULT_PROPERTIES "")
28+
29+add_qml_test(. Shell IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS}
30+ ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
2831add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
2932add_qml_test(Components DraggingArea)
3033add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
tests/qmltests/Components/tst_Stage.qml+1
@@ -161,6 +161,7 @@ Rectangle {
161161 applicationManager: fakeAppManager
162162 rightEdgeDraggingAreaWidth: units.gu(2)
163163 fullyShown: true
164+ fullyHidden: false
164165 newApplicationScreenshot: FakeApplicationScreenshot {
165166 id: newAppScreenshot
166167 parent: stage
tests/qmltests/Dash/tst_Dash.qml+4−4
@@ -38,8 +38,8 @@ Item {
3838 scopeDelegateMapping: {
3939 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
4040 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
41− "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
42− "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
41+ "home.scope": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
42+ "applications.scope": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
4343 }
4444 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
4545 }
@@ -69,8 +69,8 @@ Item {
6969 return [
7070 { tag: "MockScope1", visualIndex: 0, shouldBeVisible: true },
7171 { tag: "MockScope2", visualIndex: -1, shouldBeVisible: false },
72− { tag: "MockScope3", visualIndex: 1, shouldBeVisible: true },
73− { tag: "MockScope4", visualIndex: 2, shouldBeVisible: true },
72+ { tag: "home.scope", visualIndex: 1, shouldBeVisible: true },
73+ { tag: "applications.scope", visualIndex: 2, shouldBeVisible: true },
7474 { tag: "MockScope5", visualIndex: 3, shouldBeVisible: true },
7575 ]
7676 }
tests/qmltests/Dash/tst_DashContent.qml+12−12
@@ -31,8 +31,8 @@ Item {
3131 property var scopeStatus: {
3232 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
3333 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
34− 'MockScope3': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
35− 'MockScope4': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
34+ 'home.scope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
35+ 'applications.scope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
3636 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
3737 }
3838
@@ -55,8 +55,8 @@ Item {
5555 scopeDelegateMapping: {
5656 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
5757 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
58− "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
59− "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
58+ "home.scope": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
59+ "applications.scope": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
6060 }
6161 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
6262 }
@@ -68,11 +68,11 @@ Item {
6868 scopeStatus["MockScope2"].movementStarted = 0;
6969 scopeStatus["MockScope2"].positionedAtBeginning = 0;
7070
71− scopeStatus["MockScope3"].movementStarted = 0;
72− scopeStatus["MockScope3"].positionedAtBeginning = 0;
71+ scopeStatus["home.scope"].movementStarted = 0;
72+ scopeStatus["home.scope"].positionedAtBeginning = 0;
7373
74− scopeStatus["MockScope4"].movementStarted = 0;
75− scopeStatus["MockScope4"].positionedAtBeginning = 0;
74+ scopeStatus["applications.scope"].movementStarted = 0;
75+ scopeStatus["applications.scope"].positionedAtBeginning = 0;
7676
7777 scopeStatus["MockScope5"].movementStarted = 0;
7878 scopeStatus["MockScope5"].positionedAtBeginning = 0;
@@ -127,8 +127,8 @@ Item {
127127 compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
128128 compare(scopeStatus["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
129129 compare(scopeStatus["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
130− compare(scopeStatus["MockScope3"].movementStarted, 1, "MockScope3 should have emitted movementStarted signal when content list did.");
131− compare(scopeStatus["MockScope4"].movementStarted, 1, "MockScope4 should have emitted movementStarted signal when content list did.");
130+ compare(scopeStatus["home.scope"].movementStarted, 1, "home.scope should have emitted movementStarted signal when content list did.");
131+ compare(scopeStatus["applications.scope"].movementStarted, 1, "applications.scope should have emitted movementStarted signal when content list did.");
132132 compare(scopeStatus["MockScope5"].movementStarted, 1, "MockScope5 should have emitted movementStarted signal when content list did.");
133133 }
134134
@@ -140,8 +140,8 @@ Item {
140140 dashContent.positionedAtBeginning();
141141 compare(scopeStatus["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
142142 compare(scopeStatus["MockScope2"].positionedAtBeginning, 1, "MockScope2 should have emitted positionedAtBeginning signal when DashContent did.");
143− compare(scopeStatus["MockScope3"].positionedAtBeginning, 1, "MockScope3 should have emitted positionedAtBeginning signal when DashContent did.");
144− compare(scopeStatus["MockScope4"].positionedAtBeginning, 1, "MockScope4 should have emitted positionedAtBeginning signal when DashContent did.");
143+ compare(scopeStatus["home.scope"].positionedAtBeginning, 1, "home.scope should have emitted positionedAtBeginning signal when DashContent did.");
144+ compare(scopeStatus["applications.scope"].positionedAtBeginning, 1, "applications.scope should have emitted positionedAtBeginning signal when DashContent did.");
145145 compare(scopeStatus["MockScope5"].positionedAtBeginning, 1, "MockScope5 should have emitted positionedAtBeginning signal when DashContent did.");
146146 }
147147
tests/qmltests/Panel/tst_IndicatorItem.qml+10−16
@@ -34,34 +34,28 @@ Rectangle {
3434 UT.UnityTestCase {
3535 name: "IndicatorItem"
3636
37− function init_test() {
38− }
39−
4037 function test_dimmed() {
41− init_test()
42−
43− indicatorItem.dimmed = true
44− compare(indicatorItem.opacity > 0, true, "IndicatorItem opacity should not be 0")
45− compare(indicatorItem.opacity < 1, true, "IndicatorItem opacity should not be 1")
38+ indicatorItem.dimmed = false;
39+ tryCompareFunction(function(){return indicatorItem.opacity}, 1.0);
40+ indicatorItem.dimmed = true;
41+ tryCompareFunction(function(){return indicatorItem.opacity < 1.0}, true);
4642 }
4743
4844 function test_highlight() {
49− var itemHighlight = findChild(indicatorItem, "highlight")
50− verify(itemHighlight != undefined)
45+ var itemHighlight = findChild(indicatorItem, "highlight");
46+ verify(itemHighlight != undefined);
5147
5248 indicatorItem.highlighted = true;
53− compare(itemHighlight.visible, true, "Indicator should be highlighted")
49+ compare(itemHighlight.visible, true, "Indicator should be highlighted");
5450
5551 indicatorItem.highlighted = false;
56− compare(itemHighlight.visible, false, "Indicator should not be highlighted")
52+ compare(itemHighlight.visible, false, "Indicator should not be highlighted");
5753 }
5854
5955 function test_empty() {
60− init_test()
61−
62− compare(indicatorItem.visible, false, "IndicatorItem should not be visible.")
56+ compare(indicatorItem.visible, false, "IndicatorItem should not be visible.");
6357 indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
64− tryCompare(indicatorItem, "visible", true)
58+ tryCompare(indicatorItem, "visible", true);
6559 }
6660 }
6761}
tests/qmltests/Panel/tst_IndicatorRow.qml+21−21
@@ -61,13 +61,13 @@ Item {
6161
6262 function test_set_current_item() {
6363 init_test();
64− indicatorRow.setItem(0);
64+ indicatorRow.setCurrentItem(0);
6565 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu1", "Incorrect item at position 0");
6666
67− indicatorRow.setItem(1);
67+ indicatorRow.setCurrentItem(1);
6868 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu2", "Incorrect item at position 1");
6969
70− indicatorRow.setItem(2);
70+ indicatorRow.setCurrentItem(2);
7171 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu3", "Incorrect item at position 2");
7272 }
7373 }
@@ -83,13 +83,13 @@ Item {
8383 function test_current_item_commit() {
8484 init_test();
8585
86− indicatorRow.setItem(1);
86+ indicatorRow.setCurrentItem(1);
8787 indicatorRow.state = "commit";
88− tryCompare(get_indicator_item_at(0), "y", -indicatorRow.height);
89− tryCompare(get_indicator_item_at(1), "y", 0);
90− tryCompare(get_indicator_item_at(2), "y", -indicatorRow.height);
91− tryCompare(get_indicator_item_at(3), "y", -indicatorRow.height);
92− tryCompare(get_indicator_item_at(4), "y", -indicatorRow.height);
88+ tryCompare(get_indicator_item_at(0), "opacity", 0.0);
89+ tryCompare(get_indicator_item_at(1), "opacity", 1.0);
90+ tryCompare(get_indicator_item_at(2), "opacity", 0.0);
91+ tryCompare(get_indicator_item_at(3), "opacity", 0.0);
92+ tryCompare(get_indicator_item_at(4), "opacity", 0.0);
9393
9494 }
9595 }
@@ -106,7 +106,7 @@ Item {
106106 init_test();
107107
108108 indicatorRow.state = "initial";
109− indicatorRow.setItem(0);
109+ indicatorRow.setCurrentItem(0);
110110
111111 compare(indicatorRow.currentItem.highlighted, false, "Indicator should not highlight when in initial state");
112112 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in initial state");
@@ -119,9 +119,9 @@ Item {
119119 init_test();
120120
121121 indicatorRow.state = "hint";
122− indicatorRow.setItem(0);
122+ indicatorRow.setCurrentItem(0);
123123
124− compare(indicatorRow.currentItem.highlighted, false, "Indicator should not highlight when in hint state");
124+ compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in hint state");
125125 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in hint state");
126126 compare(get_indicator_item_at(2).highlighted, false, "Other indicators should not highlight when in hint state");
127127 compare(get_indicator_item_at(3).highlighted, false, "Other indicators should not highlight when in hint state");
@@ -132,7 +132,7 @@ Item {
132132 init_test();
133133
134134 indicatorRow.state = "reveal";
135− indicatorRow.setItem(0);
135+ indicatorRow.setCurrentItem(0);
136136
137137 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in reveal state");
138138 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in commit state");
@@ -145,7 +145,7 @@ Item {
145145 init_test();
146146
147147 indicatorRow.state = "commit";
148− indicatorRow.setItem(0);
148+ indicatorRow.setCurrentItem(0);
149149
150150 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in commit state");
151151 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in commit state");
@@ -158,7 +158,7 @@ Item {
158158 init_test();
159159
160160 indicatorRow.state = "locked";
161− indicatorRow.setItem(0);
161+ indicatorRow.setCurrentItem(0);
162162
163163 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in locked state");
164164 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in locked state");
@@ -180,7 +180,7 @@ Item {
180180 init_test();
181181
182182 indicatorRow.state = "initial";
183− indicatorRow.setItem(0);
183+ indicatorRow.setCurrentItem(0);
184184
185185 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in intiial state");
186186 compare(get_indicator_item_at(1).dimmed, false, "Other indicators should not dim when in initial state");
@@ -193,9 +193,9 @@ Item {
193193 init_test();
194194
195195 indicatorRow.state = "hint";
196− indicatorRow.setItem(0);
196+ indicatorRow.setCurrentItem(0);
197197
198− compare(get_indicator_item_at(0).dimmed, true, "Current indicator should dim when in hint state");
198+ compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in hint state");
199199 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in hint state");
200200 compare(get_indicator_item_at(2).dimmed, true, "Other indicators should dim when in hint state");
201201 compare(get_indicator_item_at(3).dimmed, true, "Other indicators should dim when in hint state");
@@ -206,7 +206,7 @@ Item {
206206 init_test();
207207
208208 indicatorRow.state = "reveal";
209− indicatorRow.setItem(0);
209+ indicatorRow.setCurrentItem(0);
210210
211211 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in reveal state");
212212 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in reveal state");
@@ -219,7 +219,7 @@ Item {
219219 init_test();
220220
221221 indicatorRow.state = "commit";
222− indicatorRow.setItem(0);
222+ indicatorRow.setCurrentItem(0);
223223
224224 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in commit state");
225225 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in commit state");
@@ -232,7 +232,7 @@ Item {
232232 init_test();
233233
234234 indicatorRow.state = "locked";
235− indicatorRow.setItem(0);
235+ indicatorRow.setCurrentItem(0);
236236
237237 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in locked state");
238238 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in locked state");
tests/qmltests/Panel/tst_MenuContent.qml+4−4
@@ -77,15 +77,15 @@ Item {
7777
7878 function activate_next_content()
7979 {
80− if (menuContent.currentIndex == -1)
80+ if (menuContent.currentMenuIndex == -1)
8181 activate_content(0);
8282 else
83− activate_content((menuContent.currentIndex + 1) % indicatorsModel.count)
83+ activate_content((menuContent.currentMenuIndex + 1) % indicatorsModel.count)
8484 }
8585
8686 function activate_content(index)
8787 {
88− menuContent.currentIndex = index
88+ menuContent.currentMenuIndex = index
8989 }
9090
9191 function get_test_menu_objecName(index) {
@@ -197,7 +197,7 @@ Item {
197197
198198 for (var i = 0; i < 100; i++) {
199199 activate_content(i % menu_count);
200− compare(menuContent.currentIndex, i%menu_count);
200+ compare(menuContent.currentMenuIndex, i%menu_count);
201201 }
202202 wait(100);
203203 }
tests/qmltests/Panel/tst_Panel.qml+83−43
@@ -30,11 +30,11 @@ Item {
3030 width: units.gu(40)
3131 height: units.gu(80)
3232
33− property bool search_clicked: false
33+ property bool searchClicked: false
3434
3535 Connections {
3636 target: panel
37− onSearchClicked: search_clicked = true
37+ onSearchClicked: searchClicked = true
3838 }
3939
4040 Panel {
@@ -54,27 +54,26 @@ Item {
5454
5555
5656 function init() {
57− search_clicked = false;
57+ searchClicked = false;
5858 panel.indicators.hide();
5959 tryCompare(panel.indicators.hideAnimation, "running", false);
6060 tryCompare(panel.indicators, "state", "initial");
6161 }
6262
6363 function get_indicator_item(index) {
64− var row_repeater = findChild(panel.indicators, "rowRepeater");
65− verify(row_repeater != undefined);
66− return row_repeater.itemAt(index);
64+ var rowRepeater = findChild(panel.indicators, "rowRepeater");
65+ verify(rowRepeater != undefined);
66+ return rowRepeater.itemAt(index);
6767 }
6868
6969 function get_indicator_item_position(index) {
70+ var indicatorRow = findChild(panel.indicators, "indicatorRow");
71+ verify(indicatorRow != undefined);
7072
71− var indicator_row = findChild(panel.indicators, "indicatorRow");
72− verify(indicator_row != undefined);
73−
74− var indicator_item = get_indicator_item(index);
75− verify(indicator_item != undefined);
73+ var indicatorItem = get_indicator_item(index);
74+ verify(indicatorItem != undefined);
7675
77− return panel.mapFromItem(indicator_item, indicator_item.width/2, indicator_item.height/2);
76+ return panel.mapFromItem(indicatorItem, indicatorItem.width/2, indicatorItem.height/2);
7877 }
7978
8079 // Pressing on the indicator panel should activate the indicator hints
@@ -85,9 +84,9 @@ Item {
8584 // (switches between normal and fullscreen modes are animated)
8685 tryCompare(panel.indicators, "y", 0);
8786
88− var indicator_item_coord = get_indicator_item_position(0);
87+ var indicatorItemCoord = get_indicator_item_position(0);
8988
90− touchPress(panel, indicator_item_coord.x, panel.panelHeight / 2);
89+ touchPress(panel, indicatorItemCoord.x, panel.panelHeight / 2);
9190
9291 // hint animation should be run, meaning that indicators will move downwards
9392 // by hintValue pixels without any drag taking place
@@ -96,7 +95,7 @@ Item {
9695 tryCompare(panel.indicators, "partiallyOpened", true);
9796 tryCompare(panel.indicators, "fullyOpened", false);
9897
99− touchRelease(panel, indicator_item_coord.x, panel.panelHeight/2);
98+ touchRelease(panel, indicatorItemCoord.x, panel.panelHeight/2);
10099 }
101100
102101 // Pressing on the top edge of the screen should have no effect if the panel
@@ -107,9 +106,9 @@ Item {
107106 // (switches between normal and fullscreen modes are animated)
108107 tryCompare(panel.indicators, "y", -panel.panelHeight);
109108
110− var indicator_item_coord = get_indicator_item_position(0);
109+ var indicatorItemCoord = get_indicator_item_position(0);
111110
112− touchPress(panel, indicator_item_coord.x, panel.panelHeight / 2);
111+ touchPress(panel, indicatorItemCoord.x, panel.panelHeight / 2);
113112
114113 // Give some time for a hint animation to change things, if any
115114 wait(500);
@@ -126,7 +125,7 @@ Item {
126125 compare(panel.indicators.fullyOpened, false, "Indicator should not be partially" +
127126 " opened when panel is pressed in fullscreenmode");
128127
129− touchRelease(panel, indicator_item_coord.x, panel.panelHeight/2);
128+ touchRelease(panel, indicatorItemCoord.x, panel.panelHeight/2);
130129 }
131130
132131 function test_drag_show_data() { return get_window_data(); }
@@ -137,14 +136,14 @@ Item {
137136 function test_drag_show(data) {
138137 panel.fullscreenMode = data.fullscreenFlag;
139138
140− var indicator_row = findChild(panel.indicators, "indicatorRow");
141− verify(indicator_row != undefined);
139+ var indicatorRow = findChild(panel.indicators, "indicatorRow");
140+ verify(indicatorRow != undefined);
142141
143− var row_repeater = findChild(panel.indicators, "rowRepeater");
144− verify(indicator_row != undefined);
142+ var rowRepeater = findChild(panel.indicators, "rowRepeater");
143+ verify(indicatorRow != undefined);
145144
146− var menu_content = findChild(panel.indicators, "menuContent");
147− verify(indicator_row != undefined);
145+ var menuContent = findChild(panel.indicators, "menuContent");
146+ verify(indicatorRow != undefined);
148147
149148 // Wait for the indicators to get into position.
150149 // (switches between normal and fullscreen modes are animated)
@@ -155,20 +154,20 @@ Item {
155154 }
156155
157156 // do this for each indicator item
158− for (var i = 0; i < row_repeater.count; i++) {
157+ for (var i = 0; i < rowRepeater.count; i++) {
159158
160− var indicator_item = get_indicator_item(i);
161− verify(indicator_item != undefined);
159+ var indicatorItem = get_indicator_item(i);
160+ verify(indicatorItem != undefined);
162161
163− var indicator_item_coord = get_indicator_item_position(i);
162+ var indicatorItemCoord = get_indicator_item_position(i);
164163
165164 touchPress(panel,
166− indicator_item_coord.x, panel.panelHeight / 2);
165+ indicatorItemCoord.x, panel.panelHeight / 2);
167166
168167 // 1) Drag the mouse down
169168 touchFlick(panel,
170− indicator_item_coord.x, panel.panelHeight / 2,
171− indicator_item_coord.x, panel.height * 0.8,
169+ indicatorItemCoord.x, panel.panelHeight / 2,
170+ indicatorItemCoord.x, panel.height * 0.8,
172171 false /* beginTouch */, false /* endTouch */);
173172
174173 // Indicators height should follow the drag, and therefore increase accordingly.
@@ -177,13 +176,13 @@ Item {
177176 function() {return panel.indicators.height >= panel.height * 0.5},
178177 true);
179178
180− touchRelease(panel, indicator_item_coord.x, panel.height * 0.8);
179+ touchRelease(panel, indicatorItemCoord.x, panel.height * 0.8);
181180
182− compare(indicator_row.currentItem, indicator_item,
181+ compare(indicatorRow.currentItem, indicatorItem,
183182 "Incorrect item activated at position " + i);
184− compare(menu_content.currentIndex, i, "Menu conetent should be enabled for item at position " + i);
183+ compare(menuContent.currentMenuIndex, i, "Menu conetent should be enabled for item at position " + i);
185184
186− // init for next indicator_item
185+ // init for next indicatorItem
187186 init();
188187 }
189188 }
@@ -192,12 +191,12 @@ Item {
192191 panel.fullscreenMode = false;
193192 panel.searchVisible = true;
194193
195− var search_indicator = findChild(panel, "search");
196− verify(search_indicator != undefined);
194+ var searchIndicator = findChild(panel, "search");
195+ verify(searchIndicator != undefined);
197196
198− tap(search_indicator, 1, 1);
197+ tap(searchIndicator, 1, 1);
199198
200− compare(search_clicked, true,
199+ compare(searchClicked, true,
201200 "Tapping search indicator while it was enabled did not emit searchClicked signal");
202201 }
203202
@@ -205,13 +204,54 @@ Item {
205204 panel.fullscreenMode = false;
206205 panel.searchVisible = false;
207206
208− var search_indicator = findChild(panel, "search");
209− verify(search_indicator != undefined);
207+ var searchIndicator = findChild(panel, "search");
208+ verify(searchIndicator != undefined);
210209
211− tap(search_indicator, 1, 1);
210+ tap(searchIndicator, 1, 1);
212211
213− compare(search_clicked, false,
212+ compare(searchClicked, false,
214213 "Tapping search indicator while it was not visible emitted searchClicked signal");
215214 }
215+
216+ // Test the vertical velocity check when flicking the indicators open at an angle.
217+ // If the vertical velocity is above a specific point, we shouldnt change active indicators
218+ // if the x position changes
219+ function test_vertical_velocity_detector() {
220+ panel.fullscreenMode = false;
221+ panel.searchVisible = false;
222+
223+ var indicatorRow = findChild(panel.indicators, "indicatorRow");
224+ verify(indicatorRow != undefined);
225+
226+ var rowRepeater = findChild(panel.indicators, "rowRepeater");
227+ verify(indicatorRow != undefined);
228+
229+ // Get the first indicator
230+ var indicatorItemFirst = get_indicator_item(0);
231+ verify(indicatorItemFirst != undefined);
232+
233+ var indicatorItemCoordFirst = get_indicator_item_position(0);
234+ var indicatorItemCoordNext = get_indicator_item_position(indicatorRow.count - 1);
235+
236+ touchPress(panel,
237+ indicatorItemCoordFirst.x, panel.panelHeight / 2);
238+
239+ // 1) Drag the mouse down to hint a bit
240+ touchFlick(panel,
241+ indicatorItemCoordFirst.x, panel.panelHeight / 2,
242+ indicatorItemCoordFirst.x, panel.panelHeight * 2,
243+ false /* beginTouch */, false /* endTouch */);
244+
245+ tryCompare(indicatorRow, "currentItem", indicatorItemFirst)
246+
247+ // 1) Flick mouse down to bottom
248+ touchFlick(panel,
249+ indicatorItemCoordFirst.x, panel.panelHeight * 2,
250+ indicatorItemCoordNext.x, panel.height,
251+ false /* beginTouch */, true /* endTouch */,
252+ units.gu(10) /* speed */, 30 /* iterations */); // more samples needed for accurate velocity
253+
254+ compare(indicatorRow.currentItem, indicatorItemFirst, "First indicator should still be the current item");
255+ }
216256 }
217257}
tests/qmltests/Panel/tst_SearchIndicator.qml+8−10
@@ -41,24 +41,22 @@ Item {
4141 when: windowShown
4242
4343 function test_clickedSignal() {
44− clickedSpy.clear()
44+ clickedSpy.clear();
4545 mouseClick(searchIndicator,
4646 searchIndicator.width / 2, searchIndicator.height / 2);
47− compare(clickedSpy.count, 1)
47+ compare(clickedSpy.count, 1);
4848 }
4949
5050 function test_hideUp() {
51− var container = findChild(searchIndicator, "container")
52− searchIndicator.state = "hiddenUp"
53− tryCompare(container, "opacity", 0)
54− tryCompare(container, "y", -container.height)
51+ var container = findChild(searchIndicator, "container");
52+ searchIndicator.state = "hidden";
53+ tryCompare(container, "opacity", 0);
5554 }
5655
5756 function test_show() {
58− var container = findChild(searchIndicator, "container")
59− searchIndicator.state = "visible"
60− tryCompare(container, "opacity", 1)
61− tryCompare(container, "y", 0)
57+ var container = findChild(searchIndicator, "container");
58+ searchIndicator.state = "visible";
59+ tryCompare(container, "opacity", 1);
6260 }
6361 }
6462}
tests/qmltests/tst_Shell.qmlnew file+281
@@ -0,0 +1,281 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Daniel d'Andrada <daniel.dandrada@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import QtQuick 2.0
21+import QtTest 1.0
22+import Ubuntu.Application 0.1
23+import Unity.Test 0.1 as UT
24+
25+import "../.."
26+
27+Item {
28+ width: shell.width
29+ height: shell.height
30+
31+ QtObject {
32+ id: applicationArguments
33+
34+ function hasGeometry() {
35+ return false;
36+ }
37+
38+ function width() {
39+ return 0;
40+ }
41+
42+ function height() {
43+ return 0;
44+ }
45+ }
46+
47+ Shell {
48+ id: shell
49+ }
50+
51+ UT.UnityTestCase {
52+ name: "Shell"
53+ when: windowShown
54+
55+ function initTestCase() {
56+ // swipe away the greeter/lockscreen
57+ var touchX = shell.width - (shell.edgeSize / 2);
58+ var touchY = shell.height / 2;
59+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
60+
61+ var dash = findChild(shell, "dash");
62+ // wait until the animation has finished
63+ tryCompare(dash, "contentScale", 1.0);
64+ tryCompare(dash, "opacity", 1.0);
65+ }
66+
67+ function cleanup() {
68+ // kill all (fake) running apps
69+ ApplicationManager.mainStageApplications.clear();
70+ ApplicationManager.sideStageApplications.clear();
71+
72+ var dashHome = findChild(shell, "DashHome");
73+ swipeUntilScopeViewIsReached(dashHome);
74+ }
75+
76+ /*
77+ Test the effect of a right-edge drag on the dash in 3 situations:
78+ 1 - when no application has been launched yet
79+ 2 - when there's a minimized application
80+ 3 - after the last running application has been closed/stopped
81+
82+ The behavior of Dash on 3 should be the same as on 1.
83+ */
84+ function test_rightEdgeDrag() {
85+ checkRightEdgeDragWithNoRunningApps();
86+
87+ dragLauncherIntoView();
88+
89+ // Launch an app from the launcher
90+ tapOnAppIconInLauncher();
91+ waitUntilApplicationWindowIsFullyVisible();
92+
93+ // Minimize the application we just launched
94+ swipeFromLeftEdge();
95+
96+ waitForUIToSettle();
97+
98+ checkRightEdgeDragWithMinimizedApp();
99+
100+ // Minimize that application once again
101+ swipeFromLeftEdge();
102+
103+ // kill it
104+ ApplicationManager.mainStageApplications.clear();
105+
106+ waitForUIToSettle();
107+
108+ // Right edge behavior should now be the same as before that app was launched
109+ checkRightEdgeDragWithNoRunningApps();
110+ }
111+
112+ /*
113+ Perform a right-edge drag when the Dash is being show and there are
114+ no running/minimized apps to be restored.
115+
116+ The expected behavior is that an animation should be played to hint the
117+ user that his right-edge drag gesture has been successfully recognized
118+ but there is no application to be brought to foreground.
119+ */
120+ function checkRightEdgeDragWithNoRunningApps() {
121+ var touchX = shell.width - (shell.edgeSize / 2);
122+ var touchY = shell.height / 2;
123+
124+ var dash = findChild(shell, "dash");
125+ // check that dash has normal scale and opacity
126+ compare(dash.contentScale, 1.0);
127+ compare(dash.opacity, 1.0);
128+
129+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
130+ true /* beginTouch */, false /* endTouch */);
131+
132+ // check that Dash has been scaled down and had its opacity reduced
133+ tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
134+ tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
135+
136+ touchRelease(shell, shell.width * 0.1, touchY);
137+
138+ // and now everything should have gone back to normal
139+ tryCompare(dash, "contentScale", 1.0);
140+ tryCompare(dash, "opacity", 1.0);
141+ }
142+
143+ /*
144+ Perform a right-edge drag when the Dash is being show and there is
145+ a running/minimized app to be restored.
146+
147+ The expected behavior is that the dash should fade away and ultimately be
148+ made invisible once the gesture is finished as the restored app will now
149+ be on foreground.
150+ */
151+ function checkRightEdgeDragWithMinimizedApp() {
152+ var touchX = shell.width - (shell.edgeSize / 2);
153+ var touchY = shell.height / 2;
154+
155+ var dash = findChild(shell, "dash");
156+ // check that dash has normal scale and opacity
157+ tryCompare(dash, "contentScale", 1.0);
158+ tryCompare(dash, "opacity", 1.0);
159+
160+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
161+ true /* beginTouch */, false /* endTouch */);
162+
163+ // check that Dash has been scaled down and had its opacity reduced
164+ tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
165+ tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
166+
167+ touchRelease(shell, shell.width * 0.1, touchY);
168+
169+ // dash should have gone away, now that the app is on foreground
170+ tryCompare(dash, "visible", false);
171+ }
172+
173+ // Wait for the whole UI to settle down
174+ function waitForUIToSettle() {
175+ waitUntilApplicationWindowIsFullyHidden();
176+ var dashContentList = findChild(shell, "dashContentList");
177+ tryCompare(dashContentList, "moving", false);
178+ }
179+
180+ function dragLauncherIntoView() {
181+ var launcherPanel = findChild(shell, "launcherPanel");
182+ verify(launcherPanel.x = - launcherPanel.width);
183+ swipeFromLeftEdge();
184+ tryCompare(launcherPanel, "x", 0);
185+ }
186+
187+ function tapOnAppIconInLauncher() {
188+ var launcherPanel = findChild(shell, "launcherPanel");
189+
190+ // pick the first icon, the one at the bottom.
191+ var appIcon = findChild(launcherPanel, "launcherDelegate0")
192+
193+ // Swipe upwards over the launcher to ensure that this icon
194+ // at the bottom is not folded and faded away.
195+ var touchStartX = launcherPanel.width / 2;
196+ var touchStartY = launcherPanel.height / 2;
197+ touchFlick(launcherPanel, touchStartX, touchStartY, touchStartX, 0);
198+ tryCompare(launcherPanel, "moving", false);
199+
200+ // NB tapping (i.e., using touch events) doesn't activate the icon... go figure...
201+ mouseClick(appIcon, appIcon.width / 2, appIcon.height / 2);
202+ }
203+
204+ function waitUntilApplicationWindowIsFullyVisible() {
205+ var underlay = findChild(shell, "underlay");
206+ tryCompare(underlay, "visible", false);
207+ }
208+
209+ function waitUntilApplicationWindowIsFullyHidden() {
210+ var stages = findChild(shell, "stages");
211+ tryCompare(stages, "fullyHidden", true);
212+ }
213+
214+ function swipeUntilScopeViewIsReached(scopeView) {
215+ while (!itemIsOnScreen(scopeView)) {
216+ if (itemIsToLeftOfScreen(scopeView)) {
217+ swipeRightFromCenter();
218+ } else {
219+ swipeLeftFromCenter();
220+ }
221+ waitUntilItemStopsMoving(scopeView);
222+ }
223+ }
224+
225+ function swipeFromLeftEdge() {
226+ var touchStartX = 2;
227+ var touchStartY = shell.height / 2;
228+ touchFlick(shell, touchStartX, touchStartY, shell.width * 0.75, touchStartY);
229+ }
230+
231+ function swipeLeftFromCenter() {
232+ var touchStartX = shell.width / 2;
233+ var touchStartY = shell.height / 2;
234+ touchFlick(shell, touchStartX, touchStartY, 0, touchStartY);
235+ }
236+
237+ function swipeRightFromCenter() {
238+ var touchStartX = shell.width / 2;
239+ var touchStartY = shell.height / 2;
240+ touchFlick(shell, touchStartX, touchStartY, shell.width, touchStartY);
241+ }
242+
243+ function swipeUpFromCenter() {
244+ var touchStartX = shell.width / 2;
245+ var touchStartY = shell.height / 2;
246+ touchFlick(shell, touchStartX, touchStartY, touchStartX, 0);
247+ }
248+
249+ function itemIsOnScreen(item) {
250+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
251+
252+ return itemRectInShell.x >= 0
253+ && itemRectInShell.y >= 0
254+ && itemRectInShell.x + itemRectInShell.width <= shell.width
255+ && itemRectInShell.y + itemRectInShell.height <= shell.height;
256+ }
257+
258+ function itemIsToLeftOfScreen(item) {
259+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
260+ return itemRectInShell.x < 0;
261+ }
262+
263+ function waitUntilItemStopsMoving(item) {
264+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
265+ var previousX = itemRectInShell.x;
266+ var previousY = itemRectInShell.y;
267+ var isStill = false;
268+
269+ do {
270+ wait(100);
271+ itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
272+ if (itemRectInShell.x == previousX && itemRectInShell.y == previousY) {
273+ isStill = true;
274+ } else {
275+ previousX = itemRectInShell.x;
276+ previousY = itemRectInShell.y;
277+ }
278+ } while (!isStill);
279+ }
280+ }
281+}
tests/utils/modules/Unity/Test/UnityTestCase.qml+22−18
@@ -32,32 +32,34 @@ TestCase {
3232 // Qt uses in QQuickViewTestUtil::flick
3333 // speed is in pixels/second
3434 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
35− speed) {
35+ speed, iterations) {
3636 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
3737 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
3838
3939 // set a default speed if not specified
4040 speed = (speed != null) ? speed : units.gu(10);
4141
42+ // set a default iterations if not specified
43+ iterations = (iterations !== undefined) ? iterations : 5
44+
4245 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
4346 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
4447
45− var nIterations = 5
46− var timeStep = totalTime / nIterations
47− var diffX = (toX - x) / nIterations
48− var diffY = (toY - y) / nIterations
48+ var timeStep = totalTime / iterations
49+ var diffX = (toX - x) / iterations
50+ var diffY = (toY - y) / iterations
4951 if (pressMouse) {
5052 fakeDateTime.currentTimeMs += timeStep
5153 mousePress(item, x, y)
5254 }
53− for (var i = 0; i < nIterations; ++i) {
55+ for (var i = 0; i < iterations; ++i) {
5456 fakeDateTime.currentTimeMs += timeStep
55− if (i === nIterations - 1) {
57+ if (i === iterations - 1) {
5658 // Avoid any rounding errors by making the last move be at precisely
5759 // the point specified
58− mouseMove(item, toX, toY, nIterations / speed)
60+ mouseMove(item, toX, toY, iterations / speed)
5961 } else {
60− mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, nIterations / speed)
62+ mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
6163 }
6264 }
6365 if (releaseMouse) {
@@ -186,7 +188,7 @@ TestCase {
186188 }
187189
188190 // speed is in pixels/second
189− function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed) {
191+ function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
190192
191193 // Default to true for beginTouch if not present
192194 beginTouch = (beginTouch !== undefined) ? beginTouch : true
@@ -197,13 +199,15 @@ TestCase {
197199 // Set a default speed if not specified
198200 speed = (speed !== undefined) ? speed : units.gu(10)
199201
202+ // Set a default iterations if not specified
203+ var iterations = (iterations !== undefined) ? iterations : 5
204+
200205 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
201206 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
202207
203− var nIterations = 5
204− var timeStep = totalTime / nIterations
205− var diffX = (toX - x) / nIterations
206− var diffY = (toY - y) / nIterations
208+ var timeStep = totalTime / iterations
209+ var diffX = (toX - x) / iterations
210+ var diffY = (toY - y) / iterations
207211 if (beginTouch) {
208212 fakeDateTime.currentTimeMs += timeStep
209213
@@ -211,17 +215,17 @@ TestCase {
211215 event.press(0 /* touchId */, x, y)
212216 event.commit()
213217 }
214− for (var i = 0; i < nIterations; ++i) {
218+ for (var i = 0; i < iterations; ++i) {
215219 fakeDateTime.currentTimeMs += timeStep
216− if (i === nIterations - 1) {
220+ if (i === iterations - 1) {
217221 // Avoid any rounding errors by making the last move be at precisely
218222 // the point specified
219− wait(nIterations / speed)
223+ wait(iterations / speed)
220224 var event = touchEvent()
221225 event.move(0 /* touchId */, toX, toY)
222226 event.commit()
223227 } else {
224− wait(nIterations / speed)
228+ wait(iterations / speed)
225229 var event = touchEvent()
226230 event.move(0 /* touchId */, x + (i + 1) * diffX, y + (i + 1) * diffY)
227231 event.commit()