mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Bumps the Dart version to 3.8 across the repo (excluding engine/src/flutter/third_party) and applies formatting updates from Dart 3.8. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
3590 lines
120 KiB
Dart
3590 lines
120 KiB
Dart
// 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';
|
|
import '../widgets/feedback_tester.dart';
|
|
import '../widgets/semantics_tester.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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 double customMarginValue = 10.0;
|
|
final GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 respects ambient Directionality', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/40702.
|
|
Widget buildApp(String text, TextDirection textDirection) {
|
|
return MaterialApp(
|
|
home: Directionality(
|
|
textDirection: textDirection,
|
|
child: Center(
|
|
child: Tooltip(
|
|
message: text,
|
|
child: Container(width: 100.0, height: 100.0, color: Colors.green[500]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(tooltipText, TextDirection.rtl));
|
|
await tester.longPress(find.byType(Tooltip));
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
RenderParagraph tooltipRenderParagraph = tester.renderObject<RenderParagraph>(
|
|
find.text(tooltipText),
|
|
);
|
|
expect(tooltipRenderParagraph.textDirection, TextDirection.rtl);
|
|
|
|
await tester.pump(const Duration(seconds: 10));
|
|
await tester.pumpAndSettle();
|
|
await tester.pump();
|
|
|
|
await tester.pumpWidget(buildApp(tooltipText, TextDirection.ltr));
|
|
await tester.longPress(find.byType(Tooltip));
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
tooltipRenderParagraph = tester.renderObject<RenderParagraph>(find.text(tooltipText));
|
|
expect(tooltipRenderParagraph.textDirection, TextDirection.ltr);
|
|
});
|
|
|
|
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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> 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('Tooltip stays after long press', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
message: tooltipText,
|
|
child: Container(width: 100.0, height: 100.0, color: Colors.green[500]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip));
|
|
|
|
// long press reveals tooltip
|
|
await tester.pump(kLongPressTimeout);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
await gesture.up();
|
|
|
|
// tap (down, up) gesture hides tooltip, since its not
|
|
// a long press
|
|
await tester.tap(tooltip);
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
// long press once more
|
|
gesture = await tester.startGesture(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await tester.pump(kLongPressTimeout);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// keep holding the long press, should still show tooltip
|
|
await tester.pump(kLongPressTimeout);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Tooltip dismiss countdown begins on long press release', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Specs: https://github.com/flutter/flutter/issues/4182
|
|
const Duration showDuration = Duration(seconds: 1);
|
|
const Duration eternity = Duration(days: 9999);
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress, showDuration: showDuration);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip));
|
|
|
|
await tester.pump(kLongPressTimeout);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
// Keep holding to prevent the tooltip from dismissing.
|
|
await tester.pump(eternity);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
await tester.pump();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
await tester.pump(showDuration);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip is dismissed after a long press and showDuration expired', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Duration showDuration = Duration(seconds: 3);
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress, showDuration: showDuration);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(tooltip));
|
|
|
|
// Long press reveals tooltip
|
|
await tester.pump(kLongPressTimeout);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
await gesture.up();
|
|
|
|
// Tooltip is dismissed after showDuration expired
|
|
await tester.pump(showDuration);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip is dismissed after a tap and showDuration expired', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Duration showDuration = Duration(seconds: 3);
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, showDuration: showDuration);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Tooltip is dismissed after showDuration expired
|
|
await tester.pump(showDuration);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip is dismissed after tap to dismiss immediately', (WidgetTester tester) async {
|
|
// This test relies on not ignoring pointer events.
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, ignorePointer: false);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
// Tap to trigger the tooltip.
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Tap to dismiss the tooltip. Tooltip is dismissed immediately.
|
|
await _testGestureTap(tester, find.text(tooltipText));
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip is not dismissed after tap if enableTapToDismiss is false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// This test relies on not ignoring pointer events.
|
|
await setWidgetForTooltipMode(
|
|
tester,
|
|
TooltipTriggerMode.tap,
|
|
enableTapToDismiss: false,
|
|
ignorePointer: false,
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
// Tap to trigger the tooltip.
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Tap the tooltip. Tooltip is not dismissed .
|
|
await _testGestureTap(tester, find.text(tooltipText));
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'Tooltip is dismissed after a tap and showDuration expired when competing with a GestureDetector',
|
|
(WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/98854
|
|
const Duration showDuration = Duration(seconds: 3);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: GestureDetector(
|
|
onVerticalDragStart: (_) {
|
|
/* Do nothing */
|
|
},
|
|
child: const Tooltip(
|
|
message: tooltipText,
|
|
triggerMode: TooltipTriggerMode.tap,
|
|
showDuration: showDuration,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await tester.tap(tooltip);
|
|
// Wait for GestureArena disambiguation, delay is kPressTimeout to disambiguate
|
|
// between onTap and onVerticalDragStart
|
|
await tester.pump(kPressTimeout);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Tooltip is dismissed after showDuration expired
|
|
await tester.pump(showDuration);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tooltipText), findsNothing);
|
|
},
|
|
);
|
|
|
|
testWidgets('Dispatch the mouse events before tip overlay detached', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/96890
|
|
const Duration waitDuration = Duration.zero;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
return gesture.removePointer();
|
|
});
|
|
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: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Trigger the tip overlay.
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
await gesture.moveTo(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
|
|
// Remove the `Tooltip` widget.
|
|
await tester.pumpWidget(const MaterialApp(home: Center(child: SizedBox.shrink())));
|
|
|
|
// The tooltip should be removed, including the overlay child.
|
|
expect(find.text(tooltipText), findsNothing);
|
|
expect(find.byTooltip(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Calling ensureTooltipVisible on an unmounted TooltipState returns false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/95851
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(message: tooltipText, child: SizedBox(width: 100.0, height: 100.0)),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TooltipState tooltipState = tester.state(find.byType(Tooltip));
|
|
expect(tooltipState.ensureTooltipVisible(), true);
|
|
|
|
// Remove the tooltip.
|
|
await tester.pumpWidget(const MaterialApp(home: Center(child: SizedBox.shrink())));
|
|
|
|
expect(tooltipState.ensureTooltipVisible(), false);
|
|
});
|
|
|
|
testWidgets('Tooltip shows/hides when hovered', (WidgetTester tester) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
return gesture.removePointer();
|
|
});
|
|
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: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
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();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Wait a looong time to make sure that it doesn't go away if the mouse is
|
|
// still over the widget.
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
|
|
// Wait for it to disappear.
|
|
await tester.pumpAndSettle();
|
|
await gesture.removePointer();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/141644.
|
|
// This allows the user to quickly explore the UI via tooltips.
|
|
testWidgets('Tooltip shows without delay when the mouse moves from another tooltip', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Duration waitDuration = Durations.extralong1;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
return gesture.removePointer();
|
|
});
|
|
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: Column(
|
|
children: <Widget>[
|
|
Tooltip(
|
|
message: 'first tooltip',
|
|
waitDuration: waitDuration,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
Tooltip(
|
|
message: 'last tooltip',
|
|
waitDuration: waitDuration,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(find.byType(Tooltip).first));
|
|
await tester.pump();
|
|
// Wait for the first tooltip to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(find.text('first tooltip'), findsOneWidget);
|
|
expect(find.text('last tooltip'), findsNothing);
|
|
|
|
// Move to the second tooltip and expect it to show up immediately.
|
|
await gesture.moveTo(tester.getCenter(find.byType(Tooltip).last));
|
|
await tester.pump();
|
|
expect(find.text('first tooltip'), findsNothing);
|
|
expect(find.text('last tooltip'), findsOneWidget);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/142045.
|
|
testWidgets(
|
|
'Tooltip shows/hides when the mouse hovers, and then exits and re-enters in quick succession',
|
|
(WidgetTester tester) async {
|
|
const Duration waitDuration = Durations.extralong1;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
return gesture.removePointer();
|
|
});
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(const Offset(1.0, 1.0));
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
exitDuration: waitDuration,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Future<void> mouseEnterAndWaitUntilVisible() async {
|
|
await gesture.moveTo(tester.getCenter(find.byType(Tooltip)));
|
|
await tester.pump();
|
|
await tester.pump(waitDuration);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOne);
|
|
}
|
|
|
|
Future<void> mouseExit() async {
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
}
|
|
|
|
Future<void> performSequence(Iterable<Future<void> Function()> actions) async {
|
|
for (final Future<void> Function() action in actions) {
|
|
await action();
|
|
}
|
|
}
|
|
|
|
await performSequence(<Future<void> Function()>[mouseEnterAndWaitUntilVisible]);
|
|
expect(find.text(tooltipText), findsOne);
|
|
|
|
// Wait for reset.
|
|
await mouseExit();
|
|
await tester.pump(const Duration(hours: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await performSequence(<Future<void> Function()>[
|
|
mouseEnterAndWaitUntilVisible,
|
|
mouseExit,
|
|
mouseEnterAndWaitUntilVisible,
|
|
]);
|
|
expect(find.text(tooltipText), findsOne);
|
|
|
|
// Wait for reset.
|
|
await mouseExit();
|
|
await tester.pump(const Duration(hours: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await performSequence(<Future<void> Function()>[
|
|
mouseEnterAndWaitUntilVisible,
|
|
mouseExit,
|
|
mouseEnterAndWaitUntilVisible,
|
|
mouseExit,
|
|
mouseEnterAndWaitUntilVisible,
|
|
]);
|
|
expect(find.text(tooltipText), findsOne);
|
|
|
|
// Wait for reset.
|
|
await mouseExit();
|
|
await tester.pump(const Duration(hours: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
},
|
|
);
|
|
|
|
testWidgets('Tooltip text is also hoverable', (WidgetTester tester) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
return gesture.removePointer();
|
|
});
|
|
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: Center(
|
|
child: Tooltip(
|
|
// This test relies on not ignoring pointer events.
|
|
ignorePointer: false,
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
child: Text('I am tool tip'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Wait a looong time to make sure that it doesn't go away if the mouse is
|
|
// still over the widget.
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Hover to the tool tip text and verify the tooltip doesn't go away.
|
|
await gesture.moveTo(tester.getTopLeft(find.text(tooltipText)));
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
|
|
// Wait for it to disappear.
|
|
await tester.pumpAndSettle();
|
|
await gesture.removePointer();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip should not show more than one tooltip when hovered', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Duration waitDuration = Duration(milliseconds: 500);
|
|
final UniqueKey innerKey = UniqueKey();
|
|
final UniqueKey outerKey = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: 'Outer',
|
|
child: Container(
|
|
key: outerKey,
|
|
width: 100,
|
|
height: 100,
|
|
alignment: Alignment.centerRight,
|
|
child: Tooltip(
|
|
message: 'Inner',
|
|
child: SizedBox(key: innerKey, width: 25, height: 100),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
TestGesture? gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
gesture?.removePointer();
|
|
});
|
|
|
|
// Both the inner and outer containers have tooltips associated with them, but only
|
|
// the currently hovered one should appear, even though the pointer is inside both.
|
|
final Finder outer = find.byKey(outerKey);
|
|
final Finder inner = find.byKey(innerKey);
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(outer));
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(inner));
|
|
await tester.pump();
|
|
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
|
|
expect(find.text('Outer'), findsNothing);
|
|
expect(find.text('Inner'), findsOneWidget);
|
|
await gesture.moveTo(tester.getCenter(outer));
|
|
await tester.pump();
|
|
// Wait for it to switch.
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Outer'), findsOneWidget);
|
|
expect(find.text('Inner'), findsNothing);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
|
|
// Wait for all tooltips to disappear.
|
|
await tester.pumpAndSettle();
|
|
await gesture.removePointer();
|
|
gesture = null;
|
|
expect(find.text('Outer'), findsNothing);
|
|
expect(find.text('Inner'), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip can be dismissed by escape key', (WidgetTester tester) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
TestGesture? gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
if (gesture != null) {
|
|
return gesture.removePointer();
|
|
}
|
|
});
|
|
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: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
child: Text('I am tool tip'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Try to dismiss the tooltip with the shortcut key
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pumpAndSettle();
|
|
await gesture.removePointer();
|
|
gesture = null;
|
|
});
|
|
|
|
testWidgets('Multiple Tooltips are dismissed by escape key', (WidgetTester tester) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Tooltip(
|
|
message: 'message1',
|
|
waitDuration: waitDuration,
|
|
showDuration: Duration(days: 1),
|
|
child: Text('tooltip1'),
|
|
),
|
|
Spacer(flex: 2),
|
|
Tooltip(
|
|
message: 'message2',
|
|
waitDuration: waitDuration,
|
|
showDuration: Duration(days: 1),
|
|
child: Text('tooltip2'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
tester.state<TooltipState>(find.byTooltip('message1')).ensureTooltipVisible();
|
|
tester.state<TooltipState>(find.byTooltip('message2')).ensureTooltipVisible();
|
|
await tester.pump();
|
|
await tester.pump(waitDuration);
|
|
// Make sure both messages are on the screen.
|
|
expect(find.text('message1'), findsOneWidget);
|
|
expect(find.text('message2'), findsOneWidget);
|
|
|
|
// Try to dismiss the tooltip with the shortcut key
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('message1'), findsNothing);
|
|
expect(find.text('message2'), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip does not attempt to show after unmount', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/54096.
|
|
const Duration waitDuration = Duration(seconds: 1);
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(() async {
|
|
return gesture.removePointer();
|
|
});
|
|
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: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
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();
|
|
|
|
// Pump another random widget to unmount the Tooltip widget.
|
|
await tester.pumpWidget(const MaterialApp(home: Center(child: SizedBox())));
|
|
|
|
// If the issue regresses, an exception will be thrown while we are waiting.
|
|
await tester.pump(waitDuration);
|
|
});
|
|
|
|
testWidgets('Does tooltip contribute semantics', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
|
|
late final OverlayEntry entry;
|
|
addTearDown(
|
|
() => entry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: 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,
|
|
child: const SizedBox(width: 10.0, height: 10.0),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestSemantics expected = TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics.rootChild(id: 1, tooltip: 'TIP', textDirection: TextDirection.ltr),
|
|
],
|
|
);
|
|
|
|
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
|
|
|
|
// This triggers a rebuild of the semantics because the tree changes.
|
|
tooltipKey.currentState?.ensureTooltipVisible();
|
|
|
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
|
|
|
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('Tooltip semantics does not merge into child', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
|
|
late final OverlayEntry entry;
|
|
addTearDown(
|
|
() => entry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
entry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return ListView(
|
|
children: <Widget>[
|
|
const Text('before'),
|
|
Tooltip(
|
|
key: tooltipKey,
|
|
showDuration: const Duration(seconds: 50),
|
|
message: 'B',
|
|
child: const Text('child'),
|
|
),
|
|
const Text('after'),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
tooltipKey.currentState?.ensureTooltipVisible();
|
|
|
|
// Starts the animation.
|
|
await tester.pump();
|
|
// Make sure the fade in animation has started and the tooltip isn't transparent.
|
|
await tester.pump(const Duration(seconds: 2));
|
|
|
|
expect(
|
|
semantics,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
|
|
children: <TestSemantics>[
|
|
TestSemantics(label: 'before'),
|
|
TestSemantics(
|
|
label: 'child',
|
|
tooltip: 'B',
|
|
children: <TestSemantics>[TestSemantics(label: 'B')],
|
|
),
|
|
TestSemantics(label: 'after'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
ignoreTransform: true,
|
|
),
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
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 GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
|
|
const String textSpan1Text = 'I am a rich tooltip message. ';
|
|
const String 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('Haptic feedback', (WidgetTester tester) async {
|
|
final FeedbackTester feedback = FeedbackTester();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: 'Foo',
|
|
child: Container(width: 100.0, height: 100.0, color: Colors.green[500]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.longPress(find.byType(Tooltip));
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
expect(feedback.hapticCount, 1);
|
|
|
|
feedback.dispose();
|
|
});
|
|
|
|
testWidgets('Semantics included', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: 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', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(message: 'Foo', excludeFromSemantics: true, 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', (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: 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"']);
|
|
});
|
|
testWidgets('default Tooltip debugFillProperties with richMessage', (WidgetTester tester) async {
|
|
final DiagnosticPropertiesBuilder 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 DiagnosticPropertiesBuilder 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 triggers on tap when trigger mode is tap', (WidgetTester tester) async {
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Tooltip triggers on long press when mode is long press', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureLongPress(tester, tooltip);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Tooltip does not trigger on tap when trigger mode is longPress', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip does not trigger when trigger mode is manual', (WidgetTester tester) async {
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.manual);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
await _testGestureLongPress(tester, tooltip);
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Tooltip onTriggered is called when Tooltip triggers', (WidgetTester tester) async {
|
|
bool onTriggeredCalled = false;
|
|
void onTriggered() => onTriggeredCalled = true;
|
|
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.longPress, onTriggered: onTriggered);
|
|
Finder tooltip = find.byType(Tooltip);
|
|
await _testGestureLongPress(tester, tooltip);
|
|
expect(onTriggeredCalled, true);
|
|
|
|
onTriggeredCalled = false;
|
|
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, onTriggered: onTriggered);
|
|
tooltip = find.byType(Tooltip);
|
|
await _testGestureTap(tester, tooltip);
|
|
expect(onTriggeredCalled, true);
|
|
});
|
|
|
|
testWidgets('Tooltip onTriggered is not called when Tooltip is hovered', (
|
|
WidgetTester tester,
|
|
) async {
|
|
bool onTriggeredCalled = false;
|
|
void onTriggered() => onTriggeredCalled = true;
|
|
|
|
const Duration waitDuration = Duration.zero;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(Offset.zero);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
onTriggered: onTriggered,
|
|
child: const SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
await gesture.moveTo(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(onTriggeredCalled, false);
|
|
});
|
|
|
|
testWidgets('dismissAllToolTips dismisses hovered tooltips', (WidgetTester tester) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(Offset.zero);
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
await gesture.moveTo(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
expect(Tooltip.dismissAllToolTips(), isTrue);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Hovered tooltips do not dismiss after showDuration', (WidgetTester tester) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(Offset.zero);
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder tooltip = find.byType(Tooltip);
|
|
await gesture.moveTo(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
// Wait for it to appear.
|
|
await tester.pump(waitDuration);
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
await tester.longPressAt(tester.getCenter(tooltip));
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
// Still visible.
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
|
|
// Still visible.
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The tooltip is no longer hovered and becomes invisible.
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
});
|
|
|
|
testWidgets('Hovered tooltips with showDuration set do dismiss when hovering elsewhere', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Duration waitDuration = Duration.zero;
|
|
const Duration showDuration = Duration(seconds: 1);
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: waitDuration,
|
|
showDuration: showDuration,
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byTooltip(tooltipText)));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible when hovered.',
|
|
);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
// Set a duration equal to the default exit
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tooltip should not wait for showDuration before it hides itself.',
|
|
);
|
|
});
|
|
|
|
testWidgets('Hovered tooltips hide after stopping the hover', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: SizedBox.square(
|
|
dimension: 10.0,
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox.expand(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byTooltip(tooltipText)));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible when hovered.',
|
|
);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tooltip should be hidden when no longer hovered.',
|
|
);
|
|
});
|
|
|
|
testWidgets('Hovered tooltips hide after stopping the hover and exitDuration expires', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Duration exitDuration = Duration(seconds: 1);
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: SizedBox.square(
|
|
dimension: 10.0,
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
exitDuration: exitDuration,
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox.expand(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byTooltip(tooltipText)));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible when hovered.',
|
|
);
|
|
|
|
await gesture.moveTo(Offset.zero);
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should wait until exitDuration expires before being hidden',
|
|
);
|
|
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tooltip should be hidden when no longer hovered.',
|
|
);
|
|
});
|
|
|
|
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 trigger mode ignores mouse events', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Tooltip(
|
|
message: tooltipText,
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox.expand(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture mouseGesture = await tester.startGesture(
|
|
tester.getCenter(find.byTooltip(tooltipText)),
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await tester.pump(kLongPressTimeout + kPressTimeout);
|
|
await mouseGesture.up();
|
|
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
|
|
final TestGesture touchGesture = await tester.startGesture(
|
|
tester.getCenter(find.byTooltip(tooltipText)),
|
|
);
|
|
await tester.pump(kLongPressTimeout + kPressTimeout);
|
|
await touchGesture.up();
|
|
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Tooltip does not block other mouse regions', (WidgetTester tester) async {
|
|
bool entered = false;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: MouseRegion(
|
|
onEnter: (PointerEnterEvent event) {
|
|
entered = true;
|
|
},
|
|
child: const Tooltip(message: tooltipText, child: SizedBox.expand()),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(entered, isFalse);
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byType(Tooltip)));
|
|
await gesture.removePointer();
|
|
|
|
expect(entered, isTrue);
|
|
});
|
|
|
|
testWidgets('Does not rebuild on mouse connect/disconnect', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/117627
|
|
int buildCount = 0;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Tooltip(
|
|
message: tooltipText,
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
buildCount += 1;
|
|
return const SizedBox.expand();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(buildCount, 1);
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await tester.pump();
|
|
await gesture.removePointer();
|
|
await tester.pump();
|
|
|
|
expect(buildCount, 1);
|
|
});
|
|
|
|
testWidgets('Tooltip should not ignore users tap on richMessage', (WidgetTester tester) async {
|
|
bool isTapped = false;
|
|
final TapGestureRecognizer 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('Hold mouse button down and hover over the Tooltip widget', (
|
|
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 TestGesture mouseGesture = await tester.startGesture(
|
|
Offset.zero,
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
addTearDown(mouseGesture.removePointer);
|
|
await mouseGesture.moveTo(tester.getCenter(find.byTooltip(tooltipText)));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible when hovered.',
|
|
);
|
|
|
|
await mouseGesture.up();
|
|
await tester.pump(const Duration(days: 10));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible even when there is a PointerUp when hovered.',
|
|
);
|
|
|
|
await mouseGesture.moveTo(Offset.zero);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tooltip should be dismissed with no hovering mouse cursor.',
|
|
);
|
|
});
|
|
|
|
testWidgets('Hovered text should dismiss when clicked outside', (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(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Avoid using startGesture here to avoid the PointDown event from also being
|
|
// interpreted as a PointHover event by the Tooltip.
|
|
final TestGesture mouseGesture1 = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(mouseGesture1.removePointer);
|
|
await mouseGesture1.moveTo(tester.getCenter(find.byTooltip(tooltipText)));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible when hovered.',
|
|
);
|
|
|
|
// Tapping on the Tooltip widget should dismiss the tooltip, since the
|
|
// trigger mode is longPress.
|
|
await tester.tap(find.byTooltip(tooltipText));
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tooltipText), findsNothing);
|
|
await mouseGesture1.removePointer();
|
|
|
|
final TestGesture mouseGesture2 = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
addTearDown(mouseGesture2.removePointer);
|
|
await mouseGesture2.moveTo(tester.getCenter(find.byTooltip(tooltipText)));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsOneWidget,
|
|
reason: 'Tooltip should be visible when hovered.',
|
|
);
|
|
|
|
await tester.tapAt(Offset.zero);
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tapping outside of the Tooltip widget should dismiss the tooltip.',
|
|
);
|
|
});
|
|
|
|
testWidgets('Mouse tap and hover over the Tooltip widget', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/127575 .
|
|
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(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The PointDown event is also interpreted as a PointHover event by the
|
|
// Tooltip. This should be pretty rare but since it's more of a tap event
|
|
// than a hover event, the tooltip shouldn't show unless the triggerMode
|
|
// is set to tap.
|
|
final TestGesture mouseGesture1 = await tester.startGesture(
|
|
tester.getCenter(find.byTooltip(tooltipText)),
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
addTearDown(mouseGesture1.removePointer);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tooltip should NOT be visible when hovered and tapped, when trigger mode is not tap',
|
|
);
|
|
await mouseGesture1.up();
|
|
await mouseGesture1.removePointer();
|
|
await tester.pump(const Duration(days: 10));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: SizedBox.square(
|
|
dimension: 10.0,
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: Duration(seconds: 1),
|
|
triggerMode: TooltipTriggerMode.tap,
|
|
child: SizedBox.expand(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture mouseGesture2 = await tester.startGesture(
|
|
tester.getCenter(find.byTooltip(tooltipText)),
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
addTearDown(mouseGesture2.removePointer);
|
|
// The tap should be ignored, since Tooltip does not track "trigger gestures"
|
|
// for mouse devices.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await mouseGesture2.up();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(
|
|
find.text(tooltipText),
|
|
findsNothing,
|
|
reason: 'Tooltip should NOT be visible when hovered and tapped, when trigger mode is tap',
|
|
);
|
|
});
|
|
|
|
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 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 does not initialize animation controller in dispose process', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: Duration(seconds: 1),
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox.square(dimension: 50),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Tooltip)));
|
|
await tester.pumpWidget(const SizedBox());
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets(
|
|
'Tooltip does not crash when showing the tooltip but the OverlayPortal is unmounted, during dispose',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: SelectionArea(
|
|
child: Center(
|
|
child: Tooltip(
|
|
message: tooltipText,
|
|
waitDuration: Duration(seconds: 1),
|
|
triggerMode: TooltipTriggerMode.longPress,
|
|
child: SizedBox.square(dimension: 50),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TooltipState tooltipState = tester.state(find.byType(Tooltip));
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Tooltip)));
|
|
tooltipState.ensureTooltipVisible();
|
|
await tester.pumpWidget(const SizedBox());
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
},
|
|
);
|
|
|
|
testWidgets('Tooltip is not selectable', (WidgetTester tester) async {
|
|
const String 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 String 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 String 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 GlobalKey<TooltipState> 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 GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
|
|
const TextStyle 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 GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
|
|
const BoxConstraints themeConstraints = BoxConstraints.tightFor(width: 300, height: 150);
|
|
const BoxConstraints 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/168545
|
|
testWidgets('The Tooltip on the ModalBottomSheet can still be displayed after showMenu.', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final GlobalKey<NavigatorState> 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();
|
|
});
|
|
}
|
|
|
|
Future<void> setWidgetForTooltipMode(
|
|
WidgetTester tester,
|
|
TooltipTriggerMode triggerMode, {
|
|
Duration? showDuration,
|
|
bool? enableTapToDismiss,
|
|
TooltipTriggeredCallback? onTriggered,
|
|
bool? ignorePointer,
|
|
}) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Tooltip(
|
|
message: tooltipText,
|
|
triggerMode: triggerMode,
|
|
onTriggered: onTriggered,
|
|
showDuration: showDuration,
|
|
enableTapToDismiss: enableTapToDismiss ?? true,
|
|
ignorePointer: ignorePointer,
|
|
child: const SizedBox(width: 100.0, height: 100.0),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _testGestureLongPress(WidgetTester tester, Finder tooltip) async {
|
|
final TestGesture gestureLongPress = await tester.startGesture(tester.getCenter(tooltip));
|
|
await tester.pump();
|
|
await tester.pump(kLongPressTimeout);
|
|
await gestureLongPress.up();
|
|
await tester.pump();
|
|
}
|
|
|
|
Future<void> _testGestureTap(WidgetTester tester, Finder tooltip) async {
|
|
await tester.tap(tooltip);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
}
|
|
|
|
SemanticsNode _findDebugSemantics(RenderObject object) {
|
|
return object.debugSemantics ?? _findDebugSemantics(object.parent!);
|
|
}
|