// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/vmservice.dart'; import 'package:mockito/mockito.dart'; import '../src/common.dart'; import '../src/context.dart'; void main() { TestRunner createTestRunner() { // TODO(jacobr): make these tests run with `trackWidgetCreation: true` as // well as the default flags. return TestRunner( [ FlutterDevice( MockDevice(), buildInfo: const BuildInfo( BuildMode.debug, null, trackWidgetCreation: false, treeShakeIcons: false, ), ), ], ); } group('keyboard input handling', () { testUsingContext('single help character', () async { final TestRunner testRunner = createTestRunner(); final TerminalHandler terminalHandler = TerminalHandler(testRunner); expect(testRunner.hasHelpBeenPrinted, false); await terminalHandler.processTerminalInput('h'); expect(testRunner.hasHelpBeenPrinted, true); }); testUsingContext('help character surrounded with newlines', () async { final TestRunner testRunner = createTestRunner(); final TerminalHandler terminalHandler = TerminalHandler(testRunner); expect(testRunner.hasHelpBeenPrinted, false); await terminalHandler.processTerminalInput('\nh\n'); expect(testRunner.hasHelpBeenPrinted, true); }); }); group('keycode verification, brought to you by the letter', () { MockResidentRunner mockResidentRunner; TerminalHandler terminalHandler; setUp(() { mockResidentRunner = MockResidentRunner(); terminalHandler = TerminalHandler(mockResidentRunner); when(mockResidentRunner.supportsServiceProtocol).thenReturn(true); }); testUsingContext('a, can handle trailing newlines', () async { await terminalHandler.processTerminalInput('a\n'); expect(terminalHandler.lastReceivedCommand, 'a'); }); testUsingContext('n, can handle trailing only newlines', () async { await terminalHandler.processTerminalInput('\n\n'); expect(terminalHandler.lastReceivedCommand, ''); }); testUsingContext('a - debugToggleProfileWidgetBuilds with service protocol', () async { await terminalHandler.processTerminalInput('a'); verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1); }); testUsingContext('a - debugToggleProfileWidgetBuilds without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('a'); verifyNever(mockResidentRunner.debugToggleProfileWidgetBuilds()); }); testUsingContext('a - debugToggleProfileWidgetBuilds', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(true); await terminalHandler.processTerminalInput('a'); verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1); }); testUsingContext('d,D - detach', () async { await terminalHandler.processTerminalInput('d'); await terminalHandler.processTerminalInput('D'); verify(mockResidentRunner.detach()).called(2); }); testUsingContext('h,H,? - printHelp', () async { await terminalHandler.processTerminalInput('h'); await terminalHandler.processTerminalInput('H'); await terminalHandler.processTerminalInput('?'); verify(mockResidentRunner.printHelp(details: true)).called(3); }); testUsingContext('k - toggles CanvasKit rendering and prints results', () async { when(mockResidentRunner.supportsCanvasKit).thenReturn(true); when(mockResidentRunner.toggleCanvaskit()) .thenAnswer((Invocation invocation) async { return true; }); await terminalHandler.processTerminalInput('k'); verify(mockResidentRunner.toggleCanvaskit()).called(1); }); testUsingContext('i, I - debugToggleWidgetInspector with service protocol', () async { await terminalHandler.processTerminalInput('i'); await terminalHandler.processTerminalInput('I'); verify(mockResidentRunner.debugToggleWidgetInspector()).called(2); }); testUsingContext('i, I - debugToggleWidgetInspector without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('i'); await terminalHandler.processTerminalInput('I'); verifyNever(mockResidentRunner.debugToggleWidgetInspector()); }); testUsingContext('l - list flutter views', () async { final MockFlutterDevice mockFlutterDevice = MockFlutterDevice(); when(mockResidentRunner.isRunningDebug).thenReturn(true); when(mockResidentRunner.flutterDevices).thenReturn([mockFlutterDevice]); when(mockResidentRunner.listFlutterViews()).thenAnswer((Invocation invocation) async { return []; }); await terminalHandler.processTerminalInput('l'); expect(testLogger.statusText, contains('Connected views:\n')); }); testUsingContext('L - debugDumpLayerTree with service protocol', () async { await terminalHandler.processTerminalInput('L'); verify(mockResidentRunner.debugDumpLayerTree()).called(1); }); testUsingContext('L - debugDumpLayerTree without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('L'); verifyNever(mockResidentRunner.debugDumpLayerTree()); }); testUsingContext('o,O - debugTogglePlatform with service protocol and debug mode', () async { when(mockResidentRunner.isRunningDebug).thenReturn(true); await terminalHandler.processTerminalInput('o'); await terminalHandler.processTerminalInput('O'); verify(mockResidentRunner.debugTogglePlatform()).called(2); }); testUsingContext('o,O - debugTogglePlatform without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); when(mockResidentRunner.isRunningDebug).thenReturn(true); await terminalHandler.processTerminalInput('o'); await terminalHandler.processTerminalInput('O'); verifyNever(mockResidentRunner.debugTogglePlatform()); }); testUsingContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async { when(mockResidentRunner.isRunningDebug).thenReturn(true); await terminalHandler.processTerminalInput('p'); verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1); }); testUsingContext('p - debugTogglePlatform without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); when(mockResidentRunner.isRunningDebug).thenReturn(true); await terminalHandler.processTerminalInput('p'); verifyNever(mockResidentRunner.debugToggleDebugPaintSizeEnabled()); }); testUsingContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async { when(mockResidentRunner.isRunningDebug).thenReturn(true); await terminalHandler.processTerminalInput('p'); verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1); }); testUsingContext('p - debugTogglePlatform without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); when(mockResidentRunner.isRunningDebug).thenReturn(true); await terminalHandler.processTerminalInput('p'); verifyNever(mockResidentRunner.debugToggleDebugPaintSizeEnabled()); }); testUsingContext('P - debugTogglePerformanceOverlayOverride with service protocol', () async { await terminalHandler.processTerminalInput('P'); verify(mockResidentRunner.debugTogglePerformanceOverlayOverride()).called(1); }); testUsingContext('P - debugTogglePerformanceOverlayOverride without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('P'); verifyNever(mockResidentRunner.debugTogglePerformanceOverlayOverride()); }); testUsingContext('q,Q - exit', () async { await terminalHandler.processTerminalInput('q'); await terminalHandler.processTerminalInput('Q'); verify(mockResidentRunner.exit()).called(2); }); testUsingContext('s - screenshot', () async { final MockDevice mockDevice = MockDevice(); final MockFlutterDevice mockFlutterDevice = MockFlutterDevice(); when(mockResidentRunner.isRunningDebug).thenReturn(true); when(mockResidentRunner.flutterDevices).thenReturn([mockFlutterDevice]); when(mockFlutterDevice.device).thenReturn(mockDevice); when(mockDevice.supportsScreenshot).thenReturn(true); await terminalHandler.processTerminalInput('s'); verify(mockResidentRunner.screenshot(mockFlutterDevice)).called(1); }); testUsingContext('r - hotReload supported and succeeds', () async { when(mockResidentRunner.canHotReload).thenReturn(true); when(mockResidentRunner.restart(fullRestart: false)) .thenAnswer((Invocation invocation) async { return OperationResult(0, ''); }); await terminalHandler.processTerminalInput('r'); verify(mockResidentRunner.restart(fullRestart: false)).called(1); }); testUsingContext('r - hotReload supported and fails', () async { when(mockResidentRunner.canHotReload).thenReturn(true); when(mockResidentRunner.restart(fullRestart: false)) .thenAnswer((Invocation invocation) async { return OperationResult(1, ''); }); await terminalHandler.processTerminalInput('r'); verify(mockResidentRunner.restart(fullRestart: false)).called(1); expect(testLogger.statusText, contains('Try again after fixing the above error(s).')); }); testUsingContext('r - hotReload supported and fails fatally', () async { when(mockResidentRunner.canHotReload).thenReturn(true); when(mockResidentRunner.hotMode).thenReturn(true); when(mockResidentRunner.restart(fullRestart: false)) .thenAnswer((Invocation invocation) async { return OperationResult(1, 'fail', fatal: true); }); expect(terminalHandler.processTerminalInput('r'), throwsToolExit()); }); testUsingContext('r - hotReload unsupported', () async { when(mockResidentRunner.canHotReload).thenReturn(false); await terminalHandler.processTerminalInput('r'); verifyNever(mockResidentRunner.restart(fullRestart: false)); }); testUsingContext('R - hotRestart supported and succeeds', () async { when(mockResidentRunner.canHotRestart).thenReturn(true); when(mockResidentRunner.hotMode).thenReturn(true); when(mockResidentRunner.restart(fullRestart: true)) .thenAnswer((Invocation invocation) async { return OperationResult(0, ''); }); await terminalHandler.processTerminalInput('R'); verify(mockResidentRunner.restart(fullRestart: true)).called(1); }); testUsingContext('R - hotRestart supported and fails', () async { when(mockResidentRunner.canHotRestart).thenReturn(true); when(mockResidentRunner.hotMode).thenReturn(true); when(mockResidentRunner.restart(fullRestart: true)) .thenAnswer((Invocation invocation) async { return OperationResult(1, 'fail'); }); await terminalHandler.processTerminalInput('R'); verify(mockResidentRunner.restart(fullRestart: true)).called(1); expect(testLogger.statusText, contains('Try again after fixing the above error(s).')); }); testUsingContext('R - hotRestart supported and fails fatally', () async { when(mockResidentRunner.canHotRestart).thenReturn(true); when(mockResidentRunner.hotMode).thenReturn(true); when(mockResidentRunner.restart(fullRestart: true)) .thenAnswer((Invocation invocation) async { return OperationResult(1, 'fail', fatal: true); }); expect(() => terminalHandler.processTerminalInput('R'), throwsToolExit()); }); testUsingContext('R - hot restart unsupported', () async { when(mockResidentRunner.canHotRestart).thenReturn(false); await terminalHandler.processTerminalInput('R'); verifyNever(mockResidentRunner.restart(fullRestart: true)); }); testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async { await terminalHandler.processTerminalInput('S'); verify(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder()).called(1); }); testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('S'); verifyNever(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder()); }); testUsingContext('t,T - debugDumpRenderTree with service protocol', () async { await terminalHandler.processTerminalInput('t'); await terminalHandler.processTerminalInput('T'); verify(mockResidentRunner.debugDumpRenderTree()).called(2); }); testUsingContext('t,T - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('t'); await terminalHandler.processTerminalInput('T'); verifyNever(mockResidentRunner.debugDumpRenderTree()); }); testUsingContext('U - debugDumpRenderTree with service protocol', () async { await terminalHandler.processTerminalInput('U'); verify(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1); }); testUsingContext('U - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('U'); verifyNever(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()); }); testUsingContext('v - launchDevTools', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(true); await terminalHandler.processTerminalInput('v'); verify(mockResidentRunner.launchDevTools()).called(1); }); testUsingContext('w,W - debugDumpApp with service protocol', () async { await terminalHandler.processTerminalInput('w'); await terminalHandler.processTerminalInput('W'); verify(mockResidentRunner.debugDumpApp()).called(2); }); testUsingContext('w,W - debugDumpApp without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('w'); await terminalHandler.processTerminalInput('W'); verifyNever(mockResidentRunner.debugDumpApp()); }); testUsingContext('z,Z - debugToggleDebugCheckElevationsEnabled with service protocol', () async { await terminalHandler.processTerminalInput('z'); await terminalHandler.processTerminalInput('Z'); verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2); }); testUsingContext('z,Z - debugToggleDebugCheckElevationsEnabled without service protocol', () async { when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); await terminalHandler.processTerminalInput('z'); await terminalHandler.processTerminalInput('Z'); // This should probably be disable when the service protocol is not enabled. verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2); }); }); } class MockDevice extends Mock implements Device { MockDevice() { when(isSupported()).thenReturn(true); } } class MockResidentRunner extends Mock implements ResidentRunner {} class MockFlutterDevice extends Mock implements FlutterDevice {} class TestRunner extends ResidentRunner { TestRunner(List devices) : super(devices); bool hasHelpBeenPrinted = false; String receivedCommand; @override Future cleanupAfterSignal() async { } @override Future cleanupAtFinish() async { } @override void printHelp({ bool details }) { hasHelpBeenPrinted = true; } @override Future run({ Completer connectionInfoCompleter, Completer appStartedCompleter, String route, }) async => null; @override Future attach({ Completer connectionInfoCompleter, Completer appStartedCompleter, }) async => null; }