on xenial help sign in
Merge trunk and fix conflicts
commit01061252f1b9dd8638cd2e06d07d7582039d5edd authorAndrea Cimitan <andrea.cimitan@canonical.com> dateFri 26 Jul 2013, 12:10 (13 years ago) parentccedc8af2c2cdb
64 files changed+2321−962
ApplicationArguments.h+49 CMakeLists.txt+1 Dash/Dash.qml−3 Dash/DashApps.qml+1−1 Dash/DashContent.qml+5−2 Dash/GenericScopeView.qml+9 Notifications/Notification.qml+4−3 Notifications/Notifications.qml+1 Panel/IndicatorItem.qml+5 Panel/IndicatorRow.qml+34−48 Panel/Indicators.qml+89−101 Panel/MenuContent.qml+13−3 Panel/Panel.qml+28−9 Panel/SearchIndicator.qml+3−5 Shell.qml+2−2 cmake/modules/autopilot.cmake+2−1 debian/changelog+35 debian/control+2 main.cpp+4−6 plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+9−5 plugins/Unity/Indicators/CMakeLists.txt+1 plugins/Unity/Indicators/menucontentactivator.cpp+345 plugins/Unity/Indicators/menucontentactivator.h+117 plugins/Unity/Indicators/plugin.cpp+3 plugins/Unity/Indicators/qml/IndicatorPage.qml−6 plugins/Unity/categories.cpp+4−1 plugins/Unity/categories.h+2−1 po/uk.po+4−4 tests/autopilot/setup.py+17−6 tests/autopilot/unity8/__init__.py+90−5 tests/autopilot/unity8/indicators_client/__init__.py+1−1 tests/autopilot/unity8/indicators_client/tests/__init__.py+13−9 tests/autopilot/unity8/indicators_client/tests/test_battery.py+14−3 tests/autopilot/unity8/shell/__init__.py+35−5 tests/autopilot/unity8/shell/emulators/__init__.py+23−4 tests/autopilot/unity8/shell/emulators/dash.py+52 tests/autopilot/unity8/shell/emulators/greeter.py+40 tests/autopilot/unity8/shell/emulators/hud.py+83 tests/autopilot/unity8/shell/emulators/launcher.py+38 tests/autopilot/unity8/shell/emulators/main_window.py+43−12 tests/autopilot/unity8/shell/tests/__init__.py+216−58 tests/autopilot/unity8/shell/tests/helpers.py−161 tests/autopilot/unity8/shell/tests/test_hud.py+151 tests/autopilot/unity8/shell/tests/test_lock_screen.py+131 tests/autopilot/unity8/shell/tests/testbigscreen.py−25 tests/autopilot/unity8/shell/tests/testhud.py−145 tests/autopilot/unity8/shell/tests/testlockscreen.py−118 tests/mocks/Unity/Indicators/CMakeLists.txt+1 tests/mocks/Unity/Indicators/fakeplugin.cpp+4 tests/mocks/Unity/Notifications/CMakeLists.txt−4 tests/mocks/Unity/fake_categories.cpp+5−2 tests/mocks/Unity/fake_categories.h+2−1 tests/plugins/Unity/Indicators/CMakeLists.txt+1 tests/plugins/Unity/Indicators/menucontentactivatortest.cpp+303 tests/qmltests/Dash/tst_Dash.qml+4−4 tests/qmltests/Dash/tst_DashContent.qml+12−12 tests/qmltests/Notifications/tst_Notifications.qml+20−9 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+102−65 tests/utils/modules/Unity/Test/UnityTestCase.qml+22−18
ApplicationArguments.hnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authored by: Nick Dedekind <nick.dedekind@canonical.com>
17+ */
18+
19+
20+#ifndef APPLICATION_ARGUMENTS_H
21+#define APPLICATION_ARGUMENTS_H
22+
23+#include <QObject>
24+#include <QSize>
25+#include <QStringList>
26+
27+class ApplicationArguments : public QObject
28+{
29+ Q_OBJECT
30+public:
31+ ApplicationArguments(const QStringList& args) {
32+ if (args.contains(QLatin1String("-geometry")) && args.size() > args.indexOf(QLatin1String("-geometry")) + 1) {
33+ QStringList geometryArg = args.at(args.indexOf(QLatin1String("-geometry")) + 1).split('x');
34+ if (geometryArg.size() == 2) {
35+ m_size.rwidth() = geometryArg.at(0).toInt();
36+ m_size.rheight() = geometryArg.at(1).toInt();
37+ }
38+ }
39+ }
40+
41+ Q_INVOKABLE bool hasGeometry() const { return m_size.isValid(); }
42+ Q_INVOKABLE int width() const { return m_size.width(); }
43+ Q_INVOKABLE int height() const { return m_size.height(); }
44+
45+private:
46+ QSize m_size;
47+};
48+
49+#endif // APPLICATION_ARGUMENTS_H
CMakeLists.txt+1
@@ -101,6 +101,7 @@ set(shell_app_HDRS
101101
102102add_executable(${SHELL_APP}
103103 ${shellapplication_MOC_SRCS}
104+ ApplicationArguments.h
104105 main.cpp
105106 MouseTouchAdaptor.cpp
106107 )
Dash/Dash.qml−3
@@ -27,9 +27,6 @@ Showable {
2727 property string showScopeOnLoaded: "home.scope"
2828 property real contentScale: 1.0
2929
30− width: units.gu(40)
31− height: units.gu(71)
32−
3330 function setCurrentScope(scopeId, animate, reset) {
3431 var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId)
3532
Dash/DashApps.qml+1−1
@@ -88,7 +88,7 @@ ScopeView {
8888 model: scopeView.scope.categories
8989
9090 delegate: Item {
91− Component.onCompleted: if (index == 3) scopeView.installedModel = Qt.binding(function() { return model.results; });
91+ Component.onCompleted: if (id == "installed") scopeView.installedModel = Qt.binding(function() { return model.results; });
9292 }
9393 }
9494
Dash/DashContent.qml+5−2
@@ -20,8 +20,6 @@ import Unity 0.1
2020
2121Item {
2222 id: dashContent
23− width: units.gu(40)
24− height: units.gu(71)
2523
2624 property var model: null
2725 property var scopes: null
@@ -117,6 +115,11 @@ Item {
117115 asynchronous: true
118116 source: scopeMapper.map(scope.id)
119117
118+ // these are needed for autopilot tests
119+ readonly property string scopeId: scope.id
120+ readonly property bool isCurrent: ListView.isCurrentItem
121+ readonly property bool isLoaded: status == Loader.Ready
122+
120123 onLoaded: {
121124 item.scope = Qt.binding(function() { return scope })
122125 item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
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) {
Notifications/Notification.qml+4−3
@@ -28,6 +28,7 @@ UbuntuShape {
2828 property var actions
2929 property var notificationId
3030 property var type
31+ property var hints
3132 property var notification
3233
3334 objectName: "background"
@@ -164,7 +165,7 @@ UbuntuShape {
164165 bottom: parent.bottom
165166 }
166167 text: notification.type == Notification.SnapDecision && actionRepeater.count >= 2 ? actionRepeater.itemAt(1).actionLabel : ""
167− color: "#cdcdcb"
168+ gradient: UbuntuColors.greyGradient
168169 onClicked: {
169170 if (actionRepeater.count > 2) {
170171 buttonRow.expanded = !buttonRow.expanded
@@ -194,7 +195,7 @@ UbuntuShape {
194195 top: parent.top
195196 bottom: parent.bottom
196197 }
197− color: "#d85317" // FIXME ??
198+ gradient: notification.hints == Notification.ButtonTint ? UbuntuColors.orangeGradient : UbuntuColors.greyGradient
198199 visible: width > 0
199200 onClicked: notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
200201 }
@@ -241,7 +242,7 @@ UbuntuShape {
241242
242243 text: loader.actionLabel
243244 height: units.gu(4)
244− color: "#cdcdcb" // FIXME ?? which color?
245+ gradient: UbuntuColors.greyGradient
245246 onClicked: notification.notification.invokeAction(loader.actionId)
246247 }
247248 }
Notifications/Notifications.qml+1
@@ -32,6 +32,7 @@ ListView {
3232 right: parent.right
3333 }
3434 type: model.type
35+ hints: model.hints
3536 iconSource: model.icon
3637 secondaryIconSource: model.secondaryIcon
3738 summary: model.summary
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−101
@@ -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,57 +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− }
259+ Connections {
260+ target: menuContent
261+ onCurrentMenuIndexChanged: {
262+ indicatorRow.setCurrentItem(menuContent.currentMenuIndex);
270263 }
271264 }
272265 }
266+
273267 Connections {
274268 target: showAnimation
275269 onRunningChanged: {
276270 if (showAnimation.running) {
277− indicators.calculateCurrentItem(dragHandle.touchX, false);
278271 indicators.state = "commit";
279272 }
280273 }
281274 }
282275
283276 Connections {
284− target: dragHandle
285− onTouchXChanged: {
286− var buffer = dragHandle.dragging ? true : false;
287− indicators.calculateCurrentItem(dragHandle.touchX, buffer);
277+ target: hideAnimation
278+ onRunningChanged: {
279+ if (hideAnimation.running) {
280+ indicators.state = "initial";
281+ indicatorRow.currentItem = null;
282+ }
288283 }
289284 }
290285
291− // We start with pinned states. (default pinnedMode: true)
292− states: pinnedModeStates
293− // because of dynamic assignment of states, we need to assign state after completion.
294− Component.onCompleted: state = "initial"
295−
296− // changing states will reset state to "".
297− onPinnedModeChanged: {
298− var last_state = state;
299− states = (pinnedMode) ? pinnedModeStates : offScreenModeStates;
300− 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+ }
301296 }
302297
303− property var dragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle
304298 DragHandle {
305299 id: showDragHandle
306300 anchors.bottom: parent.bottom
@@ -314,7 +308,7 @@ Showable {
314308 hintDisplacement: pinnedMode ? indicators.hintValue : 0
315309 autoCompleteDragThreshold: maxTotalDragDistance / 2
316310 stretch: true
317− maxTotalDragDistance: openedHeight - handle.height
311+ maxTotalDragDistance: openedHeight - panelHeight
318312 distanceThreshold: pinnedMode ? 0 : units.gu(3)
319313
320314 onStatusChanged: {
@@ -326,28 +320,40 @@ Showable {
326320 DragHandle {
327321 id: hideDragHandle
328322 anchors.fill: handle
329− height: panelHeight
330323 direction: Direction.Upwards
331324 enabled: indicators.shown
332− hintDisplacement: units.gu(2)
325+ hintDisplacement: indicators.hintValue
333326 autoCompleteDragThreshold: maxTotalDragDistance / 6
334327 stretch: true
335− maxTotalDragDistance: referenceOpenedHeight - 2*panelHeight
328+ maxTotalDragDistance: openedHeight - panelHeight
336329 distanceThreshold: 0
337330 }
338331
339− property list<State> offScreenModeStates: [
332+ AxisVelocityCalculator {
333+ id: yVelocityCalculator
334+ }
335+
336+ states: [
340337 State {
341338 name: "initial"
342339 },
343340 State {
344341 name: "hint"
345− 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+ }
346353 },
347354 State {
348355 name: "reveal"
349356 extend: "hint"
350− StateChangeScript { script: calculateCurrentItem(dragHandle.touchX, false); }
351357 },
352358 State {
353359 name: "locked"
@@ -358,25 +364,7 @@ Showable {
358364 extend: "hint"
359365 }
360366 ]
361−
362− property list<State> pinnedModeStates: [
363− State {
364− name: "initial"
365− },
366− State {
367− name: "hint"
368− },
369− State {
370− name: "reveal"
371− StateChangeScript { script: calculateCurrentItem(dragHandle.touchX, false); }
372− },
373− State {
374− name: "locked"
375− },
376− State {
377− name: "commit"
378− }
379− ]
367+ state: "initial"
380368
381369 transitions: [
382370 Transition {
Panel/MenuContent.qml+13−3
@@ -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
@@ -63,7 +63,7 @@ Rectangle {
6363
6464 delegate: Loader {
6565 clip: true
66− property bool contentActive: content.__contentActive
66+ property bool contentActive: content.__contentActive && menuActivator.content[index].active
6767
6868 onContentActiveChanged: {
6969 if (contentActive && item) {
@@ -137,6 +137,16 @@ Rectangle {
137137 id: contentReleaseTimer
138138
139139 interval: contentReleaseInterval
140− onTriggered: __contentActive = false
140+ onTriggered: {
141+ __contentActive = false;
142+ menuActivator.clear();
143+ }
144+ }
145+
146+ Indicators.MenuContentActivator {
147+ id: menuActivator
148+ running: content.__contentActive
149+ baseIndex: content.currentMenuIndex
150+ count: indicatorsModel.count
141151 }
142152}
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+2−2
@@ -37,8 +37,8 @@ FocusScope {
3737
3838 // this is only here to select the width / height of the window if not running fullscreen
3939 property bool tablet: false
40− width: tablet ? units.gu(160) : units.gu(40)
41− height: tablet ? units.gu(100) : units.gu(71)
40+ width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41+ height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
4242
4343 property real edgeSize: units.gu(2)
4444 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
cmake/modules/autopilot.cmake+2−1
@@ -2,8 +2,9 @@ add_custom_target(autopilot)
22
33function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
44 add_custom_target(autopilot-${TEST_NAME}
5− COMMAND autopilot run ${TEST_SUITE}
5+ COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_PRIVATE_LIBDIR}/qml/mocks autopilot run ${TEST_SUITE}
66 WORKING_DIRECTORY ${WORKING_DIR}
7+ DEPENDS install
78 )
89
910 add_dependencies(autopilot autopilot-${TEST_NAME})
debian/changelog+35
@@ -1,3 +1,38 @@
1+unity8 (7.81.3+13.10.20130725ubuntu.unity.next-0ubuntu1) saucy; urgency=low
2+
3+ [ Michal Hruby ]
4+ * Fix adding items into an empty LVWPH.
5+
6+ [ Michał Sawicz ]
7+ * Move to using upstart in run_on_device.
8+ * Added heeding and qmltest for button-tint hint.
9+ * Refactoring and cleanup of the Unity8 Autopilot tests.
10+ * Hide the Unity launcher during autopiloting and skip battery tests
11+ if unavailable.
12+
13+ [ Ying-Chun Liu ]
14+ * Let GenericScope support loading different renderers.
15+
16+ [ Nick Dedekind ]
17+ * Removed overview from indicators.
18+ * Menus for indicators is now created prioritised by distance from
19+ current item to speed up user experience.
20+ * Add ApplicationArguments to know the geometry from start.
21+
22+ [ Mirco Müller ]
23+ * Added heeding and qmltest for button-tint hint.
24+
25+ [ Christopher Lee ]
26+ * Refactoring and cleanup of the Unity8 Autopilot tests.
27+
28+ [ Thomi Richards ]
29+ * Refactoring and cleanup of the Unity8 Autopilot tests.
30+
31+ [ Ubuntu daily release ]
32+ * Automatic snapshot from revision 129 (ubuntu-unity/next)
33+
34+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 25 Jul 2013 03:02:12 +0000
35+
136unity8 (7.81.3+13.10.20130718ubuntu.unity.next-0ubuntu1) saucy; urgency=low
237
338 [ Andrea Cimitan ]
debian/control+2
@@ -89,6 +89,8 @@ Depends: indicators-client (>= ${source:Version}),
8989 ${misc:Depends},
9090 ${python:Depends},
9191 ${shlibs:Depends},
92+Recommends: gir1.2-glib-2.0,
93+ python-gi,
9294Description: Test package for Unity 8 shell
9395 Autopilot tests for the unity8 package
9496
main.cpp+4−6
@@ -18,6 +18,7 @@
1818 */
1919
2020// Qt
21+#include <QtQml>
2122#include <QtQuick/QQuickView>
2223#include <QtGui/QIcon>
2324#include <QtGui/QGuiApplication>
@@ -30,6 +31,7 @@
3031// local
3132#include "paths.h"
3233#include "MouseTouchAdaptor.h"
34+#include "ApplicationArguments.h"
3335
3436namespace {
3537
@@ -86,6 +88,7 @@ int main(int argc, char** argv)
8688 resolveIconTheme();
8789
8890 QStringList args = application.arguments();
91+ ApplicationArguments qmlArgs(args);
8992
9093 // The testability driver is only loaded by QApplication but not by QGuiApplication.
9194 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
@@ -110,6 +113,7 @@ int main(int argc, char** argv)
110113 view->setResizeMode(QQuickView::SizeRootObjectToView);
111114 view->setTitle("Qml Phone Shell");
112115 view->engine()->setBaseUrl(QUrl::fromLocalFile(::shellAppDirectory()));
116+ view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
113117 if (args.contains(QLatin1String("-frameless"))) {
114118 view->setFlags(Qt::FramelessWindowHint);
115119 }
@@ -141,12 +145,6 @@ int main(int argc, char** argv)
141145 if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || args.contains(QLatin1String("-fullscreen"))) {
142146 view->showFullScreen();
143147 } else {
144− if (args.contains(QLatin1String("-geometry")) && args.size() > args.indexOf(QLatin1String("-geometry")) + 1) {
145− QStringList geometryArg = args.at(args.indexOf(QLatin1String("-geometry")) + 1).split('x');
146− if (geometryArg.size() == 2) {
147− view->resize(geometryArg.at(0).toInt(), geometryArg.at(1).toInt());
148− }
149− }
150148 view->show();
151149 }
152150
plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+9−5
@@ -785,10 +785,14 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
785785 }
786786 if (growDown) {
787787 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);
788+ } else if (remove.index <= m_firstVisibleIndex) {
789+ if (!m_visibleItems.isEmpty()) {
790+ // We removed the first item that is the one that positions the rest
791+ // position the new first item correctly
792+ m_visibleItems.first()->setY(oldFirstValidIndexPos);
793+ } else {
794+ m_firstVisibleIndex = -1;
795+ }
792796 }
793797 } else if (remove.index + remove.count <= m_firstVisibleIndex) {
794798 m_firstVisibleIndex -= remove.count;
@@ -806,7 +810,7 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
806810 Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
807811// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
808812 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();
813+ const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
810814 if (insertingInValidIndexes || firstItemWithViewOnTop)
811815 {
812816 // If the items we are adding won't be really visible
plugins/Unity/Indicators/CMakeLists.txt+1
@@ -23,6 +23,7 @@ set(IndicatorsQML_SOURCES
2323 indicators.h
2424 indicatorsmanager.cpp
2525 indicatorsmodel.cpp
26+ menucontentactivator.cpp
2627 plugin.cpp
2728)
2829add_definitions(-DUNITYINDICATORS_LIBRARY)
plugins/Unity/Indicators/menucontentactivator.cppnew file+345
@@ -0,0 +1,345 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Nick Dedekind <nick.dedekind@canonical.com>
17+ */
18+
19+ #include "menucontentactivator.h"
20+
21+// Essentially a QTimer wrapper
22+class ContentTimer : public UnityIndicators::AbstractTimer
23+{
24+ Q_OBJECT
25+public:
26+ ContentTimer(QObject *parent) : UnityIndicators::AbstractTimer(parent) {
27+ m_timer.setSingleShot(false);
28+ connect(&m_timer, &QTimer::timeout,
29+ this, &UnityIndicators::AbstractTimer::timeout);
30+ }
31+ virtual int interval() const { return m_timer.interval(); }
32+ virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
33+ virtual void start() { m_timer.start(); UnityIndicators::AbstractTimer::start(); }
34+ virtual void stop() { m_timer.stop(); UnityIndicators::AbstractTimer::stop(); }
35+private:
36+ QTimer m_timer;
37+};
38+
39+class MenuContentActivatorPrivate : public QObject
40+{
41+ Q_OBJECT
42+public:
43+ MenuContentActivatorPrivate(MenuContentActivator* parent)
44+ : m_running(false),
45+ m_baseIndex(0),
46+ m_delta(0),
47+ m_count(0),
48+ m_timer(NULL),
49+ q(parent)
50+ {}
51+
52+ ~MenuContentActivatorPrivate()
53+ {
54+ qDeleteAll(m_content);
55+ m_content.clear();
56+ }
57+
58+ int findNextInactiveDelta(bool* finished = NULL);
59+
60+ static int content_count(QQmlListProperty<MenuContentState> *prop);
61+ static MenuContentState* content_at(QQmlListProperty<MenuContentState> *prop, int index);
62+
63+ bool m_running;
64+ int m_baseIndex;
65+ int m_delta;
66+ int m_count;
67+ UnityIndicators::AbstractTimer* m_timer;
68+ QMap<int, MenuContentState*> m_content;
69+ MenuContentActivator* q;
70+};
71+
72+MenuContentActivator::MenuContentActivator(QObject* parent)
73+ : QObject(parent),
74+ d(new MenuContentActivatorPrivate(this))
75+{
76+ qRegisterMetaType<QQmlListProperty<MenuContentState> > ("QQmlListProperty<MenuContentState>");
77+
78+ setContentTimer(new ContentTimer(this));
79+ d->m_timer->setInterval(75);
80+}
81+
82+MenuContentActivator::~MenuContentActivator()
83+{
84+ delete d;
85+}
86+
87+void MenuContentActivator::restart()
88+{
89+ // when we start, make sure we have the base index in the list.
90+ setMenuContentState(d->m_baseIndex, true);
91+ setDelta(0);
92+
93+ // check if we've finished before starting the timer.
94+ bool finished = false;
95+ d->findNextInactiveDelta(&finished);
96+ if (!finished) {
97+ d->m_timer->start();
98+ } else {
99+ d->m_timer->stop();
100+ }
101+
102+ if (!d->m_running) {
103+ d->m_running = true;
104+ Q_EMIT runningChanged(true);
105+ }
106+}
107+
108+void MenuContentActivator::stop()
109+{
110+ d->m_timer->stop();
111+ if (!d->m_running) {
112+ d->m_running = false;
113+ Q_EMIT runningChanged(false);
114+ }
115+}
116+
117+void MenuContentActivator::clear()
118+{
119+ qDeleteAll(d->m_content);
120+ d->m_content.clear();
121+
122+ setDelta(0);
123+ d->m_timer->stop();
124+
125+ Q_EMIT contentChanged();
126+}
127+
128+bool MenuContentActivator::isMenuContentActive(int index) const
129+{
130+ if (d->m_content.contains(index))
131+ return d->m_content[index]->isActive();
132+ return false;
133+}
134+
135+void MenuContentActivator::setRunning(bool running)
136+{
137+ if (running) {
138+ restart();
139+ } else {
140+ stop();
141+ }
142+}
143+
144+bool MenuContentActivator::isRunning() const
145+{
146+ return d->m_running;
147+}
148+
149+void MenuContentActivator::setBaseIndex(int index)
150+{
151+ if (d->m_baseIndex != index) {
152+ d->m_baseIndex = index;
153+
154+ if (isRunning()) {
155+ restart();
156+ }
157+
158+ Q_EMIT baseIndexChanged(index);
159+ }
160+}
161+
162+int MenuContentActivator::baseIndex() const
163+{
164+ return d->m_baseIndex;
165+}
166+
167+void MenuContentActivator::setCount(int count)
168+{
169+ if (d->m_count != count) {
170+ d->m_count = count;
171+ Q_EMIT countChanged(count);
172+
173+ if (isRunning()) {
174+ restart();
175+ }
176+ }
177+}
178+
179+int MenuContentActivator::count() const
180+{
181+ return d->m_count;
182+}
183+
184+void MenuContentActivator::setDelta(int delta)
185+{
186+ if (d->m_delta != delta) {
187+ d->m_delta = delta;
188+ Q_EMIT deltaChanged(d->m_delta);
189+ }
190+}
191+
192+int MenuContentActivator::delta() const
193+{
194+ return d->m_delta;
195+}
196+
197+QQmlListProperty<MenuContentState> MenuContentActivator::content()
198+{
199+ return QQmlListProperty<MenuContentState>(this,
200+ 0,
201+ MenuContentActivatorPrivate::content_count,
202+ MenuContentActivatorPrivate::content_at);
203+}
204+
205+void MenuContentActivator::onTimeout()
206+{
207+ bool finished = false;
208+ int tempDelta = d->findNextInactiveDelta(&finished);
209+ if (!finished) {
210+ setMenuContentState(d->m_baseIndex + tempDelta, true);
211+ setDelta(tempDelta);
212+ }
213+
214+ if (finished) {
215+ d->m_timer->stop();
216+ }
217+}
218+
219+void MenuContentActivator::setContentTimer(UnityIndicators::AbstractTimer *timer)
220+{
221+ int interval = 0;
222+ bool timerWasRunning = false;
223+
224+ // can be null when called from the constructor
225+ if (d->m_timer) {
226+ interval = d->m_timer->interval();
227+ timerWasRunning = d->m_timer->isRunning();
228+ if (d->m_timer->parent() == this) {
229+ delete d->m_timer;
230+ }
231+ }
232+
233+ d->m_timer = timer;
234+ timer->setInterval(interval);
235+ connect(timer, &UnityIndicators::AbstractTimer::timeout,
236+ this, &MenuContentActivator::onTimeout);
237+ if (timerWasRunning) {
238+ d->m_timer->start();
239+ }
240+}
241+
242+void MenuContentActivator::setMenuContentState(int index, bool active)
243+{
244+ if (d->m_content.contains(index)) {
245+ d->m_content[index]->setActive(active);
246+ } else {
247+ d->m_content[index] = new MenuContentState(active);
248+ Q_EMIT contentChanged();
249+ }
250+}
251+
252+int MenuContentActivatorPrivate::findNextInactiveDelta(bool* finished)
253+{
254+ if (m_count == 0 || m_baseIndex >= m_count) {
255+ if (finished) *finished = true;
256+ return 0;
257+ }
258+
259+ int tmpDelta = m_delta;
260+ bool topReached = false, bottomReached = false;
261+ while(true) {
262+
263+ // prechecks for bottom and top limits.
264+ if (tmpDelta > 0 && bottomReached) tmpDelta = -tmpDelta;
265+ if (tmpDelta < 0 && topReached) tmpDelta = (-tmpDelta) + 1;
266+
267+ if (tmpDelta > 0) {
268+ // negative of baseIndex
269+ tmpDelta = -tmpDelta;
270+ // reached the bottom?
271+ if (m_baseIndex + tmpDelta < 0) {
272+ bottomReached = true;
273+ // if we've reached the top as well, then we know we're done.
274+ if (topReached) {
275+ if (finished) *finished = true;
276+ return 0;
277+ }
278+ continue;
279+ }
280+ } else {
281+ // positive of baseIndex
282+ tmpDelta = (-tmpDelta) + 1;
283+ // reached the top?
284+ if (m_baseIndex + tmpDelta >= m_count) {
285+ topReached = true;
286+ // if we've reached the bottom as well, then we know we're done.
287+ if (bottomReached) {
288+ if (finished) *finished = true;
289+ return 0;
290+ }
291+ continue;
292+ }
293+ }
294+
295+ if (q->isMenuContentActive(m_baseIndex + tmpDelta)) {
296+ continue;
297+ }
298+ break;
299+ }
300+ if (finished) *finished = false;
301+ return tmpDelta;
302+}
303+
304+int MenuContentActivatorPrivate::content_count(QQmlListProperty<MenuContentState> *prop)
305+{
306+ MenuContentActivator *p = qobject_cast<MenuContentActivator*>(prop->object);
307+ // we'll create MenuContentState on demand.
308+ return p->count();
309+}
310+
311+MenuContentState* MenuContentActivatorPrivate::content_at(QQmlListProperty<MenuContentState> *prop, int index)
312+{
313+ MenuContentActivator *p = qobject_cast<MenuContentActivator*>(prop->object);
314+ MenuContentActivatorPrivate *d = p->d;
315+
316+ if (!d->m_content.contains(index)) {
317+ MenuContentState* content = new MenuContentState(false);
318+ d->m_content[index] = content;
319+ Q_EMIT p->contentChanged();
320+ return content;
321+ }
322+
323+ return d->m_content[index];
324+}
325+
326+MenuContentState::MenuContentState(bool active)
327+ : m_active(active)
328+{
329+}
330+
331+bool MenuContentState::isActive() const
332+{
333+ return m_active;
334+}
335+
336+void MenuContentState::setActive(bool active)
337+{
338+ if (m_active != active) {
339+ m_active = active;
340+ Q_EMIT activeChanged();
341+ }
342+}
343+
344+// Because we are defining a new QObject-based class (ContentTimer) here.
345+#include "menucontentactivator.moc"
plugins/Unity/Indicators/menucontentactivator.hnew file+117
@@ -0,0 +1,117 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Nick Dedekind <nick.dedekind@canonical.com>
17+ */
18+
19+#ifndef MENUCONTENTACTIVATOR_H
20+#define MENUCONTENTACTIVATOR_H
21+
22+#include "unityindicatorsglobal.h"
23+
24+#include <QObject>
25+#include <QTimer>
26+#include <QQmlListProperty>
27+
28+
29+namespace UnityIndicators {
30+/* Defines an interface for a Timer. */
31+class UNITYINDICATORS_EXPORT AbstractTimer : public QObject {
32+ Q_OBJECT
33+public:
34+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
35+ virtual int interval() const = 0;
36+ virtual void setInterval(int msecs) = 0;
37+ virtual void start() { m_isRunning = true; };
38+ virtual void stop() { m_isRunning = false; }
39+ bool isRunning() const { return m_isRunning; }
40+Q_SIGNALS:
41+ void timeout();
42+private:
43+ bool m_isRunning;
44+};
45+}
46+
47+/* Defines a object to express the active state of a menu. */
48+class UNITYINDICATORS_EXPORT MenuContentState : public QObject
49+{
50+ Q_OBJECT
51+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
52+public:
53+ MenuContentState(bool active);
54+
55+ bool isActive() const;
56+ void setActive(bool active);
57+
58+Q_SIGNALS:
59+ void activeChanged();
60+private:
61+ bool m_active;
62+};
63+
64+class MenuContentActivatorPrivate;
65+
66+class UNITYINDICATORS_EXPORT MenuContentActivator : public QObject
67+{
68+ Q_OBJECT
69+ Q_PROPERTY(int baseIndex READ baseIndex WRITE setBaseIndex NOTIFY baseIndexChanged)
70+ Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
71+ Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
72+ Q_PROPERTY(QQmlListProperty<MenuContentState> content READ content NOTIFY contentChanged DESIGNABLE false)
73+public:
74+ MenuContentActivator(QObject* parent = NULL);
75+ ~MenuContentActivator();
76+
77+ Q_INVOKABLE void restart();
78+ Q_INVOKABLE void stop();
79+ Q_INVOKABLE void clear();
80+ Q_INVOKABLE bool isMenuContentActive(int index) const;
81+
82+ void setRunning(bool running);
83+ bool isRunning() const;
84+
85+ void setBaseIndex(int index);
86+ int baseIndex() const;
87+
88+ void setCount(int index);
89+ int count() const;
90+
91+ void setDelta(int index);
92+ int delta() const;
93+
94+ QQmlListProperty<MenuContentState> content();
95+
96+ // Replaces the existing Timer with the given one.
97+ //
98+ // Useful for providing a fake timer when testing.
99+ void setContentTimer(UnityIndicators::AbstractTimer *timer);
100+ void setMenuContentState(int index, bool active);
101+
102+Q_SIGNALS:
103+ void baseIndexChanged(int baseIndex);
104+ void deltaChanged(int delta);
105+ void runningChanged(bool running);
106+ void countChanged(int count);
107+ void contentChanged();
108+
109+private Q_SLOTS:
110+ void onTimeout();
111+
112+private:
113+ MenuContentActivatorPrivate* d;
114+ friend class MenuContentActivatorPrivate;
115+};
116+
117+#endif // MENUCONTENTACTIVATOR_H
plugins/Unity/Indicators/plugin.cpp+3
@@ -27,6 +27,7 @@
2727#include "indicatorsmanager.h"
2828#include "indicatorsmodel.h"
2929#include "indicators.h"
30+#include "menucontentactivator.h"
3031
3132void IndicatorsPlugin::registerTypes(const char *uri)
3233{
@@ -35,7 +36,9 @@ void IndicatorsPlugin::registerTypes(const char *uri)
3536 qmlRegisterType<IndicatorsManager>(uri, 0, 1, "IndicatorsManager");
3637 qmlRegisterType<FlatMenuProxyModel>(uri, 0, 1, "FlatMenuProxyModel");
3738 qmlRegisterType<IndicatorsModel>(uri, 0, 1, "IndicatorsModel");
39+ qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
3840
41+ qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
3942 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
4043 qmlRegisterUncreatableType<NetworkActionState>(uri, 0, 1, "NetworkActionState", "Can't create NetworkActionState class");
4144 qmlRegisterUncreatableType<NetworkConnection>(uri, 0, 1, "NetworkConnection", "Can't create NetworkConnection class");
plugins/Unity/Indicators/qml/IndicatorPage.qml−6
@@ -188,10 +188,4 @@ IndicatorBase {
188188 mainMenu.currentIndex = -1;
189189 mainMenu.positionViewAtBeginning();
190190 }
191−
192− Component.onCompleted: {
193− if ((busType != 0) && (busName != "") && (deviceMenuObjectPath != "")) {
194− start();
195− }
196− }
197191}
plugins/Unity/categories.cpp+4−1
@@ -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
@@ -131,7 +132,7 @@ Categories::data(const QModelIndex& index, int role) const
131132
132133 switch (role) {
133134 case RoleId:
134− return QVariant::fromValue(index.row());
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+2−1
@@ -49,7 +49,8 @@ public:
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;
po/uk.po+4−4
@@ -8,13 +8,13 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-06-26 22:58+0200\n"
11−"PO-Revision-Date: 2013-06-27 10:47+0000\n"
12−"Last-Translator: Yuri Chornoivan <yurchor@gmail.com>\n"
11+"PO-Revision-Date: 2013-07-21 05:47+0000\n"
12+"Last-Translator: Ярослав Смук <Smuk.Slavko@yandex.ua>\n"
1313"Language-Team: Ukrainian <uk@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2013-07-17 07:49+0000\n"
17+"X-Launchpad-Export-Date: 2013-07-22 06:19+0000\n"
1818"X-Generator: Launchpad (build 16696)\n"
1919
2020#: Applications/applications.js:23
@@ -217,4 +217,4 @@ msgstr "Вибачте, для вказаних вами ключових слі
217217#: Shell.qml:383
218218#, qt-format
219219msgid "Please enter %1"
220−msgstr ""
220+msgstr "Будь ласка, введіть %1"
tests/autopilot/setup.py+17−6
@@ -1,11 +1,22 @@
11#!/usr/bin/python
2−
32# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
4−# Copyright 2013 Canonical
53#
6−# This program is free software: you can redistribute it and/or modify it
7−# under the terms of the GNU General Public License version 3, as published
8−# by the Free Software Foundation.
4+# Unity Autopilot Test Suite
5+# Copyright (C) 2012-2013 Canonical
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, either version 3 of the License, or
10+# (at your option) any later version.
11+#
12+# This program is distributed in the hope that it will be useful,
13+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+# GNU General Public License for more details.
16+#
17+# You should have received a copy of the GNU General Public License
18+# along with this program. If not, see <http://www.gnu.org/licenses/>.
19+#
920
1021
1122from distutils.core import setup
@@ -14,7 +25,7 @@ from setuptools import find_packages
1425setup(
1526 name='unity',
1627 version='8.0',
17− description='Unity 8 autopilot tests.',
28+ description='Unity autopilot tests.',
1829 url='https://launchpad.net/unity',
1930 license='GPLv3',
2031 packages=find_packages(),
tests/autopilot/unity8/__init__.py+90−5
@@ -1,8 +1,93 @@
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.
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+"""unity autopilot tests and emulators - top level package."""
21+import os
22+import os.path
23+import subprocess
24+import sysconfig
25+
26+
27+def running_installed_tests():
28+ binary_path = get_binary_path()
29+ return binary_path.startswith('/usr')
30+
31+
32+def get_lib_path():
33+ """Return the library path to use in this test run."""
34+ if running_installed_tests():
35+ lib_path = os.path.join(
36+ "/usr/lib/",
37+ sysconfig.get_config_var('MULTIARCH'),
38+ "unity8"
39+ )
40+ else:
41+ binary_path = get_binary_path()
42+ lib_path = os.path.dirname(binary_path)
43+ return lib_path
44+
45+
46+def get_default_extra_mock_libraries():
47+ mocks_path = get_mocks_library_path()
48+ return os.path.join(mocks_path, 'libusermetrics')
49+
50+
51+def get_mocks_library_path():
52+ if running_installed_tests():
53+ mock_path = "qml/mocks/"
54+ else:
55+ mock_path = "../lib/x86_64-linux-gnu/unity8/qml/mocks/"
56+ lib_path = get_lib_path()
57+ ld_library_path = os.path.abspath(
58+ os.path.join(
59+ lib_path,
60+ mock_path,
61+ )
62+ )
63+
64+ if not os.path.exists(ld_library_path):
65+ raise RuntimeError(
66+ "Expected library path does not exists: %s." % (ld_library_path)
67+ )
68+ return ld_library_path
69+
70+
71+def get_binary_path(binary="unity8"):
72+ """Return the path to the specified binary."""
73+ binary_path = os.path.abspath(
74+ os.path.join(
75+ os.path.dirname(__file__),
76+ "../../../builddir/install/bin/%s" % binary
77+ )
78+ )
79+ if not os.path.exists(binary_path):
80+ try:
81+ binary_path = subprocess.check_output(['which', binary]).strip()
82+ except subprocess.CalledProcessError as e:
83+ raise RuntimeError("Unable to locate %s binary: %r" % (binary, e))
84+ return binary_path
85+
786
8−"""unity8 autopilot tests and emulators - top level package."""
87+def get_grid_size():
88+ grid_size = os.getenv('GRID_UNIT_PX')
89+ if grid_size is None:
90+ raise RuntimeError(
91+ "Environment variable GRID_UNIT_PX has not been set."
92+ )
93+ return int(grid_size)
tests/autopilot/unity8/indicators_client/__init__.py+1−1
@@ -5,4 +5,4 @@
55# under the terms of the GNU General Public License version 3, as published
66# by the Free Software Foundation.
77
8−"""unity8 indicators-client autopilot tests and emulators - sub level package."""
8+"""unity indicators-client autopilot tests and emulators - sub level package."""
tests/autopilot/unity8/indicators_client/tests/__init__.py+13−9
@@ -5,7 +5,7 @@
55# under the terms of the GNU General Public License version 3, as published
66# by the Free Software Foundation.
77
8−"""qml-phone-shell autopilot tests."""
8+"""indicators-client autopilot tests."""
99
1010import os.path
1111
@@ -15,6 +15,7 @@ from autopilot.matchers import Eventually
1515from autopilot.platform import model
1616from testtools.matchers import Equals
1717
18+from unity8 import get_binary_path
1819from unity8.indicators_client.emulators.main_window import MainWindow
1920from logging import getLogger
2021import sys
@@ -29,9 +30,12 @@ class IndicatorsTestCase(AutopilotTestCase):
2930
3031 """A common test case class that provides several useful methods for indicator tests."""
3132
33+ geometry = None
34+ grid_unit = None
35+
3236 if model() == 'Desktop':
3337 scenarios = [
34− ('with mouse', dict(input_device_class=Mouse)),
38+ ('with mouse', dict(input_device_class=Mouse, geometry="400x800", grid_unit="12")),
3539 ]
3640 else:
3741 scenarios = [
@@ -41,11 +45,11 @@ class IndicatorsTestCase(AutopilotTestCase):
4145 def setUp(self, geometry, grid_size):
4246 self.pointing_device = Pointer(self.input_device_class.create())
4347 super(IndicatorsTestCase, self).setUp()
44− if grid_size != "0":
45− os.environ['GRID_UNIT_PX'] = grid_size
48+ if grid_size is not None:
49+ self.patch_environment("GRID_UNIT_PX", str(grid_size))
4650 self.grid_size = int(grid_size)
4751 else:
48− self.grid_size = int(os.environ['GRID_UNIT_PX'])
52+ self.grid_size = int(os.getenv('GRID_UNIT_PX'))
4953
5054 if os.path.realpath(__file__).startswith("/usr/"):
5155 self.launch_test_installed(geometry)
@@ -53,15 +57,15 @@ class IndicatorsTestCase(AutopilotTestCase):
5357 self.launch_test_local(geometry)
5458
5559 def launch_test_local(self, geometry):
56− if geometry != "0x0":
57− self.app = self.launch_test_application("../../builddir/src/Panel/Indicators/client/indicators-client",
60+ if geometry is None:
61+ self.app = self.launch_test_application(get_binary_path("indicators-client"),
5862 "-geometry", geometry, app_type='qt')
5963 else:
60− self.app = self.launch_test_application("../../builddir/src/Panel/Indicators/client/indicators-client",
64+ self.app = self.launch_test_application(get_binary_path("indicators-client"),
6165 app_type='qt')
6266
6367 def launch_test_installed(self, geometry):
64− if geometry != "0x0":
68+ if geometry is not None:
6569 self.app = self.launch_test_application("indicators-client", "-geometry", geometry, app_type='qt')
6670 else:
6771 self.app = self.launch_test_application("indicators-client", app_type='qt')
tests/autopilot/unity8/indicators_client/tests/test_battery.py+14−3
@@ -8,6 +8,7 @@
88"""Add tests here if you want to ensure the behaviour of the power indicator menus are correct"""
99
1010from testtools.matchers import Equals, NotEquals
11+from autopilot.input import Touch
1112from autopilot.matchers import Eventually
1213
1314from unity8.indicators_client.tests import IndicatorsTestCase
@@ -17,7 +18,7 @@ import math
1718
1819class TestDisplayMenus(IndicatorsTestCase):
1920 def setUp(self):
20− super(TestDisplayMenus, self).setUp("400x800", "12")
21+ super(TestDisplayMenus, self).setUp(self.geometry, self.grid_unit)
2122
2223 # This opens the messaging menu so you don't have to do that in
2324 # every test case
@@ -69,7 +70,11 @@ class TestDisplayMenus(IndicatorsTestCase):
6970
7071 # wait for the switch menu item
7172 fn_auto_brightness = lambda: self.app.select_single("SwitchMenuItem", objectName="auto-brightness");
72− 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")
7378 auto_brightness = fn_auto_brightness();
7479
7580 old_ab_value = auto_brightness.checked
@@ -82,8 +87,14 @@ class TestDisplayMenus(IndicatorsTestCase):
8287 def test_brightness_slider(self):
8388 """Test the auto-bright switch"""
8489
90+ if self.input_device_class is Touch:
91+ self.skipTest("Dragging is broken with Touch input (LP: #1203808).")
8592 fn_brightness_menu = lambda: self.app.select_single("SliderMenuItem", objectName="brightness");
86− 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")
8798 brightness_menu = fn_brightness_menu();
8899
89100 old_ab_value = brightness_menu.value
tests/autopilot/unity8/shell/__init__.py+35−5
@@ -1,8 +1,38 @@
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.
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+"""unity shell autopilot tests and emulators - sub level package."""
21+
22+from functools import wraps
23+import logging
24+
25+
26+logger = logging.getLogger(__name__)
27+
728
8−"""unity8 shell autopilot tests and emulators - sub level package."""
29+def with_lightdm_mock(mock_type):
30+ """A simple decorator that sets up the LightDM mock for a single test."""
31+ def with_lightdm_mock_internal(fn):
32+ @wraps(fn)
33+ def wrapper(*args, **kwargs):
34+ tests_self = args[0]
35+ tests_self.patch_lightdm_mock(mock_type)
36+ return fn(*args, **kwargs)
37+ return wrapper
38+ return with_lightdm_mock_internal
tests/autopilot/unity8/shell/emulators/__init__.py+23−4
@@ -1,6 +1,25 @@
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.
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+
21+from autopilot.introspection import CustomEmulatorBase
22+
23+
24+class UnityEmulatorBase(CustomEmulatorBase):
25+ """A base class for all unity emulators."""
tests/autopilot/unity8/shell/emulators/dash.pynew file+52
@@ -0,0 +1,52 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+from unity8.shell.emulators import UnityEmulatorBase
21+
22+
23+class Dash(UnityEmulatorBase):
24+
25+ """An emulator that understands the Dash."""
26+
27+ def get_home_applications_grid(self):
28+ return self.select_single(
29+ "ApplicationsFilterGrid",
30+ objectName="dashHomeApplicationsGrid"
31+ )
32+
33+ def get_application_icon(self, text):
34+ """Returns a 'Tile' icon that has the text 'text' from the application
35+ grid.
36+
37+ Will return None if the icon isn't found.
38+
39+ :param text: String containing the text of the icon to search for.
40+
41+ """
42+ app_grid = self.get_home_applications_grid()
43+ resp_grid = app_grid.select_single('ResponsiveGridView')
44+ return resp_grid.select_single('Tile', text=text)
45+
46+ def get_scope(self, scope_name='home'):
47+ dash_content = self.select_single(
48+ 'QQuickListView',
49+ objectName='dashContentList'
50+ )
51+ scope_id = "%s.scope" % scope_name
52+ return dash_content.select_single('QQuickLoader', scopeId=scope_id)
tests/autopilot/unity8/shell/emulators/greeter.pynew file+40
@@ -0,0 +1,40 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+from unity8.shell.emulators import UnityEmulatorBase
21+from autopilot.input import Touch
22+
23+
24+class Greeter(UnityEmulatorBase):
25+
26+ """An emulator that understands the greeter screen."""
27+
28+ def unlock(self):
29+ """Swipe the greeter screen away."""
30+ self.created.wait_for(True)
31+ touch = Touch.create()
32+
33+ rect = self.globalRect
34+ start_x = rect[0] + rect[2] - 3
35+ start_y = int(rect[1] + rect[3] / 2)
36+ stop_x = int(rect[0] + rect[2] * 0.2)
37+ stop_y = start_y
38+ touch.drag(start_x, start_y, stop_x, stop_y)
39+
40+ self.created.wait_for(False)
tests/autopilot/unity8/shell/emulators/hud.pynew file+83
@@ -0,0 +1,83 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+from collections import namedtuple
21+
22+from unity8 import get_grid_size
23+from unity8.shell.emulators import UnityEmulatorBase
24+from autopilot.input import Touch
25+
26+
27+SwipeCoords = namedtuple('SwipeCoords', 'start_x end_x start_y end_y')
28+
29+
30+class Hud(UnityEmulatorBase):
31+
32+ """An emulator that understands the Hud."""
33+
34+ def show(self):
35+ """Swipes open the Hud."""
36+ touch = Touch.create()
37+
38+ window = self.get_root_instance().select_single('QQuickView')
39+ hud_show_button = window.select_single("HudButton")
40+
41+ start_x = int(window.x + (window.width / 2))
42+ end_x = start_x
43+ start_y = window.y + window.height
44+ end_y = int(hud_show_button.y + (hud_show_button.height/2))
45+
46+ touch.press(start_x, start_y)
47+ touch._finger_move(end_x, end_y)
48+ try:
49+ hud_show_button.opacity.wait_for(1.0)
50+ touch.release()
51+ self.shown.wait_for(True)
52+ except AssertionError:
53+ raise
54+ finally:
55+ if touch._touch_finger is not None:
56+ touch.release()
57+
58+ def dismiss(self):
59+ """Closes the open Hud."""
60+ # Ensure that the Hud is actually open
61+ self.shown.wait_for(True)
62+ touch = Touch.create()
63+ x, y = self.get_close_button_coords()
64+ touch.tap(x, y)
65+ self.y.wait_for(self.height)
66+
67+ def get_close_button_coords(self):
68+ """Returns the coordinates of the Huds close button bar."""
69+ rect = self.globalRect
70+ x = int(rect[0] + rect[2] / 2)
71+ y = rect[1] + get_grid_size()
72+ return x, y
73+
74+ def get_button_swipe_coords(self, main_view, hud_show_button):
75+ """Returns the coords both start and end x,y for swiping to make the
76+ 'hud show' button appear.
77+ """
78+ start_x = int(main_view.x + (main_view.width / 2))
79+ end_x = start_x
80+ start_y = main_view.y + (main_view.height - 3)
81+ end_y = int(hud_show_button.y + (hud_show_button.height/2))
82+
83+ return SwipeCoords(start_x, end_x, start_y, end_y)
tests/autopilot/unity8/shell/emulators/launcher.pynew file+38
@@ -0,0 +1,38 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+from unity8.shell.emulators import UnityEmulatorBase
21+from autopilot.input import Touch
22+
23+
24+class Launcher(UnityEmulatorBase):
25+
26+ """An emulator that understands the Launcher."""
27+
28+ def show(self):
29+ """Swipes open the launcher."""
30+ touch = Touch.create()
31+
32+ view = self.get_root_instance().select_single('QQuickView')
33+ start_x = view.x + 1
34+ start_y = view.y + view.height / 2
35+ stop_x = start_x + self.panelWidth + 1
36+ stop_y = start_y
37+ touch.drag(start_x, start_y, stop_x, stop_y)
38+ self.shown.wait_for(True)
tests/autopilot/unity8/shell/emulators/main_window.py+43−12
@@ -1,9 +1,28 @@
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.
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+
21+from unity8.shell.emulators.greeter import Greeter
22+from unity8.shell.emulators.hud import Hud
23+from unity8.shell.emulators.dash import Dash
24+from unity8.shell.emulators.launcher import Launcher
25+
726
827class MainWindow(object):
928 """An emulator class that makes it easy to interact with the shell"""
@@ -16,10 +35,13 @@ class MainWindow(object):
1635 return self.app.select_single("QQuickView")
1736
1837 def get_greeter(self):
19− return self.app.select_single("Greeter")
38+ return self.app.select_single(Greeter)
2039
2140 def get_greeter_content_loader(self):
22− return self.app.select_single("QQuickLoader", objectName="greeterContentLoader")
41+ return self.app.select_single(
42+ "QQuickLoader",
43+ objectName="greeterContentLoader"
44+ )
2345
2446 def get_login_loader(self):
2547 return self.app.select_single("QQuickLoader", objectName="loginLoader")
@@ -28,7 +50,7 @@ class MainWindow(object):
2850 return self.app.select_single("LoginList")
2951
3052 def get_hud(self):
31− return self.app.select_single("Hud")
53+ return self.app.select_single(Hud)
3254
3355 def get_hud_showable(self):
3456 return self.app.select_single("Showable", objectName="hudShowable")
@@ -37,22 +59,31 @@ class MainWindow(object):
3759 return self.app.select_single("HudButton")
3860
3961 def get_dash(self):
40− return self.app.select_single("Dash")
62+ return self.app.select_single(Dash)
4163
4264 def get_dash_home_applications_grid(self):
43− return self.app.select_single("ApplicationsFilterGrid", objectName="dashHomeApplicationsGrid")
65+ return self.app.select_single(
66+ "ApplicationsFilterGrid",
67+ objectName="dashHomeApplicationsGrid"
68+ )
4469
4570 def get_bottombar(self):
4671 return self.app.select_single("Bottombar")
4772
4873 def get_launcher(self):
49− return self.app.select_single("Launcher")
74+ return self.app.select_single(Launcher)
5075
5176 def get_pinPadLoader(self):
52− return self.app.select_single("QQuickLoader", objectName="pinPadLoader")
77+ return self.app.select_single(
78+ "QQuickLoader",
79+ objectName="pinPadLoader"
80+ )
5381
5482 def get_pinPadButton(self, buttonId):
55− return self.app.select_single("PinPadButton", objectName="pinPadButton%i" % buttonId)
83+ return self.app.select_single(
84+ "PinPadButton",
85+ objectName="pinPadButton%i" % buttonId
86+ )
5687
5788 def get_lockscreen(self):
5889 return self.app.select_single("Lockscreen")
tests/autopilot/unity8/shell/tests/__init__.py+216−58
@@ -1,81 +1,239 @@
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.
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+#
719
8−"""unity8 autopilot tests."""
20+"""unity autopilot tests."""
921
10−import os.path
11−import sysconfig
22+try:
23+ from gi.repository import Gio
24+except ImportError:
25+ Gio = None
1226
27+from autopilot.platform import model
1328from autopilot.testcase import AutopilotTestCase
1429from autopilot.matchers import Eventually
15−from autopilot.platform import model
16−from testtools.matchers import Equals
30+from autopilot.input import Touch
31+from autopilot.display import Display
32+import logging
33+import os.path
34+from testtools.matchers import Equals, NotEquals
1735
36+from unity8 import (
37+ get_lib_path,
38+ get_binary_path,
39+ get_mocks_library_path,
40+ get_default_extra_mock_libraries,
41+)
42+from unity8.shell.emulators import UnityEmulatorBase
43+from unity8.shell.emulators.dash import Dash
1844from unity8.shell.emulators.main_window import MainWindow
1945
20−class FormFactors(object):
21− Phone, Tablet, Desktop = range(3)
2246
23−class ShellTestCase(AutopilotTestCase):
47+logger = logging.getLogger(__name__)
2448
25− """A common test case class that provides several useful methods for shell tests."""
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
2653
27− libdir = "/usr/lib/{0}/unity8".format(sysconfig.get_config_var('MULTIARCH'))
28− lightdm_mock = "full"
54+def _get_device_emulation_scenarios():
55+ if model() == 'Desktop':
56+ return [
57+ (
58+ 'Desktop Nexus 4',
59+ dict(app_width=768, app_height=1280, grid_unit_px=18)
60+ ),
61+ (
62+ 'Desktop Nexus 10',
63+ dict(app_width=2560, app_height=1600, grid_unit_px=20)
64+ ),
65+ ]
66+ else:
67+ return [
68+ (
69+ 'Native Device',
70+ dict(app_width=0, app_height=0, grid_unit_px=0)
71+ )
72+ ]
2973
30− def setUp(self, geometry, grid_size):
31− super(ShellTestCase, self).setUp()
32− # Lets assume we are installed system wide if this file is somewhere in /usr
33− if grid_size != "0":
34− os.environ['GRID_UNIT_PX'] = grid_size
35− self.grid_size = int(grid_size)
36− else:
37− self.grid_size = int(os.environ['GRID_UNIT_PX'])
38− if os.path.realpath(__file__).startswith("/usr/"):
39− self.launch_test_installed(geometry)
74+
75+class UnityTestCase(AutopilotTestCase):
76+
77+ """A test case base class for the Unity shell tests."""
78+
79+ def setUp(self):
80+ 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+
88+ self._proxy = None
89+ self._lightdm_mock_type = None
90+ self.touch = Touch.create()
91+ self._setup_display_details()
92+
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+
97+ def _setup_display_details(self):
98+ scale_divisor = self._determine_geometry()
99+ self._setup_grid_size(scale_divisor)
100+
101+ def _determine_geometry(self):
102+ """Use the geometry that may be supplied or use the default."""
103+ width = getattr(self, 'app_width', 0)
104+ height = getattr(self, 'app_height', 0)
105+ scale_divisor = 1
106+ if width == 0 and width == 0:
107+ self.unity_geometry_args = ['-fullscreen']
40108 else:
41− self.launch_test_local(geometry)
42−
43− def launch_test_local(self, geometry):
44− os.environ['LD_LIBRARY_PATH'] = "../../builddir/tests/mocks/libusermetrics:../../builddir/tests/mocks/LightDM" + self.lightdm_mock
45− os.environ['QML2_IMPORT_PATH'] = "../../builddir/tests/mocks"
46− if geometry != "0x0":
47− self.app = self.launch_test_application(
48− "../../builddir/unity8", "-geometry", geometry, "-frameless", app_type='qt')
109+ if self._geo_larger_than_display(width, height):
110+ scale_divisor = self._get_scaled_down_geo(width, height)
111+ width = width / scale_divisor
112+ height = height / scale_divisor
113+ logger.info(
114+ "Geometry larger than display, scaled down to: %dx%d",
115+ width,
116+ height
117+ )
118+ geo_string = "%dx%d" % (width, height)
119+ self.unity_geometry_args = [
120+ '-geometry',
121+ geo_string,
122+ '-frameless',
123+ '-mousetouch'
124+ ]
125+ return scale_divisor
126+
127+ def _setup_grid_size(self, scale_divisor):
128+ """Use the grid size that may be supplied or use the default."""
129+ if getattr(self, 'grid_unit_px', 0) == 0:
130+ self.grid_size = int(os.getenv('GRID_UNIT_PX'))
49131 else:
50− self.app = self.launch_test_application(
51− "../../builddir/unity8", "-fullscreen", app_type='qt')
52−
53− def launch_test_installed(self, geometry):
54− os.environ['LD_LIBRARY_PATH'] = "{0}/qml/mocks/libusermetrics:{0}/qml/mocks/LightDM/{1}".format(self.libdir, self.lightdm_mock)
55− os.environ['QML2_IMPORT_PATH'] = "{0}/qml/mocks".format(self.libdir)
56− if model() == 'Desktop' and geometry != "0x0":
57− self.app = self.launch_test_application(
58− "unity8", "-geometry", geometry, "-frameless", app_type='qt')
132+ self.grid_size = int(self.grid_unit_px / scale_divisor)
133+ self.patch_environment("GRID_UNIT_PX", str(self.grid_size))
134+
135+ def _geo_larger_than_display(self, width, height):
136+ should_scale = getattr(self, 'scale_geo', True)
137+ if should_scale:
138+ screen = Display.create()
139+ screen_width = screen.get_screen_width()
140+ screen_height = screen.get_screen_height()
141+ return (width > screen_width) or (height > screen_height)
59142 else:
60− self.app = self.launch_test_application(
61− "unity8", "-fullscreen", app_type='qt')
143+ return False
144+
145+ def _get_scaled_down_geo(self, width, height):
146+ divisor = 1
147+ while self._geo_larger_than_display(width / divisor, height / divisor):
148+ divisor = divisor * 2
149+ return divisor
150+
151+ def launch_unity(self):
152+ """Launch the unity shell, return a proxy object for it."""
153+ binary_path = get_binary_path()
154+ lib_path = get_lib_path()
155+
156+ logger.info(
157+ "Lib path is '%s', binary path is '%s'",
158+ lib_path,
159+ binary_path
160+ )
161+
162+ if self._lightdm_mock_type is None:
163+ self.patch_lightdm_mock()
164+ self._setup_extra_mock_environment_patch()
165+
166+ app_proxy = self.launch_test_application(
167+ binary_path,
168+ *self.unity_geometry_args,
169+ app_type='qt',
170+ emulator_base=UnityEmulatorBase
171+ )
172+ self._set_proxy(app_proxy)
173+
174+ # Ensure that the dash is visible before we return:
175+ logger.debug("Unity started, waiting for it to be ready.")
176+ self.assertUnityReady()
177+ logger.debug("Unity loaded and ready.")
178+
179+ return app_proxy
180+
181+ def patch_lightdm_mock(self, mock_type='single'):
182+ self._lightdm_mock_type = mock_type
183+ logger.info("Setting up LightDM mock type '%s'", mock_type)
184+ new_ld_library_path = "%s:%s" % (
185+ get_default_extra_mock_libraries(),
186+ self._get_lightdm_mock_path(mock_type)
187+ )
188+ logger.info("New library path: %s", new_ld_library_path)
189+
190+ self.patch_environment('LD_LIBRARY_PATH', new_ld_library_path)
191+
192+ def _get_lightdm_mock_path(self, mock_type):
193+ lib_path = get_mocks_library_path()
194+ lightdm_mock_path = os.path.abspath(
195+ os.path.join(lib_path, "LightDM", mock_type)
196+ )
197+
198+ if not os.path.exists(lightdm_mock_path):
199+ raise RuntimeError(
200+ "LightDM mock '%s' does not exist at path '%s'."
201+ % (mock_type, lightdm_mock_path)
202+ )
203+ return lightdm_mock_path
204+
205+ def _setup_extra_mock_environment_patch(self):
206+ mocks_library_path = get_mocks_library_path()
207+ self.patch_environment('QML2_IMPORT_PATH', mocks_library_path)
208+
209+ def _set_proxy(self, proxy):
210+ """Keep a copy of the proxy object, so we can use it to get common
211+ parts of the shell later on.
212+
213+ """
214+ self._proxy = proxy
215+ self.addCleanup(self._clear_proxy)
216+
217+ def _clear_proxy(self):
218+ self._proxy = None
62219
63− def skipWrapper(*args, **kwargs):
64− pass
220+ def assertUnityReady(self):
221+ dash = self.get_dash()
222+ home_scope = dash.get_scope('home')
65223
66− def form_factor(self):
67− return FormFactors.Desktop
224+ # FIXME! There is a huge timeout here for when we're doing CI on
225+ # VMs. See lp:1203715
226+ self.assertThat(
227+ home_scope.isLoaded,
228+ Eventually(Equals(True), timeout=60)
229+ )
230+ self.assertThat(home_scope.isCurrent, Eventually(Equals(True)))
68231
69− def __getattribute__(self, attr_name):
70− attr = object.__getattribute__(self, attr_name);
71− if attr_name.startswith("test_"):
72− try:
73− if self.form_factor() in attr.blacklist:
74− return self.skipWrapper
75− except:
76− pass
77− return attr
232+ def get_dash(self):
233+ dash = self._proxy.select_single(Dash)
234+ self.assertThat(dash, NotEquals(None))
235+ return dash
78236
79237 @property
80238 def main_window(self):
81− return MainWindow(self.app)
239+ return MainWindow(self._proxy)
tests/autopilot/unity8/shell/tests/helpers.pydeleted−161
@@ -1,161 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
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−from testtools.matchers import Equals, NotEquals
9−from autopilot.matchers import Eventually
10−
11−import logging
12−
13−logger = logging.getLogger(__name__)
14−
15−class TestShellHelpers(object):
16− """Helpers for testing the Shell"""
17−
18− def select_greeter_user(self, username):
19− greeter = self.main_window.get_greeter()
20− self.assertThat(greeter.created, Eventually(Equals(True)))
21− self.assertThat(greeter.multiUser, Eventually(Equals(True)))
22−
23− login_loader = self.main_window.get_login_loader()
24− self.assertThat(login_loader.progress, Eventually(Equals(1)))
25−
26− login_list = self.main_window.get_login_list()
27− list_view = login_list.get_children_by_type("QQuickListView")[0]
28−
29− try_count = 0
30− max_tries = 50 # just in case we go off rails
31− while try_count < max_tries:
32− users = list_view.get_children_by_type("QQuickItem")[0].get_children_by_type("QQuickItem")
33− target_user = None
34− for user in users:
35− try:
36− user_label = user.get_children_by_type("Label")[0]
37− if user.opacity < 0.1:
38− continue # off-screen item
39− if user_label.text == username:
40− target_user = user
41− break
42− elif target_user is None or user.y > target_user.y:
43− target_user = user
44− except Exception:
45− pass
46− if target_user is None:
47− break
48− user_label = target_user.get_children_by_type("Label")[0]
49− self.touch.tap_object(user_label)
50− self.assertThat(list_view.movingInternally, Eventually(Equals(False)))
51− if user_label.text == username:
52− return login_list.get_children_by_type("TextField")[0]
53− try_count = try_count + 1
54− self.fail() # We didn't find it
55−
56− def unlock_greeter(self, retries=2):
57− greeter = self.main_window.get_greeter()
58− self.assertThat(greeter.created, Eventually(Equals(True)))
59−
60− if greeter.multiUser:
61− password_field = self.select_greeter_user("No Password")
62− self.assertThat(password_field.opacity, Eventually(Equals(1)))
63− self.touch.tap_object(password_field)
64−
65− else:
66− rect = greeter.globalRect
67− start_x = rect[0] + rect[2] - 3
68− start_y = int(rect[1] + rect[3] / 2)
69− stop_x = int(rect[0] + rect[2] * 0.2)
70− stop_y = start_y
71− self.touch.drag(start_x, start_y, stop_x, stop_y)
72−
73− # Because the shell loads up lots of stuff, unlocking the greeter can
74− # be a bit stuttery while scopes are still consuming all resources.
75− # Give it another (max retries) chance
76− try:
77− self.assertThat(greeter.created, Eventually(Equals(False)))
78− except:
79− if retries > 0:
80− logger.warning("Failed to unlock greeter. Retrying...")
81− self.unlock_greeter(retries-1)
82− else:
83− logger.warning("Failed to unlock greeter. Giving up. Tests may fail...")
84−
85−
86− def open_first_dash_home_app(self, retries=2):
87− self.assertThat(lambda: self.main_window.get_dash_home_applications_grid(), Eventually(NotEquals(None)))
88− app_grid = self.main_window.get_dash_home_applications_grid()
89− # Wait for the grids to be expanded
90− self.assertThat(app_grid.get_children()[0].totalContentHeight, Eventually(NotEquals(0)))
91− self.assertThat(app_grid.get_children()[0].height, Eventually(Equals(app_grid.get_children()[0].totalContentHeight)))
92− first_app = app_grid.get_children()[0].get_children()[0].get_children()[0].get_children()[0]
93− self.touch.tap_object(first_app)
94− bottombar = self.main_window.get_bottombar()
95−
96− # if we have huge amounts of pixels (e.g. Nexus10), but slow video (e.g. VM) it might take a little
97− # until the dash is fully rendered after sliding away the greeter/lockscreen and the click might
98− # go to the void for the first time because there's no real way to know what the display has
99− # painted already. Give it another (max retries) chance.
100− try:
101− self.assertThat(lambda: getattr(bottombar, "applicationIsOnForeground"),
102− Eventually(Equals(True)))
103− except:
104− if retries > 0:
105− logger.warning("Failed to launch app. Retrying...")
106− self.open_first_dash_home_app(retries-1)
107− else:
108− logger.warning("Failed to launch app. Giving up. Tests may fail...")
109−
110−
111− def show_hud(self, retries=2):
112− self.open_first_dash_home_app()
113−
114− hud_show_button = self.main_window.get_hud_show_button()
115− hud = self.main_window.get_hud()
116− window = self.main_window.get_qml_view()
117− start_x = int(window.x + window.width / 2)
118− start_y = window.y + window.height - 3
119− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
120− self.touch.press(start_x, start_y)
121− self.touch._finger_move(start_x, start_y - self.grid_size)
122− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
123− self.touch._finger_move(start_x, start_y - self.grid_size * 2)
124− self.touch._finger_move(start_x, start_y - self.grid_size * 3)
125− self.touch._finger_move(start_x, start_y - self.grid_size * 4)
126− try:
127− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
128− except:
129− if retries > 0:
130− logger.warning("Failed to get hud button to show. Retrying...")
131− self.touch.release()
132− self.show_hud(retries-1)
133− return
134− else:
135− logger.warning("Failed to get hud button to show. Giving up. Tests may fail...")
136− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(False)))
137− self.touch._finger_move(start_x, start_y - self.grid_size * 34)
138− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
139− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(True)))
140− self.touch.release()
141− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
142− self.assertThat(hud.shown, Eventually(Equals(True)))
143−
144− def close_hud_click(self):
145− hud = self.main_window.get_hud()
146− self.assertThat(hud.shown, Eventually(Equals(True)))
147− rect = hud.globalRect
148− x = int(rect[0] + rect[2] / 2)
149− y = rect[1] + self.grid_size
150− self.touch.tap(x, y)
151− self.assertThat(hud.shown, Eventually(Equals(False)))
152−
153− def show_launcher(self):
154− launcher = self.main_window.get_launcher()
155− view = self.main_window.get_qml_view()
156− start_x = view.x + 1
157− start_y = view.y + view.height / 2
158− stop_x = start_x + launcher.panelWidth + 1
159− stop_y = start_y
160− self.touch.drag(start_x, start_y, stop_x, stop_y)
161− self.assertThat(launcher.shown, Eventually(Equals(True)))
tests/autopilot/unity8/shell/tests/test_hud.pynew file+151
@@ -0,0 +1,151 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+from __future__ import absolute_import
21+
22+from unity8.shell import with_lightdm_mock
23+from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
24+
25+from testtools.matchers import Equals
26+from autopilot.matchers import Eventually
27+
28+
29+class TestHud(UnityTestCase):
30+
31+ """Tests the Shell HUD."""
32+
33+ scenarios = _get_device_emulation_scenarios()
34+
35+ def test_show_hud_button_appears(self):
36+ """Swiping up while an app is active must show the 'show hud' button.
37+
38+ """
39+ self.launch_unity()
40+ self.main_window.get_greeter().unlock()
41+ window = self.main_window.get_qml_view()
42+ hud_show_button = self.main_window.get_hud_show_button()
43+ hud = self.main_window.get_hud()
44+
45+ self._launch_test_app_from_app_screen()
46+
47+ swipe_coords = hud.get_button_swipe_coords(
48+ window,
49+ hud_show_button
50+ )
51+ self.touch.press(swipe_coords.start_x, swipe_coords.start_y)
52+ self.addCleanup(self.touch.release)
53+ self.touch._finger_move(swipe_coords.end_x, swipe_coords.end_y)
54+ self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
55+
56+ def test_show_hud_appears(self):
57+ """Releasing the touch on the 'show hud' button must display the hud.
58+
59+ """
60+ self.launch_unity()
61+ self.main_window.get_greeter().unlock()
62+ window = self.main_window.get_qml_view()
63+ hud_show_button = self.main_window.get_hud_show_button()
64+ hud = self.main_window.get_hud()
65+
66+ self._launch_test_app_from_app_screen()
67+
68+ swipe_coords = hud.get_button_swipe_coords(
69+ window,
70+ hud_show_button
71+ )
72+ self.touch.press(swipe_coords.start_x, swipe_coords.start_y)
73+ self.addCleanup(self._maybe_release_finger)
74+ self.touch._finger_move(swipe_coords.end_x, swipe_coords.end_y)
75+
76+ self.assertThat(hud.shown, Eventually(Equals(False)))
77+ self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
78+ self.touch.release()
79+ self.assertThat(hud.shown, Eventually(Equals(True)))
80+
81+ def test_hide_hud_click(self):
82+ """Tapping the close button of the Hud must dismiss it."""
83+ self.launch_unity()
84+ self.main_window.get_greeter().unlock()
85+ hud = self.main_window.get_hud()
86+
87+ self._launch_test_app_from_app_screen()
88+ hud.show()
89+
90+ x, y = hud.get_close_button_coords()
91+ self.touch.tap(x, y)
92+ self.assertThat(hud.shown, Eventually(Equals(False)))
93+
94+ def test_hide_hud_dragging(self):
95+ """Once open the Hud must close if the upper bar is dragged and
96+ released downward.
97+
98+ """
99+ self.launch_unity()
100+ self.main_window.get_greeter().unlock()
101+ hud = self.main_window.get_hud()
102+ window = self.main_window.get_qml_view()
103+
104+ self._launch_test_app_from_app_screen()
105+ hud.show()
106+
107+ start_x, start_y = hud.get_close_button_coords()
108+ end_x = start_x
109+ end_y = int(window.height / 2)
110+
111+ self.touch.drag(start_x, start_y, end_x, end_y)
112+ self.assertThat(hud.shown, Eventually(Equals(False)))
113+
114+ def test_launcher_hides_hud(self):
115+ """Opening the Launcher while the Hud is active must close the Hud."""
116+ self.launch_unity()
117+ self.main_window.get_greeter().unlock()
118+ hud = self.main_window.get_hud()
119+ launcher = self.main_window.get_launcher()
120+
121+ self._launch_test_app_from_app_screen()
122+
123+ hud.show()
124+ launcher.show()
125+
126+ self.assertThat(hud.shown, Eventually(Equals(False)))
127+
128+ def _launch_test_app_from_app_screen(self):
129+ """Launches the camera app using the Dash UI.
130+
131+ Because when testing on the desktop running
132+ self.launch_application() will launch the application on the desktop
133+ itself and not within the Unity UI.
134+
135+ """
136+ dash = self.main_window.get_dash()
137+ icon = dash.get_application_icon('Camera')
138+ self.touch.tap_object(icon)
139+
140+ # Ensure application is open
141+ bottombar = self.main_window.get_bottombar()
142+ self.assertThat(bottombar.applicationIsOnForeground,
143+ Eventually(Equals(True)))
144+
145+ # Because some tests are manually manipulating the finger, we want to
146+ # cleanup if the test fails, but we don't want to fail with an exception if
147+ # we don't.
148+ def _maybe_release_finger(self):
149+ """Only release the finger if it is in fact down."""
150+ if self.touch._touch_finger is not None:
151+ self.touch.release()
tests/autopilot/unity8/shell/tests/test_lock_screen.pynew file+131
@@ -0,0 +1,131 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
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.
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+
21+from __future__ import absolute_import
22+
23+from unity8.shell import with_lightdm_mock
24+from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
25+
26+from autopilot.matchers import Eventually
27+from autopilot.platform import model
28+from testtools import skipUnless
29+from testtools.matchers import Equals
30+
31+
32+class TestLockscreen(UnityTestCase):
33+
34+ """Tests for the lock screen."""
35+
36+ scenarios = _get_device_emulation_scenarios()
37+
38+ @with_lightdm_mock("single-pin")
39+ def test_can_unlock_pin_screen(self):
40+ """Must be able to unlock the PIN entry lock screen."""
41+ self.launch_unity()
42+ greeter = self.main_window.get_greeter()
43+ greeter.unlock()
44+
45+ lockscreen = self._wait_for_lockscreen()
46+ self._enter_pincode("1234")
47+
48+ self.assertThat(lockscreen.shown, Eventually(Equals(False)))
49+
50+ @with_lightdm_mock("single-passphrase")
51+ def test_can_unlock_passphrase_screen(self):
52+ """Must be able to unlock the passphrase entry screen."""
53+ self.launch_unity()
54+ greeter = self.main_window.get_greeter()
55+ greeter.unlock()
56+
57+ lockscreen = self._wait_for_lockscreen()
58+ self._enter_passphrase("password")
59+
60+ self.assertThat(lockscreen.shown, Eventually(Equals(False)))
61+
62+ @with_lightdm_mock("single-pin")
63+ def test_pin_screen_wrong_code(self):
64+ """Entering the wrong pin code must not dismiss the lock screen."""
65+ self.launch_unity()
66+ greeter = self.main_window.get_greeter()
67+ greeter.unlock()
68+
69+ lockscreen = self._wait_for_lockscreen()
70+ self._enter_pincode("4321")
71+
72+ pinentryField = self.main_window.get_pinentryField()
73+ self.assertThat(pinentryField.text, Eventually(Equals("")))
74+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
75+
76+ @with_lightdm_mock("single-passphrase")
77+ def test_passphrase_screen_wrong_password(self):
78+ """Entering the wrong password must not dismiss the lock screen."""
79+ self.launch_unity()
80+ greeter = self.main_window.get_greeter()
81+ greeter.unlock()
82+
83+ lockscreen = self._wait_for_lockscreen()
84+ self._enter_passphrase("foobar")
85+
86+ pinentryField = self.main_window.get_pinentryField()
87+ self.assertThat(pinentryField.text, Eventually(Equals("")))
88+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
89+
90+ def _wait_for_lockscreen(self):
91+ """Wait for the lock screen to load, and return it."""
92+ pinPadLoader = self.main_window.get_pinPadLoader()
93+ self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
94+ lockscreen = self.main_window.get_lockscreen()
95+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
96+ return lockscreen
97+
98+ def _enter_pincode(self, code):
99+ """Enter code 'code' into the single-pin lightdm pincode entry
100+ screen.
101+
102+ :param code: must be a string of numeric characters.
103+ :raises: TypeError if code is not a string.
104+ :raises: ValueError if code contains non-numeric characters.
105+
106+ """
107+
108+ if not isinstance(code, basestring):
109+ raise TypeError("'code' parameter must be a string.")
110+ for num in code:
111+ if not num.isdigit():
112+ raise ValueError(
113+ "'code' parameter contains non-numeric characters."
114+ )
115+ self.touch.tap_object(self.main_window.get_pinPadButton(int(num)))
116+
117+ def _enter_passphrase(self, passphrase):
118+ """Enter the password specified in 'passphrase' into the password entry
119+ field.
120+
121+ :param passphrase: The string you want to enter.
122+ :raises: TypeError if passphrase is not a string.
123+
124+ """
125+ if not isinstance(passphrase, basestring):
126+ raise TypeError("'passphrase' parameter must be a string.")
127+
128+ pinentryField = self.main_window.get_pinentryField()
129+ self.touch.tap_object(pinentryField)
130+ self.keyboard.type(passphrase)
131+ self.keyboard.type("\n")
tests/autopilot/unity8/shell/tests/testbigscreen.pydeleted−25
@@ -1,25 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
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−"""Add tests here if you want to ensure everything is aligned correctly on huge screens. Because this test does not fit on most screens, you should not use input devices here or your tests are likely to fail"""
9−
10−from testtools.matchers import Equals
11−from autopilot.matchers import Eventually
12−
13−from unity8.shell.tests import ShellTestCase
14−
15−class TestBig(ShellTestCase):
16− def setUp(self):
17− super(TestBig, self).setUp("2560x1600", "20")
18− self.assertThat(self.main_window.get_qml_view().visible, Eventually(Equals(True)))
19−
20− def tearDown(self):
21− super(TestBig, self).tearDown()
22−
23− def test_hud_not_shown_greeter(self):
24− hud_showable = self.main_window.get_hud_showable()
25− self.assertThat(hud_showable.y, Eventually(Equals(1600)))
tests/autopilot/unity8/shell/tests/testhud.pydeleted−145
@@ -1,145 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
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.
12−#
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:
15−#
16−# test_testname.blacklist = (FormFactors.Tablet, FormFactors.Desktop,)
17−#
18−# Available form factors are:
19−# FormFactors.Phone
20−# FormFactors.Tablet
21−# FormFactors.Desktop
22−
23−
24−"""Tests for the Shell"""
25−
26−from __future__ import absolute_import
27−
28−from unity8.shell.tests import ShellTestCase, FormFactors
29−from unity8.shell.tests.helpers import TestShellHelpers
30−
31−from autopilot.input import Mouse, Touch, Pointer
32−from testtools.matchers import Equals, NotEquals, GreaterThan, MismatchError
33−from autopilot.matchers import Eventually
34−from autopilot.display import Display
35−from autopilot.platform import model
36−
37−import unittest
38−import time
39−import os
40−from os import path
41−
42−
43−class TestHud(ShellTestCase, TestShellHelpers):
44−
45− """Tests the Shell"""
46−
47− # Scenarios:
48− # Fill in the scenarios to run the whole test suite with multiple configurations.
49− # Use app_width, app_height and grid_unit_px to set the apps geometry.
50− # Set app_width and app_height to 0 to use fullscreen.
51− # Set grid_unit_px to 0 to use the current system environment.
52−
53− if model() == 'Desktop':
54− scenarios = [
55− ('Nexus 4', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single")),
56− ('Nexus 10', dict(app_width=2560, app_height=1600, grid_unit_px=20, lightdm_mock="full")),
57−# TODO: don't run fullscreen tests just yet as the VM is performing too badly for that. Enable this once
58−# Autopilot tests are running on bear metal.
59−# ('Fullscreen', dict(app_width=0, app_height=0, grid_unit_px=10, lightdm_mock="full")),
60− ]
61− else:
62− scenarios = [
63− ('Fullscreen', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single")),
64− ]
65−
66− def setUp(self):
67− self.touch = Touch.create()
68−
69− sg = Display().create()
70− divisor = 1
71− while (sg.get_screen_width() < self.app_width / divisor or sg.get_screen_height() < self.app_height / divisor):
72− divisor = divisor * 2
73− super(TestHud, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
74−
75− dash = self.main_window.get_dash()
76− self.assertThat(dash.showScopeOnLoaded, Eventually(Equals(""), timeout=30))
77−
78− def test_show_hud(self):
79− hud = self.main_window.get_hud()
80− self.unlock_greeter()
81− self.show_hud()
82− self.assertThat(hud.shown, Eventually(Equals(True)))
83−
84− def test_show_hud_button_dont_open(self):
85− self.unlock_greeter()
86− self.open_first_dash_home_app()
87− hud_show_button = self.main_window.get_hud_show_button()
88− hud = self.main_window.get_hud()
89− window = self.main_window.get_qml_view()
90− start_x = int(window.x + window.width / 2)
91− start_y = window.y + window.height - 2
92− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
93− self.touch.press(start_x, start_y)
94− self.touch._finger_move(start_x, start_y - self.grid_size)
95− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
96− self.touch._finger_move(start_x, start_y - self.grid_size * 2)
97− self.touch._finger_move(start_x, start_y - self.grid_size * 3)
98− self.touch._finger_move(start_x, start_y - self.grid_size * 4)
99− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
100− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(False)))
101− self.touch._finger_move(start_x, start_y - self.grid_size * 34)
102− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
103− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(True)))
104− self.touch._finger_move(start_x, start_y - self.grid_size)
105− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
106− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(False)))
107− self.touch.release()
108− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
109− self.assertThat(hud.shown, Eventually(Equals(False)))
110−
111− def test_hide_hud_click(self):
112− hud = self.main_window.get_hud()
113− self.unlock_greeter()
114− self.show_hud()
115− self.close_hud_click()
116− self.assertThat(hud.shown, Eventually(Equals(False)))
117−
118− def test_hide_hud_click_outside_handle(self):
119− hud = self.main_window.get_hud()
120− self.unlock_greeter()
121− self.show_hud()
122− rect = hud.globalRect
123− x = int(rect[0] + rect[2] / 2)
124− y = rect[1] + hud.handleHeight + 1
125− self.touch.tap(x, y)
126− self.assertRaises(MismatchError, lambda: self.assertThat(hud.shown, Eventually(Equals(False), timeout=3)))
127−
128− def test_hide_hud_dragging(self):
129− hud = self.main_window.get_hud()
130− self.unlock_greeter()
131− self.show_hud()
132− rect = hud.globalRect
133− start_x = rect[0] + (rect[2] - rect[0]) / 2
134− start_y = rect[1] + 1
135− stop_x = start_x
136− stop_y = start_y + (rect[3] - rect[1]) / 2
137− self.touch.drag(start_x, start_y, stop_x, stop_y)
138− self.assertThat(hud.shown, Eventually(Equals(False)))
139−
140− def test_hide_hud_launcher(self):
141− hud = self.main_window.get_hud()
142− self.unlock_greeter()
143− self.show_hud()
144− self.show_launcher()
145− self.assertThat(hud.shown, Eventually(Equals(False)))
tests/autopilot/unity8/shell/tests/testlockscreen.pydeleted−118
@@ -1,118 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
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.
12−#
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:
15−#
16−# test_testname.blacklist = (FormFactors.Tablet, FormFactors.Desktop,)
17−#
18−# Available form factors are:
19−# FormFactors.Phone
20−# FormFactors.Tablet
21−# FormFactors.Desktop
22−
23−
24−"""Tests for the Shell"""
25−
26−from __future__ import absolute_import
27−
28−from unity8.shell.tests import ShellTestCase, FormFactors
29−from unity8.shell.tests.helpers import TestShellHelpers
30−
31−from autopilot.input import Mouse, Touch, Pointer
32−from testtools.matchers import Equals, NotEquals, GreaterThan, MismatchError
33−from autopilot.matchers import Eventually
34−from autopilot.display import Display
35−from autopilot.platform import model
36−
37−import unittest
38−import time
39−import os
40−from os import path
41−
42−
43−class TestLockscreens(ShellTestCase, TestShellHelpers):
44−
45− """Tests the Lockscreens"""
46−
47− # Scenarios:
48− # Fill in the scenarios to run the whole test suite with multiple configurations.
49− # Use app_width, app_height and grid_unit_px to set the apps geometry.
50− # Set app_width and app_height to 0 to use fullscreen.
51− # Set grid_unit_px to 0 to use the current system environment.
52−
53− if model() == 'Desktop':
54− scenarios = [
55− ('Pinlock', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single-pin")),
56− ('Keylock', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single-passphrase")),
57− ]
58− else:
59− scenarios = [
60− ('Pinlock', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single-pin")),
61− ('Keylock', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single-key")),
62− ]
63−
64− def setUp(self):
65− self.touch = Touch.create()
66−
67− sg = Display().create()
68− divisor = 1
69− while (sg.get_screen_width() < self.app_width / divisor or sg.get_screen_height() < self.app_height / divisor):
70− divisor = divisor * 2
71− super(TestLockscreens, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
72−
73− dash = self.main_window.get_dash()
74− self.assertThat(dash.showScopeOnLoaded, Eventually(Equals(""), timeout=30))
75−
76− def test_unlock(self):
77− self.unlock_greeter()
78−
79− pinPadLoader = self.main_window.get_pinPadLoader();
80− self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
81− lockscreen = self.main_window.get_lockscreen();
82− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
83−
84− if self.lightdm_mock == "single-pin":
85− self.touch.tap_object(self.main_window.get_pinPadButton(1))
86− self.touch.tap_object(self.main_window.get_pinPadButton(2))
87− self.touch.tap_object(self.main_window.get_pinPadButton(3))
88− self.touch.tap_object(self.main_window.get_pinPadButton(4))
89− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
90− else:
91− pinentryField = self.main_window.get_pinentryField()
92− self.touch.tap_object(pinentryField)
93− self.keyboard.type("password\n")
94− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
95−
96− def test_unlock_wrong(self):
97− self.unlock_greeter()
98−
99− pinPadLoader = self.main_window.get_pinPadLoader();
100− self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
101− lockscreen = self.main_window.get_lockscreen();
102− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
103− pinentryField = self.main_window.get_pinentryField()
104−
105− if self.lightdm_mock == "single-pin":
106− self.touch.tap_object(self.main_window.get_pinPadButton(4))
107− self.touch.tap_object(self.main_window.get_pinPadButton(3))
108− self.touch.tap_object(self.main_window.get_pinPadButton(2))
109− self.assertThat(pinentryField.text, Eventually(Equals("432")))
110− self.touch.tap_object(self.main_window.get_pinPadButton(1))
111−
112− self.assertThat(pinentryField.text, Eventually(Equals("")))
113− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
114− else:
115− self.touch.tap_object(pinentryField)
116− self.keyboard.type("foobar\n")
117− self.assertThat(pinentryField.text, Eventually(Equals("")))
118− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
tests/mocks/Unity/Indicators/CMakeLists.txt+1
@@ -12,6 +12,7 @@ include_directories(
1212set(IndicatorsFakeQml_SOURCES
1313 fakeplugin.cpp
1414 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
15+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
1516)
1617
1718set(IndicatorsFakeQml_RESOURCES
tests/mocks/Unity/Indicators/fakeplugin.cpp+4
@@ -24,11 +24,15 @@
2424
2525// local
2626#include "indicators.h"
27+#include "menucontentactivator.h"
2728
2829void IndicatorsFakePlugin::registerTypes(const char * uri)
2930{
3031 Q_INIT_RESOURCE(indicators_fake);
3132
33+ qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
34+
35+ qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
3236 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
3337 qmlRegisterUncreatableType<NetworkActionState>(uri, 0, 1, "NetworkActionState", "Can't create NetworkActionState class");
3438 qmlRegisterUncreatableType<NetworkConnection>(uri, 0, 1, "NetworkConnection", "Can't create NetworkConnection class");
tests/mocks/Unity/Notifications/CMakeLists.txt−4
@@ -9,7 +9,3 @@ add_custom_target(NotificationsQmlFiles ALL
99 COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
1010 DEPENDS ${QMLFILES}
1111)
12−
13−install(FILES ${QMLFILES}
14− DESTINATION ${SHELL_PRIVATE_LIBDIR}/qml/mocks/Unity/Notifications
15− )
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/plugins/Unity/Indicators/CMakeLists.txt+1
@@ -24,4 +24,5 @@ endmacro(run_tests)
2424run_tests(
2525 indicatorsmanagertest
2626 indicatorsmodeltest
27+ menucontentactivatortest
2728 )
tests/plugins/Unity/Indicators/menucontentactivatortest.cppnew file+303
@@ -0,0 +1,303 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Nick Dedekind <nick.dedekind@canonical.com>
18+ */
19+
20+#include "menucontentactivator.h"
21+#include "paths.h"
22+
23+#include <QtTest>
24+#include <QDebug>
25+
26+class FakeTimer : public UnityIndicators::AbstractTimer
27+{
28+ Q_OBJECT
29+public:
30+ FakeTimer(QObject *parent = 0)
31+ : UnityIndicators::AbstractTimer(parent)
32+ {}
33+
34+ virtual int interval() const { return m_duration; }
35+ virtual void setInterval(int msecs) { m_duration = msecs; }
36+
37+ void emitTimeout() {
38+ if (isRunning()) {
39+ Q_EMIT timeout();
40+ }
41+ }
42+private:
43+ int m_duration;
44+};
45+
46+
47+class MenuConentActivatorTest : public QObject
48+{
49+ Q_OBJECT
50+private Q_SLOTS:
51+ void init() // called right before each and every test function is executed
52+ {
53+ m_fakeTimeSource = new FakeTimer(this);
54+ m_deltas.clear();
55+ }
56+ void cleanup() // called right after each and every test function is executed
57+ {
58+ delete m_fakeTimeSource;
59+ m_fakeTimeSource = 0;
60+ }
61+
62+ /*
63+ * Tests the ordering of activation
64+ */
65+ void testContentActiveChange()
66+ {
67+ MenuContentActivator activator;
68+ activator.setContentTimer(m_fakeTimeSource);
69+ activator.setCount(10);
70+ activator.setBaseIndex(5);
71+ activator.restart();
72+
73+ QCOMPARE(activator.isMenuContentActive(4), false);
74+ QCOMPARE(activator.isMenuContentActive(5), true); // always active at base index.
75+ QCOMPARE(activator.isMenuContentActive(6), false);
76+
77+ m_fakeTimeSource->emitTimeout();
78+ QCOMPARE(activator.isMenuContentActive(4), false);
79+ QCOMPARE(activator.isMenuContentActive(5), true);
80+ QCOMPARE(activator.isMenuContentActive(6), true);
81+ QCOMPARE(activator.isMenuContentActive(7), false);
82+
83+ m_fakeTimeSource->emitTimeout();
84+ QCOMPARE(activator.isMenuContentActive(3), false);
85+ QCOMPARE(activator.isMenuContentActive(4), true);
86+ QCOMPARE(activator.isMenuContentActive(5), true);
87+ QCOMPARE(activator.isMenuContentActive(6), true);
88+ QCOMPARE(activator.isMenuContentActive(7), false);
89+
90+ m_fakeTimeSource->emitTimeout();
91+ QCOMPARE(activator.isMenuContentActive(3), false);
92+ QCOMPARE(activator.isMenuContentActive(4), true);
93+ QCOMPARE(activator.isMenuContentActive(5), true);
94+ QCOMPARE(activator.isMenuContentActive(6), true);
95+ QCOMPARE(activator.isMenuContentActive(7), true);
96+ QCOMPARE(activator.isMenuContentActive(8), false);
97+
98+ m_fakeTimeSource->emitTimeout();
99+ QCOMPARE(activator.isMenuContentActive(2), false);
100+ QCOMPARE(activator.isMenuContentActive(3), true);
101+ QCOMPARE(activator.isMenuContentActive(4), true);
102+ QCOMPARE(activator.isMenuContentActive(5), true);
103+ QCOMPARE(activator.isMenuContentActive(6), true);
104+ QCOMPARE(activator.isMenuContentActive(7), true);
105+ QCOMPARE(activator.isMenuContentActive(8), false);
106+
107+ m_fakeTimeSource->emitTimeout();
108+ QCOMPARE(activator.isMenuContentActive(2), false);
109+ QCOMPARE(activator.isMenuContentActive(3), true);
110+ QCOMPARE(activator.isMenuContentActive(4), true);
111+ QCOMPARE(activator.isMenuContentActive(5), true);
112+ QCOMPARE(activator.isMenuContentActive(6), true);
113+ QCOMPARE(activator.isMenuContentActive(7), true);
114+ QCOMPARE(activator.isMenuContentActive(8), true);
115+ QCOMPARE(activator.isMenuContentActive(9), false);
116+
117+ m_fakeTimeSource->emitTimeout();
118+ QCOMPARE(activator.isMenuContentActive(1), false);
119+ QCOMPARE(activator.isMenuContentActive(2), true);
120+ QCOMPARE(activator.isMenuContentActive(3), true);
121+ QCOMPARE(activator.isMenuContentActive(4), true);
122+ QCOMPARE(activator.isMenuContentActive(5), true);
123+ QCOMPARE(activator.isMenuContentActive(6), true);
124+ QCOMPARE(activator.isMenuContentActive(7), true);
125+ QCOMPARE(activator.isMenuContentActive(8), true);
126+ QCOMPARE(activator.isMenuContentActive(9), false);
127+
128+ m_fakeTimeSource->emitTimeout();
129+ QCOMPARE(activator.isMenuContentActive(1), false);
130+ QCOMPARE(activator.isMenuContentActive(2), true);
131+ QCOMPARE(activator.isMenuContentActive(3), true);
132+ QCOMPARE(activator.isMenuContentActive(4), true);
133+ QCOMPARE(activator.isMenuContentActive(5), true);
134+ QCOMPARE(activator.isMenuContentActive(6), true);
135+ QCOMPARE(activator.isMenuContentActive(7), true);
136+ QCOMPARE(activator.isMenuContentActive(8), true);
137+ QCOMPARE(activator.isMenuContentActive(9), true);
138+ QCOMPARE(activator.isMenuContentActive(10), false);
139+
140+ m_fakeTimeSource->emitTimeout();
141+ QCOMPARE(activator.isMenuContentActive(0), false);
142+ QCOMPARE(activator.isMenuContentActive(1), true);
143+ QCOMPARE(activator.isMenuContentActive(2), true);
144+ QCOMPARE(activator.isMenuContentActive(3), true);
145+ QCOMPARE(activator.isMenuContentActive(4), true);
146+ QCOMPARE(activator.isMenuContentActive(5), true);
147+ QCOMPARE(activator.isMenuContentActive(6), true);
148+ QCOMPARE(activator.isMenuContentActive(7), true);
149+ QCOMPARE(activator.isMenuContentActive(8), true);
150+ QCOMPARE(activator.isMenuContentActive(9), true);
151+ QCOMPARE(activator.isMenuContentActive(10), false);
152+
153+ m_fakeTimeSource->emitTimeout();
154+ QCOMPARE(activator.isMenuContentActive(-1), false);
155+ QCOMPARE(activator.isMenuContentActive(0), true);
156+ QCOMPARE(activator.isMenuContentActive(1), true);
157+ QCOMPARE(activator.isMenuContentActive(2), true);
158+ QCOMPARE(activator.isMenuContentActive(3), true);
159+ QCOMPARE(activator.isMenuContentActive(4), true);
160+ QCOMPARE(activator.isMenuContentActive(5), true);
161+ QCOMPARE(activator.isMenuContentActive(6), true);
162+ QCOMPARE(activator.isMenuContentActive(7), true);
163+ QCOMPARE(activator.isMenuContentActive(8), true);
164+ QCOMPARE(activator.isMenuContentActive(9), true);
165+ QCOMPARE(activator.isMenuContentActive(10), false);
166+ }
167+
168+ /*
169+ * Tests the delta calculation for each timeout.
170+ */
171+ void testDelta()
172+ {
173+ MenuContentActivator activator;
174+ activator.setContentTimer(m_fakeTimeSource);
175+ activator.setCount(9);
176+ activator.setBaseIndex(3);
177+
178+ connect(&activator, &MenuContentActivator::deltaChanged,
179+ this, &MenuConentActivatorTest::onDeltaChange);
180+ activator.restart();
181+
182+ QCOMPARE(m_deltas, QList<int>()); // empty
183+ QCOMPARE(getIndexList(&activator), QList<int>()); // empty
184+
185+ m_fakeTimeSource->emitTimeout();
186+ QCOMPARE(m_deltas, QList<int>() << 1);
187+ QCOMPARE(getIndexList(&activator), QList<int>() << 4);
188+
189+ m_fakeTimeSource->emitTimeout();
190+ QCOMPARE(m_deltas, QList<int>() << 1 << -1);
191+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2);
192+
193+ m_fakeTimeSource->emitTimeout();
194+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2);
195+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5);
196+
197+ m_fakeTimeSource->emitTimeout();
198+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2);
199+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1);
200+
201+ m_fakeTimeSource->emitTimeout();
202+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3);
203+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6);
204+
205+ m_fakeTimeSource->emitTimeout();
206+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3);
207+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0);
208+
209+ m_fakeTimeSource->emitTimeout();
210+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3 << 4);
211+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0 << 7);
212+
213+ m_fakeTimeSource->emitTimeout();
214+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3 << 4 << 5);
215+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0 << 7 << 8);
216+
217+ m_fakeTimeSource->emitTimeout();
218+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3 << 4 << 5);
219+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0 << 7 << 8);
220+ }
221+
222+ /*
223+ * Tests that changing the base index re-prioritizes the activation
224+ * around the base index.
225+ */
226+ void testBaseIndexChange()
227+ {
228+ MenuContentActivator activator;
229+ activator.setContentTimer(m_fakeTimeSource);
230+ activator.setCount(12);
231+ activator.setBaseIndex(5);
232+ activator.restart();
233+
234+ m_fakeTimeSource->emitTimeout();
235+ m_fakeTimeSource->emitTimeout();
236+ QCOMPARE(activator.isMenuContentActive(3), false);
237+ QCOMPARE(activator.isMenuContentActive(4), true);
238+ QCOMPARE(activator.isMenuContentActive(5), true); // always active at base index.
239+ QCOMPARE(activator.isMenuContentActive(6), true);
240+ QCOMPARE(activator.isMenuContentActive(7), false);
241+
242+ activator.setBaseIndex(8);
243+ QCOMPARE(activator.isMenuContentActive(3), false);
244+ QCOMPARE(activator.isMenuContentActive(4), true);
245+ QCOMPARE(activator.isMenuContentActive(5), true);
246+ QCOMPARE(activator.isMenuContentActive(6), true);
247+ QCOMPARE(activator.isMenuContentActive(7), false);
248+ QCOMPARE(activator.isMenuContentActive(8), true);
249+ QCOMPARE(activator.isMenuContentActive(9), false);
250+
251+ m_fakeTimeSource->emitTimeout();
252+ QCOMPARE(activator.isMenuContentActive(3), false);
253+ QCOMPARE(activator.isMenuContentActive(4), true);
254+ QCOMPARE(activator.isMenuContentActive(5), true);
255+ QCOMPARE(activator.isMenuContentActive(6), true);
256+ QCOMPARE(activator.isMenuContentActive(7), false);
257+ QCOMPARE(activator.isMenuContentActive(8), true);
258+ QCOMPARE(activator.isMenuContentActive(9), true);
259+ QCOMPARE(activator.isMenuContentActive(10), false);
260+
261+ m_fakeTimeSource->emitTimeout();
262+ QCOMPARE(activator.isMenuContentActive(3), false);
263+ QCOMPARE(activator.isMenuContentActive(4), true);
264+ QCOMPARE(activator.isMenuContentActive(5), true);
265+ QCOMPARE(activator.isMenuContentActive(6), true);
266+ QCOMPARE(activator.isMenuContentActive(7), true);
267+ QCOMPARE(activator.isMenuContentActive(8), true);
268+ QCOMPARE(activator.isMenuContentActive(9), true);
269+ QCOMPARE(activator.isMenuContentActive(10), false);
270+
271+ m_fakeTimeSource->emitTimeout();
272+ QCOMPARE(activator.isMenuContentActive(3), false);
273+ QCOMPARE(activator.isMenuContentActive(4), true);
274+ QCOMPARE(activator.isMenuContentActive(5), true);
275+ QCOMPARE(activator.isMenuContentActive(6), true);
276+ QCOMPARE(activator.isMenuContentActive(7), true);
277+ QCOMPARE(activator.isMenuContentActive(8), true);
278+ QCOMPARE(activator.isMenuContentActive(9), true);
279+ QCOMPARE(activator.isMenuContentActive(10), true);
280+ QCOMPARE(activator.isMenuContentActive(11), false);
281+ }
282+
283+ void onDeltaChange(int delta)
284+ {
285+ m_deltas << delta;
286+ }
287+
288+private:
289+ QList<int> getIndexList(MenuContentActivator* activator)
290+ {
291+ QList<int> list;
292+ Q_FOREACH(int delta, m_deltas) {
293+ list << (activator->baseIndex() + delta);
294+ }
295+ return list;
296+ }
297+
298+ FakeTimer* m_fakeTimeSource;
299+ QList<int> m_deltas;
300+};
301+
302+QTEST_MAIN(MenuConentActivatorTest)
303+#include "menucontentactivatortest.moc"
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/Notifications/tst_Notifications.qml+20−9
@@ -48,6 +48,7 @@ Row {
4848 function addSnapDecisionNotification() {
4949 var n = {
5050 type: Notification.SnapDecision,
51+ hints: Notification.ButtonTint,
5152 summary: "Tom Ato",
5253 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
5354 icon: "../graphics/avatars/funky.png",
@@ -189,8 +190,9 @@ Row {
189190 function test_NotificationRenderer_data() {
190191 return [
191192 {
192− tag: "Snap Decision with secondary icon",
193+ tag: "Snap Decision with secondary icon and button-tint",
193194 type: Notification.SnapDecision,
195+ hints: Notification.ButtonTint,
194196 summary: "Tom Ato",
195197 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
196198 icon: "../graphics/avatars/funky.png",
@@ -205,7 +207,8 @@ Row {
205207 interactiveAreaEnabled: false,
206208 iconVisible: true,
207209 secondaryIconVisible: true,
208− buttonRowVisible: true
210+ buttonRowVisible: true,
211+ buttonTinted: true
209212 },
210213 {
211214 tag: "Ephemeral notification - icon-summary layout",
@@ -220,7 +223,8 @@ Row {
220223 interactiveAreaEnabled: false,
221224 iconVisible: false,
222225 secondaryIconVisible: true,
223− buttonRowVisible: false
226+ buttonRowVisible: false,
227+ buttonTinted: false
224228 },
225229 {
226230 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
@@ -235,7 +239,8 @@ Row {
235239 interactiveAreaEnabled: false,
236240 iconVisible: false,
237241 secondaryIconVisible: true,
238− buttonRowVisible: false
242+ buttonRowVisible: false,
243+ buttonTinted: false
239244 },
240245 {
241246 tag: "Interactive notification",
@@ -250,10 +255,11 @@ Row {
250255 interactiveAreaEnabled: true,
251256 iconVisible: true,
252257 secondaryIconVisible: false,
253− buttonRowVisible: false
258+ buttonRowVisible: false,
259+ buttonTinted: false
254260 },
255261 {
256− tag: "Snap Decision without secondary icon",
262+ tag: "Snap Decision without secondary icon and no button-tint",
257263 type: Notification.SnapDecision,
258264 summary: "Bro Coly",
259265 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
@@ -266,7 +272,8 @@ Row {
266272 interactiveAreaEnabled: false,
267273 iconVisible: true,
268274 secondaryIconVisible: false,
269− buttonRowVisible: true
275+ buttonRowVisible: true,
276+ buttonTinted: false
270277 },
271278 {
272279 tag: "Ephemeral notification",
@@ -281,7 +288,8 @@ Row {
281288 interactiveAreaEnabled: false,
282289 iconVisible: true,
283290 secondaryIconVisible: true,
284− buttonRowVisible: false
291+ buttonRowVisible: false,
292+ buttonTinted: false
285293 }
286294 ]
287295 }
@@ -356,6 +364,9 @@ Row {
356364 actionSpy.clear()
357365 }
358366
367+ // check the tinting of the positive/right button
368+ verify(buttonAccept.gradient === data.buttonTinted ? UbuntuColors.orangeGradient : UbuntuColors.greyGradient, "button has the wrong color-tint")
369+
359370 // click the positive/right button
360371 mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2)
361372 actionSpy.wait()
@@ -370,7 +381,7 @@ Row {
370381 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
371382 waitForRendering(notification)
372383 actionSpy.clear()
373− 
384+
374385 // test the additional buttons
375386 for (var i = 2; i < data.actions.length; i++) {
376387 waitForRendering(notification)
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.qml+102−65
@@ -19,14 +19,35 @@
1919
2020import QtQuick 2.0
2121import QtTest 1.0
22−import Unity.Test 0.1 as UT
2322import Ubuntu.Application 0.1
24−import GSettings 1.0
23+import Unity.Test 0.1 as UT
2524
2625import "../.."
2726
28−Shell {
29− id: shell
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+
3051 UT.UnityTestCase {
3152 name: "Shell"
3253 when: windowShown
@@ -37,14 +58,10 @@ Shell {
3758 var touchY = shell.height / 2;
3859 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
3960
40− // wait until the animation has finished
41−
4261 var dash = findChild(shell, "dash");
62+ // wait until the animation has finished
4363 tryCompare(dash, "contentScale", 1.0);
4464 tryCompare(dash, "opacity", 1.0);
45−
46− var greeter = findChild(shell, "greeter");
47− tryCompare(greeter, "x", -greeter.width);
4865 }
4966
5067 function cleanup() {
@@ -56,6 +73,42 @@ Shell {
5673 swipeUntilScopeViewIsReached(dashHome);
5774 }
5875
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+
59112 /*
60113 Perform a right-edge drag when the Dash is being show and there are
61114 no running/minimized apps to be restored.
@@ -64,7 +117,7 @@ Shell {
64117 user that his right-edge drag gesture has been successfully recognized
65118 but there is no application to be brought to foreground.
66119 */
67− function test_rightEdgeDragWithNoRunningApps() {
120+ function checkRightEdgeDragWithNoRunningApps() {
68121 var touchX = shell.width - (shell.edgeSize / 2);
69122 var touchY = shell.height / 2;
70123
@@ -88,56 +141,40 @@ Shell {
88141 }
89142
90143 /*
91− Regression test for bug https://bugs.launchpad.net/touch-preview-images/+bug/1193419
92−
93− When the user minimizes an application (left-edge swipe) he should always end up in the "Running Apps"
94− category of the "Applications" scope view.
95−
96− Steps:
97− - go to apps lens
98− - scroll to the bottom
99− - reveal launcher and launch an app
100− - perform long left edge swipe to go minimize the app and go back to the dash.
144+ Perform a right-edge drag when the Dash is being show and there is
145+ a running/minimized app to be restored.
101146
102− Expected Results
103− - apps lens shown and Running Apps visible on screen
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.
104150 */
105− function test_minimizingAppTakesToRunningApps() {
106− var dashApps = findChild(shell, "DashApps");
107−
108− swipeUntilScopeViewIsReached(dashApps);
109−
110− // swipe finger up until the running/recent apps section (which we assume
111− // it's the first one) is as far from view as possible.
112− var appsCategoryListView = findChild(dashApps, "categoryListView");
113− while (!appsCategoryListView.atYEnd) {
114− swipeUpFromCenter();
115− tryCompare(appsCategoryListView, "moving", false);
116− }
151+ function checkRightEdgeDragWithMinimizedApp() {
152+ var touchX = shell.width - (shell.edgeSize / 2);
153+ var touchY = shell.height / 2;
117154
118− // Switch away from the Applications scope.
119− swipeRightFromCenter();
120− waitUntilItemStopsMoving(dashApps);
121− verify(!itemIsOnScreen(dashApps));
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);
122159
123− dragLauncherIntoView();
160+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
161+ true /* beginTouch */, false /* endTouch */);
124162
125− // Launch an app from the launcher
126− tapOnAppIconInLauncher();
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);
127166
128− waitUntilApplicationWindowIsFullyVisible();
167+ touchRelease(shell, shell.width * 0.1, touchY);
129168
130− // Minimize the application we just launched
131− swipeFromLeftEdge();
169+ // dash should have gone away, now that the app is on foreground
170+ tryCompare(dash, "visible", false);
171+ }
132172
133− // Wait for the whole UI to settle down
173+ // Wait for the whole UI to settle down
174+ function waitForUIToSettle() {
134175 waitUntilApplicationWindowIsFullyHidden();
135− waitUntilItemStopsMoving(dashApps);
136− tryCompare(appsCategoryListView, "moving", false);
137−
138− verify(itemIsOnScreen(dashApps));
139− var runningApplicationsGrid = findChild(dashApps, "runningApplicationsGrid");
140− verify(itemIsOnScreen(runningApplicationsGrid));
176+ var dashContentList = findChild(shell, "dashContentList");
177+ tryCompare(dashContentList, "moving", false);
141178 }
142179
143180 function dragLauncherIntoView() {
@@ -209,6 +246,20 @@ Shell {
209246 touchFlick(shell, touchStartX, touchStartY, touchStartX, 0);
210247 }
211248
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+
212263 function waitUntilItemStopsMoving(item) {
213264 var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
214265 var previousX = itemRectInShell.x;
@@ -227,20 +278,6 @@ Shell {
227278 } while (!isStill);
228279 }
229280
230− function itemIsOnScreen(item) {
231− var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
232−
233− return itemRectInShell.x >= 0
234− && itemRectInShell.y >= 0
235− && itemRectInShell.x + itemRectInShell.width <= shell.width
236− && itemRectInShell.y + itemRectInShell.height <= shell.height;
237− }
238−
239− function itemIsToLeftOfScreen(item) {
240− var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
241− return itemRectInShell.x < 0;
242− }
243−
244281 function test_wallpaper_data() {
245282 return [
246283 {tag: "red", url: "tests/data/unity/backgrounds/red.png"},
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()