diff --git a/packages/devtools_app/lib/src/app.dart b/packages/devtools_app/lib/src/app.dart index 89cc017be65..b97aec609a7 100644 --- a/packages/devtools_app/lib/src/app.dart +++ b/packages/devtools_app/lib/src/app.dart @@ -197,6 +197,10 @@ class DevToolsAppState extends State with AutoDisposeMixin { Map args, DevToolsNavigationState? state, ) { + if (FrameworkCore.initializationInProgress) { + return const MaterialPage(child: CenteredCircularProgressIndicator()); + } + // Provide the appropriate page route. if (pages.containsKey(page)) { Widget widget = pages[page!]!( diff --git a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart index ad07b2bbf82..3f6507a02b0 100644 --- a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart +++ b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart @@ -12,6 +12,7 @@ import 'package:devtools_extensions/api.dart'; import 'package:flutter/material.dart'; import '../../shared/banner_messages.dart'; +import '../../shared/common_widgets.dart'; import '../../shared/globals.dart'; import '_controller_web.dart'; import 'controller.dart'; @@ -25,7 +26,8 @@ class EmbeddedExtension extends StatefulWidget { State createState() => _EmbeddedExtensionState(); } -class _EmbeddedExtensionState extends State { +class _EmbeddedExtensionState extends State + with AutoDisposeMixin { late final EmbeddedExtensionControllerImpl _embeddedExtensionController; late final _ExtensionIFrameController iFrameController; @@ -42,8 +44,16 @@ class _EmbeddedExtensionState extends State { Widget build(BuildContext context) { return Container( color: Theme.of(context).scaffoldBackgroundColor, - child: HtmlElementView( - viewType: _embeddedExtensionController.viewId, + child: ValueListenableBuilder( + valueListenable: extensionService.refreshInProgress, + builder: (context, refreshing, _) { + if (refreshing) { + return const CenteredCircularProgressIndicator(); + } + return HtmlElementView( + viewType: _embeddedExtensionController.viewId, + ); + }, ), ); } diff --git a/packages/devtools_app/lib/src/extensions/extension_service.dart b/packages/devtools_app/lib/src/extensions/extension_service.dart index c94508bf750..b6cdcde1d5f 100644 --- a/packages/devtools_app/lib/src/extensions/extension_service.dart +++ b/packages/devtools_app/lib/src/extensions/extension_service.dart @@ -37,6 +37,10 @@ class ExtensionService extends DisposableController ); } + /// Whether extensions are actively being refreshed by the DevTools server. + ValueListenable get refreshInProgress => _refreshInProgress; + final _refreshInProgress = ValueNotifier(false); + final _extensionEnabledStates = >{}; @@ -44,7 +48,9 @@ class ExtensionService extends DisposableController await _maybeRefreshExtensions(); addAutoDisposeListener( serviceConnection.serviceManager.connectedState, - _maybeRefreshExtensions, + () async { + await _maybeRefreshExtensions(); + }, ); // TODO(https://github.com/flutter/flutter/issues/134470): refresh on @@ -66,10 +72,12 @@ class ExtensionService extends DisposableController final appRootPath = await _connectedAppRootPath(); if (appRootPath == null) return; + _refreshInProgress.value = true; _availableExtensions.value = await server.refreshAvailableExtensions(appRootPath) ..sort(); await _refreshExtensionEnabledStates(); + _refreshInProgress.value = false; } Future _refreshExtensionEnabledStates() async { diff --git a/packages/devtools_app/lib/src/framework/framework_core.dart b/packages/devtools_app/lib/src/framework/framework_core.dart index 86cb9ccf5cf..f79b41df23a 100644 --- a/packages/devtools_app/lib/src/framework/framework_core.dart +++ b/packages/devtools_app/lib/src/framework/framework_core.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:devtools_app_shared/utils.dart'; +import 'package:devtools_shared/devtools_shared.dart'; import 'package:devtools_shared/service.dart'; import 'package:logging/logging.dart'; @@ -50,11 +51,13 @@ class FrameworkCore { _log.info('DevTools version ${devtools.version}.'); } + static bool initializationInProgress = false; + /// Returns true if we're able to connect to a device and false otherwise. static Future initVmService( String url, { - Uri? explicitUri, - required ErrorReporter errorReporter, + required String serviceUriAsString, + ErrorReporter? errorReporter = _defaultErrorReporter, bool logException = true, }) async { if (serviceConnection.serviceManager.hasConnection) { @@ -63,8 +66,10 @@ class FrameworkCore { return true; } - final Uri? uri = explicitUri ?? getServiceUriFromQueryString(url); + final normalizedUri = normalizeVmServiceUri(serviceUriAsString); + final Uri? uri = normalizedUri ?? getServiceUriFromQueryString(url); if (uri != null) { + initializationInProgress = true; final finishedCompleter = Completer(); try { @@ -95,12 +100,21 @@ class FrameworkCore { if (logException) { _log.shout(e, e, st); } - errorReporter('Unable to connect to VM service at $uri: $e', e); + errorReporter!('Unable to connect to VM service at $uri: $e', e); return false; + } finally { + initializationInProgress = false; } } else { // Don't report an error here because we do not have a URI to connect to. return false; } } + + static void _defaultErrorReporter(String title, Object error) { + notificationService.pushError( + '$title, $error', + isReportable: false, + ); + } } diff --git a/packages/devtools_app/lib/src/framework/home_screen.dart b/packages/devtools_app/lib/src/framework/home_screen.dart index 1c186ea7ba8..70a867e6754 100644 --- a/packages/devtools_app/lib/src/framework/home_screen.dart +++ b/packages/devtools_app/lib/src/framework/home_screen.dart @@ -279,20 +279,20 @@ class _ConnectDialogState extends State gac.home, gac.HomeScreenEvents.connectToApp.name, ); - if (connectDialogController.text.isEmpty) { + + final uri = connectDialogController.text; + if (uri.isEmpty) { notificationService.push('Please enter a VM Service URL.'); return; } assert(() { if (_debugSharedPreferences != null) { - _debugSharedPreferences! - .setString(_vmServiceUriKey, connectDialogController.text); + _debugSharedPreferences!.setString(_vmServiceUriKey, uri); } return true; }()); - final uri = normalizeVmServiceUri(connectDialogController.text); // Cache the routerDelegate and notifications providers before the async // gap as the landing screen may not be displayed by the time the async gap // is complete but we still want to show notifications and change the route. @@ -302,13 +302,7 @@ class _ConnectDialogState extends State final routerDelegate = DevToolsRouterDelegate.of(context); final connected = await FrameworkCore.initVmService( '', - explicitUri: uri, - errorReporter: (message, error) { - notificationService.pushError( - '$message $error', - isReportable: false, - ); - }, + serviceUriAsString: uri, ); if (connected) { final connectedUri = @@ -316,7 +310,7 @@ class _ConnectDialogState extends State routerDelegate.updateArgsIfChanged({'uri': '$connectedUri'}); final shortUri = connectedUri.replace(path: ''); notificationService.push('Successfully connected to $shortUri.'); - } else if (uri == null) { + } else if (normalizeVmServiceUri(uri) == null) { notificationService.push( 'Failed to connect to the VM Service at "${connectDialogController.text}".\n' 'The link was not valid.', diff --git a/packages/devtools_app/lib/src/framework/initializer.dart b/packages/devtools_app/lib/src/framework/initializer.dart index bae5d003f51..ccee5a5393a 100644 --- a/packages/devtools_app/lib/src/framework/initializer.dart +++ b/packages/devtools_app/lib/src/framework/initializer.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_shared/devtools_shared.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -130,17 +129,9 @@ class _InitializerState extends State return; } - errorReporter ??= (String message, Object error) { - notificationService.pushError( - '$message, $error', - isReportable: false, - ); - }; - - final uri = normalizeVmServiceUri(widget.url!); final connected = await FrameworkCore.initVmService( '', - explicitUri: uri, + serviceUriAsString: widget.url!, errorReporter: errorReporter, logException: logException, ); diff --git a/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart b/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart index 54c222a3ca6..48017694079 100644 --- a/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart +++ b/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart @@ -52,7 +52,7 @@ class DebuggerController extends DisposableController addAutoDisposeListener(_selectedStackFrame, _updateCurrentFrame); addAutoDisposeListener(_stackFramesWithLocation, _updateCurrentFrame); - if (serviceConnection.serviceManager.hasService) { + if (serviceConnection.serviceManager.connectedState.value.connected) { _initialize(); } } diff --git a/packages/devtools_app/lib/src/screens/debugger/debugger_screen.dart b/packages/devtools_app/lib/src/screens/debugger/debugger_screen.dart index a9a1220fdba..0a417b154fc 100644 --- a/packages/devtools_app/lib/src/screens/debugger/debugger_screen.dart +++ b/packages/devtools_app/lib/src/screens/debugger/debugger_screen.dart @@ -77,7 +77,7 @@ class DebuggerScreen extends Screen { const FixedValueListenable(true); @override - Widget build(BuildContext context) => const DebuggerScreenBody(); + Widget build(BuildContext context) => const _DebuggerScreenBodyWrapper(); @override Widget buildStatus(BuildContext context) { @@ -86,26 +86,21 @@ class DebuggerScreen extends Screen { } } -class DebuggerScreenBody extends StatefulWidget { - const DebuggerScreenBody({super.key}); - - static final codeViewKey = GlobalKey(debugLabel: 'codeViewKey'); - static final scriptViewKey = GlobalKey(debugLabel: 'scriptViewKey'); - static const callStackCopyButtonKey = - Key('debugger_call_stack_copy_to_clipboard_button'); +/// Wrapper widget for the [DebuggerScreenBody] that handles screen +/// initialization. +class _DebuggerScreenBodyWrapper extends StatefulWidget { + const _DebuggerScreenBodyWrapper(); @override - DebuggerScreenBodyState createState() => DebuggerScreenBodyState(); + _DebuggerScreenBodyWrapperState createState() => + _DebuggerScreenBodyWrapperState(); } -class DebuggerScreenBodyState extends State +class _DebuggerScreenBodyWrapperState extends State<_DebuggerScreenBodyWrapper> with AutoDisposeMixin, - ProvidedControllerMixin { - static const callStackTitle = 'Call Stack'; - static const variablesTitle = 'Variables'; - static const breakpointsTitle = 'Breakpoints'; - + ProvidedControllerMixin { late bool _shownFirstScript; @override @@ -136,107 +131,54 @@ class DebuggerScreenBodyState extends State @override Widget build(BuildContext context) { - final codeViewController = controller.codeViewController; + return DebuggerScreenBody( + shownFirstScript: () => _shownFirstScript, + setShownFirstScript: (value) => _shownFirstScript = value, + ); + } +} + +@visibleForTesting +class DebuggerScreenBody extends StatelessWidget { + const DebuggerScreenBody({ + super.key, + required this.shownFirstScript, + required this.setShownFirstScript, + }); + + final bool Function() shownFirstScript; + + final void Function(bool) setShownFirstScript; + + @override + Widget build(BuildContext context) { return Split( axis: Axis.horizontal, initialFractions: const [0.25, 0.75], children: [ - RoundedOutlinedBorder( + const RoundedOutlinedBorder( clip: true, - child: debuggerPanes(), + child: DebuggerWindows(), ), - Column( - children: [ - const DebuggingControls(), - const SizedBox(height: intermediateSpacing), - Expanded( - child: ValueListenableBuilder( - valueListenable: codeViewController.fileExplorerVisible, - builder: (context, visible, child) { - // Conditional expression - // ignore: prefer-conditional-expression - if (visible) { - // TODO(devoncarew): Animate this opening and closing. - return Split( - axis: Axis.horizontal, - initialFractions: const [0.7, 0.3], - children: [ - child!, - RoundedOutlinedBorder( - clip: true, - child: ProgramExplorer( - controller: - codeViewController.programExplorerController, - onNodeSelected: _onNodeSelected, - ), - ), - ], - ); - } else { - return child!; - } - }, - child: MultiValueListenableBuilder( - listenables: [ - codeViewController.currentScriptRef, - codeViewController.currentParsedScript, - ], - builder: (context, values, _) { - final scriptRef = values.first as ScriptRef?; - final parsedScript = values.second as ParsedScript?; - if (scriptRef != null && - parsedScript != null && - !_shownFirstScript) { - ga.timeEnd( - DebuggerScreen.id, - gac.pageReady, - ); - unawaited( - serviceConnection.sendDwdsEvent( - screen: DebuggerScreen.id, - action: gac.pageReady, - ), - ); - _shownFirstScript = true; - } - - return CodeView( - key: DebuggerScreenBody.codeViewKey, - codeViewController: codeViewController, - debuggerController: controller, - scriptRef: scriptRef, - parsedScript: parsedScript, - onSelected: (script, line) => unawaited( - breakpointManager.toggleBreakpoint(script, line), - ), - ); - }, - ), - ), - ), - ], + DebuggerSourceAndControls( + shownFirstScript: shownFirstScript, + setShownFirstScript: setShownFirstScript, ), ], ); } +} - void _onNodeSelected(VMServiceObjectNode? node) { - final location = node?.location; - if (location != null) { - final routerDelegate = DevToolsRouterDelegate.of(context); - Router.navigate(context, () { - routerDelegate.updateStateIfChanged( - CodeViewSourceLocationNavigationState( - script: location.scriptRef, - line: location.location?.line ?? 0, - object: node!.object, - ), - ); - }); - } - } +class DebuggerWindows extends StatelessWidget { + const DebuggerWindows({super.key}); - Widget debuggerPanes() { + static const callStackTitle = 'Call Stack'; + static const variablesTitle = 'Variables'; + static const breakpointsTitle = 'Breakpoints'; + + @override + Widget build(BuildContext context) { + final controller = Provider.of(context); return LayoutBuilder( builder: (context, constraints) { return FlexSplitColumn( @@ -246,6 +188,8 @@ class DebuggerScreenBodyState extends State headers: [ AreaPaneHeader( title: const Text(callStackTitle), + roundedTopBorder: false, + includeTopBorder: false, actions: [ CopyToClipboardControl( dataProvider: () { @@ -258,21 +202,16 @@ class DebuggerScreenBodyState extends State } return callStackList.join('\n'); }, - buttonKey: DebuggerScreenBody.callStackCopyButtonKey, ), ], - roundedTopBorder: false, - includeTopBorder: false, ), const AreaPaneHeader( title: Text(variablesTitle), roundedTopBorder: false, ), - AreaPaneHeader( - title: const Text(breakpointsTitle), - actions: [ - _breakpointsRightChild(), - ], + const AreaPaneHeader( + title: Text(breakpointsTitle), + actions: [_BreakpointsWindowActions()], rightPadding: 0.0, roundedTopBorder: false, ), @@ -286,8 +225,13 @@ class DebuggerScreenBodyState extends State }, ); } +} - Widget _breakpointsRightChild() { +class _BreakpointsWindowActions extends StatelessWidget { + const _BreakpointsWindowActions(); + + @override + Widget build(BuildContext context) { return ValueListenableBuilder>( valueListenable: breakpointManager.breakpointsWithLocation, builder: (context, breakpoints, _) { @@ -310,6 +254,111 @@ class DebuggerScreenBodyState extends State } } +class DebuggerSourceAndControls extends StatelessWidget { + const DebuggerSourceAndControls({ + super.key, + required this.shownFirstScript, + required this.setShownFirstScript, + }); + + final bool Function() shownFirstScript; + + final void Function(bool) setShownFirstScript; + + @override + Widget build(BuildContext context) { + final controller = Provider.of(context); + final codeViewController = controller.codeViewController; + return Column( + children: [ + const DebuggingControls(), + const SizedBox(height: intermediateSpacing), + Expanded( + child: ValueListenableBuilder( + valueListenable: codeViewController.fileExplorerVisible, + builder: (context, visible, child) { + // Conditional expression + // ignore: prefer-conditional-expression + if (visible) { + // TODO(devoncarew): Animate this opening and closing. + return Split( + axis: Axis.horizontal, + initialFractions: const [0.7, 0.3], + children: [ + child!, + RoundedOutlinedBorder( + clip: true, + child: ProgramExplorer( + controller: + codeViewController.programExplorerController, + onNodeSelected: (node) => + _onNodeSelected(context, node), + ), + ), + ], + ); + } else { + return child!; + } + }, + child: MultiValueListenableBuilder( + listenables: [ + codeViewController.currentScriptRef, + codeViewController.currentParsedScript, + ], + builder: (context, values, _) { + final scriptRef = values.first as ScriptRef?; + final parsedScript = values.second as ParsedScript?; + if (scriptRef != null && + parsedScript != null && + !shownFirstScript()) { + ga.timeEnd( + DebuggerScreen.id, + gac.pageReady, + ); + unawaited( + serviceConnection.sendDwdsEvent( + screen: DebuggerScreen.id, + action: gac.pageReady, + ), + ); + setShownFirstScript(true); + } + + return CodeView( + codeViewController: codeViewController, + debuggerController: controller, + scriptRef: scriptRef, + parsedScript: parsedScript, + onSelected: (script, line) => unawaited( + breakpointManager.toggleBreakpoint(script, line), + ), + ); + }, + ), + ), + ), + ], + ); + } + + void _onNodeSelected(BuildContext context, VMServiceObjectNode? node) { + final location = node?.location; + if (location != null) { + final routerDelegate = DevToolsRouterDelegate.of(context); + Router.navigate(context, () { + routerDelegate.updateStateIfChanged( + CodeViewSourceLocationNavigationState( + script: location.scriptRef, + line: location.location?.line ?? 0, + object: node!.object, + ), + ); + }); + } + } +} + class GoToLineNumberIntent extends Intent { const GoToLineNumberIntent(this._context, this._controller); diff --git a/packages/devtools_app/lib/src/service/service_manager.dart b/packages/devtools_app/lib/src/service/service_manager.dart index f7ab0c87e93..6022201ecf7 100644 --- a/packages/devtools_app/lib/src/service/service_manager.dart +++ b/packages/devtools_app/lib/src/service/service_manager.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:devtools_app_shared/service.dart'; +import 'package:devtools_app_shared/utils.dart'; import 'package:logging/logging.dart'; import 'package:vm_service/vm_service.dart' hide Error; @@ -35,6 +36,10 @@ const debugLogServiceProtocolEvents = false; const defaultRefreshRate = 60.0; +/// The amount of time we will wait for the main isolate to become non-null when +/// calling [ServiceConnectionManager.rootLibraryForMainIsolate]. +const _waitForRootLibraryTimeout = Duration(seconds: 3); + class ServiceConnectionManager { ServiceConnectionManager() { serviceManager = ServiceManager() @@ -167,9 +172,10 @@ class ServiceConnectionManager { } Future rootLibraryForMainIsolate() async { - if (!serviceManager.connectedState.value.connected) return null; - - final mainIsolateRef = serviceManager.isolateManager.mainIsolate.value; + final mainIsolateRef = await whenValueNonNull( + serviceManager.isolateManager.mainIsolate, + timeout: _waitForRootLibraryTimeout, + ); if (mainIsolateRef == null) return null; final isolateState = diff --git a/packages/devtools_app/lib/src/shared/routing.dart b/packages/devtools_app/lib/src/shared/routing.dart index 1aed4eb1d51..df04cfee09c 100644 --- a/packages/devtools_app/lib/src/shared/routing.dart +++ b/packages/devtools_app/lib/src/shared/routing.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import '../framework/framework_core.dart'; import 'globals.dart'; import 'primitives/utils.dart'; @@ -46,7 +47,7 @@ class DevToolsRouteInformationParser @override Future parseRouteInformation( RouteInformation routeInformation, - ) { + ) async { var uri = routeInformation.uri; if (_forceVmServiceUri != null) { final newQueryParams = Map.from(uri.queryParameters); @@ -54,11 +55,16 @@ class DevToolsRouteInformationParser uri = uri.copyWith(queryParameters: newQueryParams); } - // If the uri has been modified and we do not have a vm service uri as a - // query parameter, ensure we manually disconnect from any previously - // connected applications. - if (uri.queryParameters['uri'] == null) { - serviceConnection.serviceManager.manuallyDisconnect(); + final uriFromParams = uri.queryParameters['uri']; + if (uriFromParams == null) { + // If the uri has been modified and we do not have a vm service uri as a + // query parameter, ensure we manually disconnect from any previously + // connected applications. + await serviceConnection.serviceManager.manuallyDisconnect(); + } else if (_forceVmServiceUri == null) { + // Otherwise, connect to the vm service from the query parameter before + // loading the route (but do not do this in a testing environment). + await FrameworkCore.initVmService('', serviceUriAsString: uriFromParams); } // routeInformation.path comes from the address bar and (when not empty) is @@ -131,11 +137,11 @@ class DevToolsRouterDelegate extends RouterDelegate /// /// This will usually only contain a single item (it's the visible stack, /// not the history). - final routes = ListQueue(); + final _routes = ListQueue(); @override DevToolsRouteConfiguration? get currentConfiguration => - routes.isEmpty ? null : routes.last; + _routes.isEmpty ? null : _routes.last; @override Widget build(BuildContext context) { @@ -148,11 +154,11 @@ class DevToolsRouterDelegate extends RouterDelegate key: navigatorKey, pages: [_getPage(context, page, args, state)], onPopPage: (_, __) { - if (routes.length <= 1) { + if (_routes.length <= 1) { return false; } - routes.removeLast(); + _routes.removeLast(); notifyListeners(); return true; }, @@ -219,7 +225,7 @@ class DevToolsRouterDelegate extends RouterDelegate /// Replaces the navigation stack with a new route. void _replaceStack(DevToolsRouteConfiguration configuration) { _currentPage = configuration.page; - routes + _routes ..clear() ..add(configuration); } diff --git a/packages/devtools_app/test/debugger/debugger_codeview_statistics_test.dart b/packages/devtools_app/test/debugger/debugger_codeview_statistics_test.dart index f48b662b5d7..2db696ec969 100644 --- a/packages/devtools_app/test/debugger/debugger_codeview_statistics_test.dart +++ b/packages/devtools_app/test/debugger/debugger_codeview_statistics_test.dart @@ -85,7 +85,10 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + DebuggerSourceAndControls( + shownFirstScript: () => true, + setShownFirstScript: (_) {}, + ), debugger: controller, ), ); diff --git a/packages/devtools_app/test/debugger/debugger_codeview_test.dart b/packages/devtools_app/test/debugger/debugger_codeview_test.dart index 915cacc1510..f743a157545 100644 --- a/packages/devtools_app/test/debugger/debugger_codeview_test.dart +++ b/packages/devtools_app/test/debugger/debugger_codeview_test.dart @@ -71,7 +71,10 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + DebuggerSourceAndControls( + shownFirstScript: () => true, + setShownFirstScript: (_) {}, + ), debugger: controller, ), ); @@ -101,7 +104,7 @@ void main() { findsOneWidget, ); await expectLater( - find.byKey(DebuggerScreenBody.codeViewKey), + find.byType(CodeView), matchesDevToolsGolden( '../test_infra/goldens/codeview_scrollbars.png', ), diff --git a/packages/devtools_app/test/debugger/debugger_screen_breakpoints_test.dart b/packages/devtools_app/test/debugger/debugger_screen_breakpoints_test.dart index 869dbc1a6f2..ab39e21c5f0 100644 --- a/packages/devtools_app/test/debugger/debugger_screen_breakpoints_test.dart +++ b/packages/devtools_app/test/debugger/debugger_screen_breakpoints_test.dart @@ -74,7 +74,7 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + const DebuggerWindows(), debugger: controller, ), ); diff --git a/packages/devtools_app/test/debugger/debugger_screen_call_stack_test.dart b/packages/devtools_app/test/debugger/debugger_screen_call_stack_test.dart index 3eb1225122b..3aa145856a7 100644 --- a/packages/devtools_app/test/debugger/debugger_screen_call_stack_test.dart +++ b/packages/devtools_app/test/debugger/debugger_screen_call_stack_test.dart @@ -51,7 +51,7 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + const DebuggerWindows(), debugger: controller, ), ); diff --git a/packages/devtools_app/test/debugger/debugger_screen_dap_variables_test.dart b/packages/devtools_app/test/debugger/debugger_screen_dap_variables_test.dart index 4dffc628910..65686daf049 100644 --- a/packages/devtools_app/test/debugger/debugger_screen_dap_variables_test.dart +++ b/packages/devtools_app/test/debugger/debugger_screen_dap_variables_test.dart @@ -61,7 +61,7 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + const DebuggerWindows(), debugger: controller, ), ); diff --git a/packages/devtools_app/test/debugger/debugger_screen_explorer_hidden_test.dart b/packages/devtools_app/test/debugger/debugger_screen_explorer_hidden_test.dart deleted file mode 100644 index 0e8abda986d..00000000000 --- a/packages/devtools_app/test/debugger/debugger_screen_explorer_hidden_test.dart +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020 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 'package:devtools_app/devtools_app.dart'; -import 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_test/devtools_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:vm_service/vm_service.dart'; - -void main() { - const windowSize = Size(4000.0, 4000.0); - final fakeServiceConnection = FakeServiceConnectionManager(); - final scriptManager = MockScriptManager(); - mockConnectedApp( - fakeServiceConnection.serviceManager.connectedApp!, - isProfileBuild: false, - isFlutterApp: true, - isWebApp: false, - ); - setGlobal(ServiceConnectionManager, fakeServiceConnection); - setGlobal(IdeTheme, IdeTheme()); - setGlobal(ScriptManager, scriptManager); - setGlobal(NotificationService, NotificationService()); - setGlobal(BreakpointManager, BreakpointManager()); - setGlobal( - DevToolsEnvironmentParameters, - ExternalDevToolsEnvironmentParameters(), - ); - setGlobal(PreferencesController, PreferencesController()); - fakeServiceConnection.consoleService.ensureServiceInitialized(); - when(fakeServiceConnection.errorBadgeManager.errorCountNotifier('debugger')) - .thenReturn(ValueNotifier(0)); - final debuggerController = createMockDebuggerControllerWithDefaults(); - final codeViewController = debuggerController.codeViewController; - - final scripts = [ - ScriptRef(uri: 'package:/test/script.dart', id: 'test-script'), - ]; - - when(scriptManager.sortedScripts).thenReturn(ValueNotifier(scripts)); - when(codeViewController.showFileOpener).thenReturn(ValueNotifier(false)); - when(codeViewController.showProfileInformation).thenReturn( - const FixedValueListenable(false), - ); - - // File Explorer view is hidden - when(codeViewController.fileExplorerVisible).thenReturn(ValueNotifier(false)); - - Future pumpDebuggerScreen( - WidgetTester tester, - DebuggerController controller, - ) async { - await tester.pumpWidget( - wrapWithControllers( - const DebuggerScreenBody(), - debugger: controller, - ), - ); - } - - testWidgetsWithWindowSize( - 'File Explorer hidden', - windowSize, - (WidgetTester tester) async { - await pumpDebuggerScreen(tester, debuggerController); - expect(find.text('File Explorer'), findsOneWidget); - }, - ); -} diff --git a/packages/devtools_app/test/debugger/debugger_screen_explorer_visibility_test.dart b/packages/devtools_app/test/debugger/debugger_screen_explorer_visibility_test.dart new file mode 100644 index 00000000000..85f774788dc --- /dev/null +++ b/packages/devtools_app/test/debugger/debugger_screen_explorer_visibility_test.dart @@ -0,0 +1,125 @@ +// Copyright 2020 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 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app_shared/ui.dart'; +import 'package:devtools_app_shared/utils.dart'; +import 'package:devtools_test/devtools_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:vm_service/vm_service.dart'; + +void main() { + const windowSize = Size(4000.0, 4000.0); + + late FakeServiceConnectionManager fakeServiceConnection; + late MockScriptManager scriptManager; + late MockDebuggerController debuggerController; + late CodeViewController mockCodeViewController; + final scripts = [ + ScriptRef(uri: 'package:test/script.dart', id: 'test-script'), + ]; + + group( + 'FileExplorer', + () { + setUp(() { + fakeServiceConnection = FakeServiceConnectionManager(); + scriptManager = MockScriptManager(); + mockConnectedApp( + fakeServiceConnection.serviceManager.connectedApp!, + isFlutterApp: true, + isProfileBuild: false, + isWebApp: false, + ); + setGlobal(ServiceConnectionManager, fakeServiceConnection); + setGlobal(IdeTheme, IdeTheme()); + setGlobal(NotificationService, NotificationService()); + setGlobal(ScriptManager, scriptManager); + setGlobal(BreakpointManager, BreakpointManager()); + setGlobal( + DevToolsEnvironmentParameters, + ExternalDevToolsEnvironmentParameters(), + ); + setGlobal(PreferencesController, PreferencesController()); + fakeServiceConnection.consoleService.ensureServiceInitialized(); + when( + fakeServiceConnection.errorBadgeManager + .errorCountNotifier('debugger'), + ).thenReturn(ValueNotifier(0)); + final mockProgramExplorerController = + createMockProgramExplorerControllerWithDefaults(); + mockCodeViewController = createMockCodeViewControllerWithDefaults( + programExplorerController: mockProgramExplorerController, + ); + debuggerController = createMockDebuggerControllerWithDefaults( + codeViewController: mockCodeViewController, + ); + + when(scriptManager.sortedScripts).thenReturn(ValueNotifier(scripts)); + + when(mockProgramExplorerController.rootObjectNodes).thenReturn( + ValueNotifier( + [ + VMServiceObjectNode( + mockCodeViewController.programExplorerController, + 'package:test', + null, + ), + ], + ), + ); + when(mockCodeViewController.showFileOpener) + .thenReturn(ValueNotifier(false)); + }); + + Future pumpDebuggerScreen( + WidgetTester tester, + DebuggerController controller, + ) async { + await tester.pumpWidget( + wrapWithControllers( + DebuggerSourceAndControls( + shownFirstScript: () => true, + setShownFirstScript: (_) {}, + ), + debugger: controller, + ), + ); + } + + testWidgetsWithWindowSize( + 'visible', + windowSize, + (WidgetTester tester) async { + // File Explorer view is shown + when(mockCodeViewController.fileExplorerVisible) + .thenReturn(ValueNotifier(true)); + await pumpDebuggerScreen(tester, debuggerController); + // One for the button and one for the title of the File Explorer view. + expect(find.text('File Explorer'), findsNWidgets(2)); + + // test for items in the libraries tree + expect( + find.text(scripts.first.uri!.split('/').first), + findsOneWidget, + ); + }, + ); + + testWidgetsWithWindowSize( + 'hidden', + windowSize, + (WidgetTester tester) async { + // File Explorer view is hidden + when(mockCodeViewController.fileExplorerVisible) + .thenReturn(ValueNotifier(false)); + await pumpDebuggerScreen(tester, debuggerController); + expect(find.text('File Explorer'), findsOneWidget); + }, + ); + }, + ); +} diff --git a/packages/devtools_app/test/debugger/debugger_screen_explorer_visible_test.dart b/packages/devtools_app/test/debugger/debugger_screen_explorer_visible_test.dart deleted file mode 100644 index 26a05e9c41e..00000000000 --- a/packages/devtools_app/test/debugger/debugger_screen_explorer_visible_test.dart +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2020 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 'package:devtools_app/devtools_app.dart'; -import 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_test/devtools_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:vm_service/vm_service.dart'; - -void main() { - const windowSize = Size(4000.0, 4000.0); - - final fakeServiceConnection = FakeServiceConnectionManager(); - final scriptManager = MockScriptManager(); - mockConnectedApp( - fakeServiceConnection.serviceManager.connectedApp!, - isFlutterApp: true, - isProfileBuild: false, - isWebApp: false, - ); - setGlobal(ServiceConnectionManager, fakeServiceConnection); - setGlobal(IdeTheme, IdeTheme()); - setGlobal(NotificationService, NotificationService()); - setGlobal(ScriptManager, scriptManager); - setGlobal(BreakpointManager, BreakpointManager()); - setGlobal( - DevToolsEnvironmentParameters, - ExternalDevToolsEnvironmentParameters(), - ); - setGlobal(PreferencesController, PreferencesController()); - fakeServiceConnection.consoleService.ensureServiceInitialized(); - when(fakeServiceConnection.errorBadgeManager.errorCountNotifier('debugger')) - .thenReturn(ValueNotifier(0)); - final mockProgramExplorerController = - createMockProgramExplorerControllerWithDefaults(); - final mockCodeViewController = createMockCodeViewControllerWithDefaults( - programExplorerController: mockProgramExplorerController, - ); - final debuggerController = createMockDebuggerControllerWithDefaults( - codeViewController: mockCodeViewController, - ); - final scripts = [ - ScriptRef(uri: 'package:test/script.dart', id: 'test-script'), - ]; - - when(scriptManager.sortedScripts).thenReturn(ValueNotifier(scripts)); - - when(mockProgramExplorerController.rootObjectNodes).thenReturn( - ValueNotifier( - [ - VMServiceObjectNode( - mockCodeViewController.programExplorerController, - 'package:test', - null, - ), - ], - ), - ); - when(mockCodeViewController.showFileOpener).thenReturn(ValueNotifier(false)); - - // File Explorer view is shown - when(mockCodeViewController.fileExplorerVisible) - .thenReturn(ValueNotifier(true)); - - Future pumpDebuggerScreen( - WidgetTester tester, - DebuggerController controller, - ) async { - await tester.pumpWidget( - wrapWithControllers( - const DebuggerScreenBody(), - debugger: controller, - ), - ); - } - - testWidgetsWithWindowSize( - 'File Explorer visible', - windowSize, - (WidgetTester tester) async { - await pumpDebuggerScreen(tester, debuggerController); - // One for the button and one for the title of the File Explorer view. - expect(find.text('File Explorer'), findsNWidgets(2)); - - // test for items in the libraries tree - expect(find.text(scripts.first.uri!.split('/').first), findsOneWidget); - }, - ); -} diff --git a/packages/devtools_app/test/debugger/debugger_screen_paused_test.dart b/packages/devtools_app/test/debugger/debugger_screen_paused_test.dart index 257c8a3eea7..993d17d9f2f 100644 --- a/packages/devtools_app/test/debugger/debugger_screen_paused_test.dart +++ b/packages/devtools_app/test/debugger/debugger_screen_paused_test.dart @@ -18,77 +18,71 @@ import 'package:vm_service/vm_service.dart'; import '../test_infra/utils/test_utils.dart'; void main() { - final screen = DebuggerScreen(); - const windowSize = Size(2500.0, 1500.0); - final fakeServiceConnection = FakeServiceConnectionManager(); - final scriptManager = MockScriptManager(); - mockConnectedApp( - fakeServiceConnection.serviceManager.connectedApp!, - isFlutterApp: true, - isProfileBuild: false, - isWebApp: false, - ); - setGlobal(ServiceConnectionManager, fakeServiceConnection); - setGlobal(IdeTheme, IdeTheme()); - setGlobal(ScriptManager, scriptManager); - setGlobal(NotificationService, NotificationService()); - setGlobal(BreakpointManager, BreakpointManager()); - setGlobal( - DevToolsEnvironmentParameters, - ExternalDevToolsEnvironmentParameters(), - ); - setGlobal(PreferencesController, PreferencesController()); - fakeServiceConnection.consoleService.ensureServiceInitialized(); - when(fakeServiceConnection.errorBadgeManager.errorCountNotifier('debugger')) - .thenReturn(ValueNotifier(0)); - final debuggerController = createMockDebuggerControllerWithDefaults(); - final codeViewController = debuggerController.codeViewController; - final scriptsHistory = ScriptsHistory(); - scriptsHistory.pushEntry(mockScript!); - when(codeViewController.scriptsHistory).thenReturn(scriptsHistory); - when(debuggerController.stackFramesWithLocation).thenReturn( - ValueNotifier([ - _stackFrame1, - _stackFrame2, - ]), - ); - when(codeViewController.currentScriptRef) - .thenReturn(ValueNotifier(mockScriptRef)); - when(codeViewController.currentParsedScript) - .thenReturn(ValueNotifier(mockParsedScript)); - when(codeViewController.navigationInProgress).thenReturn(false); - - Finder findDebuggerButtonWithTitle(String title) => find.byWidgetPredicate( - (Widget widget) => widget is DebuggerButton && widget.title == title, - ); - - Finder findStackFrameWithText(String text) => find.byWidgetPredicate( - (Widget widget) => - widget is RichText && widget.text.toPlainText().contains(text), - ); - - bool gutterItemForLineIsVisible(int lineNumber) { - final gutterItems = find.byType(GutterItem); - final firstGutterItem = getWidgetFromFinder(gutterItems.first); - final lastGutterItem = getWidgetFromFinder(gutterItems.last); - final lineRange = - Range(firstGutterItem.lineNumber, lastGutterItem.lineNumber); - - return lineRange.contains(lineNumber); + late FakeServiceConnectionManager fakeServiceConnection; + late MockScriptManager scriptManager; + late MockDebuggerController debuggerController; + + setUp(() { + fakeServiceConnection = FakeServiceConnectionManager(); + scriptManager = MockScriptManager(); + + mockConnectedApp( + fakeServiceConnection.serviceManager.connectedApp!, + isFlutterApp: true, + isProfileBuild: false, + isWebApp: false, + ); + setGlobal(ServiceConnectionManager, fakeServiceConnection); + setGlobal(IdeTheme, IdeTheme()); + setGlobal(ScriptManager, scriptManager); + setGlobal(NotificationService, NotificationService()); + setGlobal(BreakpointManager, BreakpointManager()); + setGlobal( + DevToolsEnvironmentParameters, + ExternalDevToolsEnvironmentParameters(), + ); + setGlobal(PreferencesController, PreferencesController()); + fakeServiceConnection.consoleService.ensureServiceInitialized(); + when(fakeServiceConnection.errorBadgeManager.errorCountNotifier('debugger')) + .thenReturn(ValueNotifier(0)); + debuggerController = createMockDebuggerControllerWithDefaults(); + final codeViewController = debuggerController.codeViewController; + final scriptsHistory = ScriptsHistory(); + scriptsHistory.pushEntry(mockScript!); + when(codeViewController.scriptsHistory).thenReturn(scriptsHistory); + when(debuggerController.stackFramesWithLocation).thenReturn( + ValueNotifier([ + _stackFrame1, + _stackFrame2, + ]), + ); + when(codeViewController.currentScriptRef) + .thenReturn(ValueNotifier(mockScriptRef)); + when(codeViewController.currentParsedScript) + .thenReturn(ValueNotifier(mockParsedScript)); + when(codeViewController.navigationInProgress).thenReturn(false); + }); + + Future pumpDebuggerScreen(WidgetTester tester) async { + await tester.pumpWidget( + wrapWithControllers( + DebuggerScreenBody( + shownFirstScript: () => true, + setShownFirstScript: (_) {}, + ), + debugger: debuggerController, + ), + ); } testWidgetsWithWindowSize( 'debugger controls paused', windowSize, (WidgetTester tester) async { - await tester.pumpWidget( - wrapWithControllers( - Builder(builder: screen.build), - debugger: debuggerController, - ), - ); + await pumpDebuggerScreen(tester); + (serviceConnection.serviceManager.isolateManager as FakeIsolateManager) .setMainIsolatePausedState(true); await tester.pump(); @@ -121,12 +115,7 @@ void main() { when(debuggerController.selectedStackFrame) .thenReturn(stackFrameNotifier); - await tester.pumpWidget( - wrapWithControllers( - Builder(builder: screen.build), - debugger: debuggerController, - ), - ); + await pumpDebuggerScreen(tester); await tester.pumpAndSettle(); // The first stack frame is visible: @@ -193,3 +182,22 @@ final _stackFrame2 = StackFrameAndSourcePosition( ), position: const SourcePosition(line: _stackFrame2Line, column: 1), ); + +Finder findDebuggerButtonWithTitle(String title) => find.byWidgetPredicate( + (Widget widget) => widget is DebuggerButton && widget.title == title, + ); + +Finder findStackFrameWithText(String text) => find.byWidgetPredicate( + (Widget widget) => + widget is RichText && widget.text.toPlainText().contains(text), + ); + +bool gutterItemForLineIsVisible(int lineNumber) { + final gutterItems = find.byType(GutterItem); + final firstGutterItem = getWidgetFromFinder(gutterItems.first); + final lastGutterItem = getWidgetFromFinder(gutterItems.last); + final lineRange = + Range(firstGutterItem.lineNumber, lastGutterItem.lineNumber); + + return lineRange.contains(lineNumber); +} diff --git a/packages/devtools_app/test/debugger/debugger_screen_variables_test.dart b/packages/devtools_app/test/debugger/debugger_screen_variables_test.dart index ca6d5fb34fe..12b9cbcb3f7 100644 --- a/packages/devtools_app/test/debugger/debugger_screen_variables_test.dart +++ b/packages/devtools_app/test/debugger/debugger_screen_variables_test.dart @@ -55,7 +55,7 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + const DebuggerWindows(), debugger: controller, ), ); diff --git a/packages/devtools_app/test/debugger/debugger_scripts_test.dart b/packages/devtools_app/test/debugger/debugger_scripts_test.dart index a7394e14bb6..caf3b58a321 100644 --- a/packages/devtools_app/test/debugger/debugger_scripts_test.dart +++ b/packages/devtools_app/test/debugger/debugger_scripts_test.dart @@ -49,7 +49,10 @@ void main() { ) async { await tester.pumpWidget( wrapWithControllers( - const DebuggerScreenBody(), + DebuggerSourceAndControls( + shownFirstScript: () => true, + setShownFirstScript: (_) {}, + ), debugger: controller, ), ); diff --git a/packages/devtools_app/test/extensions/extension_settings_test.dart b/packages/devtools_app/test/extensions/extension_settings_test.dart index 74baa676dbb..cdd94436b56 100644 --- a/packages/devtools_app/test/extensions/extension_settings_test.dart +++ b/packages/devtools_app/test/extensions/extension_settings_test.dart @@ -34,7 +34,7 @@ void main() { ExtensionService, await createMockExtensionServiceWithDefaults([]), ); - await tester.pumpWidget(wrap(dialog)); + await tester.pumpWidget(wrapSimple(dialog)); expect(find.text('DevTools Extensions'), findsOneWidget); expect( find.textContaining('Extensions are provided by the pub packages'), @@ -49,7 +49,7 @@ void main() { testWidgets( 'builds dialog with available extensions', (WidgetTester tester) async { - await tester.pumpWidget(wrap(dialog)); + await tester.pumpWidget(wrapSimple(dialog)); expect(find.text('DevTools Extensions'), findsOneWidget); expect( find.textContaining('Extensions are provided by the pub packages'), @@ -70,7 +70,7 @@ void main() { testWidgets( 'pressing toggle buttons makes calls to the $ExtensionService', (WidgetTester tester) async { - await tester.pumpWidget(wrap(dialog)); + await tester.pumpWidget(wrapSimple(dialog)); expect( extensionService.enabledStateListenable(barExtension.name).value, @@ -141,7 +141,7 @@ void main() { ExtensionEnabledState.disabled, ); - await tester.pumpWidget(wrap(dialog)); + await tester.pumpWidget(wrapSimple(dialog)); await expectLater( find.byWidget(dialog), matchesDevToolsGolden( @@ -154,7 +154,7 @@ void main() { testWidgets( 'toggle buttons update for changes to value notifiers', (WidgetTester tester) async { - await tester.pumpWidget(wrap(dialog)); + await tester.pumpWidget(wrapSimple(dialog)); await expectLater( find.byWidget(dialog), matchesDevToolsGolden( @@ -175,7 +175,7 @@ void main() { enable: false, ); - await tester.pumpWidget(wrap(dialog)); + await tester.pumpWidget(wrapSimple(dialog)); await expectLater( find.byWidget(dialog), matchesDevToolsGolden( diff --git a/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart b/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart index 41e2fd97c35..b8809c012c5 100644 --- a/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart +++ b/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart @@ -21,7 +21,7 @@ void main() { WidgetTester tester, ) async { await tester.pumpWidget( - wrap(EventDetails(selectedEvent)), + wrapSimple(EventDetails(selectedEvent)), ); expect(find.byType(EventDetails), findsOneWidget); } @@ -85,7 +85,7 @@ void main() { EventSummary eventSummary; testWidgets('event with connected events', (WidgetTester tester) async { eventSummary = EventSummary(asyncEventWithInstantChildren); - await tester.pumpWidget(wrap(eventSummary)); + await tester.pumpWidget(wrapSimple(eventSummary)); expect(find.byType(EventSummary), findsOneWidget); expect(find.text('Time: 29.1 ms'), findsOneWidget); expect(find.text('Thread id: 19333'), findsOneWidget); @@ -97,7 +97,7 @@ void main() { testWidgets('event without connected events', (WidgetTester tester) async { eventSummary = EventSummary(goldenUiTimelineEvent); - await tester.pumpWidget(wrap(eventSummary)); + await tester.pumpWidget(wrapSimple(eventSummary)); expect(find.byType(EventSummary), findsOneWidget); expect(find.text('Time: 1.6 ms'), findsOneWidget); expect(find.text('Thread id: 1'), findsOneWidget); @@ -109,7 +109,7 @@ void main() { testWidgets('event with args', (WidgetTester tester) async { eventSummary = EventSummary(goldenRasterTimelineEvent); - await tester.pumpWidget(wrap(eventSummary)); + await tester.pumpWidget(wrapSimple(eventSummary)); expect(find.byType(EventSummary), findsOneWidget); expect(find.text('Time: 28.4 ms'), findsOneWidget); expect(find.text('Thread id: 2'), findsOneWidget); @@ -121,7 +121,7 @@ void main() { testWidgets('event without args', (WidgetTester tester) async { eventSummary = EventSummary(goldenUiTimelineEvent); - await tester.pumpWidget(wrap(eventSummary)); + await tester.pumpWidget(wrapSimple(eventSummary)); expect(find.byType(EventSummary), findsOneWidget); expect(find.text('Time: 1.6 ms'), findsOneWidget); expect(find.text('Thread id: 1'), findsOneWidget); diff --git a/packages/devtools_app/test/primitives/utils_test.dart b/packages/devtools_app/test/primitives/utils_test.dart index e8f018c4a92..b8b320fd8ee 100644 --- a/packages/devtools_app/test/primitives/utils_test.dart +++ b/packages/devtools_app/test/primitives/utils_test.dart @@ -1343,7 +1343,7 @@ void main() { value: controller, child: Builder( builder: (context) { - return wrap( + return wrapSimple( const TestStatefulWidget(), ); }, diff --git a/packages/devtools_app/test/shared/editable_list_test.dart b/packages/devtools_app/test/shared/editable_list_test.dart index 66d1ad2193f..4ff5c435bc4 100644 --- a/packages/devtools_app/test/shared/editable_list_test.dart +++ b/packages/devtools_app/test/shared/editable_list_test.dart @@ -30,7 +30,7 @@ void main() { windowSize, (WidgetTester tester) async { await tester.pumpWidget( - wrap( + wrapSimple( EditableList( entries: entries([]), textFieldLabel: label, @@ -59,7 +59,7 @@ void main() { gaScreen: '', ); await tester.pumpWidget( - wrap(widget), + wrapSimple(widget), ); final actionBar = find.byType(EditableListActionBar); @@ -94,7 +94,7 @@ void main() { gaScreen: '', ); await tester.pumpWidget( - wrap(widget), + wrapSimple(widget), ); final refreshButton = find.byType(RefreshButton); @@ -121,7 +121,7 @@ void main() { gaScreen: '', ); await tester.pumpWidget( - wrap(widget), + wrapSimple(widget), ); final removeButton = find.byType(EditableListRemoveDirectoryButton); @@ -150,7 +150,7 @@ void main() { gaScreen: '', ); await tester.pumpWidget( - wrap(widget), + wrapSimple(widget), ); final copyButton = find.byType(EditableListCopyDirectoryButton); @@ -175,7 +175,7 @@ void main() { gaScreen: '', ); await tester.pumpWidget( - wrap(widget), + wrapSimple(widget), ); final actionBar = find.byType(EditableListActionBar); @@ -208,7 +208,7 @@ void main() { gaScreen: '', ); await tester.pumpWidget( - wrap(widget), + wrapSimple(widget), ); final removeButton = find.byType(EditableListRemoveDirectoryButton); diff --git a/packages/devtools_app/test/shared/scaffold_debugger_test.dart b/packages/devtools_app/test/shared/scaffold_debugger_test.dart index 4cc11875bf5..935f0dfd3c4 100644 --- a/packages/devtools_app/test/shared/scaffold_debugger_test.dart +++ b/packages/devtools_app/test/shared/scaffold_debugger_test.dart @@ -69,6 +69,7 @@ void main() { await tester.pumpWidget( wrapWithControllers( DevToolsScaffold( + page: DebuggerScreen.id, screens: [ _TestScreen( DebuggerScreen.id, diff --git a/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart b/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart index 2add494131b..c5394a8f3e8 100644 --- a/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart +++ b/packages/devtools_app/test/shared/scaffold_debugging_controls_test.dart @@ -76,7 +76,10 @@ void main() { await tester.pumpWidget( wrapWithControllers( - DevToolsScaffold(screens: const [_screen1, _screen2]), + DevToolsScaffold( + page: _screen1.screenId, + screens: const [_screen1, _screen2], + ), debugger: mockDebuggerController, analytics: AnalyticsController(enabled: false, firstRun: false), releaseNotes: ReleaseNotesController(), diff --git a/packages/devtools_app/test/shared/scaffold_no_app_test.dart b/packages/devtools_app/test/shared/scaffold_no_app_test.dart index 2cf9c10023e..59317d14bb1 100644 --- a/packages/devtools_app/test/shared/scaffold_no_app_test.dart +++ b/packages/devtools_app/test/shared/scaffold_no_app_test.dart @@ -58,7 +58,10 @@ void main() { when(mockServiceManager.connectedAppInitialized).thenReturn(false); await tester.pumpWidget( wrapScaffold( - DevToolsScaffold(screens: const [_screen1, _screen2]), + DevToolsScaffold( + page: _screen1.screenId, + screens: const [_screen1, _screen2], + ), ), ); expect(find.byKey(_k1), findsOneWidget); diff --git a/packages/devtools_app/test/shared/scaffold_profile_test.dart b/packages/devtools_app/test/shared/scaffold_profile_test.dart index dfd3b0e8e43..c7231653345 100644 --- a/packages/devtools_app/test/shared/scaffold_profile_test.dart +++ b/packages/devtools_app/test/shared/scaffold_profile_test.dart @@ -69,7 +69,10 @@ void main() { await tester.pumpWidget( wrapWithControllers( - DevToolsScaffold(screens: const [_screen1, _screen2]), + DevToolsScaffold( + page: _screen1.screenId, + screens: const [_screen1, _screen2], + ), debugger: mockDebuggerController, analytics: AnalyticsController(enabled: false, firstRun: false), releaseNotes: ReleaseNotesController(), diff --git a/packages/devtools_app/test/shared/scaffold_test.dart b/packages/devtools_app/test/shared/scaffold_test.dart index 0e7ca45dcad..f5e0a3fd43b 100644 --- a/packages/devtools_app/test/shared/scaffold_test.dart +++ b/packages/devtools_app/test/shared/scaffold_test.dart @@ -58,6 +58,7 @@ void main() { await tester.pumpWidget( wrapScaffold( DevToolsScaffold( + page: _screen1.screenId, screens: const [_screen1, _screen2, _screen3, _screen4, _screen5], ), ), @@ -81,6 +82,7 @@ void main() { await tester.pumpWidget( wrapScaffold( DevToolsScaffold( + page: _screen1.screenId, screens: const [_screen1, _screen2, _screen3, _screen4, _screen5], ), ), @@ -104,6 +106,7 @@ void main() { await tester.pumpWidget( wrapScaffold( DevToolsScaffold( + page: _screen1.screenId, screens: const [_screen1, _screen2, _screen3, _screen4, _screen5], ), ), @@ -154,7 +157,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( wrapScaffold( - DevToolsScaffold(screens: const [_screen1]), + DevToolsScaffold(page: _screen1.screenId, screens: const [_screen1]), ), ); expect(find.byKey(_k1), findsOneWidget); @@ -165,7 +168,10 @@ void main() { testWidgets('displays only the selected tab', (WidgetTester tester) async { await tester.pumpWidget( wrapScaffold( - DevToolsScaffold(screens: const [_screen1, _screen2]), + DevToolsScaffold( + page: _screen1.screenId, + screens: const [_screen1, _screen2], + ), ), ); expect(find.byKey(_k1), findsOneWidget); diff --git a/packages/devtools_app/test/test_infra/goldens/codeview_scrollbars.png b/packages/devtools_app/test/test_infra/goldens/codeview_scrollbars.png index 42d92136081..a8be121ec61 100644 Binary files a/packages/devtools_app/test/test_infra/goldens/codeview_scrollbars.png and b/packages/devtools_app/test/test_infra/goldens/codeview_scrollbars.png differ diff --git a/packages/devtools_app/test/vm_developer/object_inspector/class_hierarchy_explorer_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/class_hierarchy_explorer_test.dart index ba5268c098f..289f242c441 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/class_hierarchy_explorer_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/class_hierarchy_explorer_test.dart @@ -103,7 +103,7 @@ void main() { (tester) async { final controller = objectInspectorViewController.classHierarchyController; await tester.pumpWidget( - wrap( + wrapSimple( ClassHierarchyExplorer( controller: objectInspectorViewController, ), diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart index bbe261e210e..371700a65a0 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart @@ -227,7 +227,7 @@ void main() { windowSize, (WidgetTester tester) async { await tester.pumpWidget( - wrap( + wrapSimple( VmCodeDisplay( code: mockCodeObject, controller: ObjectInspectorViewController(), diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_object_pool_display_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_object_pool_display_test.dart index 496f85b2f02..15711cf9f6a 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/vm_object_pool_display_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_object_pool_display_test.dart @@ -86,7 +86,7 @@ void main() { windowSize, (WidgetTester tester) async { await tester.pumpWidget( - wrap( + wrapSimple( VmObjectPoolDisplay( objectPool: mockObjectPool, controller: ObjectInspectorViewController(), diff --git a/packages/devtools_app_shared/lib/src/service/service_manager.dart b/packages/devtools_app_shared/lib/src/service/service_manager.dart index 7ba22ea6fe6..31da582de82 100644 --- a/packages/devtools_app_shared/lib/src/service/service_manager.dart +++ b/packages/devtools_app_shared/lib/src/service/service_manager.dart @@ -104,9 +104,7 @@ class ServiceManager { VM? vm; String? sdkVersion; - bool get hasService => service != null; - - bool get hasConnection => hasService && connectedApp != null; + bool get hasConnection => service != null && connectedApp != null; bool get connectedAppInitialized => hasConnection && connectedApp!.connectedAppInitialized; diff --git a/packages/devtools_app_shared/lib/src/utils/utils.dart b/packages/devtools_app_shared/lib/src/utils/utils.dart index 2e2847378c6..fc4c03240d4 100644 --- a/packages/devtools_app_shared/lib/src/utils/utils.dart +++ b/packages/devtools_app_shared/lib/src/utils/utils.dart @@ -60,7 +60,10 @@ final class ImmediateValueNotifier extends ValueNotifier { } } -Future whenValueNonNull(ValueListenable listenable) { +Future whenValueNonNull( + ValueListenable listenable, { + Duration? timeout, +}) { if (listenable.value != null) return Future.value(listenable.value); final completer = Completer(); void listener() { @@ -72,6 +75,10 @@ Future whenValueNonNull(ValueListenable listenable) { } listenable.addListener(listener); + + if (timeout != null) { + return completer.future.timeout(timeout); + } return completer.future; } diff --git a/packages/devtools_shared/lib/src/extensions/extension_manager.dart b/packages/devtools_shared/lib/src/extensions/extension_manager.dart index 43226b53076..4ef7f4ea993 100644 --- a/packages/devtools_shared/lib/src/extensions/extension_manager.dart +++ b/packages/devtools_shared/lib/src/extensions/extension_manager.dart @@ -88,11 +88,11 @@ class ExtensionsManager { ); try { - final pluginConfig = DevToolsExtensionConfig.parse({ + final extensionConfig = DevToolsExtensionConfig.parse({ ...config, DevToolsExtensionConfig.pathKey: location, }); - devtoolsExtensions.add(pluginConfig); + devtoolsExtensions.add(extensionConfig); } on StateError catch (e) { print(e.message); continue; diff --git a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart index d65850b07b2..52f5175110f 100644 --- a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart +++ b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart @@ -34,7 +34,6 @@ class FakeServiceConnectionManager extends Fake service: service, hasConnection: hasConnection, connectedAppInitialized: connectedAppInitialized, - hasService: hasService, availableLibraries: availableLibraries, availableServices: availableServices, onVmServiceOpened: resolvedUriManager.vmServiceOpened, @@ -102,7 +101,6 @@ class FakeServiceManager extends Fake VmServiceWrapper? service, this.hasConnection = true, this.connectedAppInitialized = true, - this.hasService = true, this.availableServices = const [], this.availableLibraries = const [], this.onVmServiceOpened, @@ -167,9 +165,6 @@ class FakeServiceManager extends Fake @override bool hasConnection; - @override - bool hasService; - @override bool connectedAppInitialized; diff --git a/packages/devtools_test/lib/src/wrappers.dart b/packages/devtools_test/lib/src/wrappers.dart index c33f221bfc0..4f66d6a4562 100644 --- a/packages/devtools_test/lib/src/wrappers.dart +++ b/packages/devtools_test/lib/src/wrappers.dart @@ -14,7 +14,8 @@ import 'package:provider/provider.dart'; /// for widget state to be preserved. final _testNavigatorKey = GlobalKey(); -/// Wraps [widget] with the build context it needs to load in a test. +/// Wraps [widget] with the build context it needs to load in a test as well as +/// the [DevToolsRouterDelegate]. /// /// This includes a [MaterialApp] to provide context like [Theme.of], a /// [Material] to support elements like [TextField] that draw ink effects, and a @@ -49,6 +50,33 @@ Widget wrap(Widget widget) { ); } +/// Wraps [widget] with the build context it needs to load in a test. +/// +/// This includes a [MaterialApp] to provide context like [Theme.of], a +/// [Material] to support elements like [TextField] that draw ink effects, and a +/// [Directionality] to support [RenderFlex] widgets like [Row] and [Column]. +Widget wrapSimple(Widget widget) { + return MaterialApp( + theme: themeFor( + isDarkTheme: false, + ideTheme: IdeTheme(), + theme: ThemeData( + useMaterial3: true, + colorScheme: lightColorScheme, + ), + ), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Provider.value( + value: HoverCardController(), + child: widget, + ), + ), + ), + ); +} + Widget wrapWithControllers( Widget widget, { InspectorController? inspector, @@ -62,6 +90,7 @@ Widget wrapWithControllers( AnalyticsController? analytics, ReleaseNotesController? releaseNotes, VMDeveloperToolsController? vmDeveloperTools, + bool includeRouter = true, }) { final _providers = [ if (inspector != null) @@ -82,14 +111,13 @@ Widget wrapWithControllers( if (vmDeveloperTools != null) Provider.value(value: vmDeveloperTools), ]; - return wrap( - wrapWithNotifications( - MultiProvider( - providers: _providers, - child: widget, - ), + final child = wrapWithNotifications( + MultiProvider( + providers: _providers, + child: widget, ), ); + return includeRouter ? wrap(child) : wrapSimple(child); } Widget wrapWithNotifications(Widget child) {