@@ -0,0 +1,1078 @@
2+ * Copyright (C) 2014 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/>.
18+import QtQuick.Layouts 1.1
20+import Ubuntu.Components 1.1
21+import Ubuntu.Components.ListItems 1.0 as ListItem
22+import Unity.Application 0.1
24+import LightDM 0.1 as LightDM
31+ width: units.gu(160) + controls.width
32+ height: units.gu(100)
35+ id: applicationArguments
37+ function hasGeometry() {
48+ property string deviceName: "mako"
52+ id: mockOrientationLock
53+ property int savedOrientation
57+ id: mockUsageModeSettings
58+ property string usageMode: usageModeSelector.model[usageModeSelector.selectedIndex]
61+ property int physicalOrientation0
62+ property int physicalOrientation90
63+ property int physicalOrientation180
64+ property int physicalOrientation270
65+ property real primaryOrientationAngle
67+ state: applicationArguments.deviceName
72+ target: orientedShellLoader
78+ physicalOrientation0: Qt.PortraitOrientation
79+ physicalOrientation90: Qt.InvertedLandscapeOrientation
80+ physicalOrientation180: Qt.InvertedPortraitOrientation
81+ physicalOrientation270: Qt.LandscapeOrientation
82+ primaryOrientationAngle: 0
88+ target: orientedShellLoader
90+ height: units.gu(100)
94+ physicalOrientation90: Qt.PortraitOrientation
95+ physicalOrientation180: Qt.InvertedLandscapeOrientation
96+ physicalOrientation270: Qt.InvertedPortraitOrientation
97+ physicalOrientation0: Qt.LandscapeOrientation
98+ primaryOrientationAngle: 0
104+ target: orientedShellLoader
106+ height: units.gu(100)
110+ physicalOrientation270: Qt.PortraitOrientation
111+ physicalOrientation0: Qt.InvertedLandscapeOrientation
112+ physicalOrientation90: Qt.InvertedPortraitOrientation
113+ physicalOrientation180: Qt.LandscapeOrientation
114+ primaryOrientationAngle: 90
120+ id: orientedShellLoader
122+ x: ((root.width - controls.width) - width) / 2
123+ y: (root.height - height) / 2
127+ property bool itemDestroyed: false
128+ sourceComponent: Component {
130+ anchors.fill: parent
131+ usageModeSettings: mockUsageModeSettings
132+ physicalOrientation: root.physicalOrientation0
133+ orientationLocked: orientationLockedCheckBox.checked
134+ orientationLock: mockOrientationLock
135+ Component.onDestruction: {
136+ orientedShellLoader.itemDestroyed = true;
142+ function orientationsToStr(orientations) {
143+ if (orientations === Qt.PrimaryOrientation) {
147+ if (orientations & Qt.PortraitOrientation) {
150+ if (orientations & Qt.InvertedPortraitOrientation) {
151+ str += " InvertedPortrait";
153+ if (orientations & Qt.LandscapeOrientation) {
154+ str += " Landscape";
156+ if (orientations & Qt.InvertedLandscapeOrientation) {
157+ str += " InvertedLandscape";
169+ bottom: parent.bottom
174+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
175+ spacing: units.gu(1)
177+ text: "Show Greeter"
178+ activeFocusOnPress: false
180+ if (orientedShellLoader.status !== Loader.Ready)
183+ LightDM.Greeter.showGreeter();
187+ text: "Physical Orientation:"
191+ text: root.orientationsToStr(root.physicalOrientation0) + " (0)"
192+ activeFocusOnPress: false
194+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
199+ text: root.orientationsToStr(root.physicalOrientation90) + " (90)"
200+ activeFocusOnPress: false
202+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
207+ text: root.orientationsToStr(root.physicalOrientation180) + " (180)"
208+ activeFocusOnPress: false
210+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
215+ text: root.orientationsToStr(root.physicalOrientation270) + " (270)"
216+ activeFocusOnPress: false
218+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
222+ Layout.fillWidth: true
224+ id: orientationLockedCheckBox
226+ activeFocusOnPress: false
229+ text: "Orientation Locked"
230+ anchors.verticalCenter: parent.verticalCenter
234+ text: "Power dialog"
235+ activeFocusOnPress: false
236+ onClicked: { testCase.showPowerDialog(); }
238+ ListItem.ItemSelector {
239+ id: deviceNameSelector
240+ anchors { left: parent.left; right: parent.right }
241+ activeFocusOnPress: false
243+ model: ["mako", "manta", "flo"]
244+ onSelectedIndexChanged: {
245+ testCase.tearDown();
246+ applicationArguments.deviceName = model[selectedIndex];
247+ orientedShellLoader.active = true;
250+ ListItem.ItemSelector {
251+ id: usageModeSelector
252+ anchors { left: parent.left; right: parent.right }
253+ activeFocusOnPress: false
255+ model: ["Staged", "Windowed", "Automatic"]
258+ text: "Switch fullscreen"
259+ activeFocusOnPress: false
261+ var app = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId);
262+ app.fullscreen = !app.fullscreen;
270+ name: "OrientedShell"
273+ property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
276+ SignalSpy { id: signalSpy }
277+ SignalSpy { id: signalSpy2 }
280+ id: spreadRepeaterConnections
281+ ignoreUnknownSignals : true
282+ property var itemAddedCallback: null
284+ if (itemAddedCallback) {
285+ itemAddedCallback(item);
291+ if (orientedShellLoader.active) {
292+ // happens for the very first test function as shell
293+ // is loaded by default
298+ function tearDown() {
299+ orientedShellLoader.itemDestroyed = false;
300+ orientedShellLoader.active = false;
302+ tryCompare(orientedShellLoader, "status", Loader.Null);
303+ tryCompare(orientedShellLoader, "item", null);
304+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
305+ // actually took place. Likely because Loader waits until the next event loop
306+ // iteration to do its work. So to ensure the reload, we will wait until the
307+ // Shell instance gets destroyed.
308+ tryCompare(orientedShellLoader, "itemDestroyed", true);
310+ // kill all (fake) running apps
313+ spreadRepeaterConnections.target = null;
314+ spreadRepeaterConnections.itemAddedCallback = null;
315+ signalSpy.target = null;
316+ signalSpy.signalName = "";
318+ LightDM.Greeter.authenticate(""); // reset greeter
321+ function cleanup() {
322+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
328+ function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut_data() {
330+ {tag: "mako", deviceName: "mako"},
331+ {tag: "manta", deviceName: "manta"},
332+ {tag: "flo", deviceName: "flo"}
335+ function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut(data) {
336+ loadShell(data.deviceName);
338+ // unity8-dash supports only primary orientation and should be already running
339+ var dashAppWindow = findChild(shell, "appWindow_unity8-dash");
340+ verify(dashAppWindow);
341+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
342+ var dashApp = dashAppWindow.application
344+ compare(dashApp.rotatesWindowContents, false);
345+ compare(dashApp.supportedOrientations, Qt.PrimaryOrientation);
346+ compare(dashApp.stage, ApplicationInfoInterface.MainStage);
348+ tryCompareFunction(function(){return dashApp.session.surface != null;}, true);
349+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
351+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
354+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
355+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
359+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
360+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
364+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
365+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
368+ function test_greeterRemainsInPrimaryOrientation_data() {
370+ {tag: "mako", deviceName: "mako"},
371+ {tag: "manta", deviceName: "manta"},
372+ {tag: "flo", deviceName: "flo"}
375+ function test_greeterRemainsInPrimaryOrientation(data) {
376+ loadShell(data.deviceName);
378+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
381+ // ensure the mock gmail-webapp is as we expect
382+ compare(gmailApp.rotatesWindowContents, false);
383+ compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
384+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
385+ compare(gmailApp.stage, ApplicationInfoInterface.MainStage);
387+ // wait until it's able to rotate
388+ tryCompare(shell, "orientationChangesEnabled", true);
390+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
392+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle + 90);
396+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle);
398+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
400+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
402+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
405+ function test_appRotatesWindowContents_data() {
407+ {tag: "mako", deviceName: "mako", orientationAngleAfterRotation: 90},
408+ {tag: "manta", deviceName: "manta", orientationAngleAfterRotation: 90},
409+ {tag: "flo", deviceName: "flo", orientationAngleAfterRotation: 180}
412+ function test_appRotatesWindowContents(data) {
413+ loadShell(data.deviceName);
414+ var cameraApp = ApplicationManager.startApplication("camera-app");
417+ // ensure the mock camera-app is as we expect
418+ compare(cameraApp.fullscreen, true);
419+ compare(cameraApp.rotatesWindowContents, true);
420+ compare(cameraApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
421+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
423+ waitUntilAppSurfaceShowsUp("camera-app")
425+ var cameraSurface = cameraApp.session.surface;
426+ verify(cameraSurface);
428+ var focusChangedSpy = signalSpy;
429+ focusChangedSpy.clear();
430+ focusChangedSpy.target = cameraSurface;
431+ focusChangedSpy.signalName = "activeFocusChanged";
432+ verify(focusChangedSpy.valid);
434+ verify(cameraSurface.activeFocus);
436+ tryCompare(shell, "orientationChangesEnabled", true);
438+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
439+ verify(rotationStates);
440+ var immediateTransition = null
441+ for (var i = 0; i < rotationStates.transitions.length && !immediateTransition; ++i) {
442+ var transition = rotationStates.transitions[i];
443+ if (transition.objectName == "immediateTransition") {
444+ immediateTransition = transition;
447+ verify(immediateTransition);
448+ var transitionSpy = signalSpy2;
449+ transitionSpy.clear();
450+ transitionSpy.target = immediateTransition;
451+ transitionSpy.signalName = "runningChanged";
452+ verify(transitionSpy.valid);
456+ tryCompare(cameraSurface, "orientationAngle", data.orientationAngleAfterRotation);
458+ // the rotation should have been immediate
459+ // false -> true -> false
460+ compare(transitionSpy.count, 2);
462+ // It should retain native dimensions regardless of its rotation/orientation
463+ compare(cameraSurface.width, orientedShell.width);
464+ compare(cameraSurface.height, orientedShell.height);
466+ // Surface focus shouldn't have been touched because of the rotation
467+ compare(focusChangedSpy.count, 0);
472+ Shell orientation angle matches the screen one.
475+ User switches to an app that has an orientation angle different from the
476+ shell one but that also happens to support the current shell orientation
480+ The app should get rotated to match shell's orientation angle
482+ function test_switchingToAppWithDifferentRotation_data() {
484+ {tag: "mako", deviceName: "mako", shellAngleAfterRotation: 90},
485+ {tag: "manta", deviceName: "manta", shellAngleAfterRotation: 90},
486+ {tag: "flo", deviceName: "flo", shellAngleAfterRotation: 180}
489+ function test_switchingToAppWithDifferentRotation(data) {
490+ loadShell(data.deviceName);
491+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
494+ // ensure the mock gmail-webapp is as we expect
495+ compare(gmailApp.rotatesWindowContents, false);
496+ compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
497+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
498+ compare(gmailApp.stage, ApplicationInfoInterface.MainStage);
500+ waitUntilAppSurfaceShowsUp("gmail-webapp")
502+ var musicApp = ApplicationManager.startApplication("music-app");
505+ // ensure the mock music-app is as we expect
506+ compare(musicApp.rotatesWindowContents, false);
507+ compare(musicApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
508+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
509+ compare(musicApp.stage, ApplicationInfoInterface.MainStage);
511+ waitUntilAppSurfaceShowsUp("music-app")
512+ tryCompare(shell, "orientationChangesEnabled", true);
515+ tryCompare(shell, "transformRotationAngle", data.shellAngleAfterRotation);
517+ performEdgeSwipeToSwitchToPreviousApp();
519+ tryCompare(shell, "mainAppWindowOrientationAngle", data.shellAngleAfterRotation);
520+ compare(shell.transformRotationAngle, data.shellAngleAfterRotation);
525+ - Device supports portrait, landscape and inverted-landscape
528+ 1 - Launch app that supports all orientations
529+ 2 - Rotate device to inverted-landscape
530+ 3 - See that shell gets rotated to inverted-landscape accordingly
531+ 4 - Rotate device to inverted-portrait
534+ Shell stays at inverted-landscape
537+ Shell rotates to landscape
540+ Rationale being that shell should be rotated to the closest supported orientation.
541+ In that case, landscape and inverted-landscape are both 90 degrees away from the physical
542+ orientation (Screen.orientation), so they are both equally good alternatives
544+ function test_rotateToUnsupportedDeviceOrientation(data) {
546+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
549+ // ensure the mock twitter-webapp is as we expect
550+ compare(twitterApp.rotatesWindowContents, false);
551+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
552+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
554+ waitUntilAppSurfaceShowsUp("twitter-webapp")
556+ rotateTo(data.rotationAngle);
557+ tryCompare(shell, "transformRotationAngle", data.rotationAngle);
560+ tryCompare(shell, "transformRotationAngle", data.rotationAngle);
562+ function test_rotateToUnsupportedDeviceOrientation_data() {
564+ {tag: "90", rotationAngle: 90},
565+ {tag: "270", rotationAngle: 270}
569+ function test_launchLandscapeOnlyAppFromPortrait() {
571+ var weatherApp = ApplicationManager.startApplication("ubuntu-weather-app");
574+ // ensure the mock app is as we expect
575+ compare(weatherApp.supportedOrientations, Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation);
577+ waitUntilAppSurfaceShowsUp("ubuntu-weather-app");
579+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
580+ waitUntilTransitionsEnd(rotationStates);
582+ tryCompareFunction(function (){return shell.transformRotationAngle === 90
583+ || shell.transformRotationAngle === 270;}, true);
587+ - launch an app that supports all orientations, such as twitter-webapp
588+ - wait a bit until that app is considered to have finished initializing and is thus
589+ ready to get resized/rotated
590+ - switch back to dash
591+ - rotate device to 90 degrees
592+ - Physical orientation is 90 but Shell orientation is kept at 0 because unity8-dash
593+ doesn't support such orientation
594+ - do a long right-edge drag to show the apps spread
595+ - tap on twitter-webapp
597+ Shell will rotate to match the physical orientation.
599+ This is a kind of tricky case as there are a few things happening at the same time:
600+ 1 - Stage switching from apps spread (phase 2) to showing the focused app (phase 0)
601+ 2 - orientation and aspect ratio (ie size) changes
603+ This may trigger some corner case bugs. such as one were
604+ the greeter is not kept completely outside the shell, causing the black rect in Shell.qml
605+ have an opacity > 0.
607+ function test_greeterStaysAwayAfterRotation() {
609+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
612+ // ensure the mock twitter-webapp is as we expect
613+ compare(twitterApp.rotatesWindowContents, false);
614+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
615+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
617+ waitUntilAppSurfaceShowsUp("twitter-webapp");
618+ waitUntilAppWindowCanRotate("twitter-webapp");
620+ // go back to unity8-dash
621+ performEdgeSwipeToSwitchToPreviousApp();
624+ wait(1); // spin the event loop to let all bindings do their thing
625+ // should not rotat as unity8-dash doesn't support it
626+ tryCompare(shell, "transformRotationAngle", 0);
628+ performEdgeSwipeToShowAppSpread();
630+ // wait until things have settled
631+ var greeter = findChild(shell, "greeter");
632+ tryCompare(greeter, "animating", false);
634+ var twitterDelegate = findChild(shell, "appDelegate1");
635+ compare(twitterDelegate.application.appId, "twitter-webapp");
636+ twitterDelegate.clicked();
638+ // now it should finally follow the physical orientation
639+ tryCompare(shell, "transformRotationAngle", 90);
641+ // greeter should remaing completely hidden
642+ tryCompare(greeter, "shown", false);
645+ function test_appInSideStageDoesntRotateOnStartUp_data() {
647+ {tag: "manta", deviceName: "manta"},
648+ {tag: "flo", deviceName: "flo"}
651+ function test_appInSideStageDoesntRotateOnStartUp(data) {
652+ loadShell(data.deviceName);
654+ var twitterDelegate = null;
656+ verify(spreadRepeaterConnections.target);
657+ spreadRepeaterConnections.itemAddedCallback = function(item) {
658+ verify(item.application.appId, "twitter-webapp");
659+ twitterDelegate = item;
660+ signalSpy.target = findInvisibleChild(item, "orientationChangeAnimation");
661+ verify(signalSpy.valid);
665+ signalSpy.target = null;
666+ signalSpy.signalName = "runningChanged";
668+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
671+ // ensure the mock twitter-webapp is as we expect
672+ compare(twitterApp.rotatesWindowContents, false);
673+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
674+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
675+ compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
677+ // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
678+ // set up the signalSpy target accordingly.
679+ tryCompareFunction(function(){ return signalSpy.target != null && signalSpy.valid; }, true);
681+ tryCompare(twitterDelegate, "orientationChangesEnabled", true);
683+ var appWindowWithShadow = findChild(twitterDelegate, "appWindowWithShadow");
684+ verify(appWindowWithShadow);
685+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
687+ // no reason for any rotation animation to have taken place
688+ compare(signalSpy.count, 0);
691+ function test_portraitOnlyAppInSideStage_data() {
693+ {tag: "manta", deviceName: "manta"},
694+ {tag: "flo", deviceName: "flo"}
697+ function test_portraitOnlyAppInSideStage(data) {
698+ loadShell(data.deviceName);
700+ var dialerDelegate = null;
701+ verify(spreadRepeaterConnections.target);
702+ spreadRepeaterConnections.itemAddedCallback = function(item) {
703+ dialerDelegate = item;
704+ verify(item.application.appId, "dialer-app");
707+ var dialerApp = ApplicationManager.startApplication("dialer-app");
710+ // ensure the mock dialer-app is as we expect
711+ compare(dialerApp.rotatesWindowContents, false);
712+ compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
713+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
715+ tryCompareFunction(function(){ return dialerDelegate != null; }, true);
716+ tryCompare(dialerDelegate, "orientationChangesEnabled", true);
718+ var appWindowWithShadow = findChild(dialerDelegate, "appWindowWithShadow");
719+ verify(appWindowWithShadow);
720+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
722+ // app must have portrait aspect ratio
723+ verify(appWindowWithShadow.width < appWindowWithShadow.height);
725+ compare(appWindowWithShadow.rotation, 0);
727+ verifyAppWindowWithinSpreadDelegateBoundaries(dialerDelegate);
729+ // shell should remain in its primery orientation as the app in the main stage
730+ // is the one that dictates its orientation. In this case it's unity8-dash
731+ // which supports only primary orientation
732+ compare(shell.orientation, orientedShell.primaryOrientation);
735+ function test_sideStageAppsRemainPortraitInSpread_data() {
737+ {tag: "manta", deviceName: "manta"},
738+ {tag: "flo", deviceName: "flo"}
741+ function test_sideStageAppsRemainPortraitInSpread(data) {
742+ loadShell(data.deviceName);
747+ var dialerDelegate = null;
748+ verify(spreadRepeaterConnections.target);
749+ spreadRepeaterConnections.itemAddedCallback = function(item) {
750+ dialerDelegate = item;
751+ verify(item.application.appId, "dialer-app");
754+ var dialerApp = ApplicationManager.startApplication("dialer-app");
757+ // ensure the mock dialer-app is as we expect
758+ compare(dialerApp.rotatesWindowContents, false);
759+ compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
760+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
762+ tryCompareFunction(function(){ return dialerDelegate != null; }, true);
763+ waitUntilAppDelegateIsFullyInit(dialerDelegate);
768+ var twitterDelegate = null;
769+ spreadRepeaterConnections.itemAddedCallback = function(item) {
770+ twitterDelegate = item;
771+ verify(item.application.appId, "twitter-webapp");
774+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
777+ // ensure the mock twitter-webapp is as we expect
778+ compare(twitterApp.rotatesWindowContents, false);
779+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
780+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
781+ compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
783+ tryCompareFunction(function(){ return twitterDelegate != null; }, true);
784+ waitUntilAppDelegateIsFullyInit(twitterDelegate);
787+ // Edge swipe to show spread and check orientations
789+ performEdgeSwipeToShowAppSpread();
792+ var appWindowWithShadow = findChild(dialerDelegate, "appWindowWithShadow");
793+ verify(appWindowWithShadow);
794+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
795+ compare(appWindowWithShadow.width, dialerDelegate.width);
796+ compare(appWindowWithShadow.height, dialerDelegate.height);
797+ compare(appWindowWithShadow.rotation, 0);
800+ var appWindowWithShadow = findChild(twitterDelegate, "appWindowWithShadow");
801+ verify(appWindowWithShadow);
802+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
803+ compare(appWindowWithShadow.width, twitterDelegate.width);
804+ compare(appWindowWithShadow.height, twitterDelegate.height);
805+ compare(appWindowWithShadow.rotation, 0);
808+ verifyAppWindowWithinSpreadDelegateBoundaries(dialerDelegate);
809+ verifyAppWindowWithinSpreadDelegateBoundaries(twitterDelegate);
812+ function test_launchedAppHasActiveFocus_data() {
814+ {tag: "mako", deviceName: "mako"},
815+ {tag: "manta", deviceName: "manta"},
816+ {tag: "flo", deviceName: "flo"}
819+ function test_launchedAppHasActiveFocus(data) {
820+ loadShell(data.deviceName);
822+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
824+ waitUntilAppSurfaceShowsUp("gmail-webapp")
826+ verify(gmailApp.session.surface);
828+ tryCompare(gmailApp.session.surface, "activeFocus", true);
831+ function test_launchLandscapeOnlyAppOverPortraitOnlyDashThenSwitchToDash() {
834+ // starts as portrait, as unity8-dash is portrait only
835+ tryCompare(shell, "transformRotationAngle", 0);
837+ var weatherApp = ApplicationManager.startApplication("ubuntu-weather-app");
840+ // ensure the mock app is as we expect
841+ compare(weatherApp.supportedOrientations, Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation);
843+ waitUntilAppSurfaceShowsUp("ubuntu-weather-app");
845+ // should have rotated to landscape
846+ tryCompareFunction(function () { return shell.transformRotationAngle == 270
847+ || shell.transformRotationAngle == 90; }, true);
849+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
850+ waitUntilTransitionsEnd(rotationStates);
852+ performLeftEdgeSwipeToSwitchToDash();
854+ // Should be back to portrait
855+ tryCompare(shell, "transformRotationAngle", 0);
858+ // angle - rotation angle in degrees clockwise, relative to the primary orientation.
859+ function rotateTo(angle) {
862+ rotate0Button.clicked();
865+ rotate90Button.clicked();
868+ rotate180Button.clicked();
871+ rotate270Button.clicked();
877+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
878+ waitUntilTransitionsEnd(rotationStates);
881+ function waitUntilAppDelegateIsFullyInit(spreadDelegate) {
882+ tryCompare(spreadDelegate, "orientationChangesEnabled", true);
884+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
885+ verify(appWindowWithShadow);
886+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
888+ var appWindowStates = findInvisibleChild(appWindowWithShadow, "applicationWindowStateGroup");
889+ verify(appWindowStates);
890+ tryCompare(appWindowStates, "state", "surface");
893+ function waitUntilAppSurfaceShowsUp(appId) {
894+ var appWindow = findChild(shell, "appWindow_" + appId);
896+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
897+ verify(appWindowStates);
898+ tryCompare(appWindowStates, "state", "surface");
901+ function waitUntilAppWindowCanRotate(appId) {
902+ var appWindow = findChild(shell, "appWindow_" + appId);
904+ tryCompare(appWindow, "orientationChangesEnabled", true);
907+ function waitUntilShellIsInOrientation(orientation) {
908+ tryCompare(shell, "orientation", orientation);
909+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
910+ waitUntilTransitionsEnd(rotationStates);
913+ function performLeftEdgeSwipeToSwitchToDash() {
914+ var spreadView = findChild(shell, "spreadView");
915+ var swipeLength = spreadView.width * 0.7;
917+ var touchStartX = 1;
918+ var touchStartY = shell.height / 2;
920+ touchStartX, touchStartY,
921+ touchStartX + swipeLength, touchStartY);
923+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
926+ function performEdgeSwipeToSwitchToPreviousApp() {
927+ // swipe just enough to ensure an app switch action.
928+ // If we swipe too much we will trigger the spread mode
929+ // and we don't want that.
930+ var spreadView = findChild(shell, "spreadView");
931+ var swipeLength = (spreadView.positionMarker1 * spreadView.width) * 0.5;
933+ verify(ApplicationManager.count >= 2);
934+ var previousApp = ApplicationManager.get(1);
936+ var touchStartX = shell.width - 1;
937+ var touchStartY = shell.height / 2;
939+ touchStartX, touchStartY,
940+ touchStartX - swipeLength, touchStartY);
942+ // ensure the app switch animation has ended
943+ tryCompare(spreadView, "shiftedContentX", 0);
945+ compare(ApplicationManager.get(0).appId, previousApp.appId);
948+ function performEdgeSwipeToShowAppSpread() {
949+ var touchStartY = shell.height / 2;
951+ shell.width - 1, touchStartY,
954+ var spreadView = findChild(shell, "spreadView");
956+ tryCompare(spreadView, "phase", 2);
957+ tryCompare(spreadView, "flicking", false);
958+ tryCompare(spreadView, "moving", false);
961+ function showPowerDialog() {
962+ var dialogs = findChild(orientedShell, "dialogs");
963+ var dialogsPrivate = findInvisibleChild(dialogs, "dialogsPrivate");
964+ dialogsPrivate.showPowerDialog();
967+ function verifyAppWindowWithinSpreadDelegateBoundaries(spreadDelegate) {
968+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
969+ verify(appWindowWithShadow);
971+ var windowInDelegateCoords = appWindowWithShadow.mapToItem(spreadDelegate, 0, 0,
972+ appWindowWithShadow.width, appWindowWithShadow.height);
974+ compare(windowInDelegateCoords.x, 0);
975+ compare(windowInDelegateCoords.y, 0);
976+ compare(windowInDelegateCoords.width, spreadDelegate.width);
977+ compare(windowInDelegateCoords.height, spreadDelegate.height);
980+ function swipeAwayGreeter() {
981+ var greeter = findChild(shell, "greeter");
982+ tryCompare(greeter, "fullyShown", true);
984+ var touchX = shell.width - (shell.edgeSize / 2);
985+ var touchY = shell.height / 2;
986+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
988+ // wait until the animation has finished
989+ tryCompare(greeter, "shown", false);
990+ waitForRendering(greeter);
993+ function showGreeter() {
994+ LightDM.Greeter.showGreeter();
995+ // wait until the animation has finished
996+ var greeter = findChild(shell, "greeter");
997+ tryCompare(greeter, "fullyShown", true);
1000+ function killApps() {
1001+ while (ApplicationManager.count > 1) {
1002+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
1003+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
1005+ compare(ApplicationManager.count, 1)
1008+ function loadShell(deviceName) {
1009+ applicationArguments.deviceName = deviceName;
1011+ // reload our test subject to get it in a fresh state once again
1012+ orientedShellLoader.active = true;
1014+ tryCompare(orientedShellLoader, "status", Loader.Ready);
1015+ removeTimeConstraintsFromDirectionalDragAreas(orientedShellLoader.item);
1017+ shell = findChild(orientedShell, "shell");
1019+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
1021+ waitUntilShellIsInOrientation(root.physicalOrientation0);
1023+ swipeAwayGreeter();
1025+ var spreadRepeater = findChild(shell, "spreadRepeater");
1026+ if (spreadRepeater) {
1027+ spreadRepeaterConnections.target = spreadRepeater;
1031+ // expectedAngle is in orientedShell's coordinate system
1032+ function checkAppSurfaceOrientation(app, expectedAngle) {
1033+ var surface = app.session.surface;
1035+ console.warn("no surface");
1039+ if (!itemIsChildOfOrientedShell(surface)) {
1040+ console.warn("surface not a child of OrientedShell");
1044+ var topMargin = 0.;
1045+ if (!app.fullscreen) {
1046+ var appsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
1047+ verify(appsDisplayLoader);
1048+ verify(appsDisplayLoader.item);
1049+ verify(appsDisplayLoader.item.maximizedAppTopMargin !== undefined);
1050+ topMargin = appsDisplayLoader.item.maximizedAppTopMargin;
1053+ var point = surface.mapToItem(orientedShell, 0, 0);
1055+ switch (expectedAngle) {
1057+ return point.x === 0. && point.y === topMargin;
1059+ return point.x === orientedShell.width - topMargin && point.y === 0;
1061+ return point.x === orientedShell.width && point.y === orientedShell.height - topMargin;
1063+ return point.x === topMargin && point.y === orientedShell.height;
1067+ function itemIsChildOfOrientedShell(item) {
1068+ var parent = item.parent;
1070+ while (parent && !found) {
1071+ found = parent === orientedShell;
1072+ parent = parent.parent;