// 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/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';

void main() {
  // Returns the RenderEditable at the given index, or the first if not given.
  RenderEditable findRenderEditable(WidgetTester tester, {int index = 0}) {
    final RenderObject root = tester.renderObject(find.byType(EditableText).at(index));
    expect(root, isNotNull);

    late RenderEditable renderEditable;
    void recursiveFinder(RenderObject child) {
      if (child is RenderEditable) {
        renderEditable = child;
        return;
      }
      child.visitChildren(recursiveFinder);
    }
    root.visitChildren(recursiveFinder);
    expect(renderEditable, isNotNull);
    return renderEditable;
  }

  List<TextSelectionPoint> globalize(Iterable<TextSelectionPoint> points, RenderBox box) {
    return points.map<TextSelectionPoint>((TextSelectionPoint point) {
      return TextSelectionPoint(
        box.localToGlobal(point.point),
        point.direction,
      );
    }).toList();
  }

  Offset textOffsetToPosition(WidgetTester tester, int offset, {int index = 0}) {
    final RenderEditable renderEditable = findRenderEditable(tester, index: index);
    final List<TextSelectionPoint> endpoints = globalize(
      renderEditable.getEndpointsForSelection(
        TextSelection.collapsed(offset: offset),
      ),
      renderEditable,
    );
    expect(endpoints.length, 1);
    return endpoints[0].point + const Offset(kIsWeb? 1.0 : 0.0, -2.0);
  }

  testWidgetsWithLeakTracking('SearchBar defaults', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(useMaterial3: true);
    final ColorScheme colorScheme = theme.colorScheme;

    await tester.pumpWidget(
      MaterialApp(
        theme: theme,
        home: const Material(
          child: SearchBar(
            hintText: 'hint text',
          )
        ),
      ),
    );

    final Finder searchBarMaterial = find.descendant(
      of: find.byType(SearchBar),
      matching: find.byType(Material),
    );

    final Material material = tester.widget<Material>(searchBarMaterial);
    checkSearchBarDefaults(tester, colorScheme, material);
  });

  testWidgetsWithLeakTracking('SearchBar respects controller property', (WidgetTester tester) async {
    const String defaultText = 'default text';
    final TextEditingController controller = TextEditingController(text: defaultText);
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchBar(
            controller: controller,
          ),
        ),
      ),
    );

    expect(controller.value.text, defaultText);
    expect(find.text(defaultText), findsOneWidget);

    const String updatedText = 'updated text';
    await tester.enterText(find.byType(SearchBar), updatedText);
    expect(controller.value.text, updatedText);
    expect(find.text(defaultText), findsNothing);
    expect(find.text(updatedText), findsOneWidget);
  });

  testWidgetsWithLeakTracking('SearchBar respects focusNode property', (WidgetTester tester) async {
    final FocusNode node = FocusNode();
    addTearDown(node.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchBar(
            focusNode: node,
          ),
        ),
      ),
    );

    expect(node.hasFocus, false);

    node.requestFocus();
    await tester.pump();
    expect(node.hasFocus, true);

    node.unfocus();
    await tester.pump();
    expect(node.hasFocus, false);
  });

  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(
      MaterialApp(
        home: Center(
          child: SearchBar(
            leading: IconButton(
              icon: const Icon(Icons.search),
              onPressed: () {},
            ),
            trailing: <Widget>[
              IconButton(
                icon: const Icon(Icons.menu),
                onPressed: () {},
              )
            ],
          ),
        ),
      ),
    );

    final Rect barRect = tester.getRect(find.byType(SearchBar));
    expect(barRect.size, const Size(800.0, 56.0));
    expect(barRect, equals(const Rect.fromLTRB(0.0, 272.0, 800.0, 328.0)));

    final Rect leadingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.search));
    // Default left padding is 8.0, and icon button has 8.0 padding, so in total the padding between
    // the edge of the bar and the icon of the button is 16.0, which matches the spec.
    expect(leadingIcon.left, equals(barRect.left + 8.0));

    final Rect textField = tester.getRect(find.byType(TextField));
    expect(textField.left, equals(leadingIcon.right + 8.0));

    final Rect trailingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.menu));
    expect(trailingIcon.left, equals(textField.right + 8.0));
    expect(trailingIcon.right, equals(barRect.right - 8.0));
  });

  testWidgetsWithLeakTracking('SearchBar has correct default layout and padding - RTL', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.rtl,
          child: Center(
            child: SearchBar(
              leading: IconButton(
                icon: const Icon(Icons.search),
                onPressed: () {},
              ),
              trailing: <Widget>[
                IconButton(
                  icon: const Icon(Icons.menu),
                  onPressed: () {},
                )
              ],
            ),
          ),
        ),
      ),
    );

    final Rect barRect = tester.getRect(find.byType(SearchBar));
    expect(barRect.size, const Size(800.0, 56.0));
    expect(barRect, equals(const Rect.fromLTRB(0.0, 272.0, 800.0, 328.0)));

    // The default padding is set to 8.0 so the distance between the icon of the button
    // and the edge of the bar is 16.0, which matches the spec.
    final Rect leadingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.search));
    expect(leadingIcon.right, equals(barRect.right - 8.0));

    final Rect textField = tester.getRect(find.byType(TextField));
    expect(textField.right, equals(leadingIcon.left - 8.0));

    final Rect trailingIcon = tester.getRect(find.widgetWithIcon(IconButton, Icons.menu));
    expect(trailingIcon.right, equals(textField.left - 8.0));
    expect(trailingIcon.left, equals(barRect.left + 8.0));
  });

  testWidgetsWithLeakTracking('SearchBar respects hintText property', (WidgetTester tester) async {
    const String hintText = 'hint text';
    await tester.pumpWidget(
      const MaterialApp(
        home: Material(
          child: SearchBar(
            hintText: hintText,
          ),
        ),
      ),
    );

    expect(find.text(hintText), findsOneWidget);
  });

  testWidgetsWithLeakTracking('SearchBar respects leading property', (WidgetTester tester) async {
    final ThemeData theme = ThemeData();
    final ColorScheme colorScheme = theme.colorScheme;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchBar(
            leading: IconButton(
              icon: const Icon(Icons.search),
              onPressed: () {},
            ),
          ),
        ),
      ),
    );

    expect(find.widgetWithIcon(IconButton, Icons.search), findsOneWidget);
    final Color? iconColor = _iconStyle(tester, Icons.search)?.color;
    expect(iconColor, colorScheme.onSurface); // Default icon color.
  });

  testWidgetsWithLeakTracking('SearchBar respects trailing property', (WidgetTester tester) async {
    final ThemeData theme = ThemeData();
    final ColorScheme colorScheme = theme.colorScheme;
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchBar(
            trailing: <Widget>[
              IconButton(
                icon: const Icon(Icons.menu),
                onPressed: () {},
              ),
            ],
          ),
        ),
      ),
    );

    expect(find.widgetWithIcon(IconButton, Icons.menu), findsOneWidget);
    final Color? iconColor = _iconStyle(tester, Icons.menu)?.color;
    expect(iconColor, colorScheme.onSurfaceVariant); // Default icon color.
  });

  testWidgetsWithLeakTracking('SearchBar respects onTap property', (WidgetTester tester) async {
    int tapCount = 0;
    await tester.pumpWidget(
      MaterialApp(
        home: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Material(
              child: SearchBar(
                onTap: () {
                  setState(() {
                    tapCount++;
                  });
                }
              ),
            );
          }
        ),
      ),
    );
    expect(tapCount, 0);
    await tester.tap(find.byType(SearchBar));
    expect(tapCount, 1);
    await tester.tap(find.byType(SearchBar));
    expect(tapCount, 2);
  });

  testWidgetsWithLeakTracking('SearchBar respects onChanged property', (WidgetTester tester) async {
    int changeCount = 0;
    await tester.pumpWidget(
      MaterialApp(
        home: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Material(
              child: SearchBar(
                onChanged: (_) {
                  setState(() {
                    changeCount++;
                  });
                }
              ),
            );
          }
        ),
      ),
    );

    expect(changeCount, 0);
    await tester.enterText(find.byType(SearchBar), 'a');
    expect(changeCount, 1);
    await tester.enterText(find.byType(SearchBar), 'b');
    expect(changeCount, 2);
  });

  testWidgetsWithLeakTracking('SearchBar respects onSubmitted property', (WidgetTester tester) async {
    String submittedQuery = '';
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchBar(
            onSubmitted: (String text) {
              submittedQuery = text;
            },
          ),
        ),
      ),
    );

    await tester.enterText(find.byType(SearchBar), 'query');
    await tester.testTextInput.receiveAction(TextInputAction.done);

    expect(submittedQuery, equals('query'));
  });

  testWidgetsWithLeakTracking('SearchBar respects constraints property', (WidgetTester tester) async {
    const BoxConstraints constraints = BoxConstraints(maxWidth: 350.0, minHeight: 80);
    await tester.pumpWidget(
      const MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              constraints: constraints,
            ),
          ),
        ),
      ),
    );

    final Rect barRect = tester.getRect(find.byType(SearchBar));
    expect(barRect.size, const Size(350.0, 80.0));
  });

  testWidgetsWithLeakTracking('SearchBar respects elevation property', (WidgetTester tester) async {
    const double pressedElevation = 0.0;
    const double hoveredElevation = 1.0;
    const double focusedElevation = 2.0;
    const double defaultElevation = 3.0;
    double getElevation(Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return pressedElevation;
      }
      if (states.contains(MaterialState.hovered)) {
        return hoveredElevation;
      }
      if (states.contains(MaterialState.focused)) {
        return focusedElevation;
      }
      return defaultElevation;
    }
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              elevation: MaterialStateProperty.resolveWith<double>(getElevation),
            ),
          ),
        ),
      ),
    );

    final Finder searchBarMaterial = find.descendant(
      of: find.byType(SearchBar),
      matching: find.byType(Material),
    );
    Material material = tester.widget<Material>(searchBarMaterial);

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.elevation, hoveredElevation);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();

    material = tester.widget<Material>(searchBarMaterial);
    expect(material.elevation, pressedElevation);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.elevation, focusedElevation);
  });

  testWidgetsWithLeakTracking('SearchBar respects backgroundColor property', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              backgroundColor: MaterialStateProperty.resolveWith<Color>(_getColor),
            ),
          ),
        ),
      ),
    );

    final Finder searchBarMaterial = find.descendant(
      of: find.byType(SearchBar),
      matching: find.byType(Material),
    );
    Material material = tester.widget<Material>(searchBarMaterial);

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.color, hoveredColor);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();

    material = tester.widget<Material>(searchBarMaterial);
    expect(material.color, pressedColor);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.color, focusedColor);
  });

  testWidgetsWithLeakTracking('SearchBar respects shadowColor property', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              shadowColor: MaterialStateProperty.resolveWith<Color>(_getColor),
            ),
          ),
        ),
      ),
    );

    final Finder searchBarMaterial = find.descendant(
      of: find.byType(SearchBar),
      matching: find.byType(Material),
    );
    Material material = tester.widget<Material>(searchBarMaterial);

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.shadowColor, hoveredColor);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();

    material = tester.widget<Material>(searchBarMaterial);
    expect(material.shadowColor, pressedColor);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.shadowColor, focusedColor);
  });

  testWidgetsWithLeakTracking('SearchBar respects surfaceTintColor property', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              surfaceTintColor: MaterialStateProperty.resolveWith<Color>(_getColor),
            ),
          ),
        ),
      ),
    );

    final Finder searchBarMaterial = find.descendant(
      of: find.byType(SearchBar),
      matching: find.byType(Material),
    );
    Material material = tester.widget<Material>(searchBarMaterial);

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.surfaceTintColor, hoveredColor);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();

    material = tester.widget<Material>(searchBarMaterial);
    expect(material.surfaceTintColor, pressedColor);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.surfaceTintColor, focusedColor);
  });

  testWidgetsWithLeakTracking('SearchBar respects overlayColor property', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode();
    addTearDown(focusNode.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              focusNode: focusNode,
              overlayColor: MaterialStateProperty.resolveWith<Color>(_getColor),
            ),
          ),
        ),
      ),
    );

    RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pumpAndSettle();
    expect(inkFeatures, paints..rect(color: hoveredColor.withOpacity(1.0)));

    // On pressed.
    await tester.pumpAndSettle();
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();
    inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
    expect(inkFeatures, paints..rect()..rect(color: pressedColor.withOpacity(1.0)));

    // On focused.
    await tester.pumpAndSettle();
    await gesture.up();
    await tester.pumpAndSettle();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
    expect(inkFeatures, paints..rect()..rect(color: focusedColor.withOpacity(1.0)));
  });

  testWidgetsWithLeakTracking('SearchBar respects side and shape properties', (WidgetTester tester) async {
    const BorderSide pressedSide = BorderSide(width: 2.0);
    const BorderSide hoveredSide = BorderSide(width: 3.0);
    const BorderSide focusedSide = BorderSide(width: 4.0);
    const BorderSide defaultSide = BorderSide(width: 5.0);

    const OutlinedBorder pressedShape = RoundedRectangleBorder();
    const OutlinedBorder hoveredShape = ContinuousRectangleBorder();
    const OutlinedBorder focusedShape = CircleBorder();
    const OutlinedBorder defaultShape = StadiumBorder();
    BorderSide getSide(Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return pressedSide;
      }
      if (states.contains(MaterialState.hovered)) {
        return hoveredSide;
      }
      if (states.contains(MaterialState.focused)) {
        return focusedSide;
      }
      return defaultSide;
    }
    OutlinedBorder getShape(Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return pressedShape;
      }
      if (states.contains(MaterialState.hovered)) {
        return hoveredShape;
      }
      if (states.contains(MaterialState.focused)) {
        return focusedShape;
      }
      return defaultShape;
    }
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              side: MaterialStateProperty.resolveWith<BorderSide>(getSide),
              shape: MaterialStateProperty.resolveWith<OutlinedBorder>(getShape),
            ),
          ),
        ),
      ),
    );

    final Finder searchBarMaterial = find.descendant(
      of: find.byType(SearchBar),
      matching: find.byType(Material),
    );
    Material material = tester.widget<Material>(searchBarMaterial);

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.shape, hoveredShape.copyWith(side: hoveredSide));

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();

    material = tester.widget<Material>(searchBarMaterial);
    expect(material.shape, pressedShape.copyWith(side: pressedSide));

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    material = tester.widget<Material>(searchBarMaterial);
    expect(material.shape, focusedShape.copyWith(side: focusedSide));
  });

  testWidgetsWithLeakTracking('SearchBar respects padding property', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              leading: Icon(Icons.search),
              padding: MaterialStatePropertyAll<EdgeInsets>(EdgeInsets.all(16.0)),
              trailing: <Widget>[
                Icon(Icons.menu),
              ]
            ),
          ),
        ),
      ),
    );

    final Rect barRect = tester.getRect(find.byType(SearchBar));
    final Rect leadingRect = tester.getRect(find.byIcon(Icons.search));
    final Rect textFieldRect = tester.getRect(find.byType(TextField));
    final Rect trailingRect = tester.getRect(find.byIcon(Icons.menu));

    expect(barRect.left, leadingRect.left - 16.0);
    expect(leadingRect.right, textFieldRect.left - 16.0);
    expect(textFieldRect.right, trailingRect.left - 16.0);
    expect(trailingRect.right, barRect.right - 16.0);
  });

  testWidgetsWithLeakTracking('SearchBar respects hintStyle property', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              hintText: 'hint text',
              hintStyle: MaterialStateProperty.resolveWith<TextStyle?>(_getTextStyle),
            ),
          ),
        ),
      ),
    );

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    Text helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, hoveredColor);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();
    helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, pressedColor);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, focusedColor);
  });

  testWidgetsWithLeakTracking('SearchBar respects textStyle property', (WidgetTester tester) async {
    final TextEditingController controller = TextEditingController(text: 'input text');
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              controller: controller,
              textStyle: MaterialStateProperty.resolveWith<TextStyle?>(_getTextStyle),
            ),
          ),
        ),
      ),
    );

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    EditableText inputText = tester.widget(find.text('input text'));
    expect(inputText.style.color, hoveredColor);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();
    inputText = tester.widget(find.text('input text'));
    expect(inputText.style.color, pressedColor);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    inputText = tester.widget(find.text('input text'));
    expect(inputText.style.color, focusedColor);
  });

  testWidgetsWithLeakTracking('SearchBar respects textCapitalization property', (WidgetTester tester) async {
    Widget buildSearchBar(TextCapitalization textCapitalization) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              textCapitalization: textCapitalization,
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchBar(TextCapitalization.characters));
    await tester.pump();
    TextField textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.characters);

    await tester.pumpWidget(buildSearchBar(TextCapitalization.sentences));
    await tester.pump();
    textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.sentences);

    await tester.pumpWidget(buildSearchBar(TextCapitalization.words));
    await tester.pump();
    textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.words);

    await tester.pumpWidget(buildSearchBar(TextCapitalization.none));
    await tester.pump();
    textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.none);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects textCapitalization property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TextCapitalization textCapitalization) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchAnchor(
              textCapitalization: textCapitalization,
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.ac_unit),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchAnchor(TextCapitalization.characters));
    await tester.pump();
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();
    TextField textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.characters);
    await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
    await tester.pump();

    await tester.pumpWidget(buildSearchAnchor(TextCapitalization.none));
    await tester.pump();
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();
    textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.none);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewOnChanged and viewOnSubmitted properties', (WidgetTester tester) async {
    final SearchController controller = SearchController();
    addTearDown(controller.dispose);
    int onChangedCalled = 0;
    int onSubmittedCalled = 0;
    await tester.pumpWidget(MaterialApp(
      home: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            return Center(
              child: Material(
                child: SearchAnchor(
                  searchController: controller,
                  viewOnChanged: (String value) {
                    setState(() {
                      onChangedCalled = onChangedCalled + 1;
                    });
                  },
                  viewOnSubmitted: (String value) {
                    setState(() {
                      onSubmittedCalled = onSubmittedCalled + 1;
                    });
                    controller.closeView(value);
                  },
                  builder: (BuildContext context, SearchController controller) {
                    return SearchBar(
                      onTap: () {
                        if (!controller.isOpen) {
                          controller.openView();
                        }
                      },
                    );
                  },
                  suggestionsBuilder: (BuildContext context, SearchController controller) {
                    return <Widget>[];
                  },
                ),
              ),
            );
          }
      ),
    ));
    await tester.tap(find.byType(SearchBar)); // Open search view.
    await tester.pumpAndSettle();
    expect(controller.isOpen, true);

    final Finder barOnView = find.descendant(
        of: findViewContent(),
        matching: find.byType(TextField)
    );
    await tester.enterText(barOnView, 'a');
    expect(onChangedCalled, 1);
    await tester.enterText(barOnView, 'abc');
    expect(onChangedCalled, 2);

    await tester.testTextInput.receiveAction(TextInputAction.done);
    expect(onSubmittedCalled, 1);
    expect(controller.isOpen, false);
  });

  testWidgetsWithLeakTracking('SearchAnchor.bar respects textCapitalization property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TextCapitalization textCapitalization) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchAnchor.bar(
              textCapitalization: textCapitalization,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchAnchor(TextCapitalization.characters));
    await tester.pump();
    await tester.tap(find.byType(SearchBar)); // Open search view.
    await tester.pumpAndSettle();
    final Finder textFieldFinder = find.descendant(of: findViewContent(), matching: find.byType(TextField));
    final TextField textFieldInView = tester.widget<TextField>(textFieldFinder);
    expect(textFieldInView.textCapitalization, TextCapitalization.characters);
    // Close search view.
    await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
    await tester.pumpAndSettle();
    final TextField textField = tester.widget(find.byType(TextField));
    expect(textField.textCapitalization, TextCapitalization.characters);
  });

  testWidgetsWithLeakTracking('SearchAnchor.bar respects onChanged and onSubmitted properties', (WidgetTester tester) async {
    final SearchController controller = SearchController();
    addTearDown(controller.dispose);
    int onChangedCalled = 0;
    int onSubmittedCalled = 0;
    await tester.pumpWidget(MaterialApp(
      home: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return Center(
            child: Material(
              child: SearchAnchor.bar(
                searchController: controller,
                onSubmitted: (String value) {
                  setState(() {
                    onSubmittedCalled = onSubmittedCalled + 1;
                  });
                  controller.closeView(value);
                },
                onChanged: (String value) {
                  setState(() {
                    onChangedCalled = onChangedCalled + 1;
                  });
                },
                suggestionsBuilder: (BuildContext context, SearchController controller) {
                  return <Widget>[];
                },
              ),
            ),
          );
        }
      ),
    ));
    await tester.tap(find.byType(SearchBar)); // Open search view.
    await tester.pumpAndSettle();
    expect(controller.isOpen, true);

    final Finder barOnView = find.descendant(
      of: findViewContent(),
      matching: find.byType(TextField)
    );
    await tester.enterText(barOnView, 'a');
    expect(onChangedCalled, 1);
    await tester.enterText(barOnView, 'abc');
    expect(onChangedCalled, 2);

    await tester.testTextInput.receiveAction(TextInputAction.done);
    expect(onSubmittedCalled, 1);
    expect(controller.isOpen, false);

    await tester.testTextInput.receiveAction(TextInputAction.done);
    expect(onSubmittedCalled, 2);
  });

  testWidgetsWithLeakTracking('hintStyle can override textStyle for hintText', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              hintText: 'hint text',
              hintStyle: MaterialStateProperty.resolveWith<TextStyle?>(_getTextStyle),
              textStyle: const MaterialStatePropertyAll<TextStyle>(TextStyle(color: Colors.pink)),
            ),
          ),
        ),
      ),
    );

    // On hovered.
    final TestGesture gesture = await _pointGestureToSearchBar(tester);
    await tester.pump();
    Text helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, hoveredColor);

    // On pressed.
    await gesture.down(tester.getCenter(find.byType(SearchBar)));
    await tester.pumpAndSettle();
    helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, pressedColor);

    // On focused.
    await gesture.up();
    await tester.pump();
    // Remove the pointer so we are no longer hovering.
    await gesture.removePointer();
    await tester.pump();
    helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, focusedColor);
  });

  // Regression test for https://github.com/flutter/flutter/issues/127092.
  testWidgetsWithLeakTracking('The text is still centered when SearchBar text field is smaller than 48', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: const Center(
          child: Material(
            child: SearchBar(
              constraints: BoxConstraints.tightFor(height: 35.0),
            ),
          ),
        ),
      ),
    );

    await tester.enterText(find.byType(TextField), 'input text');
    final Finder textContent = find.text('input text');
    final double textCenterY = tester.getCenter(textContent).dy;
    final Finder searchBar = find.byType(SearchBar);
    final double searchBarCenterY = tester.getCenter(searchBar).dy;
    expect(textCenterY, searchBarCenterY);
  });

  testWidgetsWithLeakTracking('The search view defaults', (WidgetTester tester) async {
    final ThemeData theme = ThemeData(useMaterial3: true);
    final ColorScheme colorScheme = theme.colorScheme;
    await tester.pumpWidget(
      MaterialApp(
        theme: theme,
        home: Scaffold(
          body: Material(
            child: Align(
              alignment: Alignment.topLeft,
              child: SearchAnchor(
                viewHintText: 'hint text',
                builder: (BuildContext context, SearchController controller) {
                  return const Icon(Icons.search);
                },
                suggestionsBuilder: (BuildContext context, SearchController controller) {
                  return <Widget>[];
                },
              ),
            ),
          ),
        ),
      ),
    );

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();
    final Material material = getSearchViewMaterial(tester);
    expect(material.elevation, 6.0);
    expect(material.color, colorScheme.surface);
    expect(material.surfaceTintColor, colorScheme.surfaceTint);
    expect(material.clipBehavior, Clip.antiAlias);

    final Finder findDivider = find.byType(Divider);
    final Container dividerContainer = tester.widget<Container>(find.descendant(of: findDivider, matching: find.byType(Container)).first);
    final BoxDecoration decoration = dividerContainer.decoration! as BoxDecoration;
    expect(decoration.border!.bottom.color, colorScheme.outline);

    // Default search view has a leading back button on the start of the header.
    expect(find.widgetWithIcon(IconButton, Icons.arrow_back), findsOneWidget);

    // Default search view has a trailing close button on the end of the header.
    // It is used to clear the input in the text field.
    expect(find.widgetWithIcon(IconButton, Icons.close), findsOneWidget);

    final Text helperText = tester.widget(find.text('hint text'));
    expect(helperText.style?.color, colorScheme.onSurfaceVariant);
    expect(helperText.style?.fontSize, 16.0);
    expect(helperText.style?.fontFamily, 'Roboto');
    expect(helperText.style?.fontWeight, FontWeight.w400);

    const String input = 'entered text';
    await tester.enterText(find.byType(SearchBar), input);
    final EditableText inputText = tester.widget(find.text(input));
    expect(inputText.style.color, colorScheme.onSurface);
    expect(inputText.style.fontSize, 16.0);
    expect(inputText.style.fontFamily, 'Roboto');
    expect(inputText.style.fontWeight, FontWeight.w400);
  });

  testWidgetsWithLeakTracking('The search view default size on different platforms', (WidgetTester tester) async {
    // The search view should be is full-screen on mobile platforms,
    // and have a size of (360, 2/3 screen height) on other platforms
    Widget buildSearchAnchor(TargetPlatform platform) {
      return MaterialApp(
        theme: ThemeData(platform: platform),
        home: Scaffold(
          body: SafeArea(
            child: Material(
              child: Align(
                alignment: Alignment.topLeft,
                child: SearchAnchor(
                  builder: (BuildContext context, SearchController controller) {
                    return const Icon(Icons.search);
                  },
                  suggestionsBuilder: (BuildContext context, SearchController controller) {
                    return <Widget>[];
                  },
                ),
              ),
            ),
          ),
        ),
      );
    }

    for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.android, TargetPlatform.fuchsia ]) {
      await tester.pumpWidget(Container());
      await tester.pumpWidget(buildSearchAnchor(platform));
      await tester.tap(find.byIcon(Icons.search));
      await tester.pumpAndSettle();
      final SizedBox sizedBox = tester.widget<SizedBox>(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
      expect(sizedBox.width, 800.0);
      expect(sizedBox.height, 600.0);
    }

    for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.linux, TargetPlatform.windows ]) {
      await tester.pumpWidget(Container());
      await tester.pumpWidget(buildSearchAnchor(platform));
      await tester.tap(find.byIcon(Icons.search));
      await tester.pumpAndSettle();
      final SizedBox sizedBox = tester.widget<SizedBox>(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
      expect(sizedBox.width, 360.0);
      expect(sizedBox.height, 400.0);
    }
  });

  testWidgetsWithLeakTracking('SearchAnchor respects isFullScreen property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TargetPlatform platform) {
      return MaterialApp(
        theme: ThemeData(platform: platform),
        home: Scaffold(
          body: SafeArea(
            child: Material(
              child: Align(
                alignment: Alignment.topLeft,
                child: SearchAnchor(
                  isFullScreen: true,
                  builder: (BuildContext context, SearchController controller) {
                    return const Icon(Icons.search);
                  },
                  suggestionsBuilder: (BuildContext context, SearchController controller) {
                    return <Widget>[];
                  },
                ),
              ),
            ),
          ),
        ),
      );
    }

    for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.linux, TargetPlatform.windows ]) {
      await tester.pumpWidget(Container());
      await tester.pumpWidget(buildSearchAnchor(platform));
      await tester.tap(find.byIcon(Icons.search));
      await tester.pumpAndSettle();
      final SizedBox sizedBox = tester.widget<SizedBox>(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
      expect(sizedBox.width, 800.0);
      expect(sizedBox.height, 600.0);
    }
  });

  testWidgetsWithLeakTracking('SearchAnchor respects controller property', (WidgetTester tester) async {
    const String defaultText = 'initial text';
    final SearchController controller = SearchController();
    addTearDown(controller.dispose);
    controller.text = defaultText;

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchAnchor(
            searchController: controller,
            builder: (BuildContext context, SearchController controller) {
              return IconButton(icon: const Icon(Icons.search), onPressed: () {
                controller.openView();
              },);
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      ),
    );

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(controller.value.text, defaultText);
    expect(find.text(defaultText), findsOneWidget);

    const String updatedText = 'updated text';
    await tester.enterText(find.byType(SearchBar), updatedText);
    expect(controller.value.text, updatedText);
    expect(find.text(defaultText), findsNothing);
    expect(find.text(updatedText), findsOneWidget);
  });

  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}) {
      return MaterialApp(
        home: Material(
          child: SearchAnchor(
            viewBuilder: viewBuilder,
            builder: (BuildContext context, SearchController controller) {
              return IconButton(icon: const Icon(Icons.search), onPressed: () {
                controller.openView();
              },);
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      );
    }

    await tester.pumpWidget(buildAnchor());
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    // Default is a ListView.
    expect(find.byType(ListView), findsOneWidget);

    await tester.pumpWidget(Container());
    await tester.pumpWidget(buildAnchor(viewBuilder: (Iterable<Widget> suggestions)
      => GridView.count(crossAxisCount: 5, children: suggestions.toList(),)
    ));
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(find.byType(ListView), findsNothing);
    expect(find.byType(GridView), findsOneWidget);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewLeading property', (WidgetTester tester) async {
    Widget buildAnchor({Widget? viewLeading}) {
      return MaterialApp(
        home: Material(
          child: SearchAnchor(
            viewLeading: viewLeading,
            builder: (BuildContext context, SearchController controller) {
              return IconButton(icon: const Icon(Icons.search), onPressed: () {
                controller.openView();
              },);
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      );
    }

    await tester.pumpWidget(buildAnchor());
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    // Default is a icon button with arrow_back.
    expect(find.widgetWithIcon(IconButton, Icons.arrow_back), findsOneWidget);

    await tester.pumpWidget(Container());
    await tester.pumpWidget(buildAnchor(viewLeading: const Icon(Icons.history)));
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(find.byIcon(Icons.arrow_back), findsNothing);
    expect(find.byIcon(Icons.history), findsOneWidget);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewTrailing property', (WidgetTester tester) async {
    Widget buildAnchor({Iterable<Widget>? viewTrailing}) {
      return MaterialApp(
        home: Material(
          child: SearchAnchor(
            viewTrailing: viewTrailing,
            builder: (BuildContext context, SearchController controller) {
              return IconButton(icon: const Icon(Icons.search), onPressed: () {
                controller.openView();
              },);
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      );
    }

    await tester.pumpWidget(buildAnchor());
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    // Default is a icon button with close icon.
    expect(find.widgetWithIcon(IconButton, Icons.close), findsOneWidget);

    await tester.pumpWidget(Container());
    await tester.pumpWidget(buildAnchor(viewTrailing: <Widget>[const Icon(Icons.history)]));
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(find.byIcon(Icons.close), findsNothing);
    expect(find.byIcon(Icons.history), findsOneWidget);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewHintText property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          viewHintText: 'hint text',
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));
    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(find.text('hint text'), findsOneWidget);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewBackgroundColor property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          viewBackgroundColor: Colors.purple,
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(getSearchViewMaterial(tester).color, Colors.purple);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewElevation property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          viewElevation: 3.0,
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(getSearchViewMaterial(tester).elevation, 3.0);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewSurfaceTint property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          viewSurfaceTintColor: Colors.purple,
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(getSearchViewMaterial(tester).surfaceTintColor, Colors.purple);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewSide property', (WidgetTester tester) async {
    const BorderSide side = BorderSide(color: Colors.purple, width: 5.0);
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          isFullScreen: false,
          viewSide: side,
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(getSearchViewMaterial(tester).shape, RoundedRectangleBorder(side: side, borderRadius: BorderRadius.circular(28.0)));
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewShape property', (WidgetTester tester) async {
    const BorderSide side = BorderSide(color: Colors.purple, width: 5.0);
    const OutlinedBorder shape = StadiumBorder(side: side);

    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          isFullScreen: false,
          viewShape: shape,
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    expect(getSearchViewMaterial(tester).shape, shape);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects headerTextStyle property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          headerTextStyle: theme.textTheme.bodyLarge?.copyWith(color: Colors.red),
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();
    await tester.enterText(find.byType(SearchBar), 'input text');
    await tester.pumpAndSettle();

    final EditableText inputText = tester.widget(find.text('input text'));
    expect(inputText.style.color, Colors.red);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects headerHintStyle property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          viewHintText: 'hint text',
          headerHintStyle: theme.textTheme.bodyLarge?.copyWith(color: Colors.orange),
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();

    final Text inputText = tester.widget(find.text('hint text'));
    expect(inputText.style?.color, Colors.orange);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects dividerColor property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: SearchAnchor(
          dividerColor: Colors.red,
          builder: (BuildContext context, SearchController controller) {
            return IconButton(icon: const Icon(Icons.search), onPressed: () {
              controller.openView();
            },);
          },
          suggestionsBuilder: (BuildContext context, SearchController controller) {
            return <Widget>[];
          },
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();

    final Finder findDivider = find.byType(Divider);
    final Container dividerContainer = tester.widget<Container>(find.descendant(of: findDivider, matching: find.byType(Container)).first);
    final BoxDecoration decoration = dividerContainer.decoration! as BoxDecoration;
    expect(decoration.border!.bottom.color, Colors.red);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects viewConstraints property', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Center(
          child: SearchAnchor(
            isFullScreen: false,
            viewConstraints: BoxConstraints.tight(const Size(280.0, 390.0)),
            builder: (BuildContext context, SearchController controller) {
              return IconButton(icon: const Icon(Icons.search), onPressed: () {
                controller.openView();
              },);
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      ),
    ));

    await tester.tap(find.widgetWithIcon(IconButton, Icons.search));
    await tester.pumpAndSettle();

    final SizedBox sizedBox = tester.widget<SizedBox>(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(sizedBox.width, 280.0);
    expect(sizedBox.height, 390.0);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects builder property - LTR', (WidgetTester tester) async {
    Widget buildAnchor({required SearchAnchorChildBuilder builder}) {
      return MaterialApp(
        home: Material(
          child: Align(
            alignment: Alignment.topCenter,
            child: SearchAnchor(
              isFullScreen: false,
              builder: builder,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildAnchor(
      builder: (BuildContext context, SearchController controller)
        => const Icon(Icons.search)
    ));
    final Rect anchorRect = tester.getRect(find.byIcon(Icons.search));
    expect(anchorRect.size, const Size(24.0, 24.0));
    expect(anchorRect, equals(const Rect.fromLTRB(388.0, 0.0, 412.0, 24.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect, equals(const Rect.fromLTRB(388.0, 0.0, 748.0, 400.0)));

    // Search view top left should be the same as the anchor top left
    expect(searchViewRect.topLeft, anchorRect.topLeft);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects builder property - RTL', (WidgetTester tester) async {
    Widget buildAnchor({required SearchAnchorChildBuilder builder}) {
      return MaterialApp(
        home: Directionality(
          textDirection: TextDirection.rtl,
          child: Material(
            child: Align(
              alignment: Alignment.topCenter,
              child: SearchAnchor(
                isFullScreen: false,
                builder: builder,
                suggestionsBuilder: (BuildContext context, SearchController controller) {
                  return <Widget>[];
                },
              ),
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildAnchor(builder: (BuildContext context, SearchController controller)
    => const Icon(Icons.search)));
    final Rect anchorRect = tester.getRect(find.byIcon(Icons.search));
    expect(anchorRect.size, const Size(24.0, 24.0));
    expect(anchorRect, equals(const Rect.fromLTRB(388.0, 0.0, 412.0, 24.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect, equals(const Rect.fromLTRB(52.0, 0.0, 412.0, 400.0)));

    // Search view top right should be the same as the anchor top right
    expect(searchViewRect.topRight, anchorRect.topRight);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects suggestionsBuilder property', (WidgetTester tester) async {
    final SearchController controller = SearchController();
    addTearDown(controller.dispose);
    const String suggestion = 'suggestion text';

    await tester.pumpWidget(MaterialApp(
      home: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return Material(
            child: Align(
              alignment: Alignment.topCenter,
              child: SearchAnchor(
                searchController: controller,
                builder: (BuildContext context, SearchController controller) {
                  return const Icon(Icons.search);
                },
                suggestionsBuilder: (BuildContext context, SearchController controller) {
                  return <Widget>[
                    ListTile(
                      title: const Text(suggestion),
                      onTap: () {
                        setState(() {
                          controller.closeView(suggestion);
                        });
                    }),
                  ];
                },
              ),
            ),
          );
        }
      ),
    ));
    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Finder listTile = find.widgetWithText(ListTile, suggestion);
    expect(listTile, findsOneWidget);
    await tester.tap(listTile);
    await tester.pumpAndSettle();

    expect(controller.isOpen, false);
    expect(controller.value.text, suggestion);
  });

  testWidgetsWithLeakTracking('SearchAnchor should update suggestions on changes to search controller', (WidgetTester tester) async {
    final SearchController controller = SearchController();
    const List<String> suggestions = <String>['foo','far','bim'];
    addTearDown(controller.dispose);

    await tester.pumpWidget(MaterialApp(
      home: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return Material(
            child: Align(
              alignment: Alignment.topCenter,
              child: SearchAnchor(
                searchController: controller,
                builder: (BuildContext context, SearchController controller) {
                  return const Icon(Icons.search);
                },
                suggestionsBuilder: (BuildContext context, SearchController controller) {
                  final String searchText = controller.text.toLowerCase();
                  if (searchText.isEmpty) {
                    return const <Widget>[
                      Center(
                        child: Text('No Search'),
                      ),
                    ];
                  }
                  final Iterable<String> filterSuggestions = suggestions.where(
                    (String suggestion) => suggestion.toLowerCase().contains(searchText),
                  );
                  return filterSuggestions.map((String suggestion) {
                    return ListTile(
                      title: Text(suggestion),
                      trailing: IconButton(
                        icon: const Icon(Icons.call_missed),
                        onPressed: () {
                          controller.text = suggestion;
                        },
                      ),
                      onTap: () {
                        controller.closeView(suggestion);
                      },
                    );
                  }).toList();
                },
              ),
            ),
          );
        }
      ),
    ));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Finder listTile1 = find.widgetWithText(ListTile, 'foo');
    final Finder listTile2 = find.widgetWithText(ListTile, 'far');
    final Finder listTile3 = find.widgetWithText(ListTile, 'bim');
    final Finder textWidget = find.widgetWithText(Center, 'No Search');
    final Finder iconInListTile1 = find.descendant(of: listTile1, matching: find.byIcon(Icons.call_missed));

    expect(textWidget,findsOneWidget);
    expect(listTile1, findsNothing);
    expect(listTile2, findsNothing);
    expect(listTile3, findsNothing);

    await tester.enterText(find.byType(SearchBar), 'f');
    await tester.pumpAndSettle();
    expect(textWidget,findsNothing);
    expect(listTile1, findsOneWidget);
    expect(listTile2, findsOneWidget);
    expect(listTile3, findsNothing);

    await tester.tap(iconInListTile1);
    await tester.pumpAndSettle();
    expect(controller.value.text, 'foo');
    expect(textWidget,findsNothing);
    expect(listTile1, findsOneWidget);
    expect(listTile2, findsNothing);
    expect(listTile3, findsNothing);

    await tester.tap(listTile1);
    await tester.pumpAndSettle();
    expect(controller.isOpen, false);
    expect(controller.value.text, 'foo');
    expect(textWidget,findsNothing);
    expect(listTile1, findsNothing);
    expect(listTile2, findsNothing);
    expect(listTile3, findsNothing);
  });

  testWidgetsWithLeakTracking('SearchAnchor suggestionsBuilder property could be async', (WidgetTester tester) async {
    final SearchController controller = SearchController();
    addTearDown(controller.dispose);
    const String suggestion = 'suggestion text';

    await tester.pumpWidget(MaterialApp(
      home: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return Material(
            child: Align(
              alignment: Alignment.topCenter,
              child: SearchAnchor(
                searchController: controller,
                builder: (BuildContext context, SearchController controller) {
                  return const Icon(Icons.search);
                },
                suggestionsBuilder: (BuildContext context, SearchController controller) async {
                  return <Widget>[
                    ListTile(
                      title: const Text(suggestion),
                      onTap: () {
                        setState(() {
                          controller.closeView(suggestion);
                        });
                      },
                    ),
                  ];
                },
              ),
            ),
          );
        },
      ),
    ));
    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Finder text = find.text(suggestion);
    expect(text, findsOneWidget);
    await tester.tap(text);
    await tester.pumpAndSettle();

    expect(controller.isOpen, false);
    expect(controller.value.text, suggestion);
  });

  testWidgetsWithLeakTracking('SearchAnchor.bar has a default search bar as the anchor', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Align(
          alignment: Alignment.topLeft,
          child: SearchAnchor.bar(
            isFullScreen: false,
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      ),),
    );

    expect(find.byType(SearchBar), findsOneWidget);
    final Rect anchorRect = tester.getRect(find.byType(SearchBar));
    expect(anchorRect.size, const Size(800.0, 56.0));
    expect(anchorRect, equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 400.0)));

    // Search view has same width with the default anchor(search bar).
    expect(searchViewRect.width, anchorRect.width);
  });

  testWidgetsWithLeakTracking('SearchController can open/close view', (WidgetTester tester) async {
    final SearchController controller = SearchController();
    addTearDown(controller.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchAnchor.bar(
            searchController: controller,
            isFullScreen: false,
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[
                ListTile(
                  title: const Text('item 0'),
                  onTap: () {
                    controller.closeView('item 0');
                  },
                )
              ];
            },
          ),
        ),
      ),
    );

    expect(controller.isOpen, false);
    await tester.tap(find.byType(SearchBar));
    await tester.pumpAndSettle();

    expect(controller.isOpen, true);
    await tester.tap(find.widgetWithText(ListTile, 'item 0'));
    await tester.pumpAndSettle();
    expect(controller.isOpen, false);
    controller.openView();
    expect(controller.isOpen, true);
  });

  testWidgetsWithLeakTracking('Search view does not go off the screen - LTR', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Align(
            // Put the search anchor on the bottom-right corner of the screen to test
            // if the search view goes off the window.
            alignment: Alignment.bottomRight,
            child: SearchAnchor(
              isFullScreen: false,
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.search),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      ),
    );

    final Finder findIconButton = find.widgetWithIcon(IconButton, Icons.search);
    final Rect iconButton = tester.getRect(findIconButton);
    // Icon button has a size of (48.0, 48.0) and the screen size is (800.0, 600.0).
    expect(iconButton, equals(const Rect.fromLTRB(752.0, 552.0, 800.0, 600.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect, equals(const Rect.fromLTRB(440.0, 200.0, 800.0, 600.0)));
  });

  testWidgetsWithLeakTracking('Search view does not go off the screen - RTL', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Directionality(
          textDirection: TextDirection.rtl,
          child: Material(
            child: Align(
              // Put the search anchor on the bottom-left corner of the screen to test
              // if the search view goes off the window when the text direction is right-to-left.
              alignment: Alignment.bottomLeft,
              child: SearchAnchor(
                isFullScreen: false,
                builder: (BuildContext context, SearchController controller) {
                  return IconButton(
                    icon: const Icon(Icons.search),
                    onPressed: () {
                      controller.openView();
                    },
                  );
                },
                suggestionsBuilder: (BuildContext context, SearchController controller) {
                  return <Widget>[];
                },
              ),
            ),
          ),
        ),
      ),
    );

    final Finder findIconButton = find.widgetWithIcon(IconButton, Icons.search);
    final Rect iconButton = tester.getRect(findIconButton);
    expect(iconButton, equals(const Rect.fromLTRB(0.0, 552.0, 48.0, 600.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 200.0, 360.0, 600.0)));
  });

  testWidgetsWithLeakTracking('Search view becomes smaller if the window size is smaller than the view size', (WidgetTester tester) async {
    addTearDown(tester.view.reset);
    tester.view.physicalSize = const Size(200.0, 200.0);
    tester.view.devicePixelRatio = 1.0;

    Widget buildSearchAnchor({TextDirection textDirection = TextDirection.ltr}) {
      return MaterialApp(
        home: Directionality(
          textDirection: textDirection,
          child: Material(
            child: SearchAnchor(
              isFullScreen: false,
              builder: (BuildContext context, SearchController controller) {
                return Align(
                  alignment: Alignment.bottomRight,
                  child: IconButton(
                    icon: const Icon(Icons.search),
                    onPressed: () {
                      controller.openView();
                    },
                  ),
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }

    // Test LTR text direction.
    await tester.pumpWidget(buildSearchAnchor());

    final Finder findIconButton = find.widgetWithIcon(IconButton, Icons.search);
    final Rect iconButton = tester.getRect(findIconButton);
    // The icon button size is (48.0, 48.0), and the screen size is (200.0, 200.0)
    expect(iconButton, equals(const Rect.fromLTRB(152.0, 152.0, 200.0, 200.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0)));

    // Test RTL text direction.
    await tester.pumpWidget(Container());
    await tester.pumpWidget(buildSearchAnchor(textDirection: TextDirection.rtl));

    final Finder findIconButtonRTL = find.widgetWithIcon(IconButton, Icons.search);
    final Rect iconButtonRTL = tester.getRect(findIconButtonRTL);
    // The icon button size is (48.0, 48.0), and the screen size is (200.0, 200.0)
    expect(iconButtonRTL, equals(const Rect.fromLTRB(152.0, 152.0, 200.0, 200.0)));

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRectRTL = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRectRTL, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0)));
  });

  testWidgetsWithLeakTracking('Docked search view route is popped if the window size changes', (WidgetTester tester) async {
    addTearDown(tester.view.reset);
    tester.view.physicalSize = const Size(500.0, 600.0);
    tester.view.devicePixelRatio = 1.0;

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchAnchor(
            isFullScreen: false,
            builder: (BuildContext context, SearchController controller) {
              return Align(
                alignment: Alignment.bottomRight,
                child: IconButton(
                  icon: const Icon(Icons.search),
                  onPressed: () {
                    controller.openView();
                  },
                ),
              );
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      ),
    );

    // Open the search view
    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();
    expect(find.byIcon(Icons.arrow_back), findsOneWidget);

    // Change window size
    tester.view.physicalSize = const Size(250.0, 200.0);
    tester.view.devicePixelRatio = 1.0;
    await tester.pumpAndSettle();
    expect(find.byIcon(Icons.arrow_back), findsNothing);
  });

  testWidgetsWithLeakTracking('Full-screen search view route should stay if the window size changes', (WidgetTester tester) async {
    addTearDown(tester.view.reset);
    tester.view.physicalSize = const Size(500.0, 600.0);
    tester.view.devicePixelRatio = 1.0;

    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: SearchAnchor(
            isFullScreen: true,
            builder: (BuildContext context, SearchController controller) {
              return Align(
                alignment: Alignment.bottomRight,
                child: IconButton(
                  icon: const Icon(Icons.search),
                  onPressed: () {
                    controller.openView();
                  },
                ),
              );
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      ),
    );

    // Open a full-screen search view
    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();
    expect(find.byIcon(Icons.arrow_back), findsOneWidget);

    // Change window size
    tester.view.physicalSize = const Size(250.0, 200.0);
    tester.view.devicePixelRatio = 1.0;
    await tester.pumpAndSettle();
    expect(find.byIcon(Icons.arrow_back), findsOneWidget);
  });

  testWidgetsWithLeakTracking('Search view route does not throw exception during pop animation', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/126590.
    await tester.pumpWidget(
      MaterialApp(
        home: Material(
          child: Center(
            child: SearchAnchor(
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.search),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return List<Widget>.generate(5, (int index) {
                  final String item = 'item $index';
                  return ListTile(
                    leading: const Icon(Icons.history),
                    title: Text(item),
                    trailing: const Icon(Icons.chevron_right),
                    onTap: () {},
                  );
                });
              }),
          ),
        ),
      ),
    );

    // Open search view
    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    // Pop search view route
    await tester.tap(find.byIcon(Icons.arrow_back));
    await tester.pumpAndSettle();

    // No exception.
  });

  testWidgetsWithLeakTracking('Docked search should position itself correctly based on closest navigator', (WidgetTester tester) async {
    const double rootSpacing = 100.0;

    await tester.pumpWidget(
      MaterialApp(
        builder: (BuildContext context, Widget? child) {
          return Scaffold(
            body: Padding(
              padding: const EdgeInsets.all(rootSpacing),
              child: child,
            ),
          );
        },
        home: Material(
          child: SearchAnchor(
            isFullScreen: false,
            builder: (BuildContext context, SearchController controller) {
              return IconButton(
                icon: const Icon(Icons.search),
                onPressed: () {
                  controller.openView();
                },
              );
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              return <Widget>[];
            },
          ),
        ),
      ),
    );

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect.topLeft, equals(const Offset(rootSpacing, rootSpacing)));
  });

  testWidgetsWithLeakTracking('Docked search view with nested navigator does not go off the screen', (WidgetTester tester) async {
    addTearDown(tester.view.reset);
    tester.view.physicalSize = const Size(400.0, 400.0);
    tester.view.devicePixelRatio = 1.0;

    const double rootSpacing = 100.0;

    await tester.pumpWidget(
      MaterialApp(
        builder: (BuildContext context, Widget? child) {
          return Scaffold(
            body: Padding(
              padding: const EdgeInsets.all(rootSpacing),
              child: child,
            ),
          );
        },
        home: Material(
          child: Align(
            alignment: Alignment.bottomRight,
            child: SearchAnchor(
              isFullScreen: false,
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.search),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      ),
    );

    await tester.tap(find.byIcon(Icons.search));
    await tester.pumpAndSettle();

    final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
    expect(searchViewRect.bottomRight, equals(const Offset(300.0, 300.0)));
  });

  // Regression tests for https://github.com/flutter/flutter/issues/128332
  group('SearchAnchor text selection', () {
    testWidgetsWithLeakTracking('can right-click to select word', (WidgetTester tester) async {
      const String defaultText = 'initial text';
      final SearchController controller = SearchController();
      addTearDown(controller.dispose);
      controller.text = defaultText;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: SearchAnchor.bar(
              searchController: controller,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );

      expect(controller.value.text, defaultText);
      expect(find.text(defaultText), findsOneWidget);

      final TestGesture gesture = await tester.startGesture(
        textOffsetToPosition(tester, 4) + const Offset(0.0, -9.0),
        kind: PointerDeviceKind.mouse,
        buttons: kSecondaryMouseButton,
      );
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle();
      expect(controller.value.selection, const TextSelection(baseOffset: 0, extentOffset: 7));
      await gesture.removePointer();
    }, variant: TargetPlatformVariant.only(TargetPlatform.macOS));

    testWidgetsWithLeakTracking('can click to set position', (WidgetTester tester) async {
      const String defaultText = 'initial text';
      final SearchController controller = SearchController();
      addTearDown(controller.dispose);
      controller.text = defaultText;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: SearchAnchor.bar(
              searchController: controller,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );

      expect(controller.value.text, defaultText);
      expect(find.text(defaultText), findsOneWidget);

      final TestGesture gesture = await _pointGestureToSearchBar(tester);
      await gesture.down(textOffsetToPosition(tester, 2) + const Offset(0.0, -9.0));
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle(kDoubleTapTimeout);
      expect(controller.value.selection, const TextSelection.collapsed(offset: 2));

      await gesture.down(textOffsetToPosition(tester, 9, index: 1) + const Offset(0.0, -9.0));
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle();
      expect(controller.value.selection, const TextSelection.collapsed(offset: 9));
      await gesture.removePointer();
    }, variant: TargetPlatformVariant.desktop());

    testWidgetsWithLeakTracking('can double-click to select word', (WidgetTester tester) async {
      const String defaultText = 'initial text';
      final SearchController controller = SearchController();
      addTearDown(controller.dispose);
      controller.text = defaultText;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: SearchAnchor.bar(
              searchController: controller,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );

      expect(controller.value.text, defaultText);
      expect(find.text(defaultText), findsOneWidget);

      final TestGesture gesture = await _pointGestureToSearchBar(tester);
      final Offset targetPosition = textOffsetToPosition(tester, 4) + const Offset(0.0, -9.0);
      await gesture.down(targetPosition);
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle(kDoubleTapTimeout);

      final Offset targetPositionAfterViewOpened = textOffsetToPosition(tester, 4, index: 1) + const Offset(0.0, -9.0);
      await gesture.down(targetPositionAfterViewOpened);
      await tester.pumpAndSettle();
      await gesture.up();
      await tester.pump();

      await gesture.down(targetPositionAfterViewOpened);
      await tester.pump();
      await gesture.up();
      await tester.pump();
      expect(controller.value.selection, const TextSelection(baseOffset: 0, extentOffset: 7));
      await gesture.removePointer();
    }, variant: TargetPlatformVariant.desktop());

    testWidgetsWithLeakTracking('can triple-click to select field', (WidgetTester tester) async {
      const String defaultText = 'initial text';
      final SearchController controller = SearchController();
      addTearDown(controller.dispose);
      controller.text = defaultText;

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: SearchAnchor.bar(
              searchController: controller,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );

      expect(controller.value.text, defaultText);
      expect(find.text(defaultText), findsOneWidget);

      final TestGesture gesture = await _pointGestureToSearchBar(tester);
      final Offset targetPosition = textOffsetToPosition(tester, 4) + const Offset(0.0, -9.0);
      await gesture.down(targetPosition);
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle(kDoubleTapTimeout);

      final Offset targetPositionAfterViewOpened = textOffsetToPosition(tester, 4, index: 1) + const Offset(0.0, -9.0);
      await gesture.down(targetPositionAfterViewOpened);
      await tester.pump();
      await gesture.up();
      await tester.pump();

      await gesture.down(targetPositionAfterViewOpened);
      await tester.pump();
      await gesture.up();
      await tester.pump();

      await gesture.down(targetPositionAfterViewOpened);
      await tester.pump();
      await gesture.up();
      await tester.pumpAndSettle();
      expect(controller.value.selection, const TextSelection(baseOffset: 0, extentOffset: 12));
      await gesture.removePointer();
    }, variant: TargetPlatformVariant.desktop());
  });

  // Regression tests for https://github.com/flutter/flutter/issues/126623
  group('Overall InputDecorationTheme does not impact SearchBar and SearchView', () {

    const InputDecorationTheme inputDecorationTheme = InputDecorationTheme(
      focusColor: Colors.green,
      hoverColor: Colors.blue,
      outlineBorder: BorderSide(color: Colors.pink, width: 10),
      isDense: true,
      contentPadding: EdgeInsets.symmetric(horizontal: 20),
      hintStyle: TextStyle(color: Colors.purpleAccent),
      fillColor: Colors.tealAccent,
      filled: true,
      isCollapsed: true,
      border: OutlineInputBorder(),
      focusedBorder: UnderlineInputBorder(),
      enabledBorder: UnderlineInputBorder(),
      errorBorder: UnderlineInputBorder(),
      focusedErrorBorder: UnderlineInputBorder(),
      disabledBorder: UnderlineInputBorder(),
      constraints: BoxConstraints(maxWidth: 300),
    );
    final ThemeData theme = ThemeData(
      useMaterial3: true,
      inputDecorationTheme: inputDecorationTheme
    );

    void checkDecorationInSearchBar(WidgetTester tester) {
      final Finder textField = findTextField();
      final InputDecoration? decoration = tester.widget<TextField>(textField).decoration;

      expect(decoration?.border, InputBorder.none);
      expect(decoration?.focusedBorder, InputBorder.none);
      expect(decoration?.enabledBorder, InputBorder.none);
      expect(decoration?.errorBorder, null);
      expect(decoration?.focusedErrorBorder, null);
      expect(decoration?.disabledBorder, null);
      expect(decoration?.constraints, null);
      expect(decoration?.isCollapsed, false);
      expect(decoration?.filled, false);
      expect(decoration?.fillColor, null);
      expect(decoration?.focusColor, null);
      expect(decoration?.hoverColor, null);
      expect(decoration?.contentPadding, EdgeInsets.zero);
      expect(decoration?.hintStyle?.color, theme.colorScheme.onSurfaceVariant);
    }

    testWidgetsWithLeakTracking('Overall InputDecorationTheme does not override text field style'
        ' in SearchBar', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          theme: theme,
          home: const Center(
            child: Material(
              child: SearchBar(hintText: 'hint text'),
            ),
          ),
        ),
      );

      // Check input decoration in `SearchBar`
      checkDecorationInSearchBar(tester);

      // Check search bar defaults.
      final Finder searchBarMaterial = find.descendant(
        of: find.byType(SearchBar),
        matching: find.byType(Material),
      );

      final Material material = tester.widget<Material>(searchBarMaterial);
      checkSearchBarDefaults(tester, theme.colorScheme, material);
    });

    testWidgetsWithLeakTracking('Overall InputDecorationTheme does not override text field style'
        ' in the search view route', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          theme: theme,
          home: Scaffold(
            body: Material(
              child: Align(
                alignment: Alignment.topLeft,
                child: SearchAnchor(
                  viewHintText: 'hint text',
                  builder: (BuildContext context, SearchController controller) {
                    return const Icon(Icons.search);
                  },
                  suggestionsBuilder: (BuildContext context, SearchController controller) {
                    return <Widget>[];
                  },
                ),
              ),
            ),
          ),
        ),
      );

      await tester.tap(find.byIcon(Icons.search));
      await tester.pumpAndSettle();

      // Check input decoration in `SearchBar`
      checkDecorationInSearchBar(tester);

      // Check search bar defaults in search view route.
      final Finder searchBarMaterial = find.descendant(
        of: find.descendant(of: findViewContent(), matching: find.byType(SearchBar)),
        matching: find.byType(Material),
      ).first;

      final Material material = tester.widget<Material>(searchBarMaterial);
      expect(material.color, Colors.transparent);
      expect(material.elevation, 0.0);
      final Text hintText = tester.widget(find.text('hint text'));
      expect(hintText.style?.color, theme.colorScheme.onSurfaceVariant);

      const String input = 'entered text';
      await tester.enterText(find.byType(SearchBar), input);
      final EditableText inputText = tester.widget(find.text(input));
      expect(inputText.style.color, theme.colorScheme.onSurface);
    });
  });

  testWidgetsWithLeakTracking('SearchAnchor view respects theme brightness', (WidgetTester tester) async {
    Widget buildSearchAnchor(ThemeData theme) {
      return MaterialApp(
        theme: theme,
        home: Center(
          child: Material(
            child: SearchAnchor(
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.ac_unit),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }

    ThemeData theme = ThemeData(brightness: Brightness.light);
    await tester.pumpWidget(buildSearchAnchor(theme));

    // Open the search view.
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();

    // Test the search view background color.
    Material material = getSearchViewMaterial(tester);
    expect(material.color, theme.colorScheme.surface);

    // Change the theme brightness.
    theme = ThemeData(brightness: Brightness.dark);
    await tester.pumpWidget(buildSearchAnchor(theme));
    await tester.pumpAndSettle();

    // Test the search view background color.
    material = getSearchViewMaterial(tester);
    expect(material.color, theme.colorScheme.surface);
  });

  testWidgetsWithLeakTracking('Search view widgets can inherit local themes', (WidgetTester tester) async {
    final ThemeData globalTheme = ThemeData(colorSchemeSeed: Colors.red);
    final ThemeData localTheme = ThemeData(
      colorSchemeSeed: Colors.green,
      iconButtonTheme: IconButtonThemeData(
        style: IconButton.styleFrom(
          backgroundColor: const Color(0xffffff00)
        ),
      ),
      cardTheme: const CardTheme(color: Color(0xff00ffff)),
    );
    Widget buildSearchAnchor() {
      return MaterialApp(
        theme: globalTheme,
        home: Center(
          child: Builder(
            builder: (BuildContext context) {
              return Theme(
                data: localTheme,
                child: Material(
                  child: SearchAnchor.bar(
                    suggestionsBuilder: (BuildContext context, SearchController controller) {
                      return <Widget>[
                        Card(
                          child: ListTile(
                            onTap: () {},
                            title: const Text('Item 1'),
                          ),
                        ),
                      ];
                    },
                  ),
                ),
              );
            }
          ),
        ),
      );
    }

    await tester.pumpWidget(buildSearchAnchor());

    // Open the search view.
    await tester.tap(find.byType(SearchBar));
    await tester.pumpAndSettle();

    // Test the search view background color.
    final Material searchViewMaterial = getSearchViewMaterial(tester);
    expect(searchViewMaterial.color, localTheme.colorScheme.surface);

    // Test the search view icons background color.
    final Material iconButtonMaterial = tester.widget<Material>(find.descendant(
      of: find.byType(IconButton),
      matching: find.byType(Material),
    ).first);
    expect(find.byWidget(iconButtonMaterial), findsOneWidget);
    expect(iconButtonMaterial.color, localTheme.iconButtonTheme.style?.backgroundColor?.resolve(<MaterialState>{}));

    // Test the suggestion card color.
    final Material suggestionMaterial = tester.widget<Material>(find.descendant(
      of: find.byType(Card),
      matching: find.byType(Material),
    ).first);
    expect(suggestionMaterial.color, localTheme.cardTheme.color);
  });

  testWidgetsWithLeakTracking('SearchBar respects keyboardType property', (WidgetTester tester) async {
    Widget buildSearchBar(TextInputType keyboardType) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              keyboardType: keyboardType,
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchBar(TextInputType.number));
    await tester.pump();
    TextField textField = tester.widget(find.byType(TextField));
    expect(textField.keyboardType, TextInputType.number);

    await tester.pumpWidget(buildSearchBar(TextInputType.phone));
    await tester.pump();
    textField = tester.widget(find.byType(TextField));
    expect(textField.keyboardType, TextInputType.phone);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects keyboardType property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TextInputType keyboardType) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchAnchor(
              keyboardType: keyboardType,
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.ac_unit),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchAnchor(TextInputType.number));
    await tester.pump();
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();
    TextField textField = tester.widget(find.byType(TextField));
    expect(textField.keyboardType, TextInputType.number);
    await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
    await tester.pump();

    await tester.pumpWidget(buildSearchAnchor(TextInputType.phone));
    await tester.pump();
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();
    textField = tester.widget(find.byType(TextField));
    expect(textField.keyboardType, TextInputType.phone);
  });

  testWidgetsWithLeakTracking('SearchAnchor.bar respects keyboardType property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TextInputType keyboardType) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchAnchor.bar(
              keyboardType: keyboardType,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchAnchor(TextInputType.number));
    await tester.pump();
    await tester.tap(find.byType(SearchBar)); // Open search view.
    await tester.pumpAndSettle();
    final Finder textFieldFinder = find.descendant(of: findViewContent(), matching: find.byType(TextField));
    final TextField textFieldInView = tester.widget<TextField>(textFieldFinder);
    expect(textFieldInView.keyboardType, TextInputType.number);
    // Close search view.
    await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
    await tester.pumpAndSettle();
    final TextField textField = tester.widget(find.byType(TextField));
    expect(textField.keyboardType, TextInputType.number);
  });

  testWidgetsWithLeakTracking('SearchBar respects textInputAction property', (WidgetTester tester) async {
    Widget buildSearchBar(TextInputAction textInputAction) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchBar(
              textInputAction: textInputAction,
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchBar(TextInputAction.previous));
    await tester.pump();
    TextField textField = tester.widget(find.byType(TextField));
    expect(textField.textInputAction, TextInputAction.previous);

    await tester.pumpWidget(buildSearchBar(TextInputAction.send));
    await tester.pump();
    textField = tester.widget(find.byType(TextField));
    expect(textField.textInputAction, TextInputAction.send);
  });

  testWidgetsWithLeakTracking('SearchAnchor respects textInputAction property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TextInputAction textInputAction) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchAnchor(
              textInputAction: textInputAction,
              builder: (BuildContext context, SearchController controller) {
                return IconButton(
                  icon: const Icon(Icons.ac_unit),
                  onPressed: () {
                    controller.openView();
                  },
                );
              },
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchAnchor(TextInputAction.previous));
    await tester.pump();
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();
    TextField textField = tester.widget(find.byType(TextField));
    expect(textField.textInputAction, TextInputAction.previous);
    await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
    await tester.pump();

    await tester.pumpWidget(buildSearchAnchor(TextInputAction.send));
    await tester.pump();
    await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
    await tester.pumpAndSettle();
    textField = tester.widget(find.byType(TextField));
    expect(textField.textInputAction, TextInputAction.send);
  });

  testWidgetsWithLeakTracking('SearchAnchor.bar respects textInputAction property', (WidgetTester tester) async {
    Widget buildSearchAnchor(TextInputAction textInputAction) {
      return MaterialApp(
        home: Center(
          child: Material(
            child: SearchAnchor.bar(
              textInputAction: textInputAction,
              suggestionsBuilder: (BuildContext context, SearchController controller) {
                return <Widget>[];
              },
            ),
          ),
        ),
      );
    }
    await tester.pumpWidget(buildSearchAnchor(TextInputAction.previous));
    await tester.pump();
    await tester.tap(find.byType(SearchBar)); // Open search view.
    await tester.pumpAndSettle();
    final Finder textFieldFinder = find.descendant(of: findViewContent(), matching: find.byType(TextField));
    final TextField textFieldInView = tester.widget<TextField>(textFieldFinder);
    expect(textFieldInView.textInputAction, TextInputAction.previous);
    // Close search view.
    await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back));
    await tester.pumpAndSettle();
    final TextField textField = tester.widget(find.byType(TextField));
    expect(textField.textInputAction, TextInputAction.previous);
  });
}

Future<void> checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {
  expect(material.animationDuration, const Duration(milliseconds: 200));
  expect(material.borderOnForeground, true);
  expect(material.borderRadius, null);
  expect(material.clipBehavior, Clip.none);
  expect(material.color, colorScheme.surface);
  expect(material.elevation, 6.0);
  expect(material.shadowColor, colorScheme.shadow);
  expect(material.surfaceTintColor, colorScheme.surfaceTint);
  expect(material.shape, const StadiumBorder());

  final Text helperText = tester.widget(find.text('hint text'));
  expect(helperText.style?.color, colorScheme.onSurfaceVariant);
  expect(helperText.style?.fontSize, 16.0);
  expect(helperText.style?.fontFamily, 'Roboto');
  expect(helperText.style?.fontWeight, FontWeight.w400);

  const String input = 'entered text';
  await tester.enterText(find.byType(SearchBar), input);
  final EditableText inputText = tester.widget(find.text(input));
  expect(inputText.style.color, colorScheme.onSurface);
  expect(inputText.style.fontSize, 16.0);
  expect(helperText.style?.fontFamily, 'Roboto');
  expect(inputText.style.fontWeight, FontWeight.w400);
}

Finder findTextField() {
  return find.descendant(
    of: find.byType(SearchBar),
    matching: find.byType(TextField)
  );
}

TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
  final RichText iconRichText = tester.widget<RichText>(
    find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
  );
  return iconRichText.text.style;
}

const Color pressedColor = Colors.red;
const Color hoveredColor = Colors.orange;
const Color focusedColor = Colors.yellow;
const Color defaultColor = Colors.green;

Color _getColor(Set<MaterialState> states) {
  if (states.contains(MaterialState.pressed)) {
    return pressedColor;
  }
  if (states.contains(MaterialState.hovered)) {
    return hoveredColor;
  }
  if (states.contains(MaterialState.focused)) {
    return focusedColor;
  }
  return defaultColor;
}

final ThemeData theme = ThemeData();
final TextStyle? pressedStyle = theme.textTheme.bodyLarge?.copyWith(color: pressedColor);
final TextStyle? hoveredStyle = theme.textTheme.bodyLarge?.copyWith(color: hoveredColor);
final TextStyle? focusedStyle = theme.textTheme.bodyLarge?.copyWith(color: focusedColor);

TextStyle? _getTextStyle(Set<MaterialState> states) {
  if (states.contains(MaterialState.pressed)) {
    return pressedStyle;
  }
  if (states.contains(MaterialState.hovered)) {
    return hoveredStyle;
  }
  if (states.contains(MaterialState.focused)) {
    return focusedStyle;
  }
  return null;
}

Future<TestGesture> _pointGestureToSearchBar(WidgetTester tester) async {
  final Offset center = tester.getCenter(find.byType(SearchBar));
  final TestGesture gesture = await tester.createGesture(
    kind: PointerDeviceKind.mouse,
  );

  // On hovered.
  await gesture.addPointer();
  await gesture.moveTo(center);
  return gesture;
}
Finder findViewContent() {
  return find.byWidgetPredicate((Widget widget) {
    return widget.runtimeType.toString() == '_ViewContent';
  });
}

Material getSearchViewMaterial(WidgetTester tester) {
  return tester.widget<Material>(find.descendant(of: findViewContent(), matching: find.byType(Material)).first);
}