| // Copyright 2024 The Chromium 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:io'; |
| |
| import 'package:devtools_app/devtools_app.dart'; |
| import 'package:devtools_app/src/shared/constants.dart'; |
| import 'package:devtools_app/src/standalone_ui/api/impl/vs_code_api.dart'; |
| import 'package:devtools_app/src/standalone_ui/vs_code/debug_sessions.dart'; |
| import 'package:devtools_app_shared/ui.dart'; |
| import 'package:devtools_app_shared/utils.dart'; |
| import 'package:devtools_test/devtools_test.dart'; |
| import 'package:devtools_test/helpers.dart'; |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:mockito/mockito.dart'; |
| |
| import '../../test_infra/test_data/dart_tooling_api/mock_api.dart'; |
| |
| void main() { |
| const windowSize = Size(2000.0, 2000.0); |
| |
| late MockVsCodeApi mockVsCodeApi; |
| late final Map<String, VsCodeDevice> deviceMap; |
| |
| setUpAll(() { |
| // Set test mode so that the debug list of extensions will be used. |
| setTestMode(); |
| |
| final devices = stubbedDevices.map((d) => MapEntry(d.id, d)); |
| deviceMap = {for (final d in devices) d.key: d.value}; |
| }); |
| |
| setUp(() { |
| mockVsCodeApi = MockVsCodeApi(); |
| when(mockVsCodeApi.capabilities).thenReturn( |
| VsCodeCapabilitiesImpl({ |
| 'executeCommand': true, |
| 'selectDevice': true, |
| 'openDevToolsPage': true, |
| 'openDevToolsExternally': true, |
| 'hotReload': true, |
| 'hotRestart': true, |
| }), |
| ); |
| setGlobal(IdeTheme, IdeTheme()); |
| setGlobal(PreferencesController, PreferencesController()); |
| }); |
| |
| Future<void> pumpDebugSessions(WidgetTester tester) async { |
| await tester.pumpWidget( |
| wrap( |
| DebugSessions( |
| api: mockVsCodeApi, |
| sessions: _debugSessions, |
| deviceMap: deviceMap, |
| ), |
| ), |
| ); |
| } |
| |
| group('$DebugSessions', () { |
| Finder iconButtonFinder(IconData icon, {required int index}) { |
| return find |
| .byWidgetPredicate( |
| (widget) => |
| widget.runtimeType == IconButton && |
| ((widget as IconButton).icon as Icon).icon == icon, |
| ) |
| .at(index); |
| } |
| |
| void verifyDebugSessionState( |
| WidgetTester tester, { |
| required int debugSessionIndex, |
| required String sessionDisplayText, |
| required bool hotButtonsEnabled, |
| required bool devtoolsButtonEnabled, |
| }) { |
| expect(find.text(sessionDisplayText), findsOneWidget); |
| |
| final hotReloadButtonFinder = |
| iconButtonFinder(hotReloadIcon, index: debugSessionIndex); |
| final hotRestartButtonFinder = |
| iconButtonFinder(hotRestartIcon, index: debugSessionIndex); |
| final devtoolsButtonFinder = |
| iconButtonFinder(Icons.construction, index: debugSessionIndex); |
| expect(hotReloadButtonFinder, findsOneWidget); |
| expect(hotRestartButtonFinder, findsOneWidget); |
| expect(devtoolsButtonFinder, findsOneWidget); |
| |
| final hotReloadButton = |
| tester.widget(hotReloadButtonFinder) as IconButton; |
| final hotRestartButton = |
| tester.widget(hotRestartButtonFinder) as IconButton; |
| final devtoolsMenuButton = |
| tester.widget(devtoolsButtonFinder) as IconButton; |
| expect( |
| hotReloadButton.onPressed, |
| hotButtonsEnabled ? isNotNull : isNull, |
| ); |
| expect( |
| hotRestartButton.onPressed, |
| hotButtonsEnabled ? isNotNull : isNull, |
| ); |
| expect( |
| devtoolsMenuButton.onPressed, |
| devtoolsButtonEnabled ? isNotNull : isNull, |
| ); |
| } |
| |
| final tests = [ |
| ( |
| sessionDisplay: 'Session (Flutter) (macos) (debug)', |
| hotButtonsEnabled: true, |
| devtoolsButtonEnabled: true, |
| ), |
| ( |
| sessionDisplay: 'Session (Flutter) (macos) (profile)', |
| hotButtonsEnabled: false, |
| devtoolsButtonEnabled: true, |
| ), |
| ( |
| sessionDisplay: 'Session (Flutter) (macos) (release)', |
| hotButtonsEnabled: false, |
| devtoolsButtonEnabled: false, |
| ), |
| ( |
| sessionDisplay: 'Session (Flutter) (macos) (jit_release)', |
| hotButtonsEnabled: false, |
| devtoolsButtonEnabled: false, |
| ), |
| ( |
| sessionDisplay: 'Session (Flutter) (chrome) (debug)', |
| hotButtonsEnabled: true, |
| devtoolsButtonEnabled: true, |
| ), |
| ( |
| sessionDisplay: 'Session (Flutter) (chrome) (profile)', |
| hotButtonsEnabled: false, |
| devtoolsButtonEnabled: true, |
| ), |
| ( |
| sessionDisplay: 'Session (Flutter) (chrome) (release)', |
| hotButtonsEnabled: false, |
| devtoolsButtonEnabled: false, |
| ), |
| ( |
| sessionDisplay: 'Session (Dart) (macos)', |
| hotButtonsEnabled: true, |
| devtoolsButtonEnabled: true, |
| ), |
| ]; |
| |
| testWidgetsWithWindowSize( |
| 'rows render properly for run mode', |
| windowSize, |
| (tester) async { |
| await pumpDebugSessions(tester); |
| await tester.pump(const Duration(milliseconds: 500)); |
| for (var i = 0; i < tests.length; i++) { |
| final test = tests[i]; |
| // ignore: avoid_print, defines individual test case. |
| print('testing: ${test.sessionDisplay}'); |
| verifyDebugSessionState( |
| tester, |
| debugSessionIndex: i, |
| sessionDisplayText: test.sessionDisplay, |
| hotButtonsEnabled: test.hotButtonsEnabled, |
| devtoolsButtonEnabled: test.devtoolsButtonEnabled, |
| ); |
| } |
| }, |
| ); |
| |
| testWidgetsWithWindowSize( |
| 'DevTools dropdown contains extensions submenu', |
| windowSize, |
| (tester) async { |
| await pumpDebugSessions(tester); |
| await tester.pump(const Duration(milliseconds: 500)); |
| |
| // Index 0 so that we are checking a debug desktop session, where the |
| // DevTools button is enabled. |
| final devtoolsButtonFinder = |
| iconButtonFinder(Icons.construction, index: 0); |
| expect(devtoolsButtonFinder, findsOneWidget); |
| await tester.tap(devtoolsButtonFinder); |
| await tester.pumpAndSettle(); |
| |
| final extensionsSubmenuButtonFinder = find.text('Extensions'); |
| expect(extensionsSubmenuButtonFinder, findsOneWidget); |
| |
| // We should not see the extensions in the dropdown menu at this point. |
| expect(find.text('bar'), findsNothing); |
| expect(find.text('foo'), findsNothing); |
| expect(find.text('provider'), findsNothing); |
| |
| final hoverLocation = tester.getCenter(extensionsSubmenuButtonFinder); |
| await tester.startGesture(hoverLocation, kind: PointerDeviceKind.mouse); |
| await tester.pumpAndSettle(); |
| |
| // We should now see the extensions in the dropdown menu. |
| expect(find.text('bar'), findsOneWidget); |
| expect(find.text('foo'), findsOneWidget); |
| expect(find.text('provider'), findsOneWidget); |
| }, |
| ); |
| }); |
| } |
| |
| final _debugSessions = <VsCodeDebugSession>[ |
| // Flutter native apps. |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'macos', |
| flutterMode: 'debug', |
| ), |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'macos', |
| flutterMode: 'profile', |
| ), |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'macos', |
| flutterMode: 'release', |
| ), |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'macos', |
| flutterMode: 'jit_release', |
| ), |
| // Flutter web apps. |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'chrome', |
| flutterMode: 'debug', |
| ), |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'chrome', |
| flutterMode: 'profile', |
| ), |
| generateDebugSession( |
| debuggerType: 'Flutter', |
| deviceId: 'chrome', |
| flutterMode: 'release', |
| ), |
| // Dart CLI app. |
| generateDebugSession( |
| debuggerType: 'Dart', |
| deviceId: 'macos', |
| ), |
| ]; |
| |
| VsCodeDebugSession generateDebugSession({ |
| required String debuggerType, |
| required String deviceId, |
| String? flutterMode, |
| }) { |
| return VsCodeDebugSessionImpl( |
| id: '$debuggerType-$deviceId-$flutterMode', |
| name: 'Session ($debuggerType) ($deviceId)', |
| vmServiceUri: 'ws://127.0.0.1:1234/ws', |
| flutterMode: flutterMode, |
| flutterDeviceId: deviceId, |
| debuggerType: debuggerType, |
| projectRootPath: |
| Platform.isWindows ? r'C:\mock\root\path' : '/mock/root/path', |
| ); |
| } |