1// Copyright 2017 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6import 'package:flutter_tools/src/base/common.dart'; 7import 'package:flutter_tools/src/base/logger.dart'; 8import 'package:flutter_tools/src/build_info.dart'; 9import 'package:flutter_tools/src/device.dart'; 10import 'package:flutter_tools/src/globals.dart'; 11import 'package:flutter_tools/src/resident_runner.dart'; 12import 'package:flutter_tools/src/vmservice.dart'; 13import 'package:mockito/mockito.dart'; 14 15import '../src/common.dart'; 16import '../src/context.dart'; 17 18void main() { 19 TestRunner createTestRunner() { 20 // TODO(jacobr): make these tests run with `trackWidgetCreation: true` as 21 // well as the default flags. 22 return TestRunner( 23 <FlutterDevice>[FlutterDevice(MockDevice(), trackWidgetCreation: false, buildMode: BuildMode.debug)], 24 ); 25 } 26 27 group('keyboard input handling', () { 28 testUsingContext('single help character', () async { 29 final TestRunner testRunner = createTestRunner(); 30 final TerminalHandler terminalHandler = TerminalHandler(testRunner); 31 expect(testRunner.hasHelpBeenPrinted, false); 32 await terminalHandler.processTerminalInput('h'); 33 expect(testRunner.hasHelpBeenPrinted, true); 34 }); 35 36 testUsingContext('help character surrounded with newlines', () async { 37 final TestRunner testRunner = createTestRunner(); 38 final TerminalHandler terminalHandler = TerminalHandler(testRunner); 39 expect(testRunner.hasHelpBeenPrinted, false); 40 await terminalHandler.processTerminalInput('\nh\n'); 41 expect(testRunner.hasHelpBeenPrinted, true); 42 }); 43 }); 44 45 group('keycode verification, brought to you by the letter', () { 46 MockResidentRunner mockResidentRunner; 47 TerminalHandler terminalHandler; 48 49 setUp(() { 50 mockResidentRunner = MockResidentRunner(); 51 terminalHandler = TerminalHandler(mockResidentRunner); 52 when(mockResidentRunner.supportsServiceProtocol).thenReturn(true); 53 }); 54 55 testUsingContext('a, can handle trailing newlines', () async { 56 await terminalHandler.processTerminalInput('a\n'); 57 58 expect(terminalHandler.lastReceivedCommand, 'a'); 59 }); 60 61 testUsingContext('n, can handle trailing only newlines', () async { 62 await terminalHandler.processTerminalInput('\n\n'); 63 64 expect(terminalHandler.lastReceivedCommand, ''); 65 }); 66 67 testUsingContext('a - debugToggleProfileWidgetBuilds with service protocol', () async { 68 await terminalHandler.processTerminalInput('a'); 69 70 verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1); 71 }); 72 73 testUsingContext('a - debugToggleProfileWidgetBuilds without service protocol', () async { 74 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 75 await terminalHandler.processTerminalInput('a'); 76 77 verifyNever(mockResidentRunner.debugToggleProfileWidgetBuilds()); 78 }); 79 80 81 testUsingContext('a - debugToggleProfileWidgetBuilds', () async { 82 when(mockResidentRunner.supportsServiceProtocol).thenReturn(true); 83 await terminalHandler.processTerminalInput('a'); 84 85 verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1); 86 }); 87 88 testUsingContext('d,D - detach', () async { 89 await terminalHandler.processTerminalInput('d'); 90 await terminalHandler.processTerminalInput('D'); 91 92 verify(mockResidentRunner.detach()).called(2); 93 }); 94 95 testUsingContext('h,H,? - printHelp', () async { 96 await terminalHandler.processTerminalInput('h'); 97 await terminalHandler.processTerminalInput('H'); 98 await terminalHandler.processTerminalInput('?'); 99 100 verify(mockResidentRunner.printHelp(details: true)).called(3); 101 }); 102 103 testUsingContext('i, I - debugToggleWidgetInspector with service protocol', () async { 104 await terminalHandler.processTerminalInput('i'); 105 await terminalHandler.processTerminalInput('I'); 106 107 verify(mockResidentRunner.debugToggleWidgetInspector()).called(2); 108 }); 109 110 testUsingContext('i, I - debugToggleWidgetInspector without service protocol', () async { 111 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 112 await terminalHandler.processTerminalInput('i'); 113 await terminalHandler.processTerminalInput('I'); 114 115 verifyNever(mockResidentRunner.debugToggleWidgetInspector()); 116 }); 117 118 testUsingContext('l - list flutter views', () async { 119 final MockFlutterDevice mockFlutterDevice = MockFlutterDevice(); 120 when(mockResidentRunner.isRunningDebug).thenReturn(true); 121 when(mockResidentRunner.flutterDevices).thenReturn(<FlutterDevice>[mockFlutterDevice]); 122 when(mockFlutterDevice.views).thenReturn(<FlutterView>[]); 123 124 await terminalHandler.processTerminalInput('l'); 125 126 final BufferLogger bufferLogger = logger; 127 128 expect(bufferLogger.statusText, contains('Connected views:\n')); 129 }); 130 131 testUsingContext('L - debugDumpLayerTree with service protocol', () async { 132 await terminalHandler.processTerminalInput('L'); 133 134 verify(mockResidentRunner.debugDumpLayerTree()).called(1); 135 }); 136 137 testUsingContext('L - debugDumpLayerTree without service protocol', () async { 138 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 139 await terminalHandler.processTerminalInput('L'); 140 141 verifyNever(mockResidentRunner.debugDumpLayerTree()); 142 }); 143 144 testUsingContext('o,O - debugTogglePlatform with service protocol and debug mode', () async { 145 when(mockResidentRunner.isRunningDebug).thenReturn(true); 146 await terminalHandler.processTerminalInput('o'); 147 await terminalHandler.processTerminalInput('O'); 148 149 verify(mockResidentRunner.debugTogglePlatform()).called(2); 150 }); 151 152 testUsingContext('o,O - debugTogglePlatform without service protocol', () async { 153 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 154 when(mockResidentRunner.isRunningDebug).thenReturn(true); 155 await terminalHandler.processTerminalInput('o'); 156 await terminalHandler.processTerminalInput('O'); 157 158 verifyNever(mockResidentRunner.debugTogglePlatform()); 159 }); 160 161 testUsingContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async { 162 when(mockResidentRunner.isRunningDebug).thenReturn(true); 163 await terminalHandler.processTerminalInput('p'); 164 165 verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1); 166 }); 167 168 testUsingContext('p - debugTogglePlatform without service protocol', () async { 169 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 170 when(mockResidentRunner.isRunningDebug).thenReturn(true); 171 await terminalHandler.processTerminalInput('p'); 172 173 verifyNever(mockResidentRunner.debugToggleDebugPaintSizeEnabled()); 174 }); 175 176 testUsingContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async { 177 when(mockResidentRunner.isRunningDebug).thenReturn(true); 178 await terminalHandler.processTerminalInput('p'); 179 180 verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1); 181 }); 182 183 testUsingContext('p - debugTogglePlatform without service protocol', () async { 184 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 185 when(mockResidentRunner.isRunningDebug).thenReturn(true); 186 await terminalHandler.processTerminalInput('p'); 187 188 verifyNever(mockResidentRunner.debugToggleDebugPaintSizeEnabled()); 189 }); 190 191 testUsingContext('P - debugTogglePerformanceOverlayOverride with service protocol', () async { 192 await terminalHandler.processTerminalInput('P'); 193 194 verify(mockResidentRunner.debugTogglePerformanceOverlayOverride()).called(1); 195 }); 196 197 testUsingContext('P - debugTogglePerformanceOverlayOverride without service protocol', () async { 198 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 199 await terminalHandler.processTerminalInput('P'); 200 201 verifyNever(mockResidentRunner.debugTogglePerformanceOverlayOverride()); 202 }); 203 204 testUsingContext('q,Q - exit', () async { 205 await terminalHandler.processTerminalInput('q'); 206 await terminalHandler.processTerminalInput('Q'); 207 208 verify(mockResidentRunner.exit()).called(2); 209 }); 210 211 testUsingContext('s - screenshot', () async { 212 final MockDevice mockDevice = MockDevice(); 213 final MockFlutterDevice mockFlutterDevice = MockFlutterDevice(); 214 when(mockResidentRunner.isRunningDebug).thenReturn(true); 215 when(mockResidentRunner.flutterDevices).thenReturn(<FlutterDevice>[mockFlutterDevice]); 216 when(mockFlutterDevice.device).thenReturn(mockDevice); 217 when(mockDevice.supportsScreenshot).thenReturn(true); 218 219 await terminalHandler.processTerminalInput('s'); 220 221 verify(mockResidentRunner.screenshot(mockFlutterDevice)).called(1); 222 }); 223 224 testUsingContext('r - hotReload supported and succeeds', () async { 225 when(mockResidentRunner.canHotReload).thenReturn(true); 226 when(mockResidentRunner.restart(fullRestart: false)) 227 .thenAnswer((Invocation invocation) async { 228 return OperationResult(0, ''); 229 }); 230 await terminalHandler.processTerminalInput('r'); 231 232 verify(mockResidentRunner.restart(fullRestart: false)).called(1); 233 }); 234 235 testUsingContext('r - hotReload supported and fails', () async { 236 when(mockResidentRunner.canHotReload).thenReturn(true); 237 when(mockResidentRunner.restart(fullRestart: false)) 238 .thenAnswer((Invocation invocation) async { 239 return OperationResult(1, ''); 240 }); 241 await terminalHandler.processTerminalInput('r'); 242 243 verify(mockResidentRunner.restart(fullRestart: false)).called(1); 244 245 final BufferLogger bufferLogger = logger; 246 247 expect(bufferLogger.statusText, contains('Try again after fixing the above error(s).')); 248 }); 249 250 testUsingContext('r - hotReload supported and fails fatally', () async { 251 when(mockResidentRunner.canHotReload).thenReturn(true); 252 when(mockResidentRunner.hotMode).thenReturn(true); 253 when(mockResidentRunner.restart(fullRestart: false)) 254 .thenAnswer((Invocation invocation) async { 255 return OperationResult(1, 'fail', fatal: true); 256 }); 257 expect(terminalHandler.processTerminalInput('r'), throwsA(isInstanceOf<ToolExit>())); 258 }); 259 260 testUsingContext('r - hotReload unsupported', () async { 261 when(mockResidentRunner.canHotReload).thenReturn(false); 262 await terminalHandler.processTerminalInput('r'); 263 264 verifyNever(mockResidentRunner.restart(fullRestart: false)); 265 }); 266 267 testUsingContext('R - hotRestart supported and succeeds', () async { 268 when(mockResidentRunner.canHotRestart).thenReturn(true); 269 when(mockResidentRunner.hotMode).thenReturn(true); 270 when(mockResidentRunner.restart(fullRestart: true)) 271 .thenAnswer((Invocation invocation) async { 272 return OperationResult(0, ''); 273 }); 274 await terminalHandler.processTerminalInput('R'); 275 276 verify(mockResidentRunner.restart(fullRestart: true)).called(1); 277 }); 278 279 testUsingContext('R - hotRestart supported and fails', () async { 280 when(mockResidentRunner.canHotRestart).thenReturn(true); 281 when(mockResidentRunner.hotMode).thenReturn(true); 282 when(mockResidentRunner.restart(fullRestart: true)) 283 .thenAnswer((Invocation invocation) async { 284 return OperationResult(1, 'fail'); 285 }); 286 await terminalHandler.processTerminalInput('R'); 287 288 verify(mockResidentRunner.restart(fullRestart: true)).called(1); 289 290 final BufferLogger bufferLogger = logger; 291 292 expect(bufferLogger.statusText, contains('Try again after fixing the above error(s).')); 293 }); 294 295 testUsingContext('R - hotRestart supported and fails fatally', () async { 296 when(mockResidentRunner.canHotRestart).thenReturn(true); 297 when(mockResidentRunner.hotMode).thenReturn(true); 298 when(mockResidentRunner.restart(fullRestart: true)) 299 .thenAnswer((Invocation invocation) async { 300 return OperationResult(1, 'fail', fatal: true); 301 }); 302 expect(() => terminalHandler.processTerminalInput('R'), throwsA(isInstanceOf<ToolExit>())); 303 }); 304 305 testUsingContext('R - hot restart unsupported', () async { 306 when(mockResidentRunner.canHotRestart).thenReturn(false); 307 await terminalHandler.processTerminalInput('R'); 308 309 verifyNever(mockResidentRunner.restart(fullRestart: true)); 310 }); 311 312 testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async { 313 await terminalHandler.processTerminalInput('S'); 314 315 verify(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder()).called(1); 316 }); 317 318 testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async { 319 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 320 await terminalHandler.processTerminalInput('S'); 321 322 verifyNever(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder()); 323 }); 324 325 testUsingContext('t,T - debugDumpRenderTree with service protocol', () async { 326 await terminalHandler.processTerminalInput('t'); 327 await terminalHandler.processTerminalInput('T'); 328 329 verify(mockResidentRunner.debugDumpRenderTree()).called(2); 330 }); 331 332 testUsingContext('t,T - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async { 333 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 334 await terminalHandler.processTerminalInput('t'); 335 await terminalHandler.processTerminalInput('T'); 336 337 verifyNever(mockResidentRunner.debugDumpRenderTree()); 338 }); 339 340 testUsingContext('U - debugDumpRenderTree with service protocol', () async { 341 await terminalHandler.processTerminalInput('U'); 342 343 verify(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1); 344 }); 345 346 testUsingContext('U - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async { 347 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 348 await terminalHandler.processTerminalInput('U'); 349 350 verifyNever(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()); 351 }); 352 353 testUsingContext('w,W - debugDumpApp with service protocol', () async { 354 await terminalHandler.processTerminalInput('w'); 355 await terminalHandler.processTerminalInput('W'); 356 357 verify(mockResidentRunner.debugDumpApp()).called(2); 358 }); 359 360 testUsingContext('w,W - debugDumpApp without service protocol', () async { 361 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 362 await terminalHandler.processTerminalInput('w'); 363 await terminalHandler.processTerminalInput('W'); 364 365 verifyNever(mockResidentRunner.debugDumpApp()); 366 }); 367 368 testUsingContext('z,Z - debugToggleDebugCheckElevationsEnabled with service protocol', () async { 369 await terminalHandler.processTerminalInput('z'); 370 await terminalHandler.processTerminalInput('Z'); 371 372 verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2); 373 }); 374 375 testUsingContext('z,Z - debugToggleDebugCheckElevationsEnabled without service protocol', () async { 376 when(mockResidentRunner.supportsServiceProtocol).thenReturn(false); 377 await terminalHandler.processTerminalInput('z'); 378 await terminalHandler.processTerminalInput('Z'); 379 380 // This should probably be disable when the service protocol is not enabled. 381 verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2); 382 }); 383 }); 384} 385 386class MockDevice extends Mock implements Device { 387 MockDevice() { 388 when(isSupported()).thenReturn(true); 389 } 390} 391 392class MockResidentRunner extends Mock implements ResidentRunner {} 393 394class MockFlutterDevice extends Mock implements FlutterDevice {} 395 396class TestRunner extends ResidentRunner { 397 TestRunner(List<FlutterDevice> devices) 398 : super(devices); 399 400 bool hasHelpBeenPrinted = false; 401 String receivedCommand; 402 403 @override 404 Future<void> cleanupAfterSignal() async { } 405 406 @override 407 Future<void> cleanupAtFinish() async { } 408 409 @override 410 void printHelp({ bool details }) { 411 hasHelpBeenPrinted = true; 412 } 413 414 @override 415 Future<int> run({ 416 Completer<DebugConnectionInfo> connectionInfoCompleter, 417 Completer<void> appStartedCompleter, 418 String route, 419 }) async => null; 420 421 @override 422 Future<int> attach({ 423 Completer<DebugConnectionInfo> connectionInfoCompleter, 424 Completer<void> appStartedCompleter, 425 }) async => null; 426} 427