// Copyright 2014 The Flutter 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:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; const String tooltipText = 'TIP'; const double _customPaddingValue = 10.0; void _ensureTooltipVisible(GlobalKey key) { // This function uses "as dynamic"to defeat the static analysis. In general // you want to avoid using this style in your code, as it will cause the // analyzer to be unable to help you catch errors. // // In this case, we do it because we are trying to call internal methods of // the tooltip code in order to test it. Normally, the state of a tooltip is a // private class, but by using a GlobalKey we can get a handle to that object // and by using "as dynamic" we can bypass the analyzer's type checks and call // methods that we aren't supposed to be able to know about. // // It's ok to do this in tests, but you really don't want to do it in // production code. // ignore: avoid_dynamic_calls (key.currentState as dynamic).ensureTooltipVisible(); } void main() { test('TooltipThemeData copyWith, ==, hashCode basics', () { expect(const TooltipThemeData(), const TooltipThemeData().copyWith()); expect(const TooltipThemeData().hashCode, const TooltipThemeData().copyWith().hashCode); }); test('TooltipThemeData defaults', () { const TooltipThemeData theme = TooltipThemeData(); expect(theme.height, null); expect(theme.padding, null); expect(theme.verticalOffset, null); expect(theme.preferBelow, null); expect(theme.excludeFromSemantics, null); expect(theme.decoration, null); expect(theme.textStyle, null); expect(theme.textAlign, null); expect(theme.waitDuration, null); expect(theme.showDuration, null); expect(theme.triggerMode, null); expect(theme.enableFeedback, null); }); testWidgets('Default TooltipThemeData debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const TooltipThemeData().debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, <String>[]); }); testWidgets('TooltipThemeData implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const Duration wait = Duration(milliseconds: 100); const Duration show = Duration(milliseconds: 200); const TooltipTriggerMode triggerMode = TooltipTriggerMode.longPress; const bool enableFeedback = true; const TooltipThemeData( height: 15.0, padding: EdgeInsets.all(20.0), verticalOffset: 10.0, preferBelow: false, excludeFromSemantics: true, decoration: BoxDecoration(color: Color(0xffffffff)), textStyle: TextStyle(decoration: TextDecoration.underline), textAlign: TextAlign.center, waitDuration: wait, showDuration: show, triggerMode: triggerMode, enableFeedback: enableFeedback, ).debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, <String>[ 'height: 15.0', 'padding: EdgeInsets.all(20.0)', 'vertical offset: 10.0', 'position: above', 'semantics: excluded', 'decoration: BoxDecoration(color: Color(0xffffffff))', 'textStyle: TextStyle(inherit: true, decoration: TextDecoration.underline)', 'textAlign: TextAlign.center', 'wait duration: $wait', 'show duration: $show', 'triggerMode: $triggerMode', 'enableFeedback: true', ]); }); testWidgets('Tooltip verticalOffset, preferBelow; center prefer above fits - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( height: 100.0, padding: EdgeInsets.zero, verticalOffset: 100.0, preferBelow: false, ), ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Stack( children: <Widget>[ Positioned( left: 400.0, top: 300.0, child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ), ], ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen * ___ * }- 10.0 margin * |___| * }-100.0 height * | * }-100.0 vertical offset * o * y=300.0 * * * * * * *********************/ final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent! as RenderBox; expect(tip.size.height, equals(100.0)); expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(100.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(200.0)); }); testWidgets('Tooltip verticalOffset, preferBelow; center prefer above fits - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TooltipTheme( data: const TooltipThemeData( height: 100.0, padding: EdgeInsets.zero, verticalOffset: 100.0, preferBelow: false, ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Stack( children: <Widget>[ Positioned( left: 400.0, top: 300.0, child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ), ], ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen * ___ * }- 10.0 margin * |___| * }-100.0 height * | * }-100.0 vertical offset * o * y=300.0 * * * * * * *********************/ final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent! as RenderBox; expect(tip.size.height, equals(100.0)); expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(100.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(200.0)); }); testWidgets('Tooltip verticalOffset, preferBelow; center prefer above does not fit - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( height: 190.0, padding: EdgeInsets.zero, verticalOffset: 100.0, preferBelow: false, ), ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Stack( children: <Widget>[ Positioned( left: 400.0, top: 299.0, child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ), ], ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) // we try to put it here but it doesn't fit: /********************* 800x600 screen * ___ * }- 10.0 margin * |___| * }-190.0 height (starts at y=9.0) * | * }-100.0 vertical offset * o * y=299.0 * * * * * * *********************/ // so we put it here: /********************* 800x600 screen * * * * * o * y=299.0 * _|_ * }-100.0 vertical offset * |___| * }-190.0 height * * }- 10.0 margin *********************/ final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent! as RenderBox; expect(tip.size.height, equals(190.0)); expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(399.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(589.0)); }); testWidgets('Tooltip verticalOffset, preferBelow; center prefer above does not fit - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TooltipTheme( data: const TooltipThemeData( height: 190.0, padding: EdgeInsets.zero, verticalOffset: 100.0, preferBelow: false, ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Stack( children: <Widget>[ Positioned( left: 400.0, top: 299.0, child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ), ], ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) // we try to put it here but it doesn't fit: /********************* 800x600 screen * ___ * }- 10.0 margin * |___| * }-190.0 height (starts at y=9.0) * | * }-100.0 vertical offset * o * y=299.0 * * * * * * *********************/ // so we put it here: /********************* 800x600 screen * * * * * o * y=299.0 * _|_ * }-100.0 vertical offset * |___| * }-190.0 height * * }- 10.0 margin *********************/ final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent! as RenderBox; expect(tip.size.height, equals(190.0)); expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(399.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(589.0)); }); testWidgets('Tooltip verticalOffset, preferBelow; center preferBelow fits - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( height: 190.0, padding: EdgeInsets.zero, verticalOffset: 100.0, preferBelow: true, ), ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Stack( children: <Widget>[ Positioned( left: 400.0, top: 300.0, child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ), ], ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pumpAndSettle(); // faded in, show timer started (and at 0.0) /********************* 800x600 screen * * * * * o * y=300.0 * _|_ * }-100.0 vertical offset * |___| * }-190.0 height * * }- 10.0 margin *********************/ final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent! as RenderBox; expect(tip.size.height, equals(190.0)); expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(400.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(590.0)); }); testWidgets('Tooltip verticalOffset, preferBelow; center prefer below fits - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TooltipTheme( data: const TooltipThemeData( height: 190.0, padding: EdgeInsets.zero, verticalOffset: 100.0, preferBelow: true, ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Stack( children: <Widget>[ Positioned( left: 400.0, top: 300.0, child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ), ], ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pumpAndSettle(); // faded in, show timer started (and at 0.0) /********************* 800x600 screen * * * * * o * y=300.0 * _|_ * }-100.0 vertical offset * |___| * }-190.0 height * * }- 10.0 margin *********************/ final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent! as RenderBox; expect(tip.size.height, equals(190.0)); expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(400.0)); expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(590.0)); }); testWidgets('Tooltip margin - ThemeData', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( padding: EdgeInsets.zero, margin: EdgeInsets.all(_customPaddingValue), ), ), child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ); }, ), ], ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent!.parent!.parent!.parent!.parent! as RenderBox; final RenderBox tooltipContent = tester.renderObject(find.text(tooltipText)); final Offset topLeftTipInGlobal = tip.localToGlobal(tip.size.topLeft(Offset.zero)); final Offset topLeftTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.topLeft(Offset.zero)); expect(topLeftTooltipContentInGlobal.dx, topLeftTipInGlobal.dx + _customPaddingValue); expect(topLeftTooltipContentInGlobal.dy, topLeftTipInGlobal.dy + _customPaddingValue); final Offset topRightTipInGlobal = tip.localToGlobal(tip.size.topRight(Offset.zero)); final Offset topRightTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.topRight(Offset.zero)); expect(topRightTooltipContentInGlobal.dx, topRightTipInGlobal.dx - _customPaddingValue); expect(topRightTooltipContentInGlobal.dy, topRightTipInGlobal.dy + _customPaddingValue); final Offset bottomLeftTipInGlobal = tip.localToGlobal(tip.size.bottomLeft(Offset.zero)); final Offset bottomLeftTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.bottomLeft(Offset.zero)); expect(bottomLeftTooltipContentInGlobal.dx, bottomLeftTipInGlobal.dx + _customPaddingValue); expect(bottomLeftTooltipContentInGlobal.dy, bottomLeftTipInGlobal.dy - _customPaddingValue); final Offset bottomRightTipInGlobal = tip.localToGlobal(tip.size.bottomRight(Offset.zero)); final Offset bottomRightTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.bottomRight(Offset.zero)); expect(bottomRightTooltipContentInGlobal.dx, bottomRightTipInGlobal.dx - _customPaddingValue); expect(bottomRightTooltipContentInGlobal.dy, bottomRightTipInGlobal.dy - _customPaddingValue); }); testWidgets('Tooltip margin - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return TooltipTheme( data: const TooltipThemeData( padding: EdgeInsets.zero, margin: EdgeInsets.all(_customPaddingValue), ), child: Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ), ); }, ), ], ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent!.parent!.parent!.parent!.parent! as RenderBox; final RenderBox tooltipContent = tester.renderObject(find.text(tooltipText)); final Offset topLeftTipInGlobal = tip.localToGlobal(tip.size.topLeft(Offset.zero)); final Offset topLeftTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.topLeft(Offset.zero)); expect(topLeftTooltipContentInGlobal.dx, topLeftTipInGlobal.dx + _customPaddingValue); expect(topLeftTooltipContentInGlobal.dy, topLeftTipInGlobal.dy + _customPaddingValue); final Offset topRightTipInGlobal = tip.localToGlobal(tip.size.topRight(Offset.zero)); final Offset topRightTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.topRight(Offset.zero)); expect(topRightTooltipContentInGlobal.dx, topRightTipInGlobal.dx - _customPaddingValue); expect(topRightTooltipContentInGlobal.dy, topRightTipInGlobal.dy + _customPaddingValue); final Offset bottomLeftTipInGlobal = tip.localToGlobal(tip.size.bottomLeft(Offset.zero)); final Offset bottomLeftTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.bottomLeft(Offset.zero)); expect(bottomLeftTooltipContentInGlobal.dx, bottomLeftTipInGlobal.dx + _customPaddingValue); expect(bottomLeftTooltipContentInGlobal.dy, bottomLeftTipInGlobal.dy - _customPaddingValue); final Offset bottomRightTipInGlobal = tip.localToGlobal(tip.size.bottomRight(Offset.zero)); final Offset bottomRightTooltipContentInGlobal = tooltipContent.localToGlobal(tooltipContent.size.bottomRight(Offset.zero)); expect(bottomRightTooltipContentInGlobal.dx, bottomRightTipInGlobal.dx - _customPaddingValue); expect(bottomRightTooltipContentInGlobal.dy, bottomRightTipInGlobal.dy - _customPaddingValue); }); testWidgets('Tooltip message textStyle - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(MaterialApp( theme: ThemeData( tooltipTheme: const TooltipThemeData( textStyle: TextStyle( color: Colors.orange, decoration: TextDecoration.underline, ), ), ), home: Tooltip( key: key, message: tooltipText, child: Container( width: 100.0, height: 100.0, color: Colors.green[500], ), ), )); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; expect(textStyle.color, Colors.orange); expect(textStyle.fontFamily, null); expect(textStyle.decoration, TextDecoration.underline); }); testWidgets('Tooltip message textStyle - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(MaterialApp( home: TooltipTheme( data: const TooltipThemeData(), child: Tooltip( textStyle: const TextStyle( color: Colors.orange, decoration: TextDecoration.underline, ), key: key, message: tooltipText, child: Container( width: 100.0, height: 100.0, color: Colors.green[500], ), ), ), )); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; expect(textStyle.color, Colors.orange); expect(textStyle.fontFamily, null); expect(textStyle.decoration, TextDecoration.underline); }); testWidgets('Tooltip message textAlign - TooltipTheme', (WidgetTester tester) async { Future<void> pumpTooltipWithTextAlign({TextAlign? textAlign}) async { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget( MaterialApp( home: TooltipTheme( data: TooltipThemeData( textAlign: textAlign, ), child: Tooltip( key: tooltipKey, message: tooltipText, child: Container( width: 100.0, height: 100.0, color: Colors.green[500], ), ), ), ), ); tooltipKey.currentState?.ensureTooltipVisible(); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) } // Default value should be TextAlign.start await pumpTooltipWithTextAlign(); TextAlign textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!; expect(textAlign, TextAlign.start); await pumpTooltipWithTextAlign(textAlign: TextAlign.center); textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!; expect(textAlign, TextAlign.center); await pumpTooltipWithTextAlign(textAlign: TextAlign.end); textAlign = tester.widget<Text>(find.text(tooltipText)).textAlign!; expect(textAlign, TextAlign.end); }); testWidgets('Tooltip decoration - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); const Decoration customDecoration = ShapeDecoration( shape: StadiumBorder(), color: Color(0x80800000), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( decoration: customDecoration, ), ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent!.parent!.parent!.parent! as RenderBox; expect(tip.size.height, equals(32.0)); expect(tip.size.width, equals(74.0)); expect(tip, paints..rrect(color: const Color(0x80800000))); }); testWidgets('Tooltip decoration - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); const Decoration customDecoration = ShapeDecoration( shape: StadiumBorder(), color: Color(0x80800000), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TooltipTheme( data: const TooltipThemeData(decoration: customDecoration), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Tooltip( key: key, message: tooltipText, child: const SizedBox( width: 0.0, height: 0.0, ), ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent!.parent!.parent!.parent! as RenderBox; expect(tip.size.height, equals(32.0)); expect(tip.size.width, equals(74.0)); expect(tip, paints..rrect(color: const Color(0x80800000))); }); testWidgets('Tooltip height and padding - ThemeData.tooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); const double customTooltipHeight = 100.0; const double customPaddingVal = 20.0; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( height: customTooltipHeight, padding: EdgeInsets.all(customPaddingVal), ), ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Tooltip( key: key, message: tooltipText, ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pumpAndSettle(); final RenderBox tip = tester.renderObject(find.ancestor( of: find.text(tooltipText), matching: find.byType(Padding).first, // select [Tooltip.padding] instead of [Tooltip.margin] )); final RenderBox content = tester.renderObject(find.ancestor( of: find.text(tooltipText), matching: find.byType(Center), )); expect(tip.size.height, equals(customTooltipHeight)); expect(content.size.height, equals(customTooltipHeight - 2 * customPaddingVal)); expect(content.size.width, equals(tip.size.width - 2 * customPaddingVal)); }); testWidgets('Tooltip height and padding - TooltipTheme', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); const double customTooltipHeight = 100.0; const double customPaddingValue = 20.0; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TooltipTheme( data: const TooltipThemeData( height: customTooltipHeight, padding: EdgeInsets.all(customPaddingValue), ), child: Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext context) { return Tooltip( key: key, message: tooltipText, ); }, ), ], ), ), ), ); _ensureTooltipVisible(key); await tester.pumpAndSettle(); final RenderBox tip = tester.renderObject(find.ancestor( of: find.text(tooltipText), matching: find.byType(Padding).first, // select [Tooltip.padding] instead of [Tooltip.margin] )); final RenderBox content = tester.renderObject(find.ancestor( of: find.text(tooltipText), matching: find.byType(Center), )); expect(tip.size.height, equals(customTooltipHeight)); expect(content.size.height, equals(customTooltipHeight - 2 * customPaddingValue)); expect(content.size.width, equals(tip.size.width - 2 * customPaddingValue)); }); testWidgets('Tooltip waitDuration - ThemeData.tooltipTheme', (WidgetTester tester) async { const Duration customWaitDuration = Duration(milliseconds: 500); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); await gesture.moveTo(const Offset(1.0, 1.0)); await tester.pump(); await gesture.moveTo(Offset.zero); await tester.pumpWidget( MaterialApp( home: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( waitDuration: customWaitDuration, ), ), child: const Center( child: Tooltip( message: tooltipText, child: SizedBox( width: 100.0, height: 100.0, ), ), ), ), ), ); final Finder tooltip = find.byType(Tooltip); await gesture.moveTo(Offset.zero); await tester.pump(); await gesture.moveTo(tester.getCenter(tooltip)); await tester.pump(); await tester.pump(const Duration(milliseconds: 250)); expect(find.text(tooltipText), findsNothing); // Should not appear yet await tester.pump(const Duration(milliseconds: 250)); expect(find.text(tooltipText), findsOneWidget); // Should appear after customWaitDuration await gesture.moveTo(Offset.zero); await tester.pump(); // Wait for it to disappear. await tester.pump(customWaitDuration); expect(find.text(tooltipText), findsNothing); }); testWidgets('Tooltip waitDuration - TooltipTheme', (WidgetTester tester) async { const Duration customWaitDuration = Duration(milliseconds: 500); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); await gesture.moveTo(const Offset(1.0, 1.0)); await tester.pump(); await gesture.moveTo(Offset.zero); await tester.pumpWidget( const MaterialApp( home: TooltipTheme( data: TooltipThemeData(waitDuration: customWaitDuration), child: Center( child: Tooltip( message: tooltipText, child: SizedBox( width: 100.0, height: 100.0, ), ), ), ), ), ); final Finder tooltip = find.byType(Tooltip); await gesture.moveTo(Offset.zero); await tester.pump(); await gesture.moveTo(tester.getCenter(tooltip)); await tester.pump(); await tester.pump(const Duration(milliseconds: 250)); expect(find.text(tooltipText), findsNothing); // Should not appear yet await tester.pump(const Duration(milliseconds: 250)); expect(find.text(tooltipText), findsOneWidget); // Should appear after customWaitDuration await gesture.moveTo(Offset.zero); await tester.pump(); // Wait for it to disappear. await tester.pump(customWaitDuration); // Should disappear after customWaitDuration expect(find.text(tooltipText), findsNothing); }); testWidgets('Tooltip showDuration - ThemeData.tooltipTheme', (WidgetTester tester) async { const Duration customShowDuration = Duration(milliseconds: 3000); await tester.pumpWidget( MaterialApp( home: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData( showDuration: customShowDuration, ), ), child: const Center( child: Tooltip( message: tooltipText, child: SizedBox( width: 100.0, height: 100.0, ), ), ), ), ), ); final Finder tooltip = find.byType(Tooltip); final TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip)); await tester.pump(); await tester.pump(kLongPressTimeout); await gesture.up(); expect(find.text(tooltipText), findsOneWidget); await tester.pump(); await tester.pump(const Duration(milliseconds: 2000)); // Tooltip should remain expect(find.text(tooltipText), findsOneWidget); await tester.pump(const Duration(milliseconds: 1000)); await tester.pumpAndSettle(); // Tooltip should fade out after expect(find.text(tooltipText), findsNothing); }); testWidgets('Tooltip showDuration - TooltipTheme', (WidgetTester tester) async { const Duration customShowDuration = Duration(milliseconds: 3000); await tester.pumpWidget( const MaterialApp( home: TooltipTheme( data: TooltipThemeData(showDuration: customShowDuration), child: Center( child: Tooltip( message: tooltipText, child: SizedBox( width: 100.0, height: 100.0, ), ), ), ), ), ); final Finder tooltip = find.byType(Tooltip); final TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip)); await tester.pump(); await tester.pump(kLongPressTimeout); await gesture.up(); expect(find.text(tooltipText), findsOneWidget); await tester.pump(); await tester.pump(const Duration(milliseconds: 2000)); // Tooltip should remain expect(find.text(tooltipText), findsOneWidget); await tester.pump(const Duration(milliseconds: 1000)); await tester.pumpAndSettle(); // Tooltip should fade out after expect(find.text(tooltipText), findsNothing); }); testWidgets('Tooltip triggerMode - ThemeData.triggerMode', (WidgetTester tester) async { const TooltipTriggerMode triggerMode = TooltipTriggerMode.tap; await tester.pumpWidget( MaterialApp( home: Theme( data: ThemeData( tooltipTheme: const TooltipThemeData(triggerMode: triggerMode), ), child: const Center( child: Tooltip( message: tooltipText, child: SizedBox(width: 100.0, height: 100.0), ), ), ), ), ); final Finder tooltip = find.byType(Tooltip); final TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip)); await gesture.up(); await tester.pump(); expect(find.text(tooltipText), findsOneWidget); // Tooltip should show immediately after tap }); testWidgets('Tooltip triggerMode - TooltipTheme', (WidgetTester tester) async { const TooltipTriggerMode triggerMode = TooltipTriggerMode.tap; await tester.pumpWidget( const MaterialApp( home: TooltipTheme( data: TooltipThemeData(triggerMode: triggerMode), child: Center( child: Tooltip( message: tooltipText, child: SizedBox(width: 100.0, height: 100.0), ), ), ), ), ); final Finder tooltip = find.byType(Tooltip); final TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip)); await gesture.up(); await tester.pump(); expect(find.text(tooltipText), findsOneWidget); // Tooltip should show immediately after tap }); testWidgets('Semantics included by default - ThemeData.tooltipTheme', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( MaterialApp( theme: ThemeData(), home: const Center( child: Tooltip( message: 'Foo', child: Text('Bar'), ), ), ), ); expect(semantics, hasSemantics(TestSemantics.root( children: <TestSemantics>[ TestSemantics.rootChild( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( tooltip: 'Foo', label: 'Bar', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ignoreRect: true, ignoreId: true, ignoreTransform: true)); semantics.dispose(); }); testWidgets('Semantics included by default - TooltipTheme', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( const MaterialApp( home: TooltipTheme( data: TooltipThemeData(), child: Center( child: Tooltip( message: 'Foo', child: Text('Bar'), ), ), ), ), ); expect(semantics, hasSemantics(TestSemantics.root( children: <TestSemantics>[ TestSemantics.rootChild( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( tooltip: 'Foo', label: 'Bar', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ignoreRect: true, ignoreId: true, ignoreTransform: true)); semantics.dispose(); }); testWidgets('Semantics excluded - ThemeData.tooltipTheme', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( MaterialApp( theme: ThemeData( tooltipTheme: const TooltipThemeData( excludeFromSemantics: true, ), ), home: const Center( child: Tooltip( message: 'Foo', child: Text('Bar'), ), ), ), ); expect(semantics, hasSemantics(TestSemantics.root( children: <TestSemantics>[ TestSemantics.rootChild( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( label: 'Bar', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ignoreRect: true, ignoreId: true, ignoreTransform: true)); semantics.dispose(); }); testWidgets('Semantics excluded - TooltipTheme', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( const MaterialApp( home: TooltipTheme( data: TooltipThemeData(excludeFromSemantics: true), child: Center( child: Tooltip( message: 'Foo', child: Text('Bar'), ), ), ), ), ); expect(semantics, hasSemantics(TestSemantics.root( children: <TestSemantics>[ TestSemantics.rootChild( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( label: 'Bar', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ignoreRect: true, ignoreId: true, ignoreTransform: true)); semantics.dispose(); }); testWidgets('has semantic events by default - ThemeData.tooltipTheme', (WidgetTester tester) async { final List<dynamic> semanticEvents = <dynamic>[]; tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, (dynamic message) async { semanticEvents.add(message); }); final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( MaterialApp( theme: ThemeData(), home: Center( child: Tooltip( message: 'Foo', child: Container( width: 100.0, height: 100.0, color: Colors.green[500], ), ), ), ), ); await tester.longPress(find.byType(Tooltip)); final RenderObject object = tester.firstRenderObject(find.byType(Tooltip)); expect(semanticEvents, unorderedEquals(<dynamic>[ <String, dynamic>{ 'type': 'longPress', 'nodeId': findDebugSemantics(object).id, 'data': <String, dynamic>{}, }, <String, dynamic>{ 'type': 'tooltip', 'data': <String, dynamic>{ 'message': 'Foo', }, }, ])); semantics.dispose(); tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null); }); testWidgets('has semantic events by default - TooltipTheme', (WidgetTester tester) async { final List<dynamic> semanticEvents = <dynamic>[]; tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, (dynamic message) async { semanticEvents.add(message); }); final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( MaterialApp( home: TooltipTheme( data: const TooltipThemeData(), child: Center( child: Tooltip( message: 'Foo', child: Container( width: 100.0, height: 100.0, color: Colors.green[500], ), ), ), ), ), ); await tester.longPress(find.byType(Tooltip)); final RenderObject object = tester.firstRenderObject(find.byType(Tooltip)); expect(semanticEvents, unorderedEquals(<dynamic>[ <String, dynamic>{ 'type': 'longPress', 'nodeId': findDebugSemantics(object).id, 'data': <String, dynamic>{}, }, <String, dynamic>{ 'type': 'tooltip', 'data': <String, dynamic>{ 'message': 'Foo', }, }, ])); semantics.dispose(); tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null); }); testWidgets('default Tooltip debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const Tooltip(message: 'message').debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()).toList(); expect(description, <String>[ '"message"', ]); }); } SemanticsNode findDebugSemantics(RenderObject object) { if (object.debugSemantics != null) { return object.debugSemantics!; } return findDebugSemantics(object.parent! as RenderObject); }