diff --git a/packages/devtools_app/lib/src/analytics/constants.dart b/packages/devtools_app/lib/src/analytics/constants.dart index 105e62d4e3e..c5fbcbb6f56 100644 --- a/packages/devtools_app/lib/src/analytics/constants.dart +++ b/packages/devtools_app/lib/src/analytics/constants.dart @@ -60,6 +60,7 @@ const String trackLayouts = 'trackLayouts'; const disableClipLayersOption = 'disableClipLayers'; const disableOpacityLayersOption = 'disableOpacityLayers'; const disablePhysicalShapeLayersOption = 'disablePhysicalShapeLayers'; +const shaderCompilationDocsTooltipLink = 'shaderCompilationDocsTooltipLink'; // CPU profiler UX actions: const profileGranularityPrefix = 'profileGranularity'; diff --git a/packages/devtools_app/lib/src/app.dart b/packages/devtools_app/lib/src/app.dart index 49b2cbc837c..0513c0eee0b 100644 --- a/packages/devtools_app/lib/src/app.dart +++ b/packages/devtools_app/lib/src/app.dart @@ -528,18 +528,16 @@ class DevToolsAboutDialog extends StatelessWidget { } Widget _createFeedbackLink(BuildContext context) { - final reportIssuesLink = devToolsExtensionPoints.issueTrackerLink(); - return InkWell( - onTap: () async { - ga.select( - analytics_constants.devToolsMain, - analytics_constants.feedbackLink, - ); - await launchUrl(reportIssuesLink.url, context); - }, - child: Text( - reportIssuesLink.display, - style: Theme.of(context).linkTextStyle, + return RichText( + text: LinkTextSpan( + link: devToolsExtensionPoints.issueTrackerLink(), + context: context, + onTap: () { + ga.select( + analytics_constants.devToolsMain, + analytics_constants.feedbackLink, + ); + }, ), ); } diff --git a/packages/devtools_app/lib/src/banner_messages.dart b/packages/devtools_app/lib/src/banner_messages.dart index b20e938f950..98a8fddbdd5 100644 --- a/packages/devtools_app/lib/src/banner_messages.dart +++ b/packages/devtools_app/lib/src/banner_messages.dart @@ -3,12 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'common_widgets.dart'; -import 'config_specific/launch_url/launch_url.dart'; import 'globals.dart'; import 'screen.dart'; import 'theme.dart'; @@ -243,17 +241,13 @@ Relaunch your application with the '--profile' argument, or ''', fontSize: defaultFontSize, ), ), - TextSpan( - text: 'relaunch in profile mode from VS Code or IntelliJ', - style: TextStyle( - decoration: TextDecoration.underline, - color: _BannerError.linkColor, - fontSize: defaultFontSize, + LinkTextSpan( + link: const Link( + display: 'relaunch in profile mode from VS Code or IntelliJ', + url: _runInProfileModeDocsUrl, ), - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(_runInProfileModeDocsUrl, context); - }, + context: context, + style: Theme.of(context).errorMessageLinkStyle, ), TextSpan( text: '.', @@ -320,17 +314,13 @@ To pre-compile shaders, see the instructions at ''', fontSize: defaultFontSize, ), ), - TextSpan( - text: preCompileShadersDocsUrl, - style: TextStyle( - decoration: TextDecoration.underline, - color: _BannerError.linkColor, - fontSize: defaultFontSize, + LinkTextSpan( + link: const Link( + display: preCompileShadersDocsUrl, + url: preCompileShadersDocsUrl, ), - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(preCompileShadersDocsUrl, context); - }, + context: context, + style: Theme.of(context).errorMessageLinkStyle, ), TextSpan( text: '.', @@ -365,17 +355,13 @@ You are opting in to a high CPU sampling rate. This may affect the performance o fontSize: defaultFontSize, ), ), - TextSpan( - text: 'documentation', - style: TextStyle( - decoration: TextDecoration.underline, - color: _BannerWarning.linkColor, - fontSize: defaultFontSize, + LinkTextSpan( + link: const Link( + display: 'documentation', + url: _profileGranularityDocsUrl, ), - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(_profileGranularityDocsUrl, context); - }, + context: context, + style: Theme.of(context).warningMessageLinkStyle, ), TextSpan( text: ' to understand the trade-offs associated with this setting.', @@ -408,17 +394,13 @@ For the most accurate absolute memory stats, relaunch your application with the fontSize: defaultFontSize, ), ), - TextSpan( - text: 'relaunch in profile mode from VS Code or IntelliJ', - style: TextStyle( - decoration: TextDecoration.underline, - color: _BannerWarning.linkColor, - fontSize: defaultFontSize, + LinkTextSpan( + link: const Link( + display: 'relaunch in profile mode from VS Code or IntelliJ', + url: _runInProfileModeDocsUrl, ), - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl(_runInProfileModeDocsUrl, context); - }, + context: context, + style: Theme.of(context).warningMessageLinkStyle, ), TextSpan( text: '.', @@ -454,3 +436,17 @@ void maybePushDebugModeMemoryMessage( .addMessage(DebugModeMemoryMessage(screenId).build(context)); } } + +extension BannerMessageThemeExtension on ThemeData { + TextStyle get warningMessageLinkStyle => TextStyle( + decoration: TextDecoration.underline, + color: _BannerWarning.linkColor, + fontSize: defaultFontSize, + ); + + TextStyle get errorMessageLinkStyle => TextStyle( + decoration: TextDecoration.underline, + color: _BannerError.linkColor, + fontSize: defaultFontSize, + ); +} diff --git a/packages/devtools_app/lib/src/common_widgets.dart b/packages/devtools_app/lib/src/common_widgets.dart index 89f2835d9af..7e2a75e4755 100644 --- a/packages/devtools_app/lib/src/common_widgets.dart +++ b/packages/devtools_app/lib/src/common_widgets.dart @@ -6,8 +6,11 @@ import 'dart:convert'; import 'dart:math'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'analytics/analytics.dart' as ga; +import 'config_specific/launch_url/launch_url.dart'; import 'globals.dart'; import 'scaffold.dart'; import 'theme.dart'; @@ -627,17 +630,20 @@ class DevToolsTooltip extends StatelessWidget { @override Widget build(BuildContext context) { + TextStyle style = textStyle; + if (richMessage == null) { + style = TextStyle( + color: Theme.of(context).colorScheme.tooltipTextColor, + fontSize: defaultFontSize, + ); + } return Tooltip( message: message, richMessage: richMessage, waitDuration: waitDuration, preferBelow: preferBelow, padding: padding, - textStyle: textStyle ?? - TextStyle( - color: Theme.of(context).colorScheme.tooltipTextColor, - fontSize: defaultFontSize, - ), + textStyle: style, decoration: decoration, child: child, ); @@ -1310,6 +1316,71 @@ class FormattedJson extends StatelessWidget { } } +class MoreInfoLink extends StatelessWidget { + const MoreInfoLink({ + Key key, + @required this.url, + @required this.gaScreenName, + @required this.gaSelectedItemDescription, + }) : super(key: key); + + final String url; + + final String gaScreenName; + + final String gaSelectedItemDescription; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return InkWell( + onTap: () => _onLinkTap(context), + borderRadius: BorderRadius.circular(defaultBorderRadius), + child: Padding( + padding: const EdgeInsets.all(denseSpacing), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + 'More info', + style: theme.linkTextStyle, + ), + const SizedBox(width: densePadding), + Icon( + Icons.launch, + size: tooltipIconSize, + color: theme.colorScheme.toggleButtonsTitle, + ) + ], + ), + ), + ); + } + + void _onLinkTap(BuildContext context) { + launchUrl(url, context); + ga.select(gaScreenName, gaSelectedItemDescription); + } +} + +class LinkTextSpan extends TextSpan { + LinkTextSpan({ + @required Link link, + @required BuildContext context, + TextStyle style, + VoidCallback onTap, + }) : super( + text: link.display, + style: style ?? Theme.of(context).linkTextStyle, + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (onTap != null) onTap(); + await launchUrl(link.url, context); + }, + ); +} + class Link { const Link({this.display, this.url}); diff --git a/packages/devtools_app/lib/src/debugger/codeview.dart b/packages/devtools_app/lib/src/debugger/codeview.dart index b2736399728..d4c4d5db330 100644 --- a/packages/devtools_app/lib/src/debugger/codeview.dart +++ b/packages/devtools_app/lib/src/debugger/codeview.dart @@ -21,6 +21,7 @@ import '../globals.dart'; import '../history_viewport.dart'; import '../theme.dart'; import '../ui/colors.dart'; +import '../ui/hover.dart'; import '../ui/search.dart'; import '../ui/utils.dart'; import '../utils.dart'; @@ -29,7 +30,6 @@ import 'common.dart'; import 'debugger_controller.dart'; import 'debugger_model.dart'; import 'file_search.dart'; -import 'hover.dart'; import 'key_sets.dart'; import 'program_explorer_model.dart'; import 'variables.dart'; diff --git a/packages/devtools_app/lib/src/device_dialog.dart b/packages/devtools_app/lib/src/device_dialog.dart index a1bdc425b35..bb7f71d71b7 100644 --- a/packages/devtools_app/lib/src/device_dialog.dart +++ b/packages/devtools_app/lib/src/device_dialog.dart @@ -52,20 +52,16 @@ class DeviceDialog extends StatelessWidget { final items = { 'Dart Version': version, 'CPU / OS': '${vm.targetCPU}$bits / ${vm.operatingSystem}', + if (flutterVersion != null) ...{ + 'Flutter Version': + '${flutterVersion.version} / ${flutterVersion.channel}', + 'Framework / Engine': '${flutterVersion.frameworkRevision} / ' + '${flutterVersion.engineRevision}', + }, + if (serviceManager.service.connectedUri != null) + 'VM Service Connection': serviceManager.service.connectedUri.toString(), }; - if (flutterVersion != null) { - items['Flutter Version'] = - '${flutterVersion.version} / ${flutterVersion.channel}'; - items['Framework / Engine'] = '${flutterVersion.frameworkRevision} / ' - '${flutterVersion.engineRevision}'; - } - - if (serviceManager.service.connectedUri != null) { - items['VM Service Connection'] = - serviceManager.service.connectedUri.toString(); - } - // TODO(kenz): set actions alignment to `spaceBetween` if // https://github.com/flutter/flutter/issues/69708 is fixed. return DevToolsDialog( diff --git a/packages/devtools_app/lib/src/inspector/diagnostics.dart b/packages/devtools_app/lib/src/inspector/diagnostics.dart index 009c507d9d2..cc604e006a2 100644 --- a/packages/devtools_app/lib/src/inspector/diagnostics.dart +++ b/packages/devtools_app/lib/src/inspector/diagnostics.dart @@ -6,10 +6,10 @@ import 'package:flutter/material.dart'; import '../debugger/debugger_controller.dart'; import '../debugger/debugger_model.dart'; -import '../debugger/hover.dart'; import '../debugger/variables.dart'; import '../globals.dart'; import '../theme.dart'; +import '../ui/hover.dart'; import '../ui/icons.dart'; import '../utils.dart'; import 'diagnostics_node.dart'; diff --git a/packages/devtools_app/lib/src/performance/flutter_frames_chart.dart b/packages/devtools_app/lib/src/performance/flutter_frames_chart.dart index e64cbc0b2da..e624c0a080e 100644 --- a/packages/devtools_app/lib/src/performance/flutter_frames_chart.dart +++ b/packages/devtools_app/lib/src/performance/flutter_frames_chart.dart @@ -17,6 +17,8 @@ import '../globals.dart'; import '../scaffold.dart'; import '../theme.dart'; import '../ui/colors.dart'; +import '../ui/hover.dart'; +import '../ui/utils.dart'; import '../utils.dart'; import 'performance_controller.dart'; import 'performance_model.dart'; @@ -330,9 +332,9 @@ class FlutterFramesChartItem extends StatelessWidget { child: Stack( children: [ // TODO(kenz): make tooltip to persist if the frame is selected. - DevToolsTooltip( - message: _tooltipText(frame, hasShaderJank), - padding: const EdgeInsets.all(denseSpacing), + FlutterFrameTooltip( + frame: frame, + hasShaderJank: hasShaderJank, child: Container( padding: const EdgeInsets.symmetric(horizontal: densePadding), color: selected ? colorScheme.selectedFrameBackgroundColor : null, @@ -357,28 +359,12 @@ class FlutterFramesChartItem extends StatelessWidget { color: defaultSelectionColor, height: selectedIndicatorHeight, ), - if (hasShaderJank) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - ShaderJankWarningIcon(), - ], - ), + if (hasShaderJank) const ShaderJankWarningIcon(), ], ), ); } - // TODO(kenz): Support a rich tooltip - // https://github.com/flutter/devtools/issues/3139 - String _tooltipText(FlutterFrame frame, bool hasShaderJank) { - return [ - 'UI: ${msText(frame.buildTime)}', - 'Raster: ${msText(frame.rasterTime)}', - if (hasShaderJank) 'Shader Compilation: ${msText(frame.shaderDuration)}', - ].join('\n'); - } - void _selectFrame() { if (frame != controller.selectedFrame.value) { // TODO(kenz): the shader time could be missing here if a frame is @@ -399,6 +385,103 @@ class FlutterFramesChartItem extends StatelessWidget { } } +class FlutterFrameTooltip extends StatelessWidget { + const FlutterFrameTooltip({ + Key key, + @required this.child, + @required this.frame, + @required this.hasShaderJank, + }) : super(key: key); + + final Widget child; + + final FlutterFrame frame; + + final bool hasShaderJank; + + static const double _moreInfoLinkWidth = 85.0; + + static const _textMeasurementBuffer = 4.0; + + @override + Widget build(BuildContext context) { + return HoverCardTooltip( + enabled: () => true, + onHover: (_) => _buildCardData(context), + child: child, + ); + } + + Future _buildCardData(BuildContext context) { + final textColor = Theme.of(context).colorScheme.toggleButtonsTitle; + final textStyle = TextStyle(color: textColor); + final uiText = 'UI: ${msText(frame.buildTime)}'; + final rasterText = 'Raster: ${msText(frame.rasterTime)}'; + final shaderText = hasShaderJank + ? 'Shader Compilation: ${msText(frame.shaderDuration)} -' + : ''; + return Future.value( + HoverCardData( + position: HoverCardPosition.element, + width: _calculateTooltipWidth([uiText, rasterText, shaderText]), + contents: Material( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + uiText, + style: textStyle, + ), + const SizedBox(height: densePadding), + Text( + rasterText, + style: textStyle, + ), + if (hasShaderJank) + Row( + children: [ + Icon( + Icons.subdirectory_arrow_right, + color: textColor, + size: defaultIconSizeBeforeScaling, + ), + Text( + shaderText, + style: textStyle, + ), + const MoreInfoLink( + url: preCompileShadersDocsUrl, + gaScreenName: analytics_constants.performance, + gaSelectedItemDescription: + analytics_constants.shaderCompilationDocsTooltipLink, + ), + ], + ), + ], + ), + ), + ), + ); + } + + double _calculateTooltipWidth(List lines) { + var maxWidth = 0.0; + for (final line in lines) { + final lineWidth = calculateTextSpanWidth(TextSpan(text: line)); + maxWidth = math.max(maxWidth, lineWidth); + } + // Add (2 * denseSpacing) for the card padding, and add + // [_textMeasurementBuffer] to account for slight variations in the measured + // text vs text displayed. + maxWidth += 2 * denseSpacing + _textMeasurementBuffer; + if (hasShaderJank) { + return maxWidth + defaultIconSizeBeforeScaling + _moreInfoLinkWidth; + } + return maxWidth; + } +} + class AverageFPS extends StatelessWidget { const AverageFPS({this.frames, this.displayRefreshRate}); diff --git a/packages/devtools_app/lib/src/profiler/profiler_screen.dart b/packages/devtools_app/lib/src/profiler/profiler_screen.dart index ccbaf5b193f..3f919913f10 100644 --- a/packages/devtools_app/lib/src/profiler/profiler_screen.dart +++ b/packages/devtools_app/lib/src/profiler/profiler_screen.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:vm_service/vm_service.dart' hide Stack; @@ -17,7 +16,6 @@ import '../auto_dispose_mixin.dart'; import '../banner_messages.dart'; import '../common_widgets.dart'; import '../config_specific/import_export/import_export.dart'; -import '../config_specific/launch_url/launch_url.dart'; import '../globals.dart'; import '../listenable.dart'; import '../notifications.dart'; @@ -200,14 +198,12 @@ class _ProfilerScreenBodyState extends State text: ''' \n\nIf you are attempting to profile on a real iOS device, you may be hitting a known issue. Try using this ''', ), - TextSpan( - text: 'workaround', - style: Theme.of(context).linkTextStyle, - recognizer: TapGestureRecognizer() - ..onTap = () async { - await launchUrl( - iosProfilerWorkaround, context); - }, + LinkTextSpan( + link: const Link( + display: 'workaround', + url: iosProfilerWorkaround, + ), + context: context, ), const TextSpan(text: '.'), ] diff --git a/packages/devtools_app/lib/src/status_line.dart b/packages/devtools_app/lib/src/status_line.dart index 9bebd9da475..0032d854135 100644 --- a/packages/devtools_app/lib/src/status_line.dart +++ b/packages/devtools_app/lib/src/status_line.dart @@ -10,7 +10,6 @@ import '../devtools.dart' as devtools; import 'analytics/analytics.dart' as ga; import 'analytics/constants.dart' as analytics_constants; import 'common_widgets.dart'; -import 'config_specific/launch_url/launch_url.dart'; import 'device_dialog.dart'; import 'globals.dart'; import 'info/info_controller.dart'; @@ -112,18 +111,19 @@ class StatusLine extends StatelessWidget { ) { final String docPageId = currentScreen.docPageId; if (docPageId != null) { - return InkWell( - onTap: () async { - final url = 'https://flutter.dev/devtools/$docPageId'; - ga.select( - currentScreen.screenId, - analytics_constants.documentationLink, - ); - await launchUrl(url, context); - }, - child: Text( - 'flutter.dev/devtools/$docPageId', - style: Theme.of(context).linkTextStyle, + return RichText( + text: LinkTextSpan( + link: Link( + display: 'flutter.dev/devtools/$docPageId', + url: 'https://flutter.dev/devtools/$docPageId', + ), + onTap: () { + ga.select( + currentScreen.screenId, + analytics_constants.documentationLink, + ); + }, + context: context, ), ); } else { diff --git a/packages/devtools_app/lib/src/theme.dart b/packages/devtools_app/lib/src/theme.dart index 06b095d7d9b..9c76b8e0534 100644 --- a/packages/devtools_app/lib/src/theme.dart +++ b/packages/devtools_app/lib/src/theme.dart @@ -145,10 +145,11 @@ double get smallButtonHeight => scaleByFontFactor(20.0); double get buttonMinWidth => scaleByFontFactor(36.0); -double get defaultIconSize => scaleByFontFactor(16.0); double get actionsIconSize => scaleByFontFactor(20.0); +double get defaultIconSize => scaleByFontFactor(defaultIconSizeBeforeScaling); double get tooltipIconSize => scaleByFontFactor(12.0); +const defaultIconSizeBeforeScaling = 16.0; const defaultSpacing = 16.0; const denseSpacing = 8.0; const denseModeDenseSpacing = 2.0; diff --git a/packages/devtools_app/lib/src/debugger/hover.dart b/packages/devtools_app/lib/src/ui/hover.dart similarity index 99% rename from packages/devtools_app/lib/src/debugger/hover.dart rename to packages/devtools_app/lib/src/ui/hover.dart index 8e176d4ac3c..a7d1dcc6a9b 100644 --- a/packages/devtools_app/lib/src/debugger/hover.dart +++ b/packages/devtools_app/lib/src/ui/hover.dart @@ -10,8 +10,8 @@ import 'package:flutter/material.dart'; import '../eval_on_dart_library.dart'; import '../theme.dart'; -import '../ui/utils.dart'; import '../utils.dart'; +import 'utils.dart'; /// Regex for valid Dart identifiers. final _identifier = RegExp(r'^[a-zA-Z0-9]|_|\$'); diff --git a/packages/devtools_app/lib/src/ui/service_extension_widgets.dart b/packages/devtools_app/lib/src/ui/service_extension_widgets.dart index d136d968df5..ff5fddcc530 100644 --- a/packages/devtools_app/lib/src/ui/service_extension_widgets.dart +++ b/packages/devtools_app/lib/src/ui/service_extension_widgets.dart @@ -9,10 +9,8 @@ import 'package:flutter/material.dart'; import '../analytics/analytics.dart' as ga; import '../auto_dispose_mixin.dart'; import '../common_widgets.dart'; -import '../config_specific/launch_url/launch_url.dart'; import '../config_specific/logger/logger.dart'; import '../core/message_bus.dart'; -import '../debugger/hover.dart'; import '../globals.dart'; import '../notifications.dart'; import '../scaffold.dart'; @@ -21,6 +19,7 @@ import '../service_manager.dart'; import '../service_registrations.dart'; import '../theme.dart'; import '../utils.dart'; +import 'hover.dart'; import 'label.dart'; /// Group of buttons where each button toggles the state of a VMService @@ -863,15 +862,6 @@ class ServiceExtensionRichTooltip extends StatelessWidget { static const double _tooltipWidth = 300.0; - void _onLinkTap(BuildContext context) { - launchUrl(description.tooltipUrl, context); - - ga.select( - description.gaScreenName, - description.gaItemTooltipLink, - ); - } - @override Widget build(BuildContext context) { return HoverCardTooltip( @@ -899,28 +889,10 @@ class ServiceExtensionRichTooltip extends StatelessWidget { ), Align( alignment: Alignment.bottomRight, - child: InkWell( - onTap: () => _onLinkTap(context), - borderRadius: BorderRadius.circular(defaultBorderRadius), - child: Padding( - padding: const EdgeInsets.all(denseSpacing), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - 'More info', - style: Theme.of(context).linkTextStyle, - ), - const SizedBox(width: densePadding), - Icon( - Icons.launch, - size: tooltipIconSize, - color: textColor, - ) - ], - ), - ), + child: MoreInfoLink( + url: description.tooltipUrl, + gaScreenName: description.gaScreenName, + gaSelectedItemDescription: description.gaItemTooltipLink, ), ), ], diff --git a/packages/devtools_app/test/device_dialog_test.dart b/packages/devtools_app/test/device_dialog_test.dart index 233d27cf303..ae34ceb4f58 100644 --- a/packages/devtools_app/test/device_dialog_test.dart +++ b/packages/devtools_app/test/device_dialog_test.dart @@ -55,10 +55,11 @@ void main() { await tester.pumpWidget(wrap(deviceDialog)); expect(find.text('Device Info'), findsOneWidget); - expect(findSubstring(deviceDialog, 'Dart Version'), findsOneWidget); - expect(findSubstring(deviceDialog, 'Flutter Version'), findsNothing); - expect( - findSubstring(deviceDialog, 'VM Service Connection'), findsOneWidget); + expect(find.text('Dart Version: '), findsOneWidget); + expect(find.text('CPU / OS: '), findsOneWidget); + expect(find.text('Flutter Version: '), findsNothing); + expect(find.text('Framework / Engine: '), findsNothing); + expect(find.text('VM Service Connection: '), findsOneWidget); }); testWidgetsWithWindowSize('builds dialog flutter', windowSize, @@ -77,10 +78,11 @@ void main() { await tester.pumpWidget(wrap(deviceDialog)); expect(find.text('Device Info'), findsOneWidget); - expect(findSubstring(deviceDialog, 'Dart Version'), findsOneWidget); - expect(findSubstring(deviceDialog, 'Flutter Version'), findsOneWidget); - expect( - findSubstring(deviceDialog, 'VM Service Connection'), findsOneWidget); + expect(find.text('Dart Version: '), findsOneWidget); + expect(find.text('CPU / OS: '), findsOneWidget); + expect(find.text('Flutter Version: '), findsOneWidget); + expect(find.text('Framework / Engine: '), findsOneWidget); + expect(find.text('VM Service Connection: '), findsOneWidget); }); }); @@ -117,8 +119,10 @@ void main() { await tester.pumpWidget(wrap(vmFlagsDialog)); expect(find.text('VM Flags'), findsOneWidget); - expect(findSubstring(vmFlagsDialog, 'flag 1 name'), findsOneWidget); - expect(findSubstring(vmFlagsDialog, 'flag 1 comment'), findsOneWidget); + expect(find.text('flag 1 name'), findsOneWidget); + final Text commentText = tester + .firstWidget(findSubstring(vmFlagsDialog, 'flag 1 comment')); + expect(commentText, isNotNull); }); }); } diff --git a/packages/devtools_app/test/hover_test.dart b/packages/devtools_app/test/hover_test.dart index 9a4fb52d1ec..ea74040e64b 100644 --- a/packages/devtools_app/test/hover_test.dart +++ b/packages/devtools_app/test/hover_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:devtools_app/src/debugger/hover.dart'; +import 'package:devtools_app/src/ui/hover.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/devtools_test/lib/utils.dart b/packages/devtools_test/lib/utils.dart index f43ed1b3e27..9e240bd2302 100644 --- a/packages/devtools_test/lib/utils.dart +++ b/packages/devtools_test/lib/utils.dart @@ -138,10 +138,11 @@ Finder findSubstring(Widget widget, String text) { if (widget is Text) { if (widget.data != null) return widget.data.contains(text); return widget.textSpan.toPlainText().contains(text); + } else if (widget is RichText) { + return widget.text.toPlainText().contains(text); } else if (widget is SelectableText) { if (widget.data != null) return widget.data.contains(text); } - return false; }); }