| // 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 'dart:ui'; |
| |
| import 'package:flutter/cupertino.dart'; |
| import 'package:flutter/foundation.dart'; |
| 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'; |
| |
| const String tooltipText = 'TIP'; |
| |
| Finder _findTooltipContainer(String tooltipText) { |
| return find.ancestor(of: find.text(tooltipText), matching: find.byType(Container)); |
| } |
| |
| void main() { |
| testWidgets('Does tooltip end up in the right place - center', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 300.0, |
| top: 0.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 20.0, |
| padding: const EdgeInsets.all(5.0), |
| verticalOffset: 20.0, |
| preferBelow: false, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| * o * y=0 |
| * | * }- 20.0 vertical offset, of which 10.0 is in the screen edge margin |
| * +----+ * \- (5.0 padding in height) |
| * | | * |- 20 height |
| * +----+ * /- (5.0 padding in height) |
| * * |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| final Offset tipInGlobal = tip.localToGlobal(tip.size.topCenter(Offset.zero)); |
| // The exact position of the left side depends on the font the test framework |
| // happens to pick, so we don't test that. |
| expect(tipInGlobal.dx, 300.0); |
| expect(tipInGlobal.dy, 20.0); |
| }); |
| |
| testWidgets('Does tooltip end up in the right place - center with padding outside overlay', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Padding( |
| padding: const EdgeInsets.all(20), |
| child: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 300.0, |
| top: 0.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 20.0, |
| padding: const EdgeInsets.all(5.0), |
| verticalOffset: 20.0, |
| preferBelow: false, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /************************ 800x600 screen |
| * ________________ * }- 20.0 padding outside overlay |
| * | o | * y=0 |
| * | | | * }- 20.0 vertical offset, of which 10.0 is in the screen edge margin |
| * | +----+ | * \- (5.0 padding in height) |
| * | | | | * |- 20 height |
| * | +----+ | * /- (5.0 padding in height) |
| * |________________| * |
| * * } - 20.0 padding outside overlay |
| ************************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| final Offset tipInGlobal = tip.localToGlobal(tip.size.topCenter(Offset.zero)); |
| // The exact position of the left side depends on the font the test framework |
| // happens to pick, so we don't test that. |
| expect(tipInGlobal.dx, 320.0); |
| expect(tipInGlobal.dy, 40.0); |
| }); |
| |
| testWidgets('Material2 - Does tooltip end up in the right place - top left', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 0.0, |
| top: 0.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 20.0, |
| padding: const EdgeInsets.all(5.0), |
| verticalOffset: 20.0, |
| preferBelow: false, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| *o * y=0 |
| *| * }- 20.0 vertical offset, of which 10.0 is in the screen edge margin |
| *+----+ * \- (5.0 padding in height) |
| *| | * |- 20 height |
| *+----+ * /- (5.0 padding in height) |
| * * |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(24.0)); // 14.0 height + 5.0 padding * 2 (top, bottom) |
| expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)), equals(const Offset(10.0, 20.0))); |
| }); |
| |
| testWidgets('Material3 - Does tooltip end up in the right place - top left', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 0.0, |
| top: 0.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 20.0, |
| padding: const EdgeInsets.all(5.0), |
| verticalOffset: 20.0, |
| preferBelow: false, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| *o * y=0 |
| *| * }- 20.0 vertical offset, of which 10.0 is in the screen edge margin |
| *+----+ * \- (5.0 padding in height) |
| *| | * |- 20 height |
| *+----+ * /- (5.0 padding in height) |
| * * |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(30.0)); // 20.0 height + 5.0 padding * 2 (top, bottom) |
| expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)), equals(const Offset(10.0, 20.0))); |
| }); |
| |
| testWidgets('Does tooltip end up in the right place - center prefer above fits', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 400.0, |
| top: 300.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 100.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 100.0, |
| preferBelow: false, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| 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(_findTooltipContainer(tooltipText)); |
| 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('Does tooltip end up in the right place - center prefer above does not fit', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 400.0, |
| top: 299.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 190.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 100.0, |
| preferBelow: false, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| 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(_findTooltipContainer(tooltipText)); |
| 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('Does tooltip end up in the right place - center prefer below fits', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 400.0, |
| top: 300.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 190.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 100.0, |
| preferBelow: true, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // 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(_findTooltipContainer(tooltipText)); |
| 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('Material2 - Does tooltip end up in the right place - way off to the right', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 1600.0, |
| top: 300.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 10.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 10.0, |
| preferBelow: true, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| * * |
| * * |
| * * y=300.0; target --> o |
| * ___| * }-10.0 vertical offset |
| * |___| * }-10.0 height |
| * * |
| * * }-10.0 margin |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(14.0)); |
| expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(310.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dx, equals(790.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(324.0)); |
| }); |
| |
| testWidgets('Material3 - Does tooltip end up in the right place - way off to the right', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 1600.0, |
| top: 300.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 10.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 10.0, |
| preferBelow: true, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| * * |
| * * |
| * * y=300.0; target --> o |
| * ___| * }-10.0 vertical offset |
| * |___| * }-10.0 height |
| * * |
| * * }-10.0 margin |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(20.0)); |
| expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(310.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dx, equals(790.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(330.0)); |
| }); |
| |
| testWidgets('Material2 - Does tooltip end up in the right place - near the edge', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 780.0, |
| top: 300.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 10.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 10.0, |
| preferBelow: true, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| * * |
| * * |
| * o * y=300.0 |
| * __| * }-10.0 vertical offset |
| * |___| * }-10.0 height |
| * * |
| * * }-10.0 margin |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(14.0)); |
| expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(310.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dx, equals(790.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(324.0)); |
| }); |
| |
| testWidgets('Material3 - Does tooltip end up in the right place - near the edge', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Stack( |
| children: <Widget>[ |
| Positioned( |
| left: 780.0, |
| top: 300.0, |
| child: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| height: 10.0, |
| padding: EdgeInsets.zero, |
| verticalOffset: 10.0, |
| preferBelow: true, |
| child: const SizedBox.shrink(), |
| ), |
| ), |
| ], |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| /********************* 800x600 screen |
| * * |
| * * |
| * o * y=300.0 |
| * __| * }-10.0 vertical offset |
| * |___| * }-10.0 height |
| * * |
| * * }-10.0 margin |
| *********************/ |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(20.0)); |
| expect(tip.localToGlobal(tip.size.topLeft(Offset.zero)).dy, equals(310.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dx, equals(790.0)); |
| expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(330.0)); |
| }); |
| |
| testWidgets('Tooltip should be fully visible when MediaQuery.viewInsets > 0', ( |
| WidgetTester tester, |
| ) async { |
| // Regression test for https://github.com/flutter/flutter/issues/23666 |
| Widget materialAppWithViewInsets(double viewInsetsHeight) { |
| final Widget scaffold = Scaffold( |
| body: const TextField(), |
| floatingActionButton: FloatingActionButton( |
| tooltip: tooltipText, |
| onPressed: () { |
| /* do nothing */ |
| }, |
| child: const Icon(Icons.add), |
| ), |
| ); |
| return MediaQuery( |
| data: MediaQueryData(viewInsets: EdgeInsets.only(bottom: viewInsetsHeight)), |
| child: MaterialApp(useInheritedMediaQuery: true, home: scaffold), |
| ); |
| } |
| |
| // Start with MediaQuery.viewInsets.bottom = 0 |
| await tester.pumpWidget(materialAppWithViewInsets(0)); |
| |
| // Show FAB tooltip |
| final Finder fabFinder = find.byType(FloatingActionButton); |
| await tester.longPress(fabFinder); |
| await tester.pump(const Duration(milliseconds: 500)); |
| expect(find.byType(Tooltip), findsOneWidget); |
| |
| // FAB tooltip should be above FAB |
| RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| Offset fabTopRight = tester.getTopRight(fabFinder); |
| Offset tooltipTopRight = tip.localToGlobal(tip.size.topRight(Offset.zero)); |
| expect(tooltipTopRight.dy, lessThan(fabTopRight.dy)); |
| |
| // Simulate Keyboard opening (MediaQuery.viewInsets.bottom = 300)) |
| await tester.pumpWidget(materialAppWithViewInsets(300)); |
| // Wait for the tooltip to dismiss. |
| await tester.pump(const Duration(days: 1)); |
| await tester.pumpAndSettle(); |
| |
| // Show FAB tooltip |
| await tester.longPress(fabFinder); |
| await tester.pump(const Duration(milliseconds: 500)); |
| expect(find.byType(Tooltip), findsOneWidget); |
| |
| // FAB tooltip should still be above FAB |
| tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| fabTopRight = tester.getTopRight(fabFinder); |
| tooltipTopRight = tip.localToGlobal(tip.size.topRight(Offset.zero)); |
| expect(tooltipTopRight.dy, lessThan(fabTopRight.dy)); |
| }); |
| |
| testWidgets('Custom tooltip margin', (WidgetTester tester) async { |
| const customMarginValue = 10.0; |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| padding: EdgeInsets.zero, |
| margin: const EdgeInsets.all(customMarginValue), |
| child: const SizedBox.shrink(), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final Offset topLeftTipInGlobal = tester.getTopLeft(_findTooltipContainer(tooltipText)); |
| final Offset topLeftTooltipContentInGlobal = tester.getTopLeft(find.text(tooltipText)); |
| expect(topLeftTooltipContentInGlobal.dx, topLeftTipInGlobal.dx + customMarginValue); |
| expect(topLeftTooltipContentInGlobal.dy, topLeftTipInGlobal.dy + customMarginValue); |
| |
| final Offset topRightTipInGlobal = tester.getTopRight(_findTooltipContainer(tooltipText)); |
| final Offset topRightTooltipContentInGlobal = tester.getTopRight(find.text(tooltipText)); |
| expect(topRightTooltipContentInGlobal.dx, topRightTipInGlobal.dx - customMarginValue); |
| expect(topRightTooltipContentInGlobal.dy, topRightTipInGlobal.dy + customMarginValue); |
| |
| final Offset bottomLeftTipInGlobal = tester.getBottomLeft(_findTooltipContainer(tooltipText)); |
| final Offset bottomLeftTooltipContentInGlobal = tester.getBottomLeft(find.text(tooltipText)); |
| expect(bottomLeftTooltipContentInGlobal.dx, bottomLeftTipInGlobal.dx + customMarginValue); |
| expect(bottomLeftTooltipContentInGlobal.dy, bottomLeftTipInGlobal.dy - customMarginValue); |
| |
| final Offset bottomRightTipInGlobal = tester.getBottomRight(_findTooltipContainer(tooltipText)); |
| final Offset bottomRightTooltipContentInGlobal = tester.getBottomRight(find.text(tooltipText)); |
| expect(bottomRightTooltipContentInGlobal.dx, bottomRightTipInGlobal.dx - customMarginValue); |
| expect(bottomRightTooltipContentInGlobal.dy, bottomRightTipInGlobal.dy - customMarginValue); |
| }); |
| |
| testWidgets('Material2 - Default tooltip message textStyle - light', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: 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) |
| |
| final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; |
| expect(textStyle.color, Colors.white); |
| expect(textStyle.fontFamily, 'Roboto'); |
| expect(textStyle.decoration, TextDecoration.none); |
| expect( |
| textStyle.debugLabel, |
| '((englishLike bodyMedium 2014).merge(blackMountainView bodyMedium)).copyWith', |
| ); |
| }); |
| |
| testWidgets('Material3 - Default tooltip message textStyle - light', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: 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) |
| |
| final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; |
| expect(textStyle.color, Colors.white); |
| expect(textStyle.fontFamily, 'Roboto'); |
| expect(textStyle.decoration, TextDecoration.none); |
| expect( |
| textStyle.debugLabel, |
| '((englishLike bodyMedium 2021).merge((blackMountainView bodyMedium).apply)).copyWith', |
| ); |
| }); |
| |
| testWidgets('Material2 - Default tooltip message textStyle - dark', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false, brightness: Brightness.dark), |
| home: 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) |
| |
| final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; |
| expect(textStyle.color, Colors.black); |
| expect(textStyle.fontFamily, 'Roboto'); |
| expect(textStyle.decoration, TextDecoration.none); |
| expect( |
| textStyle.debugLabel, |
| '((englishLike bodyMedium 2014).merge(whiteMountainView bodyMedium)).copyWith', |
| ); |
| }); |
| |
| testWidgets('Material3 - Default tooltip message textStyle - dark', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(brightness: Brightness.dark), |
| home: 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) |
| |
| final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; |
| expect(textStyle.color, Colors.black); |
| expect(textStyle.fontFamily, 'Roboto'); |
| expect(textStyle.decoration, TextDecoration.none); |
| expect( |
| textStyle.debugLabel, |
| '((englishLike bodyMedium 2021).merge((whiteMountainView bodyMedium).apply)).copyWith', |
| ); |
| }); |
| |
| testWidgets('Custom tooltip message textStyle', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip( |
| key: tooltipKey, |
| textStyle: const TextStyle(color: Colors.orange, decoration: TextDecoration.underline), |
| 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) |
| |
| 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('Custom tooltip message textAlign', (WidgetTester tester) async { |
| Future<void> pumpTooltipWithTextAlign({TextAlign? textAlign}) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip( |
| key: tooltipKey, |
| textAlign: textAlign, |
| 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 overlay wrapped with a non-fallback DefaultTextStyle widget', ( |
| WidgetTester tester, |
| ) async { |
| // A Material widget is needed as an ancestor of the Text widget. |
| // It is invalid to have text in a Material application that |
| // does not have a Material ancestor. |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: 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) |
| |
| final TextStyle textStyle = tester |
| .widget<DefaultTextStyle>( |
| find.ancestor(of: find.text(tooltipText), matching: find.byType(DefaultTextStyle)).first, |
| ) |
| .style; |
| |
| // The default fallback text style results in a text with a |
| // double underline of Color(0xffffff00). |
| expect(textStyle.decoration, isNot(TextDecoration.underline)); |
| expect(textStyle.decorationColor, isNot(const Color(0xffffff00))); |
| expect(textStyle.decorationStyle, isNot(TextDecorationStyle.double)); |
| }); |
| |
| testWidgets('Material2 - Does tooltip end up with the right default size, shape, and color', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| child: const SizedBox.shrink(), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(32.0)); |
| expect(tip.size.width, equals(74.0)); |
| expect( |
| tip, |
| paints..rrect( |
| rrect: RRect.fromRectAndRadius(tip.paintBounds, const Radius.circular(4.0)), |
| color: const Color(0xe6616161), |
| ), |
| ); |
| |
| final Container tooltipContainer = tester.firstWidget<Container>( |
| _findTooltipContainer(tooltipText), |
| ); |
| expect(tooltipContainer.padding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0)); |
| }); |
| |
| testWidgets('Material3 - Does tooltip end up with the right default size, shape, and color', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| child: const SizedBox.shrink(), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(32.0)); |
| expect(tip.size.width, equals(74.75)); |
| expect( |
| tip, |
| paints..rrect( |
| rrect: RRect.fromRectAndRadius(tip.paintBounds, const Radius.circular(4.0)), |
| color: const Color(0xe6616161), |
| ), |
| ); |
| |
| final Container tooltipContainer = tester.firstWidget<Container>( |
| _findTooltipContainer(tooltipText), |
| ); |
| expect(tooltipContainer.padding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0)); |
| }); |
| |
| testWidgets( |
| 'Material2 - Tooltip default size, shape, and color test for Desktop', |
| (WidgetTester tester) async { |
| // Regressing test for https://github.com/flutter/flutter/issues/68601 |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Tooltip(key: tooltipKey, message: tooltipText, child: const SizedBox.shrink()), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final RenderParagraph tooltipRenderParagraph = tester.renderObject<RenderParagraph>( |
| find.text(tooltipText), |
| ); |
| expect(tooltipRenderParagraph.textSize.height, equals(12.0)); |
| |
| final RenderBox tooltipRenderBox = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tooltipRenderBox.size.height, equals(24.0)); |
| expect( |
| tooltipRenderBox, |
| paints..rrect( |
| rrect: RRect.fromRectAndRadius(tooltipRenderBox.paintBounds, const Radius.circular(4.0)), |
| color: const Color(0xe6616161), |
| ), |
| ); |
| |
| final Container tooltipContainer = tester.firstWidget<Container>( |
| _findTooltipContainer(tooltipText), |
| ); |
| expect(tooltipContainer.padding, const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0)); |
| }, |
| variant: const TargetPlatformVariant(<TargetPlatform>{ |
| TargetPlatform.macOS, |
| TargetPlatform.linux, |
| TargetPlatform.windows, |
| }), |
| ); |
| |
| testWidgets( |
| 'Material3 - Tooltip default size, shape, and color test for Desktop', |
| (WidgetTester tester) async { |
| // Regressing test for https://github.com/flutter/flutter/issues/68601 |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip(key: tooltipKey, message: tooltipText, child: const SizedBox.shrink()), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final RenderParagraph tooltipRenderParagraph = tester.renderObject<RenderParagraph>( |
| find.text(tooltipText), |
| ); |
| expect(tooltipRenderParagraph.textSize.height, equals(17.0)); |
| |
| final RenderBox tooltipRenderBox = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tooltipRenderBox.size.height, equals(25.0)); |
| expect( |
| tooltipRenderBox, |
| paints..rrect( |
| rrect: RRect.fromRectAndRadius(tooltipRenderBox.paintBounds, const Radius.circular(4.0)), |
| color: const Color(0xe6616161), |
| ), |
| ); |
| |
| final Container tooltipContainer = tester.firstWidget<Container>( |
| _findTooltipContainer(tooltipText), |
| ); |
| expect(tooltipContainer.padding, const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0)); |
| }, |
| variant: const TargetPlatformVariant(<TargetPlatform>{ |
| TargetPlatform.macOS, |
| TargetPlatform.linux, |
| TargetPlatform.windows, |
| }), |
| ); |
| |
| testWidgets('Material2 - Can tooltip decoration be customized', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| const Decoration customDecoration = ShapeDecoration( |
| shape: StadiumBorder(), |
| color: Color(0x80800000), |
| ); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Tooltip( |
| key: tooltipKey, |
| decoration: customDecoration, |
| message: tooltipText, |
| child: const SizedBox.shrink(), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(32.0)); |
| expect(tip.size.width, equals(74.0)); |
| expect(tip, paints..rrect(color: const Color(0x80800000))); |
| }); |
| |
| testWidgets('Material3 - Can tooltip decoration be customized', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| const Decoration customDecoration = ShapeDecoration( |
| shape: StadiumBorder(), |
| color: Color(0x80800000), |
| ); |
| late final OverlayEntry entry; |
| addTearDown( |
| () => entry |
| ..remove() |
| ..dispose(), |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Overlay( |
| initialEntries: <OverlayEntry>[ |
| entry = OverlayEntry( |
| builder: (BuildContext context) { |
| return Tooltip( |
| key: tooltipKey, |
| decoration: customDecoration, |
| message: tooltipText, |
| child: const SizedBox.shrink(), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) |
| |
| final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(32.0)); |
| expect(tip.size.width, equals(74.75)); |
| expect(tip, paints..rrect(color: const Color(0x80800000))); |
| }); |
| |
| testWidgets('Material2 - Tooltip text scales with textScaler', (WidgetTester tester) async { |
| Widget buildApp(String text, {required TextScaler textScaler}) { |
| return MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: MediaQuery( |
| data: MediaQueryData(textScaler: textScaler), |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<void>( |
| builder: (BuildContext context) { |
| return Center( |
| child: Tooltip( |
| message: text, |
| child: Container(width: 100.0, height: 100.0, color: Colors.green[500]), |
| ), |
| ); |
| }, |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildApp(tooltipText, textScaler: TextScaler.noScaling)); |
| await tester.longPress(find.byType(Tooltip)); |
| expect(find.text(tooltipText), findsOneWidget); |
| expect(tester.getSize(find.text(tooltipText)), equals(const Size(42.0, 14.0))); |
| RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(32.0)); |
| |
| await tester.pumpWidget(buildApp(tooltipText, textScaler: const TextScaler.linear(4.0))); |
| await tester.longPress(find.byType(Tooltip)); |
| expect(find.text(tooltipText), findsOneWidget); |
| expect(tester.getSize(find.text(tooltipText)), equals(const Size(168.0, 56.0))); |
| tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(64.0)); |
| }); |
| |
| testWidgets('Material3 - Tooltip text scales with textScaleFactor', (WidgetTester tester) async { |
| Widget buildApp(String text, {required TextScaler textScaler}) { |
| return MaterialApp( |
| home: MediaQuery( |
| data: MediaQueryData(textScaler: textScaler), |
| child: Navigator( |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<void>( |
| builder: (BuildContext context) { |
| return Center( |
| child: Tooltip( |
| message: text, |
| child: Container(width: 100.0, height: 100.0, color: Colors.green[500]), |
| ), |
| ); |
| }, |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildApp(tooltipText, textScaler: TextScaler.noScaling)); |
| await tester.longPress(find.byType(Tooltip)); |
| expect(find.text(tooltipText), findsOneWidget); |
| expect(tester.getSize(find.text(tooltipText)).width, equals(42.75)); |
| expect(tester.getSize(find.text(tooltipText)).height, equals(20.0)); |
| RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(32.0)); |
| |
| await tester.pumpWidget(buildApp(tooltipText, textScaler: const TextScaler.linear(4.0))); |
| await tester.longPress(find.byType(Tooltip)); |
| expect(find.text(tooltipText), findsOneWidget); |
| expect(tester.getSize(find.text(tooltipText)).width, equals(168.75)); |
| expect(tester.getSize(find.text(tooltipText)).height, equals(80.0)); |
| tip = tester.renderObject(_findTooltipContainer(tooltipText)); |
| expect(tip.size.height, equals(88.0)); |
| }); |
| |
| testWidgets('Tooltip text displays with richMessage', (WidgetTester tester) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| const textSpan1Text = 'I am a rich tooltip message. '; |
| const textSpan2Text = 'I am another span of a rich tooltip message'; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip( |
| key: tooltipKey, |
| richMessage: const TextSpan( |
| text: textSpan1Text, |
| children: <InlineSpan>[TextSpan(text: textSpan2Text)], |
| ), |
| 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) |
| |
| final RichText richText = tester.widget<RichText>(find.byType(RichText)); |
| expect(richText.text.toPlainText(), equals('$textSpan1Text$textSpan2Text')); |
| }); |
| |
| testWidgets('Tooltip throws assertion error when both message and richMessage are specified', ( |
| WidgetTester tester, |
| ) async { |
| expect(() { |
| MaterialApp( |
| home: Tooltip( |
| message: 'I am a tooltip message.', |
| richMessage: const TextSpan( |
| text: 'I am a rich tooltip.', |
| children: <InlineSpan>[TextSpan(text: 'I am another span of a rich tooltip.')], |
| ), |
| child: Container(width: 100.0, height: 100.0, color: Colors.green[500]), |
| ), |
| ); |
| }, throwsA(const TypeMatcher<AssertionError>())); |
| }); |
| |
| testWidgets('default Tooltip debugFillProperties', (WidgetTester tester) async { |
| final 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"']); |
| }); |
| testWidgets('default Tooltip debugFillProperties with richMessage', (WidgetTester tester) async { |
| final builder = DiagnosticPropertiesBuilder(); |
| |
| const Tooltip( |
| richMessage: TextSpan( |
| text: 'This is a ', |
| children: <InlineSpan>[TextSpan(text: 'richMessage')], |
| ), |
| ).debugFillProperties(builder); |
| |
| final List<String> description = builder.properties |
| .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) |
| .map((DiagnosticsNode node) => node.toString()) |
| .toList(); |
| |
| expect(description, <String>['"This is a richMessage"']); |
| }); |
| testWidgets('Tooltip implements debugFillProperties', (WidgetTester tester) async { |
| final builder = DiagnosticPropertiesBuilder(); |
| |
| // Not checking controller, inputFormatters, focusNode |
| const Tooltip( |
| key: ValueKey<String>('foo'), |
| message: 'message', |
| decoration: BoxDecoration(), |
| waitDuration: Duration(seconds: 1), |
| showDuration: Duration(seconds: 2), |
| padding: EdgeInsets.zero, |
| margin: EdgeInsets.all(5.0), |
| height: 100.0, |
| excludeFromSemantics: true, |
| preferBelow: false, |
| verticalOffset: 50.0, |
| triggerMode: TooltipTriggerMode.manual, |
| enableFeedback: true, |
| ).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"', |
| 'height: 100.0', |
| 'padding: EdgeInsets.zero', |
| 'margin: EdgeInsets.all(5.0)', |
| 'vertical offset: 50.0', |
| 'position: above', |
| 'semantics: excluded', |
| 'wait duration: 0:00:01.000000', |
| 'show duration: 0:00:02.000000', |
| 'triggerMode: TooltipTriggerMode.manual', |
| 'enableFeedback: true', |
| ]); |
| }); |
| |
| testWidgets('Tooltip should not be shown with empty message (with child)', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Tooltip(message: tooltipText, child: Text(tooltipText)), |
| ), |
| ); |
| expect(find.text(tooltipText), findsOneWidget); |
| }); |
| |
| testWidgets('Tooltip should not be shown with empty message (without child)', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget(const MaterialApp(home: Tooltip(message: tooltipText))); |
| expect(find.text(tooltipText), findsNothing); |
| if (tooltipText.isEmpty) { |
| expect(find.byType(SizedBox), findsOneWidget); |
| } |
| }); |
| |
| testWidgets('Tooltip should not ignore users tap on richMessage', (WidgetTester tester) async { |
| var isTapped = false; |
| final recognizer = TapGestureRecognizer(); |
| addTearDown(recognizer.dispose); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip( |
| richMessage: TextSpan( |
| text: tooltipText, |
| recognizer: recognizer |
| ..onTap = () { |
| isTapped = true; |
| }, |
| ), |
| showDuration: const Duration(seconds: 5), |
| triggerMode: TooltipTriggerMode.tap, |
| child: const Icon(Icons.refresh), |
| ), |
| ), |
| ); |
| |
| final Finder tooltip = find.byType(Tooltip); |
| expect(find.text(tooltipText), findsNothing); |
| |
| await _testGestureTap(tester, tooltip); |
| final Finder textSpan = find.text(tooltipText); |
| expect(textSpan, findsOneWidget); |
| |
| await _testGestureTap(tester, textSpan); |
| expect(isTapped, isTrue); |
| }); |
| |
| testWidgets('Tooltip does not rebuild for fade in / fade out animation', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Center( |
| child: SizedBox.square( |
| dimension: 10.0, |
| child: Tooltip( |
| message: tooltipText, |
| waitDuration: Duration(seconds: 1), |
| triggerMode: TooltipTriggerMode.longPress, |
| child: SizedBox.expand(), |
| ), |
| ), |
| ), |
| ), |
| ); |
| final TooltipState tooltipState = tester.state(find.byType(Tooltip)); |
| final element = tooltipState.context as Element; |
| // The Tooltip widget itself is almost stateless thus doesn't need |
| // rebuilding. |
| expect(element.dirty, isFalse); |
| |
| expect(tooltipState.ensureTooltipVisible(), isTrue); |
| expect(element.dirty, isFalse); |
| await tester.pump(const Duration(seconds: 1)); |
| expect(element.dirty, isFalse); |
| |
| expect(Tooltip.dismissAllToolTips(), isTrue); |
| expect(element.dirty, isFalse); |
| await tester.pump(const Duration(seconds: 1)); |
| expect(element.dirty, isFalse); |
| }); |
| |
| testWidgets('Tooltip is not selectable', (WidgetTester tester) async { |
| const tooltipText = 'AAAAAAAAAAAAAAAAAAAAAAA'; |
| String? selectedText; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: SelectionArea( |
| onSelectionChanged: (SelectedContent? content) { |
| selectedText = content?.plainText; |
| }, |
| child: const Center( |
| child: Column( |
| children: <Widget>[ |
| Text('Select Me'), |
| Tooltip( |
| message: tooltipText, |
| waitDuration: Duration(seconds: 1), |
| triggerMode: TooltipTriggerMode.longPress, |
| child: SizedBox.square(dimension: 50), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final TooltipState tooltipState = tester.state(find.byType(Tooltip)); |
| |
| final Rect textRect = tester.getRect(find.text('Select Me')); |
| final TestGesture gesture = await tester.startGesture( |
| Alignment.centerLeft.alongSize(textRect.size) + textRect.topLeft, |
| ); |
| // Drag from centerLeft to centerRight to select the text. |
| await tester.pump(const Duration(seconds: 1)); |
| await gesture.moveTo(Alignment.centerRight.alongSize(textRect.size) + textRect.topLeft); |
| await tester.pump(); |
| |
| tooltipState.ensureTooltipVisible(); |
| await tester.pump(); |
| // Make sure the tooltip becomes visible. |
| expect(find.text(tooltipText), findsOneWidget); |
| assert(selectedText != null); |
| |
| final Rect tooltipTextRect = tester.getRect(find.text(tooltipText)); |
| // Now drag from centerLeft to centerRight to select the tooltip text. |
| await gesture.moveTo( |
| Alignment.centerLeft.alongSize(tooltipTextRect.size) + tooltipTextRect.topLeft, |
| ); |
| await tester.pump(); |
| await gesture.moveTo( |
| Alignment.centerRight.alongSize(tooltipTextRect.size) + tooltipTextRect.topLeft, |
| ); |
| await tester.pump(); |
| |
| expect(selectedText, isNot(contains('A'))); |
| }); |
| |
| testWidgets('Tooltip mouse cursor behavior', (WidgetTester tester) async { |
| const SystemMouseCursor customCursor = SystemMouseCursors.grab; |
| |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Center( |
| child: Tooltip( |
| message: tooltipText, |
| mouseCursor: customCursor, |
| child: SizedBox.square(dimension: 50), |
| ), |
| ), |
| ), |
| ); |
| |
| final TestGesture gesture = await tester.createGesture( |
| kind: PointerDeviceKind.mouse, |
| pointer: 1, |
| ); |
| await gesture.addPointer(location: const Offset(10, 10)); |
| await tester.pump(); |
| expect( |
| RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), |
| SystemMouseCursors.basic, |
| ); |
| |
| final Offset chip = tester.getCenter(find.byType(Tooltip)); |
| await gesture.moveTo(chip); |
| await tester.pump(); |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), customCursor); |
| }); |
| |
| testWidgets('Tooltip overlay ignores pointer by default when passing simple message', ( |
| WidgetTester tester, |
| ) async { |
| const tooltipMessage = 'Tooltip message'; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: Center( |
| child: Tooltip( |
| message: tooltipMessage, |
| child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Finder buttonFinder = find.text('Hover me'); |
| expect(buttonFinder, findsOneWidget); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await gesture.moveTo(tester.getCenter(buttonFinder)); |
| await tester.pumpAndSettle(); |
| |
| final Finder tooltipFinder = find.text(tooltipMessage); |
| expect(tooltipFinder, findsOneWidget); |
| |
| final Finder ignorePointerFinder = find.byType(IgnorePointer); |
| |
| final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last); |
| expect(ignorePointer.ignoring, isTrue); |
| |
| await gesture.removePointer(); |
| }); |
| |
| testWidgets( |
| "Tooltip overlay with simple message doesn't ignore pointer when passing ignorePointer: false", |
| (WidgetTester tester) async { |
| const tooltipMessage = 'Tooltip message'; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: Center( |
| child: Tooltip( |
| ignorePointer: false, |
| message: tooltipMessage, |
| child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Finder buttonFinder = find.text('Hover me'); |
| expect(buttonFinder, findsOneWidget); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await gesture.moveTo(tester.getCenter(buttonFinder)); |
| await tester.pumpAndSettle(); |
| |
| final Finder tooltipFinder = find.text(tooltipMessage); |
| expect(tooltipFinder, findsOneWidget); |
| |
| final Finder ignorePointerFinder = find.byType(IgnorePointer); |
| |
| final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last); |
| expect(ignorePointer.ignoring, isFalse); |
| |
| await gesture.removePointer(); |
| }, |
| ); |
| |
| testWidgets("Tooltip overlay doesn't ignore pointer by default when passing rich message", ( |
| WidgetTester tester, |
| ) async { |
| const InlineSpan richMessage = TextSpan( |
| children: <InlineSpan>[ |
| TextSpan( |
| text: 'Rich ', |
| style: TextStyle(fontWeight: FontWeight.bold), |
| ), |
| TextSpan(text: 'Tooltip'), |
| ], |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: Center( |
| child: Tooltip( |
| richMessage: richMessage, |
| child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Finder buttonFinder = find.text('Hover me'); |
| expect(buttonFinder, findsOneWidget); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await gesture.moveTo(tester.getCenter(buttonFinder)); |
| await tester.pumpAndSettle(); |
| |
| final Finder tooltipFinder = find.textContaining('Rich Tooltip'); |
| expect(tooltipFinder, findsOneWidget); |
| |
| final Finder ignorePointerFinder = find.byType(IgnorePointer); |
| |
| final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last); |
| expect(ignorePointer.ignoring, isFalse); |
| |
| await gesture.removePointer(); |
| }); |
| |
| testWidgets('Tooltip overlay with richMessage ignores pointer when passing ignorePointer: true', ( |
| WidgetTester tester, |
| ) async { |
| const InlineSpan richMessage = TextSpan( |
| children: <InlineSpan>[ |
| TextSpan( |
| text: 'Rich ', |
| style: TextStyle(fontWeight: FontWeight.bold), |
| ), |
| TextSpan(text: 'Tooltip'), |
| ], |
| ); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: Center( |
| child: Tooltip( |
| ignorePointer: true, |
| richMessage: richMessage, |
| child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Finder buttonFinder = find.text('Hover me'); |
| expect(buttonFinder, findsOneWidget); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await gesture.moveTo(tester.getCenter(buttonFinder)); |
| await tester.pumpAndSettle(); |
| |
| final Finder tooltipFinder = find.textContaining('Rich Tooltip'); |
| expect(tooltipFinder, findsOneWidget); |
| |
| final Finder ignorePointerFinder = find.byType(IgnorePointer); |
| |
| final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last); |
| expect(ignorePointer.ignoring, isTrue); |
| |
| await gesture.removePointer(); |
| }); |
| |
| testWidgets('Tooltip should pass its default text style down to widget spans', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip( |
| key: tooltipKey, |
| richMessage: const WidgetSpan(child: Text(tooltipText)), |
| child: Container(width: 100.0, height: 100.0, color: Colors.green[500]), |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); |
| |
| final Finder defaultTextStyle = find.ancestor( |
| of: find.text(tooltipText), |
| matching: find.byType(DefaultTextStyle), |
| ); |
| final DefaultTextStyle textStyle = tester.widget<DefaultTextStyle>(defaultTextStyle.first); |
| expect(textStyle.style.color, Colors.white); |
| expect(textStyle.style.fontFamily, 'Roboto'); |
| expect(textStyle.style.decoration, TextDecoration.none); |
| expect( |
| textStyle.style.debugLabel, |
| '((englishLike bodyMedium 2021).merge((blackMountainView bodyMedium).apply)).copyWith', |
| ); |
| }); |
| |
| testWidgets('Tooltip should apply provided text style to rich messages', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| const expectedTextStyle = TextStyle(color: Colors.orange); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Tooltip( |
| key: tooltipKey, |
| richMessage: const TextSpan(text: tooltipText), |
| textStyle: expectedTextStyle, |
| child: Container(width: 100.0, height: 100.0, color: Colors.green[500]), |
| ), |
| ), |
| ); |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); |
| |
| final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style!; |
| final Finder defaultTextStyleFinder = find.ancestor( |
| of: find.text(tooltipText), |
| matching: find.byType(DefaultTextStyle), |
| ); |
| final TextStyle defaultTextStyle = tester |
| .widget<DefaultTextStyle>(defaultTextStyleFinder.first) |
| .style; |
| expect(textStyle, same(expectedTextStyle)); |
| expect(defaultTextStyle, same(expectedTextStyle)); |
| }); |
| |
| testWidgets('Tooltip respects and prefers the given constraints over theme constraints', ( |
| WidgetTester tester, |
| ) async { |
| final tooltipKey = GlobalKey<TooltipState>(); |
| const themeConstraints = BoxConstraints.tightFor(width: 300, height: 150); |
| const tooltipConstraints = BoxConstraints.tightFor(width: 500, height: 250); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(tooltipTheme: const TooltipThemeData(constraints: themeConstraints)), |
| home: Tooltip( |
| key: tooltipKey, |
| message: tooltipText, |
| constraints: tooltipConstraints, |
| padding: EdgeInsets.zero, |
| child: const ColoredBox(color: Colors.green), |
| ), |
| ), |
| ); |
| |
| tooltipKey.currentState?.ensureTooltipVisible(); |
| await tester.pump(const Duration(seconds: 2)); |
| |
| final Finder textAncestors = find.ancestor( |
| of: find.text(tooltipText), |
| matching: find.byWidgetPredicate((_) => true), |
| ); |
| expect(tester.element(textAncestors.first).size, equals(tooltipConstraints.biggest)); |
| }); |
| |
| // This is a regression test for https://github.com/flutter/flutter/issues/167359. |
| testWidgets('Tooltip does not show while transitioning from another page', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| appBar: AppBar( |
| leading: const Center( |
| child: Tooltip(message: 'Hello', child: Text('World')), |
| ), |
| ), |
| body: Builder( |
| builder: (BuildContext context) { |
| return TextButton( |
| onPressed: () => Navigator.push( |
| context, |
| CupertinoPageRoute<void>( |
| builder: (BuildContext context) => |
| Scaffold(appBar: AppBar(title: const Text('Second Page'))), |
| ), |
| ), |
| child: const Text('Go to Second Page'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.text('Go to Second Page')); |
| await tester.pumpAndSettle(); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await tester.tap(find.byType(BackButton)); |
| await tester.pump(const Duration(milliseconds: 250)); |
| await gesture.moveTo(tester.getCenter(find.text('World'))); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.takeException(), isNull); |
| }); |
| |
| // This is a regression test for https://github.com/flutter/flutter/issues/169741. |
| testWidgets( |
| 'Tooltip does not show while transitioning from another route with secondary animation', |
| (WidgetTester tester) async { |
| final observer = TransitionDurationObserver(); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorObservers: <NavigatorObserver>[observer], |
| home: Scaffold( |
| body: Builder( |
| builder: (BuildContext context) { |
| return TextButton( |
| onPressed: () => Navigator.push( |
| context, |
| CupertinoPageRoute<void>( |
| builder: (BuildContext context) => Scaffold( |
| appBar: AppBar( |
| leading: const Tooltip(message: 'Hello', child: Text('World')), |
| ), |
| body: TextButton( |
| onPressed: () { |
| Navigator.of(context).push( |
| MaterialPageRoute<void>( |
| builder: (BuildContext context) { |
| return Scaffold(appBar: AppBar(title: const Text('Third Page'))); |
| }, |
| ), |
| ); |
| }, |
| child: const Text('Go to Third Page'), |
| ), |
| ), |
| ), |
| ), |
| child: const Text('Go to Second Page'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.text('Go to Second Page'), findsOneWidget); |
| await tester.tap(find.text('Go to Second Page')); |
| await tester.pumpAndSettle(); |
| expect(find.text('Go to Third Page'), findsOneWidget); |
| |
| await tester.tap(find.text('Go to Third Page')); |
| await tester.pumpAndSettle(); |
| expect(find.text('Third Page'), findsOneWidget); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await tester.tap(find.byType(BackButton)); |
| await observer.pumpPastTransition(tester); |
| await gesture.moveTo(tester.getCenter(find.text('World'))); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.takeException(), isNull); |
| }, |
| ); |
| |
| /// This is a regression test for https://github.com/flutter/flutter/issues/168545 |
| testWidgets('The Tooltip on the ModalBottomSheet can still be displayed after showMenu.', ( |
| WidgetTester tester, |
| ) async { |
| final navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigatorKey, |
| home: const Scaffold(body: Placeholder()), |
| ), |
| ); |
| showModalBottomSheet<void>( |
| context: navigatorKey.currentContext!, |
| builder: (_) { |
| return const Center( |
| child: Tooltip(message: 'Hello', child: Text('World')), |
| ); |
| }, |
| ); |
| await tester.pumpAndSettle(); |
| showMenu<void>( |
| context: navigatorKey.currentContext!, |
| items: <PopupMenuEntry<int>>[const PopupMenuItem<int>(value: 0, child: Text('item 1'))], |
| position: RelativeRect.fill, |
| ); |
| await tester.pumpAndSettle(); |
| navigatorKey.currentState!.pop(); |
| await tester.pumpAndSettle(); |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| await gesture.moveTo(tester.getCenter(find.text('World'))); |
| await tester.pumpAndSettle(); |
| expect(find.text('Hello'), findsOne); |
| await gesture.removePointer(); |
| }); |
| |
| testWidgets('Custom tooltip positioning - positionDelegate parameter', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: Center( |
| child: Tooltip( |
| message: tooltipText, |
| positionDelegate: (TooltipPositionContext context) { |
| // Align on top right of box with bottom left of tooltip. |
| return Offset( |
| context.target.dx + (context.targetSize.width / 2), |
| context.target.dy - (context.targetSize.height / 2) - context.tooltipSize.height, |
| ); |
| }, |
| child: const SizedBox(width: 50, height: 50), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.longPress(find.byType(Tooltip)); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(find.text(tooltipText), findsOneWidget); |
| |
| final Offset targetCenter = tester.getCenter(find.byType(Tooltip)); |
| final Offset tooltipPosition = tester.getTopLeft(_findTooltipContainer(tooltipText)); |
| |
| // The tooltip should be positioned at target + (25, -25-32). |
| expect(tooltipPosition.dx, closeTo(targetCenter.dx + 25, 5.0)); |
| expect(tooltipPosition.dy, closeTo(targetCenter.dy - 25 - 32, 5.0)); |
| }); |
| |
| testWidgets('Tooltip does not crash at zero area', (WidgetTester tester) async { |
| final key = GlobalKey<TooltipState>(); |
| tester.view.physicalSize = Size.zero; |
| addTearDown(tester.view.reset); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Center( |
| child: Tooltip(key: key, message: 'X'), |
| ), |
| ), |
| ); |
| expect(tester.getSize(find.byType(Tooltip)), Size.zero); |
| key.currentState!.ensureTooltipVisible(); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(Tooltip)), Size.zero); |
| expect(find.text('X'), findsOne); |
| }); |
| } |
| |
| Future<void> _testGestureTap(WidgetTester tester, Finder tooltip) async { |
| await tester.tap(tooltip); |
| await tester.pump(const Duration(milliseconds: 10)); |
| } |