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
39 property var shell: null
42 id: applicationArguments
44 function hasGeometry() {
59 phoneNumber: "+447812221111"
68 id: mockNotificationsModel
71 return mockNotification.createObject(mockNotificationsModel)
75 function addNotification() {
77 type: Notification.Confirmation,
86 mockNotificationsModel.append(n);
89 Component.onCompleted: {
90 // must set the mock mode before loading the Shell
91 LightDMController.userMode = "single-pin";
98 usageScenario: shellRect.state
101 property string indicatorProfile: "phone"
102 orientation: shellRect.shellOrientation
103 orientations: Orientations {
104 native_: shellRect.nativeOrientation
105 primary: shellRect.primaryOrientation
108 lightIndicators: true
114 anchors.left: root.left
115 anchors.right: buttons.left
116 anchors.top: root.top
117 anchors.bottom: root.bottom
122 anchors.horizontalCenter: parent.horizontalCenter
123 anchors.top: parent.top
124 anchors.bottom: parent.bottom
126 property int shellOrientation: Qt.PortraitOrientation
127 property int nativeOrientation: Qt.PortraitOrientation
128 property int primaryOrientation: Qt.PortraitOrientation
130 state: modeSelector.model[modeSelector.selectedIndex]
144 shellOrientation: Qt.LandscapeOrientation
145 nativeOrientation: Qt.LandscapeOrientation
146 primaryOrientation: Qt.LandscapeOrientation
162 color: UbuntuColors.darkGrey
164 anchors.top: root.top
165 anchors.bottom: root.bottom
166 anchors.right: root.right
169 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
172 anchors { left: parent.left; right: parent.right }
176 if (shell === null) {
177 shell = shellComponent.createObject(shellRect);
185 anchors { left: parent.left; right: parent.right }
189 if (root.shell === null)
192 var greeter = testCase.findChild(shell, "greeter");
201 anchors { left: parent.left; right: parent.right }
203 text: "Restart Tutorial"
205 if (root.shell === null)
208 AccountsService.demoEdges = false;
209 AccountsService.demoEdgesCompleted = [];
210 AccountsService.demoEdges = true;
216 anchors { left: parent.left; right: parent.right }
220 callManager.foregroundCall = phoneCall;
222 callManager.foregroundCall = null;
228 anchors.verticalCenter: parent.verticalCenter
233 anchors { left: parent.left; right: parent.right }
235 activeFocusOnPress: false
237 var topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
238 var surface = topLevelSurfaceList.inputMethodSurface;
240 surface.requestState(Mir.RestoredState);
242 surface.requestState(Mir.MinimizedState);
248 anchors.verticalCenter: parent.verticalCenter
254 anchors { left: parent.left; right: parent.right }
255 activeFocusOnPress: false
257 model: ["phone", "tablet", "desktop"]
267 property real halfWidth: shell ? shell.width / 2 : 0
268 property real halfHeight: shell ? shell.height / 2 : 0
271 shell = createTemporaryObject(shellComponent, shellRect);
274 var tutorialTop = findChild(shell, "tutorialTop");
275 var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
276 tutorialTopTimer.interval = 0;
277 tryCompare(tutorialTop, "opacity", 1);
281 mockNotificationsModel.clear();
283 shellRect.state = "phone";
286 function prepareShell() {
287 tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
288 removeTimeConstraintsFromSwipeAreas(shell);
290 WindowStateStorage.clear();
291 callManager.foregroundCall = null;
292 AccountsService.demoEdges = false;
293 AccountsService.demoEdgesCompleted = [];
294 AccountsService.demoEdges = true;
296 LightDM.Greeter.hideGreeter();
298 stage = findChild(shell, "stage"); // from StageTestCase
301 function loadShell(state) {
302 shellRect.state = state;
306 function ensureInputMethodSurface(topLevelSurfaceList) {
307 var surfaceManager = findInvisibleChild(shell, "surfaceManager");
308 verify(surfaceManager);
309 surfaceManager.createInputMethodSurface();
311 tryCompareFunction(function() { return topLevelSurfaceList.inputMethodSurface !== null }, true);
314 function openTutorialLeft() {
315 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
316 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
317 var tutorialLeft = findInvisibleChild(tutorialLeftLoader, "tutorialLeft");
318 var tutorialLeftTimer = findInvisibleChild(tutorialLeft, "tutorialLeftTimer");
320 tutorialLeftTimer.interval = 1;
321 AccountsService.demoEdgesCompleted = ["top"];
323 tryCompare(tutorialTopLoader, "active", false);
324 tryCompare(tutorialLeft, "shown", true);
325 tryCompare(tutorialLeft, "opacity", 1);
328 function openTutorialLeftLong() {
329 var tutorialLeftLong = findChild(shell, "tutorialLeftLong");
330 var tutorialLeftLongTimer = findChild(tutorialLeftLong, "tutorialLeftLongTimer");
332 AccountsService.demoEdgesCompleted = ["top", "left"];
334 tutorialLeftLongTimer.interval = 1;
335 tryCompare(tutorialLeftLong, "shown", true);
336 tryCompare(tutorialLeftLong, "opacity", 1);
339 function openTutorialRight() {
340 var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
341 var tutorialRight = findChild(shell, "tutorialRight");
342 var tutorialRightTimer = findChild(tutorialRight, "tutorialRightTimer");
344 AccountsService.demoEdgesCompleted = ["top", "left", "left-drawer"];
345 ApplicationManager.startApplication("facebook-webapp");
347 tryCompare(tutorialLeftLongLoader, "active", false);
348 tutorialRightTimer.interval = 1;
349 tryCompare(tutorialRight, "shown", true);
350 tryCompare(tutorialRight, "opacity", 1);
353 function longLeftSwipe() {
354 // Perform a long enough swipe to cause the Drawer to appear
355 var launcher = findChild(shell, "launcher");
356 var startX = launcher.dragAreaWidth/2;
357 var startY = launcher.height/2;
360 startX+units.gu(35), startY);
363 function test_tutorialLeftEdges() {
364 var tutorial = findChild(shell, "tutorial");
365 var tutorialLeft = findChild(tutorial, "tutorialLeft");
366 var launcher = findChild(shell, "launcher");
367 var stage = findChild(shell, "stage");
368 var panel = findChild(shell, "panel");
372 verify(tutorial.running);
373 verify(tutorial.launcherEnabled);
374 verify(!tutorial.spreadEnabled);
375 verify(!tutorial.panelEnabled);
376 verify(tutorialLeft.shown);
377 verify(launcher.available);
378 verify(!stage.spreadEnabled);
379 verify(!panel.indicators.available);
382 function test_tutorialLeftFinish() {
383 var tutorial = findChild(shell, "tutorial");
384 var tutorialLeftLoader = findChild(tutorial, "tutorialLeftLoader");
385 var launcher = findChild(shell, "launcher");
389 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
391 tryCompare(tutorialLeftLoader, "shown", false);
392 tryCompare(AccountsService, "demoEdgesCompleted", ["top", "left"]);
393 tryCompare(launcher, "state", "visible");
396 function test_tutorialLeftShortDrag() {
397 // Here we want to test just barely pulling the launcher out and
398 // letting go (i.e. not triggering the "progress" property of
400 var tutorialLeft = findChild(shell, "tutorialLeft");
401 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
402 var launcher = findChild(shell, "launcher");
406 // Confirm fade during drag
407 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, true, false);
408 // compare opacity with a bound rather than hard 0.6 because progress doesn't
409 // always match the drag perfectly (takes a moment for drag to kick in)
410 tryCompareFunction(function() {
411 return tutorialLeft.opacity >= 0.6 && tutorialLeft.opacity < 0.8;
413 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, false, true);
416 // Make sure we don't do anything if we don't pull the launcher
418 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
419 tryCompare(launcher, "state", ""); // should remain hidden
420 tryCompare(tutorialLeftLoader, "shown", true); // and we should still be on left
423 // Now drag out but not past launcher itself
424 touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
426 tryCompare(tutorialLeftLoader, "shown", false);
427 tryCompare(AccountsService, "demoEdgesCompleted", ["top", "left"]);
428 tryCompare(launcher, "state", "visible");
431 function test_tutorialLeftLongFinish() {
432 var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
433 openTutorialLeftLong();
435 tryCompare(tutorialLeftLongLoader, "shown", true);
437 touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
439 tryCompare(tutorialLeftLongLoader, "shown", false);
440 tryCompare(AccountsService, "demoEdgesCompleted", ["top", "left", "left-drawer"]);
443 function test_tutorialLeftAutoSkipped() {
444 // Test that we skip the tutorial if user uses left edge themselves
445 var launcher = findChild(shell, "launcher");
447 AccountsService.demoEdgesCompleted = ["top"]
448 var tutorialLeft = findChild(shell, "tutorialLeft");
450 tryCompare(tutorialLeft, "visible", false);
451 compare(AccountsService.demoEdgesCompleted, ["top"]);
453 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
454 tryCompare(AccountsService, "demoEdgesCompleted", ["top", "left"]);
457 function test_tutorialLongLeftSwipeDisabled() {
458 // Test that a long left swipe is disabled until we get to or pass
459 // the long left tutorial.
460 var tutorial = findChild(shell, "tutorial");
461 var launcher = findChild(shell, "launcher");
462 var drawer = findChild(launcher, "drawer");
464 // Waiting for top tutorial, the Launcher should be unavailable
466 tryCompare(launcher, "state", "");
468 // Swipe the left edge far enough to open the Drawer. Only the
469 // Launcher should appear.
472 tryCompare(launcher, "state", "visible");
474 // Once the Drawer tutorial is open, the Drawer should appear on
476 openTutorialLeftLong();
478 tryCompare(launcher, "state", "drawer");
481 // That should continue after the Tutorial is no more
482 AccountsService.demoEdges = false;
483 tryCompare(tutorial, "running", false);
485 tryCompare(launcher, "state", "drawer");
488 function test_tutorialTopEdges() {
489 var tutorial = findChild(shell, "tutorial");
490 var tutorialTop = findChild(tutorial, "tutorialTop");
491 var launcher = findChild(shell, "launcher");
492 var stage = findChild(shell, "stage");
493 var panel = findChild(shell, "panel");
495 tryCompare(tutorial, "running", true);
496 verify(!tutorial.launcherEnabled);
497 verify(!tutorial.spreadEnabled);
498 verify(tutorial.panelEnabled);
499 verify(tutorialTop.shown);
500 verify(!launcher.available);
501 verify(!stage.spreadEnabled);
502 verify(panel.indicators.available);
505 function test_tutorialTopFinish() {
506 var tutorial = findChild(shell, "tutorial");
507 var tutorialTopLoader = findChild(tutorial, "tutorialTopLoader");
508 var panel = findChild(shell, "panel");
510 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
512 tryCompare(tutorialTopLoader, "shown", false);
513 tryCompare(AccountsService, "demoEdgesCompleted", ["top"]);
514 tryCompare(panel.indicators, "fullyOpened", true);
517 function test_tutorialTopShortDrag() {
518 var tutorial = findChild(shell, "tutorial");
519 var tutorialTop = findChild(tutorial, "tutorialTop");
520 var panel = findChild(shell, "panel");
522 touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, true, false);
523 // compare opacity with a bound rather than hard 0.6 because progress doesn't
524 // always match the drag perfectly (takes a moment for drag to kick in)
525 tryCompareFunction(function() {
526 return tutorialTop.opacity >= 0.5 && tutorialTop.opacity < 0.7;
528 touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, false, true);
530 compare(tutorialTop.shown, true);
533 function test_tutorialRightEdges() {
534 var tutorial = findChild(shell, "tutorial");
535 var tutorialRight = findChild(tutorial, "tutorialRight");
536 var launcher = findChild(shell, "launcher");
537 var stage = findChild(shell, "stage");
538 var panel = findChild(shell, "panel");
542 tryCompare(tutorial, "running", true);
543 verify(!tutorial.launcherEnabled);
544 verify(tutorial.spreadEnabled);
545 verify(!tutorial.panelEnabled);
546 verify(tutorialRight.shown);
547 verify(!launcher.available);
548 verify(stage.spreadEnabled);
549 verify(!panel.indicators.available);
552 function test_tutorialRightFinish() {
554 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
556 tryCompare(AccountsService, "demoEdgesCompleted", ["top", "left", "left-drawer", "right"]);
557 tryCompare(AccountsService, "demoEdges", false);
560 function test_tutorialRightShortDrag() {
561 var tutorial = findChild(shell, "tutorial");
562 var tutorialRight = findChild(tutorial, "tutorialRight");
563 var stage = findChild(shell, "stage");
566 touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
567 // compare opacity with a bound rather than hard 0.6 because progress doesn't
568 // always match the drag perfectly (takes a moment for drag to kick in)
570 return tutorialRight.opacity >= 0.6 && tutorialRight.opacity < 0.8;
572 touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, false, true);
574 compare(tutorialRight.shown, true);
577 function test_tutorialRightDelay() {
578 // Test that if we exit the top tutorial, we don't immediately
579 // jump into right tutorial.
580 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
581 var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
583 ApplicationManager.startApplication("gallery-app");
584 tryCompare(ApplicationManager, "count", 1);
586 AccountsService.demoEdgesCompleted = ["top", "left", "left-drawer"];
587 verify(tutorialRightTimer.running);
588 verify(!tutorialRightLoader.shown);
589 tutorialRightTimer.interval = 1;
590 tryCompare(tutorialRightLoader, "shown", true);
593 function test_tutorialRightAutoSkipped() {
594 // Test that we skip the tutorial if user uses right edge themselves
596 var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
597 var tutorialRight = findChild(shell, "tutorialRight");
598 var tutorialRightTimer = findChild(tutorialRight, "tutorialRightTimer");
600 tutorialRightTimer.interval = 9999999;
602 ApplicationManager.startApplication("facebook-webapp");
603 tryCompare(ApplicationManager, "count", 1);
605 AccountsService.demoEdgesCompleted = ["top", "left", "left-drawer"];
606 tryCompare(tutorialLeftLongLoader, "skipped", true);
607 verify(tutorialRightTimer.running);
608 tryCompare(tutorialRight, "isReady", true);
610 // If the app isn't fully started, it may interrupt our attempt to swipe
613 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
614 tryCompare(AccountsService, "demoEdgesCompleted", ["top", "left", "left-drawer", "right"]);
617 function test_activeCallInterruptsTutorial() {
618 var tutorialLeft = findChild(shell, "tutorialLeft");
621 callManager.foregroundCall = phoneCall;
622 verify(!tutorialLeft.shown);
623 verify(tutorialLeft.paused);
624 tryCompare(tutorialLeft, "visible", false);
626 callManager.foregroundCall = null;
627 tryCompare(tutorialLeft, "shown", true);
628 verify(!tutorialLeft.paused);
631 function test_greeterInterruptsTutorial() {
632 var tutorialTop = findChild(shell, "tutorialTop");
633 verify(tutorialTop.shown);
634 verify(!tutorialTop.paused);
636 LightDM.Greeter.showGreeter();
637 verify(!tutorialTop.shown);
638 verify(tutorialTop.paused);
639 tryCompare(tutorialTop, "visible", false);
641 LightDM.Greeter.hideGreeter();
642 tryCompare(tutorialTop, "shown", true);
643 verify(!tutorialTop.paused);
646 function test_interruptionChecksReadyStateWhenDone() {
647 // If we're done with an interruption (like active call), make sure
648 // that we don't blindly resume the tutorial -- our trigger
649 // conditions still need to be met. For example, there need to be
650 // enough apps open for the right edge tutorial.
654 var tutorialRight = findChild(shell, "tutorialRight");
655 verify(tutorialRight.isReady);
656 verify(tutorialRight.shown);
657 verify(!tutorialRight.paused);
659 callManager.foregroundCall = phoneCall;
661 callManager.foregroundCall = null;
663 verify(!tutorialRight.isReady);
664 verify(!tutorialRight.shown);
665 verify(!tutorialRight.paused);
666 compare(AccountsService.demoEdgesCompleted, ["top", "left", "left-drawer"]);
669 function test_desktopOnlyShowsTutorialRight() {
670 loadShell("desktop");
672 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
673 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
674 var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
675 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
676 verify(!tutorialLeftLoader.active);
677 verify(!tutorialTopLoader.active);
678 verify(!tutorialLeftLongLoader.active);
679 verify(tutorialRightLoader.active);
680 compare(AccountsService.demoEdgesCompleted, []);
682 var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
683 tutorialRightTimer.interval = 1;
685 ApplicationManager.startApplication("facebook-webapp");
686 tryCompare(tutorialRightLoader.item, "isReady", true);
688 tryCompare(tutorialRightLoader, "shown", true);
691 function test_desktopTutorialRightFinish() {
692 loadShell("desktop");
694 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
695 var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
696 tutorialRightTimer.interval = 1;
697 ApplicationManager.startApplication("facebook-webapp");
698 tryCompare(tutorialRightLoader, "shown", true);
700 var cursor = findChild(shell, "cursor");
701 mouseMove(shell, shell.width, shell.height / 2);
702 cursor.pushedRightBoundary(units.gu(8), 0);
703 tryCompare(tutorialRightLoader, "shown", false);
705 tryCompare(AccountsService, "demoEdges", false);
708 function test_oskDoesNotHideTutorial() {
709 var topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
710 verify(topLevelSurfaceList);
711 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
712 verify(tutorialTopLoader.shown);
714 ensureInputMethodSurface(topLevelSurfaceList);
715 var surface = topLevelSurfaceList.inputMethodSurface;
716 surface.requestState(Mir.RestoredState);
718 var inputMethod = findInvisibleChild(shell, "inputMethod");
719 tryCompare(inputMethod, "visible", true);
721 verify(tutorialTopLoader.shown);
724 function test_oskDelaysTutorial() {
725 var topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
726 verify(topLevelSurfaceList);
727 var tutorial = findChild(shell, "tutorial");
728 verify(!tutorial.delayed);
730 ensureInputMethodSurface(topLevelSurfaceList);
731 topLevelSurfaceList.inputMethodSurface.requestState(Mir.RestoredState);
733 tryCompare(tutorial, "delayed", true);
736 function test_notificationDelaysTutorial() {
737 var tutorial = findChild(shell, "tutorial");
738 verify(!tutorial.delayed);
740 var notifications = findChild(shell, "notificationList");
741 notifications.model = mockNotificationsModel;
742 addNotification(); // placeholder
745 tryCompare(tutorial, "delayed", true);
748 function test_dialogDelaysTutorial() {
749 var tutorial = findChild(shell, "tutorial");
750 verify(!tutorial.delayed);
752 var dialogs = findChild(shell, "dialogs");
753 dialogs.showPowerDialog();
755 verify(tutorial.delayed);
758 function test_delayedTutorial() {
759 var launcher = findChild(shell, "launcher");
760 var tutorial = findChild(shell, "tutorial");
761 var tutorialLeft = findChild(tutorial, "tutorialLeft");
762 var tutorialLeftTimer = findChild(tutorial, "tutorialLeftTimer");
763 // Since we'll be using the Launcher and Drawer, the left tutorial
764 // will be skipped entirely and we'll show the left long one.
765 var tutorialLeftLong = findChild(tutorial, "tutorialLeftLong");
766 var tutorialLeftLongTimer = findChild(tutorialLeftLong, "tutorialLeftLongTimer");
768 // Don't need these to start before we're ready
769 tutorialLeftTimer.interval = 999999;
770 tutorialLeftLongTimer.interval = 1;
772 // Get left tutorial ready to be skipped
773 AccountsService.demoEdgesCompleted = ["top"];
774 tryCompare(tutorialLeft, "isReady", true);
775 tryCompare(tutorialLeftTimer, "running", true);
777 // Confirm that we become unready when delayed, but timer still goes
778 tutorial.delayed = true;
779 tryCompare(tutorialLeft, "isReady", false);
780 tryCompare(tutorialLeftTimer, "running", true);
782 // Confirm that we don't open the tutorial when delayed
783 tutorialLeftTimer.interval = 1;
785 verify(!tutorialLeft.shown);
787 // Confirm the Launcher and Drawer can be opened when delayed
788 tryCompare(tutorial, "launcherEnabled", true);
789 tryCompare(tutorial, "launcherLongSwipeEnabled", true);
791 tryCompare(launcher, "state", "drawer");
794 // Confirm we go back to normal when undelayed
795 tutorial.delayed = false;
796 tryCompare(tutorialLeftLong, "shown", true);
799 function test_accountsServiceSettings() {
800 var tutorial = findChild(shell, "tutorial");
801 var tutorialTopLoader = findChild(tutorial, "tutorialTopLoader");
802 verify(tutorialTopLoader.shown);
804 AccountsService.demoEdges = false;
805 verify(findChild(tutorial, "tutorialTopLoader", 1) == null);
807 AccountsService.demoEdges = true;
808 tutorialTopLoader = findChild(shell, "tutorialTopLoader");
809 var tutorialTopTimer = findChild(tutorialTopLoader, "tutorialTopTimer");
810 verify(tutorialTopTimer.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