Unverified Commit 7d794725 authored by Kostia Sokolovskyi's avatar Kostia Sokolovskyi Committed by GitHub

SearchAnchor should dispose created FocusNode and SearchController. (#136120)

parent e0b8ec07
...@@ -310,15 +310,12 @@ class _SearchAnchorState extends State<SearchAnchor> { ...@@ -310,15 +310,12 @@ class _SearchAnchorState extends State<SearchAnchor> {
bool _anchorIsVisible = true; bool _anchorIsVisible = true;
final GlobalKey _anchorKey = GlobalKey(); final GlobalKey _anchorKey = GlobalKey();
bool get _viewIsOpen => !_anchorIsVisible; bool get _viewIsOpen => !_anchorIsVisible;
late SearchController? _internalSearchController; SearchController? _internalSearchController;
SearchController get _searchController => widget.searchController ?? _internalSearchController!; SearchController get _searchController => widget.searchController ?? (_internalSearchController ??= SearchController());
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.searchController == null) {
_internalSearchController = SearchController();
}
_searchController._attach(this); _searchController._attach(this);
} }
...@@ -334,11 +331,21 @@ class _SearchAnchorState extends State<SearchAnchor> { ...@@ -334,11 +331,21 @@ class _SearchAnchorState extends State<SearchAnchor> {
_screenSize = updatedScreenSize; _screenSize = updatedScreenSize;
} }
@override
void didUpdateWidget(SearchAnchor oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.searchController != widget.searchController) {
oldWidget.searchController?._detach(this);
_searchController._attach(this);
}
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
_searchController._detach(this); widget.searchController?._detach(this);
_internalSearchController = null; _internalSearchController?._detach(this);
_internalSearchController?.dispose();
} }
void _openView() { void _openView() {
...@@ -680,12 +687,6 @@ class _ViewContentState extends State<_ViewContent> { ...@@ -680,12 +687,6 @@ class _ViewContentState extends State<_ViewContent> {
} }
} }
@override
void dispose(){
_controller.removeListener(updateSuggestions);
super.dispose();
}
@override @override
void didUpdateWidget(covariant _ViewContent oldWidget) { void didUpdateWidget(covariant _ViewContent oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
...@@ -710,6 +711,13 @@ class _ViewContentState extends State<_ViewContent> { ...@@ -710,6 +711,13 @@ class _ViewContentState extends State<_ViewContent> {
unawaited(updateSuggestions()); unawaited(updateSuggestions());
} }
@override
void dispose() {
_controller.removeListener(updateSuggestions);
_focusNode.dispose();
super.dispose();
}
Widget viewBuilder(Iterable<Widget> suggestions) { Widget viewBuilder(Iterable<Widget> suggestions) {
if (widget.viewBuilder == null) { if (widget.viewBuilder == null) {
return MediaQuery.removePadding( return MediaQuery.removePadding(
...@@ -949,15 +957,18 @@ class SearchController extends TextEditingController { ...@@ -949,15 +957,18 @@ class SearchController extends TextEditingController {
// it controls. // it controls.
_SearchAnchorState? _anchor; _SearchAnchorState? _anchor;
/// Whether this controller has associated search anchor.
bool get isAttached => _anchor != null;
/// Whether or not the associated search view is currently open. /// Whether or not the associated search view is currently open.
bool get isOpen { bool get isOpen {
assert(_anchor != null); assert(isAttached);
return _anchor!._viewIsOpen; return _anchor!._viewIsOpen;
} }
/// Opens the search view that this controller is associated with. /// Opens the search view that this controller is associated with.
void openView() { void openView() {
assert(_anchor != null); assert(isAttached);
_anchor!._openView(); _anchor!._openView();
} }
...@@ -966,7 +977,7 @@ class SearchController extends TextEditingController { ...@@ -966,7 +977,7 @@ class SearchController extends TextEditingController {
/// If `selectedText` is given, then the text value of the controller is set to /// If `selectedText` is given, then the text value of the controller is set to
/// `selectedText`. /// `selectedText`.
void closeView(String? selectedText) { void closeView(String? selectedText) {
assert(_anchor != null); assert(isAttached);
_anchor!._closeView(selectedText); _anchor!._closeView(selectedText);
} }
...@@ -1166,7 +1177,8 @@ class SearchBar extends StatefulWidget { ...@@ -1166,7 +1177,8 @@ class SearchBar extends StatefulWidget {
class _SearchBarState extends State<SearchBar> { class _SearchBarState extends State<SearchBar> {
late final MaterialStatesController _internalStatesController; late final MaterialStatesController _internalStatesController;
late final FocusNode _focusNode; FocusNode? _internalFocusNode;
FocusNode get _focusNode => widget.focusNode ?? (_internalFocusNode ??= FocusNode());
@override @override
void initState() { void initState() {
...@@ -1175,15 +1187,12 @@ class _SearchBarState extends State<SearchBar> { ...@@ -1175,15 +1187,12 @@ class _SearchBarState extends State<SearchBar> {
_internalStatesController.addListener(() { _internalStatesController.addListener(() {
setState(() {}); setState(() {});
}); });
_focusNode = widget.focusNode ?? FocusNode();
} }
@override @override
void dispose() { void dispose() {
_internalStatesController.dispose(); _internalStatesController.dispose();
if (widget.focusNode == null) { _internalFocusNode?.dispose();
_focusNode.dispose();
}
super.dispose(); super.dispose();
} }
......
...@@ -33,9 +33,10 @@ void main() { ...@@ -33,9 +33,10 @@ void main() {
checkSearchBarDefaults(tester, colorScheme, material); checkSearchBarDefaults(tester, colorScheme, material);
}); });
testWidgets('SearchBar respects controller property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects controller property', (WidgetTester tester) async {
const String defaultText = 'default text'; const String defaultText = 'default text';
final TextEditingController controller = TextEditingController(text: defaultText); final TextEditingController controller = TextEditingController(text: defaultText);
addTearDown(controller.dispose);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -57,8 +58,10 @@ void main() { ...@@ -57,8 +58,10 @@ void main() {
expect(find.text(updatedText), findsOneWidget); expect(find.text(updatedText), findsOneWidget);
}); });
testWidgets('SearchBar respects focusNode property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects focusNode property', (WidgetTester tester) async {
final FocusNode node = FocusNode(); final FocusNode node = FocusNode();
addTearDown(node.dispose);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Material( home: Material(
...@@ -80,7 +83,74 @@ void main() { ...@@ -80,7 +83,74 @@ void main() {
expect(node.hasFocus, false); expect(node.hasFocus, false);
}); });
testWidgets('SearchBar has correct default layout and padding LTR', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar focusNode is hot swappable', (WidgetTester tester) async {
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchBar(
focusNode: node1,
),
),
),
);
expect(node1.hasFocus, isFalse);
node1.requestFocus();
await tester.pump();
expect(node1.hasFocus, isTrue);
node1.unfocus();
await tester.pump();
expect(node1.hasFocus, isFalse);
final FocusNode node2 = FocusNode();
addTearDown(node2.dispose);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchBar(
focusNode: node2,
),
),
),
);
expect(node1.hasFocus, isFalse);
expect(node2.hasFocus, isFalse);
node2.requestFocus();
await tester.pump();
expect(node1.hasFocus, isFalse);
expect(node2.hasFocus, isTrue);
node2.unfocus();
await tester.pump();
expect(node1.hasFocus, isFalse);
expect(node2.hasFocus, isFalse);
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: SearchBar(),
),
),
);
expect(node1.hasFocus, isFalse);
expect(node2.hasFocus, isFalse);
await tester.tap(find.byType(SearchBar));
await tester.pump();
expect(node1.hasFocus, isFalse);
expect(node2.hasFocus, isFalse);
});
testWidgetsWithLeakTracking('SearchBar has correct default layout and padding LTR', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -117,7 +187,7 @@ void main() { ...@@ -117,7 +187,7 @@ void main() {
expect(trailingIcon.right, equals(barRect.right - 8.0)); expect(trailingIcon.right, equals(barRect.right - 8.0));
}); });
testWidgets('SearchBar has correct default layout and padding - RTL', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar has correct default layout and padding - RTL', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Directionality( home: Directionality(
...@@ -157,7 +227,7 @@ void main() { ...@@ -157,7 +227,7 @@ void main() {
expect(trailingIcon.left, equals(barRect.left + 8.0)); expect(trailingIcon.left, equals(barRect.left + 8.0));
}); });
testWidgets('SearchBar respects hintText property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects hintText property', (WidgetTester tester) async {
const String hintText = 'hint text'; const String hintText = 'hint text';
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(
...@@ -172,7 +242,7 @@ void main() { ...@@ -172,7 +242,7 @@ void main() {
expect(find.text(hintText), findsOneWidget); expect(find.text(hintText), findsOneWidget);
}); });
testWidgets('SearchBar respects leading property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects leading property', (WidgetTester tester) async {
final ThemeData theme = ThemeData(); final ThemeData theme = ThemeData();
final ColorScheme colorScheme = theme.colorScheme; final ColorScheme colorScheme = theme.colorScheme;
await tester.pumpWidget( await tester.pumpWidget(
...@@ -193,7 +263,7 @@ void main() { ...@@ -193,7 +263,7 @@ void main() {
expect(iconColor, colorScheme.onSurface); // Default icon color. expect(iconColor, colorScheme.onSurface); // Default icon color.
}); });
testWidgets('SearchBar respects trailing property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects trailing property', (WidgetTester tester) async {
final ThemeData theme = ThemeData(); final ThemeData theme = ThemeData();
final ColorScheme colorScheme = theme.colorScheme; final ColorScheme colorScheme = theme.colorScheme;
await tester.pumpWidget( await tester.pumpWidget(
...@@ -216,7 +286,7 @@ void main() { ...@@ -216,7 +286,7 @@ void main() {
expect(iconColor, colorScheme.onSurfaceVariant); // Default icon color. expect(iconColor, colorScheme.onSurfaceVariant); // Default icon color.
}); });
testWidgets('SearchBar respects onTap property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects onTap property', (WidgetTester tester) async {
int tapCount = 0; int tapCount = 0;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -242,7 +312,7 @@ void main() { ...@@ -242,7 +312,7 @@ void main() {
expect(tapCount, 2); expect(tapCount, 2);
}); });
testWidgets('SearchBar respects onChanged property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects onChanged property', (WidgetTester tester) async {
int changeCount = 0; int changeCount = 0;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -269,7 +339,7 @@ void main() { ...@@ -269,7 +339,7 @@ void main() {
expect(changeCount, 2); expect(changeCount, 2);
}); });
testWidgets('SearchBar respects onSubmitted property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects onSubmitted property', (WidgetTester tester) async {
String submittedQuery = ''; String submittedQuery = '';
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -289,7 +359,7 @@ void main() { ...@@ -289,7 +359,7 @@ void main() {
expect(submittedQuery, equals('query')); expect(submittedQuery, equals('query'));
}); });
testWidgets('SearchBar respects constraints property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects constraints property', (WidgetTester tester) async {
const BoxConstraints constraints = BoxConstraints(maxWidth: 350.0, minHeight: 80); const BoxConstraints constraints = BoxConstraints(maxWidth: 350.0, minHeight: 80);
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(
...@@ -307,7 +377,7 @@ void main() { ...@@ -307,7 +377,7 @@ void main() {
expect(barRect.size, const Size(350.0, 80.0)); expect(barRect.size, const Size(350.0, 80.0));
}); });
testWidgets('SearchBar respects elevation property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects elevation property', (WidgetTester tester) async {
const double pressedElevation = 0.0; const double pressedElevation = 0.0;
const double hoveredElevation = 1.0; const double hoveredElevation = 1.0;
const double focusedElevation = 2.0; const double focusedElevation = 2.0;
...@@ -363,7 +433,7 @@ void main() { ...@@ -363,7 +433,7 @@ void main() {
expect(material.elevation, focusedElevation); expect(material.elevation, focusedElevation);
}); });
testWidgets('SearchBar respects backgroundColor property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects backgroundColor property', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -403,7 +473,7 @@ void main() { ...@@ -403,7 +473,7 @@ void main() {
expect(material.color, focusedColor); expect(material.color, focusedColor);
}); });
testWidgets('SearchBar respects shadowColor property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects shadowColor property', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -443,7 +513,7 @@ void main() { ...@@ -443,7 +513,7 @@ void main() {
expect(material.shadowColor, focusedColor); expect(material.shadowColor, focusedColor);
}); });
testWidgets('SearchBar respects surfaceTintColor property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects surfaceTintColor property', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -483,8 +553,10 @@ void main() { ...@@ -483,8 +553,10 @@ void main() {
expect(material.surfaceTintColor, focusedColor); expect(material.surfaceTintColor, focusedColor);
}); });
testWidgets('SearchBar respects overlayColor property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects overlayColor property', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(); final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -521,7 +593,7 @@ void main() { ...@@ -521,7 +593,7 @@ void main() {
expect(inkFeatures, paints..rect()..rect(color: focusedColor.withOpacity(1.0))); expect(inkFeatures, paints..rect()..rect(color: focusedColor.withOpacity(1.0)));
}); });
testWidgets('SearchBar respects side and shape properties', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects side and shape properties', (WidgetTester tester) async {
const BorderSide pressedSide = BorderSide(width: 2.0); const BorderSide pressedSide = BorderSide(width: 2.0);
const BorderSide hoveredSide = BorderSide(width: 3.0); const BorderSide hoveredSide = BorderSide(width: 3.0);
const BorderSide focusedSide = BorderSide(width: 4.0); const BorderSide focusedSide = BorderSide(width: 4.0);
...@@ -595,7 +667,7 @@ void main() { ...@@ -595,7 +667,7 @@ void main() {
expect(material.shape, focusedShape.copyWith(side: focusedSide)); expect(material.shape, focusedShape.copyWith(side: focusedSide));
}); });
testWidgets('SearchBar respects padding property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects padding property', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(
home: Center( home: Center(
...@@ -623,7 +695,7 @@ void main() { ...@@ -623,7 +695,7 @@ void main() {
expect(trailingRect.right, barRect.right - 16.0); expect(trailingRect.right, barRect.right - 16.0);
}); });
testWidgets('SearchBar respects hintStyle property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects hintStyle property', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -657,8 +729,10 @@ void main() { ...@@ -657,8 +729,10 @@ void main() {
expect(helperText.style?.color, focusedColor); expect(helperText.style?.color, focusedColor);
}); });
testWidgets('SearchBar respects textStyle property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects textStyle property', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'input text'); final TextEditingController controller = TextEditingController(text: 'input text');
addTearDown(controller.dispose);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -692,7 +766,7 @@ void main() { ...@@ -692,7 +766,7 @@ void main() {
expect(inputText.style.color, focusedColor); expect(inputText.style.color, focusedColor);
}); });
testWidgets('SearchBar respects textCapitalization property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchBar respects textCapitalization property', (WidgetTester tester) async {
Widget buildSearchBar(TextCapitalization textCapitalization) { Widget buildSearchBar(TextCapitalization textCapitalization) {
return MaterialApp( return MaterialApp(
home: Center( home: Center(
...@@ -725,7 +799,7 @@ void main() { ...@@ -725,7 +799,7 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.none); expect(textField.textCapitalization, TextCapitalization.none);
}); });
testWidgets('SearchAnchor respects textCapitalization property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects textCapitalization property', (WidgetTester tester) async {
Widget buildSearchAnchor(TextCapitalization textCapitalization) { Widget buildSearchAnchor(TextCapitalization textCapitalization) {
return MaterialApp( return MaterialApp(
home: Center( home: Center(
...@@ -765,7 +839,7 @@ void main() { ...@@ -765,7 +839,7 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.none); expect(textField.textCapitalization, TextCapitalization.none);
}); });
testWidgets('SearchAnchor.bar respects textCapitalization property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor.bar respects textCapitalization property', (WidgetTester tester) async {
Widget buildSearchAnchor(TextCapitalization textCapitalization) { Widget buildSearchAnchor(TextCapitalization textCapitalization) {
return MaterialApp( return MaterialApp(
home: Center( home: Center(
...@@ -794,7 +868,7 @@ void main() { ...@@ -794,7 +868,7 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.characters); expect(textField.textCapitalization, TextCapitalization.characters);
}); });
testWidgets('hintStyle can override textStyle for hintText', (WidgetTester tester) async { testWidgetsWithLeakTracking('hintStyle can override textStyle for hintText', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Center( home: Center(
...@@ -830,7 +904,7 @@ void main() { ...@@ -830,7 +904,7 @@ void main() {
}); });
// Regression test for https://github.com/flutter/flutter/issues/127092. // Regression test for https://github.com/flutter/flutter/issues/127092.
testWidgets('The text is still centered when SearchBar text field is smaller than 48', (WidgetTester tester) async { testWidgetsWithLeakTracking('The text is still centered when SearchBar text field is smaller than 48', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData(useMaterial3: true), theme: ThemeData(useMaterial3: true),
...@@ -852,7 +926,7 @@ void main() { ...@@ -852,7 +926,7 @@ void main() {
expect(textCenterY, searchBarCenterY); expect(textCenterY, searchBarCenterY);
}); });
testWidgets('The search view defaults', (WidgetTester tester) async { testWidgetsWithLeakTracking('The search view defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData(useMaterial3: true);
final ColorScheme colorScheme = theme.colorScheme; final ColorScheme colorScheme = theme.colorScheme;
await tester.pumpWidget( await tester.pumpWidget(
...@@ -912,7 +986,7 @@ void main() { ...@@ -912,7 +986,7 @@ void main() {
expect(inputText.style.fontWeight, FontWeight.w400); expect(inputText.style.fontWeight, FontWeight.w400);
}); });
testWidgets('The search view default size on different platforms', (WidgetTester tester) async { testWidgetsWithLeakTracking('The search view default size on different platforms', (WidgetTester tester) async {
// The search view should be is full-screen on mobile platforms, // The search view should be is full-screen on mobile platforms,
// and have a size of (360, 2/3 screen height) on other platforms // and have a size of (360, 2/3 screen height) on other platforms
Widget buildSearchAnchor(TargetPlatform platform) { Widget buildSearchAnchor(TargetPlatform platform) {
...@@ -959,7 +1033,7 @@ void main() { ...@@ -959,7 +1033,7 @@ void main() {
} }
}); });
testWidgets('SearchAnchor respects isFullScreen property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects isFullScreen property', (WidgetTester tester) async {
Widget buildSearchAnchor(TargetPlatform platform) { Widget buildSearchAnchor(TargetPlatform platform) {
return MaterialApp( return MaterialApp(
theme: ThemeData(platform: platform), theme: ThemeData(platform: platform),
...@@ -995,9 +1069,10 @@ void main() { ...@@ -995,9 +1069,10 @@ void main() {
} }
}); });
testWidgets('SearchAnchor respects controller property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects controller property', (WidgetTester tester) async {
const String defaultText = 'initial text'; const String defaultText = 'initial text';
final SearchController controller = SearchController(); final SearchController controller = SearchController();
addTearDown(controller.dispose);
controller.text = defaultText; controller.text = defaultText;
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1030,7 +1105,82 @@ void main() { ...@@ -1030,7 +1105,82 @@ void main() {
expect(find.text(updatedText), findsOneWidget); expect(find.text(updatedText), findsOneWidget);
}); });
testWidgets('SearchAnchor respects viewBuilder property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor attaches and detaches controllers property', (WidgetTester tester) async {
Widget builder(BuildContext context, SearchController controller) {
return const Icon(Icons.search);
}
List<Widget> suggestionsBuilder(BuildContext context, SearchController controller) {
return const <Widget>[];
}
final SearchController controller1 = SearchController();
addTearDown(controller1.dispose);
expect(controller1.isAttached, isFalse);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchAnchor(
searchController: controller1,
builder: builder,
suggestionsBuilder: suggestionsBuilder,
),
),
),
);
expect(controller1.isAttached, isTrue);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchAnchor(
builder: builder,
suggestionsBuilder: suggestionsBuilder,
),
),
),
);
expect(controller1.isAttached, isFalse);
final SearchController controller2 = SearchController();
addTearDown(controller2.dispose);
expect(controller2.isAttached, isFalse);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchAnchor(
searchController: controller2,
builder: builder,
suggestionsBuilder: suggestionsBuilder,
),
),
),
);
expect(controller1.isAttached, isFalse);
expect(controller2.isAttached, isTrue);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SearchAnchor(
builder: builder,
suggestionsBuilder: suggestionsBuilder,
),
),
),
);
expect(controller1.isAttached, isFalse);
expect(controller2.isAttached, isFalse);
});
testWidgetsWithLeakTracking('SearchAnchor respects viewBuilder property', (WidgetTester tester) async {
Widget buildAnchor({ViewBuilder? viewBuilder}) { Widget buildAnchor({ViewBuilder? viewBuilder}) {
return MaterialApp( return MaterialApp(
home: Material( home: Material(
...@@ -1065,7 +1215,7 @@ void main() { ...@@ -1065,7 +1215,7 @@ void main() {
expect(find.byType(GridView), findsOneWidget); expect(find.byType(GridView), findsOneWidget);
}); });
testWidgets('SearchAnchor respects viewLeading property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewLeading property', (WidgetTester tester) async {
Widget buildAnchor({Widget? viewLeading}) { Widget buildAnchor({Widget? viewLeading}) {
return MaterialApp( return MaterialApp(
home: Material( home: Material(
...@@ -1098,7 +1248,7 @@ void main() { ...@@ -1098,7 +1248,7 @@ void main() {
expect(find.byIcon(Icons.history), findsOneWidget); expect(find.byIcon(Icons.history), findsOneWidget);
}); });
testWidgets('SearchAnchor respects viewTrailing property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewTrailing property', (WidgetTester tester) async {
Widget buildAnchor({Iterable<Widget>? viewTrailing}) { Widget buildAnchor({Iterable<Widget>? viewTrailing}) {
return MaterialApp( return MaterialApp(
home: Material( home: Material(
...@@ -1131,7 +1281,7 @@ void main() { ...@@ -1131,7 +1281,7 @@ void main() {
expect(find.byIcon(Icons.history), findsOneWidget); expect(find.byIcon(Icons.history), findsOneWidget);
}); });
testWidgets('SearchAnchor respects viewHintText property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewHintText property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1152,7 +1302,7 @@ void main() { ...@@ -1152,7 +1302,7 @@ void main() {
expect(find.text('hint text'), findsOneWidget); expect(find.text('hint text'), findsOneWidget);
}); });
testWidgets('SearchAnchor respects viewBackgroundColor property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewBackgroundColor property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1174,7 +1324,7 @@ void main() { ...@@ -1174,7 +1324,7 @@ void main() {
expect(getSearchViewMaterial(tester).color, Colors.purple); expect(getSearchViewMaterial(tester).color, Colors.purple);
}); });
testWidgets('SearchAnchor respects viewElevation property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewElevation property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1196,7 +1346,7 @@ void main() { ...@@ -1196,7 +1346,7 @@ void main() {
expect(getSearchViewMaterial(tester).elevation, 3.0); expect(getSearchViewMaterial(tester).elevation, 3.0);
}); });
testWidgets('SearchAnchor respects viewSurfaceTint property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewSurfaceTint property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1218,7 +1368,7 @@ void main() { ...@@ -1218,7 +1368,7 @@ void main() {
expect(getSearchViewMaterial(tester).surfaceTintColor, Colors.purple); expect(getSearchViewMaterial(tester).surfaceTintColor, Colors.purple);
}); });
testWidgets('SearchAnchor respects viewSide property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewSide property', (WidgetTester tester) async {
const BorderSide side = BorderSide(color: Colors.purple, width: 5.0); const BorderSide side = BorderSide(color: Colors.purple, width: 5.0);
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
...@@ -1242,7 +1392,7 @@ void main() { ...@@ -1242,7 +1392,7 @@ void main() {
expect(getSearchViewMaterial(tester).shape, RoundedRectangleBorder(side: side, borderRadius: BorderRadius.circular(28.0))); expect(getSearchViewMaterial(tester).shape, RoundedRectangleBorder(side: side, borderRadius: BorderRadius.circular(28.0)));
}); });
testWidgets('SearchAnchor respects viewShape property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewShape property', (WidgetTester tester) async {
const BorderSide side = BorderSide(color: Colors.purple, width: 5.0); const BorderSide side = BorderSide(color: Colors.purple, width: 5.0);
const OutlinedBorder shape = StadiumBorder(side: side); const OutlinedBorder shape = StadiumBorder(side: side);
...@@ -1268,7 +1418,7 @@ void main() { ...@@ -1268,7 +1418,7 @@ void main() {
expect(getSearchViewMaterial(tester).shape, shape); expect(getSearchViewMaterial(tester).shape, shape);
}); });
testWidgets('SearchAnchor respects headerTextStyle property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects headerTextStyle property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1294,7 +1444,7 @@ void main() { ...@@ -1294,7 +1444,7 @@ void main() {
expect(inputText.style.color, Colors.red); expect(inputText.style.color, Colors.red);
}); });
testWidgets('SearchAnchor respects headerHintStyle property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects headerHintStyle property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1319,7 +1469,7 @@ void main() { ...@@ -1319,7 +1469,7 @@ void main() {
expect(inputText.style?.color, Colors.orange); expect(inputText.style?.color, Colors.orange);
}); });
testWidgets('SearchAnchor respects dividerColor property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects dividerColor property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor( child: SearchAnchor(
...@@ -1345,7 +1495,7 @@ void main() { ...@@ -1345,7 +1495,7 @@ void main() {
expect(decoration.border!.bottom.color, Colors.red); expect(decoration.border!.bottom.color, Colors.red);
}); });
testWidgets('SearchAnchor respects viewConstraints property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects viewConstraints property', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: Center( child: Center(
...@@ -1373,7 +1523,7 @@ void main() { ...@@ -1373,7 +1523,7 @@ void main() {
expect(sizedBox.height, 390.0); expect(sizedBox.height, 390.0);
}); });
testWidgets('SearchAnchor respects builder property - LTR', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects builder property - LTR', (WidgetTester tester) async {
Widget buildAnchor({required SearchAnchorChildBuilder builder}) { Widget buildAnchor({required SearchAnchorChildBuilder builder}) {
return MaterialApp( return MaterialApp(
home: Material( home: Material(
...@@ -1409,7 +1559,7 @@ void main() { ...@@ -1409,7 +1559,7 @@ void main() {
expect(searchViewRect.topLeft, anchorRect.topLeft); expect(searchViewRect.topLeft, anchorRect.topLeft);
}); });
testWidgets('SearchAnchor respects builder property - RTL', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects builder property - RTL', (WidgetTester tester) async {
Widget buildAnchor({required SearchAnchorChildBuilder builder}) { Widget buildAnchor({required SearchAnchorChildBuilder builder}) {
return MaterialApp( return MaterialApp(
home: Directionality( home: Directionality(
...@@ -1446,8 +1596,9 @@ void main() { ...@@ -1446,8 +1596,9 @@ void main() {
expect(searchViewRect.topRight, anchorRect.topRight); expect(searchViewRect.topRight, anchorRect.topRight);
}); });
testWidgets('SearchAnchor respects suggestionsBuilder property', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor respects suggestionsBuilder property', (WidgetTester tester) async {
final SearchController controller = SearchController(); final SearchController controller = SearchController();
addTearDown(controller.dispose);
const String suggestion = 'suggestion text'; const String suggestion = 'suggestion text';
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -1490,7 +1641,7 @@ void main() { ...@@ -1490,7 +1641,7 @@ void main() {
expect(controller.value.text, suggestion); expect(controller.value.text, suggestion);
}); });
testWidgets('SearchAnchor should update suggestions on changes to search controller', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor should update suggestions on changes to search controller', (WidgetTester tester) async {
final SearchController controller = SearchController(); final SearchController controller = SearchController();
const List<String> suggestions = <String>['foo','far','bim']; const List<String> suggestions = <String>['foo','far','bim'];
addTearDown(controller.dispose); addTearDown(controller.dispose);
...@@ -1579,8 +1730,9 @@ void main() { ...@@ -1579,8 +1730,9 @@ void main() {
expect(listTile3, findsNothing); expect(listTile3, findsNothing);
}); });
testWidgets('SearchAnchor suggestionsBuilder property could be async', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor suggestionsBuilder property could be async', (WidgetTester tester) async {
final SearchController controller = SearchController(); final SearchController controller = SearchController();
addTearDown(controller.dispose);
const String suggestion = 'suggestion text'; const String suggestion = 'suggestion text';
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -1624,7 +1776,7 @@ void main() { ...@@ -1624,7 +1776,7 @@ void main() {
expect(controller.value.text, suggestion); expect(controller.value.text, suggestion);
}); });
testWidgets('SearchAnchor.bar has a default search bar as the anchor', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor.bar has a default search bar as the anchor', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: Align( child: Align(
...@@ -1654,8 +1806,10 @@ void main() { ...@@ -1654,8 +1806,10 @@ void main() {
expect(searchViewRect.width, anchorRect.width); expect(searchViewRect.width, anchorRect.width);
}); });
testWidgets('SearchController can open/close view', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchController can open/close view', (WidgetTester tester) async {
final SearchController controller = SearchController(); final SearchController controller = SearchController();
addTearDown(controller.dispose);
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SearchAnchor.bar( child: SearchAnchor.bar(
...@@ -1687,7 +1841,7 @@ void main() { ...@@ -1687,7 +1841,7 @@ void main() {
expect(controller.isOpen, true); expect(controller.isOpen, true);
}); });
testWidgets('Search view does not go off the screen - LTR', (WidgetTester tester) async { testWidgetsWithLeakTracking('Search view does not go off the screen - LTR', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: Align( child: Align(
...@@ -1724,7 +1878,7 @@ void main() { ...@@ -1724,7 +1878,7 @@ void main() {
expect(searchViewRect, equals(const Rect.fromLTRB(440.0, 200.0, 800.0, 600.0))); expect(searchViewRect, equals(const Rect.fromLTRB(440.0, 200.0, 800.0, 600.0)));
}); });
testWidgets('Search view does not go off the screen - RTL', (WidgetTester tester) async { testWidgetsWithLeakTracking('Search view does not go off the screen - RTL', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Directionality( home: Directionality(
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
...@@ -1763,7 +1917,7 @@ void main() { ...@@ -1763,7 +1917,7 @@ void main() {
expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 200.0, 360.0, 600.0))); expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 200.0, 360.0, 600.0)));
}); });
testWidgets('Search view becomes smaller if the window size is smaller than the view size', (WidgetTester tester) async { testWidgetsWithLeakTracking('Search view becomes smaller if the window size is smaller than the view size', (WidgetTester tester) async {
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
tester.view.physicalSize = const Size(200.0, 200.0); tester.view.physicalSize = const Size(200.0, 200.0);
tester.view.devicePixelRatio = 1.0; tester.view.devicePixelRatio = 1.0;
...@@ -1824,7 +1978,7 @@ void main() { ...@@ -1824,7 +1978,7 @@ void main() {
expect(searchViewRectRTL, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0))); expect(searchViewRectRTL, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0)));
}); });
testWidgets('Docked search view route is popped if the window size changes', (WidgetTester tester) async { testWidgetsWithLeakTracking('Docked search view route is popped if the window size changes', (WidgetTester tester) async {
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
tester.view.physicalSize = const Size(500.0, 600.0); tester.view.physicalSize = const Size(500.0, 600.0);
tester.view.devicePixelRatio = 1.0; tester.view.devicePixelRatio = 1.0;
...@@ -1863,7 +2017,7 @@ void main() { ...@@ -1863,7 +2017,7 @@ void main() {
expect(find.byIcon(Icons.arrow_back), findsNothing); expect(find.byIcon(Icons.arrow_back), findsNothing);
}); });
testWidgets('Full-screen search view route should stay if the window size changes', (WidgetTester tester) async { testWidgetsWithLeakTracking('Full-screen search view route should stay if the window size changes', (WidgetTester tester) async {
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
tester.view.physicalSize = const Size(500.0, 600.0); tester.view.physicalSize = const Size(500.0, 600.0);
tester.view.devicePixelRatio = 1.0; tester.view.devicePixelRatio = 1.0;
...@@ -1902,7 +2056,7 @@ void main() { ...@@ -1902,7 +2056,7 @@ void main() {
expect(find.byIcon(Icons.arrow_back), findsOneWidget); expect(find.byIcon(Icons.arrow_back), findsOneWidget);
}); });
testWidgets('Search view route does not throw exception during pop animation', (WidgetTester tester) async { testWidgetsWithLeakTracking('Search view route does not throw exception during pop animation', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/126590. // Regression test for https://github.com/flutter/flutter/issues/126590.
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
...@@ -1942,7 +2096,7 @@ void main() { ...@@ -1942,7 +2096,7 @@ void main() {
// No exception. // No exception.
}); });
testWidgets('Docked search should position itself correctly based on closest navigator', (WidgetTester tester) async { testWidgetsWithLeakTracking('Docked search should position itself correctly based on closest navigator', (WidgetTester tester) async {
const double rootSpacing = 100.0; const double rootSpacing = 100.0;
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -1979,7 +2133,7 @@ void main() { ...@@ -1979,7 +2133,7 @@ void main() {
expect(searchViewRect.topLeft, equals(const Offset(rootSpacing, rootSpacing))); expect(searchViewRect.topLeft, equals(const Offset(rootSpacing, rootSpacing)));
}); });
testWidgets('Docked search view with nested navigator does not go off the screen', (WidgetTester tester) async { testWidgetsWithLeakTracking('Docked search view with nested navigator does not go off the screen', (WidgetTester tester) async {
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
tester.view.physicalSize = const Size(400.0, 400.0); tester.view.physicalSize = const Size(400.0, 400.0);
tester.view.devicePixelRatio = 1.0; tester.view.devicePixelRatio = 1.0;
...@@ -2070,7 +2224,7 @@ void main() { ...@@ -2070,7 +2224,7 @@ void main() {
expect(decoration?.hintStyle?.color, theme.colorScheme.onSurfaceVariant); expect(decoration?.hintStyle?.color, theme.colorScheme.onSurfaceVariant);
} }
testWidgets('Overall InputDecorationTheme does not override text field style' testWidgetsWithLeakTracking('Overall InputDecorationTheme does not override text field style'
' in SearchBar', (WidgetTester tester) async { ' in SearchBar', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -2096,7 +2250,7 @@ void main() { ...@@ -2096,7 +2250,7 @@ void main() {
checkSearchBarDefaults(tester, theme.colorScheme, material); checkSearchBarDefaults(tester, theme.colorScheme, material);
}); });
testWidgets('Overall InputDecorationTheme does not override text field style' testWidgetsWithLeakTracking('Overall InputDecorationTheme does not override text field style'
' in the search view route', (WidgetTester tester) async { ' in the search view route', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -2145,7 +2299,7 @@ void main() { ...@@ -2145,7 +2299,7 @@ void main() {
}); });
}); });
testWidgets('SearchAnchor view respects theme brightness', (WidgetTester tester) async { testWidgetsWithLeakTracking('SearchAnchor view respects theme brightness', (WidgetTester tester) async {
Widget buildSearchAnchor(ThemeData theme) { Widget buildSearchAnchor(ThemeData theme) {
return MaterialApp( return MaterialApp(
theme: theme, theme: theme,
...@@ -2190,7 +2344,7 @@ void main() { ...@@ -2190,7 +2344,7 @@ void main() {
expect(material.color, theme.colorScheme.surface); expect(material.color, theme.colorScheme.surface);
}); });
testWidgets('Search view widgets can inherit local themes', (WidgetTester tester) async { testWidgetsWithLeakTracking('Search view widgets can inherit local themes', (WidgetTester tester) async {
final ThemeData globalTheme = ThemeData(colorSchemeSeed: Colors.red); final ThemeData globalTheme = ThemeData(colorSchemeSeed: Colors.red);
final ThemeData localTheme = ThemeData( final ThemeData localTheme = ThemeData(
colorSchemeSeed: Colors.green, colorSchemeSeed: Colors.green,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment