2
* Copyright 2014-2016 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(40)
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
58
property bool itemDestroyed: false
59
sourceComponent: SpreadDelegate {
61
swipeToCloseEnabled: swipeToCloseCheckbox.checked
62
closeable: closeableCheckbox.checked
63
application: fakeApplication
64
surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
65
shellOrientationAngle: shellOrientationAngleSelector.value
67
switch (shellOrientationAngleSelector.value) {
69
return Qt.PortraitOrientation;
71
return Qt.InvertedLandscapeOrientation;
73
return Qt.InvertedPortraitOrientation;
75
return Qt.LandscapeOrientation;
79
orientations: Orientations {
80
// the default values will do
83
maximizedAppTopMargin: units.gu(3)
84
Component.onDestruction: {
85
spreadDelegateLoader.itemDestroyed = true;
87
Component.onCompleted: {
88
spreadDelegateLoader.itemDestroyed = false;
97
x: fakeShell.shortestDimension
101
bottom: parent.bottom
105
anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
108
id: loadWithWeatherApp
109
text: "Load with ubuntu-weather-app"
110
onClicked: { testCase.restartWithApp("ubuntu-weather-app"); }
113
id: loadWithGalleryApp
114
text: "Load with gallery-app"
115
onClicked: { testCase.restartWithApp("gallery-app"); }
118
anchors { left: parent.left; right: parent.right }
119
CheckBox { id: swipeToCloseCheckbox; checked: false; }
120
Label { text: "swipeToCloseEnabled" }
123
anchors { left: parent.left; right: parent.right }
124
CheckBox { id: closeableCheckbox; checked: false }
125
Label { text: "closeable" }
127
ListItem.ItemSelector {
128
id: shellOrientationAngleSelector
129
anchors { left: parent.left; right: parent.right }
130
text: "shellOrientationAngle"
131
model: ["0", "90", "180", "270"]
132
property int value: selectedIndex * 90
136
text: "matchShellOrientation()"
137
onClicked: { spreadDelegateLoader.item.matchShellOrientation(); }
140
id: animateToShellButton
141
text: "animateToShellOrientation()"
142
onClicked: { spreadDelegateLoader.item.animateToShellOrientation(); }
149
name: "SpreadDelegate"
154
target: spreadDelegateLoader.item
158
property var dragArea
159
property Item spreadDelegate: spreadDelegateLoader.item
165
unloadSpreadDelegate();
166
spyClosedSignal.clear();
167
shellOrientationAngleSelector.selectedIndex = 0;
168
root.fakeApplication = null;
172
function restartWithApp(appId) {
173
if (spreadDelegateLoader.active) {
174
unloadSpreadDelegate();
176
if (root.fakeApplication) {
177
ApplicationManager.stopApplication(root.fakeApplication.appId);
180
root.fakeApplication = ApplicationManager.startApplication(appId);
181
spreadDelegateLoader.active = true;
182
tryCompare(spreadDelegateLoader, "status", Loader.Ready);
184
dragArea = findInvisibleChild(spreadDelegate, "dragArea");
185
dragArea.__dateTime = fakeDateTime;
188
function unloadSpreadDelegate() {
189
spreadDelegateLoader.active = false;
190
tryCompare(spreadDelegateLoader, "status", Loader.Null);
191
tryCompare(spreadDelegateLoader, "item", null);
192
// Loader.status might be Loader.Null and Loader.item might be null but the Loader
193
// item might still be alive. So if we set Loader.active back to true
194
// again right now we will get the very same Shell instance back. So no reload
195
// actually took place. Likely because Loader waits until the next event loop
196
// iteration to do its work. So to ensure the reload, we will wait until the
197
// Shell instance gets destroyed.
198
tryCompare(spreadDelegateLoader, "itemDestroyed", true);
202
function waitUntilAppWindowIsFullyLoaded() {
203
var appWindowStateGroup = findInvisibleChild(spreadDelegate, "applicationWindowStateGroup");
204
tryCompareFunction(function() { return appWindowStateGroup.state === "surface" }, true);
205
waitUntilTransitionsEnd(appWindowStateGroup);
207
var priv = findInvisibleChild(spreadDelegate, "spreadDelegatePriv");
209
tryCompare(priv, "startingUp", false);
212
function rotateShellTo(angle) {
213
shellOrientationAngleSelector.selectedIndex = angle / 90;
216
function waitUntilRotationAnimationStops() {
217
var orientationChangeAnimation = findInvisibleChild(spreadDelegate, "orientationChangeAnimation");
218
verify(orientationChangeAnimation);
219
tryCompare(orientationChangeAnimation, "running", false);
222
function checkAppWindowTransformationMatchesRotation(appWindow, angle) {
226
var bottomRightX = 0;
227
var bottomRightY = 0;
233
bottomRightX = fakeShell.shortestDimension;
234
bottomRightY = fakeShell.longestDimension;
237
topLeftX = fakeShell.shortestDimension;
240
bottomRightY = fakeShell.longestDimension;
243
// TODO implement 180 and 270 once we need them
247
var appWindowTopLeftInRootCoords = appWindow.mapToItem(root, 0, 0);
249
if (appWindowTopLeftInRootCoords.x !== topLeftX) {
250
qtest_fail("appWindow topLeft.x ("+appWindowTopLeftInRootCoords.x+")"
251
+" doesn't match expectations ("+topLeftX+").", 1);
253
if (appWindowTopLeftInRootCoords.y !== topLeftY) {
254
qtest_fail("appWindow topLeft.y ("+appWindowTopLeftInRootCoords.y+")"
255
+" doesn't match expectations ("+topLeftY+").", 1);
258
var appWindowBottomRightInRootCoords = appWindow.mapToItem(root, appWindow.width,
260
compare(appWindowBottomRightInRootCoords.x, bottomRightX);
261
compare(appWindowBottomRightInRootCoords.y, bottomRightY);
263
if (appWindowBottomRightInRootCoords.x !== bottomRightX) {
264
qtest_fail("appWindow bottomRight.x ("+appWindowBottomRightInRootCoords.x+")"
265
+" doesn't match expectations ("+bottomRightX+").", 1);
267
if (appWindowBottomRightInRootCoords.y !== bottomRightY) {
268
qtest_fail("appWindow bottomRight.y ("+appWindowBottomRightInRootCoords.y+")"
269
+" doesn't match expectations ("+bottomRightY+").", 1);
273
function test_swipeToClose_data() {
275
{tag: "swipeToClose=true closeable=true -> appWindow moves away",
276
swipeToClose: true, closeable: true },
278
{tag: "swipeToClose=true closeable=false -> appWindow bounces back",
279
swipeToClose: true, closeable: false },
281
{tag: "swipeToClose=false -> appWindow stays put",
282
swipeToClose: false, closeable: true },
286
function test_swipeToClose(data) {
287
loadWithGalleryApp.clicked();
288
var displacedAppWindowWithShadow = findChild(spreadDelegateLoader.item, "displacedAppWindowWithShadow");
290
verify(displacedAppWindowWithShadow.y === 0);
292
swipeToCloseCheckbox.checked = data.swipeToClose;
293
closeableCheckbox.checked = data.closeable;
295
var dragDistance = spreadDelegateLoader.item.height * 0.8;
296
var touchX = spreadDelegateLoader.item.width / 2;
297
var fromY = spreadDelegateLoader.item.height * 0.9;
298
var toY = fromY - dragDistance;
299
touchFlick(spreadDelegateLoader.item,
300
touchX /* fromX */, fromY, touchX /* toX */, toY,
301
true /* beginTouch */, false /* endTouch */, dragArea.minSpeedToClose * 1.1 /* speed */);
303
if (data.swipeToClose) {
304
verify(displacedAppWindowWithShadow.y < 0);
305
var threshold = findChild(spreadDelegateLoader.item, "dragArea").threshold
306
if (data.closeable) {
307
// Verify that the delegate started moving exactly "threshold" after the finger movement
308
// and did not jump up to the finger, but lags the threshold behind
309
compare(Math.abs(Math.abs(displacedAppWindowWithShadow.y) - dragDistance), threshold);
311
verify(Math.abs(Math.abs(displacedAppWindowWithShadow.y) - dragDistance) > threshold);
314
touchRelease(spreadDelegateLoader.item, touchX, toY - units.gu(1));
316
waitForCloseAnimationToFinish();
318
if (data.closeable) {
319
verify(spyClosedSignal.count === 1);
321
verify(spyClosedSignal.count === 0);
322
tryCompare(displacedAppWindowWithShadow, "y", 0);
326
verify(displacedAppWindowWithShadow.y === 0);
328
touchRelease(spreadDelegateLoader.item, touchX, toY);
332
function test_loadingLandscapeOnlyAppWhenShellInPortrait() {
333
loadWithWeatherApp.clicked();
335
var appWindow = findChild(spreadDelegate, "appWindow");
338
// It must have landscape dimensions as it does not support portrait
339
tryCompare(appWindow, "width", fakeShell.height);
340
tryCompare(appWindow, "height", fakeShell.width - spreadDelegate.maximizedAppTopMargin);
343
function test_keepsSceneTransformationWhenShellRotates_data() {
345
{tag: "0", selectedIndex: 0},
346
{tag: "90", selectedIndex: 1},
347
{tag: "180", selectedIndex: 2},
348
{tag: "270", selectedIndex: 3}
351
function test_keepsSceneTransformationWhenShellRotates(data) {
352
loadWithGalleryApp.clicked();
354
waitUntilAppWindowIsFullyLoaded();
356
var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
357
verify(appWindowWithShadow);
359
compare(appWindowWithShadow.state, "keepSceneRotation");
361
shellOrientationAngleSelector.selectedIndex = data.selectedIndex;
363
// must keep same aspect ratio
364
compare(appWindowWithShadow.width, fakeShell.shortestDimension);
365
compare(appWindowWithShadow.height, fakeShell.longestDimension);
368
// and scene transformation must be the identity (ie, no rotation or translation)
369
checkAppWindowTransformationMatchesRotation(appWindowWithShadow, 0);
372
function waitForCloseAnimationToFinish() {
373
var closeAnimation = findInvisibleChild(spreadDelegateLoader.item, "closeAnimation");
374
wait(closeAnimation.duration * 1.5);
375
tryCompare(closeAnimation, "running", false);
378
function test_showHighLight() {
379
loadWithGalleryApp.clicked();
380
var highlightRect = findChild(spreadDelegateLoader.item, "selectionHighlight")
381
tryCompare(highlightRect, "visible", false)
382
spreadDelegateLoader.item.highlightShown = true;
383
tryCompare(highlightRect, "visible", true)
387
Checks that the ApplicationWindow position, size and rotation is correct after
388
a sequence of shell rotations folowed by rotations (immediate or not) to match them
390
function test_animateToShellThenMatchShell() {
391
loadWithGalleryApp.clicked();
392
waitUntilAppWindowIsFullyLoaded();
394
var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
395
verify(appWindowWithShadow);
397
// appWindow transformation is 0 relative to display
398
checkAppWindowTransformationMatchesRotation(appWindowWithShadow, 0);
402
// appWindow transformation remains 0 relative to display
403
checkAppWindowTransformationMatchesRotation(appWindowWithShadow, 0);
405
animateToShellButton.clicked();
406
waitUntilRotationAnimationStops();
408
// appWindow transformation is now 90 relative to display, just like shell
409
checkAppWindowTransformationMatchesRotation(appWindowWithShadow, 90);
413
// appWindow transformation is remains 90 relative to display.
414
checkAppWindowTransformationMatchesRotation(appWindowWithShadow, 90);
416
matchShellButton.clicked();
418
// appWindow transformation is back to 0 relative to display, just like shell
419
checkAppWindowTransformationMatchesRotation(appWindowWithShadow, 0);