diff --git a/packages/devtools_app/lib/src/shared/charts/treemap.dart b/packages/devtools_app/lib/src/shared/charts/treemap.dart index dae9c34600e..7580a0e4036 100644 --- a/packages/devtools_app/lib/src/shared/charts/treemap.dart +++ b/packages/devtools_app/lib/src/shared/charts/treemap.dart @@ -401,6 +401,7 @@ class _TreemapState extends State { richMessage: hoveredNode?.displayTextSpan( defaultTextStyle: defaultTextStyle, + color: Theme.of(context).colorScheme.tooltipTextColor, ) ?? const TextSpan(text: ''), waitDuration: tooltipWaitLong, diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index b3097459877..669450b922b 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -90,7 +90,8 @@ To learn more about DevTools, check out the ## App size tool updates -TODO: Remove this section if there are not any updates. +* Fixed treemap tooltip text color across light and dark theme + [#10035](https://github.com/flutter/devtools/pull/10035) ## Deep links tool updates diff --git a/packages/devtools_app/test/shared/charts/treemap_test.dart b/packages/devtools_app/test/shared/charts/treemap_test.dart index f5d24be6baa..2324a985448 100644 --- a/packages/devtools_app/test/shared/charts/treemap_test.dart +++ b/packages/devtools_app/test/shared/charts/treemap_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. import 'dart:convert'; +import 'dart:ui'; import 'package:devtools_app/src/service/service_manager.dart'; import 'package:devtools_app/src/shared/charts/treemap.dart'; @@ -29,7 +30,11 @@ void main() { } // Pump treemap widget with tree built with test data. - Future pumpTreemapWidget(WidgetTester tester, Key treemapKey) async { + Future pumpTreemapWidget( + WidgetTester tester, + Key treemapKey, { + bool isDarkTheme = false, + }) async { await tester.pumpWidget( wrap( LayoutBuilder( @@ -45,6 +50,7 @@ void main() { ); }, ), + isDarkTheme: isDarkTheme, ), ); await tester.pumpAndSettle(); @@ -221,6 +227,52 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 2)); }, skip: kIsWeb); }); + + group('Treemap tooltip text color', () { + setUp(() { + root = TreemapNode(name: 'root') + ..addChild( + TreemapNode(name: 'parent', byteSize: 1000) + ..addChild(TreemapNode(name: 'leaf_1', byteSize: 500)) + ..addChild(TreemapNode(name: 'leaf_2', byteSize: 500)), + ); + }); + for (final isDarkTheme in [false, true]) { + final themeName = isDarkTheme ? 'dark' : 'light'; + testWidgetsWithWindowSize( + 'tooltip has correct text color in $themeName theme', + windowSize, + (WidgetTester tester) async { + const treemapKey = Key('Treemap'); + await pumpTreemapWidget(tester, treemapKey, isDarkTheme: isDarkTheme); + + final tooltipFinder = find.byWidgetPredicate( + (widget) => widget is DevToolsTooltip && widget.richMessage != null, + ); + final customPaint = find.descendant( + of: tooltipFinder, + matching: find.byType(CustomPaint), + ); + + // Hover over the painted cell. + final gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await gesture.moveTo(tester.getCenter(customPaint)); + await tester.pumpAndSettle(); + + final tooltip = tester.widget(tooltipFinder); + final textSpan = tooltip.richMessage as TextSpan?; + final expectedColor = isDarkTheme ? Colors.black : Colors.white; + expect(textSpan, isNotNull); + expect(textSpan!.text, isNotEmpty); + expect(textSpan.style?.color, equals(expectedColor)); + }, + ); + } + }); } Future _loadSnapshotJsonAsTree(String snapshotJson) async { diff --git a/packages/devtools_test/lib/src/helpers/wrappers.dart b/packages/devtools_test/lib/src/helpers/wrappers.dart index 0a70c24940c..fc7f002abee 100644 --- a/packages/devtools_test/lib/src/helpers/wrappers.dart +++ b/packages/devtools_test/lib/src/helpers/wrappers.dart @@ -21,20 +21,32 @@ import 'package:provider/provider.dart'; /// for widget state to be preserved. final _testNavigatorKey = GlobalKey(); +/// Returns the [ThemeData] used by [wrap] and [wrapSimple] test helper widgets. +ThemeData _testTheme({required bool isDarkTheme}) { + return themeFor( + isDarkTheme: isDarkTheme, + ideTheme: IdeTheme(), + theme: ThemeData( + useMaterial3: true, + colorScheme: isDarkTheme ? darkColorScheme : lightColorScheme, + ), + ); +} + /// 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 /// [Directionality] to support [RenderFlex] widgets like [Row] and [Column]. -Widget wrap(Widget widget, {DevToolsQueryParams? queryParams}) { +Widget wrap( + Widget widget, { + DevToolsQueryParams? queryParams, + bool isDarkTheme = false, +}) { setGlobal(GlobalKey, _testNavigatorKey); return MaterialApp.router( - theme: themeFor( - isDarkTheme: false, - ideTheme: IdeTheme(), - theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), - ), + theme: _testTheme(isDarkTheme: isDarkTheme), routerDelegate: DevToolsRouterDelegate.test( (context, page, args, state) => MaterialPage( child: Material( @@ -62,13 +74,9 @@ Widget wrap(Widget widget, {DevToolsQueryParams? queryParams}) { /// 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) { +Widget wrapSimple(Widget widget, {bool isDarkTheme = false}) { return MaterialApp( - theme: themeFor( - isDarkTheme: false, - ideTheme: IdeTheme(), - theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), - ), + theme: _testTheme(isDarkTheme: isDarkTheme), home: Material( child: Directionality( textDirection: TextDirection.ltr, @@ -99,6 +107,7 @@ Widget wrapWithControllers( AccessibilityController? accessibility, bool includeRouter = true, DevToolsQueryParams? queryParams, + bool isDarkTheme = false, }) { // The [ScreenControllers] class uses the [OfflineDataController] class to // determine which type of controller should be returned from @@ -192,8 +201,8 @@ Widget wrapWithControllers( child = MultiProvider(providers: providers, child: child); } return includeRouter - ? wrap(child, queryParams: queryParams) - : wrapSimple(child); + ? wrap(child, queryParams: queryParams, isDarkTheme: isDarkTheme) + : wrapSimple(child, isDarkTheme: isDarkTheme); } Widget wrapWithNotifications(Widget child) {