2 * Copyright (C) 2013-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/>.
19import AccountsService 0.1
20import LightDMController 0.1
21import LightDM.FullLightDM 0.1 as LightDM
22import Ubuntu.Components 1.3
23import Ubuntu.Components.ListItems 1.3
24import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Application 0.1
26import Unity.Notifications 1.0
27import Unity.Test 0.1 as UT
31import "../../../qml/Components"
35 color: UbuntuColors.lightGrey
36 width: units.gu(100) + buttons.width
40 id: applicationArguments
42 function hasGeometry() {
57 phoneNumber: "+447812221111"
66 id: mockNotificationsModel
69 return mockNotification.createObject(mockNotificationsModel)
73 function addNotification() {
75 type: Notification.Confirmation,
84 mockNotificationsModel.append(n);
87 Component.onCompleted: {
88 // must set the mock mode before loading the Shell
89 LightDMController.userMode = "single-pin";
90 shellLoader.active = true;
95 anchors.left: root.left
96 anchors.right: buttons.left
98 anchors.bottom: root.bottom
104 anchors.horizontalCenter: parent.horizontalCenter
105 anchors.top: parent.top
106 anchors.bottom: parent.bottom
108 property int shellOrientation: Qt.PortraitOrientation
109 property int nativeOrientation: Qt.PortraitOrientation
110 property int primaryOrientation: Qt.PortraitOrientation
112 state: modeSelector.model[modeSelector.selectedIndex]
126 shellOrientation: Qt.LandscapeOrientation
127 nativeOrientation: Qt.LandscapeOrientation
128 primaryOrientation: Qt.LandscapeOrientation
140 property bool itemDestroyed: false
141 sourceComponent: Component {
143 usageScenario: shellLoader.state
146 property string indicatorProfile: "phone"
147 orientation: shellLoader.shellOrientation
148 orientations: Orientations {
149 native_: shellLoader.nativeOrientation
150 primary: shellLoader.primaryOrientation
154 Component.onDestruction: {
155 shellLoader.itemDestroyed = true;
164 color: UbuntuColors.darkGrey
166 anchors.top: root.top
167 anchors.bottom: root.bottom
168 anchors.right: root.right
171 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
174 anchors { left: parent.left; right: parent.right }
178 if (shellLoader.status !== Loader.Ready)
181 var greeter = testCase.findChild(shellLoader.item, "greeter");
190 anchors { left: parent.left; right: parent.right }
192 text: "Restart Tutorial"
194 if (shellLoader.status !== Loader.Ready)
197 AccountsService.demoEdges = false;
198 AccountsService.demoEdgesCompleted = [];
199 AccountsService.demoEdges = true;
205 anchors { left: parent.left; right: parent.right }
209 callManager.foregroundCall = phoneCall;
211 callManager.foregroundCall = null;
217 anchors.verticalCenter: parent.verticalCenter
222 anchors { left: parent.left; right: parent.right }
224 activeFocusOnPress: false
226 var surface = topLevelSurfaceList.inputMethodSurface;
228 surface.requestState(Mir.RestoredState);
230 surface.requestState(Mir.MinimizedState);
236 anchors.verticalCenter: parent.verticalCenter
242 anchors { left: parent.left; right: parent.right }
243 activeFocusOnPress: false
245 model: ["phone", "tablet", "desktop"]
255 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
256 property real halfWidth: shell ? shell.width / 2 : 0
257 property real halfHeight: shell ? shell.height / 2 : 0
261 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
263 topLevelSurfaceList = null;
270 var tutorialLeft = findChild(shell, "tutorialLeft");
271 var tutorialLeftTimer = findInvisibleChild(tutorialLeft, "tutorialLeftTimer");
272 tutorialLeftTimer.interval = 0;
273 tryCompare(tutorialLeft, "opacity", 1);
277 resetLoader("phone");
280 function resetLoader(state) {
281 shellLoader.itemDestroyed = false;
283 shellLoader.active = false;
284 shellLoader.state = state;
286 tryCompare(shellLoader, "status", Loader.Null);
287 tryCompare(shellLoader, "item", null);
288 // Loader.status might be Loader.Null and Loader.item might be null but the Loader
289 // item might still be alive. So if we set Loader.active back to true
290 // again right now we will get the very same Shell instance back. So no reload
291 // actually took place. Likely because Loader waits until the next event loop
292 // iteration to do its work. So to ensure the reload, we will wait until the
293 // Shell instance gets destroyed.
294 tryCompare(shellLoader, "itemDestroyed", true);
296 // kill all (fake) running apps
299 // reload our test subject to get it in a fresh state once again
300 shellLoader.active = true;
302 mockNotificationsModel.clear();
303 tryCompare(shellLoader, "status", Loader.Ready);
304 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
307 function prepareShell() {
308 tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
310 WindowStateStorage.clear();
311 //topLevelSurfaceList.inputMethodSurface.setState(Mir.MinimizedState);
312 callManager.foregroundCall = null;
313 AccountsService.demoEdges = false;
314 AccountsService.demoEdgesCompleted = [];
315 AccountsService.demoEdges = true;
317 LightDM.Greeter.hideGreeter();
319 stage = findChild(shell, "stage"); // from StageTestCase
321 startApplication("unity8-dash");
324 function loadShell(state) {
329 function ensureInputMethodSurface() {
330 var surfaceManager = findInvisibleChild(shell, "surfaceManager");
331 verify(surfaceManager);
332 surfaceManager.createInputMethodSurface();
334 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
337 function swipeAwayGreeter() {
338 var coverPage = findChild(shell, "coverPage");
339 tryCompare(coverPage, "showProgress", 1);
341 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
343 // wait until the animation has finished
344 var greeter = findChild(shell, "greeter");
345 tryCompare(greeter, "required", false);
346 waitForRendering(greeter);
349 function openTutorialTop() {
350 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
351 var tutorialTop = findChild(shell, "tutorialTop");
352 var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
354 tutorialTopTimer.interval = 1;
355 AccountsService.demoEdgesCompleted = ["left"];
357 tryCompare(tutorialLeftLoader, "active", false);
358 tryCompare(tutorialTop, "shown", true);
359 tryCompare(tutorialTop, "opacity", 1);
362 function openTutorialLeftLong() {
363 var tutorialLeftLong = findChild(shell, "tutorialLeftLong");
364 var tutorialLeftLongTimer = findChild(tutorialLeftLong, "tutorialLeftLongTimer");
366 AccountsService.demoEdgesCompleted = ["left", "top"];
367 ApplicationManager.startApplication("gallery-app");
369 tutorialLeftLongTimer.interval = 1;
370 tryCompare(tutorialLeftLong, "shown", true);
371 tryCompare(tutorialLeftLong, "opacity", 1);
374 function openTutorialRight() {
375 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
376 var tutorialRight = findChild(shell, "tutorialRight");
377 var tutorialRightTimer = findChild(tutorialRight, "tutorialRightTimer");
379 AccountsService.demoEdgesCompleted = ["left", "top", "left-long"];
380 ApplicationManager.startApplication("gallery-app");
381 ApplicationManager.startApplication("facebook-webapp");
382 ApplicationManager.startApplication("camera-app");
384 tryCompare(tutorialLeftLoader, "active", false);
385 tutorialRightTimer.interval = 1;
386 tryCompare(tutorialRight, "shown", true);
387 tryCompare(tutorialRight, "opacity", 1);
390 function test_tutorialLeftEdges() {
391 var tutorial = findChild(shell, "tutorial");
392 var tutorialLeft = findChild(tutorial, "tutorialLeft");
393 var launcher = findChild(shell, "launcher");
394 var stage = findChild(shell, "stage");
395 var panel = findChild(shell, "panel");
397 verify(tutorial.running);
398 verify(tutorial.launcherEnabled);
399 verify(!tutorial.spreadEnabled);
400 verify(!tutorial.panelEnabled);
401 verify(tutorialLeft.shown);
402 verify(launcher.available);
403 verify(!stage.spreadEnabled);
404 verify(!panel.indicators.available);
407 function test_tutorialLeftFinish() {
408 var tutorial = findChild(shell, "tutorial");
409 var tutorialLeftLoader = findChild(tutorial, "tutorialLeftLoader");
410 var launcher = findChild(shell, "launcher");
412 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
414 tryCompare(tutorialLeftLoader, "shown", false);
415 tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
416 tryCompare(launcher, "state", "visible");
419 function test_tutorialLeftShortDrag() {
420 // Here we want to test just barely pulling the launcher out and
421 // letting go (i.e. not triggering the "progress" property of
423 var tutorialLeft = findChild(shell, "tutorialLeft");
424 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
425 var launcher = findChild(shell, "launcher");
427 // Confirm fade during drag
428 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, true, false);
429 // compare opacity with a bound rather than hard 0.6 because progress doesn't
430 // always match the drag perfectly (takes a moment for drag to kick in)
431 tryCompareFunction(function() {
432 return tutorialLeft.opacity >= 0.6 && tutorialLeft.opacity < 0.8;
434 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, false, true);
437 // Make sure we don't do anything if we don't pull the launcher
439 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
440 tryCompare(launcher, "state", ""); // should remain hidden
441 tryCompare(tutorialLeftLoader, "shown", true); // and we should still be on left
444 // Now drag out but not past launcher itself
445 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
447 tryCompare(tutorialLeftLoader, "shown", false);
448 tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
449 tryCompare(launcher, "state", "visible");
452 function test_tutorialLeftLongFinish() {
453 AccountsService.demoEdgesCompleted = ["left", "top"];
455 var tutorialLeftLongTimer = findInvisibleChild(shell, "tutorialLeftLongTimer");
456 tutorialLeftLongTimer.interval = 1;
458 ApplicationManager.startApplication("gallery-app");
460 var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
461 tryCompare(tutorialLeftLongLoader, "shown", true);
463 touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
465 tryCompare(tutorialLeftLongLoader, "shown", false);
466 tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "left-long"]);
469 function test_tutorialLeftAutoSkipped() {
470 // Test that we skip the tutorial if user uses left edge themselves
472 var tutorialLeft = findChild(shell, "tutorialLeft");
473 LightDM.Greeter.showGreeter();
474 tryCompare(tutorialLeft, "visible", false);
475 compare(AccountsService.demoEdgesCompleted, []);
477 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
478 tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
481 function test_tutorialLongLeftSwipeDisabled() {
482 // Test that a long left swipe is disabled until we get to or pass
483 // the long left tutorial.
484 var tutorial = findChild(shell, "tutorial");
486 verify(!tutorial.launcherLongSwipeEnabled);
488 openTutorialLeftLong();
489 verify(tutorial.launcherLongSwipeEnabled);
492 verify(tutorial.launcherLongSwipeEnabled);
494 AccountsService.demoEdges = false;
495 tryCompare(tutorial, "running", false);
496 verify(tutorial.launcherLongSwipeEnabled);
499 function test_tutorialTopEdges() {
500 var tutorial = findChild(shell, "tutorial");
501 var tutorialTop = findChild(tutorial, "tutorialTop");
502 var launcher = findChild(shell, "launcher");
503 var stage = findChild(shell, "stage");
504 var panel = findChild(shell, "panel");
508 tryCompare(tutorial, "running", true);
509 verify(!tutorial.launcherEnabled);
510 verify(!tutorial.spreadEnabled);
511 verify(tutorial.panelEnabled);
512 verify(tutorialTop.shown);
513 verify(!launcher.available);
514 verify(!stage.spreadEnabled);
515 verify(panel.indicators.available);
518 function test_tutorialTopFinish() {
519 var tutorial = findChild(shell, "tutorial");
520 var tutorialTopLoader = findChild(tutorial, "tutorialTopLoader");
521 var panel = findChild(shell, "panel");
524 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
526 tryCompare(tutorialTopLoader, "shown", false);
527 tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top"]);
528 tryCompare(panel.indicators, "fullyOpened", true);
531 function test_tutorialTopShortDrag() {
532 var tutorial = findChild(shell, "tutorial");
533 var tutorialTop = findChild(tutorial, "tutorialTop");
534 var panel = findChild(shell, "panel");
537 touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, true, false);
538 // compare opacity with a bound rather than hard 0.6 because progress doesn't
539 // always match the drag perfectly (takes a moment for drag to kick in)
540 tryCompareFunction(function() {
541 return tutorialTop.opacity >= 0.5 && tutorialTop.opacity < 0.7;
543 touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, false, true);
545 compare(tutorialTop.shown, true);
548 function test_tutorialTopAutoSkipped() {
549 // Test that we skip the tutorial if user uses top edge themselves
551 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
552 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
553 AccountsService.demoEdgesCompleted = ["left"];
554 tryCompare(tutorialLeftLoader, "active", false);
555 verify(!tutorialTopLoader.shown);
557 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
558 tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top"]);
561 function test_tutorialRightEdges() {
562 var tutorial = findChild(shell, "tutorial");
563 var tutorialRight = findChild(tutorial, "tutorialRight");
564 var launcher = findChild(shell, "launcher");
565 var stage = findChild(shell, "stage");
566 var panel = findChild(shell, "panel");
570 tryCompare(tutorial, "running", true);
571 verify(!tutorial.launcherEnabled);
572 verify(tutorial.spreadEnabled);
573 verify(!tutorial.panelEnabled);
574 verify(tutorialRight.shown);
575 verify(!launcher.available);
576 verify(stage.spreadEnabled);
577 verify(!panel.indicators.available);
580 function test_tutorialRightFinish() {
582 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
584 tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "left-long", "right"]);
585 tryCompare(AccountsService, "demoEdges", false);
588 function test_tutorialRightShortDrag() {
589 var tutorial = findChild(shell, "tutorial");
590 var tutorialRight = findChild(tutorial, "tutorialRight");
591 var stage = findChild(shell, "stage");
594 touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
595 // compare opacity with a bound rather than hard 0.6 because progress doesn't
596 // always match the drag perfectly (takes a moment for drag to kick in)
597 tryCompareFunction(function() {
598 return tutorialRight.opacity >= 0.6 && tutorialRight.opacity < 0.8;
600 touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, false, true);
602 compare(tutorialRight.shown, true);
605 function test_tutorialRightDelay() {
606 // Test that if we exit the top tutorial, we don't immediately
607 // jump into right tutorial.
608 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
609 var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
612 ApplicationManager.startApplication("gallery-app");
613 ApplicationManager.startApplication("facebook-webapp");
614 ApplicationManager.startApplication("camera-app");
615 tryCompare(ApplicationManager, "count", 4);
617 AccountsService.demoEdgesCompleted = ["left", "top", "left-long"];
618 verify(tutorialRightTimer.running);
619 verify(!tutorialRightLoader.shown);
620 tutorialRightTimer.interval = 1;
621 tryCompare(tutorialRightLoader, "shown", true);
624 function test_tutorialRightAutoSkipped() {
625 // Test that we skip the tutorial if user uses right edge themselves
627 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
628 AccountsService.demoEdgesCompleted = ["left"];
629 tryCompare(tutorialLeftLoader, "active", false);
631 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
632 tryCompare(AccountsService, "demoEdgesCompleted", ["left", "right"]);
635 function test_activeCallInterruptsTutorial() {
636 var tutorialLeft = findChild(shell, "tutorialLeft");
637 verify(tutorialLeft.shown);
638 verify(!tutorialLeft.paused);
640 callManager.foregroundCall = phoneCall;
641 verify(!tutorialLeft.shown);
642 verify(tutorialLeft.paused);
643 tryCompare(tutorialLeft, "visible", false);
645 callManager.foregroundCall = null;
646 tryCompare(tutorialLeft, "shown", true);
647 verify(!tutorialLeft.paused);
650 function test_greeterInterruptsTutorial() {
651 var tutorialLeft = findChild(shell, "tutorialLeft");
652 verify(tutorialLeft.shown);
653 verify(!tutorialLeft.paused);
655 LightDM.Greeter.showGreeter();
656 verify(!tutorialLeft.shown);
657 verify(tutorialLeft.paused);
658 tryCompare(tutorialLeft, "visible", false);
660 LightDM.Greeter.hideGreeter();
661 tryCompare(tutorialLeft, "shown", true);
662 verify(!tutorialLeft.paused);
665 function test_interruptionChecksReadyStateWhenDone() {
666 // If we're done with an interruption (like active call), make sure
667 // that we don't blindly resume the tutorial -- our trigger
668 // conditions still need to be met. For example, there need to be
669 // enough apps open for the right edge tutorial.
673 var tutorialRight = findChild(shell, "tutorialRight");
674 verify(tutorialRight.isReady);
675 verify(tutorialRight.shown);
676 verify(!tutorialRight.paused);
678 callManager.foregroundCall = phoneCall;
680 callManager.foregroundCall = null;
682 verify(!tutorialRight.isReady);
683 verify(!tutorialRight.shown);
684 verify(!tutorialRight.paused);
685 compare(AccountsService.demoEdgesCompleted, ["left", "top", "left-long"]);
688 function test_desktopOnlyShowsTutorialRight() {
689 loadShell("desktop");
691 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
692 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
693 var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
694 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
695 verify(!tutorialLeftLoader.active);
696 verify(!tutorialTopLoader.active);
697 verify(!tutorialLeftLongLoader.active);
698 verify(tutorialRightLoader.active);
699 compare(AccountsService.demoEdgesCompleted, []);
701 var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
702 tutorialRightTimer.interval = 1;
704 ApplicationManager.startApplication("dialer-app");
705 ApplicationManager.startApplication("camera-app");
706 ApplicationManager.startApplication("facebook-webapp");
707 tryCompare(tutorialRightLoader.item, "isReady", true);
709 tryCompare(tutorialRightLoader, "shown", true);
712 function test_desktopTutorialRightFinish() {
713 loadShell("desktop");
715 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
716 var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
717 tutorialRightTimer.interval = 1;
718 ApplicationManager.startApplication("dialer-app");
719 ApplicationManager.startApplication("camera-app");
720 ApplicationManager.startApplication("facebook-webapp");
721 tryCompare(tutorialRightLoader, "shown", true);
723 var cursor = findChild(shell, "cursor");
724 mouseMove(shell, shell.width, shell.height / 2);
725 cursor.pushedRightBoundary(units.gu(8), 0);
726 tryCompare(tutorialRightLoader, "shown", false);
728 tryCompare(AccountsService, "demoEdges", false);
731 function test_oskDoesNotHideTutorial() {
732 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
733 verify(tutorialLeftLoader.shown);
735 ensureInputMethodSurface();
736 var surface = topLevelSurfaceList.inputMethodSurface;
737 surface.requestState(Mir.RestoredState);
739 var inputMethod = findInvisibleChild(shell, "inputMethod");
740 tryCompare(inputMethod, "visible", true);
742 verify(tutorialLeftLoader.shown);
745 function test_oskDelaysTutorial() {
746 var tutorial = findChild(shell, "tutorial");
747 verify(!tutorial.delayed);
749 ensureInputMethodSurface();
750 topLevelSurfaceList.inputMethodSurface.requestState(Mir.RestoredState);
752 tryCompare(tutorial, "delayed", true);
755 function test_notificationDelaysTutorial() {
756 var tutorial = findChild(shell, "tutorial");
757 verify(!tutorial.delayed);
759 var notifications = findChild(shell, "notificationList");
760 notifications.model = mockNotificationsModel;
761 addNotification(); // placeholder
764 tryCompare(tutorial, "delayed", true);
767 function test_dialogDelaysTutorial() {
768 var tutorial = findChild(shell, "tutorial");
769 verify(!tutorial.delayed);
771 var dialogs = findChild(shell, "dialogs");
772 dialogs.showPowerDialog();
774 verify(tutorial.delayed);
777 function test_delayedTutorial() {
778 var tutorial = findChild(shell, "tutorial");
779 var tutorialTop = findChild(tutorial, "tutorialTop");
780 var tutorialTopTimer = findChild(tutorialTop, "tutorialTopTimer");
782 // Get top tutorial ready
783 AccountsService.demoEdgesCompleted = ["left"];
784 verify(tutorialTop.isReady);
785 verify(tutorialTopTimer.running);
787 // Confirm that we become unready when delayed, but timer still goes
788 tutorial.delayed = true;
789 verify(!tutorialTop.isReady);
790 verify(tutorialTopTimer.running);
792 // Confirm that we don't open the tutorial when delayed
793 tutorialTopTimer.interval = 1;
795 verify(!tutorialTop.shown);
797 // Confirm we go back to normal when undelayed
798 tutorial.delayed = false;
799 tryCompare(tutorialTop, "shown", true);
802 function test_accountsServiceSettings() {
803 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
804 verify(tutorialLeftLoader.shown);
806 AccountsService.demoEdges = false;
807 verify(findChild(shell, "tutorialLeftLoader") == null);
809 AccountsService.demoEdges = true;
810 tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
811 var tutorialLeftTimer = findChild(tutorialLeftLoader, "tutorialLeftTimer");
812 verify(tutorialLeftTimer.running);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list