@@ -0,0 +1,328 @@
2+ * Copyright 2014-2015 Canonical Ltd.
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.
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.
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/>.
19+import Unity.Test 0.1 as UT
21+import "../../../qml/Components"
22+import "../../../qml/Stages"
23+import Ubuntu.Components 1.3
24+import Ubuntu.Components.ListItems 1.3 as ListItem
25+import Unity.Application 0.1
30+ width: fakeShell.shortestDimension + controls.width
31+ height: fakeShell.longestDimension
33+ property QtObject fakeApplication: null
38+ readonly property real shortestDimension: units.gu(100)
39+ readonly property real longestDimension: units.gu(70)
41+ width: landscape ? longestDimension : shortestDimension
42+ height: landscape ? shortestDimension : longestDimension
44+ x: landscape ? (height - width) / 2 : 0
45+ y: landscape ? (width - height) / 2 : 0
47+ property bool landscape: orientationAngle == 90 || orientationAngle == 270
48+ property int orientationAngle: shellOrientationAngleSelector.value
50+ rotation: orientationAngle
53+ id: spreadDelegateLoader
54+ property int requestedX: units.gu(1)
55+ property int requestedY: units.gu(1)
56+ property int requestedWidth: units.gu(50)
57+ property int requestedHeight: units.gu(40)
60+ width: item ? item.implicitWidth : 0
61+ height: item ? item.implicitHeight : 0
64+ property bool itemDestroyed: false
65+ sourceComponent: DecoratedWindow {
67+ application: fakeApplication
68+ requestedWidth: spreadDelegateLoader.requestedWidth
69+ requestedHeight: spreadDelegateLoader.requestedHeight
70+ surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
72+ Component.onDestruction: {
73+ spreadDelegateLoader.itemDestroyed = true;
75+ Component.onCompleted: {
76+ spreadDelegateLoader.itemDestroyed = false;
80+ Rectangle { anchors.fill: parent; color: "green"; opacity: 1 }
83+ target: spreadDelegateLoader
84+ borderThickness: units.gu(2)
95+ bottom: parent.bottom
100+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
101+ spacing: units.gu(1)
103+ id: loadWithWeatherApp
104+ text: "Load with ubuntu-weather-app"
105+ onClicked: { testCase.restartWithApp("ubuntu-weather-app"); }
108+ id: loadWithGalleryApp
109+ text: "Load with gallery-app"
110+ onClicked: { testCase.restartWithApp("gallery-app"); }
113+ anchors { left: parent.left; right: parent.right }
114+ CheckBox { id: swipeToCloseCheckbox; checked: false; }
115+ Label { text: "swipeToCloseEnabled" }
118+ text: "toggle has decoration"
119+ onClicked: spreadDelegateLoader.item.hasDecoration = !spreadDelegateLoader.item.hasDecoration
122+ text: "show/hide decoration"
123+ onClicked: spreadDelegateLoader.item.showDecoration = !spreadDelegateLoader.item.showDecoration
126+ text: "toggle scaleToPreview"
127+ onClicked: spreadDelegateLoader.item.scaleToPreview = !spreadDelegateLoader.item.scaleToPreview
131+ anchors { left: parent.left; right: parent.right }
132+ CheckBox { id: closeableCheckbox; checked: false }
133+ Label { text: "closeable" }
135+ ListItem.ItemSelector {
136+ id: shellOrientationAngleSelector
137+ anchors { left: parent.left; right: parent.right }
138+ text: "shellOrientationAngle"
139+ model: ["0", "90", "180", "270"]
140+ property int value: selectedIndex * 90
143+ text: "matchShellOrientation()"
144+ onClicked: { spreadDelegateLoader.item.matchShellOrientation(); }
147+ text: "animateToShellOrientation()"
148+ onClicked: { spreadDelegateLoader.item.animateToShellOrientation(); }
155+ name: "SpreadDelegate"
160+ target: spreadDelegateLoader.item
161+ signalName: "closed"
164+ property var dragArea
165+ property Item spreadDelegate: spreadDelegateLoader.item
170+ function cleanup() {
171+ unloadSpreadDelegate();
172+ spyClosedSignal.clear();
173+ shellOrientationAngleSelector.selectedIndex = 0;
174+ root.fakeApplication = null;
178+ function restartWithApp(appId) {
179+ if (spreadDelegateLoader.active) {
180+ unloadSpreadDelegate();
182+ if (root.fakeApplication) {
183+ ApplicationManager.stopApplication(root.fakeApplication.appId);
186+ root.fakeApplication = ApplicationManager.startApplication(appId);
187+ spreadDelegateLoader.active = true;
188+ tryCompare(spreadDelegateLoader, "status", Loader.Ready);
190+ dragArea = findInvisibleChild(spreadDelegate, "dragArea");
191+ dragArea.__dateTime = fakeDateTime;
194+ function unloadSpreadDelegate() {
195+ spreadDelegateLoader.active = false;
196+ tryCompare(spreadDelegateLoader, "status", Loader.Null);
197+ tryCompare(spreadDelegateLoader, "item", null);
198+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
199+ // item might still be alive. So if we set Loader.active back to true
200+ // again right now we will get the very same Shell instance back. So no reload
201+ // actually took place. Likely because Loader waits until the next event loop
202+ // iteration to do its work. So to ensure the reload, we will wait until the
203+ // Shell instance gets destroyed.
204+ tryCompare(spreadDelegateLoader, "itemDestroyed", true);
208+ function test_swipeToClose_data() {
210+ {tag: "swipeToClose=true closeable=true -> appWindow moves away",
211+ swipeToClose: true, closeable: true },
213+ {tag: "swipeToClose=true closeable=false -> appWindow bounces back",
214+ swipeToClose: true, closeable: false },
216+ {tag: "swipeToClose=false -> appWindow stays put",
217+ swipeToClose: false, closeable: true },
221+ function test_swipeToClose(data) {
222+ loadWithGalleryApp.clicked();
223+ var displacedAppWindowWithShadow = findChild(spreadDelegateLoader.item, "displacedAppWindowWithShadow");
225+ verify(displacedAppWindowWithShadow.y === 0);
227+ swipeToCloseCheckbox.checked = data.swipeToClose;
228+ closeableCheckbox.checked = data.closeable;
230+ var dragDistance = spreadDelegateLoader.item.height * 0.8;
231+ var touchX = spreadDelegateLoader.item.width / 2;
232+ var fromY = spreadDelegateLoader.item.height * 0.9;
233+ var toY = fromY - dragDistance;
234+ touchFlick(spreadDelegateLoader.item,
235+ touchX /* fromX */, fromY, touchX /* toX */, toY,
236+ true /* beginTouch */, false /* endTouch */, dragArea.minSpeedToClose * 1.1 /* speed */);
238+ if (data.swipeToClose) {
239+ verify(displacedAppWindowWithShadow.y < 0);
240+ var threshold = findChild(spreadDelegateLoader.item, "dragArea").threshold
241+ if (data.closeable) {
242+ // Verify that the delegate started moving exactly "threshold" after the finger movement
243+ // and did not jump up to the finger, but lags the threshold behind
244+ compare(Math.abs(Math.abs(displacedAppWindowWithShadow.y) - dragDistance), threshold);
246+ verify(Math.abs(Math.abs(displacedAppWindowWithShadow.y) - dragDistance) > threshold);
249+ touchRelease(spreadDelegateLoader.item, touchX, toY - units.gu(1));
251+ waitForCloseAnimationToFinish();
253+ if (data.closeable) {
254+ verify(spyClosedSignal.count === 1);
256+ verify(spyClosedSignal.count === 0);
257+ tryCompare(displacedAppWindowWithShadow, "y", 0);
261+ verify(displacedAppWindowWithShadow.y === 0);
263+ touchRelease(spreadDelegateLoader.item, touchX, toY);
267+ function test_loadingLandscapeOnlyAppWhenShellInPortrait() {
268+ loadWithWeatherApp.clicked();
270+ var appWindow = findChild(spreadDelegate, "appWindow");
273+ // It must have landscape dimensions as it does not support portrait
274+ tryCompare(appWindow, "width", fakeShell.height);
275+ tryCompare(appWindow, "height", fakeShell.width - spreadDelegate.maximizedAppTopMargin);
278+ function test_keepsSceneTransformationWhenShellRotates_data() {
280+ {tag: "0", selectedIndex: 0},
281+ {tag: "90", selectedIndex: 1},
282+ {tag: "180", selectedIndex: 2},
283+ {tag: "270", selectedIndex: 3}
286+ function test_keepsSceneTransformationWhenShellRotates(data) {
287+ loadWithGalleryApp.clicked();
289+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
290+ verify(appWindowWithShadow);
292+ // Wait until it reaches the state we are interested on.
293+ // It begins with "startingUp"
294+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
296+ shellOrientationAngleSelector.selectedIndex = data.selectedIndex;
298+ // must keep same aspect ratio
299+ compare(appWindowWithShadow.width, fakeShell.shortestDimension);
300+ compare(appWindowWithShadow.height, fakeShell.longestDimension);
303+ // and scene transformation must be the identity (ie, no rotation or translation)
304+ var pointInDelegateCoords = appWindowWithShadow.mapToItem(root, 0, 0);
305+ compare(pointInDelegateCoords.x, 0);
306+ compare(pointInDelegateCoords.y, 0);
308+ pointInDelegateCoords = appWindowWithShadow.mapToItem(root,
309+ fakeShell.shortestDimension, fakeShell.longestDimension);
310+ compare(pointInDelegateCoords.x, fakeShell.shortestDimension);
311+ compare(pointInDelegateCoords.y, fakeShell.longestDimension);
314+ function waitForCloseAnimationToFinish() {
315+ var closeAnimation = findInvisibleChild(spreadDelegateLoader.item, "closeAnimation");
316+ wait(closeAnimation.duration * 1.5);
317+ tryCompare(closeAnimation, "running", false);
320+ function test_showHighLight() {
321+ loadWithGalleryApp.clicked();
322+ var highlightRect = findChild(spreadDelegateLoader.item, "selectionHighlight")
323+ tryCompare(highlightRect, "visible", false)
324+ spreadDelegateLoader.item.highlightShown = true;
325+ tryCompare(highlightRect, "visible", true)